blob: 3c7acc104d6ede8d8cc5627a221be4df1a5b1e70 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Lorenz Brun16a981d2019-09-16 11:26:05 +02002// SPDX-License-Identifier: Apache-2.0
Lorenz Brun16a981d2019-09-16 11:26:05 +02003
Leopold Schabel68c58752019-11-14 21:00:59 +01004// package devicemapper is a thin wrapper for the devicemapper ioctl API.
5// See: https://github.com/torvalds/linux/blob/master/include/uapi/linux/dm-ioctl.h
Lorenz Brunae0d90d2019-09-05 17:53:56 +02006package devicemapper
Lorenz Brun16a981d2019-09-16 11:26:05 +02007
8import (
9 "bytes"
10 "encoding/binary"
11 "fmt"
12 "os"
13 "runtime"
Lorenz Brunb9044c82021-08-24 11:59:47 +020014 "strings"
Lorenz Brun031243f2021-08-24 12:14:27 +020015 "sync"
Lorenz Brun16a981d2019-09-16 11:26:05 +020016 "unsafe"
17
18 "github.com/pkg/errors"
19 "github.com/yalue/native_endian"
20 "golang.org/x/sys/unix"
21)
22
23type DMIoctl struct {
24 Version Version
25 DataSize uint32
26 DataStart uint32
27 TargetCount uint32
28 OpenCount int32
29 Flags uint32
30 EventNumber uint32
31 _padding1 uint32
32 Dev uint64
33 Name [128]byte
34 UUID [129]byte
35 _padding2 [7]byte
36 Data [16384]byte
37}
38
39type DMTargetSpec struct {
40 SectorStart uint64
41 Length uint64
42 Status int32
43 Next uint32
44 TargetType [16]byte
45}
46
47type DMTargetDeps struct {
48 Count uint32
49 Padding uint32
50 Dev []uint64
51}
52
53type DMNameList struct {
54 Dev uint64
55 Next uint32
56 Name []byte
57}
58
59type DMTargetVersions struct {
60 Next uint32
61 Version [3]uint32
62}
63
64type DMTargetMessage struct {
65 Sector uint64
66 Message []byte
67}
68
69type Version [3]uint32
70
71const (
72 /* Top level cmds */
73 DM_VERSION_CMD uintptr = (0xc138fd << 8) + iota
74 DM_REMOVE_ALL_CMD
75 DM_LIST_DEVICES_CMD
76
77 /* device level cmds */
78 DM_DEV_CREATE_CMD
79 DM_DEV_REMOVE_CMD
80 DM_DEV_RENAME_CMD
81 DM_DEV_SUSPEND_CMD
82 DM_DEV_STATUS_CMD
83 DM_DEV_WAIT_CMD
84
85 /* Table level cmds */
86 DM_TABLE_LOAD_CMD
87 DM_TABLE_CLEAR_CMD
88 DM_TABLE_DEPS_CMD
89 DM_TABLE_STATUS_CMD
90
91 /* Added later */
92 DM_LIST_VERSIONS_CMD
93 DM_TARGET_MSG_CMD
94 DM_DEV_SET_GEOMETRY_CMD
95 DM_DEV_ARM_POLL_CMD
96)
97
98const (
99 DM_READONLY_FLAG = 1 << 0 /* In/Out */
100 DM_SUSPEND_FLAG = 1 << 1 /* In/Out */
101 DM_PERSISTENT_DEV_FLAG = 1 << 3 /* In */
102)
103
104const baseDataSize = uint32(unsafe.Sizeof(DMIoctl{})) - 16384
105
106func newReq() DMIoctl {
107 return DMIoctl{
108 Version: Version{4, 0, 0},
109 DataSize: baseDataSize,
110 DataStart: baseDataSize,
111 }
112}
113
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200114// stringToDelimitedBuf copies src to dst and returns an error if len(src) >
115// len(dst), or when the string contains a null byte.
Lorenz Brun16a981d2019-09-16 11:26:05 +0200116func stringToDelimitedBuf(dst []byte, src string) error {
117 if len(src) > len(dst)-1 {
Leopold Schabel68c58752019-11-14 21:00:59 +0100118 return fmt.Errorf("string longer than target buffer (%v > %v)", len(src), len(dst)-1)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200119 }
120 for i := 0; i < len(src); i++ {
121 if src[i] == 0x00 {
Leopold Schabel68c58752019-11-14 21:00:59 +0100122 return errors.New("string contains null byte, this is unsupported by DM")
Lorenz Brun16a981d2019-09-16 11:26:05 +0200123 }
124 dst[i] = src[i]
125 }
126 return nil
127}
128
Lorenz Brunb9044c82021-08-24 11:59:47 +0200129// marshalParams marshals a list of strings into a single string according to
130// the rules in the kernel-side decoder. Strings with null bytes or only
131// whitespace characters cannot be encoded and will return an errors.
132func marshalParams(params []string) (string, error) {
133 var strb strings.Builder
134 for _, param := range params {
135 var hasNonWhitespace bool
136 for i := 0; i < len(param); i++ {
137 b := param[i]
138 if b == 0x00 {
139 return "", errors.New("parameter with null bytes cannot be encoded")
140 }
141 isWhitespace := ctypeLookup[b]&_S != 0
142 if !isWhitespace {
143 hasNonWhitespace = true
144 }
145 if isWhitespace || b == '\\' {
146 strb.WriteByte('\\')
147 }
148 strb.WriteByte(b)
149 }
150 if !hasNonWhitespace {
151 return "", errors.New("parameter with only whitespace cannot be encoded")
152 }
153 strb.WriteByte(' ')
154 }
155 return strb.String(), nil
156}
157
Lorenz Brun031243f2021-08-24 12:14:27 +0200158var ctrlFile *os.File
159var ctrlFileError error
160var ctrlFileOnce sync.Once
Lorenz Brun16a981d2019-09-16 11:26:05 +0200161
Lorenz Brun031243f2021-08-24 12:14:27 +0200162func initCtrlFile() {
163 ctrlFile, ctrlFileError = os.Open("/dev/mapper/control")
164 if os.IsNotExist(ctrlFileError) {
165 _ = os.MkdirAll("/dev/mapper", 0755)
166 ctrlFileError = unix.Mknod("/dev/mapper/control", unix.S_IFCHR|0600, int(unix.Mkdev(10, 236)))
167 if ctrlFileError != nil {
168 ctrlFileError = fmt.Errorf("devicemapper control device doesn't exist and can't be mknod()ed: %w", ctrlFileError)
169 return
Lorenz Brun16a981d2019-09-16 11:26:05 +0200170 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200171 ctrlFile, ctrlFileError = os.Open("/dev/mapper/control")
Lorenz Brun16a981d2019-09-16 11:26:05 +0200172 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200173 if ctrlFileError != nil {
174 ctrlFileError = fmt.Errorf("failed to open devicemapper control device: %w", ctrlFileError)
175 }
Lorenz Brun16a981d2019-09-16 11:26:05 +0200176}
177
178func GetVersion() (Version, error) {
179 req := newReq()
Lorenz Brun031243f2021-08-24 12:14:27 +0200180 ctrlFileOnce.Do(initCtrlFile)
181 if ctrlFileError != nil {
182 return Version{}, ctrlFileError
Lorenz Brun16a981d2019-09-16 11:26:05 +0200183 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200184 if _, _, err := unix.Syscall(unix.SYS_IOCTL, ctrlFile.Fd(), DM_VERSION_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200185 return Version{}, err
186 }
187 return req.Version, nil
188}
189
190func CreateDevice(name string) (uint64, error) {
191 req := newReq()
192 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
193 return 0, err
194 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200195 ctrlFileOnce.Do(initCtrlFile)
196 if ctrlFileError != nil {
197 return 0, ctrlFileError
Lorenz Brun16a981d2019-09-16 11:26:05 +0200198 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200199 if _, _, err := unix.Syscall(unix.SYS_IOCTL, ctrlFile.Fd(), DM_DEV_CREATE_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200200 return 0, err
201 }
202 return req.Dev, nil
203}
204
205func RemoveDevice(name string) error {
206 req := newReq()
207 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
208 return err
209 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200210 ctrlFileOnce.Do(initCtrlFile)
211 if ctrlFileError != nil {
212 return ctrlFileError
Lorenz Brun16a981d2019-09-16 11:26:05 +0200213 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200214 if _, _, err := unix.Syscall(unix.SYS_IOCTL, ctrlFile.Fd(), DM_DEV_REMOVE_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200215 return err
216 }
217 runtime.KeepAlive(req)
218 return nil
219}
220
Lorenz Brun257acab2021-08-10 12:36:17 +0200221// Target represents a byte region inside a devicemapper table for a given
222// device provided by a given target implementation.
Lorenz Brun16a981d2019-09-16 11:26:05 +0200223type Target struct {
Lorenz Brun257acab2021-08-10 12:36:17 +0200224 // StartSector is the first sector (defined as being 512 bytes long) this
225 // target covers.
Lorenz Brun16a981d2019-09-16 11:26:05 +0200226 StartSector uint64
Lorenz Brun257acab2021-08-10 12:36:17 +0200227 // Length is the number of sectors (defined as being 512 bytes long) this
228 // target covers, starting from StartSector.
229 Length uint64
230 // Type is the type of target handling this byte region.
231 // Types implemented by the Linux kernel can be found at
232 // @linux//drivers/md/... by looking for dm_register_target() calls.
233 Type string
234 // Parameters are additional parameters specific to the target type.
Lorenz Brunb9044c82021-08-24 11:59:47 +0200235 // Note that null bytes and parameters consisting only of whitespace
236 // characters cannot be encoded and will return an error.
237 Parameters []string
Lorenz Brun16a981d2019-09-16 11:26:05 +0200238}
239
Lorenz Brun257acab2021-08-10 12:36:17 +0200240func LoadTable(name string, readOnly bool, targets []Target) error {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200241 req := newReq()
242 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
243 return err
244 }
245 var data bytes.Buffer
246 for _, target := range targets {
Lorenz Brunb9044c82021-08-24 11:59:47 +0200247 encodedParams, err := marshalParams(target.Parameters)
248 if err != nil {
249 return fmt.Errorf("cannot encode parameters: %w", err)
250 }
Lorenz Brun16a981d2019-09-16 11:26:05 +0200251 // Gives the size of the spec and the null-terminated params aligned to 8 bytes
Lorenz Brunb9044c82021-08-24 11:59:47 +0200252 padding := len(encodedParams) % 8
253 targetSize := uint32(int(unsafe.Sizeof(DMTargetSpec{})) + (len(encodedParams) + 1) + padding)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200254
255 targetSpec := DMTargetSpec{
256 SectorStart: target.StartSector,
257 Length: target.Length,
258 Next: targetSize,
259 }
260 if err := stringToDelimitedBuf(targetSpec.TargetType[:], target.Type); err != nil {
261 return err
262 }
263 if err := binary.Write(&data, native_endian.NativeEndian(), &targetSpec); err != nil {
264 panic(err)
265 }
Lorenz Brunb9044c82021-08-24 11:59:47 +0200266 data.WriteString(encodedParams)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200267 data.WriteByte(0x00)
268 for i := 0; i < padding; i++ {
269 data.WriteByte(0x00)
270 }
271 }
272 req.TargetCount = uint32(len(targets))
273 if data.Len() >= 16384 {
274 return errors.New("table too large for allocated memory")
275 }
276 req.DataSize = baseDataSize + uint32(data.Len())
277 copy(req.Data[:], data.Bytes())
Lorenz Brun257acab2021-08-10 12:36:17 +0200278 if readOnly {
279 req.Flags = DM_READONLY_FLAG
280 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200281 ctrlFileOnce.Do(initCtrlFile)
282 if ctrlFileError != nil {
283 return ctrlFileError
Lorenz Brun16a981d2019-09-16 11:26:05 +0200284 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200285 if _, _, err := unix.Syscall(unix.SYS_IOCTL, ctrlFile.Fd(), DM_TABLE_LOAD_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200286 return err
287 }
288 runtime.KeepAlive(req)
289 return nil
290}
291
292func suspendResume(name string, suspend bool) error {
293 req := newReq()
294 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
295 return err
296 }
297 if suspend {
298 req.Flags = DM_SUSPEND_FLAG
299 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200300 ctrlFileOnce.Do(initCtrlFile)
301 if ctrlFileError != nil {
302 return ctrlFileError
Lorenz Brun16a981d2019-09-16 11:26:05 +0200303 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200304 if _, _, err := unix.Syscall(unix.SYS_IOCTL, ctrlFile.Fd(), DM_DEV_SUSPEND_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200305 return err
306 }
307 runtime.KeepAlive(req)
308 return nil
309}
310
311func Suspend(name string) error {
312 return suspendResume(name, true)
313}
314func Resume(name string) error {
315 return suspendResume(name, false)
316}
317
Lorenz Brun257acab2021-08-10 12:36:17 +0200318func CreateActiveDevice(name string, readOnly bool, targets []Target) (uint64, error) {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200319 dev, err := CreateDevice(name)
320 if err != nil {
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200321 return 0, fmt.Errorf("DM_DEV_CREATE failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200322 }
Lorenz Brun257acab2021-08-10 12:36:17 +0200323 if err := LoadTable(name, readOnly, targets); err != nil {
Leopold Schabel68c58752019-11-14 21:00:59 +0100324 _ = RemoveDevice(name)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200325 return 0, fmt.Errorf("DM_TABLE_LOAD failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200326 }
327 if err := Resume(name); err != nil {
Leopold Schabel68c58752019-11-14 21:00:59 +0100328 _ = RemoveDevice(name)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200329 return 0, fmt.Errorf("DM_DEV_SUSPEND failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200330 }
331 return dev, nil
332}