blob: 45410760b131e0fb9eeab387a2061a1dfd7b8145 [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.
86func Open(path string) (*Device, error) {
87 outFile, err := os.OpenFile(path, os.O_RDWR, 0640)
88 if err != nil {
89 return nil, fmt.Errorf("failed to open block device: %w", err)
90 }
91 return FromFileHandle(outFile)
92}
93
94// FromFileHandle creates a blockdev from a device handle. The device handle is
95// not duplicated, closing the returned Device will close it. If the handle is
96// not a block device, i.e does not implement block device ioctls, an error is
97// returned.
98func FromFileHandle(handle *os.File) (*Device, error) {
99 outFileC, err := handle.SyscallConn()
100 if err != nil {
101 return nil, fmt.Errorf("error getting SyscallConn: %w", err)
102 }
103 buf := make([]uint8, 0x80)
104 var n uint32
105
106 var p runtime.Pinner
107 p.Pin(&buf[0])
108 defer p.Unpin()
109
110 outFileC.Control(func(fd uintptr) {
111 err = windows.DeviceIoControl(windows.Handle(fd), IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nil, 0, &buf[0], uint32(len(buf)), &n, nil)
112 })
113 if errors.Is(err, windows.ERROR_INVALID_PARAMETER) {
114 return nil, ErrNotBlockDevice
115 } else if err != nil {
116 return nil, fmt.Errorf("when querying disk block size: %w", err)
117 }
118
119 diskGeometryBase := (*DISK_GEOMETRY_EX_RAW)(unsafe.Pointer(&buf[0]))
120 blockSize := int64(diskGeometryBase.Geometry.BytesPerSector)
121 blockCount := int64(diskGeometryBase.DiskSize) / blockSize
122 if int64(diskGeometryBase.DiskSize)%blockSize != 0 {
123 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))
124 }
125
126 return &Device{
127 backend: handle,
128 rawConn: outFileC,
129 blockSize: blockSize,
130 blockCount: blockCount,
131 }, nil
132}
133
134type File struct {
135 backend *os.File
136 rawConn syscall.RawConn
137 blockSize int64
138 blockCount int64
139}
140
141func CreateFile(name string, blockSize int64, blockCount int64) (*File, error) {
142 if blockSize < 512 {
143 return nil, fmt.Errorf("blockSize must be bigger than 512 bytes")
144 }
145 if bits.OnesCount64(uint64(blockSize)) != 1 {
146 return nil, fmt.Errorf("blockSize must be a power of two")
147 }
148 out, err := os.Create(name)
149 if err != nil {
150 return nil, fmt.Errorf("when creating backing file: %w", err)
151 }
152 rawConn, err := out.SyscallConn()
153 if err != nil {
154 return nil, fmt.Errorf("unable to get SyscallConn: %w", err)
155 }
156 return &File{
157 backend: out,
158 blockSize: blockSize,
159 rawConn: rawConn,
160 blockCount: blockCount,
161 }, nil
162}
163
164func (d *File) ReadAt(p []byte, off int64) (n int, err error) {
165 return d.backend.ReadAt(p, off)
166}
167
168func (d *File) WriteAt(p []byte, off int64) (n int, err error) {
169 return d.backend.WriteAt(p, off)
170}
171
172func (d *File) Close() error {
173 return d.backend.Close()
174}
175
176func (d *File) BlockCount() int64 {
177 return d.blockCount
178}
179
180func (d *File) BlockSize() int64 {
181 return d.blockSize
182}
183
184func (d *File) OptimalBlockSize() int64 {
185 return d.blockSize
186}
187
188func (d *File) Discard(startByte int64, endByte int64) error {
189 // Can be supported in the future via FSCTL_SET_ZERO_DATA.
190 return errors.ErrUnsupported
191}
192
193func (d *File) Zero(startByte int64, endByte int64) error {
194 // Can possibly be accelerated in the future via FSCTL_SET_ZERO_DATA.
195 return GenericZero(d, startByte, endByte)
196}
197
198func (d *File) Sync() error {
199 return d.backend.Sync()
200}