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