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