1
2
3
4
19
20 package continuity
21
22 import (
23 "bytes"
24 _ "crypto/sha256"
25 "errors"
26 "fmt"
27 "math/rand"
28 "os"
29 "path/filepath"
30 "sort"
31 "syscall"
32 "testing"
33
34 "github.com/containerd/continuity/devices"
35 "github.com/opencontainers/go-digest"
36 )
37
38
39
40
41
42
43 func TestWalkFS(t *testing.T) {
44 rand.Seed(1)
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 testResources := []dresource{
62 {
63 path: "a",
64 mode: 0o644,
65 },
66 {
67 kind: rhardlink,
68 path: "a-hardlink",
69 target: "a",
70 },
71 {
72 kind: rdirectory,
73 path: "b",
74 mode: 0o755,
75 },
76 {
77 kind: rhardlink,
78 path: "b/a-hardlink",
79 target: "a",
80 },
81 {
82 path: "b/a",
83 mode: 0o600 | os.ModeSticky,
84 },
85 {
86 kind: rdirectory,
87 path: "c",
88 mode: 0o755,
89 },
90 {
91 path: "c/a",
92 mode: 0o644,
93 },
94 {
95 kind: rrelsymlink,
96 path: "c/ca-relsymlink",
97 mode: 0o600,
98 target: "a",
99 },
100 {
101 kind: rrelsymlink,
102 path: "c/a-relsymlink",
103 mode: 0o600,
104 target: "../a",
105 },
106 {
107 kind: rabssymlink,
108 path: "c/a-abssymlink",
109 mode: 0o600,
110 target: "a",
111 },
112
113
114
115
116
117
118
119
120
121
122
123
124
125 {
126 kind: rnamedpipe,
127 path: "fifo",
128 mode: 0o666 | os.ModeNamedPipe,
129 },
130
131 {
132 kind: rdirectory,
133 path: "/dev",
134 mode: 0o755,
135 },
136
137
138
139
140
141
142 }
143
144 root := t.TempDir()
145 generateTestFiles(t, root, testResources)
146
147 ctx, err := NewContext(root)
148 if err != nil {
149 t.Fatalf("error getting context: %v", err)
150 }
151
152 m, err := BuildManifest(ctx)
153 if err != nil {
154 t.Fatalf("error building manifest: %v", err)
155 }
156
157 var b bytes.Buffer
158 MarshalText(&b, m)
159 t.Log(b.String())
160
161
162
163
164
165
166 expectedResources, err := expectedResourceList(root, testResources)
167 if err != nil {
168
169 t.Fatalf("error creating resource list: %v", err)
170 }
171
172
173 diff := diffResourceList(expectedResources, m.Resources)
174 if diff.HasDiff() {
175 t.Log("Resource list difference")
176 for _, a := range diff.Additions {
177 t.Logf("Unexpected resource: %#v", a)
178 }
179 for _, d := range diff.Deletions {
180 t.Logf("Missing resource: %#v", d)
181 }
182 for _, u := range diff.Updates {
183 t.Logf("Changed resource:\n\tExpected: %#v\n\tActual: %#v", u.Original, u.Updated)
184 }
185
186 t.FailNow()
187 }
188 }
189
190
191
192
193 type kind int
194
195 func (k kind) String() string {
196 switch k {
197 case rfile:
198 return "file"
199 case rdirectory:
200 return "directory"
201 case rhardlink:
202 return "hardlink"
203 case rchardev:
204 return "chardev"
205 case rnamedpipe:
206 return "namedpipe"
207 }
208
209 panic(fmt.Sprintf("unknown kind: %v", int(k)))
210 }
211
212 const (
213 rfile kind = iota
214 rdirectory
215 rhardlink
216 rrelsymlink
217 rabssymlink
218 rchardev
219 rnamedpipe
220 )
221
222 type dresource struct {
223 kind kind
224 path string
225 mode os.FileMode
226 target string
227 digest digest.Digest
228 size int
229 uid int64
230 gid int64
231 major, minor int
232 }
233
234 func generateTestFiles(t *testing.T, root string, resources []dresource) {
235 for i, resource := range resources {
236 p := filepath.Join(root, resource.path)
237 switch resource.kind {
238 case rfile:
239 size := rand.Intn(4 << 20)
240 d := make([]byte, size)
241 randomBytes(d)
242 dgst := digest.FromBytes(d)
243 resources[i].digest = dgst
244 resources[i].size = size
245
246
247 if err := os.WriteFile(p, d, resource.mode); err != nil {
248 t.Fatalf("error writing %q: %v", p, err)
249 }
250 case rdirectory:
251 if err := os.Mkdir(p, resource.mode); err != nil {
252 t.Fatalf("error creating directory %q: %v", p, err)
253 }
254 case rhardlink:
255 target := filepath.Join(root, resource.target)
256 if err := os.Link(target, p); err != nil {
257 t.Fatalf("error creating hardlink: %v", err)
258 }
259 case rrelsymlink:
260 if err := os.Symlink(resource.target, p); err != nil {
261 t.Fatalf("error creating symlink: %v", err)
262 }
263 case rabssymlink:
264
265 target := filepath.Join(root, resource.target)
266
267 if err := os.Symlink(target, p); err != nil {
268 t.Fatalf("error creating symlink: %v", err)
269 }
270 case rchardev, rnamedpipe:
271 if err := devices.Mknod(p, resource.mode, resource.major, resource.minor); err != nil {
272 t.Fatalf("error creating device %q: %v", p, err)
273 }
274 default:
275 t.Fatalf("unknown resource type: %v", resource.kind)
276 }
277
278 st, err := os.Lstat(p)
279 if err != nil {
280 t.Fatalf("error statting after creation: %v", err)
281 }
282 resources[i].uid = int64(st.Sys().(*syscall.Stat_t).Uid)
283 resources[i].gid = int64(st.Sys().(*syscall.Stat_t).Gid)
284 resources[i].mode = st.Mode()
285
286
287 }
288
289
290 if err := filepath.Walk(root, func(p string, fi os.FileInfo, err error) error {
291 if fi.Mode()&os.ModeSymlink != 0 {
292 target, err := os.Readlink(p)
293 if err != nil {
294 return err
295 }
296 t.Log(fi.Mode(), p, "->", target)
297 } else {
298 t.Log(fi.Mode(), p)
299 }
300
301 return nil
302 }); err != nil {
303 t.Fatalf("error walking created root: %v", err)
304 }
305
306 var b bytes.Buffer
307 if err := tree(&b, root); err != nil {
308 t.Fatalf("error running tree: %v", err)
309 }
310 t.Logf("\n%s", b.String())
311 }
312
313 func randomBytes(p []byte) {
314 for i := range p {
315 p[i] = byte(rand.Intn(1<<8 - 1))
316 }
317 }
318
319
320
321 func expectedResourceList(root string, resources []dresource) ([]Resource, error) {
322 resourceMap := map[string]Resource{}
323 paths := []string{}
324 for _, r := range resources {
325 absPath := r.path
326 if !filepath.IsAbs(absPath) {
327 absPath = "/" + absPath
328 }
329 switch r.kind {
330 case rfile:
331 f := ®ularFile{
332 resource: resource{
333 paths: []string{absPath},
334 mode: r.mode,
335 uid: r.uid,
336 gid: r.gid,
337 },
338 size: int64(r.size),
339 digests: []digest.Digest{r.digest},
340 }
341 resourceMap[absPath] = f
342 paths = append(paths, absPath)
343 case rdirectory:
344 d := &directory{
345 resource: resource{
346 paths: []string{absPath},
347 mode: r.mode,
348 uid: r.uid,
349 gid: r.gid,
350 },
351 }
352 resourceMap[absPath] = d
353 paths = append(paths, absPath)
354 case rhardlink:
355 targetPath := r.target
356 if !filepath.IsAbs(targetPath) {
357 targetPath = "/" + targetPath
358 }
359 target, ok := resourceMap[targetPath]
360 if !ok {
361 return nil, errors.New("must specify target before hardlink for test resources")
362 }
363 rf, ok := target.(*regularFile)
364 if !ok {
365 return nil, errors.New("hardlink target must be regular file")
366 }
367
368 rf.paths = append(rf.paths, absPath)
369
370
371 sort.Stable(sort.StringSlice(rf.paths))
372 case rrelsymlink, rabssymlink:
373 targetPath := r.target
374 if r.kind == rabssymlink && !filepath.IsAbs(r.target) {
375
376 targetPath = filepath.Join(root, targetPath)
377 }
378 s := &symLink{
379 resource: resource{
380 paths: []string{absPath},
381 mode: r.mode,
382 uid: r.uid,
383 gid: r.gid,
384 },
385 target: targetPath,
386 }
387 resourceMap[absPath] = s
388 paths = append(paths, absPath)
389 case rchardev:
390 d := &device{
391 resource: resource{
392 paths: []string{absPath},
393 mode: r.mode,
394 uid: r.uid,
395 gid: r.gid,
396 },
397 major: uint64(r.major),
398 minor: uint64(r.minor),
399 }
400 resourceMap[absPath] = d
401 paths = append(paths, absPath)
402 case rnamedpipe:
403 p := &namedPipe{
404 resource: resource{
405 paths: []string{absPath},
406 mode: r.mode,
407 uid: r.uid,
408 gid: r.gid,
409 },
410 }
411 resourceMap[absPath] = p
412 paths = append(paths, absPath)
413 default:
414 return nil, fmt.Errorf("unknown resource type: %v", r.kind)
415 }
416 }
417
418 if len(resourceMap) < len(paths) {
419 return nil, errors.New("resource list has duplicated paths")
420 }
421
422 sort.Strings(paths)
423
424 manifestResources := make([]Resource, len(paths))
425 for i, p := range paths {
426 manifestResources[i] = resourceMap[p]
427 }
428
429 return manifestResources, nil
430 }
431
View as plain text