github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/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  		if err := follow(n, list); err != nil {
   114  			return nil, err
   115  		}
   116  	}
   117  	for _, n := range names {
   118  		r, err := os.Open(n)
   119  		if err != nil {
   120  			return nil, err
   121  		}
   122  		defer r.Close()
   123  		f, err := elf.NewFile(r)
   124  		if err != nil {
   125  			continue
   126  		}
   127  		s := f.Section(".interp")
   128  		var interp string
   129  		if s != nil {
   130  			// If there is an interpreter section, it should be
   131  			// an error if we can't read it.
   132  			i, err := s.Data()
   133  			if err != nil {
   134  				return nil, err
   135  			}
   136  			// Ignore #! interpreters
   137  			if len(i) > 1 && i[0] == '#' && i[1] == '!' {
   138  				continue
   139  			}
   140  			// annoyingly, s.Data() seems to return the null at the end and,
   141  			// weirdly, that seems to confuse the kernel. Truncate it.
   142  			interp = string(i[:len(i)-1])
   143  		}
   144  		if interp == "" {
   145  			if f.Type != elf.ET_DYN {
   146  				continue
   147  			}
   148  			// This is a shared library. Turns out you can run an interpreter with
   149  			// --list and this shared library as an argument. What interpreter
   150  			// do we use? Well, there's no way to know. You have to guess.
   151  			// I'm not sure why they could not just put an interp section in
   152  			// .so's but maybe that would cause trouble somewhere else.
   153  			interp, err = LdSo()
   154  			if err != nil {
   155  				return nil, err
   156  			}
   157  		}
   158  		// We could just append the interp but people
   159  		// expect to see that first.
   160  		if interps[interp] == nil {
   161  			err := follow(interp, interps)
   162  			if err != nil {
   163  				return nil, err
   164  			}
   165  		}
   166  		// oh boy. Now to run the interp and get more names.
   167  		n, err := runinterp(interp, n)
   168  		if err != nil {
   169  			return nil, err
   170  		}
   171  		for i := range n {
   172  			if err := follow(n[i], list); err != nil {
   173  				log.Fatalf("ldd: %v", err)
   174  			}
   175  		}
   176  	}
   177  
   178  	for i := range interps {
   179  		libs = append(libs, interps[i])
   180  	}
   181  
   182  	for i := range list {
   183  		libs = append(libs, list[i])
   184  	}
   185  
   186  	return libs, nil
   187  }
   188  
   189  func List(names []string) ([]string, error) {
   190  	var list []string
   191  	l, err := Ldd(names)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  	for i := range l {
   196  		list = append(list, l[i].FullName)
   197  	}
   198  	return list, nil
   199  }