1
16
17 package continuity
18
19 import (
20 "errors"
21 "fmt"
22 "os"
23 "reflect"
24 "sort"
25
26 pb "github.com/containerd/continuity/proto"
27 "github.com/opencontainers/go-digest"
28 )
29
30
31
32
33
34
35
36 type Resource interface {
37
38
39
40 Path() string
41
42
43 Mode() os.FileMode
44
45 UID() int64
46 GID() int64
47 }
48
49
50
51 type ByPath []Resource
52
53 func (bp ByPath) Len() int { return len(bp) }
54 func (bp ByPath) Swap(i, j int) { bp[i], bp[j] = bp[j], bp[i] }
55 func (bp ByPath) Less(i, j int) bool { return bp[i].Path() < bp[j].Path() }
56
57 type XAttrer interface {
58 XAttrs() map[string][]byte
59 }
60
61
62
63 type Hardlinkable interface {
64
65
66
67 Paths() []string
68 }
69
70 type RegularFile interface {
71 Resource
72 XAttrer
73 Hardlinkable
74
75 Size() int64
76 Digests() []digest.Digest
77 }
78
79
80
81
82
83 func Merge(fs ...Resource) (Resource, error) {
84 if len(fs) < 1 {
85 return nil, fmt.Errorf("please provide a resource to merge")
86 }
87
88 if len(fs) == 1 {
89 return fs[0], nil
90 }
91
92 var paths []string
93 var digests []digest.Digest
94 bypath := map[string][]Resource{}
95
96
97
98
99 prototype := fs[0]
100 xattrs := make(map[string][]byte)
101
102
103 if prototypeXAttrer, ok := prototype.(XAttrer); ok {
104 for attr, value := range prototypeXAttrer.XAttrs() {
105 xattrs[attr] = value
106 }
107 }
108
109 for _, f := range fs {
110 h, isHardlinkable := f.(Hardlinkable)
111 if !isHardlinkable {
112 return nil, errNotAHardLink
113 }
114
115 if f.Mode() != prototype.Mode() {
116 return nil, fmt.Errorf("modes do not match: %v != %v", f.Mode(), prototype.Mode())
117 }
118
119 if f.UID() != prototype.UID() {
120 return nil, fmt.Errorf("uid does not match: %v != %v", f.UID(), prototype.UID())
121 }
122
123 if f.GID() != prototype.GID() {
124 return nil, fmt.Errorf("gid does not match: %v != %v", f.GID(), prototype.GID())
125 }
126
127 if xattrer, ok := f.(XAttrer); ok {
128 fxattrs := xattrer.XAttrs()
129 if !reflect.DeepEqual(fxattrs, xattrs) {
130 return nil, fmt.Errorf("resource %q xattrs do not match: %v != %v", f, fxattrs, xattrs)
131 }
132 }
133
134 for _, p := range h.Paths() {
135 pfs, ok := bypath[p]
136 if !ok {
137
138 paths = append(paths, p)
139 }
140
141 bypath[p] = append(pfs, f)
142 }
143
144 if regFile, isRegFile := f.(RegularFile); isRegFile {
145 prototypeRegFile, prototypeIsRegFile := prototype.(RegularFile)
146 if !prototypeIsRegFile {
147 return nil, errors.New("prototype is not a regular file")
148 }
149
150 if regFile.Size() != prototypeRegFile.Size() {
151 return nil, fmt.Errorf("size does not match: %v != %v", regFile.Size(), prototypeRegFile.Size())
152 }
153
154 digests = append(digests, regFile.Digests()...)
155 } else if device, isDevice := f.(Device); isDevice {
156 prototypeDevice, prototypeIsDevice := prototype.(Device)
157 if !prototypeIsDevice {
158 return nil, errors.New("prototype is not a device")
159 }
160
161 if device.Major() != prototypeDevice.Major() {
162 return nil, fmt.Errorf("major number does not match: %v != %v", device.Major(), prototypeDevice.Major())
163 }
164 if device.Minor() != prototypeDevice.Minor() {
165 return nil, fmt.Errorf("minor number does not match: %v != %v", device.Minor(), prototypeDevice.Minor())
166 }
167 } else if _, isNamedPipe := f.(NamedPipe); isNamedPipe {
168 _, prototypeIsNamedPipe := prototype.(NamedPipe)
169 if !prototypeIsNamedPipe {
170 return nil, errors.New("prototype is not a named pipe")
171 }
172 } else {
173 return nil, errNotAHardLink
174 }
175 }
176
177 sort.Stable(sort.StringSlice(paths))
178
179
180
181
182 first := bypath[paths[0]][0]
183
184 resource := resource{
185 paths: paths,
186 mode: first.Mode(),
187 uid: first.UID(),
188 gid: first.GID(),
189 xattrs: xattrs,
190 }
191
192 switch typedF := first.(type) {
193 case RegularFile:
194 var err error
195 digests, err = uniqifyDigests(digests...)
196 if err != nil {
197 return nil, err
198 }
199
200 return ®ularFile{
201 resource: resource,
202 size: typedF.Size(),
203 digests: digests,
204 }, nil
205 case Device:
206 return &device{
207 resource: resource,
208 major: typedF.Major(),
209 minor: typedF.Minor(),
210 }, nil
211
212 case NamedPipe:
213 return &namedPipe{
214 resource: resource,
215 }, nil
216
217 default:
218 return nil, errNotAHardLink
219 }
220 }
221
222 type Directory interface {
223 Resource
224 XAttrer
225
226
227 Directory()
228 }
229
230 type SymLink interface {
231 Resource
232
233
234 Target() string
235 }
236
237 type NamedPipe interface {
238 Resource
239 Hardlinkable
240 XAttrer
241
242
243
244 Pipe()
245 }
246
247 type Device interface {
248 Resource
249 Hardlinkable
250 XAttrer
251
252 Major() uint64
253 Minor() uint64
254 }
255
256 type resource struct {
257 paths []string
258 mode os.FileMode
259 uid, gid int64
260 xattrs map[string][]byte
261 }
262
263 var _ Resource = &resource{}
264
265 func (r *resource) Path() string {
266 if len(r.paths) < 1 {
267 return ""
268 }
269
270 return r.paths[0]
271 }
272
273 func (r *resource) Mode() os.FileMode {
274 return r.mode
275 }
276
277 func (r *resource) UID() int64 {
278 return r.uid
279 }
280
281 func (r *resource) GID() int64 {
282 return r.gid
283 }
284
285 type regularFile struct {
286 resource
287 size int64
288 digests []digest.Digest
289 }
290
291 var _ RegularFile = ®ularFile{}
292
293
294
295 func newRegularFile(base resource, paths []string, size int64, dgsts ...digest.Digest) (RegularFile, error) {
296 if !base.Mode().IsRegular() {
297 return nil, fmt.Errorf("not a regular file")
298 }
299
300 base.paths = make([]string, len(paths))
301 copy(base.paths, paths)
302
303
304 ds := make([]digest.Digest, len(dgsts))
305 copy(ds, dgsts)
306
307 return ®ularFile{
308 resource: base,
309 size: size,
310 digests: ds,
311 }, nil
312 }
313
314 func (rf *regularFile) Paths() []string {
315 paths := make([]string, len(rf.paths))
316 copy(paths, rf.paths)
317 return paths
318 }
319
320 func (rf *regularFile) Size() int64 {
321 return rf.size
322 }
323
324 func (rf *regularFile) Digests() []digest.Digest {
325 digests := make([]digest.Digest, len(rf.digests))
326 copy(digests, rf.digests)
327 return digests
328 }
329
330 func (rf *regularFile) XAttrs() map[string][]byte {
331 xattrs := make(map[string][]byte, len(rf.xattrs))
332
333 for attr, value := range rf.xattrs {
334 xattrs[attr] = append(xattrs[attr], value...)
335 }
336
337 return xattrs
338 }
339
340 type directory struct {
341 resource
342 }
343
344 var _ Directory = &directory{}
345
346 func newDirectory(base resource) (Directory, error) {
347 if !base.Mode().IsDir() {
348 return nil, fmt.Errorf("not a directory")
349 }
350
351 return &directory{
352 resource: base,
353 }, nil
354 }
355
356 func (d *directory) Directory() {}
357
358 func (d *directory) XAttrs() map[string][]byte {
359 xattrs := make(map[string][]byte, len(d.xattrs))
360
361 for attr, value := range d.xattrs {
362 xattrs[attr] = append(xattrs[attr], value...)
363 }
364
365 return xattrs
366 }
367
368 type symLink struct {
369 resource
370 target string
371 }
372
373 var _ SymLink = &symLink{}
374
375 func newSymLink(base resource, target string) (SymLink, error) {
376 if base.Mode()&os.ModeSymlink == 0 {
377 return nil, fmt.Errorf("not a symlink")
378 }
379
380 return &symLink{
381 resource: base,
382 target: target,
383 }, nil
384 }
385
386 func (l *symLink) Target() string {
387 return l.target
388 }
389
390 type namedPipe struct {
391 resource
392 }
393
394 var _ NamedPipe = &namedPipe{}
395
396 func newNamedPipe(base resource, paths []string) (NamedPipe, error) {
397 if base.Mode()&os.ModeNamedPipe == 0 {
398 return nil, fmt.Errorf("not a namedpipe")
399 }
400
401 base.paths = make([]string, len(paths))
402 copy(base.paths, paths)
403
404 return &namedPipe{
405 resource: base,
406 }, nil
407 }
408
409 func (np *namedPipe) Pipe() {}
410
411 func (np *namedPipe) Paths() []string {
412 paths := make([]string, len(np.paths))
413 copy(paths, np.paths)
414 return paths
415 }
416
417 func (np *namedPipe) XAttrs() map[string][]byte {
418 xattrs := make(map[string][]byte, len(np.xattrs))
419
420 for attr, value := range np.xattrs {
421 xattrs[attr] = append(xattrs[attr], value...)
422 }
423
424 return xattrs
425 }
426
427 type device struct {
428 resource
429 major, minor uint64
430 }
431
432 var _ Device = &device{}
433
434 func newDevice(base resource, paths []string, major, minor uint64) (Device, error) {
435 if base.Mode()&os.ModeDevice == 0 {
436 return nil, fmt.Errorf("not a device")
437 }
438
439 base.paths = make([]string, len(paths))
440 copy(base.paths, paths)
441
442 return &device{
443 resource: base,
444 major: major,
445 minor: minor,
446 }, nil
447 }
448
449 func (d *device) Paths() []string {
450 paths := make([]string, len(d.paths))
451 copy(paths, d.paths)
452 return paths
453 }
454
455 func (d *device) XAttrs() map[string][]byte {
456 xattrs := make(map[string][]byte, len(d.xattrs))
457
458 for attr, value := range d.xattrs {
459 xattrs[attr] = append(xattrs[attr], value...)
460 }
461
462 return xattrs
463 }
464
465 func (d device) Major() uint64 {
466 return d.major
467 }
468
469 func (d device) Minor() uint64 {
470 return d.minor
471 }
472
473
474
475
476 func toProto(resource Resource) *pb.Resource {
477 b := &pb.Resource{
478 Path: []string{resource.Path()},
479 Mode: uint32(resource.Mode()),
480 Uid: resource.UID(),
481 Gid: resource.GID(),
482 }
483
484 if xattrer, ok := resource.(XAttrer); ok {
485
486 keys := []string{}
487 xattrs := xattrer.XAttrs()
488 for k := range xattrs {
489 keys = append(keys, k)
490 }
491 sort.Strings(keys)
492
493 for _, k := range keys {
494 b.Xattr = append(b.Xattr, &pb.XAttr{Name: k, Data: xattrs[k]})
495 }
496 }
497
498 switch r := resource.(type) {
499 case RegularFile:
500 b.Path = r.Paths()
501 b.Size = uint64(r.Size())
502
503 for _, dgst := range r.Digests() {
504 b.Digest = append(b.Digest, dgst.String())
505 }
506 case SymLink:
507 b.Target = r.Target()
508 case Device:
509 b.Major, b.Minor = r.Major(), r.Minor()
510 b.Path = r.Paths()
511 case NamedPipe:
512 b.Path = r.Paths()
513 }
514
515
516
517 sort.Strings(b.Path)
518
519 return b
520 }
521
522
523 func fromProto(b *pb.Resource) (Resource, error) {
524 base := &resource{
525 paths: b.Path,
526 mode: os.FileMode(b.Mode),
527 uid: b.Uid,
528 gid: b.Gid,
529 }
530
531 base.xattrs = make(map[string][]byte, len(b.Xattr))
532
533 for _, attr := range b.Xattr {
534 base.xattrs[attr.Name] = attr.Data
535 }
536
537 switch {
538 case base.Mode().IsRegular():
539 dgsts := make([]digest.Digest, len(b.Digest))
540 for i, dgst := range b.Digest {
541
542 dgsts[i] = digest.Digest(dgst)
543 }
544
545 return newRegularFile(*base, b.Path, int64(b.Size), dgsts...)
546 case base.Mode().IsDir():
547 return newDirectory(*base)
548 case base.Mode()&os.ModeSymlink != 0:
549 return newSymLink(*base, b.Target)
550 case base.Mode()&os.ModeNamedPipe != 0:
551 return newNamedPipe(*base, b.Path)
552 case base.Mode()&os.ModeDevice != 0:
553 return newDevice(*base, b.Path, b.Major, b.Minor)
554 }
555
556 return nil, fmt.Errorf("unknown resource record (%#v): %s", b, base.Mode())
557 }
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
View as plain text