@@ -23,8 +23,39 @@ import (
2323 "os"
2424 "path/filepath"
2525 "strings"
26+ "syscall"
2627)
2728
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+ }
48+
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+ }
55+
56+ return header , nil
57+ }
58+
2859// Tar creates a tar archive of the specified path (file or directory)
2960// and returns the content as a stream. For individual files, it preserves
3061// the directory structure relative to the working directory.
@@ -56,7 +87,7 @@ func Tar(srcPath string, workDir string) (io.Reader, error) {
5687 return fmt .Errorf ("failed to get relative path: %w" , err )
5788 }
5889
59- header , err := tar . FileInfoHeader (info , "" )
90+ header , err := buildTarHeader (info )
6091 if err != nil {
6192 return fmt .Errorf ("failed to create tar header: %w" , err )
6293 }
@@ -95,14 +126,13 @@ func Tar(srcPath string, workDir string) (io.Reader, error) {
95126 }
96127 defer file .Close ()
97128
98- header , err := tar . FileInfoHeader (info , "" )
129+ header , err := buildTarHeader (info )
99130 if err != nil {
100131 pw .CloseWithError (fmt .Errorf ("failed to create tar header: %w" , err ))
101132 return
102133 }
103134
104- // Use relative path as the header name to preserve directory structure
105- // This keeps the directory structure as part of the file path in the tar.
135+ // Use relative path as the header name to preserve directory structure.
106136 relPath , err := filepath .Rel (workDir , srcPath )
107137 if err != nil {
108138 pw .CloseWithError (fmt .Errorf ("failed to get relative path: %w" , err ))
@@ -189,9 +219,9 @@ func Untar(reader io.Reader, destPath string) error {
189219 }
190220 file .Close ()
191221
192- // Set correct permissions for the directory .
222+ // Set correct permissions for the file .
193223 if err := os .Chmod (targetPath , os .FileMode (header .Mode )); err != nil {
194- return fmt .Errorf ("failed to set directory permissions %s: %w" , targetPath , err )
224+ return fmt .Errorf ("failed to set file permissions %s: %w" , targetPath , err )
195225 }
196226 // Set modification time for the file.
197227 if err := os .Chtimes (targetPath , header .ModTime , header .ModTime ); err != nil {
0 commit comments