blob: d56e8f9f95daa78df81a4577687cab2fe6ce73a5 [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
206type Target struct {
207 StartSector uint64
208 Length uint64
209 Type string
210 Parameters string
211}
212
213func LoadTable(name string, targets []Target) error {
214 req := newReq()
215 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
216 return err
217 }
218 var data bytes.Buffer
219 for _, target := range targets {
220 // Gives the size of the spec and the null-terminated params aligned to 8 bytes
221 padding := len(target.Parameters) % 8
222 targetSize := uint32(int(unsafe.Sizeof(DMTargetSpec{})) + (len(target.Parameters) + 1) + padding)
223
224 targetSpec := DMTargetSpec{
225 SectorStart: target.StartSector,
226 Length: target.Length,
227 Next: targetSize,
228 }
229 if err := stringToDelimitedBuf(targetSpec.TargetType[:], target.Type); err != nil {
230 return err
231 }
232 if err := binary.Write(&data, native_endian.NativeEndian(), &targetSpec); err != nil {
233 panic(err)
234 }
235 data.WriteString(target.Parameters)
236 data.WriteByte(0x00)
237 for i := 0; i < padding; i++ {
238 data.WriteByte(0x00)
239 }
240 }
241 req.TargetCount = uint32(len(targets))
242 if data.Len() >= 16384 {
243 return errors.New("table too large for allocated memory")
244 }
245 req.DataSize = baseDataSize + uint32(data.Len())
246 copy(req.Data[:], data.Bytes())
247 fd, err := getFd()
248 if err != nil {
249 return err
250 }
251 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_TABLE_LOAD_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
252 return err
253 }
254 runtime.KeepAlive(req)
255 return nil
256}
257
258func suspendResume(name string, suspend bool) error {
259 req := newReq()
260 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
261 return err
262 }
263 if suspend {
264 req.Flags = DM_SUSPEND_FLAG
265 }
266 fd, err := getFd()
267 if err != nil {
268 return err
269 }
270 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_DEV_SUSPEND_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
271 return err
272 }
273 runtime.KeepAlive(req)
274 return nil
275}
276
277func Suspend(name string) error {
278 return suspendResume(name, true)
279}
280func Resume(name string) error {
281 return suspendResume(name, false)
282}
283
284func CreateActiveDevice(name string, targets []Target) (uint64, error) {
285 dev, err := CreateDevice(name)
286 if err != nil {
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200287 return 0, fmt.Errorf("DM_DEV_CREATE failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200288 }
289 if err := LoadTable(name, targets); err != nil {
Leopold Schabel68c58752019-11-14 21:00:59 +0100290 _ = RemoveDevice(name)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200291 return 0, fmt.Errorf("DM_TABLE_LOAD failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200292 }
293 if err := Resume(name); err != nil {
Leopold Schabel68c58752019-11-14 21:00:59 +0100294 _ = RemoveDevice(name)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200295 return 0, fmt.Errorf("DM_DEV_SUSPEND failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200296 }
297 return dev, nil
298}