Skip to content

Commit 0564e30

Browse files
committed
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 93da0b6 commit 0564e30

File tree

2 files changed

+42
-5
lines changed

2 files changed

+42
-5
lines changed

src/archive/tar/stat_unix.go

+41-4
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,62 @@ package tar
88

99
import (
1010
"os"
11+
"os/user"
12+
"runtime"
13+
"strconv"
14+
"sync"
1115
"syscall"
1216
)
1317

1418
func init() {
1519
sysStat = statUnix
1620
}
1721

22+
// userMap and groupMap caches UID and GID lookups for performance reasons.
23+
// The downside is that renaming uname or gname by the OS never takes effect.
24+
var userMap, groupMap sync.Map // map[int]string
25+
1826
func statUnix(fi os.FileInfo, h *Header) error {
1927
sys, ok := fi.Sys().(*syscall.Stat_t)
2028
if !ok {
2129
return nil
2230
}
2331
h.Uid = int(sys.Uid)
2432
h.Gid = int(sys.Gid)
25-
// TODO(bradfitz): populate username & group. os/user
26-
// doesn't cache LookupId lookups, and lacks group
27-
// lookup functions.
33+
34+
// Best effort at populating Uname and Gname.
35+
// The os/user functions may fail for any number of reasons
36+
// (not implemented on that platform, cgo not enabled, etc).
37+
if u, ok := userMap.Load(h.Uid); ok {
38+
h.Uname = u.(string)
39+
} else if u, err := user.LookupId(strconv.Itoa(h.Uid)); err == nil {
40+
h.Uname = u.Username
41+
userMap.Store(h.Uid, h.Uname)
42+
}
43+
if g, ok := groupMap.Load(h.Gid); ok {
44+
h.Gname = g.(string)
45+
} else if g, err := user.LookupGroupId(strconv.Itoa(h.Gid)); err == nil {
46+
h.Gname = g.Name
47+
groupMap.Store(h.Gid, h.Gname)
48+
}
49+
2850
h.AccessTime = statAtime(sys)
2951
h.ChangeTime = statCtime(sys)
30-
// TODO(bradfitz): major/minor device numbers?
52+
53+
// Best effort at populating Devmajor and Devminor.
54+
if h.Typeflag == TypeChar || h.Typeflag == TypeBlock {
55+
dev := uint64(sys.Rdev) // May be int32 or uint32
56+
switch runtime.GOOS {
57+
case "linux":
58+
// Copied from golang.org/x/sys/unix/dev_linux.go.
59+
major := uint32((dev & 0x00000000000fff00) >> 8)
60+
major |= uint32((dev & 0xfffff00000000000) >> 32)
61+
minor := uint32((dev & 0x00000000000000ff) >> 0)
62+
minor |= uint32((dev & 0x00000ffffff00000) >> 12)
63+
h.Devmajor, h.Devminor = int64(major), int64(minor)
64+
default:
65+
// TODO: Implement others (see https://golang.org/issue/8106)
66+
}
67+
}
3168
return nil
3269
}

src/go/build/deps_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ var pkgDeps = map[string][]string{
226226
"go/types": {"L4", "GOPARSER", "container/heap", "go/constant"},
227227

228228
// One of a kind.
229-
"archive/tar": {"L4", "OS", "syscall"},
229+
"archive/tar": {"L4", "OS", "syscall", "os/user"},
230230
"archive/zip": {"L4", "OS", "compress/flate"},
231231
"container/heap": {"sort"},
232232
"compress/bzip2": {"L4"},

0 commit comments

Comments
 (0)