1/* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15*/ 16 17package continuity 18 19import ( 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// TODO(stevvooe): A record based model, somewhat sketched out at the bottom 31// of this file, will be more flexible. Another possibly is to tie the package 32// interface directly to the protobuf type. This will have efficiency 33// advantages at the cost coupling the nasty codegen types to the exported 34// interface. 35 36type Resource interface { 37 // Path provides the primary resource path relative to the bundle root. In 38 // cases where resources have more than one path, such as with hard links, 39 // this will return the primary path, which is often just the first entry. 40 Path() string 41 42 // Mode returns the 43 Mode() os.FileMode 44 45 UID() int64 46 GID() int64 47} 48 49// ByPath provides the canonical sort order for a set of resources. Use with 50// sort.Stable for deterministic sorting. 51type ByPath []Resource 52 53func (bp ByPath) Len() int { return len(bp) } 54func (bp ByPath) Swap(i, j int) { bp[i], bp[j] = bp[j], bp[i] } 55func (bp ByPath) Less(i, j int) bool { return bp[i].Path() < bp[j].Path() } 56 57type XAttrer interface { 58 XAttrs() map[string][]byte 59} 60 61// Hardlinkable is an interface that a resource type satisfies if it can be a 62// hardlink target. 63type Hardlinkable interface { 64 // Paths returns all paths of the resource, including the primary path 65 // returned by Resource.Path. If len(Paths()) > 1, the resource is a hard 66 // link. 67 Paths() []string 68} 69 70type RegularFile interface { 71 Resource 72 XAttrer 73 Hardlinkable 74 75 Size() int64 76 Digests() []digest.Digest 77} 78 79// Merge two or more Resources into new file. Typically, this should be 80// used to merge regular files as hardlinks. If the files are not identical, 81// other than Paths and Digests, the merge will fail and an error will be 82// returned. 83func 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 // The attributes are all compared against the first to make sure they 97 // agree before adding to the above collections. If any of these don't 98 // correctly validate, the merge fails. 99 prototype := fs[0] 100 xattrs := make(map[string][]byte) 101 102 // initialize xattrs for use below. All files must have same xattrs. 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 // ensure paths are unique by only appending on a new path. 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 // Choose a "canonical" file. Really, it is just the first file to sort 180 // against. We also effectively select the very first digest as the 181 // "canonical" one for this file. 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 222type Directory interface { 223 Resource 224 XAttrer 225 226 // Directory is a no-op method to identify directory objects by interface. 227 Directory() 228} 229 230type SymLink interface { 231 Resource 232 233 // Target returns the target of the symlink contained in the . 234 Target() string 235} 236 237type NamedPipe interface { 238 Resource 239 Hardlinkable 240 XAttrer 241 242 // Pipe is a no-op method to allow consistent resolution of NamedPipe 243 // interface. 244 Pipe() 245} 246 247type Device interface { 248 Resource 249 Hardlinkable 250 XAttrer 251 252 Major() uint64 253 Minor() uint64 254} 255 256type resource struct { 257 paths []string 258 mode os.FileMode 259 uid, gid int64 260 xattrs map[string][]byte 261} 262 263var _ Resource = &resource{} 264 265func (r *resource) Path() string { 266 if len(r.paths) < 1 { 267 return "" 268 } 269 270 return r.paths[0] 271} 272 273func (r *resource) Mode() os.FileMode { 274 return r.mode 275} 276 277func (r *resource) UID() int64 { 278 return r.uid 279} 280 281func (r *resource) GID() int64 { 282 return r.gid 283} 284 285type regularFile struct { 286 resource 287 size int64 288 digests []digest.Digest 289} 290 291var _ RegularFile = ®ularFile{} 292 293// newRegularFile returns the RegularFile, using the populated base resource 294// and one or more digests of the content. 295func 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 // make our own copy of digests 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 314func (rf *regularFile) Paths() []string { 315 paths := make([]string, len(rf.paths)) 316 copy(paths, rf.paths) 317 return paths 318} 319 320func (rf *regularFile) Size() int64 { 321 return rf.size 322} 323 324func (rf *regularFile) Digests() []digest.Digest { 325 digests := make([]digest.Digest, len(rf.digests)) 326 copy(digests, rf.digests) 327 return digests 328} 329 330func (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 340type directory struct { 341 resource 342} 343 344var _ Directory = &directory{} 345 346func 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 356func (d *directory) Directory() {} 357 358func (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 368type symLink struct { 369 resource 370 target string 371} 372 373var _ SymLink = &symLink{} 374 375func 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 386func (l *symLink) Target() string { 387 return l.target 388} 389 390type namedPipe struct { 391 resource 392} 393 394var _ NamedPipe = &namedPipe{} 395 396func 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 409func (np *namedPipe) Pipe() {} 410 411func (np *namedPipe) Paths() []string { 412 paths := make([]string, len(np.paths)) 413 copy(paths, np.paths) 414 return paths 415} 416 417func (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 427type device struct { 428 resource 429 major, minor uint64 430} 431 432var _ Device = &device{} 433 434func 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 449func (d *device) Paths() []string { 450 paths := make([]string, len(d.paths)) 451 copy(paths, d.paths) 452 return paths 453} 454 455func (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 465func (d device) Major() uint64 { 466 return d.major 467} 468 469func (d device) Minor() uint64 { 470 return d.minor 471} 472 473// toProto converts a resource to a protobuf record. We'd like to push this 474// the individual types but we want to keep this all together during 475// prototyping. 476func 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 // Sorts the XAttrs by name for consistent ordering. 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 // enforce a few stability guarantees that may not be provided by the 516 // resource implementation. 517 sort.Strings(b.Path) 518 519 return b 520} 521 522// fromProto converts from a protobuf Resource to a Resource interface. 523func 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 // TODO(stevvooe): Should we be validating at this point? 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// NOTE(stevvooe): An alternative model that supports inline declaration. 560// Convenient for unit testing where inline declarations may be desirable but 561// creates an awkward API for the standard use case. 562 563// type ResourceKind int 564 565// const ( 566// ResourceRegularFile = iota + 1 567// ResourceDirectory 568// ResourceSymLink 569// Resource 570// ) 571 572// type Resource struct { 573// Kind ResourceKind 574// Paths []string 575// Mode os.FileMode 576// UID string 577// GID string 578// Size int64 579// Digests []digest.Digest 580// Target string 581// Major, Minor int 582// XAttrs map[string][]byte 583// } 584 585// type RegularFile struct { 586// Paths []string 587// Size int64 588// Digests []digest.Digest 589// Perm os.FileMode // os.ModePerm + sticky, setuid, setgid 590// } 591