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