Skip to content

Commit

Permalink
chore: dpkg: rewrite strip functions
Browse files Browse the repository at this point in the history
  • Loading branch information
M0Rf30 committed Oct 18, 2024
1 parent 53f3778 commit 70995d5
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 81 deletions.
1 change: 1 addition & 0 deletions examples/yap/PKGBUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
22 changes: 2 additions & 20 deletions pkg/dpkg/dpkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
128 changes: 67 additions & 61 deletions pkg/options/strip.go
Original file line number Diff line number Diff line change
@@ -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
}
23 changes: 23 additions & 0 deletions pkg/pkgbuild/pkgbuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
}
Expand Down Expand Up @@ -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
}
}
}
89 changes: 89 additions & 0 deletions pkg/utils/file.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package utils

import (
"debug/elf"
"os"
"path/filepath"
"strings"

"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
Expand Down Expand Up @@ -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,
Expand All @@ -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 "!<arch>" magic string which indicates a static library
if string(header) == "!<arch>\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.
Expand Down Expand Up @@ -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
}
1 change: 1 addition & 0 deletions pkg/utils/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 70995d5

Please sign in to comment.