@@ -23,38 +23,17 @@ import (
2323 "os"
2424 "path/filepath"
2525 "strings"
26- "syscall"
2726)
2827
29- // buildTarHeader creates a tar header from FileInfo without using CGO-based
30- // os/user.LookupGroupId or os/user.LookupUserId. This avoids a segfault in
31- // statically linked CGO binaries caused by glibc NSS (getgrgid_r) being
32- // incompatible with static linking across different glibc versions.
33- // See: https://github.com/modelpack/modctl/issues/285
34- func buildTarHeader (info os.FileInfo ) (* tar.Header , error ) {
35- header := & tar.Header {
36- Name : info .Name (),
37- Size : info .Size (),
38- Mode : int64 (info .Mode ()),
39- ModTime : info .ModTime (),
40- }
41-
42- // Set file type flag.
43- if info .IsDir () {
44- header .Typeflag = tar .TypeDir
45- } else {
46- header .Typeflag = tar .TypeReg
47- }
28+ type noLookupFileInfo struct {
29+ os.FileInfo
30+ }
4831
49- // Safely extract UID/GID from syscall.Stat_t without CGO user/group name lookup.
50- // We intentionally leave Uname/Gname empty to avoid os/user CGO calls entirely.
51- if stat , ok := info .Sys ().(* syscall.Stat_t ); ok {
52- header .Uid = int (stat .Uid )
53- header .Gid = int (stat .Gid )
54- }
32+ // Uname returns an empty user name to skip CGO-based uid→name lookup.
33+ func (n noLookupFileInfo ) Uname () (string , error ) { return "" , nil }
5534
56- return header , nil
57- }
35+ // Gname returns an empty group name to skip CGO-based gid→name lookup.
36+ func ( n noLookupFileInfo ) Gname () ( string , error ) { return "" , nil }
5837
5938// Tar creates a tar archive of the specified path (file or directory)
6039// and returns the content as a stream. For individual files, it preserves
@@ -81,24 +60,35 @@ func Tar(srcPath string, workDir string) (io.Reader, error) {
8160 return err
8261 }
8362
84- // Create a relative path for the tar file header.
85- relPath , err := filepath .Rel (workDir , path )
86- if err != nil {
87- return fmt .Errorf ("failed to get relative path: %w" , err )
63+ // Resolve symlink target if needed.
64+ link := ""
65+ if info .Mode ()& os .ModeSymlink != 0 {
66+ link , err = os .Readlink (path )
67+ if err != nil {
68+ return fmt .Errorf ("failed to read symlink %s: %w" , path , err )
69+ }
8870 }
8971
90- header , err := buildTarHeader ( info )
72+ header , err := tar . FileInfoHeader ( noLookupFileInfo { info }, link )
9173 if err != nil {
9274 return fmt .Errorf ("failed to create tar header: %w" , err )
9375 }
9476
95- // Set the header name to preserve directory structure.
77+ // FileInfoHeader only fills the base name; set the full relative path.
78+ relPath , err := filepath .Rel (workDir , path )
79+ if err != nil {
80+ return fmt .Errorf ("failed to get relative path: %w" , err )
81+ }
9682 header .Name = relPath
83+ if info .IsDir () {
84+ header .Name += "/"
85+ }
86+
9787 if err := tw .WriteHeader (header ); err != nil {
9888 return fmt .Errorf ("failed to write header: %w" , err )
9989 }
10090
101- if ! info .IsDir () {
91+ if ! info .IsDir () && info . Mode () & os . ModeSymlink == 0 {
10292 file , err := os .Open (path )
10393 if err != nil {
10494 return fmt .Errorf ("failed to open file %s: %w" , path , err )
@@ -126,21 +116,29 @@ func Tar(srcPath string, workDir string) (io.Reader, error) {
126116 }
127117 defer file .Close ()
128118
129- header , err := buildTarHeader (info )
119+ // Resolve symlink target if needed.
120+ link := ""
121+ if info .Mode ()& os .ModeSymlink != 0 {
122+ link , err = os .Readlink (srcPath )
123+ if err != nil {
124+ pw .CloseWithError (fmt .Errorf ("failed to read symlink %s: %w" , srcPath , err ))
125+ return
126+ }
127+ }
128+ header , err := tar .FileInfoHeader (noLookupFileInfo {info }, link )
130129 if err != nil {
131130 pw .CloseWithError (fmt .Errorf ("failed to create tar header: %w" , err ))
132131 return
133132 }
134133
135- // Use relative path as the header name to preserve directory structure .
134+ // FileInfoHeader only fills the base name; set the full relative path .
136135 relPath , err := filepath .Rel (workDir , srcPath )
137136 if err != nil {
138137 pw .CloseWithError (fmt .Errorf ("failed to get relative path: %w" , err ))
139138 return
140139 }
141-
142- // Use the relative path (including directories) as the header name.
143140 header .Name = relPath
141+
144142 if err := tw .WriteHeader (header ); err != nil {
145143 pw .CloseWithError (fmt .Errorf ("failed to write header: %w" , err ))
146144 return
0 commit comments