1
2
3
4 package devicemapper
5
6 import (
7 "errors"
8 "flag"
9 "os"
10 "syscall"
11 "testing"
12 "unsafe"
13
14 "golang.org/x/sys/unix"
15 )
16
17 var (
18 integration = flag.Bool("integration", false, "run integration tests")
19 )
20
21 func clearTestDependencies() {
22 removeDeviceWrapper = removeDevice
23 openMapperWrapper = openMapper
24 }
25
26 func TestMain(m *testing.M) {
27 flag.Parse()
28 m.Run()
29 }
30
31 func validateDevice(t *testing.T, p string, sectors int64, writable bool) {
32 t.Helper()
33 dev, err := os.OpenFile(p, os.O_RDWR|os.O_SYNC, 0)
34 if err != nil {
35 t.Fatal(err)
36 }
37 defer dev.Close()
38
39 var size int64
40 _, _, errno := unix.Syscall(unix.SYS_IOCTL, dev.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&size)))
41 if errno != 0 {
42 t.Fatal(errno)
43 }
44 if size != sectors*512 {
45 t.Fatalf("expected %d bytes, got %d", sectors*512, size)
46 }
47
48 var b [512]byte
49 _, err = unix.Read(int(dev.Fd()), b[:])
50 if !errors.Is(err, unix.EIO) {
51 t.Fatalf("expected EIO, got %s", err)
52 }
53 _, err = unix.Write(int(dev.Fd()), b[:])
54 if writable && !errors.Is(err, unix.EIO) {
55 t.Fatalf("expected EIO, got %s", err)
56 } else if !errors.Is(err, unix.EPERM) {
57 t.Fatalf("expected EPERM, got %s", err)
58 }
59 }
60
61 type device struct {
62 Name, Path string
63 }
64
65 func (d *device) Close() (err error) {
66 if d.Name != "" {
67 err = RemoveDevice(d.Name)
68 if err == nil {
69 d.Name = ""
70 }
71 }
72 return err
73 }
74
75 func createDevice(name string, flags CreateFlags, targets []Target) (*device, error) {
76 p, err := CreateDevice(name, flags, targets)
77 if err != nil {
78 return nil, err
79 }
80 return &device{Name: name, Path: p}, nil
81 }
82
83 func TestCreateError(t *testing.T) {
84 clearTestDependencies()
85
86 if !*integration {
87 t.Skip()
88 }
89 d, err := createDevice("test-device", 0, []Target{
90 {Type: "error", SectorStart: 0, LengthInBlocks: 1},
91 {Type: "error", SectorStart: 1, LengthInBlocks: 2},
92 })
93 if err != nil {
94 t.Fatal(err)
95 }
96 defer d.Close()
97 validateDevice(t, d.Path, 3, true)
98 err = d.Close()
99 if err != nil {
100 t.Fatal(err)
101 }
102 }
103
104 func TestReadOnlyError(t *testing.T) {
105 clearTestDependencies()
106
107 if !*integration {
108 t.Skip()
109 }
110 d, err := createDevice("test-device", CreateReadOnly, []Target{
111 {Type: "error", SectorStart: 0, LengthInBlocks: 1},
112 {Type: "error", SectorStart: 1, LengthInBlocks: 2},
113 })
114 if err != nil {
115 t.Fatal(err)
116 }
117 defer d.Close()
118 validateDevice(t, d.Path, 3, false)
119 err = d.Close()
120 if err != nil {
121 t.Fatal(err)
122 }
123 }
124
125 func TestLinearError(t *testing.T) {
126 clearTestDependencies()
127
128 if !*integration {
129 t.Skip()
130 }
131 b, err := createDevice("base-device", 0, []Target{
132 {Type: "error", SectorStart: 0, LengthInBlocks: 100},
133 })
134 if err != nil {
135 t.Fatal(err)
136 }
137 defer b.Close()
138 d, err := createDevice("linear-device", 0, []Target{
139 LinearTarget(0, 50, b.Path, 50),
140 })
141 if err != nil {
142 t.Fatal(err)
143 }
144 defer d.Close()
145 validateDevice(t, d.Path, 50, true)
146 err = d.Close()
147 if err != nil {
148 t.Fatal(err)
149 }
150 err = b.Close()
151 if err != nil {
152 t.Fatal(err)
153 }
154 }
155
156 func TestRemoveDeviceRetriesOnSyscallEBUSY(t *testing.T) {
157 clearTestDependencies()
158
159 rmDeviceCalled := false
160 retryDone := false
161
162 openMapperWrapper = func() (*os.File, error) {
163 return os.CreateTemp("", "")
164 }
165 removeDeviceWrapper = func(_ *os.File, _ string) error {
166 if !rmDeviceCalled {
167 rmDeviceCalled = true
168 return &dmError{
169 Op: 1,
170 Err: syscall.EBUSY,
171 }
172 }
173 if !retryDone {
174 retryDone = true
175 return nil
176 }
177 return nil
178 }
179
180 if err := RemoveDevice("test"); err != nil {
181 t.Fatalf("expected no error, got: %s", err)
182 }
183 if !rmDeviceCalled {
184 t.Fatalf("expected removeDevice to be called at least once")
185 }
186 if !retryDone {
187 t.Fatalf("expected removeDevice to be retried after initial failure")
188 }
189 }
190
191 func TestRemoveDeviceFailsOnNonSyscallEBUSY(t *testing.T) {
192 clearTestDependencies()
193
194 expectedError := &dmError{
195 Op: 0,
196 Err: syscall.EACCES,
197 }
198 rmDeviceCalled := false
199 retryDone := false
200 openMapperWrapper = func() (*os.File, error) {
201 return os.CreateTemp("", "")
202 }
203 removeDeviceWrapper = func(_ *os.File, _ string) error {
204 if !rmDeviceCalled {
205 rmDeviceCalled = true
206 return expectedError
207 }
208 if !retryDone {
209 retryDone = true
210 return nil
211 }
212 return nil
213 }
214
215 if err := RemoveDevice("test"); err != expectedError {
216 t.Fatalf("expected error %q, instead got %q", expectedError, err)
217 }
218 if !rmDeviceCalled {
219 t.Fatalf("expected removeDevice to be called once")
220 }
221 if retryDone {
222 t.Fatalf("no retries should've been attempted")
223 }
224 }
225
View as plain text