blob: 1999a00deb943e2c708ea738f5c35704f3aea8d0 [file] [log] [blame]
Lorenz Brun16a981d2019-09-16 11:26:05 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
Leopold Schabel68c58752019-11-14 21:00:59 +010017// package devicemapper is a thin wrapper for the devicemapper ioctl API.
18// See: https://github.com/torvalds/linux/blob/master/include/uapi/linux/dm-ioctl.h
Lorenz Brunae0d90d2019-09-05 17:53:56 +020019package devicemapper
Lorenz Brun16a981d2019-09-16 11:26:05 +020020
21import (
22 "bytes"
23 "encoding/binary"
24 "fmt"
25 "os"
26 "runtime"
Lorenz Brunb9044c82021-08-24 11:59:47 +020027 "strings"
Lorenz Brun031243f2021-08-24 12:14:27 +020028 "sync"
Lorenz Brun16a981d2019-09-16 11:26:05 +020029 "unsafe"
30
31 "github.com/pkg/errors"
32 "github.com/yalue/native_endian"
33 "golang.org/x/sys/unix"
34)
35
36type DMIoctl struct {
37 Version Version
38 DataSize uint32
39 DataStart uint32
40 TargetCount uint32
41 OpenCount int32
42 Flags uint32
43 EventNumber uint32
44 _padding1 uint32
45 Dev uint64
46 Name [128]byte
47 UUID [129]byte
48 _padding2 [7]byte
49 Data [16384]byte
50}
51
52type DMTargetSpec struct {
53 SectorStart uint64
54 Length uint64
55 Status int32
56 Next uint32
57 TargetType [16]byte
58}
59
60type DMTargetDeps struct {
61 Count uint32
62 Padding uint32
63 Dev []uint64
64}
65
66type DMNameList struct {
67 Dev uint64
68 Next uint32
69 Name []byte
70}
71
72type DMTargetVersions struct {
73 Next uint32
74 Version [3]uint32
75}
76
77type DMTargetMessage struct {
78 Sector uint64
79 Message []byte
80}
81
82type Version [3]uint32
83
84const (
85 /* Top level cmds */
86 DM_VERSION_CMD uintptr = (0xc138fd << 8) + iota
87 DM_REMOVE_ALL_CMD
88 DM_LIST_DEVICES_CMD
89
90 /* device level cmds */
91 DM_DEV_CREATE_CMD
92 DM_DEV_REMOVE_CMD
93 DM_DEV_RENAME_CMD
94 DM_DEV_SUSPEND_CMD
95 DM_DEV_STATUS_CMD
96 DM_DEV_WAIT_CMD
97
98 /* Table level cmds */
99 DM_TABLE_LOAD_CMD
100 DM_TABLE_CLEAR_CMD
101 DM_TABLE_DEPS_CMD
102 DM_TABLE_STATUS_CMD
103
104 /* Added later */
105 DM_LIST_VERSIONS_CMD
106 DM_TARGET_MSG_CMD
107 DM_DEV_SET_GEOMETRY_CMD
108 DM_DEV_ARM_POLL_CMD
109)
110
111const (
112 DM_READONLY_FLAG = 1 << 0 /* In/Out */
113 DM_SUSPEND_FLAG = 1 << 1 /* In/Out */
114 DM_PERSISTENT_DEV_FLAG = 1 << 3 /* In */
115)
116
117const baseDataSize = uint32(unsafe.Sizeof(DMIoctl{})) - 16384
118
119func newReq() DMIoctl {
120 return DMIoctl{
121 Version: Version{4, 0, 0},
122 DataSize: baseDataSize,
123 DataStart: baseDataSize,
124 }
125}
126
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200127// stringToDelimitedBuf copies src to dst and returns an error if len(src) >
128// len(dst), or when the string contains a null byte.
Lorenz Brun16a981d2019-09-16 11:26:05 +0200129func stringToDelimitedBuf(dst []byte, src string) error {
130 if len(src) > len(dst)-1 {
Leopold Schabel68c58752019-11-14 21:00:59 +0100131 return fmt.Errorf("string longer than target buffer (%v > %v)", len(src), len(dst)-1)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200132 }
133 for i := 0; i < len(src); i++ {
134 if src[i] == 0x00 {
Leopold Schabel68c58752019-11-14 21:00:59 +0100135 return errors.New("string contains null byte, this is unsupported by DM")
Lorenz Brun16a981d2019-09-16 11:26:05 +0200136 }
137 dst[i] = src[i]
138 }
139 return nil
140}
141
Lorenz Brunb9044c82021-08-24 11:59:47 +0200142// marshalParams marshals a list of strings into a single string according to
143// the rules in the kernel-side decoder. Strings with null bytes or only
144// whitespace characters cannot be encoded and will return an errors.
145func marshalParams(params []string) (string, error) {
146 var strb strings.Builder
147 for _, param := range params {
148 var hasNonWhitespace bool
149 for i := 0; i < len(param); i++ {
150 b := param[i]
151 if b == 0x00 {
152 return "", errors.New("parameter with null bytes cannot be encoded")
153 }
154 isWhitespace := ctypeLookup[b]&_S != 0
155 if !isWhitespace {
156 hasNonWhitespace = true
157 }
158 if isWhitespace || b == '\\' {
159 strb.WriteByte('\\')
160 }
161 strb.WriteByte(b)
162 }
163 if !hasNonWhitespace {
164 return "", errors.New("parameter with only whitespace cannot be encoded")
165 }
166 strb.WriteByte(' ')
167 }
168 return strb.String(), nil
169}
170
Lorenz Brun031243f2021-08-24 12:14:27 +0200171var ctrlFile *os.File
172var ctrlFileError error
173var ctrlFileOnce sync.Once
Lorenz Brun16a981d2019-09-16 11:26:05 +0200174
Lorenz Brun031243f2021-08-24 12:14:27 +0200175func initCtrlFile() {
176 ctrlFile, ctrlFileError = os.Open("/dev/mapper/control")
177 if os.IsNotExist(ctrlFileError) {
178 _ = os.MkdirAll("/dev/mapper", 0755)
179 ctrlFileError = unix.Mknod("/dev/mapper/control", unix.S_IFCHR|0600, int(unix.Mkdev(10, 236)))
180 if ctrlFileError != nil {
181 ctrlFileError = fmt.Errorf("devicemapper control device doesn't exist and can't be mknod()ed: %w", ctrlFileError)
182 return
Lorenz Brun16a981d2019-09-16 11:26:05 +0200183 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200184 ctrlFile, ctrlFileError = os.Open("/dev/mapper/control")
Lorenz Brun16a981d2019-09-16 11:26:05 +0200185 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200186 if ctrlFileError != nil {
187 ctrlFileError = fmt.Errorf("failed to open devicemapper control device: %w", ctrlFileError)
188 }
Lorenz Brun16a981d2019-09-16 11:26:05 +0200189}
190
191func GetVersion() (Version, error) {
192 req := newReq()
Lorenz Brun031243f2021-08-24 12:14:27 +0200193 ctrlFileOnce.Do(initCtrlFile)
194 if ctrlFileError != nil {
195 return Version{}, ctrlFileError
Lorenz Brun16a981d2019-09-16 11:26:05 +0200196 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200197 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 +0200198 return Version{}, err
199 }
200 return req.Version, nil
201}
202
203func CreateDevice(name string) (uint64, error) {
204 req := newReq()
205 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
206 return 0, err
207 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200208 ctrlFileOnce.Do(initCtrlFile)
209 if ctrlFileError != nil {
210 return 0, ctrlFileError
Lorenz Brun16a981d2019-09-16 11:26:05 +0200211 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200212 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 +0200213 return 0, err
214 }
215 return req.Dev, nil
216}
217
218func RemoveDevice(name string) error {
219 req := newReq()
220 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
221 return err
222 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200223 ctrlFileOnce.Do(initCtrlFile)
224 if ctrlFileError != nil {
225 return ctrlFileError
Lorenz Brun16a981d2019-09-16 11:26:05 +0200226 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200227 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 +0200228 return err
229 }
230 runtime.KeepAlive(req)
231 return nil
232}
233
Lorenz Brun257acab2021-08-10 12:36:17 +0200234// Target represents a byte region inside a devicemapper table for a given
235// device provided by a given target implementation.
Lorenz Brun16a981d2019-09-16 11:26:05 +0200236type Target struct {
Lorenz Brun257acab2021-08-10 12:36:17 +0200237 // StartSector is the first sector (defined as being 512 bytes long) this
238 // target covers.
Lorenz Brun16a981d2019-09-16 11:26:05 +0200239 StartSector uint64
Lorenz Brun257acab2021-08-10 12:36:17 +0200240 // Length is the number of sectors (defined as being 512 bytes long) this
241 // target covers, starting from StartSector.
242 Length uint64
243 // Type is the type of target handling this byte region.
244 // Types implemented by the Linux kernel can be found at
245 // @linux//drivers/md/... by looking for dm_register_target() calls.
246 Type string
247 // Parameters are additional parameters specific to the target type.
Lorenz Brunb9044c82021-08-24 11:59:47 +0200248 // Note that null bytes and parameters consisting only of whitespace
249 // characters cannot be encoded and will return an error.
250 Parameters []string
Lorenz Brun16a981d2019-09-16 11:26:05 +0200251}
252
Lorenz Brun257acab2021-08-10 12:36:17 +0200253func LoadTable(name string, readOnly bool, targets []Target) error {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200254 req := newReq()
255 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
256 return err
257 }
258 var data bytes.Buffer
259 for _, target := range targets {
Lorenz Brunb9044c82021-08-24 11:59:47 +0200260 encodedParams, err := marshalParams(target.Parameters)
261 if err != nil {
262 return fmt.Errorf("cannot encode parameters: %w", err)
263 }
Lorenz Brun16a981d2019-09-16 11:26:05 +0200264 // Gives the size of the spec and the null-terminated params aligned to 8 bytes
Lorenz Brunb9044c82021-08-24 11:59:47 +0200265 padding := len(encodedParams) % 8
266 targetSize := uint32(int(unsafe.Sizeof(DMTargetSpec{})) + (len(encodedParams) + 1) + padding)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200267
268 targetSpec := DMTargetSpec{
269 SectorStart: target.StartSector,
270 Length: target.Length,
271 Next: targetSize,
272 }
273 if err := stringToDelimitedBuf(targetSpec.TargetType[:], target.Type); err != nil {
274 return err
275 }
276 if err := binary.Write(&data, native_endian.NativeEndian(), &targetSpec); err != nil {
277 panic(err)
278 }
Lorenz Brunb9044c82021-08-24 11:59:47 +0200279 data.WriteString(encodedParams)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200280 data.WriteByte(0x00)
281 for i := 0; i < padding; i++ {
282 data.WriteByte(0x00)
283 }
284 }
285 req.TargetCount = uint32(len(targets))
286 if data.Len() >= 16384 {
287 return errors.New("table too large for allocated memory")
288 }
289 req.DataSize = baseDataSize + uint32(data.Len())
290 copy(req.Data[:], data.Bytes())
Lorenz Brun257acab2021-08-10 12:36:17 +0200291 if readOnly {
292 req.Flags = DM_READONLY_FLAG
293 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200294 ctrlFileOnce.Do(initCtrlFile)
295 if ctrlFileError != nil {
296 return ctrlFileError
Lorenz Brun16a981d2019-09-16 11:26:05 +0200297 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200298 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 +0200299 return err
300 }
301 runtime.KeepAlive(req)
302 return nil
303}
304
305func suspendResume(name string, suspend bool) error {
306 req := newReq()
307 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
308 return err
309 }
310 if suspend {
311 req.Flags = DM_SUSPEND_FLAG
312 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200313 ctrlFileOnce.Do(initCtrlFile)
314 if ctrlFileError != nil {
315 return ctrlFileError
Lorenz Brun16a981d2019-09-16 11:26:05 +0200316 }
Lorenz Brun031243f2021-08-24 12:14:27 +0200317 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 +0200318 return err
319 }
320 runtime.KeepAlive(req)
321 return nil
322}
323
324func Suspend(name string) error {
325 return suspendResume(name, true)
326}
327func Resume(name string) error {
328 return suspendResume(name, false)
329}
330
Lorenz Brun257acab2021-08-10 12:36:17 +0200331func CreateActiveDevice(name string, readOnly bool, targets []Target) (uint64, error) {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200332 dev, err := CreateDevice(name)
333 if err != nil {
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200334 return 0, fmt.Errorf("DM_DEV_CREATE failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200335 }
Lorenz Brun257acab2021-08-10 12:36:17 +0200336 if err := LoadTable(name, readOnly, targets); err != nil {
Leopold Schabel68c58752019-11-14 21:00:59 +0100337 _ = RemoveDevice(name)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200338 return 0, fmt.Errorf("DM_TABLE_LOAD failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200339 }
340 if err := Resume(name); err != nil {
Leopold Schabel68c58752019-11-14 21:00:59 +0100341 _ = RemoveDevice(name)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200342 return 0, fmt.Errorf("DM_DEV_SUSPEND failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200343 }
344 return dev, nil
345}