blob: 58076763711610d84278154eb6cffd18f6e80fcf [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"
27 "unsafe"
28
29 "github.com/pkg/errors"
30 "github.com/yalue/native_endian"
31 "golang.org/x/sys/unix"
32)
33
34type DMIoctl struct {
35 Version Version
36 DataSize uint32
37 DataStart uint32
38 TargetCount uint32
39 OpenCount int32
40 Flags uint32
41 EventNumber uint32
42 _padding1 uint32
43 Dev uint64
44 Name [128]byte
45 UUID [129]byte
46 _padding2 [7]byte
47 Data [16384]byte
48}
49
50type DMTargetSpec struct {
51 SectorStart uint64
52 Length uint64
53 Status int32
54 Next uint32
55 TargetType [16]byte
56}
57
58type DMTargetDeps struct {
59 Count uint32
60 Padding uint32
61 Dev []uint64
62}
63
64type DMNameList struct {
65 Dev uint64
66 Next uint32
67 Name []byte
68}
69
70type DMTargetVersions struct {
71 Next uint32
72 Version [3]uint32
73}
74
75type DMTargetMessage struct {
76 Sector uint64
77 Message []byte
78}
79
80type Version [3]uint32
81
82const (
83 /* Top level cmds */
84 DM_VERSION_CMD uintptr = (0xc138fd << 8) + iota
85 DM_REMOVE_ALL_CMD
86 DM_LIST_DEVICES_CMD
87
88 /* device level cmds */
89 DM_DEV_CREATE_CMD
90 DM_DEV_REMOVE_CMD
91 DM_DEV_RENAME_CMD
92 DM_DEV_SUSPEND_CMD
93 DM_DEV_STATUS_CMD
94 DM_DEV_WAIT_CMD
95
96 /* Table level cmds */
97 DM_TABLE_LOAD_CMD
98 DM_TABLE_CLEAR_CMD
99 DM_TABLE_DEPS_CMD
100 DM_TABLE_STATUS_CMD
101
102 /* Added later */
103 DM_LIST_VERSIONS_CMD
104 DM_TARGET_MSG_CMD
105 DM_DEV_SET_GEOMETRY_CMD
106 DM_DEV_ARM_POLL_CMD
107)
108
109const (
110 DM_READONLY_FLAG = 1 << 0 /* In/Out */
111 DM_SUSPEND_FLAG = 1 << 1 /* In/Out */
112 DM_PERSISTENT_DEV_FLAG = 1 << 3 /* In */
113)
114
115const baseDataSize = uint32(unsafe.Sizeof(DMIoctl{})) - 16384
116
117func newReq() DMIoctl {
118 return DMIoctl{
119 Version: Version{4, 0, 0},
120 DataSize: baseDataSize,
121 DataStart: baseDataSize,
122 }
123}
124
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200125// stringToDelimitedBuf copies src to dst and returns an error if len(src) >
126// len(dst), or when the string contains a null byte.
Lorenz Brun16a981d2019-09-16 11:26:05 +0200127func stringToDelimitedBuf(dst []byte, src string) error {
128 if len(src) > len(dst)-1 {
Leopold Schabel68c58752019-11-14 21:00:59 +0100129 return fmt.Errorf("string longer than target buffer (%v > %v)", len(src), len(dst)-1)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200130 }
131 for i := 0; i < len(src); i++ {
132 if src[i] == 0x00 {
Leopold Schabel68c58752019-11-14 21:00:59 +0100133 return errors.New("string contains null byte, this is unsupported by DM")
Lorenz Brun16a981d2019-09-16 11:26:05 +0200134 }
135 dst[i] = src[i]
136 }
137 return nil
138}
139
140var fd uintptr
141
142func getFd() (uintptr, error) {
143 if fd == 0 {
144 f, err := os.Open("/dev/mapper/control")
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200145 if os.IsNotExist(err) {
Leopold Schabel68c58752019-11-14 21:00:59 +0100146 _ = os.MkdirAll("/dev/mapper", 0755)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200147 if err := unix.Mknod("/dev/mapper/control", unix.S_IFCHR|0600, int(unix.Mkdev(10, 236))); err != nil {
148 return 0, err
149 }
150 f, err = os.Open("/dev/mapper/control")
151 if err != nil {
152 return 0, err
153 }
154 } else if err != nil {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200155 return 0, err
156 }
157 fd = f.Fd()
158 return f.Fd(), nil
159 }
160 return fd, nil
161}
162
163func GetVersion() (Version, error) {
164 req := newReq()
165 fd, err := getFd()
166 if err != nil {
167 return Version{}, err
168 }
169 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_VERSION_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
170 return Version{}, err
171 }
172 return req.Version, nil
173}
174
175func CreateDevice(name string) (uint64, error) {
176 req := newReq()
177 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
178 return 0, err
179 }
180 fd, err := getFd()
181 if err != nil {
182 return 0, err
183 }
184 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_DEV_CREATE_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
185 return 0, err
186 }
187 return req.Dev, nil
188}
189
190func RemoveDevice(name string) error {
191 req := newReq()
192 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
193 return err
194 }
195 fd, err := getFd()
196 if err != nil {
197 return err
198 }
199 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_DEV_REMOVE_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
200 return err
201 }
202 runtime.KeepAlive(req)
203 return nil
204}
205
Lorenz Brun257acab2021-08-10 12:36:17 +0200206// Target represents a byte region inside a devicemapper table for a given
207// device provided by a given target implementation.
Lorenz Brun16a981d2019-09-16 11:26:05 +0200208type Target struct {
Lorenz Brun257acab2021-08-10 12:36:17 +0200209 // StartSector is the first sector (defined as being 512 bytes long) this
210 // target covers.
Lorenz Brun16a981d2019-09-16 11:26:05 +0200211 StartSector uint64
Lorenz Brun257acab2021-08-10 12:36:17 +0200212 // Length is the number of sectors (defined as being 512 bytes long) this
213 // target covers, starting from StartSector.
214 Length uint64
215 // Type is the type of target handling this byte region.
216 // Types implemented by the Linux kernel can be found at
217 // @linux//drivers/md/... by looking for dm_register_target() calls.
218 Type string
219 // Parameters are additional parameters specific to the target type.
220 Parameters string
Lorenz Brun16a981d2019-09-16 11:26:05 +0200221}
222
Lorenz Brun257acab2021-08-10 12:36:17 +0200223func LoadTable(name string, readOnly bool, targets []Target) error {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200224 req := newReq()
225 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
226 return err
227 }
228 var data bytes.Buffer
229 for _, target := range targets {
230 // Gives the size of the spec and the null-terminated params aligned to 8 bytes
231 padding := len(target.Parameters) % 8
232 targetSize := uint32(int(unsafe.Sizeof(DMTargetSpec{})) + (len(target.Parameters) + 1) + padding)
233
234 targetSpec := DMTargetSpec{
235 SectorStart: target.StartSector,
236 Length: target.Length,
237 Next: targetSize,
238 }
239 if err := stringToDelimitedBuf(targetSpec.TargetType[:], target.Type); err != nil {
240 return err
241 }
242 if err := binary.Write(&data, native_endian.NativeEndian(), &targetSpec); err != nil {
243 panic(err)
244 }
245 data.WriteString(target.Parameters)
246 data.WriteByte(0x00)
247 for i := 0; i < padding; i++ {
248 data.WriteByte(0x00)
249 }
250 }
251 req.TargetCount = uint32(len(targets))
252 if data.Len() >= 16384 {
253 return errors.New("table too large for allocated memory")
254 }
255 req.DataSize = baseDataSize + uint32(data.Len())
256 copy(req.Data[:], data.Bytes())
Lorenz Brun257acab2021-08-10 12:36:17 +0200257 if readOnly {
258 req.Flags = DM_READONLY_FLAG
259 }
Lorenz Brun16a981d2019-09-16 11:26:05 +0200260 fd, err := getFd()
261 if err != nil {
262 return err
263 }
264 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_TABLE_LOAD_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
265 return err
266 }
267 runtime.KeepAlive(req)
268 return nil
269}
270
271func suspendResume(name string, suspend bool) error {
272 req := newReq()
273 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
274 return err
275 }
276 if suspend {
277 req.Flags = DM_SUSPEND_FLAG
278 }
279 fd, err := getFd()
280 if err != nil {
281 return err
282 }
283 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_DEV_SUSPEND_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
284 return err
285 }
286 runtime.KeepAlive(req)
287 return nil
288}
289
290func Suspend(name string) error {
291 return suspendResume(name, true)
292}
293func Resume(name string) error {
294 return suspendResume(name, false)
295}
296
Lorenz Brun257acab2021-08-10 12:36:17 +0200297func CreateActiveDevice(name string, readOnly bool, targets []Target) (uint64, error) {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200298 dev, err := CreateDevice(name)
299 if err != nil {
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200300 return 0, fmt.Errorf("DM_DEV_CREATE failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200301 }
Lorenz Brun257acab2021-08-10 12:36:17 +0200302 if err := LoadTable(name, readOnly, targets); err != nil {
Leopold Schabel68c58752019-11-14 21:00:59 +0100303 _ = RemoveDevice(name)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200304 return 0, fmt.Errorf("DM_TABLE_LOAD failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200305 }
306 if err := Resume(name); err != nil {
Leopold Schabel68c58752019-11-14 21:00:59 +0100307 _ = RemoveDevice(name)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200308 return 0, fmt.Errorf("DM_DEV_SUSPEND failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200309 }
310 return dev, nil
311}