blob: 9b560e6f58f08bb65c4795bd81321a7d7fd453de [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 Brun16a981d2019-09-16 11:26:05 +020028 "unsafe"
29
30 "github.com/pkg/errors"
31 "github.com/yalue/native_endian"
32 "golang.org/x/sys/unix"
33)
34
35type DMIoctl struct {
36 Version Version
37 DataSize uint32
38 DataStart uint32
39 TargetCount uint32
40 OpenCount int32
41 Flags uint32
42 EventNumber uint32
43 _padding1 uint32
44 Dev uint64
45 Name [128]byte
46 UUID [129]byte
47 _padding2 [7]byte
48 Data [16384]byte
49}
50
51type DMTargetSpec struct {
52 SectorStart uint64
53 Length uint64
54 Status int32
55 Next uint32
56 TargetType [16]byte
57}
58
59type DMTargetDeps struct {
60 Count uint32
61 Padding uint32
62 Dev []uint64
63}
64
65type DMNameList struct {
66 Dev uint64
67 Next uint32
68 Name []byte
69}
70
71type DMTargetVersions struct {
72 Next uint32
73 Version [3]uint32
74}
75
76type DMTargetMessage struct {
77 Sector uint64
78 Message []byte
79}
80
81type Version [3]uint32
82
83const (
84 /* Top level cmds */
85 DM_VERSION_CMD uintptr = (0xc138fd << 8) + iota
86 DM_REMOVE_ALL_CMD
87 DM_LIST_DEVICES_CMD
88
89 /* device level cmds */
90 DM_DEV_CREATE_CMD
91 DM_DEV_REMOVE_CMD
92 DM_DEV_RENAME_CMD
93 DM_DEV_SUSPEND_CMD
94 DM_DEV_STATUS_CMD
95 DM_DEV_WAIT_CMD
96
97 /* Table level cmds */
98 DM_TABLE_LOAD_CMD
99 DM_TABLE_CLEAR_CMD
100 DM_TABLE_DEPS_CMD
101 DM_TABLE_STATUS_CMD
102
103 /* Added later */
104 DM_LIST_VERSIONS_CMD
105 DM_TARGET_MSG_CMD
106 DM_DEV_SET_GEOMETRY_CMD
107 DM_DEV_ARM_POLL_CMD
108)
109
110const (
111 DM_READONLY_FLAG = 1 << 0 /* In/Out */
112 DM_SUSPEND_FLAG = 1 << 1 /* In/Out */
113 DM_PERSISTENT_DEV_FLAG = 1 << 3 /* In */
114)
115
116const baseDataSize = uint32(unsafe.Sizeof(DMIoctl{})) - 16384
117
118func newReq() DMIoctl {
119 return DMIoctl{
120 Version: Version{4, 0, 0},
121 DataSize: baseDataSize,
122 DataStart: baseDataSize,
123 }
124}
125
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200126// stringToDelimitedBuf copies src to dst and returns an error if len(src) >
127// len(dst), or when the string contains a null byte.
Lorenz Brun16a981d2019-09-16 11:26:05 +0200128func stringToDelimitedBuf(dst []byte, src string) error {
129 if len(src) > len(dst)-1 {
Leopold Schabel68c58752019-11-14 21:00:59 +0100130 return fmt.Errorf("string longer than target buffer (%v > %v)", len(src), len(dst)-1)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200131 }
132 for i := 0; i < len(src); i++ {
133 if src[i] == 0x00 {
Leopold Schabel68c58752019-11-14 21:00:59 +0100134 return errors.New("string contains null byte, this is unsupported by DM")
Lorenz Brun16a981d2019-09-16 11:26:05 +0200135 }
136 dst[i] = src[i]
137 }
138 return nil
139}
140
Lorenz Brunb9044c82021-08-24 11:59:47 +0200141// marshalParams marshals a list of strings into a single string according to
142// the rules in the kernel-side decoder. Strings with null bytes or only
143// whitespace characters cannot be encoded and will return an errors.
144func marshalParams(params []string) (string, error) {
145 var strb strings.Builder
146 for _, param := range params {
147 var hasNonWhitespace bool
148 for i := 0; i < len(param); i++ {
149 b := param[i]
150 if b == 0x00 {
151 return "", errors.New("parameter with null bytes cannot be encoded")
152 }
153 isWhitespace := ctypeLookup[b]&_S != 0
154 if !isWhitespace {
155 hasNonWhitespace = true
156 }
157 if isWhitespace || b == '\\' {
158 strb.WriteByte('\\')
159 }
160 strb.WriteByte(b)
161 }
162 if !hasNonWhitespace {
163 return "", errors.New("parameter with only whitespace cannot be encoded")
164 }
165 strb.WriteByte(' ')
166 }
167 return strb.String(), nil
168}
169
Lorenz Brun16a981d2019-09-16 11:26:05 +0200170var fd uintptr
171
172func getFd() (uintptr, error) {
173 if fd == 0 {
174 f, err := os.Open("/dev/mapper/control")
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200175 if os.IsNotExist(err) {
Leopold Schabel68c58752019-11-14 21:00:59 +0100176 _ = os.MkdirAll("/dev/mapper", 0755)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200177 if err := unix.Mknod("/dev/mapper/control", unix.S_IFCHR|0600, int(unix.Mkdev(10, 236))); err != nil {
178 return 0, err
179 }
180 f, err = os.Open("/dev/mapper/control")
181 if err != nil {
182 return 0, err
183 }
184 } else if err != nil {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200185 return 0, err
186 }
187 fd = f.Fd()
188 return f.Fd(), nil
189 }
190 return fd, nil
191}
192
193func GetVersion() (Version, error) {
194 req := newReq()
195 fd, err := getFd()
196 if err != nil {
197 return Version{}, err
198 }
199 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_VERSION_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
200 return Version{}, err
201 }
202 return req.Version, nil
203}
204
205func CreateDevice(name string) (uint64, error) {
206 req := newReq()
207 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
208 return 0, err
209 }
210 fd, err := getFd()
211 if err != nil {
212 return 0, err
213 }
214 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_DEV_CREATE_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
215 return 0, err
216 }
217 return req.Dev, nil
218}
219
220func RemoveDevice(name string) error {
221 req := newReq()
222 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
223 return err
224 }
225 fd, err := getFd()
226 if err != nil {
227 return err
228 }
229 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_DEV_REMOVE_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
230 return err
231 }
232 runtime.KeepAlive(req)
233 return nil
234}
235
Lorenz Brun257acab2021-08-10 12:36:17 +0200236// Target represents a byte region inside a devicemapper table for a given
237// device provided by a given target implementation.
Lorenz Brun16a981d2019-09-16 11:26:05 +0200238type Target struct {
Lorenz Brun257acab2021-08-10 12:36:17 +0200239 // StartSector is the first sector (defined as being 512 bytes long) this
240 // target covers.
Lorenz Brun16a981d2019-09-16 11:26:05 +0200241 StartSector uint64
Lorenz Brun257acab2021-08-10 12:36:17 +0200242 // Length is the number of sectors (defined as being 512 bytes long) this
243 // target covers, starting from StartSector.
244 Length uint64
245 // Type is the type of target handling this byte region.
246 // Types implemented by the Linux kernel can be found at
247 // @linux//drivers/md/... by looking for dm_register_target() calls.
248 Type string
249 // Parameters are additional parameters specific to the target type.
Lorenz Brunb9044c82021-08-24 11:59:47 +0200250 // Note that null bytes and parameters consisting only of whitespace
251 // characters cannot be encoded and will return an error.
252 Parameters []string
Lorenz Brun16a981d2019-09-16 11:26:05 +0200253}
254
Lorenz Brun257acab2021-08-10 12:36:17 +0200255func LoadTable(name string, readOnly bool, targets []Target) error {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200256 req := newReq()
257 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
258 return err
259 }
260 var data bytes.Buffer
261 for _, target := range targets {
Lorenz Brunb9044c82021-08-24 11:59:47 +0200262 encodedParams, err := marshalParams(target.Parameters)
263 if err != nil {
264 return fmt.Errorf("cannot encode parameters: %w", err)
265 }
Lorenz Brun16a981d2019-09-16 11:26:05 +0200266 // Gives the size of the spec and the null-terminated params aligned to 8 bytes
Lorenz Brunb9044c82021-08-24 11:59:47 +0200267 padding := len(encodedParams) % 8
268 targetSize := uint32(int(unsafe.Sizeof(DMTargetSpec{})) + (len(encodedParams) + 1) + padding)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200269
270 targetSpec := DMTargetSpec{
271 SectorStart: target.StartSector,
272 Length: target.Length,
273 Next: targetSize,
274 }
275 if err := stringToDelimitedBuf(targetSpec.TargetType[:], target.Type); err != nil {
276 return err
277 }
278 if err := binary.Write(&data, native_endian.NativeEndian(), &targetSpec); err != nil {
279 panic(err)
280 }
Lorenz Brunb9044c82021-08-24 11:59:47 +0200281 data.WriteString(encodedParams)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200282 data.WriteByte(0x00)
283 for i := 0; i < padding; i++ {
284 data.WriteByte(0x00)
285 }
286 }
287 req.TargetCount = uint32(len(targets))
288 if data.Len() >= 16384 {
289 return errors.New("table too large for allocated memory")
290 }
291 req.DataSize = baseDataSize + uint32(data.Len())
292 copy(req.Data[:], data.Bytes())
Lorenz Brun257acab2021-08-10 12:36:17 +0200293 if readOnly {
294 req.Flags = DM_READONLY_FLAG
295 }
Lorenz Brun16a981d2019-09-16 11:26:05 +0200296 fd, err := getFd()
297 if err != nil {
298 return err
299 }
300 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_TABLE_LOAD_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
301 return err
302 }
303 runtime.KeepAlive(req)
304 return nil
305}
306
307func suspendResume(name string, suspend bool) error {
308 req := newReq()
309 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
310 return err
311 }
312 if suspend {
313 req.Flags = DM_SUSPEND_FLAG
314 }
315 fd, err := getFd()
316 if err != nil {
317 return err
318 }
319 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_DEV_SUSPEND_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
320 return err
321 }
322 runtime.KeepAlive(req)
323 return nil
324}
325
326func Suspend(name string) error {
327 return suspendResume(name, true)
328}
329func Resume(name string) error {
330 return suspendResume(name, false)
331}
332
Lorenz Brun257acab2021-08-10 12:36:17 +0200333func CreateActiveDevice(name string, readOnly bool, targets []Target) (uint64, error) {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200334 dev, err := CreateDevice(name)
335 if err != nil {
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200336 return 0, fmt.Errorf("DM_DEV_CREATE failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200337 }
Lorenz Brun257acab2021-08-10 12:36:17 +0200338 if err := LoadTable(name, readOnly, targets); err != nil {
Leopold Schabel68c58752019-11-14 21:00:59 +0100339 _ = RemoveDevice(name)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200340 return 0, fmt.Errorf("DM_TABLE_LOAD failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200341 }
342 if err := Resume(name); err != nil {
Leopold Schabel68c58752019-11-14 21:00:59 +0100343 _ = RemoveDevice(name)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200344 return 0, fmt.Errorf("DM_DEV_SUSPEND failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200345 }
346 return dev, nil
347}