From 0564e304a6ea394a42929060c588469dbd6f32af Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Mon, 28 Aug 2017 16:25:56 -0700 Subject: [PATCH] archive/tar: populate uname/gname/devmajor/devminor in FileInfoHeader We take a best-effort approach since information for these fields are not well supported on all platforms. user.LookupId+user.LookupGroupId is currently 15x slower than os.Stat. For performance reasons, we perpetually cache username and groupname with a sync.Map. As a result, this function will not be updated whenever the user or group names are renamed in the OS. However, this is a better situation than before, where those fields were not populated at all. Change-Id: I3cec8291aed7675dea89ee1cbda92bd493c8831f Reviewed-on: https://go-review.googlesource.com/59531 Run-TryBot: Joe Tsai TryBot-Result: Gobot Gobot Reviewed-by: Ian Lance Taylor --- src/archive/tar/stat_unix.go | 45 ++++++++++++++++++++++++++++++++---- src/go/build/deps_test.go | 2 +- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/archive/tar/stat_unix.go b/src/archive/tar/stat_unix.go index cb843db4cf..aa6d82b1c6 100644 --- a/src/archive/tar/stat_unix.go +++ b/src/archive/tar/stat_unix.go @@ -8,6 +8,10 @@ package tar import ( "os" + "os/user" + "runtime" + "strconv" + "sync" "syscall" ) @@ -15,6 +19,10 @@ func init() { sysStat = statUnix } +// userMap and groupMap caches UID and GID lookups for performance reasons. +// The downside is that renaming uname or gname by the OS never takes effect. +var userMap, groupMap sync.Map // map[int]string + func statUnix(fi os.FileInfo, h *Header) error { sys, ok := fi.Sys().(*syscall.Stat_t) if !ok { @@ -22,11 +30,40 @@ func statUnix(fi os.FileInfo, h *Header) error { } 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. + + // Best effort at populating Uname and Gname. + // The os/user functions may fail for any number of reasons + // (not implemented on that platform, cgo not enabled, etc). + if u, ok := userMap.Load(h.Uid); ok { + h.Uname = u.(string) + } else if u, err := user.LookupId(strconv.Itoa(h.Uid)); err == nil { + h.Uname = u.Username + userMap.Store(h.Uid, h.Uname) + } + if g, ok := groupMap.Load(h.Gid); ok { + h.Gname = g.(string) + } else if g, err := user.LookupGroupId(strconv.Itoa(h.Gid)); err == nil { + h.Gname = g.Name + groupMap.Store(h.Gid, h.Gname) + } + h.AccessTime = statAtime(sys) h.ChangeTime = statCtime(sys) - // TODO(bradfitz): major/minor device numbers? + + // Best effort at populating Devmajor and Devminor. + if h.Typeflag == TypeChar || h.Typeflag == TypeBlock { + dev := uint64(sys.Rdev) // May be int32 or uint32 + switch runtime.GOOS { + case "linux": + // Copied from golang.org/x/sys/unix/dev_linux.go. + major := uint32((dev & 0x00000000000fff00) >> 8) + major |= uint32((dev & 0xfffff00000000000) >> 32) + minor := uint32((dev & 0x00000000000000ff) >> 0) + minor |= uint32((dev & 0x00000ffffff00000) >> 12) + h.Devmajor, h.Devminor = int64(major), int64(minor) + default: + // TODO: Implement others (see https://golang.org/issue/8106) + } + } return nil } diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 87abfba921..041c52310a 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -226,7 +226,7 @@ var pkgDeps = map[string][]string{ "go/types": {"L4", "GOPARSER", "container/heap", "go/constant"}, // One of a kind. - "archive/tar": {"L4", "OS", "syscall"}, + "archive/tar": {"L4", "OS", "syscall", "os/user"}, "archive/zip": {"L4", "OS", "compress/flate"}, "container/heap": {"sort"}, "compress/bzip2": {"L4"},