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 }