blob: 5422e5547fd8f81d80010d3b51f2b222830d5bdb [file] [log] [blame]
Lorenz Bruncb9f3d32023-07-27 15:21:49 +02001//go:build darwin
2
3package blockdev
4
5import (
6 "errors"
7 "fmt"
8 "math/bits"
9 "os"
10 "syscall"
11
12 "golang.org/x/sys/unix"
13)
14
15// TODO(lorenz): Upstream these to x/sys/unix.
16const (
17 DKIOCGETBLOCKSIZE = 0x40046418
18 DKIOCGETBLOCKCOUNT = 0x40086419
19)
20
21type Device struct {
22 backend *os.File
23 rawConn syscall.RawConn
24 blockSize int64
25 blockCount int64
26}
27
28func (d *Device) ReadAt(p []byte, off int64) (n int, err error) {
29 return d.backend.ReadAt(p, off)
30}
31
32func (d *Device) WriteAt(p []byte, off int64) (n int, err error) {
33 return d.backend.WriteAt(p, off)
34}
35
36func (d *Device) Close() error {
37 return d.backend.Close()
38}
39
40func (d *Device) BlockCount() int64 {
41 return d.blockCount
42}
43
44func (d *Device) BlockSize() int64 {
45 return d.blockSize
46}
47
48func (d *Device) Discard(startByte int64, endByte int64) error {
49 // Can be implemented using DKIOCUNMAP, but needs x/sys/unix extension.
50 // Not mandatory, so this is fine for now.
Lorenz Brun65b1c682023-09-14 15:49:39 +020051 return errors.ErrUnsupported
Lorenz Bruncb9f3d32023-07-27 15:21:49 +020052}
53
54func (d *Device) OptimalBlockSize() int64 {
55 return d.blockSize
56}
57
58func (d *Device) Zero(startByte int64, endByte int64) error {
59 // It doesn't look like MacOS even has any zeroing acceleration, so just
60 // use the generic one.
61 return GenericZero(d, startByte, endByte)
62}
63
64// Open opens a block device given a path to its inode.
65func Open(path string) (*Device, error) {
66 outFile, err := os.OpenFile(path, os.O_RDWR, 0640)
67 if err != nil {
68 return nil, fmt.Errorf("failed to open block device: %w", err)
69 }
70 return FromFileHandle(outFile)
71}
72
73// FromFileHandle creates a blockdev from a device handle. The device handle is
74// not duplicated, closing the returned Device will close it. If the handle is
75// not a block device, i.e does not implement block device ioctls, an error is
76// returned.
77func FromFileHandle(handle *os.File) (*Device, error) {
78 outFileC, err := handle.SyscallConn()
79 if err != nil {
80 return nil, fmt.Errorf("error getting SyscallConn: %w", err)
81 }
82 var blockSize int
83 outFileC.Control(func(fd uintptr) {
84 blockSize, err = unix.IoctlGetInt(int(fd), DKIOCGETBLOCKSIZE)
85 })
86 if errors.Is(err, unix.ENOTTY) || errors.Is(err, unix.EINVAL) {
87 return nil, ErrNotBlockDevice
88 } else if err != nil {
89 return nil, fmt.Errorf("when querying disk block size: %w", err)
90 }
91
92 var blockCount int
93 var getSizeErr error
94 outFileC.Control(func(fd uintptr) {
95 blockCount, getSizeErr = unix.IoctlGetInt(int(fd), DKIOCGETBLOCKCOUNT)
96 })
97
98 if getSizeErr != nil {
99 return nil, fmt.Errorf("when querying disk block count: %w", err)
100 }
101 return &Device{
102 backend: handle,
103 rawConn: outFileC,
104 blockSize: int64(blockSize),
105 blockCount: int64(blockCount),
106 }, nil
107}
108
109type File struct {
110 backend *os.File
111 rawConn syscall.RawConn
112 blockSize int64
113 blockCount int64
114}
115
116func CreateFile(name string, blockSize int64, blockCount int64) (*File, error) {
117 if blockSize < 512 {
118 return nil, fmt.Errorf("blockSize must be bigger than 512 bytes")
119 }
120 if bits.OnesCount64(uint64(blockSize)) != 1 {
121 return nil, fmt.Errorf("blockSize must be a power of two")
122 }
123 out, err := os.Create(name)
124 if err != nil {
125 return nil, fmt.Errorf("when creating backing file: %w", err)
126 }
127 rawConn, err := out.SyscallConn()
128 if err != nil {
129 return nil, fmt.Errorf("unable to get SyscallConn: %w", err)
130 }
131 return &File{
132 backend: out,
133 blockSize: blockSize,
134 rawConn: rawConn,
135 blockCount: blockCount,
136 }, nil
137}
138
139func (d *File) ReadAt(p []byte, off int64) (n int, err error) {
140 return d.backend.ReadAt(p, off)
141}
142
143func (d *File) WriteAt(p []byte, off int64) (n int, err error) {
144 return d.backend.WriteAt(p, off)
145}
146
147func (d *File) Close() error {
148 return d.backend.Close()
149}
150
151func (d *File) BlockCount() int64 {
152 return d.blockCount
153}
154
155func (d *File) BlockSize() int64 {
156 return d.blockSize
157}
158
159func (d *File) Discard(startByte int64, endByte int64) error {
160 // Can be supported in the future via fnctl.
Lorenz Brun65b1c682023-09-14 15:49:39 +0200161 return errors.ErrUnsupported
Lorenz Bruncb9f3d32023-07-27 15:21:49 +0200162}
163
164func (d *File) OptimalBlockSize() int64 {
165 return d.blockSize
166}
167
168func (d *File) Zero(startByte int64, endByte int64) error {
169 // Can possibly be accelerated in the future via fnctl.
170 return GenericZero(d, startByte, endByte)
171}