Skip to content

Commit

Permalink
fix(deb): content file modes to support gdebi (#904)
Browse files Browse the repository at this point in the history
* deb: Fix content file modes to support gdebi, etc.

* deb: Better errors and fewer shadowing danger during data.tar creation
  • Loading branch information
erikgeiser authored Feb 5, 2025
1 parent 0d40536 commit 38a851d
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 35 deletions.
127 changes: 93 additions & 34 deletions deb/deb.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -386,40 +387,49 @@ func fillDataTar(info *nfpm.Info, w io.Writer) (md5sums []byte, instSize int64,
}

func createFilesInsideDataTar(info *nfpm.Info, tw *tar.Writer) (md5buf bytes.Buffer, instSize int64, err error) {
// create files and implicit directories
for _, file := range info.Contents {
var size int64 // declare early to avoid shadowing err
switch file.Type {
case files.TypeRPMGhost:
// skip ghost files in deb
continue
continue // skip ghost files in deb
case files.TypeDir, files.TypeImplicitDir:
err = tw.WriteHeader(&tar.Header{
Name: files.AsExplicitRelativePath(file.Destination),
Mode: int64(file.FileInfo.Mode),
Typeflag: tar.TypeDir,
Format: tar.FormatGNU,
Uname: file.FileInfo.Owner,
Gname: file.FileInfo.Group,
ModTime: modtime.Get(info.MTime),
})
header, err := tarHeader(file, info.MTime)
if err != nil {
return md5buf, 0, fmt.Errorf("build directory header for %q: %w",
file.Destination, err)
}

err = tw.WriteHeader(header)
if err != nil {
return md5buf, 0, fmt.Errorf("create directory %q in data tar: %w",
header.Name, err)
}
case files.TypeSymlink:
err = newItemInsideTar(tw, []byte{}, &tar.Header{
Name: files.AsExplicitRelativePath(file.Destination),
Linkname: file.Source,
Typeflag: tar.TypeSymlink,
ModTime: modtime.Get(info.MTime),
Format: tar.FormatGNU,
})
header, err := tarHeader(file, info.MTime)
if err != nil {
return md5buf, 0, fmt.Errorf("build symlink header for %q: %w",
file.Destination, err)
}

err = newItemInsideTar(tw, []byte{}, header)
if err != nil {
return md5buf, 0, fmt.Errorf("create symlink %q in data tar: %w",
header.Linkname, err)
}
case files.TypeDebChangelog:
size, err = createChangelogInsideDataTar(tw, &md5buf, info, file.Destination)
size, err := createChangelogInsideDataTar(tw, &md5buf, info, file.Destination)
if err != nil {
return md5buf, 0, fmt.Errorf("write changelog to data tar: %w", err)
}

instSize += size
default:
size, err = copyToTarAndDigest(file, tw, &md5buf)
}
if err != nil {
return md5buf, 0, err
size, err := copyToTarAndDigest(file, tw, &md5buf)
if err != nil {
return md5buf, 0, fmt.Errorf("write %q to data tar: %w", file.Destination, err)
}

instSize += size
}
instSize += size
}

return md5buf, instSize, nil
Expand All @@ -433,18 +443,11 @@ func copyToTarAndDigest(file *files.Content, tw *tar.Writer, md5w io.Writer) (in
// don't care if it errs while closing...
defer tarFile.Close() // nolint: errcheck,gosec

header, err := tar.FileInfoHeader(file, file.Source)
header, err := tarHeader(file)
if err != nil {
return 0, err
}

// tar.FileInfoHeader only uses file.Mode().Perm() which masks the mode with
// 0o777 which we don't want because we want to be able to set the suid bit.
header.Mode = int64(file.Mode())
header.Format = tar.FormatGNU
header.Name = files.AsExplicitRelativePath(file.Destination)
header.Uname = file.FileInfo.Owner
header.Gname = file.FileInfo.Group
if err := tw.WriteHeader(header); err != nil {
return 0, fmt.Errorf("cannot write header of %s to data.tar.gz: %w", file.Source, err)
}
Expand Down Expand Up @@ -804,3 +807,59 @@ func writeControl(w io.Writer, data controlData) error {
})
return template.Must(tmpl.Parse(controlTemplate)).Execute(w, data)
}

func tarHeader(content *files.Content, preferredModTimes ...time.Time) (*tar.Header, error) {
const (
ISUID = 0o4000 // Set uid
ISGID = 0o2000 // Set gid
ISVTX = 0o1000 // Save text (sticky bit)
)

fm := content.Mode()

h := &tar.Header{
Name: content.Name(),
ModTime: modtime.Get(
append(preferredModTimes, content.ModTime())...),
Mode: int64(fm & 0o7777),
Uname: content.FileInfo.Owner,
Gname: content.FileInfo.Group,
Format: tar.FormatGNU,
}

switch {
case content.IsDir() || fm&fs.ModeDir != 0:
h.Typeflag = tar.TypeDir
h.Name = files.AsExplicitRelativePath(content.Destination)
case content.Type == files.TypeSymlink || fm&fs.ModeSymlink != 0:
h.Typeflag = tar.TypeSymlink
h.Name = files.AsExplicitRelativePath(content.Destination)
h.Linkname = content.Source
case fm&fs.ModeDevice != 0:
if fm&fs.ModeCharDevice != 0 {
h.Typeflag = tar.TypeChar
} else {
h.Typeflag = tar.TypeBlock
}
case fm&fs.ModeNamedPipe != 0:
h.Typeflag = tar.TypeFifo
case fm&fs.ModeSocket != 0:
return nil, fmt.Errorf("archive/tar: sockets not supported")
default:
h.Typeflag = tar.TypeReg
h.Name = files.AsExplicitRelativePath(content.Destination)
h.Size = content.Size()
}

if fm&fs.ModeSetuid != 0 {
h.Mode |= ISUID
}
if fm&fs.ModeSetgid != 0 {
h.Mode |= ISGID
}
if fm&fs.ModeSticky != 0 {
h.Mode |= ISVTX
}

return h, nil
}
2 changes: 1 addition & 1 deletion files/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func (c *Content) ModTime() time.Time {

// IsDir to part of the os.FileInfo interface
func (c *Content) IsDir() bool {
return false
return c.Type == TypeDir || c.Type == TypeImplicitDir
}

// Sys to part of the os.FileInfo interface
Expand Down

0 comments on commit 38a851d

Please sign in to comment.