github.com/goreleaser/nfpm/v2@v2.44.0/ipk/tar.go (about) 1 package ipk 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "compress/gzip" 7 "fmt" 8 "io" 9 "os" 10 "time" 11 12 "github.com/goreleaser/nfpm/v2/files" 13 ) 14 15 // newTGZ creates a new tar.gz archive with the given name and populates it 16 // with the given function. 17 // 18 // The function returns the bytes of the archive, its size and an error if any. 19 func newTGZ(name string, populate func(*tar.Writer) error) ([]byte, error) { 20 var buf bytes.Buffer 21 22 gz := gzip.NewWriter(&buf) 23 tarball := tar.NewWriter(gz) 24 25 // the writers are properly closed later, this is just in case that we error out 26 defer gz.Close() // nolint: errcheck 27 defer tarball.Close() // nolint: errcheck 28 29 if err := populate(tarball); err != nil { 30 return nil, fmt.Errorf("cannot populate '%s': %w", name, err) 31 } 32 33 if err := tarball.Close(); err != nil { 34 return nil, fmt.Errorf("cannot close '%s': %w", name, err) 35 } 36 37 if err := gz.Close(); err != nil { 38 return nil, fmt.Errorf("cannot close '%s': %w", name, err) 39 } 40 41 return buf.Bytes(), nil 42 } 43 44 // writeFile writes a file from the filesystem to the tarball. 45 func writeFile(out *tar.Writer, file *files.Content) (int64, error) { 46 f, err := os.OpenFile(file.Source, os.O_RDONLY, 0o600) //nolint:gosec 47 if err != nil { 48 return 0, fmt.Errorf("could not open file %s to read and include in the archive: %w", file.Source, err) 49 } 50 defer f.Close() // nolint: errcheck 51 52 header, err := tar.FileInfoHeader(file, file.Source) 53 if err != nil { 54 return 0, err 55 } 56 57 content, err := io.ReadAll(f) 58 if err != nil { 59 return 0, err 60 } 61 62 size := int64(len(content)) 63 64 // tar.FileInfoHeader only uses file.Mode().Perm() which masks the mode with 65 // 0o777 which we don't want because we want to be able to set the suid bit. 66 header.Mode = int64(file.Mode()) 67 header.Format = tar.FormatGNU 68 header.Name = files.AsExplicitRelativePath(file.Destination) 69 header.Size = size 70 header.Uname = file.FileInfo.Owner 71 header.Gname = file.FileInfo.Group 72 if err := out.WriteHeader(header); err != nil { 73 return 0, fmt.Errorf("cannot write tar header for file %s to archive: %w", file.Source, err) 74 } 75 76 n, err := out.Write(content) 77 if err != nil { 78 return 0, fmt.Errorf("%s: failed to copy: %w", file.Source, err) 79 } 80 81 if int64(n) != size { 82 return 0, fmt.Errorf("%s: failed to copy: expected %d bytes, copied %d", file.Source, size, n) 83 } 84 85 return size, nil 86 } 87 88 // writeToFile writes a file to the tarball where the contents are an array of bytes. 89 func writeToFile(out *tar.Writer, filename string, content []byte, mtime time.Time) error { 90 header := tar.Header{ 91 Name: files.AsExplicitRelativePath(filename), 92 Size: int64(len(content)), 93 Mode: 0o644, 94 ModTime: mtime, 95 Typeflag: tar.TypeReg, 96 Format: tar.FormatGNU, 97 } 98 99 if err := out.WriteHeader(&header); err != nil { 100 return fmt.Errorf("cannot write file header %s to archive: %w", header.Name, err) 101 } 102 103 _, err := out.Write(content) 104 if err != nil { 105 return fmt.Errorf("cannot write file %s payload: %w", header.Name, err) 106 } 107 return nil 108 }