blob: 9e3d51c532c45af60f2d4596868130649bae53db [file] [log] [blame]
Timon Stampfli007d66e2024-12-15 16:29:04 +01001//go:build windows
2
3package blockdev
4
5import (
6 "errors"
7 "fmt"
8 "math/bits"
9 "os"
10 "runtime"
11 "syscall"
12 "unsafe"
13
14 "golang.org/x/sys/windows"
15)
16
17const (
18 IOCTL_DISK_BASE = 0x00000007
19 IOCTL_DISK_GET_DRIVE_GEOMETRY_EX = (IOCTL_DISK_BASE << 16) | (0x0028 << 2)
20)
21
22type DISK_GEOMETRY struct {
23 Cylinders uint64
24 MediaType uint32
25 TracksPerCylinder uint32
26 SectorsPerTrack uint32
27 BytesPerSector uint32
28}
29
30type DISK_GEOMETRY_EX_RAW struct {
31 Geometry DISK_GEOMETRY
32 DiskSize uint64
33}
34
35type Device struct {
36 backend *os.File
37 rawConn syscall.RawConn
38 blockSize int64
39 blockCount int64
40}
41
42func (d *Device) ReadAt(p []byte, off int64) (n int, err error) {
43 return d.backend.ReadAt(p, off)
44}
45
46func (d *Device) WriteAt(p []byte, off int64) (n int, err error) {
47 return d.backend.WriteAt(p, off)
48}
49
50func (d *Device) Close() error {
51 return d.backend.Close()
52}
53
54func (d *Device) BlockCount() int64 {
55 return d.blockCount
56}
57
58func (d *Device) BlockSize() int64 {
59 return d.blockSize
60}
61
62func (d *Device) OptimalBlockSize() int64 {
63 return d.blockSize
64}
65
66func (d *Device) Discard(startByte int64, endByte int64) error {
67 // Can be implemented using DKIOCUNMAP, but needs x/sys/unix extension.
68 // Not mandatory, so this is fine for now.
69 return errors.ErrUnsupported
70}
71
72func (d *Device) Zero(startByte int64, endByte int64) error {
73 // It doesn't look like MacOS even has any zeroing acceleration, so just
74 // use the generic one.
75 return GenericZero(d, startByte, endByte)
76}
77
78func (d *Device) Sync() error {
79 return d.backend.Sync()
80}
81
82// Open opens a block device given a path to its inode.
83func Open(path string) (*Device, error) {
84 outFile, err := os.OpenFile(path, os.O_RDWR, 0640)
85 if err != nil {
86 return nil, fmt.Errorf("failed to open block device: %w", err)
87 }
88 return FromFileHandle(outFile)
89}
90
91// FromFileHandle creates a blockdev from a device handle. The device handle is
92// not duplicated, closing the returned Device will close it. If the handle is
93// not a block device, i.e does not implement block device ioctls, an error is
94// returned.
95func FromFileHandle(handle *os.File) (*Device, error) {
96 outFileC, err := handle.SyscallConn()
97 if err != nil {
98 return nil, fmt.Errorf("error getting SyscallConn: %w", err)
99 }
100 buf := make([]uint8, 0x80)
101 var n uint32
102
103 var p runtime.Pinner
104 p.Pin(&buf[0])
105 defer p.Unpin()
106
107 outFileC.Control(func(fd uintptr) {
108 err = windows.DeviceIoControl(windows.Handle(fd), IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nil, 0, &buf[0], uint32(len(buf)), &n, nil)
109 })
110 if errors.Is(err, windows.ERROR_INVALID_PARAMETER) {
111 return nil, ErrNotBlockDevice
112 } else if err != nil {
113 return nil, fmt.Errorf("when querying disk block size: %w", err)
114 }
115
116 diskGeometryBase := (*DISK_GEOMETRY_EX_RAW)(unsafe.Pointer(&buf[0]))
117 blockSize := int64(diskGeometryBase.Geometry.BytesPerSector)
118 blockCount := int64(diskGeometryBase.DiskSize) / blockSize
119 if int64(diskGeometryBase.DiskSize)%blockSize != 0 {
120 return nil, fmt.Errorf("block device size is not an integer multiple of its block size (%d %% %d = %d)", diskGeometryBase.DiskSize, blockSize, diskGeometryBase.DiskSize%uint64(blockSize))
121 }
122
123 return &Device{
124 backend: handle,
125 rawConn: outFileC,
126 blockSize: blockSize,
127 blockCount: blockCount,
128 }, nil
129}
130
131type File struct {
132 backend *os.File
133 rawConn syscall.RawConn
134 blockSize int64
135 blockCount int64
136}
137
138func CreateFile(name string, blockSize int64, blockCount int64) (*File, error) {
139 if blockSize < 512 {
140 return nil, fmt.Errorf("blockSize must be bigger than 512 bytes")
141 }
142 if bits.OnesCount64(uint64(blockSize)) != 1 {
143 return nil, fmt.Errorf("blockSize must be a power of two")
144 }
145 out, err := os.Create(name)
146 if err != nil {
147 return nil, fmt.Errorf("when creating backing file: %w", err)
148 }
149 rawConn, err := out.SyscallConn()
150 if err != nil {
151 return nil, fmt.Errorf("unable to get SyscallConn: %w", err)
152 }
153 return &File{
154 backend: out,
155 blockSize: blockSize,
156 rawConn: rawConn,
157 blockCount: blockCount,
158 }, nil
159}
160
161func (d *File) ReadAt(p []byte, off int64) (n int, err error) {
162 return d.backend.ReadAt(p, off)
163}
164
165func (d *File) WriteAt(p []byte, off int64) (n int, err error) {
166 return d.backend.WriteAt(p, off)
167}
168
169func (d *File) Close() error {
170 return d.backend.Close()
171}
172
173func (d *File) BlockCount() int64 {
174 return d.blockCount
175}
176
177func (d *File) BlockSize() int64 {
178 return d.blockSize
179}
180
181func (d *File) OptimalBlockSize() int64 {
182 return d.blockSize
183}
184
185func (d *File) Discard(startByte int64, endByte int64) error {
186 // Can be supported in the future via FSCTL_SET_ZERO_DATA.
187 return errors.ErrUnsupported
188}
189
190func (d *File) Zero(startByte int64, endByte int64) error {
191 // Can possibly be accelerated in the future via FSCTL_SET_ZERO_DATA.
192 return GenericZero(d, startByte, endByte)
193}
194
195func (d *File) Sync() error {
196 return d.backend.Sync()
197}