func TestNegativeTime(t *testing.T) { ts := NewTestCase(t) defer ts.Cleanup() _, err := os.Create(ts.origFile) if err != nil { t.Fatalf("Create failed: %v", err) } var stat syscall.Stat_t // set negative nanosecond will occur errors on UtimesNano as invalid argument ut := time.Date(1960, time.January, 10, 23, 0, 0, 0, time.UTC) tim := []syscall.Timespec{ syscall.NsecToTimespec(ut.UnixNano()), syscall.NsecToTimespec(ut.UnixNano()), } err = syscall.UtimesNano(ts.mountFile, tim) if err != nil { t.Fatalf("UtimesNano failed: %v", err) } err = syscall.Lstat(ts.mountFile, &stat) if err != nil { t.Fatalf("Lstat failed: %v", err) } if stat.Atim.Sec >= 0 || stat.Mtim.Sec >= 0 { t.Errorf("Got wrong timestamps %v", stat) } }
func touch(name string) { st, err := stat(name) if err != nil { if !os.IsNotExist(err) { ek(err) return } if *cflag { return } } else { if !*aflag { times[0] = st.Atim } if !*mflag { times[1] = st.Mtim } err = syscall.UtimesNano(name, []syscall.Timespec{times[0], times[1]}) ek(err) return } f, err := os.OpenFile(name, os.O_CREATE|os.O_EXCL, 0644) if ek(err) { return } f.Close() touch(name) }
// Setting nanoseconds should work for dates after 1970 func TestUtimesNano(t *testing.T) { tc := NewTestCase(t) defer tc.Cleanup() path := tc.mountFile err := ioutil.WriteFile(path, []byte("xyz"), 0600) if err != nil { t.Fatal(err) } ts := make([]syscall.Timespec, 2) // atime ts[0].Sec = 1 ts[0].Nsec = 2 // mtime ts[1].Sec = 3 ts[1].Nsec = 4 err = syscall.UtimesNano(path, ts) if err != nil { t.Fatal(err) } var st syscall.Stat_t err = syscall.Stat(path, &st) if err != nil { t.Fatal(err) } if st.Atim != ts[0] { t.Errorf("Wrong atime: %v, want: %v", st.Atim, ts[0]) } if st.Mtim != ts[1] { t.Errorf("Wrong mtime: %v, want: %v", st.Mtim, ts[1]) } }
func setZeroModTime(filename string) error { var utimes = []syscall.Timespec{ syscall.NsecToTimespec(0), syscall.NsecToTimespec(0), } return syscall.UtimesNano(filename, utimes) }
func UtimesNano(path string, atime time.Time, mtime time.Time) error { var utimes [2]syscall.Timespec utimes[0] = syscall.NsecToTimespec(atime.UnixNano()) utimes[1] = syscall.NsecToTimespec(mtime.UnixNano()) if err := syscall.UtimesNano(path, utimes[0:]); err != nil { return &os.PathError{"chtimes", path, err} } return nil }
// RSyncWithDelete syncs srcDir to destDir func RSyncWithDelete(srcDirName, destDirName string) error { // first remove everything thats not in srcdir err := filepath.Walk(destDirName, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // relative to the root "destDirName" relPath := path[len(destDirName):] if !FileExists(filepath.Join(srcDirName, relPath)) { if err := os.RemoveAll(path); err != nil { return err } if info.IsDir() { return filepath.SkipDir } } return nil }) if err != nil { return err } // then copy or update the data from srcdir to destdir err = filepath.Walk(srcDirName, func(src string, info os.FileInfo, err error) error { if err != nil { return err } // relative to the root "srcDirName" relPath := src[len(srcDirName):] dst := filepath.Join(destDirName, relPath) if info.IsDir() { if err := os.MkdirAll(dst, info.Mode()); err != nil { return err } // this can panic. The alternative would be to use the "st, ok" pattern, and then if !ok... panic? st := info.Sys().(*syscall.Stat_t) ts := []syscall.Timespec{st.Atim, st.Mtim} return syscall.UtimesNano(dst, ts) } if !FilesAreEqual(src, dst) { // XXX: we should (eventually) use CopyFile here, // but we need to teach it about preserving // of atime/mtime and permissions output, err := exec.Command("cp", "-va", src, dst).CombinedOutput() if err != nil { return fmt.Errorf("Failed to copy %s to %s (%s)", src, dst, output) } } return nil }) return err }
// Chtimes changes the access and modification times of the named // file, similar to the Unix utime() or utimes() functions. // // The underlying filesystem may truncate or round the values to a // less precise time unit. // If there is an error, it will be of type *PathError. func Chtimes(name string, atime time.Time, mtime time.Time) error { var utimes [2]syscall.Timespec utimes[0] = syscall.NsecToTimespec(atime.UnixNano()) utimes[1] = syscall.NsecToTimespec(mtime.UnixNano()) if e := syscall.UtimesNano(name, utimes[0:]); e != nil { return &PathError{"chtimes", name, e} } return nil }
func UtimesNano(path string, atime time.Time, mtime time.Time) error { // Note that this is disambiguated from plain `os.Chtimes` only in that it refuses to fall back to lower precision on old kernels. // Like LUtimesNano, it depends on kernel 2.6.22 or newer. var utimes [2]syscall.Timespec utimes[0] = syscall.NsecToTimespec(atime.UnixNano()) utimes[1] = syscall.NsecToTimespec(mtime.UnixNano()) if err := syscall.UtimesNano(path, utimes[0:]); err != nil { return &os.PathError{"chtimes", path, err} } return nil }
func (node Node) RestoreTimestamps(path string) error { var utimes = [...]syscall.Timespec{ syscall.NsecToTimespec(node.AccessTime.UnixNano()), syscall.NsecToTimespec(node.ModTime.UnixNano()), } if node.Type == "symlink" { return node.restoreSymlinkTimestamps(path, utimes) } if err := syscall.UtimesNano(path, utimes[:]); err != nil { return errors.Wrap(err, "UtimesNano") } return nil }
func (constor *Constor) createPath(dirpath string) error { dirs := strings.Split(dirpath, "/") if len(dirs) == 0 { return syscall.EIO } subdir := "" for _, dir := range dirs { if dir == "" { continue } subdir = Path.Join(subdir, "/", dir) li := constor.getLayer(subdir) if li == 0 { continue } if li == -1 { return syscall.EIO } stat := syscall.Stat_t{} if err := constor.Lstat(subdir, &stat); err != nil { return err } subdirl := Path.Join(constor.layers[0], subdir) if err := syscall.Mkdir(subdirl, stat.Mode); err != nil { return err } if err := syscall.Chown(subdirl, int(stat.Uid), int(stat.Gid)); err != nil { return err } if err := syscall.UtimesNano(subdirl, []syscall.Timespec{stat.Atim, stat.Mtim}); err != nil { return err } inoitoa := strconv.Itoa(int(stat.Ino)) inobyte := []byte(inoitoa) if err := syscall.Setxattr(subdirl, INOXATTR, inobyte, 0); err != nil { return err } inode, err := constor.inodemap.findInode(stat.Ino) if err != nil { return err } inode.Lock() inode.layer = 0 inode.Unlock() } return nil }
func (f *file) Utimens(a *time.Time, m *time.Time) fuse.Status { ts := make([]syscall.Timespec, 2) if a == nil { ts[0].Nsec = _UTIME_OMIT } else { ts[0].Sec = a.Unix() } if m == nil { ts[1].Nsec = _UTIME_OMIT } else { ts[1].Sec = m.Unix() } f.lock.Lock() fn := fmt.Sprintf("/proc/self/fd/%d", f.fd.Fd()) err := syscall.UtimesNano(fn, ts) f.lock.Unlock() return fuse.ToStatus(err) }
// ExtractTarInsecure extracts a tarball (from a tar.Reader) into the target // directory. If pwl is not nil, only the paths in the map are extracted. If // overwrite is true, existing files will be overwritten. func ExtractTarInsecure(tr *tar.Reader, target string, overwrite bool, pwl PathWhitelistMap, editor FilePermissionsEditor) error { um := syscall.Umask(0) defer syscall.Umask(um) var dirhdrs []*tar.Header Tar: for { hdr, err := tr.Next() switch err { case io.EOF: break Tar case nil: if pwl != nil { relpath := filepath.Clean(hdr.Name) if _, ok := pwl[relpath]; !ok { continue } } err = extractFile(tr, target, hdr, overwrite, editor) if err != nil { return fmt.Errorf("error extracting tarball: %v", err) } if hdr.Typeflag == tar.TypeDir { dirhdrs = append(dirhdrs, hdr) } default: return fmt.Errorf("error extracting tarball: %v", err) } } // Restore dirs atime and mtime. This has to be done after extracting // as a file extraction will change its parent directory's times. for _, hdr := range dirhdrs { p := filepath.Join(target, hdr.Name) if err := syscall.UtimesNano(p, HdrToTimespec(hdr)); err != nil { return err } } return nil }
// Untar reads a stream of bytes from `archive`, parses it as a tar archive, // and unpacks it into the directory at `dest`. // The archive may be compressed with one of the following algorithms: // identity (uncompressed), gzip, bzip2, xz. // If `dest` does not exist, it is created unless there are multiple entries in `archive`. // In the latter case, an error is returned. // If `dest` is an existing file, it gets overwritten. // If `dest` is an existing directory, its files get merged (with overwrite for conflicting files). func Untar(archive io.Reader, dest string, options *TarOptions) error { if archive == nil { return fmt.Errorf("Empty archive") } decompressedArchive, err := DecompressStream(archive) if err != nil { return err } defer decompressedArchive.Close() tr := tar.NewReader(decompressedArchive) var ( dirs []*tar.Header create bool multipleEntries bool ) if fi, err := os.Lstat(dest); err != nil { if !os.IsNotExist(err) { return err } // destination does not exist, so it is assumed it has to be created. create = true } else if !fi.IsDir() { // destination exists and is not a directory, so it will be overwritten. create = true } // Iterate through the files in the archive. for { hdr, err := tr.Next() if err == io.EOF { // end of tar archive break } if err != nil { return err } // Return an error if destination needs to be created and there is more than 1 entry in the tar stream. if create && multipleEntries { return fmt.Errorf("Trying to untar an archive with multiple entries to an inexistant target `%s`: did you mean `%s` instead?", dest, filepath.Dir(dest)) } // Normalize name, for safety and for a simple is-root check hdr.Name = filepath.Clean(hdr.Name) if !strings.HasSuffix(hdr.Name, "/") { // Not the root directory, ensure that the parent directory exists parent := filepath.Dir(hdr.Name) parentPath := filepath.Join(dest, parent) if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { err = os.MkdirAll(parentPath, 0777) if err != nil { return err } } } var path string if create { path = dest // we are renaming hdr.Name to dest } else { path = filepath.Join(dest, hdr.Name) } // If path exits we almost always just want to remove and replace it // The only exception is when it is a directory *and* the file from // the layer is also a directory. Then we want to merge them (i.e. // just apply the metadata from the layer). if fi, err := os.Lstat(path); err == nil { if fi.IsDir() && hdr.Name == "." { continue } if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { if err := os.RemoveAll(path); err != nil { return err } } } if err := createTarFile(path, dest, hdr, tr, options == nil || !options.NoLchown); err != nil { return err } // Successfully added an entry. Predicting multiple entries for next iteration (not current one). multipleEntries = true // Directory mtimes must be handled at the end to avoid further // file creation in them to modify the directory mtime if hdr.Typeflag == tar.TypeDir { dirs = append(dirs, hdr) } } for _, hdr := range dirs { path := filepath.Join(dest, hdr.Name) ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} if err := syscall.UtimesNano(path, ts); err != nil { return err } } return nil }
func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { tr := tar.NewReader(layer) trBuf := pools.BufioReader32KPool.Get(tr) defer pools.BufioReader32KPool.Put(trBuf) var dirs []*tar.Header aufsTempdir := "" aufsHardlinks := make(map[string]*tar.Header) // Iterate through the files in the archive. for { hdr, err := tr.Next() if err == io.EOF { // end of tar archive break } if err != nil { return 0, err } size += hdr.Size // Normalize name, for safety and for a simple is-root check hdr.Name = filepath.Clean(hdr.Name) // Windows does not support filenames with colons in them. Ignore // these files. This is not a problem though (although it might // appear that it is). Let's suppose a client is running docker pull. // The daemon it points to is Windows. Would it make sense for the // client to be doing a docker pull Ubuntu for example (which has files // with colons in the name under /usr/share/man/man3)? No, absolutely // not as it would really only make sense that they were pulling a // Windows image. However, for development, it is necessary to be able // to pull Linux images which are in the repository. // // TODO Windows. Once the registry is aware of what images are Windows- // specific or Linux-specific, this warning should be changed to an error // to cater for the situation where someone does manage to upload a Linux // image but have it tagged as Windows inadvertantly. if runtime.GOOS == "windows" { if strings.Contains(hdr.Name, ":") { logrus.Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name) continue } } // Note as these operations are platform specific, so must the slash be. if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) { // Not the root directory, ensure that the parent directory exists. // This happened in some tests where an image had a tarfile without any // parent directories. parent := filepath.Dir(hdr.Name) parentPath := filepath.Join(dest, parent) if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { err = system.MkdirAll(parentPath, 0600) if err != nil { return 0, err } } } // Skip AUFS metadata dirs if strings.HasPrefix(hdr.Name, ".wh..wh.") { // Regular files inside /.wh..wh.plnk can be used as hardlink targets // We don't want this directory, but we need the files in them so that // such hardlinks can be resolved. if strings.HasPrefix(hdr.Name, ".wh..wh.plnk") && hdr.Typeflag == tar.TypeReg { basename := filepath.Base(hdr.Name) aufsHardlinks[basename] = hdr if aufsTempdir == "" { if aufsTempdir, err = ioutil.TempDir("", "dockerplnk"); err != nil { return 0, err } defer os.RemoveAll(aufsTempdir) } if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true); err != nil { return 0, err } } continue } path := filepath.Join(dest, hdr.Name) rel, err := filepath.Rel(dest, path) if err != nil { return 0, err } // Note as these operations are platform specific, so must the slash be. if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) } base := filepath.Base(path) if strings.HasPrefix(base, ".wh.") { originalBase := base[len(".wh."):] originalPath := filepath.Join(filepath.Dir(path), originalBase) if err := os.RemoveAll(originalPath); err != nil { return 0, err } } else { // If path exits we almost always just want to remove and replace it. // The only exception is when it is a directory *and* the file from // the layer is also a directory. Then we want to merge them (i.e. // just apply the metadata from the layer). if fi, err := os.Lstat(path); err == nil { if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { if err := os.RemoveAll(path); err != nil { return 0, err } } } trBuf.Reset(tr) srcData := io.Reader(trBuf) srcHdr := hdr // Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so // we manually retarget these into the temporary files we extracted them into if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), ".wh..wh.plnk") { linkBasename := filepath.Base(hdr.Linkname) srcHdr = aufsHardlinks[linkBasename] if srcHdr == nil { return 0, fmt.Errorf("Invalid aufs hardlink") } tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename)) if err != nil { return 0, err } defer tmpFile.Close() srcData = tmpFile } if err := createTarFile(path, dest, srcHdr, srcData, true); err != nil { return 0, err } // Directory mtimes must be handled at the end to avoid further // file creation in them to modify the directory mtime if hdr.Typeflag == tar.TypeDir { dirs = append(dirs, hdr) } } } for _, hdr := range dirs { path := filepath.Join(dest, hdr.Name) ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} if err := syscall.UtimesNano(path, ts); err != nil { return 0, err } } return size, nil }
func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error { tr := tar.NewReader(decompressedArchive) trBuf := pools.BufioReader32KPool.Get(nil) defer pools.BufioReader32KPool.Put(trBuf) var dirs []*tar.Header // Iterate through the files in the archive. loop: for { hdr, err := tr.Next() if err == io.EOF { // end of tar archive break } if err != nil { return err } // Normalize name, for safety and for a simple is-root check // This keeps "../" as-is, but normalizes "/../" to "/". Or Windows: // This keeps "..\" as-is, but normalizes "\..\" to "\". hdr.Name = filepath.Clean(hdr.Name) for _, exclude := range options.ExcludePatterns { if strings.HasPrefix(hdr.Name, exclude) { continue loop } } // After calling filepath.Clean(hdr.Name) above, hdr.Name will now be in // the filepath format for the OS on which the daemon is running. Hence // the check for a slash-suffix MUST be done in an OS-agnostic way. if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) { // Not the root directory, ensure that the parent directory exists parent := filepath.Dir(hdr.Name) parentPath := filepath.Join(dest, parent) if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { err = system.MkdirAll(parentPath, 0777) if err != nil { return err } } } path := filepath.Join(dest, hdr.Name) rel, err := filepath.Rel(dest, path) if err != nil { return err } if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) } // If path exits we almost always just want to remove and replace it // The only exception is when it is a directory *and* the file from // the layer is also a directory. Then we want to merge them (i.e. // just apply the metadata from the layer). if fi, err := os.Lstat(path); err == nil { if options.NoOverwriteDirNonDir && fi.IsDir() && hdr.Typeflag != tar.TypeDir { // If NoOverwriteDirNonDir is true then we cannot replace // an existing directory with a non-directory from the archive. return fmt.Errorf("cannot overwrite directory %q with non-directory %q", path, dest) } if options.NoOverwriteDirNonDir && !fi.IsDir() && hdr.Typeflag == tar.TypeDir { // If NoOverwriteDirNonDir is true then we cannot replace // an existing non-directory with a directory from the archive. return fmt.Errorf("cannot overwrite non-directory %q with directory %q", path, dest) } if fi.IsDir() && hdr.Name == "." { continue } if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { if err := os.RemoveAll(path); err != nil { return err } } } trBuf.Reset(tr) if err := createTarFile(path, dest, hdr, trBuf, !options.NoLchown, options.ChownOpts); err != nil { return err } // Directory mtimes must be handled at the end to avoid further // file creation in them to modify the directory mtime if hdr.Typeflag == tar.TypeDir { dirs = append(dirs, hdr) } } for _, hdr := range dirs { path := filepath.Join(dest, hdr.Name) ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} if err := syscall.UtimesNano(path, ts); err != nil { return err } } return nil }
func CopyTree(src, dest string, uidRange *user.UidRange) error { cleanSrc := filepath.Clean(src) dirs := make(map[string][]syscall.Timespec) copyWalker := func(path string, info os.FileInfo, err error) error { if err != nil { return err } rootLess := path[len(cleanSrc):] target := filepath.Join(dest, rootLess) mode := info.Mode() switch { case mode.IsDir(): err := os.Mkdir(target, mode.Perm()) if err != nil { return err } dir, err := os.Open(target) if err != nil { return err } if err := dir.Chmod(mode); err != nil { dir.Close() return err } dir.Close() case mode.IsRegular(): if err := CopyRegularFile(path, target); err != nil { return err } case mode&os.ModeSymlink == os.ModeSymlink: if err := CopySymlink(path, target); err != nil { return err } case mode&os.ModeCharDevice == os.ModeCharDevice: stat := syscall.Stat_t{} if err := syscall.Stat(path, &stat); err != nil { return err } dev := device.Makedev(device.Major(stat.Rdev), device.Minor(stat.Rdev)) mode := uint32(mode) | syscall.S_IFCHR if err := syscall.Mknod(target, mode, int(dev)); err != nil { return err } case mode&os.ModeDevice == os.ModeDevice: stat := syscall.Stat_t{} if err := syscall.Stat(path, &stat); err != nil { return err } dev := device.Makedev(device.Major(stat.Rdev), device.Minor(stat.Rdev)) mode := uint32(mode) | syscall.S_IFBLK if err := syscall.Mknod(target, mode, int(dev)); err != nil { return err } case mode&os.ModeNamedPipe == os.ModeNamedPipe: if err := syscall.Mkfifo(target, uint32(mode)); err != nil { return err } default: return fmt.Errorf("unsupported mode: %v", mode) } var srcUid = info.Sys().(*syscall.Stat_t).Uid var srcGid = info.Sys().(*syscall.Stat_t).Gid shiftedUid, shiftedGid, err := uidRange.ShiftRange(srcUid, srcGid) if err != nil { return err } if err := os.Lchown(target, int(shiftedUid), int(shiftedGid)); err != nil { return err } // lchown(2) says that, depending on the linux kernel version, it // can change the file's mode also if executed as root. So call // os.Chmod after it. if mode&os.ModeSymlink != os.ModeSymlink { if err := os.Chmod(target, mode); err != nil { return err } } ts, err := pathToTimespec(path) if err != nil { return err } if mode.IsDir() { dirs[target] = ts } if mode&os.ModeSymlink != os.ModeSymlink { if err := syscall.UtimesNano(target, ts); err != nil { return err } } else { if err := LUtimesNano(target, ts); err != nil { return err } } return nil } if err := filepath.Walk(cleanSrc, copyWalker); err != nil { return err } // Restore dirs atime and mtime. This has to be done after copying // as a file copying will change its parent directory's times. for dirPath, ts := range dirs { if err := syscall.UtimesNano(dirPath, ts); err != nil { return err } } return nil }
func (constor *Constor) copyup(inode *Inode) error { constor.log("%s", inode.id) if inode.layer == 0 { return nil } src := constor.getPath(inode.layer, inode.id) if src == "" { return syscall.EIO } dst := constor.getPath(0, inode.id) if dst == "" { return syscall.EIO } fi, err := os.Lstat(src) if err != nil { return err } if fi.Mode()&os.ModeSymlink == os.ModeSymlink { linkName, err := os.Readlink(src) if err != nil { return err } err = os.Symlink(linkName, dst) if err != nil { return err } } else if fi.Mode()&os.ModeDir == os.ModeDir { err := os.Mkdir(dst, fi.Mode()) if err != nil { return err } } else { in, err := os.Open(src) if err != nil { return err } defer in.Close() out, err := os.Create(dst) if err != nil { return err } defer out.Close() _, err = io.Copy(out, in) if err != nil { return err } err = out.Close() if err != nil { return err } } stat := syscall.Stat_t{} if err = syscall.Lstat(src, &stat); err != nil { return err } if fi.Mode()&os.ModeSymlink != os.ModeSymlink { if err = syscall.Chmod(dst, stat.Mode); err != nil { return err } } if err = syscall.Lchown(dst, int(stat.Uid), int(stat.Gid)); err != nil { return err } links, err := Lgetxattr(src, LINKSXATTR) if err == nil && len(links) > 0 { err := Lsetxattr(dst, LINKSXATTR, links, 0) if err != nil { return err } } if fi.Mode()&os.ModeSymlink != os.ModeSymlink { if err = syscall.UtimesNano(dst, []syscall.Timespec{stat.Atim, stat.Mtim}); err != nil { return err } } inode.layer = 0 constor.log("done", inode.id) return nil }
// ApplyLayer parses a diff in the standard layer format from `layer`, and // applies it to the directory `dest`. func ApplyLayer(dest string, layer Archive) error { // We need to be able to set any perms oldmask := syscall.Umask(0) defer syscall.Umask(oldmask) layer, err := DecompressStream(layer) if err != nil { return err } tr := tar.NewReader(layer) var dirs []*tar.Header // Iterate through the files in the archive. for { hdr, err := tr.Next() if err == io.EOF { // end of tar archive break } if err != nil { return err } // Normalize name, for safety and for a simple is-root check hdr.Name = filepath.Clean(hdr.Name) if !strings.HasSuffix(hdr.Name, "/") { // Not the root directory, ensure that the parent directory exists // This happened in some tests where an image had a tarfile without any // parent directories parent := filepath.Dir(hdr.Name) parentPath := filepath.Join(dest, parent) if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { err = os.MkdirAll(parentPath, 600) if err != nil { return err } } } // Skip AUFS metadata dirs if strings.HasPrefix(hdr.Name, ".wh..wh.") { continue } path := filepath.Join(dest, hdr.Name) base := filepath.Base(path) if strings.HasPrefix(base, ".wh.") { originalBase := base[len(".wh."):] originalPath := filepath.Join(filepath.Dir(path), originalBase) if err := os.RemoveAll(originalPath); err != nil { return err } } else { // If path exits we almost always just want to remove and replace it // The only exception is when it is a directory *and* the file from // the layer is also a directory. Then we want to merge them (i.e. // just apply the metadata from the layer). hasDir := false if fi, err := os.Lstat(path); err == nil { if fi.IsDir() && hdr.Typeflag == tar.TypeDir { hasDir = true } else { if err := os.RemoveAll(path); err != nil { return err } } } switch hdr.Typeflag { case tar.TypeDir: if !hasDir { err = os.Mkdir(path, os.FileMode(hdr.Mode)) if err != nil { return err } } dirs = append(dirs, hdr) case tar.TypeReg, tar.TypeRegA: // Source is regular file file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, os.FileMode(hdr.Mode)) if err != nil { return err } if _, err := io.Copy(file, tr); err != nil { file.Close() return err } file.Close() case tar.TypeBlock, tar.TypeChar, tar.TypeFifo: mode := uint32(hdr.Mode & 07777) switch hdr.Typeflag { case tar.TypeBlock: mode |= syscall.S_IFBLK case tar.TypeChar: mode |= syscall.S_IFCHR case tar.TypeFifo: mode |= syscall.S_IFIFO } if err := syscall.Mknod(path, mode, int(mkdev(hdr.Devmajor, hdr.Devminor))); err != nil { return err } case tar.TypeLink: if err := os.Link(filepath.Join(dest, hdr.Linkname), path); err != nil { return err } case tar.TypeSymlink: if err := os.Symlink(hdr.Linkname, path); err != nil { return err } default: utils.Debugf("unhandled type %d\n", hdr.Typeflag) } if err = syscall.Lchown(path, hdr.Uid, hdr.Gid); err != nil { return err } // There is no LChmod, so ignore mode for symlink. Also, this // must happen after chown, as that can modify the file mode if hdr.Typeflag != tar.TypeSymlink { err = syscall.Chmod(path, uint32(hdr.Mode&07777)) if err != nil { return err } } // Directories must be handled at the end to avoid further // file creation in them to modify the mtime if hdr.Typeflag != tar.TypeDir { ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} // syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and if hdr.Typeflag != tar.TypeSymlink { if err := syscall.UtimesNano(path, ts); err != nil { return err } } else { if err := LUtimesNano(path, ts); err != nil { return err } } } } } for _, hdr := range dirs { path := filepath.Join(dest, hdr.Name) ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} if err := syscall.UtimesNano(path, ts); err != nil { return err } } return nil }
func UtimesNano(path string, ts []syscall.Timespec) error { return syscall.UtimesNano(path, ts) }
// ApplyLayer parses a diff in the standard layer format from `layer`, and // applies it to the directory `dest`. func ApplyLayer(dest string, layer Archive) error { // We need to be able to set any perms oldmask := syscall.Umask(0) defer syscall.Umask(oldmask) layer, err := DecompressStream(layer) if err != nil { return err } tr := tar.NewReader(layer) var dirs []*tar.Header // Iterate through the files in the archive. for { hdr, err := tr.Next() if err == io.EOF { // end of tar archive break } if err != nil { return err } // Normalize name, for safety and for a simple is-root check hdr.Name = filepath.Clean(hdr.Name) if !strings.HasSuffix(hdr.Name, "/") { // Not the root directory, ensure that the parent directory exists. // This happened in some tests where an image had a tarfile without any // parent directories. parent := filepath.Dir(hdr.Name) parentPath := filepath.Join(dest, parent) if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { err = os.MkdirAll(parentPath, 600) if err != nil { return err } } } // Skip AUFS metadata dirs if strings.HasPrefix(hdr.Name, ".wh..wh.") { continue } path := filepath.Join(dest, hdr.Name) base := filepath.Base(path) if strings.HasPrefix(base, ".wh.") { originalBase := base[len(".wh."):] originalPath := filepath.Join(filepath.Dir(path), originalBase) if err := os.RemoveAll(originalPath); err != nil { return err } } else { // If path exits we almost always just want to remove and replace it. // The only exception is when it is a directory *and* the file from // the layer is also a directory. Then we want to merge them (i.e. // just apply the metadata from the layer). if fi, err := os.Lstat(path); err == nil { if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { if err := os.RemoveAll(path); err != nil { return err } } } if err := createTarFile(path, dest, hdr, tr); err != nil { return err } // Directory mtimes must be handled at the end to avoid further // file creation in them to modify the directory mtime if hdr.Typeflag == tar.TypeDir { dirs = append(dirs, hdr) } } } for _, hdr := range dirs { path := filepath.Join(dest, hdr.Name) ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} if err := syscall.UtimesNano(path, ts); err != nil { return err } } return nil }
// ExtractFile extracts the file described by hdr from the given tarball into // the provided directory. // If overwrite is true, existing files will be overwritten. func ExtractFile(tr *tar.Reader, hdr *tar.Header, dir string, overwrite bool) error { p := filepath.Join(dir, hdr.Name) fi := hdr.FileInfo() typ := hdr.Typeflag if overwrite { info, err := os.Lstat(p) switch { case os.IsNotExist(err): case err == nil: // If the old and new paths are both dirs do nothing or // RemoveAll will remove all dir's contents if !info.IsDir() || typ != tar.TypeDir { err := os.RemoveAll(p) if err != nil { return err } } default: return err } } // Create parent dir if it doesn't exist if err := os.MkdirAll(filepath.Dir(p), DEFAULT_DIR_MODE); err != nil { return err } switch { case typ == tar.TypeReg || typ == tar.TypeRegA: f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR, fi.Mode()) if err != nil { return err } _, err = io.Copy(f, tr) if err != nil { f.Close() return err } f.Close() case typ == tar.TypeDir: if err := os.MkdirAll(p, fi.Mode()); err != nil { return err } dir, err := os.Open(p) if err != nil { return err } if err := dir.Chmod(fi.Mode()); err != nil { dir.Close() return err } dir.Close() case typ == tar.TypeLink: dest := filepath.Join(dir, hdr.Linkname) if !strings.HasPrefix(dest, dir) { return insecureLinkError(fmt.Errorf("insecure link %q -> %q", p, hdr.Linkname)) } if err := os.Link(dest, p); err != nil { return err } case typ == tar.TypeSymlink: dest := filepath.Join(filepath.Dir(p), hdr.Linkname) if !strings.HasPrefix(dest, dir) { return insecureLinkError(fmt.Errorf("insecure symlink %q -> %q", p, hdr.Linkname)) } if err := os.Symlink(hdr.Linkname, p); err != nil { return err } case typ == tar.TypeChar: dev := makedev(int(hdr.Devmajor), int(hdr.Devminor)) mode := uint32(fi.Mode()) | syscall.S_IFCHR if err := syscall.Mknod(p, mode, dev); err != nil { return err } case typ == tar.TypeBlock: dev := makedev(int(hdr.Devmajor), int(hdr.Devminor)) mode := uint32(fi.Mode()) | syscall.S_IFBLK if err := syscall.Mknod(p, mode, dev); err != nil { return err } case typ == tar.TypeFifo: if err := syscall.Mkfifo(p, uint32(fi.Mode())); err != nil { return err } // TODO(jonboulle): implement other modes default: return fmt.Errorf("unsupported type: %v", typ) } if err := os.Lchown(p, hdr.Uid, hdr.Gid); err != nil { return err } // lchown(2) says that, depending on the linux kernel version, it // can change the file's mode also if executed as root. So call // os.Chmod after it. if typ != tar.TypeSymlink { if err := os.Chmod(p, fi.Mode()); err != nil { return err } } // Restore entry atime and mtime. // Use special function LUtimesNano not available on go's syscall package because we // have to restore symlink's times and not the referenced file times. ts := HdrToTimespec(hdr) if hdr.Typeflag != tar.TypeSymlink { if err := syscall.UtimesNano(p, ts); err != nil { return err } } else { if err := LUtimesNano(p, ts); err != nil && err != ErrNotSupportedPlatform { return err } } return nil }
func (constor *Constor) copyup(inode *Inode) error { src, err := constor.getPath(inode.ino) if err != nil { return err } dst, err := constor.dentrymap.getPath(inode.ino) if err != nil { return err } err = constor.createPath(Path.Dir(dst)) if err != nil { return err } dst = Path.Join(constor.layers[0], dst) fi, err := os.Lstat(src) if err != nil { return err } if fi.Mode()&os.ModeSymlink == os.ModeSymlink { linkName, err := os.Readlink(src) if err != nil { return err } err = os.Symlink(linkName, dst) if err != nil { return err } } else if fi.Mode()&os.ModeDir == os.ModeDir { err := os.Mkdir(dst, fi.Mode()) if err != nil { return err } } else { in, err := os.Open(src) if err != nil { return err } defer in.Close() out, err := os.Create(dst) if err != nil { return err } defer out.Close() _, err = io.Copy(out, in) if err != nil { return err } err = out.Close() if err != nil { return err } } stat := syscall.Stat_t{} if err = syscall.Lstat(src, &stat); err != nil { return err } if fi.Mode()&os.ModeSymlink != os.ModeSymlink { if err = syscall.Chmod(dst, stat.Mode); err != nil { return err } } if err = syscall.Lchown(dst, int(stat.Uid), int(stat.Gid)); err != nil { return err } if fi.Mode()&os.ModeSymlink != os.ModeSymlink { if err = syscall.UtimesNano(dst, []syscall.Timespec{stat.Atim, stat.Mtim}); err != nil { return err } } inoitoa := strconv.Itoa(int(stat.Ino)) inobyte := []byte(inoitoa) // if err = syscall.Setxattr(dst, INOXATTR, inobyte, 0); err != nil { // return err // } if err = Lsetxattr(dst, INOXATTR, inobyte, 0); err != nil { return err } inode.layer = 0 path, err := constor.dentrymap.getPath(inode.ino) constor.log("ino %d file %s", inode.ino, path) return nil }
// ApplyLayer parses a diff in the standard layer format from `layer`, and // applies it to the directory `dest`. func ApplyLayer(dest string, layer ArchiveReader) error { // We need to be able to set any perms oldmask := syscall.Umask(0) defer syscall.Umask(oldmask) layer, err := DecompressStream(layer) if err != nil { return err } tr := tar.NewReader(layer) trBuf := bufio.NewReaderSize(nil, trBufSize) var dirs []*tar.Header aufsTempdir := "" aufsHardlinks := make(map[string]*tar.Header) // Iterate through the files in the archive. for { hdr, err := tr.Next() if err == io.EOF { // end of tar archive break } if err != nil { return err } // Normalize name, for safety and for a simple is-root check hdr.Name = filepath.Clean(hdr.Name) if !strings.HasSuffix(hdr.Name, "/") { // Not the root directory, ensure that the parent directory exists. // This happened in some tests where an image had a tarfile without any // parent directories. parent := filepath.Dir(hdr.Name) parentPath := filepath.Join(dest, parent) if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { err = os.MkdirAll(parentPath, 0600) if err != nil { return err } } } // Skip AUFS metadata dirs if strings.HasPrefix(hdr.Name, ".wh..wh.") { // Regular files inside /.wh..wh.plnk can be used as hardlink targets // We don't want this directory, but we need the files in them so that // such hardlinks can be resolved. if strings.HasPrefix(hdr.Name, ".wh..wh.plnk") && hdr.Typeflag == tar.TypeReg { basename := filepath.Base(hdr.Name) aufsHardlinks[basename] = hdr if aufsTempdir == "" { if aufsTempdir, err = ioutil.TempDir("", "dockerplnk"); err != nil { return err } defer os.RemoveAll(aufsTempdir) } if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true); err != nil { return err } } continue } path := filepath.Join(dest, hdr.Name) base := filepath.Base(path) if strings.HasPrefix(base, ".wh.") { originalBase := base[len(".wh."):] originalPath := filepath.Join(filepath.Dir(path), originalBase) if err := os.RemoveAll(originalPath); err != nil { return err } } else { // If path exits we almost always just want to remove and replace it. // The only exception is when it is a directory *and* the file from // the layer is also a directory. Then we want to merge them (i.e. // just apply the metadata from the layer). if fi, err := os.Lstat(path); err == nil { if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { if err := os.RemoveAll(path); err != nil { return err } } } trBuf.Reset(tr) srcData := io.Reader(trBuf) srcHdr := hdr // Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so // we manually retarget these into the temporary files we extracted them into if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), ".wh..wh.plnk") { linkBasename := filepath.Base(hdr.Linkname) srcHdr = aufsHardlinks[linkBasename] if srcHdr == nil { return fmt.Errorf("Invalid aufs hardlink") } tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename)) if err != nil { return err } defer tmpFile.Close() srcData = tmpFile } if err := createTarFile(path, dest, srcHdr, srcData, true); err != nil { return err } // Directory mtimes must be handled at the end to avoid further // file creation in them to modify the directory mtime if hdr.Typeflag == tar.TypeDir { dirs = append(dirs, hdr) } } } for _, hdr := range dirs { path := filepath.Join(dest, hdr.Name) ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} if err := syscall.UtimesNano(path, ts); err != nil { return err } } return nil }
// Untar reads a stream of bytes from `archive`, parses it as a tar archive, // and unpacks it into the directory at `path`. // The archive may be compressed with one of the following algorithms: // identity (uncompressed), gzip, bzip2, xz. // FIXME: specify behavior when target path exists vs. doesn't exist. func Untar(archive io.Reader, dest string, options *TarOptions) error { if archive == nil { return fmt.Errorf("Empty archive") } decompressedArchive, err := DecompressStream(archive) if err != nil { return err } defer decompressedArchive.Close() tr := tar.NewReader(decompressedArchive) var dirs []*tar.Header // Iterate through the files in the archive. for { hdr, err := tr.Next() if err == io.EOF { // end of tar archive break } if err != nil { return err } // Normalize name, for safety and for a simple is-root check hdr.Name = filepath.Clean(hdr.Name) if !strings.HasSuffix(hdr.Name, "/") { // Not the root directory, ensure that the parent directory exists parent := filepath.Dir(hdr.Name) parentPath := filepath.Join(dest, parent) if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { err = os.MkdirAll(parentPath, 0777) if err != nil { return err } } } path := filepath.Join(dest, hdr.Name) // If path exits we almost always just want to remove and replace it // The only exception is when it is a directory *and* the file from // the layer is also a directory. Then we want to merge them (i.e. // just apply the metadata from the layer). if fi, err := os.Lstat(path); err == nil { if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { if err := os.RemoveAll(path); err != nil { return err } } } if err := createTarFile(path, dest, hdr, tr); err != nil { return err } // Directory mtimes must be handled at the end to avoid further // file creation in them to modify the directory mtime if hdr.Typeflag == tar.TypeDir { dirs = append(dirs, hdr) } } for _, hdr := range dirs { path := filepath.Join(dest, hdr.Name) ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} if err := syscall.UtimesNano(path, ts); err != nil { return err } } return nil }
// extractFile extracts the file described by hdr from the given tarball into // the target directory. // If overwrite is true, existing files will be overwritten. func extractFile(tr *tar.Reader, target string, hdr *tar.Header, overwrite bool, editor FilePermissionsEditor) error { p := filepath.Join(target, hdr.Name) fi := hdr.FileInfo() typ := hdr.Typeflag if overwrite { info, err := os.Lstat(p) switch { case os.IsNotExist(err): case err == nil: // If the old and new paths are both dirs do nothing or // RemoveAll will remove all dir's contents if !info.IsDir() || typ != tar.TypeDir { err := os.RemoveAll(p) if err != nil { return err } } default: return err } } // Create parent dir if it doesn't exist if err := os.MkdirAll(filepath.Dir(p), DEFAULT_DIR_MODE); err != nil { return err } switch { case typ == tar.TypeReg || typ == tar.TypeRegA: f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR, fi.Mode()) if err != nil { return err } _, err = io.Copy(f, tr) if err != nil { f.Close() return err } f.Close() case typ == tar.TypeDir: if err := os.MkdirAll(p, fi.Mode()); err != nil { return err } dir, err := os.Open(p) if err != nil { return err } if err := dir.Chmod(fi.Mode()); err != nil { dir.Close() return err } dir.Close() case typ == tar.TypeLink: dest := filepath.Join(target, hdr.Linkname) if err := os.Link(dest, p); err != nil { return err } case typ == tar.TypeSymlink: if err := os.Symlink(hdr.Linkname, p); err != nil { return err } case typ == tar.TypeChar: dev := device.Makedev(uint(hdr.Devmajor), uint(hdr.Devminor)) mode := uint32(fi.Mode()) | syscall.S_IFCHR if err := syscall.Mknod(p, mode, int(dev)); err != nil { return err } case typ == tar.TypeBlock: dev := device.Makedev(uint(hdr.Devmajor), uint(hdr.Devminor)) mode := uint32(fi.Mode()) | syscall.S_IFBLK if err := syscall.Mknod(p, mode, int(dev)); err != nil { return err } case typ == tar.TypeFifo: if err := syscall.Mkfifo(p, uint32(fi.Mode())); err != nil { return err } // TODO(jonboulle): implement other modes default: return fmt.Errorf("unsupported type: %v", typ) } if editor != nil { if err := editor(p, hdr.Uid, hdr.Gid, hdr.Typeflag, fi); err != nil { return err } } // Restore entry atime and mtime. // Use special function LUtimesNano not available on go's syscall package because we // have to restore symlink's times and not the referenced file times. ts := HdrToTimespec(hdr) if hdr.Typeflag != tar.TypeSymlink { if err := syscall.UtimesNano(p, ts); err != nil { return err } } else { if err := fileutil.LUtimesNano(p, ts); err != nil && err != ErrNotSupportedPlatform { return err } } return nil }
func UtimesNano(path string, ts []syscall.Timespec) error { if err := syscall.UtimesNano(path, ts); err != nil { return err } return nil }
func Unpack(decompressedArchive io.Reader, dest string, options *TarOptions) error { tr := tar.NewReader(decompressedArchive) trBuf := pools.BufioReader32KPool.Get(nil) defer pools.BufioReader32KPool.Put(trBuf) var dirs []*tar.Header // Iterate through the files in the archive. loop: for { hdr, err := tr.Next() if err == io.EOF { // end of tar archive break } if err != nil { return err } // Normalize name, for safety and for a simple is-root check // This keeps "../" as-is, but normalizes "/../" to "/" hdr.Name = filepath.Clean(hdr.Name) for _, exclude := range options.Excludes { if strings.HasPrefix(hdr.Name, exclude) { continue loop } } if !strings.HasSuffix(hdr.Name, "/") { // Not the root directory, ensure that the parent directory exists parent := filepath.Dir(hdr.Name) parentPath := filepath.Join(dest, parent) if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { err = os.MkdirAll(parentPath, 0777) if err != nil { return err } } } path := filepath.Join(dest, hdr.Name) rel, err := filepath.Rel(dest, path) if err != nil { return err } if strings.HasPrefix(rel, "..") { return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) } // If path exits we almost always just want to remove and replace it // The only exception is when it is a directory *and* the file from // the layer is also a directory. Then we want to merge them (i.e. // just apply the metadata from the layer). if fi, err := os.Lstat(path); err == nil { if fi.IsDir() && hdr.Name == "." { continue } if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { if err := os.RemoveAll(path); err != nil { return err } } } trBuf.Reset(tr) if err := createTarFile(path, dest, hdr, trBuf, !options.NoLchown); err != nil { return err } // Directory mtimes must be handled at the end to avoid further // file creation in them to modify the directory mtime if hdr.Typeflag == tar.TypeDir { dirs = append(dirs, hdr) } } for _, hdr := range dirs { path := filepath.Join(dest, hdr.Name) ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} if err := syscall.UtimesNano(path, ts); err != nil { return err } } return nil }
func createTarFile(path, extractDir string, hdr *tar.Header, reader *tar.Reader) error { switch hdr.Typeflag { case tar.TypeDir: // Create directory unless it exists as a directory already. // In that case we just want to merge the two if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) { if err := os.Mkdir(path, os.FileMode(hdr.Mode)); err != nil { return err } } case tar.TypeReg, tar.TypeRegA: // Source is regular file file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, os.FileMode(hdr.Mode)) if err != nil { return err } if _, err := io.Copy(file, reader); err != nil { file.Close() return err } file.Close() case tar.TypeBlock, tar.TypeChar, tar.TypeFifo: mode := uint32(hdr.Mode & 07777) switch hdr.Typeflag { case tar.TypeBlock: mode |= syscall.S_IFBLK case tar.TypeChar: mode |= syscall.S_IFCHR case tar.TypeFifo: mode |= syscall.S_IFIFO } if err := syscall.Mknod(path, mode, int(mkdev(hdr.Devmajor, hdr.Devminor))); err != nil { return err } case tar.TypeLink: if err := os.Link(filepath.Join(extractDir, hdr.Linkname), path); err != nil { return err } case tar.TypeSymlink: if err := os.Symlink(hdr.Linkname, path); err != nil { return err } case tar.TypeXGlobalHeader: utils.Debugf("PAX Global Extended Headers found and ignored") return nil default: return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag) } if err := syscall.Lchown(path, hdr.Uid, hdr.Gid); err != nil { return err } // There is no LChmod, so ignore mode for symlink. Also, this // must happen after chown, as that can modify the file mode if hdr.Typeflag != tar.TypeSymlink { if err := syscall.Chmod(path, uint32(hdr.Mode&07777)); err != nil { return err } } ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} // syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and if hdr.Typeflag != tar.TypeSymlink { if err := syscall.UtimesNano(path, ts); err != nil { return err } } else { if err := LUtimesNano(path, ts); err != nil { return err } } return nil }