├── .envrc ├── .gitignore ├── README.md ├── autopatchelf.go ├── go.mod ├── go.sum └── shell.nix /.envrc: -------------------------------------------------------------------------------- 1 | use nix 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | result 2 | .direnv/ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # autopatchelf 2 | 3 | ⚠️ This accompanying repository is a _work in progress_ at the moment. 4 | 5 | The goal of this repository is to accompany [nix-ld](https://github.com/Mic92/nix-ld) to help select the correct libraries for non-NixOS binaries. 6 | 7 | [![asciicast](https://asciinema.org/a/AcR3pbxVRt7leBuF6ds9e8epQ.svg)](https://asciinema.org/a/AcR3pbxVRt7leBuF6ds9e8epQ) 8 | 9 | ## Usage 10 | 11 | For instance to run it through a non-NixOS Ruby 12 | ```bash 13 | ❯ which ruby 14 | /usr/bin/ruby 15 | 16 | ❯ go run autopatchelf.go $(which ruby) 17 | ``` -------------------------------------------------------------------------------- /autopatchelf.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "debug/elf" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "os" 10 | "os/exec" 11 | "regexp" 12 | "strings" 13 | ) 14 | 15 | // Do a simple cursor selection using some program 16 | // https://junegunn.kr/2016/02/using-fzf-in-your-program 17 | func withFilter(command string, input func(in io.WriteCloser)) string { 18 | shell := os.Getenv("SHELL") 19 | if len(shell) == 0 { 20 | shell = "sh" 21 | } 22 | cmd := exec.Command(shell, "-c", command) 23 | cmd.Stderr = os.Stderr 24 | in, _ := cmd.StdinPipe() 25 | go func() { 26 | input(in) 27 | in.Close() 28 | }() 29 | result, _ := cmd.Output() 30 | return string(result) 31 | } 32 | 33 | func withFuzzyFilter(input func(in io.WriteCloser)) string { 34 | return withFilter("fzf --height=33%", input) 35 | } 36 | 37 | var LibraryRegex = regexp.MustCompile(`\w+`) 38 | 39 | func normalizeLibraryName(input string) string { 40 | return LibraryRegex.FindString(input) 41 | } 42 | 43 | func determineElfDependencies(filename string) { 44 | file, err := elf.Open(filename) 45 | if err != nil { 46 | fmt.Fprintf(os.Stderr, "%v is not an ELF binary: %v\n", filename, err) 47 | return 48 | } 49 | fmt.Fprintf(os.Stderr, "Parsing %v\n", filename) 50 | libraries, err := file.ImportedLibraries() 51 | if err != nil { 52 | fmt.Fprintf(os.Stderr, "Could not determine dynamic libraries for %v: %v\n", filename, err) 53 | return 54 | } 55 | 56 | fmt.Fprintf(os.Stderr, "Found the following libraries: %v\n", libraries) 57 | 58 | for _, library := range libraries { 59 | fmt.Fprintf(os.Stderr, "Determining /nix/store entry for %v\n", library) 60 | 61 | var nixLocateLine string 62 | 63 | out, err := exec.Command("nix-locate", "--at-root", "--whole-name", fmt.Sprintf("/lib/%v", library)).Output() 64 | if err != nil { 65 | fmt.Fprintf(os.Stderr, "Failure doing nix-locate for %v: %v\n", library, err) 66 | continue 67 | } 68 | 69 | // if we have no output, then an exact match was not found. 70 | // rely on the user to select 71 | if len(out) == 0 { 72 | fmt.Fprintf(os.Stderr, "Could not find an exact match for %v. Please select which to use.\n", library) 73 | normalizedLibrary := normalizeLibraryName(library) 74 | fmt.Fprintf(os.Stderr, "Using normalized library name: %v\n", normalizedLibrary) 75 | nixLocateLine = withFuzzyFilter(func(in io.WriteCloser) { 76 | // we no longer include --whole-name 77 | out, err := exec.Command("nix-locate", "--at-root", fmt.Sprintf("/lib/%v", normalizedLibrary)).Output() 78 | if err != nil { 79 | fmt.Fprintf(os.Stderr, "Failure doing nix-locate for %v\n: %v\n", normalizedLibrary, err) 80 | return 81 | } 82 | io.Copy(in, bytes.NewReader(out)) 83 | }) 84 | } else { 85 | nixLocateLine = withFuzzyFilter(func(in io.WriteCloser) { 86 | io.Copy(in, bytes.NewReader(out)) 87 | }) 88 | } 89 | 90 | fmt.Fprintf(os.Stderr, "%v has been resolved to:\n\t%v", library, nixLocateLine) 91 | 92 | // split the string by whitespace and the last field is the library. 93 | // ex. 94 | // glibc.out 0 s /nix/store/bpgdx6qqqzzi3szb0y3di3j3660f3wkj-glibc-2.31/lib/libc.so.6 95 | nixIndexOutput := strings.Fields(nixLocateLine) 96 | nixStorePath := nixIndexOutput[len(nixIndexOutput)-1] 97 | fmt.Fprintf(os.Stderr, "Realising %v\n", nixStorePath) 98 | 99 | cmd := exec.Command("nix-store", "--realise", nixStorePath) 100 | cmd.Stdout = os.Stdout 101 | cmd.Stderr = os.Stderr 102 | if err := cmd.Run(); err != nil { 103 | fmt.Fprintf(os.Stderr, "Failure doing nix-store realising for %v\n: %v\n", nixStorePath, err) 104 | return 105 | } 106 | } 107 | } 108 | 109 | func main() { 110 | flag.Parse() 111 | if flag.NArg() != 1 { 112 | fmt.Fprintln(os.Stderr, "You must provide a at least one binary.") 113 | } 114 | 115 | // process each argument if it's a binary 116 | for _, arg := range flag.Args() { 117 | determineElfDependencies(arg) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fzakaria/autopatchelf 2 | 3 | go 1.15 4 | 5 | require github.com/schollz/closestmatch v2.1.1-0.20190308193919-1fbe626be92e+incompatible // indirect 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/schollz/closestmatch v1.0.0 h1:Lp1xErBTptwH+nA8T6kntnroosYL2bsdnZKmqt2Gsug= 2 | github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= 3 | github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= 4 | github.com/schollz/closestmatch v2.1.1-0.20190308193919-1fbe626be92e+incompatible h1:zv6sQKHSe/0AUoAC7uCHFuX1FLFhW90s64dv0kETcaw= 5 | github.com/schollz/closestmatch v2.1.1-0.20190308193919-1fbe626be92e+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= 6 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { } }: 2 | with pkgs; 3 | with stdenv; 4 | mkShell { 5 | name = "autopatchelf-shell"; 6 | buildInputs = [go]; 7 | } --------------------------------------------------------------------------------