blob: 725c3a5727b7ddd48ed9eda87d74ea4a12b2ef79 [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
Jan Schära6da1712024-08-21 15:12:11 +020048func (d *Device) OptimalBlockSize() int64 {
49 return d.blockSize
50}
51
Lorenz Bruncb9f3d32023-07-27 15:21:49 +020052func (d *Device) Discard(startByte int64, endByte int64) error {
53 // Can be implemented using DKIOCUNMAP, but needs x/sys/unix extension.
54 // Not mandatory, so this is fine for now.
Lorenz Brun65b1c682023-09-14 15:49:39 +020055 return errors.ErrUnsupported
Lorenz Bruncb9f3d32023-07-27 15:21:49 +020056}
57
Lorenz Bruncb9f3d32023-07-27 15:21:49 +020058func (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
Jan Schära6da1712024-08-21 15:12:11 +020064func (d *Device) Sync() error {
65 return d.backend.Sync()
66}
67
Lorenz Bruncb9f3d32023-07-27 15:21:49 +020068// Open opens a block device given a path to its inode.
69func Open(path string) (*Device, error) {
70 outFile, err := os.OpenFile(path, os.O_RDWR, 0640)
71 if err != nil {
72 return nil, fmt.Errorf("failed to open block device: %w", err)
73 }
74 return FromFileHandle(outFile)
75}
76
77// FromFileHandle creates a blockdev from a device handle. The device handle is
78// not duplicated, closing the returned Device will close it. If the handle is
79// not a block device, i.e does not implement block device ioctls, an error is
80// returned.
81func FromFileHandle(handle *os.File) (*Device, error) {
82 outFileC, err := handle.SyscallConn()
83 if err != nil {
84 return nil, fmt.Errorf("error getting SyscallConn: %w", err)
85 }
86 var blockSize int
87 outFileC.Control(func(fd uintptr) {
88 blockSize, err = unix.IoctlGetInt(int(fd), DKIOCGETBLOCKSIZE)
89 })
90 if errors.Is(err, unix.ENOTTY) || errors.Is(err, unix.EINVAL) {
91 return nil, ErrNotBlockDevice
92 } else if err != nil {
93 return nil, fmt.Errorf("when querying disk block size: %w", err)
94 }
95
96 var blockCount int
97 var getSizeErr error
98 outFileC.Control(func(fd uintptr) {
99 blockCount, getSizeErr = unix.IoctlGetInt(int(fd), DKIOCGETBLOCKCOUNT)
100 })
101
102 if getSizeErr != nil {
103 return nil, fmt.Errorf("when querying disk block count: %w", err)
104 }
105 return &Device{
106 backend: handle,
107 rawConn: outFileC,
108 blockSize: int64(blockSize),
109 blockCount: int64(blockCount),
110 }, nil
111}
112
113type File struct {
114 backend *os.File
115 rawConn syscall.RawConn
116 blockSize int64
117 blockCount int64
118}
119
120func CreateFile(name string, blockSize int64, blockCount int64) (*File, error) {
121 if blockSize < 512 {
122 return nil, fmt.Errorf("blockSize must be bigger than 512 bytes")
123 }
124 if bits.OnesCount64(uint64(blockSize)) != 1 {
125 return nil, fmt.Errorf("blockSize must be a power of two")
126 }
127 out, err := os.Create(name)
128 if err != nil {
129 return nil, fmt.Errorf("when creating backing file: %w", err)
130 }
131 rawConn, err := out.SyscallConn()
132 if err != nil {
133 return nil, fmt.Errorf("unable to get SyscallConn: %w", err)
134 }
135 return &File{
136 backend: out,
137 blockSize: blockSize,
138 rawConn: rawConn,
139 blockCount: blockCount,
140 }, nil
141}
142
143func (d *File) ReadAt(p []byte, off int64) (n int, err error) {
144 return d.backend.ReadAt(p, off)
145}
146
147func (d *File) WriteAt(p []byte, off int64) (n int, err error) {
148 return d.backend.WriteAt(p, off)
149}
150
151func (d *File) Close() error {
152 return d.backend.Close()
153}
154
155func (d *File) BlockCount() int64 {
156 return d.blockCount
157}
158
159func (d *File) BlockSize() int64 {
160 return d.blockSize
161}
162
Jan Schära6da1712024-08-21 15:12:11 +0200163func (d *File) OptimalBlockSize() int64 {
164 return d.blockSize
165}
166
Lorenz Bruncb9f3d32023-07-27 15:21:49 +0200167func (d *File) Discard(startByte int64, endByte int64) error {
168 // Can be supported in the future via fnctl.
Lorenz Brun65b1c682023-09-14 15:49:39 +0200169 return errors.ErrUnsupported
Lorenz Bruncb9f3d32023-07-27 15:21:49 +0200170}
171
Lorenz Bruncb9f3d32023-07-27 15:21:49 +0200172func (d *File) Zero(startByte int64, endByte int64) error {
173 // Can possibly be accelerated in the future via fnctl.
174 return GenericZero(d, startByte, endByte)
175}
Jan Schära6da1712024-08-21 15:12:11 +0200176
177func (d *File) Sync() error {
178 return d.backend.Sync()
179}