Initial commit
This commit is contained in:
commit
5ed259c54c
4 changed files with 252 additions and 0 deletions
27
cmd/relocate.go
Normal file
27
cmd/relocate.go
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/rdesaintleger/relocate-distro/pkg/relocator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
prefix := flag.String("prefix", "", "Target prefix directory")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *prefix == "" {
|
||||||
|
fmt.Println("Error: target prefix directory is required")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, src := range flag.Args() {
|
||||||
|
err := relocator.RelocateFile(*prefix, src)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error processing %s: %v\n", src, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
go.mod
Normal file
3
go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
module github.com/rdesaintleger/relocate-distro
|
||||||
|
|
||||||
|
go 1.18
|
||||||
7
main.go
Normal file
7
main.go
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/rdesaintleger/relocate-distro/cmd"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
||||||
215
pkg/relocator/relocator.go
Normal file
215
pkg/relocator/relocator.go
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
package relocator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"debug/elf"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// isDynamicELF checks if the given file is a dynamically linked ELF binary.
|
||||||
|
func isDynamicELF(filePath string) (bool, error) {
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to open file: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Parse the file as an ELF binary using the debug/elf package
|
||||||
|
elfFile, err := elf.NewFile(file)
|
||||||
|
if err != nil {
|
||||||
|
// If it's not an ELF file, return false
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the ELF binary has a PT_DYNAMIC segment (indicating dynamic linking)
|
||||||
|
for _, prog := range elfFile.Progs {
|
||||||
|
if prog.Type == elf.PT_DYNAMIC {
|
||||||
|
return true, nil // Dynamically linked ELF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil // Not dynamically linked
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyRegularFile copies a file from src to dest, preserving ownership and permissions.
|
||||||
|
func copyRegularFile(dest, src string) error {
|
||||||
|
fmt.Printf("Copying file from %s to %s\n", src, dest)
|
||||||
|
|
||||||
|
// Open source file
|
||||||
|
srcFile, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
|
||||||
|
// Create destination file
|
||||||
|
destFile, err := os.Create(dest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer destFile.Close()
|
||||||
|
|
||||||
|
// Copy file contents
|
||||||
|
if _, err := io.Copy(destFile, srcFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get file info to preserve permissions and ownership
|
||||||
|
fileInfo, err := os.Lstat(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve file permissions
|
||||||
|
if err := os.Chmod(dest, fileInfo.Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve file ownership if possible
|
||||||
|
if stat, ok := fileInfo.Sys().(*syscall.Stat_t); ok {
|
||||||
|
_ = os.Lchown(dest, int(stat.Uid), int(stat.Gid)) // Ignore ownership error for non-root users
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// relocateELFBinary copies the ELF binary and updates its shared library paths.
|
||||||
|
func relocateELFBinary(prefix, dest, src string) error {
|
||||||
|
// Copy the ELF binary to the new destination
|
||||||
|
if err := copyRegularFile(dest, src); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get shared libraries using "ldd" command
|
||||||
|
out, err := exec.Command("ldd", src).Output()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get linked libraries for %s: %v", src, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store unique parent directories of linked libraries
|
||||||
|
dirSet := make(map[string]struct{})
|
||||||
|
|
||||||
|
// Parse the output from ldd and relocate each library
|
||||||
|
for _, line := range strings.Split(string(out), "\n") {
|
||||||
|
parts := strings.Fields(line)
|
||||||
|
if len(parts) >= 3 && parts[1] == "=>" {
|
||||||
|
libraryPath := parts[2] // Linked library path
|
||||||
|
|
||||||
|
// Get the directory of the library and add to the set
|
||||||
|
libraryDir := filepath.Dir(libraryPath)
|
||||||
|
dirSet[libraryDir] = struct{}{}
|
||||||
|
|
||||||
|
// Recursively relocate the library
|
||||||
|
if err := RelocateFile(prefix, libraryPath); err != nil {
|
||||||
|
return fmt.Errorf("failed to relocate library %s: %v", libraryPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate the directories into a colon-separated rpath
|
||||||
|
var dirList []string
|
||||||
|
for dir := range dirSet {
|
||||||
|
dirList = append(dirList, filepath.Join(prefix, dir))
|
||||||
|
}
|
||||||
|
rpath := strings.Join(dirList, ":")
|
||||||
|
|
||||||
|
// Update the ELF binary's rpath using patchelf
|
||||||
|
if _, err := exec.Command("patchelf", "--set-rpath", rpath, dest).Output(); err != nil {
|
||||||
|
return fmt.Errorf("failed to update rpath for %s: %v", dest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Relocated binary %s to %s with rpath: %s\n", src, dest, rpath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RelocateFile relocates a file (regular file, directory, or symlink) to the target prefix.
|
||||||
|
func RelocateFile(prefix, src string) error {
|
||||||
|
if src != "/" {
|
||||||
|
parent := filepath.Dir(src)
|
||||||
|
// Recursively relocate parent directories
|
||||||
|
if err := RelocateFile(prefix, parent); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the destination path under the prefix
|
||||||
|
dst := filepath.Join(prefix, src)
|
||||||
|
|
||||||
|
// Check if the destination already exists
|
||||||
|
if _, err := os.Lstat(dst); os.IsNotExist(err) {
|
||||||
|
// Get source file information
|
||||||
|
sinfo, err := os.Lstat(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error processing %s: %v", src, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle symbolic links
|
||||||
|
if (sinfo.Mode() & os.ModeSymlink) != 0 {
|
||||||
|
linkTarget, err := os.Readlink(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle absolute and relative symlinks
|
||||||
|
if filepath.IsAbs(linkTarget) {
|
||||||
|
relocatedTarget := filepath.Join(prefix, linkTarget)
|
||||||
|
fmt.Printf("Creating absolute symlink %s as %s\n", relocatedTarget, dst)
|
||||||
|
if err := os.Symlink(relocatedTarget, dst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return RelocateFile(prefix, linkTarget)
|
||||||
|
} else {
|
||||||
|
parent := filepath.Dir(src)
|
||||||
|
target := filepath.Join(parent, linkTarget)
|
||||||
|
fmt.Printf("Creating relative symlink %s as %s\n", linkTarget, dst)
|
||||||
|
if err := os.Symlink(linkTarget, dst); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return RelocateFile(prefix, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle directories
|
||||||
|
if sinfo.IsDir() {
|
||||||
|
fmt.Printf("Creating directory %s\n", dst)
|
||||||
|
return os.MkdirAll(dst, sinfo.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore non-regular files such as sockets, devices, etc.
|
||||||
|
if !sinfo.Mode().IsRegular() {
|
||||||
|
fmt.Printf("Skipping non-regular file: %s\n", src)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle regular files (ELF or otherwise)
|
||||||
|
isElf, err := isDynamicELF(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if isElf {
|
||||||
|
return relocateELFBinary(prefix, dst, src)
|
||||||
|
} else {
|
||||||
|
return copyRegularFile(dst, src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow symlinks if the destination is a symlink
|
||||||
|
if dinfo, err := os.Lstat(dst); err == nil && (dinfo.Mode()&os.ModeSymlink) != 0 {
|
||||||
|
linkTarget, err := os.Readlink(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if filepath.IsAbs(linkTarget) {
|
||||||
|
return RelocateFile(prefix, linkTarget)
|
||||||
|
}
|
||||||
|
parent := filepath.Dir(src)
|
||||||
|
return RelocateFile(prefix, filepath.Join(parent, linkTarget))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue