Skip to content

Commit

Permalink
fix: rpm: broken backup support for rpm native
Browse files Browse the repository at this point in the history
  • Loading branch information
M0Rf30 committed Nov 5, 2024
1 parent 56aceae commit 05bc0c2
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 49 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/github/go-spdx/v2 v2.3.2
github.com/go-git/go-git/v5 v5.12.0
github.com/go-playground/validator/v10 v10.22.1
github.com/google/rpmpack v0.6.1-0.20240413165640-60c43da513f7
github.com/google/rpmpack v0.6.1-0.20241021120301-8407d9abd9f4
github.com/mholt/archiver/v4 v4.0.0-alpha.8.0.20241018202043-264c9016644b
github.com/otiai10/copy v1.14.0
github.com/pkg/errors v0.9.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.6.1-0.20240413165640-60c43da513f7 h1:B6QHhFc8R7+dG/Vny4uAkrAdKRAVLTWMVDpHD36JF7E=
github.com/google/rpmpack v0.6.1-0.20240413165640-60c43da513f7/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
github.com/google/rpmpack v0.6.1-0.20241021120301-8407d9abd9f4 h1:Oo8zuuLz8RF+Q4HcyDqOUxNbzxjM445Z41BsYbllxXk=
github.com/google/rpmpack v0.6.1-0.20241021120301-8407d9abd9f4/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
Expand Down
2 changes: 1 addition & 1 deletion pkg/packer/packer.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func GetPackageManager(pkgBuild *pkgbuild.PKGBUILD, distro string) Packer {
PKGBUILD: pkgBuild,
}
default:
utils.Logger.Fatal("unsupported linux target",
utils.Logger.Fatal("unsupported linux distro",
utils.Logger.Args("distro", distro))
}

Expand Down
225 changes: 183 additions & 42 deletions pkg/rpm/rpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ func (r *RPM) BuildPackage(artifactsPath string) error {
BuildTime: time.Now(),
})

r.addScriptFiles(rpm)

if err := r.addFiles(rpm); err != nil {
if err := r.createFilesInsideRPM(rpm); err != nil {
return err
}

Expand Down Expand Up @@ -178,61 +176,139 @@ func (r *RPM) Update() error {
return nil
}

// addFiles adds files from the specified package directory to the RPM package.
func (r *RPM) addFiles(rpm *rpmpack.RPM) error {
var files []string

err := filepath.WalkDir(r.PKGBUILD.PackageDir,
func(path string, dirEntry fs.DirEntry, err error) error {
if err != nil {
return err
}

if dirEntry.IsDir() {
isEmptyDir := utils.IsEmptyDir(path, dirEntry)
if isEmptyDir {
files = append(files, path)
}
} else {
files = append(files, path)
}

return nil
})
// createFilesInsideRPM prepares and adds files to the specified RPM object.
// It retrieves backup files, walks through the package directory, and adds the contents to the RPM.
func (r *RPM) createFilesInsideRPM(rpm *rpmpack.RPM) error {
// Prepare a list of backup files by calling the prepareBackupFiles method.
backupFiles := r.prepareBackupFiles()

// Walk through the package directory and retrieve the contents.
contents, err := walkPackageDirectory(r.PKGBUILD.PackageDir, backupFiles)
if err != nil {
return err
return err // Return the error if walking the directory fails.
}

for _, filePath := range files {
cleanFilePath := filepath.Clean(filePath)
body, _ := os.ReadFile(cleanFilePath)
rpm.AddFile(rpmpack.RPMFile{
Name: strings.TrimPrefix(filePath, r.PKGBUILD.PackageDir),
Body: body,
})
// Add the retrieved contents to the RPM object and return any error that occurs.
return addContentsToRPM(contents, rpm)
}

// addContentsToRPM adds a slice of FileContent objects to the specified RPM object.
// It creates RPMFile objects from the FileContent and adds them to the RPM.
func addContentsToRPM(contents []*utils.FileContent, rpm *rpmpack.RPM) error {
// Iterate over each FileContent in the provided slice.
for _, content := range contents {
// Create an RPMFile from the FileContent.
file, err := createRPMFile(content)
if err != nil {
return err // Return the error if creating the RPMFile fails.
}

// Clean the file name to ensure it has a proper format.
file.Name = filepath.Clean(file.Name)
// Add the created RPMFile to the RPM object.
rpm.AddFile(*file)
}

// Return nil indicating that all contents were added successfully.
return nil
}

// addScriptFiles adds script files to the RPM package based on the PKGBUILD configuration.
func (r *RPM) addScriptFiles(rpm *rpmpack.RPM) {
if r.PKGBUILD.PreInst != "" {
rpm.AddPretrans(r.PKGBUILD.PreInst)
// asRPMDirectory creates an RPMFile object for a directory based on the provided FileContent.
// It retrieves the directory's modification time and sets the appropriate fields in the RPMFile.
func asRPMDirectory(content *utils.FileContent) *rpmpack.RPMFile {
// Get file information for the directory specified in the content.
fileInfo, _ := os.Stat(filepath.Clean(content.Source))

// Retrieve the modification time of the directory.
mTime := utils.GetModTime(fileInfo)

// Create and return an RPMFile object for the directory.
return &rpmpack.RPMFile{
Name: content.Destination, // Set the destination name.
Mode: uint(utils.TagDirectory), // Set the mode to indicate it's a directory.
MTime: mTime, // Set the modification time.
Owner: "root", // Set the owner to "root".
Group: "root", // Set the group to "root".
}
}

// asRPMFile creates an RPMFile object for a regular file based on the provided FileContent.
// It reads the file's data and retrieves its modification time.
func asRPMFile(content *utils.FileContent, fileType rpmpack.FileType) (*rpmpack.RPMFile, error) {
// Read the file data from the source path.
data, err := os.ReadFile(content.Source)
if err != nil {
return nil, err // Return nil and the error if reading the file fails.
}

if r.PKGBUILD.PreRm != "" {
rpm.AddPretrans(r.PKGBUILD.PreRm)
cleanFilePath := filepath.Clean(content.Source)
fileInfo, _ := os.Stat(cleanFilePath)

// Retrieve the modification time of the file.
mTime := utils.GetModTime(fileInfo)

// Create and return an RPMFile object for the regular file.
return &rpmpack.RPMFile{
Name: content.Destination, // Set the destination name.
Body: data, // Set the file data.
Mode: uint(fileInfo.Mode()), // Set the file mode.
MTime: mTime, // Set the modification time.
Owner: "root", // Set the owner to "root".
Group: "root", // Set the group to "root".
Type: fileType, // Set the file type.
}, nil
}

// asRPMSymlink creates an RPMFile object for a symbolic link based on the provided FileContent.
// It retrieves the link's target and modification time.
func asRPMSymlink(content *utils.FileContent) *rpmpack.RPMFile {
cleanFilePath := filepath.Clean(content.Source)
fileInfo, _ := os.Lstat(cleanFilePath) // Use Lstat to get information about the symlink.
body, _ := os.Readlink(cleanFilePath) // Read the target of the symlink.

// Retrieve the modification time of the symlink.
mTime := utils.GetModTime(fileInfo)

// Create and return an RPMFile object for the symlink.
return &rpmpack.RPMFile{
Name: content.Destination, // Set the destination name.
Body: []byte(body), // Set the target of the symlink as the body.
Mode: uint(utils.TagLink), // Set the mode to indicate it's a symlink.
MTime: mTime, // Set the modification time.
Owner: "root", // Set the owner to "root".
Group: "root", // Set the group to "root".
}
}

if r.PKGBUILD.PostInst != "" {
rpm.AddPretrans(r.PKGBUILD.PostInst)
// createContent creates a new FileContent object with the specified source path,
// destination path (relative to the package directory), and content type.
func createContent(path, packageDir, contentType string) *utils.FileContent {
return &utils.FileContent{
Source: path,
Destination: strings.TrimPrefix(path, packageDir),
Type: contentType,
}
}

if r.PKGBUILD.PostRm != "" {
rpm.AddPretrans(r.PKGBUILD.PostRm)
// createRPMFile converts a FileContent object into an RPMFile object based on its type.
// It returns the created RPMFile and any error encountered during the conversion.
func createRPMFile(content *utils.FileContent) (*rpmpack.RPMFile, error) {
var file *rpmpack.RPMFile

var err error

switch content.Type {
case utils.TypeConfigNoReplace:
file, err = asRPMFile(content, rpmpack.ConfigFile|rpmpack.NoReplaceFile)
case utils.TypeSymlink:
file = asRPMSymlink(content)
case utils.TypeDir:
file = asRPMDirectory(content)
case utils.TypeFile:
file, err = asRPMFile(content, rpmpack.GenericFile)
}

return file, err
}

// getGroup updates the section of the RPM struct with the corresponding
Expand All @@ -256,6 +332,42 @@ func (r *RPM) getRelease() {
}
}

// handleFileEntry processes a file entry at the given path, checking if it is a backup file,
// and appending its content to the provided slice based on its type (config, symlink, or regular file).
func handleFileEntry(path string, backupFiles []string,
packageDir string, contents *[]*utils.FileContent) error {
fileInfo, err := os.Lstat(path)
if err != nil {
return err // Handle error from os.Lstat
}

if fileInfo.Mode()&os.ModeSymlink != 0 {
*contents = append(*contents, createContent(path, packageDir, utils.TypeSymlink))
} else if utils.Contains(backupFiles, strings.TrimPrefix(path, packageDir)) {
*contents = append(*contents, createContent(path, packageDir, utils.TypeConfigNoReplace))
} else {
*contents = append(*contents, createContent(path, packageDir, utils.TypeFile))
}

return nil
}

// prepareBackupFiles prepares a list of backup file paths by ensuring each path
// has a leading slash and returns the resulting slice of backup file paths.
func (r *RPM) prepareBackupFiles() []string {
backupFiles := make([]string, 0)

for _, filePath := range r.PKGBUILD.Backup {
if !strings.HasPrefix(filePath, "/") {
filePath = "/" + filePath
}

backupFiles = append(backupFiles, filePath)
}

return backupFiles
}

// processDepends converts a slice of strings into a rpmpack.Relations object.
// It attempts to set each string in the slice as a relation.
// If any error occurs during the setting process, it returns nil.
Expand All @@ -280,3 +392,32 @@ func processDepends(depends []string) rpmpack.Relations {

return relations
}

// walkPackageDirectory traverses the specified package directory and collects
// file contents, including handling backup files and empty directories.
// It returns a slice of FileContent and an error if any occurs during the traversal.
func walkPackageDirectory(packageDir string, backupFiles []string) ([]*utils.FileContent, error) {
var contents []*utils.FileContent

err := filepath.WalkDir(packageDir, func(path string, dirEntry fs.DirEntry, err error) error {
if err != nil {
return err
}

if dirEntry.IsDir() {
if utils.IsEmptyDir(path, dirEntry) {
contents = append(contents, createContent(path, packageDir, utils.TypeDir))
}

return nil
}

return handleFileEntry(path, backupFiles, packageDir, &contents)
})

if err != nil {
return nil, err
}

return contents, nil
}
54 changes: 54 additions & 0 deletions pkg/utils/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,51 @@ import (
"os"
"path/filepath"
"strings"
"time"

"github.com/pkg/errors"
)

const (
// Symbolic link.
TagLink = 0o120000
// Directory.
TagDirectory = 0o40000
// TypeFile is the type of a regular file. This is also the type that is
// implied when no type is specified.
TypeFile = "file"
// TypeDir is the type of a directory that is explicitly added in order to
// declare ownership or non-standard permission.
TypeDir = "dir"
// TypeSymlink is the type of a symlink that is created at the destination
// path and points to the source path.
TypeSymlink = "symlink"
// TypeConfig is the type of a configuration file that may be changed by the
// user of the package.
TypeConfig = "config"
// TypeConfigNoReplace is like TypeConfig with an added noreplace directive
// that is respected by RPM-based distributions.
// For all other package formats it is handled exactly like TypeConfig.
TypeConfigNoReplace = "config|noreplace"
)

// FileContent describes the source and destination
// of one file to copy into a package.
type FileContent struct {
Source string
Destination string
Type string
FileInfo *FileInfo
}

type FileInfo struct {
Owner string
Group string
Mode os.FileMode
MTime time.Time
Size int64
}

// CheckWritable checks if a binary file is writeable.
//
// It checks if the file exists and if write permission is granted.
Expand Down Expand Up @@ -167,6 +208,19 @@ func GetFileType(binary string) string {
return elfFile.Type.String()
}

// GetModTime retrieves the modification time of a file and checks for overflow.
// It returns the modification time as an uint32.
func GetModTime(fileInfo os.FileInfo) uint32 {
mTime := fileInfo.ModTime().Unix()
// Check for overflow in the modification time.
if mTime < 0 || mTime > int64(^uint32(0)) {
Logger.Fatal("modification time is out of range for uint32",
Logger.Args("time", mTime))
}

return uint32(mTime)
}

// IsEmptyDir checks if a directory is empty.
//
// It takes in two parameters: path, a string representing the directory path,
Expand Down
6 changes: 3 additions & 3 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,9 @@ func GOSetup() error {

// PullContainers pulls the specified container image.
//
// target: the name of the container image to pull.
// distro: the name of the container image to pull.
// error: returns an error if the container image cannot be pulled.
func PullContainers(target string) error {
func PullContainers(distro string) error {
var containerApp string

if Exists("/usr/bin/podman") {
Expand All @@ -213,7 +213,7 @@ func PullContainers(target string) error {

args := []string{
"pull",
constants.DockerOrg + target,
constants.DockerOrg + distro,
}

if _, err := os.Stat(containerApp); err == nil {
Expand Down

0 comments on commit 05bc0c2

Please sign in to comment.