diff --git a/examples/yap/PKGBUILD b/examples/yap/PKGBUILD index 99b1baa..eab9000 100644 --- a/examples/yap/PKGBUILD +++ b/examples/yap/PKGBUILD @@ -14,6 +14,7 @@ copyright=( ) arch=('x86_64') license=('GPL-3.0-only') +options=('!strip') section=misc priority=optional url="https://github.com/M0Rf30/${pkgname}" diff --git a/pkg/dpkg/dpkg.go b/pkg/dpkg/dpkg.go index 283015d..36b04c7 100644 --- a/pkg/dpkg/dpkg.go +++ b/pkg/dpkg/dpkg.go @@ -212,24 +212,6 @@ func (d *Deb) Prepare(makeDepends []string) error { return d.PKGBUILD.GetDepends("apt-get", args, makeDepends) } -// Strip strips binaries from the Deb package. -// -// It does not take any parameters. -// It returns an error if there is any issue during stripping. -func (d *Deb) Strip() error { - var output strings.Builder - - tmpl := d.PKGBUILD.RenderSpec(options.StripScript) - - utils.Logger.Info("stripping binaries") - - if err := tmpl.Execute(&output, d.PKGBUILD); err != nil { - return err - } - - return utils.RunScript(output.String()) -} - // Update updates the Deb package list. // // It calls the GetUpdates method of the PKGBUILD field of the Deb struct @@ -347,8 +329,8 @@ func (d *Deb) PrepareFakeroot(_ string) error { return err } - if err := d.Strip(); err != nil { - return err + if d.PKGBUILD.StripEnabled { + return options.Strip(d.PKGBUILD.PackageDir) } return nil diff --git a/pkg/options/strip.go b/pkg/options/strip.go index 1619125..41e4c30 100644 --- a/pkg/options/strip.go +++ b/pkg/options/strip.go @@ -1,63 +1,69 @@ package options -// StripScript is a scriptlet taken from makepkg resources. It's executed by -// mvdan/sh interpreter and provides strip instructions to debian packaging. -// Although it's a very dirty solution, for now it's the faster way to have this -// essential feature. -const StripScript = ` - strip_file() { - local binary=$1 - shift - - local tempfile=$(mktemp "$binary.XXXXXX") - if strip "$@" "$binary" -o "$tempfile"; then - cat "$tempfile" >"$binary" - fi - rm -f "$tempfile" - } - - strip_lto() { - local binary=$1 - - local tempfile=$(mktemp "$binary.XXXXXX") - if strip -R .gnu.lto_* -R .gnu.debuglto_* -N __gnu_lto_v1 "$binary" -o "$tempfile"; then - cat "$tempfile" >"$binary" - fi - rm -f "$tempfile" - } - - # make sure library stripping variables are defined to prevent excess stripping - [[ -z ${STRIP_SHARED+x} ]] && STRIP_SHARED="-S" - [[ -z ${STRIP_STATIC+x} ]] && STRIP_STATIC="-S" - - declare binary strip_flags - binaries=$(find {{.PackageDir}} -type f -perm -u+w -exec echo {} +) - - for binary in ${binaries[@]}; do - STRIPLTO=0 - case "$(LC_ALL=C readelf -h "$binary" 2>/dev/null)" in - *Type:*'DYN (Shared object file)'*) # Libraries (.so) or Relocatable binaries - strip_flags="$STRIP_SHARED" ;; - *Type:*'DYN (Position-Independent Executable file)'*) # Relocatable binaries - strip_flags="$STRIP_SHARED" ;; - *Type:*'EXEC (Executable file)'*) # Binaries - strip_flags="$STRIP_BINARIES" ;; - *Type:*'REL (Relocatable file)'*) # Libraries (.a) or objects - if ar t "$binary" &>/dev/null; then # Libraries (.a) - strip_flags="$STRIP_STATIC" - STRIPLTO=1 - elif [[ $binary = *'.ko' || $binary = *'.o' ]]; then # Kernel module or object file - strip_flags="$STRIP_SHARED" - else - continue - fi - ;; - *) - continue - ;; - esac - strip_file "$binary" ${strip_flags} - ((STRIPLTO)) && strip_lto "$binary" - done - exit 0 -` +import ( + "io/fs" + "path/filepath" + "strings" + + "github.com/M0Rf30/yap/pkg/utils" +) + +func Strip(packageDir string) error { + utils.Logger.Info("stripping binaries") + + return filepath.WalkDir(packageDir, processFile) +} + +func processFile(binary string, dirEntry fs.DirEntry, err error) error { + if err != nil { + return err + } + + if dirEntry.IsDir() { + return nil + } + + if err := utils.CheckWritable(binary); err != nil { + return err // Skip if not writable + } + + fileType := utils.GetFileType(binary) + if fileType == "" || fileType == "ET_NONE" { + return err + } + + stripFlags, stripLTO := determineStripFlags(fileType, binary) + + if err := utils.StripFile(binary, stripFlags); err != nil { + return err + } + + if stripLTO { + if err := utils.StripLTO(binary); err != nil { + return err + } + } + + return nil +} + +func determineStripFlags(fileType, binary string) (string, bool) { + stripBinaries := "--strip-all" + stripShared := "--strip-unneeded" + stripStatic := "--strip-debug" + + switch { + case strings.Contains(fileType, "ET_DYN"): + return stripShared, false + case strings.Contains(fileType, "ET_EXEC"): + return stripBinaries, false + case strings.Contains(fileType, "ET_REL"): + if isStatic, _ := utils.IsStaticLibrary(binary); isStatic { + return stripStatic, true + } else if strings.HasSuffix(binary, ".ko") || strings.HasSuffix(binary, ".o") { + return stripShared, false + } + } + + return "", false +} diff --git a/pkg/pkgbuild/pkgbuild.go b/pkg/pkgbuild/pkgbuild.go index 9c0bb71..d8de39f 100644 --- a/pkg/pkgbuild/pkgbuild.go +++ b/pkg/pkgbuild/pkgbuild.go @@ -64,6 +64,9 @@ type PKGBUILD struct { SourceURI []string StartDir string URL string + StaticEnabled bool + StripEnabled bool + UpxEnabled bool } // AddItem adds an item to the PKGBUILD. @@ -85,6 +88,7 @@ func (pkgBuild *PKGBUILD) AddItem(key string, data any) error { pkgBuild.setMainFolders() pkgBuild.mapArrays(key, data) pkgBuild.mapFunctions(key, data) + pkgBuild.processOptions() return nil } @@ -409,3 +413,22 @@ func (pkgBuild *PKGBUILD) validateLicense() bool { return isValid } + +func (pkgBuild *PKGBUILD) processOptions() { + // Initialize all flags to true + pkgBuild.StaticEnabled = true + pkgBuild.StripEnabled = true + pkgBuild.UpxEnabled = true + + // Iterate over the features array + for _, option := range pkgBuild.Options { + switch { + case strings.HasPrefix(option, "!strip"): + pkgBuild.StripEnabled = false + case strings.HasPrefix(option, "!upx"): + pkgBuild.UpxEnabled = false + case strings.HasPrefix(option, "!static"): + pkgBuild.StaticEnabled = false + } + } +} diff --git a/pkg/utils/file.go b/pkg/utils/file.go index fdd4f5e..db9a922 100644 --- a/pkg/utils/file.go +++ b/pkg/utils/file.go @@ -1,6 +1,7 @@ package utils import ( + "debug/elf" "os" "path/filepath" "strings" @@ -8,6 +9,15 @@ import ( "github.com/pkg/errors" ) +func CheckWritable(binary string) error { + info, err := os.Stat(binary) + if err != nil || info.Mode().Perm()&0200 == 0 { + return err + } + + return nil +} + // Chmod changes the file mode of the specified path. // // It takes a string parameter `path` which represents the path of the file @@ -131,6 +141,25 @@ func GetDirSize(path string) (int64, error) { return size, err } +// GetFileType uses readelf to determine the type of the binary file. +func GetFileType(binary string) string { + // Open the ELF binary + file, err := os.Open(binary) + if err != nil { + Logger.Fatal("fatal error", + Logger.Args("error", err)) + } + defer file.Close() + + // Parse the ELF file + elfFile, err := elf.NewFile(file) + if err != nil { + return "" + } + + return elfFile.Type.String() +} + // IsEmptyDir checks if a directory is empty. // // It takes in two parameters: path, a string representing the directory path, @@ -151,6 +180,36 @@ func IsEmptyDir(path string, dirEntry os.DirEntry) bool { return len(entries) == 0 } +// isStaticLibrary checks if the binary is a static library. +func IsStaticLibrary(binary string) (bool, error) { + // Check the file extension + if strings.HasSuffix(binary, ".a") { + return true, nil + } + + // Open the file to inspect its contents + file, err := os.Open(binary) + if err != nil { + return false, err + } + defer file.Close() + + // Read the first few bytes to check the format + header := make([]byte, 8) + _, err = file.Read(header) + + if err != nil { + return false, err + } + + // Check for the "!" magic string which indicates a static library + if string(header) == "!\n" { + return true, nil + } + + return false, nil +} + // MkdirAll creates a directory and all its parent directories. // // It takes a string parameter `path` which represents the path of the directory to be created. @@ -199,3 +258,33 @@ func RemoveAll(path string) error { return nil } + +// StripFile strips the binary file using the strip command. +func StripFile(binary string, args ...string) error { + return strip(binary, args...) +} + +// StripLTO strips LTO-related sections from the binary file. +func StripLTO(binary string, args ...string) error { + return strip(binary, append(args, "-R", ".gnu.lto_*", "-R", ".gnu.debuglto_*", "-N", "__gnu_lto_v1")...) +} + +func strip(binary string, args ...string) error { + tempFile, err := os.CreateTemp("", filepath.Base(binary)) + if err != nil { + return err + } + defer os.Remove(tempFile.Name()) // Ensure the temp file is removed + + args = append(args, "-o", tempFile.Name(), binary) + + if err := Exec(false, "", "strip", args...); err != nil { + return err + } + + if err := os.Rename(tempFile.Name(), binary); err != nil { + return err + } + + return nil +} diff --git a/pkg/utils/proc.go b/pkg/utils/proc.go index 683f3bf..b29310d 100644 --- a/pkg/utils/proc.go +++ b/pkg/utils/proc.go @@ -15,6 +15,7 @@ import ( // It returns an error if the command execution fails. func Exec(excludeStdout bool, dir, name string, args ...string) error { cmd := exec.Command(name, args...) + cmd.Stdout = MultiPrinter.Writer cmd.Stderr = MultiPrinter.Writer