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