1
16
17 package continuity
18
19 import (
20 "bytes"
21 "errors"
22 "fmt"
23 "io"
24 "os"
25 "path/filepath"
26 "strings"
27
28 "github.com/containerd/continuity/devices"
29 driverpkg "github.com/containerd/continuity/driver"
30 "github.com/containerd/continuity/pathdriver"
31
32 "github.com/opencontainers/go-digest"
33 )
34
35 var (
36
37 ErrNotFound = fmt.Errorf("not found")
38
39 ErrNotSupported = fmt.Errorf("not supported")
40 )
41
42
43
44
45
46 type Context interface {
47 Apply(Resource) error
48 Verify(Resource) error
49 Resource(string, os.FileInfo) (Resource, error)
50 Walk(filepath.WalkFunc) error
51 }
52
53
54
55
56 type SymlinkPath func(root, linkname, target string) (string, error)
57
58
59 type ContextOptions struct {
60 Digester Digester
61 Driver driverpkg.Driver
62 PathDriver pathdriver.PathDriver
63 Provider ContentProvider
64 }
65
66
67
68
69 type context struct {
70 driver driverpkg.Driver
71 pathDriver pathdriver.PathDriver
72 root string
73 digester Digester
74 provider ContentProvider
75 }
76
77
78
79 func NewContext(root string) (Context, error) {
80 return NewContextWithOptions(root, ContextOptions{})
81 }
82
83
84 func NewContextWithOptions(root string, options ContextOptions) (Context, error) {
85
86 pathDriver := options.PathDriver
87 if pathDriver == nil {
88 pathDriver = pathdriver.LocalPathDriver
89 }
90
91 root = pathDriver.FromSlash(root)
92 root, err := pathDriver.Abs(pathDriver.Clean(root))
93 if err != nil {
94 return nil, err
95 }
96
97 driver := options.Driver
98 if driver == nil {
99 driver, err = driverpkg.NewSystemDriver()
100 if err != nil {
101 return nil, err
102 }
103 }
104
105 digester := options.Digester
106 if digester == nil {
107 digester = simpleDigester{digest.Canonical}
108 }
109
110
111
112
113
114 fi, err := driver.Stat(root)
115 if err != nil {
116 return nil, err
117 }
118
119 if !fi.IsDir() {
120 return nil, &os.PathError{Op: "NewContext", Path: root, Err: os.ErrInvalid}
121 }
122
123 return &context{
124 root: root,
125 driver: driver,
126 pathDriver: pathDriver,
127 digester: digester,
128 provider: options.Provider,
129 }, nil
130 }
131
132
133
134
135
136 func (c *context) Resource(p string, fi os.FileInfo) (Resource, error) {
137 fp, err := c.fullpath(p)
138 if err != nil {
139 return nil, err
140 }
141
142 if fi == nil {
143 fi, err = c.driver.Lstat(fp)
144 if err != nil {
145 return nil, err
146 }
147 }
148
149 base, err := newBaseResource(p, fi)
150 if err != nil {
151 return nil, err
152 }
153
154 base.xattrs, err = c.resolveXAttrs(fp, fi, base)
155 if err != nil && !errors.Is(err, ErrNotSupported) {
156 return nil, err
157 }
158
159
160
161 if fi.Mode().IsRegular() {
162 dgst, err := c.digest(p)
163 if err != nil {
164 return nil, err
165 }
166
167 return newRegularFile(*base, base.paths, fi.Size(), dgst)
168 }
169
170 if fi.Mode().IsDir() {
171 return newDirectory(*base)
172 }
173
174 if fi.Mode()&os.ModeSymlink != 0 {
175
176
177
178 target, err := c.driver.Readlink(fp)
179 if err != nil {
180 return nil, err
181 }
182
183 return newSymLink(*base, target)
184 }
185
186 if fi.Mode()&os.ModeNamedPipe != 0 {
187 return newNamedPipe(*base, base.paths)
188 }
189
190 if fi.Mode()&os.ModeDevice != 0 {
191 deviceDriver, ok := c.driver.(driverpkg.DeviceInfoDriver)
192 if !ok {
193 return nil, fmt.Errorf("device extraction is not supported for %s: %w", fp, ErrNotSupported)
194 }
195
196
197
198 major, minor, err := deviceDriver.DeviceInfo(fi)
199 if err != nil {
200 return nil, err
201 }
202
203 return newDevice(*base, base.paths, major, minor)
204 }
205
206 return nil, fmt.Errorf("%q (%v) is not supported: %w", fp, fi.Mode(), ErrNotFound)
207 }
208
209 func (c *context) verifyMetadata(resource, target Resource) error {
210 if target.Mode() != resource.Mode() {
211 return fmt.Errorf("resource %q has incorrect mode: %v != %v", target.Path(), target.Mode(), resource.Mode())
212 }
213
214 if target.UID() != resource.UID() {
215 return fmt.Errorf("unexpected uid for %q: %v != %v", target.Path(), target.UID(), resource.GID())
216 }
217
218 if target.GID() != resource.GID() {
219 return fmt.Errorf("unexpected gid for %q: %v != %v", target.Path(), target.GID(), target.GID())
220 }
221
222 if xattrer, ok := resource.(XAttrer); ok {
223 txattrer, tok := target.(XAttrer)
224 if !tok {
225 return fmt.Errorf("resource %q has xattrs but target does not support them", resource.Path())
226 }
227
228
229
230
231 txattrs := txattrer.XAttrs()
232 for attr, value := range xattrer.XAttrs() {
233 tvalue, ok := txattrs[attr]
234 if !ok {
235 return fmt.Errorf("resource %q target missing xattr %q", resource.Path(), attr)
236 }
237
238 if !bytes.Equal(value, tvalue) {
239 return fmt.Errorf("xattr %q value differs for resource %q", attr, resource.Path())
240 }
241 }
242 }
243
244 switch r := resource.(type) {
245 case RegularFile:
246
247
248
249
250 t, ok := target.(RegularFile)
251 if !ok {
252 return fmt.Errorf("resource %q target not a regular file", r.Path())
253 }
254
255 if t.Size() != r.Size() {
256 return fmt.Errorf("resource %q target has incorrect size: %v != %v", t.Path(), t.Size(), r.Size())
257 }
258 case Directory:
259 t, ok := target.(Directory)
260 if !ok {
261 return fmt.Errorf("resource %q target not a directory", t.Path())
262 }
263 case SymLink:
264 t, ok := target.(SymLink)
265 if !ok {
266 return fmt.Errorf("resource %q target not a symlink", t.Path())
267 }
268
269 if t.Target() != r.Target() {
270 return fmt.Errorf("resource %q target has mismatched target: %q != %q", t.Path(), t.Target(), r.Target())
271 }
272 case Device:
273 t, ok := target.(Device)
274 if !ok {
275 return fmt.Errorf("resource %q is not a device", t.Path())
276 }
277
278 if t.Major() != r.Major() || t.Minor() != r.Minor() {
279 return fmt.Errorf("resource %q has mismatched major/minor numbers: %d,%d != %d,%d", t.Path(), t.Major(), t.Minor(), r.Major(), r.Minor())
280 }
281 case NamedPipe:
282 t, ok := target.(NamedPipe)
283 if !ok {
284 return fmt.Errorf("resource %q is not a named pipe", t.Path())
285 }
286 default:
287 return fmt.Errorf("cannot verify resource: %v", resource)
288 }
289
290 return nil
291 }
292
293
294
295 func (c *context) Verify(resource Resource) error {
296 fp, err := c.fullpath(resource.Path())
297 if err != nil {
298 return err
299 }
300
301 fi, err := c.driver.Lstat(fp)
302 if err != nil {
303 return err
304 }
305
306 target, err := c.Resource(resource.Path(), fi)
307 if err != nil {
308 return err
309 }
310
311 if target.Path() != resource.Path() {
312 return fmt.Errorf("resource paths do not match: %q != %q", target.Path(), resource.Path())
313 }
314
315 if err := c.verifyMetadata(resource, target); err != nil {
316 return err
317 }
318
319 if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
320 hardlinkKey, err := newHardlinkKey(fi)
321 if err == errNotAHardLink {
322 if len(h.Paths()) > 1 {
323 return fmt.Errorf("%q is not a hardlink to %q", h.Paths()[1], resource.Path())
324 }
325 } else if err != nil {
326 return err
327 }
328
329 for _, path := range h.Paths()[1:] {
330 fpLink, err := c.fullpath(path)
331 if err != nil {
332 return err
333 }
334
335 fiLink, err := c.driver.Lstat(fpLink)
336 if err != nil {
337 return err
338 }
339
340 targetLink, err := c.Resource(path, fiLink)
341 if err != nil {
342 return err
343 }
344
345 hardlinkKeyLink, err := newHardlinkKey(fiLink)
346 if err != nil {
347 return err
348 }
349
350 if hardlinkKeyLink != hardlinkKey {
351 return fmt.Errorf("%q is not a hardlink to %q", path, resource.Path())
352 }
353
354 if err := c.verifyMetadata(resource, targetLink); err != nil {
355 return err
356 }
357 }
358 }
359
360 switch r := resource.(type) {
361 case RegularFile:
362 t, ok := target.(RegularFile)
363 if !ok {
364 return fmt.Errorf("resource %q target not a regular file", r.Path())
365 }
366
367
368
369
370
371 if !digestsMatch(t.Digests(), r.Digests()) {
372 return fmt.Errorf("digests for resource %q do not match: %v != %v", t.Path(), t.Digests(), r.Digests())
373 }
374 }
375
376 return nil
377 }
378
379 func (c *context) checkoutFile(fp string, rf RegularFile) error {
380 if c.provider == nil {
381 return fmt.Errorf("no file provider")
382 }
383 var (
384 r io.ReadCloser
385 err error
386 )
387 for _, dgst := range rf.Digests() {
388 r, err = c.provider.Reader(dgst)
389 if err == nil {
390 break
391 }
392 }
393 if err != nil {
394 return fmt.Errorf("file content could not be provided: %w", err)
395 }
396 defer r.Close()
397
398 return atomicWriteFile(fp, r, rf.Size(), rf.Mode())
399 }
400
401
402
403
404 func (c *context) Apply(resource Resource) error {
405 fp, err := c.fullpath(resource.Path())
406 if err != nil {
407 return err
408 }
409
410 if !strings.HasPrefix(fp, c.root) {
411 return fmt.Errorf("resource %v escapes root", resource)
412 }
413
414 chmod := true
415 fi, err := c.driver.Lstat(fp)
416 if err != nil {
417 if !os.IsNotExist(err) {
418 return err
419 }
420 }
421
422 switch r := resource.(type) {
423 case RegularFile:
424 if fi == nil {
425 if err := c.checkoutFile(fp, r); err != nil {
426 return fmt.Errorf("error checking out file %q: %w", resource.Path(), err)
427 }
428 chmod = false
429 } else {
430 if !fi.Mode().IsRegular() {
431 return fmt.Errorf("file %q should be a regular file, but is not", resource.Path())
432 }
433 if fi.Size() != r.Size() {
434 if err := c.checkoutFile(fp, r); err != nil {
435 return fmt.Errorf("error checking out file %q: %w", resource.Path(), err)
436 }
437 } else {
438 for _, dgst := range r.Digests() {
439 f, err := os.Open(fp)
440 if err != nil {
441 return fmt.Errorf("failure opening file for read %q: %w", resource.Path(), err)
442 }
443 compared, err := dgst.Algorithm().FromReader(f)
444 if err == nil && dgst != compared {
445 if err := c.checkoutFile(fp, r); err != nil {
446 return fmt.Errorf("error checking out file %q: %w", resource.Path(), err)
447 }
448 break
449 }
450 if err1 := f.Close(); err == nil {
451 err = err1
452 }
453 if err != nil {
454 return fmt.Errorf("error checking digest for %q: %w", resource.Path(), err)
455 }
456 }
457 }
458 }
459 case Directory:
460 if fi == nil {
461 if err := c.driver.Mkdir(fp, resource.Mode()); err != nil {
462 return err
463 }
464 } else if !fi.Mode().IsDir() {
465 return fmt.Errorf("%q should be a directory, but is not", resource.Path())
466 }
467
468 case SymLink:
469 var target string
470
471 if fi != nil {
472 if fi.Mode()&os.ModeSymlink != 0 {
473 target, err = c.driver.Readlink(fp)
474 if err != nil {
475 return err
476 }
477 }
478 }
479
480 if target != r.Target() {
481 if fi != nil {
482 if err := c.driver.Remove(fp); err != nil {
483 return err
484 }
485 }
486
487 if err := c.driver.Symlink(r.Target(), fp); err != nil {
488 return err
489 }
490 }
491
492 case Device:
493 if fi == nil {
494 if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
495 return err
496 }
497 } else if (fi.Mode() & os.ModeDevice) == 0 {
498 return fmt.Errorf("%q should be a device, but is not", resource.Path())
499 } else {
500 major, minor, err := devices.DeviceInfo(fi)
501 if err != nil {
502 return err
503 }
504 if major != r.Major() || minor != r.Minor() {
505 if err := c.driver.Remove(fp); err != nil {
506 return err
507 }
508
509 if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
510 return err
511 }
512 }
513 }
514
515 case NamedPipe:
516 if fi == nil {
517 if err := c.driver.Mkfifo(fp, resource.Mode()); err != nil {
518 return err
519 }
520 } else if (fi.Mode() & os.ModeNamedPipe) == 0 {
521 return fmt.Errorf("%q should be a named pipe, but is not", resource.Path())
522 }
523 }
524
525 if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
526 for _, path := range h.Paths() {
527 if path == resource.Path() {
528 continue
529 }
530
531 lp, err := c.fullpath(path)
532 if err != nil {
533 return err
534 }
535
536 if _, fi := c.driver.Lstat(lp); fi == nil {
537 c.driver.Remove(lp)
538 }
539 if err := c.driver.Link(fp, lp); err != nil {
540 return err
541 }
542 }
543 }
544
545
546 if chmod {
547 if err := c.driver.Lchmod(fp, resource.Mode()); err != nil {
548 return err
549 }
550 }
551
552 if err := c.driver.Lchown(fp, resource.UID(), resource.GID()); err != nil {
553 return err
554 }
555
556 if xattrer, ok := resource.(XAttrer); ok {
557
558
559
560
561 if _, ok := resource.(SymLink); ok {
562 lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
563 if !ok {
564 return fmt.Errorf("unsupported symlink xattr for resource %q", resource.Path())
565 }
566 if err := lxattrDriver.LSetxattr(fp, xattrer.XAttrs()); err != nil {
567 return err
568 }
569 } else {
570 xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
571 if !ok {
572 return fmt.Errorf("unsupported xattr for resource %q", resource.Path())
573 }
574 if err := xattrDriver.Setxattr(fp, xattrer.XAttrs()); err != nil {
575 return err
576 }
577 }
578 }
579
580 return nil
581 }
582
583
584
585
586 func (c *context) Walk(fn filepath.WalkFunc) error {
587 root := c.root
588 fi, err := c.driver.Lstat(c.root)
589 if err == nil && fi.Mode()&os.ModeSymlink != 0 {
590 root, err = c.driver.Readlink(c.root)
591 if err != nil {
592 return err
593 }
594 }
595 return c.pathDriver.Walk(root, func(p string, fi os.FileInfo, _ error) error {
596 contained, err := c.containWithRoot(p, root)
597 return fn(contained, fi, err)
598 })
599 }
600
601
602
603 func (c *context) fullpath(p string) (string, error) {
604 p = c.pathDriver.Join(c.root, p)
605 if !strings.HasPrefix(p, c.root) {
606 return "", fmt.Errorf("invalid context path")
607 }
608
609 return p, nil
610 }
611
612
613
614
615
616 func (c *context) containWithRoot(p string, root string) (string, error) {
617 sanitized, err := c.pathDriver.Rel(root, p)
618 if err != nil {
619 return "", err
620 }
621
622
623
624 return c.pathDriver.Join("/", c.pathDriver.Clean(sanitized)), nil
625 }
626
627
628 func (c *context) digest(p string) (digest.Digest, error) {
629 f, err := c.driver.Open(c.pathDriver.Join(c.root, p))
630 if err != nil {
631 return "", err
632 }
633 defer f.Close()
634
635 return c.digester.Digest(f)
636 }
637
638
639
640
641 func (c *context) resolveXAttrs(fp string, fi os.FileInfo, base *resource) (map[string][]byte, error) {
642 if fi.Mode().IsRegular() || fi.Mode().IsDir() {
643 xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
644 if !ok {
645 return nil, fmt.Errorf("xattr extraction is not supported: %w", ErrNotSupported)
646 }
647
648 return xattrDriver.Getxattr(fp)
649 }
650
651 if fi.Mode()&os.ModeSymlink != 0 {
652 lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
653 if !ok {
654 return nil, fmt.Errorf("xattr extraction for symlinks is not supported: %w", ErrNotSupported)
655 }
656
657 return lxattrDriver.LGetxattr(fp)
658 }
659
660 return nil, nil
661 }
662
View as plain text