From 2b98401a83465214d0ca5f2d52ea9d890ec6fc81 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 24 May 2012 14:10:54 -0700 Subject: [PATCH] archive/tar: add FileInfoHeader function Fixes #3295 R=adg, rsc, mike.rosset CC=golang-dev https://golang.org/cl/5796073 --- api/next.txt | 1 + src/pkg/archive/tar/common.go | 63 ++++++++++++++++++++++++++- src/pkg/archive/tar/stat_atim.go | 20 +++++++++ src/pkg/archive/tar/stat_atimespec.go | 20 +++++++++ src/pkg/archive/tar/stat_unix.go | 32 ++++++++++++++ src/pkg/archive/tar/tar_test.go | 56 ++++++++++++++++++++++++ src/pkg/go/build/deps_test.go | 2 +- 7 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 src/pkg/archive/tar/stat_atim.go create mode 100644 src/pkg/archive/tar/stat_atimespec.go create mode 100644 src/pkg/archive/tar/stat_unix.go create mode 100644 src/pkg/archive/tar/tar_test.go diff --git a/api/next.txt b/api/next.txt index 1279a7ace90..5147f995853 100644 --- a/api/next.txt +++ b/api/next.txt @@ -1,3 +1,4 @@ +pkg archive/tar, func FileInfoHeader(os.FileInfo, string) (*Header, error) pkg crypto/tls, const TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16 pkg crypto/tls, const TLS_RSA_WITH_AES_256_CBC_SHA uint16 pkg crypto/x509, const ECDSA PublicKeyAlgorithm diff --git a/src/pkg/archive/tar/common.go b/src/pkg/archive/tar/common.go index fc7a40923cd..921b9fe9bdd 100644 --- a/src/pkg/archive/tar/common.go +++ b/src/pkg/archive/tar/common.go @@ -11,7 +11,12 @@ // http://www.gnu.org/software/tar/manual/html_node/Standard.html package tar -import "time" +import ( + "errors" + "fmt" + "os" + "time" +) const ( blockSize = 512 @@ -49,6 +54,62 @@ type Header struct { ChangeTime time.Time // status change time } +// sysStat, if non-nil, populates h from system-dependent fields of fi. +var sysStat func(fi os.FileInfo, h *Header) error + +// Mode constants from the tar spec. +const ( + c_ISDIR = 040000 + c_ISFIFO = 010000 + c_ISREG = 0100000 + c_ISLNK = 0120000 + c_ISBLK = 060000 + c_ISCHR = 020000 + c_ISSOCK = 0140000 +) + +// FileInfoHeader creates a partially-populated Header from fi. +// If fi describes a symlink, FileInfoHeader records link as the link target. +func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) { + if fi == nil { + return nil, errors.New("tar: FileInfo is nil") + } + h := &Header{ + Name: fi.Name(), + ModTime: fi.ModTime(), + Mode: int64(fi.Mode().Perm()), // or'd with c_IS* constants later + } + switch { + case fi.Mode()&os.ModeType == 0: + h.Mode |= c_ISREG + h.Typeflag = TypeReg + h.Size = fi.Size() + case fi.IsDir(): + h.Typeflag = TypeDir + h.Mode |= c_ISDIR + case fi.Mode()&os.ModeSymlink != 0: + h.Typeflag = TypeSymlink + h.Mode |= c_ISLNK + h.Linkname = link + case fi.Mode()&os.ModeDevice != 0: + if fi.Mode()&os.ModeCharDevice != 0 { + h.Mode |= c_ISCHR + h.Typeflag = TypeChar + } else { + h.Mode |= c_ISBLK + h.Typeflag = TypeBlock + } + case fi.Mode()&os.ModeSocket != 0: + h.Mode |= c_ISSOCK + default: + return nil, fmt.Errorf("archive/tar: unknown file mode %v", fi.Mode()) + } + if sysStat != nil { + return h, sysStat(fi, h) + } + return h, nil +} + var zeroBlock = make([]byte, blockSize) // POSIX specifies a sum of the unsigned byte values, but the Sun tar uses signed byte values. diff --git a/src/pkg/archive/tar/stat_atim.go b/src/pkg/archive/tar/stat_atim.go new file mode 100644 index 00000000000..6029b087123 --- /dev/null +++ b/src/pkg/archive/tar/stat_atim.go @@ -0,0 +1,20 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux openbsd + +package tar + +import ( + "syscall" + "time" +) + +func statAtime(st *syscall.Stat_t) time.Time { + return time.Unix(st.Atim.Unix()) +} + +func statCtime(st *syscall.Stat_t) time.Time { + return time.Unix(st.Ctim.Unix()) +} diff --git a/src/pkg/archive/tar/stat_atimespec.go b/src/pkg/archive/tar/stat_atimespec.go new file mode 100644 index 00000000000..6f17dbe3072 --- /dev/null +++ b/src/pkg/archive/tar/stat_atimespec.go @@ -0,0 +1,20 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin freebsd netbsd + +package tar + +import ( + "syscall" + "time" +) + +func statAtime(st *syscall.Stat_t) time.Time { + return time.Unix(st.Atimespec.Unix()) +} + +func statCtime(st *syscall.Stat_t) time.Time { + return time.Unix(st.Ctimespec.Unix()) +} diff --git a/src/pkg/archive/tar/stat_unix.go b/src/pkg/archive/tar/stat_unix.go new file mode 100644 index 00000000000..92bc9242429 --- /dev/null +++ b/src/pkg/archive/tar/stat_unix.go @@ -0,0 +1,32 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux darwin freebsd openbsd netbsd + +package tar + +import ( + "os" + "syscall" +) + +func init() { + sysStat = statUnix +} + +func statUnix(fi os.FileInfo, h *Header) error { + sys, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + return nil + } + h.Uid = int(sys.Uid) + h.Gid = int(sys.Gid) + // TODO(bradfitz): populate username & group. os/user + // doesn't cache LookupId lookups, and lacks group + // lookup functions. + h.AccessTime = statAtime(sys) + h.ChangeTime = statCtime(sys) + // TODO(bradfitz): major/minor device numbers? + return nil +} diff --git a/src/pkg/archive/tar/tar_test.go b/src/pkg/archive/tar/tar_test.go new file mode 100644 index 00000000000..d3d2a628525 --- /dev/null +++ b/src/pkg/archive/tar/tar_test.go @@ -0,0 +1,56 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tar + +import ( + "os" + "testing" + "time" +) + +func TestFileInfoHeader(t *testing.T) { + fi, err := os.Lstat("testdata/small.txt") + if err != nil { + t.Fatal(err) + } + h, err := FileInfoHeader(fi, "") + if err != nil { + t.Fatalf("on small.txt: %v", err) + } + if g, e := h.Name, "small.txt"; g != e { + t.Errorf("Name = %q; want %q", g, e) + } + if g, e := h.Mode, int64(0644|c_ISREG); g != e { + t.Errorf("Mode = %#o; want %#o", g, e) + } + if g, e := h.Size, int64(5); g != e { + t.Errorf("Size = %v; want %v", g, e) + } + if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) { + t.Errorf("ModTime = %v; want %v", g, e) + } +} + +func TestFileInfoHeaderSymlink(t *testing.T) { + h, err := FileInfoHeader(symlink{}, "some-target") + if err != nil { + t.Fatal(err) + } + if g, e := h.Name, "some-symlink"; g != e { + t.Errorf("Name = %q; want %q", g, e) + } + if g, e := h.Linkname, "some-target"; g != e { + t.Errorf("Linkname = %q; want %q", g, e) + } +} + +type symlink struct{} + +func (symlink) Name() string { return "some-symlink" } +func (symlink) Size() int64 { return 0 } +func (symlink) Mode() os.FileMode { return os.ModeSymlink } +func (symlink) ModTime() time.Time { return time.Time{} } +func (symlink) IsDir() bool { return false } +func (symlink) Sys() interface{} { return nil } diff --git a/src/pkg/go/build/deps_test.go b/src/pkg/go/build/deps_test.go index 4e9f32a036f..0505a4304e8 100644 --- a/src/pkg/go/build/deps_test.go +++ b/src/pkg/go/build/deps_test.go @@ -177,7 +177,7 @@ var pkgDeps = map[string][]string{ }, // One of a kind. - "archive/tar": {"L4", "OS"}, + "archive/tar": {"L4", "OS", "syscall"}, "archive/zip": {"L4", "OS", "compress/flate"}, "compress/bzip2": {"L4"}, "compress/flate": {"L4"},