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