github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/cmds/exp/pox/pox.go (about)

     1  // Copyright 2012-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  // pox builds a portable executable as a squashfs image.
     6  // It is intended to create files compatible with tinycore
     7  // tcz files. One of more of the files can be programs
     8  // but that is not required.
     9  // This could have been a simple program but mksquashfs does not
    10  // preserve path information.
    11  // Yeah.
    12  //
    13  // Synopsis:
    14  //     pox [-[-debug]|d] [-[-file]|f tcz-file] -[-create]|c FILE [...FILE]
    15  //     pox [-[-debug]|d] [-[-file]|f tcz-file] -[-run|r] PROGRAM -- [...ARGS]
    16  //
    17  // Description:
    18  //     pox makes portable executables in squashfs format compatible with
    19  //     tcz format. We don't build in the execution code, rather, we set it
    20  //     up so we can use the command itself. You can either create the TCZ image
    21  //     or run a command within an image that was previously created.
    22  //
    23  // Options:
    24  //     debug|d: verbose
    25  //     file|f file: file name (default /tmp/pox.tcz)
    26  //     run|r: Runs the first non-flag argument to pox.  Remaining arguments will
    27  //            be passed to the program.  Use '--' before any flag-like arguments
    28  //            to prevent pox from interpretting the flags.
    29  //     create|c: create the TCZ file.
    30  //     zip|z: Use zip and unzip instead of a loopback mounted squashfs.  Be sure
    31  //            to use -z for both creation and running, or not at all.
    32  //     Exactly one of -c and -r must be used on the same command.
    33  //
    34  // Example:
    35  //	$ pox -c /bin/bash /bin/cat /bin/ls /etc/hosts
    36  //	Will build a squashfs, which will be /tmp/pox.tcz
    37  //
    38  //	$ sudo pox -r /bin/bash
    39  //	Will drop you into the /tmp/pox.tcz running bash
    40  //	You can use ls and cat on /etc/hosts.
    41  //
    42  //	Simpler example, with arguments:
    43  //	$ sudo pox -r /bin/ls -- -la
    44  //	will run `ls -la` and exit.
    45  //
    46  //	$ sudo pox -r -- /bin/ls -la
    47  //	Syntactically easier: the program name can come after '--'
    48  //
    49  // Notes:
    50  // - When running a pox, you likely need sudo to chroot
    51  //
    52  // - Binaries run out of a chroot often need files you are unaware of.  For
    53  // instance, if bash can't find terminfo files, it won't know to handle
    54  // backspaces properly.  (They occur, but are not shown).  To fix this, pass pox
    55  // all of the files you need.  For bash: `find /lib/terminfo -type f`.
    56  //
    57  // Other programs rely on help functions, such as '/bin/man'.  If your program
    58  // has built-in help commands that trigger man pages, e.g. "git help foo",
    59  // you'll want to include /bin/man too.  But you'll also need everything that
    60  // man uses, such as /etc/manpath.config.  My advice: skip it.
    61  //
    62  // - When adding all files in a directory, the easiest thing to do is:
    63  // `find $DIR -type f`  (Note the ticks: this is a bash command execution).
    64  //
    65  // - When creating a pox with an executable with shared libraries that are not
    66  // installed on your system, such as for a project installed in your home
    67  // directory, run pox from the installation prefix directory, such that the
    68  // lib/ and bin/ are in pox's working directory.  Pox will strip its working
    69  // directory from the paths of the files it builds.  Having bin/ in the root of
    70  // the pox file helps with PATH lookups, and not having the full path from your
    71  // machine in the pox file makes it easier to extract a pox file to /usr/local/.
    72  //
    73  // - Consider adding a --extract | -x option to install to the host.  One issue
    74  // would be how to handle collisions, e.g. libc.  Your app may not like the libc
    75  // on the system you run on.
    76  package main
    77  
    78  import (
    79  	"fmt"
    80  	"io"
    81  	"io/ioutil"
    82  	"log"
    83  	"os"
    84  	"os/exec"
    85  	"path/filepath"
    86  	"strings"
    87  	"syscall"
    88  
    89  	flag "github.com/spf13/pflag"
    90  	"github.com/u-root/u-root/pkg/ldd"
    91  	"github.com/u-root/u-root/pkg/loop"
    92  	"github.com/u-root/u-root/pkg/mount"
    93  )
    94  
    95  const usage = "pox [-[-debug]|d] -[-run|r] | -[-create]|c  [-[-file]|f tcz-file] file [...file]"
    96  
    97  var (
    98  	debug  = flag.BoolP("debug", "d", false, "enable debug prints")
    99  	run    = flag.BoolP("run", "r", false, "Run the first file argument")
   100  	create = flag.BoolP("create", "c", false, "create it")
   101  	zip    = flag.BoolP("zip", "z", false, "use zip instead of squashfs")
   102  	file   = flag.StringP("output", "f", "/tmp/pox.tcz", "Output file")
   103  	v      = func(string, ...interface{}) {}
   104  )
   105  
   106  // When chrooting, programs often want to access various system directories:
   107  var chrootMounts = []struct {
   108  	source string
   109  	target string
   110  	fstype string
   111  	flags  uintptr
   112  	data   string
   113  	perm   os.FileMode // for target in the chroot
   114  }{
   115  	// mount --bind /sys /chroot/sys
   116  	{"/sys", "/sys", "", mount.MS_BIND, "", 0555},
   117  	// mount -t proc /proc /chroot/proc
   118  	{"/proc", "/proc", "proc", 0, "", 0555},
   119  	// mount --bind /dev /chroot/dev
   120  	{"/dev", "/dev", "", mount.MS_BIND, "", 0755},
   121  }
   122  
   123  func poxCreate(names []string) error {
   124  	if len(names) == 0 {
   125  		return fmt.Errorf(usage)
   126  	}
   127  	l, err := ldd.Ldd(names)
   128  	if err != nil {
   129  		var stderr []byte
   130  		if eerr, ok := err.(*exec.ExitError); ok {
   131  			stderr = eerr.Stderr
   132  		}
   133  		return fmt.Errorf("Running ldd on %v: %v %s", names,
   134  			err, stderr)
   135  	}
   136  
   137  	for _, dep := range l {
   138  		v("%s", dep.FullName)
   139  		names = append(names, dep.FullName)
   140  	}
   141  	// Now we need to make a template file hierarchy and put
   142  	// the stuff we want in there.
   143  	dir, err := ioutil.TempDir("", "pox")
   144  	if err != nil {
   145  		return err
   146  	}
   147  	if !*debug {
   148  		defer os.RemoveAll(dir)
   149  	}
   150  	pwd, err := os.Getwd()
   151  	if err != nil {
   152  		return err
   153  	}
   154  	// We don't use defer() here to close files as
   155  	// that can cause open failures with a large enough number.
   156  	for _, f := range names {
   157  		v("Process %v", f)
   158  		fi, err := os.Stat(f)
   159  		if err != nil {
   160  			return err
   161  		}
   162  		in, err := os.Open(f)
   163  		if err != nil {
   164  			return err
   165  		}
   166  		f = strings.TrimPrefix(f, pwd)
   167  		dfile := filepath.Join(dir, f)
   168  		d := filepath.Dir(dfile)
   169  		if err := os.MkdirAll(d, 0755); err != nil {
   170  			in.Close()
   171  			return err
   172  		}
   173  		out, err := os.OpenFile(dfile, os.O_WRONLY|os.O_CREATE,
   174  			fi.Mode().Perm())
   175  		if err != nil {
   176  			in.Close()
   177  			return err
   178  		}
   179  		_, err = io.Copy(out, in)
   180  		in.Close()
   181  		out.Close()
   182  		if err != nil {
   183  			return err
   184  		}
   185  
   186  	}
   187  	for _, m := range chrootMounts {
   188  		if err := os.MkdirAll(filepath.Join(dir, m.target), m.perm); err != nil {
   189  			return err
   190  		}
   191  	}
   192  	err = os.Remove(*file)
   193  	if err != nil && !os.IsNotExist(err) {
   194  		return err
   195  	}
   196  	var c *exec.Cmd
   197  	if *zip {
   198  		fileAbs, err := filepath.Abs(*file)
   199  		if err != nil {
   200  			return err
   201  		}
   202  		c = exec.Command("zip", "-r", fileAbs, ".")
   203  		c.Dir = dir
   204  	} else {
   205  		c = exec.Command("mksquashfs", dir, *file, "-noappend")
   206  	}
   207  	o, err := c.CombinedOutput()
   208  	v("%v", string(o))
   209  	if err != nil {
   210  		return fmt.Errorf("%v: %v: %v", c.Args, string(o), err)
   211  	}
   212  	v("Done, your pox is in %v", *file)
   213  
   214  	return nil
   215  }
   216  
   217  func poxRun(args []string) error {
   218  	if len(args) == 0 {
   219  		return fmt.Errorf(usage)
   220  	}
   221  	dir, err := ioutil.TempDir("", "pox")
   222  	if err != nil {
   223  		return err
   224  	}
   225  	if !*debug {
   226  		defer os.RemoveAll(dir)
   227  	}
   228  
   229  	if *zip {
   230  		c := exec.Command("unzip", *file, "-d", dir)
   231  		o, err := c.CombinedOutput()
   232  		v("%v", string(o))
   233  		if err != nil {
   234  			return fmt.Errorf("%v: %v: %v", c.Args, string(o), err)
   235  		}
   236  	} else {
   237  		lo, err := loop.New(*file, "squashfs", "")
   238  		if err != nil {
   239  			return err
   240  		}
   241  		defer lo.Free() //nolint:errcheck
   242  
   243  		mountPoint, err := lo.Mount(dir, 0)
   244  		if err != nil {
   245  			return err
   246  		}
   247  		defer mountPoint.Unmount(0) //nolint:errcheck
   248  	}
   249  	for _, m := range chrootMounts {
   250  		mp, err := mount.Mount(m.source, filepath.Join(dir, m.target),
   251  			m.fstype, m.data, m.flags)
   252  		if err != nil {
   253  			return err
   254  		}
   255  		defer mp.Unmount(0) //nolint:errcheck
   256  	}
   257  
   258  	// If you pass Command a path with no slashes, it'll use PATH from the
   259  	// parent to resolve the path to exec.  Once we chroot, whatever path we
   260  	// picked is undoubtably wrong.  Let's help them out: if they give us a
   261  	// program with no /, let's look in /bin/.  If they want the root of the
   262  	// chroot, they can use "./"
   263  	if filepath.Base(args[0]) == args[0] {
   264  		args[0] = filepath.Join(string(os.PathSeparator), "bin", args[0])
   265  	}
   266  	c := exec.Command(args[0], args[1:]...)
   267  	c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr
   268  	c.SysProcAttr = &syscall.SysProcAttr{
   269  		Chroot: dir,
   270  	}
   271  	c.Env = append(os.Environ(), "PWD=.")
   272  
   273  	if err = c.Run(); err != nil {
   274  		v("pox command exited with: %v", err)
   275  	}
   276  
   277  	return nil
   278  }
   279  
   280  func pox() error {
   281  	flag.Parse()
   282  	if *debug {
   283  		v = log.Printf
   284  	}
   285  	if (*create && *run) || (!*create && !*run) {
   286  		return fmt.Errorf(usage)
   287  	}
   288  	if *create {
   289  		return poxCreate(flag.Args())
   290  	}
   291  	if *run {
   292  		return poxRun(flag.Args())
   293  	}
   294  	return fmt.Errorf(usage)
   295  }
   296  
   297  func main() {
   298  	if err := pox(); err != nil {
   299  		log.Fatal(err)
   300  	}
   301  }