github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/ldd/ldd_unix.go (about)

     1  // Copyright 2009-2018 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build freebsd || linux
     6  // +build freebsd linux
     7  
     8  // ldd returns all the library dependencies of an executable.
     9  //
    10  // The way this is done on GNU-based systems is interesting. For each ELF, one
    11  // finds the .interp section. If there is no interpreter
    12  // there's not much to do.
    13  //
    14  // If there is an interpreter, we run it with the --list option and the file as
    15  // an argument. We need to parse the output.
    16  //
    17  // For all lines with =>  as the 2nd field, we take the 3rd field as a
    18  // dependency. The field may be a symlink.  Rather than stat the link and do
    19  // other such fooling around, we can do a readlink on it; if it fails, we just
    20  // need to add that file name; if it succeeds, we need to add that file name
    21  // and repeat with the next link in the chain. We can let the kernel do the
    22  // work of figuring what to do if and when we hit EMLINK.
    23  package ldd
    24  
    25  import (
    26  	"debug/elf"
    27  	"fmt"
    28  	"os"
    29  	"os/exec"
    30  	"path/filepath"
    31  	"strings"
    32  )
    33  
    34  // Follow starts at a pathname and adds it
    35  // to a map if it is not there.
    36  // If the pathname is a symlink, indicated by the Readlink
    37  // succeeding, links repeats and continues
    38  // for as long as the name is not found in the map.
    39  func follow(l string, names map[string]*FileInfo) error {
    40  	for {
    41  		if names[l] != nil {
    42  			return nil
    43  		}
    44  		i, err := os.Lstat(l)
    45  		if err != nil {
    46  			return fmt.Errorf("%v", err)
    47  		}
    48  
    49  		names[l] = &FileInfo{FullName: l, FileInfo: i}
    50  		if i.Mode().IsRegular() {
    51  			return nil
    52  		}
    53  		// If it's a symlink, the read works; if not, it fails.
    54  		// we can skip testing the type, since we still have to
    55  		// handle any error if it's a link.
    56  		next, err := os.Readlink(l)
    57  		if err != nil {
    58  			return err
    59  		}
    60  		// It may be a relative link, so we need to
    61  		// make it abs.
    62  		if filepath.IsAbs(next) {
    63  			l = next
    64  			continue
    65  		}
    66  		l = filepath.Join(filepath.Dir(l), next)
    67  	}
    68  }
    69  
    70  func parseinterp(input string) ([]string, error) {
    71  	var names []string
    72  	for _, p := range strings.Split(input, "\n") {
    73  		f := strings.Fields(p)
    74  		if len(f) < 3 {
    75  			continue
    76  		}
    77  		if f[1] != "=>" || len(f[2]) == 0 {
    78  			continue
    79  		}
    80  		if f[0] == f[2] {
    81  			continue
    82  		}
    83  		names = append(names, f[2])
    84  	}
    85  	return names, nil
    86  }
    87  
    88  // runinterp runs the interpreter with the --list switch
    89  // and the file as an argument. For each returned line
    90  // it looks for => as the second field, indicating a
    91  // real .so (as opposed to the .vdso or a string like
    92  // 'not a dynamic executable'.
    93  func runinterp(interp, file string) ([]string, error) {
    94  	o, err := exec.Command(interp, "--list", file).Output()
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  	return parseinterp(string(o))
    99  }
   100  
   101  type FileInfo struct {
   102  	FullName string
   103  	os.FileInfo
   104  }
   105  
   106  func GetInterp(file string) (string, error) {
   107  	r, err := os.Open(file)
   108  	if err != nil {
   109  		return "fail", err
   110  	}
   111  	defer r.Close()
   112  	f, err := elf.NewFile(r)
   113  	if err != nil {
   114  		return "", nil
   115  	}
   116  	s := f.Section(".interp")
   117  	var interp string
   118  	if s != nil {
   119  		// If there is an interpreter section, it should be
   120  		// an error if we can't read it.
   121  		i, err := s.Data()
   122  		if err != nil {
   123  			return "fail", err
   124  		}
   125  		// Ignore #! interpreters
   126  		if len(i) > 1 && i[0] == '#' && i[1] == '!' {
   127  			return "", nil
   128  		}
   129  		// annoyingly, s.Data() seems to return the null at the end and,
   130  		// weirdly, that seems to confuse the kernel. Truncate it.
   131  		interp = string(i[:len(i)-1])
   132  	}
   133  	if interp == "" {
   134  		if f.Type != elf.ET_DYN || f.Class == elf.ELFCLASSNONE {
   135  			return "", nil
   136  		}
   137  		bit64 := true
   138  		if f.Class != elf.ELFCLASS64 {
   139  			bit64 = false
   140  		}
   141  
   142  		// This is a shared library. Turns out you can run an
   143  		// interpreter with --list and this shared library as an
   144  		// argument. What interpreter do we use? Well, there's no way to
   145  		// know. You have to guess.  I'm not sure why they could not
   146  		// just put an interp section in .so's but maybe that would
   147  		// cause trouble somewhere else.
   148  		interp, err = LdSo(bit64)
   149  		if err != nil {
   150  			return "fail", err
   151  		}
   152  	}
   153  	return interp, nil
   154  }
   155  
   156  // Ldd returns a list of all library dependencies for a set of files.
   157  //
   158  // If a file has no dependencies, that is not an error. The only possible error
   159  // is if a file does not exist, or it says it has an interpreter but we can't
   160  // read it, or we are not able to run its interpreter.
   161  //
   162  // It's not an error for a file to not be an ELF.
   163  func Ldd(names []string) ([]*FileInfo, error) {
   164  	var (
   165  		list    = make(map[string]*FileInfo)
   166  		interps = make(map[string]*FileInfo)
   167  		libs    []*FileInfo
   168  	)
   169  	for _, n := range names {
   170  		if err := follow(n, list); err != nil {
   171  			return nil, err
   172  		}
   173  	}
   174  	for _, n := range names {
   175  		interp, err := GetInterp(n)
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  		if interp == "" {
   180  			continue
   181  		}
   182  		// We could just append the interp but people
   183  		// expect to see that first.
   184  		if interps[interp] == nil {
   185  			err := follow(interp, interps)
   186  			if err != nil {
   187  				return nil, err
   188  			}
   189  		}
   190  		// oh boy. Now to run the interp and get more names.
   191  		n, err := runinterp(interp, n)
   192  		if err != nil {
   193  			return nil, err
   194  		}
   195  		for _, soname := range n {
   196  			if err := follow(soname, list); err != nil {
   197  				return nil, err
   198  			}
   199  		}
   200  	}
   201  
   202  	for i := range interps {
   203  		libs = append(libs, interps[i])
   204  	}
   205  
   206  	for i := range list {
   207  		libs = append(libs, list[i])
   208  	}
   209  
   210  	return libs, nil
   211  }
   212  
   213  // List returns the dependency file paths of files in names.
   214  func List(names []string) ([]string, error) {
   215  	var list []string
   216  	l, err := Ldd(names)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  	for i := range l {
   221  		list = append(list, l[i].FullName)
   222  	}
   223  	return list, nil
   224  }