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  }