github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/oci/layer/tar_extract.go (about) 1 /* 2 * umoci: Umoci Modifies Open Containers' Images 3 * Copyright (C) 2016, 2017, 2018 SUSE LLC. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package layer 19 20 import ( 21 "archive/tar" 22 "bytes" 23 "fmt" 24 "io" 25 "os" 26 "path/filepath" 27 "strings" 28 "time" 29 30 "github.com/apex/log" 31 "github.com/cyphar/filepath-securejoin" 32 "github.com/openSUSE/umoci/pkg/fseval" 33 "github.com/openSUSE/umoci/pkg/system" 34 "github.com/openSUSE/umoci/third_party/shared" 35 "github.com/pkg/errors" 36 "golang.org/x/sys/unix" 37 ) 38 39 // inUserNamespace is a cached return value of shared.RunningInUserNS(). We 40 // compute this once globally rather than for each unpack. It won't change (we 41 // would hope) after we check it the first time. 42 var inUserNamespace = shared.RunningInUserNS() 43 44 // TarExtractor represents a tar file to be extracted. 45 type TarExtractor struct { 46 // mapOptions is the set of mapping options to use when extracting 47 // filesystem layers. 48 mapOptions MapOptions 49 50 // partialRootless indicates whether "partial rootless" tricks should be 51 // applied in our extraction. Rootless and userns execution have some 52 // similar tricks necessary, but not all rootless tricks should be applied 53 // when running in a userns -- hence the term "partial rootless" tricks. 54 partialRootless bool 55 56 // fsEval is an fseval.FsEval used for extraction. 57 fsEval fseval.FsEval 58 59 // upperPaths are paths that have either been extracted in the execution of 60 // this TarExtractor or are ancestors of paths extracted. The purpose of 61 // having this stored in-memory is to be able to handle opaque whiteouts as 62 // well as some other possible ordering issues with malformed archives (the 63 // downside of this approach is that it takes up memory -- we could switch 64 // to a trie if necessary). These paths are relative to the tar root but 65 // are fully symlink-expanded so no need to worry about that line noise. 66 upperPaths map[string]struct{} 67 } 68 69 // NewTarExtractor creates a new TarExtractor. 70 func NewTarExtractor(opt MapOptions) *TarExtractor { 71 fsEval := fseval.DefaultFsEval 72 if opt.Rootless { 73 fsEval = fseval.RootlessFsEval 74 } 75 76 return &TarExtractor{ 77 mapOptions: opt, 78 partialRootless: opt.Rootless || inUserNamespace, 79 fsEval: fsEval, 80 upperPaths: make(map[string]struct{}), 81 } 82 } 83 84 // restoreMetadata applies the state described in tar.Header to the filesystem 85 // at the given path. No sanity checking is done of the tar.Header's pathname 86 // or other information. In addition, no mapping is done of the header. 87 func (te *TarExtractor) restoreMetadata(path string, hdr *tar.Header) error { 88 // Some of the tar.Header fields don't match the OS API. 89 fi := hdr.FileInfo() 90 91 // Get the _actual_ file info to figure out if the path is a symlink. 92 isSymlink := hdr.Typeflag == tar.TypeSymlink 93 if realFi, err := te.fsEval.Lstat(path); err == nil { 94 isSymlink = realFi.Mode()&os.ModeSymlink == os.ModeSymlink 95 } 96 97 // Apply the owner. If we are rootless then "user.rootlesscontainers" has 98 // already been set up by unmapHeader, so nothing to do here. 99 if !te.mapOptions.Rootless { 100 // XXX: While unpriv.Lchown doesn't make a whole lot of sense this 101 // should _probably_ be put inside FsEval. 102 if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil { 103 return errors.Wrapf(err, "restore chown metadata: %s", path) 104 } 105 } 106 107 // We cannot apply hdr.Mode to symlinks, because symlinks don't have a mode 108 // of their own (they're special in that way). We have to apply this after 109 // we've applied the owner because setuid bits are cleared when changing 110 // owner (in rootless we don't care because we're always the owner). 111 if !isSymlink { 112 if err := te.fsEval.Chmod(path, fi.Mode()); err != nil { 113 return errors.Wrapf(err, "restore chmod metadata: %s", path) 114 } 115 } 116 117 // Apply access and modified time. Note that some archives won't fill the 118 // atime and mtime fields, so we have to set them to a more sane value. 119 // Otherwise Linux will start screaming at us, and nobody wants that. 120 mtime := hdr.ModTime 121 if mtime.IsZero() { 122 // XXX: Should we instead default to atime if it's non-zero? 123 mtime = time.Now() 124 } 125 atime := hdr.AccessTime 126 if atime.IsZero() { 127 // Default to the mtime. 128 atime = mtime 129 } 130 131 // Apply xattrs. In order to make sure that we *only* have the xattr set we 132 // want, we first clear the set of xattrs from the file then apply the ones 133 // set in the tar.Header. 134 err := te.fsEval.Lclearxattrs(path, ignoreXattrs) 135 if err != nil { 136 if errors.Cause(err) != unix.ENOTSUP { 137 return errors.Wrapf(err, "clear xattr metadata: %s", path) 138 } 139 log.Warnf("ignoring ENOTSUP on clearxattrs %q", path) 140 } 141 142 for name, value := range hdr.Xattrs { 143 value := []byte(value) 144 145 // Forbidden xattrs should never be touched. 146 if _, skip := ignoreXattrs[name]; skip { 147 // If the xattr is already set to the requested value, don't bail. 148 // The reason for this logic is kinda convoluted, but effectively 149 // because restoreMetadata is called with the *on-disk* metadata we 150 // run the risk of things like "security.selinux" being included in 151 // that metadata (and thus tripping the forbidden xattr error). By 152 // only touching xattrs that have a different value we are somewhat 153 // more efficient and we don't have to special case parent restore. 154 // Of course this will only ever impact ignoreXattrs. 155 if oldValue, err := te.fsEval.Lgetxattr(path, name); err == nil { 156 if bytes.Equal(value, oldValue) { 157 log.Debugf("restore xattr metadata: skipping already-set xattr %q: %s", name, hdr.Name) 158 continue 159 } 160 } 161 log.Warnf("xatt{%s} ignoring forbidden xattr: %q", hdr.Name, name) 162 continue 163 } 164 if err := te.fsEval.Lsetxattr(path, name, value, 0); err != nil { 165 // In rootless mode, some xattrs will fail (security.capability). 166 // This is _fine_ as long as we're not running as root (in which 167 // case we shouldn't be ignoring xattrs that we were told to set). 168 // 169 // TODO: We should translate all security.capability capabilites 170 // into v3 capabilities, which allow us to write them as 171 // unprivileged users (we also would need to translate them 172 // back when creating archives). 173 if te.partialRootless && os.IsPermission(errors.Cause(err)) { 174 log.Warnf("rootless{%s} ignoring (usually) harmless EPERM on setxattr %q", hdr.Name, name) 175 continue 176 } 177 // We cannot do much if we get an ENOTSUP -- this usually means 178 // that extended attributes are simply unsupported by the 179 // underlying filesystem (such as AUFS or NFS). 180 if errors.Cause(err) == unix.ENOTSUP { 181 log.Warnf("xatt{%s} ignoring ENOTSUP on setxattr %q", hdr.Name, name) 182 continue 183 } 184 return errors.Wrapf(err, "restore xattr metadata: %s", path) 185 } 186 } 187 188 if err := te.fsEval.Lutimes(path, atime, mtime); err != nil { 189 return errors.Wrapf(err, "restore lutimes metadata: %s", path) 190 } 191 192 return nil 193 } 194 195 // applyMetadata applies the state described in tar.Header to the filesystem at 196 // the given path, using the state of the TarExtractor to remap information 197 // within the header. This should only be used with headers from a tar layer 198 // (not from the filesystem). No sanity checking is done of the tar.Header's 199 // pathname or other information. 200 func (te *TarExtractor) applyMetadata(path string, hdr *tar.Header) error { 201 // Modify the header. 202 if err := unmapHeader(hdr, te.mapOptions); err != nil { 203 return errors.Wrap(err, "unmap header") 204 } 205 206 // Restore it on the filesystme. 207 return te.restoreMetadata(path, hdr) 208 } 209 210 // isDirlink returns whether the given path is a link to a directory (or a 211 // dirlink in rsync(1) parlance) which is used by --keep-dirlink to see whether 212 // we should extract through the link or clobber the link with a directory (in 213 // the case where we see a directory to extract and a symlink already exists 214 // there). 215 func (te *TarExtractor) isDirlink(root string, path string) (bool, error) { 216 // Make sure it exists and is a symlink. 217 if _, err := te.fsEval.Readlink(path); err != nil { 218 return false, errors.Wrap(err, "read dirlink") 219 } 220 221 // Technically a string.TrimPrefix would also work... 222 unsafePath, err := filepath.Rel(root, path) 223 if err != nil { 224 return false, errors.Wrap(err, "get relative-to-root path") 225 } 226 227 // It should be noted that SecureJoin will evaluate all symlinks in the 228 // path, so we don't need to loop over it or anything like that. It'll just 229 // be done for us (in UnpackEntry only the dirname(3) is evaluated but here 230 // we evaluate the whole thing). 231 targetPath, err := securejoin.SecureJoinVFS(root, unsafePath, te.fsEval) 232 if err != nil { 233 // We hit a symlink loop -- which is fine but that means that this 234 // cannot be considered a dirlink. 235 if errno := InnerErrno(err); errno == unix.ELOOP { 236 err = nil 237 } 238 return false, errors.Wrap(err, "sanitize old target") 239 } 240 241 targetInfo, err := te.fsEval.Lstat(targetPath) 242 if err != nil { 243 // ENOENT or similar just means that it's a broken symlink, which 244 // means we have to overwrite it (but it's an allowed case). 245 if securejoin.IsNotExist(err) { 246 err = nil 247 } 248 return false, err 249 } 250 251 return targetInfo.IsDir(), nil 252 } 253 254 // UnpackEntry extracts the given tar.Header to the provided root, ensuring 255 // that the layer state is consistent with the layer state that produced the 256 // tar archive being iterated over. This does handle whiteouts, so a tar.Header 257 // that represents a whiteout will result in the path being removed. 258 func (te *TarExtractor) UnpackEntry(root string, hdr *tar.Header, r io.Reader) (Err error) { 259 // Make the paths safe. 260 hdr.Name = CleanPath(hdr.Name) 261 root = filepath.Clean(root) 262 263 log.WithFields(log.Fields{ 264 "root": root, 265 "path": hdr.Name, 266 "type": hdr.Typeflag, 267 }).Debugf("unpacking entry") 268 269 // Get directory and filename, but we have to safely get the directory 270 // component of the path. SecureJoinVFS will evaluate the path itself, 271 // which we don't want (we're clever enough to handle the actual path being 272 // a symlink). 273 unsafeDir, file := filepath.Split(hdr.Name) 274 if filepath.Join("/", hdr.Name) == "/" { 275 // If we got an entry for the root, then unsafeDir is the full path. 276 unsafeDir, file = hdr.Name, "." 277 } 278 dir, err := securejoin.SecureJoinVFS(root, unsafeDir, te.fsEval) 279 if err != nil { 280 return errors.Wrap(err, "sanitise symlinks in root") 281 } 282 path := filepath.Join(dir, file) 283 284 // Before we do anything, get the state of dir. Because we might be adding 285 // or removing files, our parent directory might be modified in the 286 // process. As a result, we want to be able to restore the old state 287 // (because we only apply state that we find in the archive we're iterating 288 // over). We can safely ignore an error here, because a non-existent 289 // directory will be fixed by later archive entries. 290 if dirFi, err := te.fsEval.Lstat(dir); err == nil && path != dir { 291 // FIXME: This is really stupid. 292 link, _ := te.fsEval.Readlink(dir) 293 dirHdr, err := tar.FileInfoHeader(dirFi, link) 294 if err != nil { 295 return errors.Wrap(err, "convert dirFi to dirHdr") 296 } 297 298 // More faking to trick restoreMetadata to actually restore the directory. 299 dirHdr.Typeflag = tar.TypeDir 300 dirHdr.Linkname = "" 301 302 // os.Lstat doesn't get the list of xattrs by default. We need to fill 303 // this explicitly. Note that while Go's "archive/tar" takes strings, 304 // in Go strings can be arbitrary byte sequences so this doesn't 305 // restrict the possible values. 306 // TODO: Move this to a separate function so we can share it with 307 // tar_generate.go. 308 xattrs, err := te.fsEval.Llistxattr(dir) 309 if err != nil { 310 if errors.Cause(err) != unix.ENOTSUP { 311 return errors.Wrap(err, "get dirHdr.Xattrs") 312 } 313 log.Warnf("ignoring ENOTSUP on listxattr %q", dir) 314 } 315 if len(xattrs) > 0 { 316 dirHdr.Xattrs = map[string]string{} 317 for _, xattr := range xattrs { 318 value, err := te.fsEval.Lgetxattr(dir, xattr) 319 if err != nil { 320 return errors.Wrap(err, "get xattr") 321 } 322 dirHdr.Xattrs[xattr] = string(value) 323 } 324 } 325 326 // Ensure that after everything we correctly re-apply the old metadata. 327 // We don't map this header because we're restoring files that already 328 // existed on the filesystem, not from a tar layer. 329 defer func() { 330 // Only overwrite the error if there wasn't one already. 331 if err := te.restoreMetadata(dir, dirHdr); err != nil { 332 if Err == nil { 333 Err = errors.Wrap(err, "restore parent directory") 334 } 335 } 336 }() 337 } 338 339 // Currently the spec doesn't specify what the hdr.Typeflag of whiteout 340 // files is meant to be. We specifically only produce regular files 341 // ('\x00') but it could be possible that someone produces a different 342 // Typeflag, expecting that the path is the only thing that matters in a 343 // whiteout entry. 344 if strings.HasPrefix(file, whPrefix) { 345 isOpaque := file == whOpaque 346 file = strings.TrimPrefix(file, whPrefix) 347 348 // We have to be quite careful here. While the most intuitive way of 349 // handling whiteouts would be to just RemoveAll without prejudice, We 350 // have to be careful here. If there is a whiteout entry for a file 351 // *after* a normal entry (in the same layer) then the whiteout must 352 // not remove the new entry. We handle this by keeping track of 353 // whichpaths have been touched by this layer's extraction (these form 354 // the "upperdir"). We also have to handle cases where a directory has 355 // been marked for deletion, but a child has been extracted in this 356 // layer. 357 358 path = filepath.Join(dir, file) 359 if isOpaque { 360 path = dir 361 } 362 363 // If the root doesn't exist we've got nothing to do. 364 // XXX: We currently cannot error out if a layer asks us to remove a 365 // non-existent path with this implementation (because we don't 366 // know if it was implicitly removed by another whiteout). In 367 // future we could add lowerPaths that would help track whether 368 // another whiteout caused the removal to "fail" or if the path 369 // was actually missing -- which would allow us to actually error 370 // out here if the layer is invalid). 371 if _, err := te.fsEval.Lstat(path); err != nil { 372 // Need to use securejoin.IsNotExist to handle ENOTDIR. 373 if securejoin.IsNotExist(err) { 374 err = nil 375 } 376 return errors.Wrap(err, "check whiteout target") 377 } 378 379 // Walk over the path to remove it. We remove a given path as soon as 380 // it isn't present in upperPaths (which includes ancestors of paths 381 // we've extracted so we only need to look up the one path). Otherwise 382 // we iterate over any children and try again. The only difference 383 // between opaque whiteouts and regular whiteouts is that we don't 384 // delete the directory itself with opaque whiteouts. 385 err = te.fsEval.Walk(path, func(subpath string, info os.FileInfo, err error) error { 386 // If we are passed an error, bail unless it's ENOENT. 387 if err != nil { 388 // If something was deleted outside of our knowledge it's not 389 // the end of the world. In principle this shouldn't happen 390 // though, so we log it for posterity. 391 if os.IsNotExist(errors.Cause(err)) { 392 log.Debugf("whiteout removal hit already-deleted path: %s", subpath) 393 err = filepath.SkipDir 394 } 395 return err 396 } 397 398 // Get the relative form of subpath to root to match 399 // te.upperPaths. 400 upperPath, err := filepath.Rel(root, subpath) 401 if err != nil { 402 return errors.Wrap(err, "find relative-to-root [should never happen]") 403 } 404 405 // Remove the path only if it hasn't been touched. 406 if _, ok := te.upperPaths[upperPath]; !ok { 407 // Opaque whiteouts don't remove the directory itself, so skip 408 // the top-level directory. 409 if isOpaque && CleanPath(path) == CleanPath(subpath) { 410 return nil 411 } 412 413 // Purge the path. We skip anything underneath (if it's a 414 // directory) since we just purged it -- and we don't want to 415 // hit ENOENT during iteration for no good reason. 416 err := errors.Wrap(te.fsEval.RemoveAll(subpath), "whiteout subpath") 417 if err == nil && info.IsDir() { 418 err = filepath.SkipDir 419 } 420 return err 421 } 422 return nil 423 }) 424 return errors.Wrap(err, "whiteout remove") 425 } 426 427 // Get information about the path. This has to be done after we've dealt 428 // with whiteouts because it turns out that lstat(2) will return EPERM if 429 // you try to stat a whiteout on AUFS. 430 fi, err := te.fsEval.Lstat(path) 431 if err != nil { 432 // File doesn't exist, just switch fi to the file header. 433 fi = hdr.FileInfo() 434 } 435 436 // Attempt to create the parent directory of the path we're unpacking. 437 // We do a MkdirAll here because even though you need to have a tar entry 438 // for every component of a new path, applyMetadata will correct any 439 // inconsistencies. 440 // FIXME: We have to make this consistent, since if the tar archive doesn't 441 // have entries for some of these components we won't be able to 442 // verify that we have consistent results during unpacking. 443 if err := te.fsEval.MkdirAll(dir, 0777); err != nil { 444 return errors.Wrap(err, "mkdir parent") 445 } 446 447 isDirlink := false 448 // We remove whatever existed at the old path to clobber it so that 449 // creating a new path will not break. The only exception is if the path is 450 // a directory in both the layer and the current filesystem, in which case 451 // we don't delete it for obvious reasons. In all other cases we clobber. 452 // 453 // Note that this will cause hard-links in the "lower" layer to not be able 454 // to point to "upper" layer inodes even if the extracted type is the same 455 // as the old one, however it is not clear whether this is something a user 456 // would expect anyway. In addition, this will incorrectly deal with a 457 // TarLink that is present before the "upper" entry in the layer but the 458 // "lower" file still exists (so the hard-link would point to the old 459 // inode). It's not clear if such an archive is actually valid though. 460 if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { 461 // If we are in --keep-dirlinks mode and the existing fs object is a 462 // symlink to a directory (with the pending object is a directory), we 463 // don't remove the symlink (and instead allow subsequent objects to be 464 // just written through the symlink into the directory). This is a very 465 // specific usecase where layers that were generated independently from 466 // each other (on different base filesystems) end up with weird things 467 // like /lib64 being a symlink only sometimes but you never want to 468 // delete libraries (not just the ones that were under the "real" 469 // directory). 470 // 471 // TODO: This code should also handle a pending symlink entry where the 472 // existing object is a directory. I'm not sure how we could 473 // disambiguate this from a symlink-to-a-file but I imagine that 474 // this is something that would also be useful in the same vein 475 // as --keep-dirlinks (which currently only prevents clobbering 476 // in the opposite case). 477 if te.mapOptions.KeepDirlinks && 478 fi.Mode()&os.ModeSymlink == os.ModeSymlink && hdr.Typeflag == tar.TypeDir { 479 isDirlink, err = te.isDirlink(root, path) 480 if err != nil { 481 return errors.Wrap(err, "check is dirlink") 482 } 483 } 484 if !(isDirlink && te.mapOptions.KeepDirlinks) { 485 if err := te.fsEval.RemoveAll(path); err != nil { 486 return errors.Wrap(err, "clobber old path") 487 } 488 } 489 } 490 491 // Now create or otherwise modify the state of the path. Right now, either 492 // the type of path matches hdr or the path doesn't exist. Note that we 493 // don't care about umasks or the initial mode here, since applyMetadata 494 // will fix all of that for us. 495 switch hdr.Typeflag { 496 // regular file 497 case tar.TypeReg, tar.TypeRegA: 498 // Create a new file, then just copy the data. 499 fh, err := te.fsEval.Create(path) 500 if err != nil { 501 return errors.Wrap(err, "create regular") 502 } 503 defer fh.Close() 504 505 // We need to make sure that we copy all of the bytes. 506 if n, err := io.Copy(fh, r); err != nil { 507 return err 508 } else if int64(n) != hdr.Size { 509 return errors.Wrap(io.ErrShortWrite, "unpack to regular file") 510 } 511 512 // Force close here so that we don't affect the metadata. 513 fh.Close() 514 515 // directory 516 case tar.TypeDir: 517 if isDirlink { 518 break 519 } 520 521 // Attempt to create the directory. We do a MkdirAll here because even 522 // though you need to have a tar entry for every component of a new 523 // path, applyMetadata will correct any inconsistencies. 524 if err := te.fsEval.MkdirAll(path, 0777); err != nil { 525 return errors.Wrap(err, "mkdirall") 526 } 527 528 // hard link, symbolic link 529 case tar.TypeLink, tar.TypeSymlink: 530 linkname := hdr.Linkname 531 532 // Hardlinks and symlinks act differently when it comes to the scoping. 533 // In both cases, we have to just unlink and then re-link the given 534 // path. But the function used and the argument are slightly different. 535 var linkFn func(string, string) error 536 switch hdr.Typeflag { 537 case tar.TypeLink: 538 linkFn = te.fsEval.Link 539 // Because hardlinks are inode-based we need to scope the link to 540 // the rootfs using SecureJoinVFS. As before, we need to be careful 541 // that we don't resolve the last part of the link path (in case 542 // the user actually wanted to hardlink to a symlink). 543 unsafeLinkDir, linkFile := filepath.Split(CleanPath(linkname)) 544 linkDir, err := securejoin.SecureJoinVFS(root, unsafeLinkDir, te.fsEval) 545 if err != nil { 546 return errors.Wrap(err, "sanitise hardlink target in root") 547 } 548 linkname = filepath.Join(linkDir, linkFile) 549 case tar.TypeSymlink: 550 linkFn = te.fsEval.Symlink 551 } 552 553 // Link the new one. 554 if err := linkFn(linkname, path); err != nil { 555 // FIXME: Currently this can break if tar hardlink entries occur 556 // before we hit the entry those hardlinks link to. I have a 557 // feeling that such archives are invalid, but the correct 558 // way of handling this is to delay link creation until the 559 // very end. Unfortunately this won't work with symlinks 560 // (which can link to directories). 561 return errors.Wrap(err, "link") 562 } 563 564 // character device node, block device node 565 case tar.TypeChar, tar.TypeBlock: 566 // In rootless mode we have no choice but to fake this, since mknod(2) 567 // doesn't work as an unprivileged user here. 568 // 569 // TODO: We need to add the concept of a fake block device in 570 // "user.rootlesscontainers", because this workaround suffers 571 // from the obvious issue that if the file is touched (even the 572 // metadata) then it will be incorrectly copied into the layer. 573 // This would break distribution images fairly badly. 574 if te.partialRootless { 575 log.Warnf("rootless{%s} creating empty file in place of device %d:%d", hdr.Name, hdr.Devmajor, hdr.Devminor) 576 fh, err := te.fsEval.Create(path) 577 if err != nil { 578 return errors.Wrap(err, "create rootless block") 579 } 580 defer fh.Close() 581 if err := fh.Chmod(0); err != nil { 582 return errors.Wrap(err, "chmod 0 rootless block") 583 } 584 goto out 585 } 586 587 // Otherwise the handling is the same as a FIFO. 588 fallthrough 589 // fifo node 590 case tar.TypeFifo: 591 // We have to remove and then create the device. In the FIFO case we 592 // could choose not to do so, but we do it anyway just to be on the 593 // safe side. 594 595 mode := system.Tarmode(hdr.Typeflag) 596 dev := unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor)) 597 598 // Create the node. 599 if err := te.fsEval.Mknod(path, os.FileMode(int64(mode)|hdr.Mode), dev); err != nil { 600 return errors.Wrap(err, "mknod") 601 } 602 603 // We should never hit any other headers (Go abstracts them away from us), 604 // and we can't handle any custom Tar extensions. So just error out. 605 default: 606 return fmt.Errorf("unpack entry: %s: unknown typeflag '\\x%x'", hdr.Name, hdr.Typeflag) 607 } 608 609 out: 610 // Apply the metadata, which will apply any mappings necessary. We don't 611 // apply metadata for hardlinks, because hardlinks don't have any separate 612 // metadata from their link (and the tar headers might not be filled). 613 if hdr.Typeflag != tar.TypeLink { 614 if err := te.applyMetadata(path, hdr); err != nil { 615 return errors.Wrap(err, "apply hdr metadata") 616 } 617 } 618 619 // Everything is done -- the path now exists. Add it (and all its 620 // ancestors) to the set of upper paths. We first have to figure out the 621 // proper path corresponding to hdr.Name though. 622 upperPath, err := filepath.Rel(root, path) 623 if err != nil { 624 // Really shouldn't happen because of the guarantees of SecureJoinVFS. 625 return errors.Wrap(err, "find relative-to-root [should never happen]") 626 } 627 for pth := upperPath; pth != filepath.Dir(pth); pth = filepath.Dir(pth) { 628 te.upperPaths[pth] = struct{}{} 629 } 630 return nil 631 }