blob: dec626016d0755676c52b71f8f69a462b8b86094 [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
Lorenz Brunae0d90d2019-09-05 17:53:56 +020017package devicemapper
Lorenz Brun16a981d2019-09-16 11:26:05 +020018
19import (
20 "bytes"
21 "encoding/binary"
22 "fmt"
23 "os"
24 "runtime"
25 "unsafe"
26
27 "github.com/pkg/errors"
28 "github.com/yalue/native_endian"
29 "golang.org/x/sys/unix"
30)
31
32type DMIoctl struct {
33 Version Version
34 DataSize uint32
35 DataStart uint32
36 TargetCount uint32
37 OpenCount int32
38 Flags uint32
39 EventNumber uint32
40 _padding1 uint32
41 Dev uint64
42 Name [128]byte
43 UUID [129]byte
44 _padding2 [7]byte
45 Data [16384]byte
46}
47
48type DMTargetSpec struct {
49 SectorStart uint64
50 Length uint64
51 Status int32
52 Next uint32
53 TargetType [16]byte
54}
55
56type DMTargetDeps struct {
57 Count uint32
58 Padding uint32
59 Dev []uint64
60}
61
62type DMNameList struct {
63 Dev uint64
64 Next uint32
65 Name []byte
66}
67
68type DMTargetVersions struct {
69 Next uint32
70 Version [3]uint32
71}
72
73type DMTargetMessage struct {
74 Sector uint64
75 Message []byte
76}
77
78type Version [3]uint32
79
80const (
81 /* Top level cmds */
82 DM_VERSION_CMD uintptr = (0xc138fd << 8) + iota
83 DM_REMOVE_ALL_CMD
84 DM_LIST_DEVICES_CMD
85
86 /* device level cmds */
87 DM_DEV_CREATE_CMD
88 DM_DEV_REMOVE_CMD
89 DM_DEV_RENAME_CMD
90 DM_DEV_SUSPEND_CMD
91 DM_DEV_STATUS_CMD
92 DM_DEV_WAIT_CMD
93
94 /* Table level cmds */
95 DM_TABLE_LOAD_CMD
96 DM_TABLE_CLEAR_CMD
97 DM_TABLE_DEPS_CMD
98 DM_TABLE_STATUS_CMD
99
100 /* Added later */
101 DM_LIST_VERSIONS_CMD
102 DM_TARGET_MSG_CMD
103 DM_DEV_SET_GEOMETRY_CMD
104 DM_DEV_ARM_POLL_CMD
105)
106
107const (
108 DM_READONLY_FLAG = 1 << 0 /* In/Out */
109 DM_SUSPEND_FLAG = 1 << 1 /* In/Out */
110 DM_PERSISTENT_DEV_FLAG = 1 << 3 /* In */
111)
112
113const baseDataSize = uint32(unsafe.Sizeof(DMIoctl{})) - 16384
114
115func newReq() DMIoctl {
116 return DMIoctl{
117 Version: Version{4, 0, 0},
118 DataSize: baseDataSize,
119 DataStart: baseDataSize,
120 }
121}
122
123func stringToDelimitedBuf(dst []byte, src string) error {
124 if len(src) > len(dst)-1 {
125 return fmt.Errorf("String longer than target buffer (%v > %v)", len(src), len(dst)-1)
126 }
127 for i := 0; i < len(src); i++ {
128 if src[i] == 0x00 {
129 return errors.New("String contains null byte, this is unsupported by DM")
130 }
131 dst[i] = src[i]
132 }
133 return nil
134}
135
136var fd uintptr
137
138func getFd() (uintptr, error) {
139 if fd == 0 {
140 f, err := os.Open("/dev/mapper/control")
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200141 if os.IsNotExist(err) {
142 os.MkdirAll("/dev/mapper", 0755)
143 if err := unix.Mknod("/dev/mapper/control", unix.S_IFCHR|0600, int(unix.Mkdev(10, 236))); err != nil {
144 return 0, err
145 }
146 f, err = os.Open("/dev/mapper/control")
147 if err != nil {
148 return 0, err
149 }
150 } else if err != nil {
Lorenz Brun16a981d2019-09-16 11:26:05 +0200151 return 0, err
152 }
153 fd = f.Fd()
154 return f.Fd(), nil
155 }
156 return fd, nil
157}
158
159func GetVersion() (Version, error) {
160 req := newReq()
161 fd, err := getFd()
162 if err != nil {
163 return Version{}, err
164 }
165 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_VERSION_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
166 return Version{}, err
167 }
168 return req.Version, nil
169}
170
171func CreateDevice(name string) (uint64, error) {
172 req := newReq()
173 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
174 return 0, err
175 }
176 fd, err := getFd()
177 if err != nil {
178 return 0, err
179 }
180 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_DEV_CREATE_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
181 return 0, err
182 }
183 return req.Dev, nil
184}
185
186func RemoveDevice(name string) error {
187 req := newReq()
188 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
189 return err
190 }
191 fd, err := getFd()
192 if err != nil {
193 return err
194 }
195 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_DEV_REMOVE_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
196 return err
197 }
198 runtime.KeepAlive(req)
199 return nil
200}
201
202type Target struct {
203 StartSector uint64
204 Length uint64
205 Type string
206 Parameters string
207}
208
209func LoadTable(name string, targets []Target) error {
210 req := newReq()
211 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
212 return err
213 }
214 var data bytes.Buffer
215 for _, target := range targets {
216 // Gives the size of the spec and the null-terminated params aligned to 8 bytes
217 padding := len(target.Parameters) % 8
218 targetSize := uint32(int(unsafe.Sizeof(DMTargetSpec{})) + (len(target.Parameters) + 1) + padding)
219
220 targetSpec := DMTargetSpec{
221 SectorStart: target.StartSector,
222 Length: target.Length,
223 Next: targetSize,
224 }
225 if err := stringToDelimitedBuf(targetSpec.TargetType[:], target.Type); err != nil {
226 return err
227 }
228 if err := binary.Write(&data, native_endian.NativeEndian(), &targetSpec); err != nil {
229 panic(err)
230 }
231 data.WriteString(target.Parameters)
232 data.WriteByte(0x00)
233 for i := 0; i < padding; i++ {
234 data.WriteByte(0x00)
235 }
236 }
237 req.TargetCount = uint32(len(targets))
238 if data.Len() >= 16384 {
239 return errors.New("table too large for allocated memory")
240 }
241 req.DataSize = baseDataSize + uint32(data.Len())
242 copy(req.Data[:], data.Bytes())
243 fd, err := getFd()
244 if err != nil {
245 return err
246 }
247 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_TABLE_LOAD_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
248 return err
249 }
250 runtime.KeepAlive(req)
251 return nil
252}
253
254func suspendResume(name string, suspend bool) error {
255 req := newReq()
256 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
257 return err
258 }
259 if suspend {
260 req.Flags = DM_SUSPEND_FLAG
261 }
262 fd, err := getFd()
263 if err != nil {
264 return err
265 }
266 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_DEV_SUSPEND_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
267 return err
268 }
269 runtime.KeepAlive(req)
270 return nil
271}
272
273func Suspend(name string) error {
274 return suspendResume(name, true)
275}
276func Resume(name string) error {
277 return suspendResume(name, false)
278}
279
280func CreateActiveDevice(name string, targets []Target) (uint64, error) {
281 dev, err := CreateDevice(name)
282 if err != nil {
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200283 return 0, fmt.Errorf("DM_DEV_CREATE failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200284 }
285 if err := LoadTable(name, targets); err != nil {
286 RemoveDevice(name)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200287 return 0, fmt.Errorf("DM_TABLE_LOAD failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200288 }
289 if err := Resume(name); err != nil {
290 RemoveDevice(name)
Lorenz Brunae0d90d2019-09-05 17:53:56 +0200291 return 0, fmt.Errorf("DM_DEV_SUSPEND failed: %w", err)
Lorenz Brun16a981d2019-09-16 11:26:05 +0200292 }
293 return dev, nil
294}