Skip to content

Commit cfc1738

Browse files
committed
move FileInfoHeaderNoLookups to separate tarheader package
This aligns with how containerd organizes the code, and allows consuming this function without requiring all the other code. Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent a17ea4b commit cfc1738

File tree

5 files changed

+152
-73
lines changed

5 files changed

+152
-73
lines changed

Diff for: archive.go

+5-64
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626

2727
"github.com/containerd/log"
2828
"github.com/klauspost/compress/zstd"
29+
"github.com/moby/go-archive/tarheader"
2930
"github.com/moby/patternmatcher"
3031
"github.com/moby/sys/sequential"
3132
"github.com/moby/sys/user"
@@ -475,71 +476,11 @@ func (compression *Compression) Extension() string {
475476
return ""
476477
}
477478

478-
// assert that we implement [tar.FileInfoNames].
479-
var _ tar.FileInfoNames = (*nosysFileInfo)(nil)
480-
481-
// nosysFileInfo hides the system-dependent info of the wrapped FileInfo to
482-
// prevent tar.FileInfoHeader from introspecting it and potentially calling into
483-
// glibc.
484-
//
485-
// It implements [tar.FileInfoNames] to further prevent [tar.FileInfoHeader]
486-
// from performing any lookups on go1.23 and up. see https://go.dev/issue/50102
487-
type nosysFileInfo struct {
488-
os.FileInfo
489-
}
490-
491-
// Uname stubs out looking up username. It implements [tar.FileInfoNames]
492-
// to prevent [tar.FileInfoHeader] from loading libraries to perform
493-
// username lookups.
494-
func (fi nosysFileInfo) Uname() (string, error) {
495-
return "", nil
496-
}
497-
498-
// Gname stubs out looking up group-name. It implements [tar.FileInfoNames]
499-
// to prevent [tar.FileInfoHeader] from loading libraries to perform
500-
// username lookups.
501-
func (fi nosysFileInfo) Gname() (string, error) {
502-
return "", nil
503-
}
504-
505-
func (fi nosysFileInfo) Sys() interface{} {
506-
// A Sys value of type *tar.Header is safe as it is system-independent.
507-
// The tar.FileInfoHeader function copies the fields into the returned
508-
// header without performing any OS lookups.
509-
if sys, ok := fi.FileInfo.Sys().(*tar.Header); ok {
510-
return sys
511-
}
512-
return nil
513-
}
514-
515-
// sysStat, if non-nil, populates hdr from system-dependent fields of fi.
516-
var sysStat func(fi os.FileInfo, hdr *tar.Header) error
517-
518479
// FileInfoHeaderNoLookups creates a partially-populated tar.Header from fi.
519480
//
520-
// Compared to the archive/tar.FileInfoHeader function, this function is safe to
521-
// call from a chrooted process as it does not populate fields which would
522-
// require operating system lookups. It behaves identically to
523-
// tar.FileInfoHeader when fi is a FileInfo value returned from
524-
// tar.Header.FileInfo().
525-
//
526-
// When fi is a FileInfo for a native file, such as returned from os.Stat() and
527-
// os.Lstat(), the returned Header value differs from one returned from
528-
// tar.FileInfoHeader in the following ways. The Uname and Gname fields are not
529-
// set as OS lookups would be required to populate them. The AccessTime and
530-
// ChangeTime fields are not currently set (not yet implemented) although that
531-
// is subject to change. Callers which require the AccessTime or ChangeTime
532-
// fields to be zeroed should explicitly zero them out in the returned Header
533-
// value to avoid any compatibility issues in the future.
481+
// Deprecated: use [tarheader.FileInfoHeaderNoLookups].
534482
func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) {
535-
hdr, err := tar.FileInfoHeader(nosysFileInfo{fi}, link)
536-
if err != nil {
537-
return nil, err
538-
}
539-
if sysStat != nil {
540-
return hdr, sysStat(fi, hdr)
541-
}
542-
return hdr, nil
483+
return tarheader.FileInfoHeaderNoLookups(fi, link)
543484
}
544485

545486
// FileInfoHeader creates a populated Header from fi.
@@ -550,7 +491,7 @@ func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) {
550491
// precision, and the Uname and Gname fields are only set when fi is a FileInfo
551492
// value returned from tar.Header.FileInfo().
552493
func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, error) {
553-
hdr, err := FileInfoHeaderNoLookups(fi, link)
494+
hdr, err := tarheader.FileInfoHeaderNoLookups(fi, link)
554495
if err != nil {
555496
return nil, err
556497
}
@@ -1420,7 +1361,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
14201361
}
14211362
defer srcF.Close()
14221363

1423-
hdr, err := FileInfoHeaderNoLookups(srcSt, "")
1364+
hdr, err := tarheader.FileInfoHeaderNoLookups(srcSt, "")
14241365
if err != nil {
14251366
return err
14261367
}

Diff for: archive_test.go

-9
Original file line numberDiff line numberDiff line change
@@ -1384,12 +1384,3 @@ func TestPigz(t *testing.T) {
13841384
assert.Equal(t, reflect.TypeOf(wrapper.Reader), reflect.TypeOf(&gzip.Reader{}))
13851385
}
13861386
}
1387-
1388-
func TestNosysFileInfo(t *testing.T) {
1389-
st, err := os.Stat("archive_test.go")
1390-
assert.NilError(t, err)
1391-
h, err := tar.FileInfoHeader(nosysFileInfo{st}, "")
1392-
assert.NilError(t, err)
1393-
assert.Check(t, h.Uname == "")
1394-
assert.Check(t, h.Gname == "")
1395-
}

Diff for: tarheader/tarheader.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package tarheader
2+
3+
import (
4+
"archive/tar"
5+
"os"
6+
)
7+
8+
// assert that we implement [tar.FileInfoNames].
9+
var _ tar.FileInfoNames = (*nosysFileInfo)(nil)
10+
11+
// nosysFileInfo hides the system-dependent info of the wrapped FileInfo to
12+
// prevent tar.FileInfoHeader from introspecting it and potentially calling into
13+
// glibc.
14+
//
15+
// It implements [tar.FileInfoNames] to further prevent [tar.FileInfoHeader]
16+
// from performing any lookups on go1.23 and up. see https://go.dev/issue/50102
17+
type nosysFileInfo struct {
18+
os.FileInfo
19+
}
20+
21+
// Uname stubs out looking up username. It implements [tar.FileInfoNames]
22+
// to prevent [tar.FileInfoHeader] from loading libraries to perform
23+
// username lookups.
24+
func (fi nosysFileInfo) Uname() (string, error) {
25+
return "", nil
26+
}
27+
28+
// Gname stubs out looking up group-name. It implements [tar.FileInfoNames]
29+
// to prevent [tar.FileInfoHeader] from loading libraries to perform
30+
// username lookups.
31+
func (fi nosysFileInfo) Gname() (string, error) {
32+
return "", nil
33+
}
34+
35+
func (fi nosysFileInfo) Sys() interface{} {
36+
// A Sys value of type *tar.Header is safe as it is system-independent.
37+
// The tar.FileInfoHeader function copies the fields into the returned
38+
// header without performing any OS lookups.
39+
if sys, ok := fi.FileInfo.Sys().(*tar.Header); ok {
40+
return sys
41+
}
42+
return nil
43+
}
44+
45+
// sysStat, if non-nil, populates hdr from system-dependent fields of fi.
46+
var sysStat func(fi os.FileInfo, hdr *tar.Header) error
47+
48+
// FileInfoHeaderNoLookups creates a partially-populated tar.Header from fi.
49+
//
50+
// Compared to the archive/tar.FileInfoHeader function, this function is safe to
51+
// call from a chrooted process as it does not populate fields which would
52+
// require operating system lookups. It behaves identically to
53+
// tar.FileInfoHeader when fi is a FileInfo value returned from
54+
// tar.Header.FileInfo().
55+
//
56+
// When fi is a FileInfo for a native file, such as returned from os.Stat() and
57+
// os.Lstat(), the returned Header value differs from one returned from
58+
// tar.FileInfoHeader in the following ways. The Uname and Gname fields are not
59+
// set as OS lookups would be required to populate them. The AccessTime and
60+
// ChangeTime fields are not currently set (not yet implemented) although that
61+
// is subject to change. Callers which require the AccessTime or ChangeTime
62+
// fields to be zeroed should explicitly zero them out in the returned Header
63+
// value to avoid any compatibility issues in the future.
64+
func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) {
65+
hdr, err := tar.FileInfoHeader(nosysFileInfo{fi}, link)
66+
if err != nil {
67+
return nil, err
68+
}
69+
if sysStat != nil {
70+
return hdr, sysStat(fi, hdr)
71+
}
72+
return hdr, nil
73+
}

Diff for: tarheader/tarheader_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package tarheader
2+
3+
import (
4+
"archive/tar"
5+
"os"
6+
"testing"
7+
)
8+
9+
func TestNosysFileInfo(t *testing.T) {
10+
st, err := os.Stat("tarheader_test.go")
11+
if err != nil {
12+
t.Fatal(err)
13+
}
14+
h, err := tar.FileInfoHeader(nosysFileInfo{st}, "")
15+
if err != nil {
16+
t.Fatal(err)
17+
}
18+
if h.Uname != "" {
19+
t.Errorf("uname should be empty; got %v", h.Uname)
20+
}
21+
if h.Gname != "" {
22+
t.Errorf("gname should be empty; got %v", h.Uname)
23+
}
24+
}

Diff for: tarheader/tarheader_unix.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//go:build !windows
2+
3+
package tarheader
4+
5+
import (
6+
"archive/tar"
7+
"os"
8+
"runtime"
9+
"syscall"
10+
11+
"golang.org/x/sys/unix"
12+
)
13+
14+
func init() {
15+
sysStat = statUnix
16+
}
17+
18+
// statUnix populates hdr from system-dependent fields of fi without performing
19+
// any OS lookups.
20+
func statUnix(fi os.FileInfo, hdr *tar.Header) error {
21+
// Devmajor and Devminor are only needed for special devices.
22+
23+
// In FreeBSD, RDev for regular files is -1 (unless overridden by FS):
24+
// https://cgit.freebsd.org/src/tree/sys/kern/vfs_default.c?h=stable/13#n1531
25+
// (NODEV is -1: https://cgit.freebsd.org/src/tree/sys/sys/param.h?h=stable/13#n241).
26+
27+
// ZFS in particular does not override the default:
28+
// https://cgit.freebsd.org/src/tree/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c?h=stable/13#n2027
29+
30+
// Since `Stat_t.Rdev` is uint64, the cast turns -1 into (2^64 - 1).
31+
// Such large values cannot be encoded in a tar header.
32+
if runtime.GOOS == "freebsd" && hdr.Typeflag != tar.TypeBlock && hdr.Typeflag != tar.TypeChar {
33+
return nil
34+
}
35+
s, ok := fi.Sys().(*syscall.Stat_t)
36+
if !ok {
37+
return nil
38+
}
39+
40+
hdr.Uid = int(s.Uid)
41+
hdr.Gid = int(s.Gid)
42+
43+
if s.Mode&unix.S_IFBLK != 0 ||
44+
s.Mode&unix.S_IFCHR != 0 {
45+
hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) //nolint: unconvert
46+
hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) //nolint: unconvert
47+
}
48+
49+
return nil
50+
}

0 commit comments

Comments
 (0)