blob: ef101de9f399825e112e82c0ab8fdea3dd0a0259 [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
17package main
18
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")
141 if err != nil {
142 return 0, err
143 }
144 fd = f.Fd()
145 return f.Fd(), nil
146 }
147 return fd, nil
148}
149
150func GetVersion() (Version, error) {
151 req := newReq()
152 fd, err := getFd()
153 if err != nil {
154 return Version{}, err
155 }
156 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_VERSION_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
157 return Version{}, err
158 }
159 return req.Version, nil
160}
161
162func CreateDevice(name string) (uint64, error) {
163 req := newReq()
164 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
165 return 0, err
166 }
167 fd, err := getFd()
168 if err != nil {
169 return 0, err
170 }
171 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_DEV_CREATE_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
172 return 0, err
173 }
174 return req.Dev, nil
175}
176
177func RemoveDevice(name string) error {
178 req := newReq()
179 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
180 return err
181 }
182 fd, err := getFd()
183 if err != nil {
184 return err
185 }
186 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_DEV_REMOVE_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
187 return err
188 }
189 runtime.KeepAlive(req)
190 return nil
191}
192
193type Target struct {
194 StartSector uint64
195 Length uint64
196 Type string
197 Parameters string
198}
199
200func LoadTable(name string, targets []Target) error {
201 req := newReq()
202 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
203 return err
204 }
205 var data bytes.Buffer
206 for _, target := range targets {
207 // Gives the size of the spec and the null-terminated params aligned to 8 bytes
208 padding := len(target.Parameters) % 8
209 targetSize := uint32(int(unsafe.Sizeof(DMTargetSpec{})) + (len(target.Parameters) + 1) + padding)
210
211 targetSpec := DMTargetSpec{
212 SectorStart: target.StartSector,
213 Length: target.Length,
214 Next: targetSize,
215 }
216 if err := stringToDelimitedBuf(targetSpec.TargetType[:], target.Type); err != nil {
217 return err
218 }
219 if err := binary.Write(&data, native_endian.NativeEndian(), &targetSpec); err != nil {
220 panic(err)
221 }
222 data.WriteString(target.Parameters)
223 data.WriteByte(0x00)
224 for i := 0; i < padding; i++ {
225 data.WriteByte(0x00)
226 }
227 }
228 req.TargetCount = uint32(len(targets))
229 if data.Len() >= 16384 {
230 return errors.New("table too large for allocated memory")
231 }
232 req.DataSize = baseDataSize + uint32(data.Len())
233 copy(req.Data[:], data.Bytes())
234 fd, err := getFd()
235 if err != nil {
236 return err
237 }
238 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_TABLE_LOAD_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
239 return err
240 }
241 runtime.KeepAlive(req)
242 return nil
243}
244
245func suspendResume(name string, suspend bool) error {
246 req := newReq()
247 if err := stringToDelimitedBuf(req.Name[:], name); err != nil {
248 return err
249 }
250 if suspend {
251 req.Flags = DM_SUSPEND_FLAG
252 }
253 fd, err := getFd()
254 if err != nil {
255 return err
256 }
257 if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, DM_DEV_SUSPEND_CMD, uintptr(unsafe.Pointer(&req))); err != 0 {
258 return err
259 }
260 runtime.KeepAlive(req)
261 return nil
262}
263
264func Suspend(name string) error {
265 return suspendResume(name, true)
266}
267func Resume(name string) error {
268 return suspendResume(name, false)
269}
270
271func CreateActiveDevice(name string, targets []Target) (uint64, error) {
272 dev, err := CreateDevice(name)
273 if err != nil {
274 return 0, errors.Wrap(err, "DM_DEV_CREATE failed")
275 }
276 if err := LoadTable(name, targets); err != nil {
277 RemoveDevice(name)
278 return 0, errors.Wrap(err, "DM_TABLE_LOAD failed")
279 }
280 if err := Resume(name); err != nil {
281 RemoveDevice(name)
282 return 0, errors.Wrap(err, "DM_DEV_SUSPEND failed")
283 }
284 return dev, nil
285}