gopkg.in/hugelgupf/u-root.v9@v9.0.0-20180831063832-3f6f1057f09b/cmds/init/init.go (about)

     1  // Copyright 2012-2017 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  // assumptions
     6  // we've been booted into a ramfs with all this stuff unpacked and ready.
     7  // we don't need a loop device mount because it's all there.
     8  // So we run /go/bin/go build installcommand
     9  // and then exec /buildbin/sh
    10  
    11  package main
    12  
    13  import (
    14  	"flag"
    15  	"fmt"
    16  	"io/ioutil"
    17  	"log"
    18  	"os"
    19  	"os/exec"
    20  	"path"
    21  	"path/filepath"
    22  	"runtime"
    23  	"strings"
    24  	"syscall"
    25  
    26  	"github.com/u-root/u-root/pkg/uroot/util"
    27  )
    28  
    29  var (
    30  	verbose  = flag.Bool("v", false, "print all build commands")
    31  	test     = flag.Bool("test", false, "Test mode: don't try to set control tty")
    32  	debug    = func(string, ...interface{}) {}
    33  	osInitGo = func() {}
    34  	cmdList  = []string{
    35  		"/inito",
    36  
    37  		"/bbin/uinit",
    38  		"/bin/uinit",
    39  		"/buildbin/uinit",
    40  
    41  		"/bin/defaultsh",
    42  	}
    43  	cmdCount int
    44  	envs     []string
    45  )
    46  
    47  func main() {
    48  	flag.Parse()
    49  	log.Printf("Welcome to u-root")
    50  	util.Rootfs()
    51  
    52  	if *verbose {
    53  		debug = log.Printf
    54  	}
    55  
    56  	// Before entering an interactive shell, decrease the loglevel because
    57  	// spamming non-critical logs onto the shell frustrates users. The logs
    58  	// are still accessible through dmesg.
    59  	const sysLogActionConsoleLevel = 8
    60  	const kernNotice = 5 // Only messages more severe than "notice" are printed.
    61  	if _, _, err := syscall.Syscall(syscall.SYS_SYSLOG, sysLogActionConsoleLevel, 0, kernNotice); err != 0 {
    62  		log.Print("Could not set log level")
    63  	}
    64  
    65  	envs = os.Environ()
    66  	debug("envs %v", envs)
    67  
    68  	// install /env.
    69  	for _, e := range envs {
    70  		nv := strings.SplitN(e, "=", 2)
    71  		if len(nv) < 2 {
    72  			nv = append(nv, "")
    73  		}
    74  		n := filepath.Join("/env", nv[0])
    75  		if err := ioutil.WriteFile(n, []byte(nv[1]), 0666); err != nil {
    76  			log.Printf("%v: %v", n, err)
    77  		}
    78  	}
    79  
    80  	var profile string
    81  	// Some systems wipe out all the environment variables we so carefully craft.
    82  	// There is a way out -- we can put them into /etc/profile.d/uroot if we want.
    83  	// The PATH variable has to change, however.
    84  	epath := fmt.Sprintf("%v:%v:%v:%v", util.GoBin(), util.PATHHEAD, "$PATH", util.PATHTAIL)
    85  	for k, v := range util.Env {
    86  		// We're doing the hacky way for now. We can clean this up later.
    87  		if k == "PATH" {
    88  			profile += "export PATH=" + epath + "\n"
    89  		} else {
    90  			profile += "export " + k + "=" + v + "\n"
    91  		}
    92  	}
    93  
    94  	// The IFS lets us force a rehash every time we type a command, so that when we
    95  	// build uroot commands we don't keep rebuilding them.
    96  	profile += "IFS=`hash -r`\n"
    97  
    98  	// IF the profile is used, THEN when the user logs in they will need a
    99  	// private tmpfs. There's no good way to do this on linux. The closest
   100  	// we can get for now is to mount a tmpfs of /go/pkg/%s_%s :-( Same
   101  	// applies to ubin. Each user should have their own.
   102  	profile += fmt.Sprintf("sudo mount -t tmpfs none /go/pkg/%s_%s\n", runtime.GOOS, runtime.GOARCH)
   103  	profile += fmt.Sprintf("sudo mount -t tmpfs none /ubin\n")
   104  	profile += fmt.Sprintf("sudo mount -t tmpfs none /pkg\n")
   105  
   106  	// Now here's some good fun. We've set environment variables we want to see used.
   107  	// But on some systems the environment variable we create is completely ignored.
   108  	// Oh, is that you again, tinycore? Well.
   109  	// So we can save the day by writing the profile string to /etc/profile.d/uroot.sh
   110  	// mode, usually, 644.
   111  	// Only bother doing this is /etc/profile.d exists and is a directory.
   112  	if fi, err := os.Stat("/etc/profile.d"); err == nil && fi.IsDir() {
   113  		if err := ioutil.WriteFile("/etc/profile.d/uroot.sh", []byte(profile), 0644); err != nil {
   114  			log.Printf("Trying to write uroot profile failed: %v", err)
   115  		}
   116  	}
   117  
   118  	// Start background build.
   119  	if isBgBuildEnabled() {
   120  		go startBgBuild()
   121  	}
   122  
   123  	osInitGo()
   124  
   125  	for _, v := range cmdList {
   126  		if _, err := os.Stat(v); os.IsNotExist(err) {
   127  			continue
   128  		}
   129  
   130  		// I *love* special cases. Evaluate just the top-most symlink.
   131  		//
   132  		// In source mode, this would be a symlink like
   133  		// /buildbin/defaultsh -> /buildbin/elvish ->
   134  		// /buildbin/installcommand.
   135  		//
   136  		// To actually get the command to build, argv[0] has to end
   137  		// with /elvish, so we resolve one level of symlink.
   138  		if path.Base(v) == "defaultsh" {
   139  			s, err := os.Readlink(v)
   140  			if err == nil {
   141  				v = s
   142  			}
   143  		}
   144  
   145  		// inito is (optionally) created by the u-root command when the
   146  		// u-root initramfs is merged with an existing initramfs that
   147  		// has a /init. The name inito means "original /init" There may
   148  		// be an inito if we are building on an existing initramfs. All
   149  		// initos need their own pid space.
   150  		var cloneFlags uintptr
   151  		if v == "/inito" {
   152  			cloneFlags = uintptr(syscall.CLONE_NEWPID)
   153  		}
   154  
   155  		cmdCount++
   156  		cmd := exec.Command(v)
   157  		cmd.Env = envs
   158  		cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
   159  		if *test {
   160  			cmd.SysProcAttr = &syscall.SysProcAttr{Cloneflags: cloneFlags}
   161  		} else {
   162  			cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true, Cloneflags: cloneFlags}
   163  		}
   164  		debug("Run %v", cmd)
   165  		if err := cmd.Start(); err != nil {
   166  			log.Printf("Error starting %v: %v", v, err)
   167  			continue
   168  		}
   169  		for {
   170  			var s syscall.WaitStatus
   171  			var r syscall.Rusage
   172  			if p, err := syscall.Wait4(-1, &s, 0, &r); p == cmd.Process.Pid {
   173  				debug("Shell exited, exit status %d", s.ExitStatus())
   174  				break
   175  			} else if p != -1 {
   176  				debug("Reaped PID %d, exit status %d", p, s.ExitStatus())
   177  			} else {
   178  				debug("Error from Wait4 for orphaned child: %v", err)
   179  				break
   180  			}
   181  		}
   182  		if err := cmd.Process.Release(); err != nil {
   183  			log.Printf("Error releasing %v:%v", v, err)
   184  		}
   185  	}
   186  	if cmdCount == 0 {
   187  		log.Printf("init: No suitable executable found in %+v", cmdList)
   188  	}
   189  
   190  	// We need to reap all children before exiting.
   191  	log.Printf("init: Waiting for orphaned children")
   192  	for {
   193  		var s syscall.WaitStatus
   194  		var r syscall.Rusage
   195  		p, err := syscall.Wait4(-1, &s, 0, &r)
   196  		if p == -1 {
   197  			break
   198  		}
   199  		log.Printf("%v: exited with %v, status %v, rusage %v", p, err, s, r)
   200  	}
   201  	log.Printf("init: All commands exited")
   202  	log.Printf("init: Syncing filesystems")
   203  	syscall.Sync()
   204  	log.Printf("init: Exiting...")
   205  }