aboutsummaryrefslogtreecommitdiffhomepage
path: root/container/initbind_test.go
blob: 323a03282ec3676cf1e4df918a88628e798b7fa1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
package container

import (
	"errors"
	"os"
	"syscall"
	"testing"

	"hakurei.app/container/bits"
	"hakurei.app/container/check"
	"hakurei.app/container/stub"
)

func TestBindMountOp(t *testing.T) {
	t.Parallel()

	checkOpBehaviour(t, []opBehaviourTestCase{
		{"ENOENT not optional", new(Params), &BindMountOp{
			Source: check.MustAbs("/bin/"),
			Target: check.MustAbs("/bin/"),
		}, []stub.Call{
			call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "", syscall.ENOENT),
		}, syscall.ENOENT, nil, nil},

		{"skip optional", new(Params), &BindMountOp{
			Source: check.MustAbs("/bin/"),
			Target: check.MustAbs("/bin/"),
			Flags:  bits.BindOptional,
		}, []stub.Call{
			call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "", syscall.ENOENT),
		}, nil, nil, nil},

		{"success optional", new(Params), &BindMountOp{
			Source: check.MustAbs("/bin/"),
			Target: check.MustAbs("/bin/"),
			Flags:  bits.BindOptional,
		}, []stub.Call{
			call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
		}, nil, []stub.Call{
			call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil),
			call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil),
			call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005)}}, nil, nil),
			call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil),
		}, nil},

		{"ensureFile device", new(Params), &BindMountOp{
			Source: check.MustAbs("/dev/null"),
			Target: check.MustAbs("/dev/null"),
			Flags:  bits.BindWritable | bits.BindDevice,
		}, []stub.Call{
			call("evalSymlinks", stub.ExpectArgs{"/dev/null"}, "/dev/null", nil),
		}, nil, []stub.Call{
			call("stat", stub.ExpectArgs{"/host/dev/null"}, isDirFi(false), nil),
			call("ensureFile", stub.ExpectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, stub.UniqueError(5)),
		}, stub.UniqueError(5)},

		{"mkdirAll ensure", new(Params), &BindMountOp{
			Source: check.MustAbs("/bin/"),
			Target: check.MustAbs("/bin/"),
			Flags:  bits.BindEnsure,
		}, []stub.Call{
			call("mkdirAll", stub.ExpectArgs{"/bin/", os.FileMode(0700)}, nil, stub.UniqueError(4)),
		}, stub.UniqueError(4), nil, nil},

		{"success ensure", new(Params), &BindMountOp{
			Source: check.MustAbs("/bin/"),
			Target: check.MustAbs("/usr/bin/"),
			Flags:  bits.BindEnsure,
		}, []stub.Call{
			call("mkdirAll", stub.ExpectArgs{"/bin/", os.FileMode(0700)}, nil, nil),
			call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
		}, nil, []stub.Call{
			call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil),
			call("mkdirAll", stub.ExpectArgs{"/sysroot/usr/bin", os.FileMode(0700)}, nil, nil),
			call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/usr/bin", "/sysroot/usr/bin", uintptr(0x4005)}}, nil, nil),
			call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/usr/bin", uintptr(0x4005), false}, nil, nil),
		}, nil},

		{"success device ro", new(Params), &BindMountOp{
			Source: check.MustAbs("/dev/null"),
			Target: check.MustAbs("/dev/null"),
			Flags:  bits.BindDevice,
		}, []stub.Call{
			call("evalSymlinks", stub.ExpectArgs{"/dev/null"}, "/dev/null", nil),
		}, nil, []stub.Call{
			call("stat", stub.ExpectArgs{"/host/dev/null"}, isDirFi(false), nil),
			call("ensureFile", stub.ExpectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
			call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot/dev/null", uintptr(0x4001)}}, nil, nil),
			call("bindMount", stub.ExpectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0x4001), false}, nil, nil),
		}, nil},

		{"success device", new(Params), &BindMountOp{
			Source: check.MustAbs("/dev/null"),
			Target: check.MustAbs("/dev/null"),
			Flags:  bits.BindWritable | bits.BindDevice,
		}, []stub.Call{
			call("evalSymlinks", stub.ExpectArgs{"/dev/null"}, "/dev/null", nil),
		}, nil, []stub.Call{
			call("stat", stub.ExpectArgs{"/host/dev/null"}, isDirFi(false), nil),
			call("ensureFile", stub.ExpectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
			call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot/dev/null", uintptr(0x4000)}}, nil, nil),
			call("bindMount", stub.ExpectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0x4000), false}, nil, nil),
		}, nil},

		{"evalSymlinks", new(Params), &BindMountOp{
			Source: check.MustAbs("/bin/"),
			Target: check.MustAbs("/bin/"),
		}, []stub.Call{
			call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", stub.UniqueError(3)),
		}, stub.UniqueError(3), nil, nil},

		{"stat", new(Params), &BindMountOp{
			Source: check.MustAbs("/bin/"),
			Target: check.MustAbs("/bin/"),
		}, []stub.Call{
			call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
		}, nil, []stub.Call{
			call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), stub.UniqueError(2)),
		}, stub.UniqueError(2)},

		{"mkdirAll", new(Params), &BindMountOp{
			Source: check.MustAbs("/bin/"),
			Target: check.MustAbs("/bin/"),
		}, []stub.Call{
			call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
		}, nil, []stub.Call{
			call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil),
			call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, stub.UniqueError(1)),
		}, stub.UniqueError(1)},

		{"bindMount", new(Params), &BindMountOp{
			Source: check.MustAbs("/bin/"),
			Target: check.MustAbs("/bin/"),
		}, []stub.Call{
			call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
		}, nil, []stub.Call{
			call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil),
			call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil),
			call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005)}}, nil, nil),
			call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, stub.UniqueError(0)),
		}, stub.UniqueError(0)},

		{"success eval equals", new(Params), &BindMountOp{
			Source: check.MustAbs("/bin/"),
			Target: check.MustAbs("/bin/"),
		}, []stub.Call{
			call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/bin", nil),
		}, nil, []stub.Call{
			call("stat", stub.ExpectArgs{"/host/bin"}, isDirFi(true), nil),
			call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil),
			call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/bin", "/sysroot/bin", uintptr(0x4005)}}, nil, nil),
			call("bindMount", stub.ExpectArgs{"/host/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil),
		}, nil},

		{"success", new(Params), &BindMountOp{
			Source: check.MustAbs("/bin/"),
			Target: check.MustAbs("/bin/"),
		}, []stub.Call{
			call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/usr/bin", nil),
		}, nil, []stub.Call{
			call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil),
			call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil),
			call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005)}}, nil, nil),
			call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil),
		}, nil},
	})

	t.Run("unreachable", func(t *testing.T) {
		t.Parallel()

		t.Run("nil sourceFinal not optional", func(t *testing.T) {
			t.Parallel()
			wantErr := OpStateError("bind")
			if err := new(BindMountOp).apply(nil, nil); !errors.Is(err, wantErr) {
				t.Errorf("apply: error = %v, want %v", err, wantErr)
			}
		})
	})

	checkOpsValid(t, []opValidTestCase{
		{"nil", (*BindMountOp)(nil), false},
		{"zero", new(BindMountOp), false},
		{"nil source", &BindMountOp{Target: check.MustAbs("/")}, false},
		{"nil target", &BindMountOp{Source: check.MustAbs("/")}, false},
		{"flag optional ensure", &BindMountOp{Source: check.MustAbs("/"), Target: check.MustAbs("/"), Flags: bits.BindOptional | bits.BindEnsure}, false},
		{"valid", &BindMountOp{Source: check.MustAbs("/"), Target: check.MustAbs("/")}, true},
	})

	checkOpsBuilder(t, []opsBuilderTestCase{
		{"autoetc", new(Ops).Bind(
			check.MustAbs("/etc/"),
			check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
			0,
		), Ops{
			&BindMountOp{
				Source: check.MustAbs("/etc/"),
				Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
			},
		}},
	})

	checkOpIs(t, []opIsTestCase{
		{"zero", new(BindMountOp), new(BindMountOp), false},

		{"internal ne", &BindMountOp{
			Source: check.MustAbs("/etc/"),
			Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
		}, &BindMountOp{
			Source:      check.MustAbs("/etc/"),
			Target:      check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
			sourceFinal: check.MustAbs("/etc/"),
		}, true},

		{"flags differs", &BindMountOp{
			Source: check.MustAbs("/etc/"),
			Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
		}, &BindMountOp{
			Source: check.MustAbs("/etc/"),
			Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
			Flags:  bits.BindOptional,
		}, false},

		{"source differs", &BindMountOp{
			Source: check.MustAbs("/.hakurei/etc/"),
			Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
		}, &BindMountOp{
			Source: check.MustAbs("/etc/"),
			Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
		}, false},

		{"target differs", &BindMountOp{
			Source: check.MustAbs("/etc/"),
			Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
		}, &BindMountOp{
			Source: check.MustAbs("/etc/"),
			Target: check.MustAbs("/etc/"),
		}, false},

		{"equals", &BindMountOp{
			Source: check.MustAbs("/etc/"),
			Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
		}, &BindMountOp{
			Source: check.MustAbs("/etc/"),
			Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
		}, true},
	})

	checkOpMeta(t, []opMetaTestCase{
		{"invalid", new(BindMountOp), "mounting", "<invalid>"},

		{"autoetc", &BindMountOp{
			Source: check.MustAbs("/etc/"),
			Target: check.MustAbs("/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659"),
		}, "mounting", `"/etc/" on "/etc/.host/048090b6ed8f9ebb10e275ff5d8c0659" flags 0x0`},

		{"hostdev", &BindMountOp{
			Source: check.MustAbs("/dev/"),
			Target: check.MustAbs("/dev/"),
			Flags:  bits.BindWritable | bits.BindDevice,
		}, "mounting", `"/dev/" flags 0x6`},
	})
}