github.com/ratrocket/u-root@v0.0.0-20180201221235-1cf9f48ee2cf/cmds/chroot/chroot.go (about)

     1  // +build !windows !plan9
     2  
     3  package main
     4  
     5  import (
     6  	"bytes"
     7  	"flag"
     8  	"fmt"
     9  	"log"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  	"syscall"
    16  )
    17  
    18  type userSpec struct {
    19  	uid uint32
    20  	gid uint32
    21  }
    22  
    23  func (u *userSpec) Set(s string) error {
    24  	var err error
    25  	userspecSplit := strings.Split(s, ":")
    26  	if len(userspecSplit) != 2 || userspecSplit[1] == "" {
    27  		return fmt.Errorf("Expected user spec flag to be \":\" separated values received %s", s)
    28  	}
    29  
    30  	u.uid, err = stringToUint32(userspecSplit[0])
    31  	if err != nil {
    32  		return err
    33  	}
    34  
    35  	u.gid, err = stringToUint32(userspecSplit[1])
    36  	if err != nil {
    37  		return err
    38  	}
    39  
    40  	return nil
    41  }
    42  
    43  func (u *userSpec) Get() interface{} {
    44  	return *u
    45  }
    46  
    47  func (u *userSpec) String() string {
    48  	return fmt.Sprintf("%d:%d", u.uid, u.gid)
    49  }
    50  
    51  func defaultUser() userSpec {
    52  	return userSpec{
    53  		uid: uint32(os.Getuid()),
    54  		gid: uint32(os.Getgid()),
    55  	}
    56  }
    57  
    58  type groupsSpec struct {
    59  	groups []uint32
    60  }
    61  
    62  func (g *groupsSpec) Set(s string) error {
    63  	groupStrs := strings.Split(s, ",")
    64  	g.groups = make([]uint32, len(groupStrs))
    65  
    66  	for index, group := range groupStrs {
    67  
    68  		gid, err := stringToUint32(group)
    69  		if err != nil {
    70  			return err
    71  		}
    72  
    73  		g.groups[index] = gid
    74  	}
    75  
    76  	return nil
    77  }
    78  
    79  func (g *groupsSpec) Get() interface{} {
    80  	return *g
    81  }
    82  
    83  func (g *groupsSpec) String() string {
    84  	var buffer bytes.Buffer
    85  
    86  	for index, gid := range g.groups {
    87  		buffer.WriteString(fmt.Sprint(gid))
    88  		if index < len(g.groups)-1 {
    89  			buffer.WriteString(",")
    90  		}
    91  	}
    92  
    93  	return buffer.String()
    94  }
    95  
    96  var (
    97  	skipchdirFlag bool
    98  	user          userSpec   = defaultUser()
    99  	groups        groupsSpec = groupsSpec{}
   100  )
   101  
   102  func init() {
   103  	flag.Var(&user, "u", "specify user and group (ID only) as USER:GROUP")
   104  	flag.Var(&groups, "g", "specify supplementary group ids as g1,g2,..,gN")
   105  	flag.BoolVar(&skipchdirFlag, "s", false, fmt.Sprint("Use this option to not change",
   106  		"the working directory to / after changing the root directory to newroot, i.e., ",
   107  		"inside the chroot. This option is only permitted when newroot is the old / directory."))
   108  }
   109  
   110  func stringToUint32(str string) (uint32, error) {
   111  	ret, err := strconv.ParseUint(str, 10, 32)
   112  	if err != nil {
   113  		return 0, err
   114  	}
   115  	return uint32(ret), nil
   116  }
   117  
   118  func parseCommand(args []string) []string {
   119  	if len(args) > 1 {
   120  		return args[1:]
   121  	}
   122  	return []string{"/bin/sh", "-i"}
   123  }
   124  
   125  func parseRoot(args []string) (root string, err error) {
   126  	if len(args) < 1 {
   127  		return "", fmt.Errorf("Missing operand")
   128  	}
   129  
   130  	return filepath.Abs(args[0])
   131  }
   132  
   133  func isRoot(dir string) (bool, error) {
   134  	realPath, err := filepath.EvalSymlinks(dir)
   135  	if err != nil {
   136  		return false, err
   137  	}
   138  	absolutePath, err := filepath.Abs(realPath)
   139  	if err != nil {
   140  		return false, err
   141  	}
   142  	if absolutePath == "/" {
   143  		return true, nil
   144  	}
   145  	return false, nil
   146  }
   147  
   148  func main() {
   149  	var (
   150  		newRoot   string
   151  		isOldroot bool
   152  		err       error
   153  	)
   154  
   155  	flag.Parse()
   156  
   157  	if flag.NFlag() == 0 && flag.NArg() == 0 {
   158  		flag.PrintDefaults()
   159  		os.Exit(1)
   160  	}
   161  
   162  	newRoot, err = parseRoot(flag.Args())
   163  	if err != nil {
   164  		log.Fatal(err)
   165  	}
   166  	isOldroot, err = isRoot(newRoot)
   167  	if err != nil {
   168  		log.Fatal(err)
   169  	}
   170  
   171  	if !skipchdirFlag {
   172  		err = os.Chdir(newRoot)
   173  		if err != nil {
   174  			log.Fatal(err)
   175  		}
   176  	} else if !isOldroot {
   177  		log.Fatal("The -s option is only permitted when newroot is the old / directory")
   178  	}
   179  
   180  	argv := parseCommand(flag.Args())
   181  
   182  	cmd := exec.Command(argv[0], argv[1:]...)
   183  
   184  	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
   185  	cmd.SysProcAttr = &syscall.SysProcAttr{
   186  		Credential: &syscall.Credential{
   187  			Uid:    user.uid,
   188  			Gid:    user.gid,
   189  			Groups: groups.groups,
   190  		},
   191  		Chroot: newRoot,
   192  	}
   193  
   194  	if err = cmd.Run(); err != nil {
   195  		log.Fatal(err)
   196  	}
   197  }