github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/worker/runtime/rootfs_manager.go (about) 1 package runtime 2 3 import ( 4 "bufio" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strconv" 9 "strings" 10 11 "github.com/opencontainers/runtime-spec/specs-go" 12 ) 13 14 //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . RootfsManager 15 16 type InvalidUidError struct { 17 UID string 18 } 19 type InvalidGidError struct { 20 GID string 21 } 22 23 func (e InvalidUidError) Error() string { 24 return fmt.Sprintf("invalid uid: %s", e.UID) 25 } 26 27 func (e InvalidGidError) Error() string { 28 return fmt.Sprintf("invalid gid: %s", e.GID) 29 } 30 31 const ( 32 DefaultUid = 0 33 DefaultGid = 0 34 ) 35 36 // RootfsManager is responsible for mutating and reading from the rootfs of a 37 // container. 38 // 39 type RootfsManager interface { 40 // SetupCwd mutates the root filesystem to guarantee the presence of a 41 // directory to be used as `cwd`. 42 // 43 SetupCwd(rootfsPath string, cwd string) (err error) 44 45 // LookupUser scans the /etc/passwd file from the root filesystem for the 46 // UID and GID of the specified username. 47 // 48 LookupUser(rootfsPath string, username string) (specs.User, bool, error) 49 } 50 51 // RootfsManagerOpt defines a functional option that when applied, modifies the 52 // configuration of a rootfsManager. 53 // 54 type RootfsManagerOpt func(m *rootfsManager) 55 56 // WithMkdirAll configures the function to be used for creating directories 57 // recursively. 58 // 59 func WithMkdirAll(f func(path string, mode os.FileMode) error) RootfsManagerOpt { 60 return func(m *rootfsManager) { 61 m.mkdirall = f 62 } 63 } 64 65 type rootfsManager struct { 66 mkdirall func(name string, mode os.FileMode) error 67 } 68 69 var _ RootfsManager = (*rootfsManager)(nil) 70 71 // NewRootfsManager instantiates a rootfsManager 72 // 73 func NewRootfsManager(opts ...RootfsManagerOpt) *rootfsManager { 74 m := &rootfsManager{ 75 mkdirall: os.MkdirAll, 76 } 77 78 for _, opt := range opts { 79 opt(m) 80 } 81 82 return m 83 } 84 85 func (r rootfsManager) SetupCwd(rootfsPath string, cwd string) error { 86 abs := filepath.Join(rootfsPath, cwd) 87 88 _, err := os.Stat(abs) 89 if err == nil { // exists 90 return nil 91 } 92 93 err = r.mkdirall(abs, 0777) 94 if err != nil { 95 return fmt.Errorf("mkdir: %w", err) 96 } 97 98 return nil 99 } 100 101 // Mostly copied from Go's `os/user` package 102 // https://github.com/golang/go/blob/f296b7a6f045325a230f77e9bda1470b1270f817/src/os/user/lookup_unix.go#L35 103 func (r rootfsManager) LookupUser(rootfsPath string, username string) (specs.User, bool, error) { 104 path := filepath.Join(rootfsPath, "etc", "passwd") 105 file, err := os.Open(path) 106 107 // follow what runc and garden does - if /etc/passwd doesn't exist but the username is "root", 108 // assume it means 0:0 109 if os.IsNotExist(err) && username == "root" { 110 return specs.User{UID: DefaultUid, GID: DefaultGid}, true, nil 111 } 112 if err != nil { 113 return specs.User{}, false, err 114 } 115 defer file.Close() 116 117 bs := bufio.NewScanner(file) 118 for bs.Scan() { 119 line := bs.Text() 120 121 // There's no spec for /etc/passwd or /etc/group, but we try to follow 122 // the same rules as the glibc parser, which allows comments and blank 123 // space at the beginning of a line. 124 line = strings.TrimSpace(line) 125 if len(line) == 0 || line[0] == '#' { 126 continue 127 } 128 129 parts := strings.Split(line, ":") 130 if len(parts) != 7 { 131 continue 132 } 133 if parts[0] != username { 134 continue 135 } 136 var ( 137 uid int 138 gid int 139 ) 140 if uid, err = strconv.Atoi(parts[2]); err != nil { 141 return specs.User{}, false, InvalidUidError{UID: parts[2]} 142 } 143 if gid, err = strconv.Atoi(parts[3]); err != nil { 144 return specs.User{}, false, InvalidGidError{GID: parts[3]} 145 } 146 return specs.User{UID: uint32(uid), GID: uint32(gid)}, true, nil 147 } 148 return specs.User{}, false, bs.Err() 149 }