github.com/ttys3/engine@v17.12.1-ce-rc2+incompatible/pkg/idtools/idtools_unix.go (about) 1 // +build !windows 2 3 package idtools 4 5 import ( 6 "bytes" 7 "fmt" 8 "io" 9 "os" 10 "path/filepath" 11 "strings" 12 "sync" 13 "syscall" 14 15 "github.com/docker/docker/pkg/system" 16 "github.com/opencontainers/runc/libcontainer/user" 17 ) 18 19 var ( 20 entOnce sync.Once 21 getentCmd string 22 ) 23 24 func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error { 25 // make an array containing the original path asked for, plus (for mkAll == true) 26 // all path components leading up to the complete path that don't exist before we MkdirAll 27 // so that we can chown all of them properly at the end. If chownExisting is false, we won't 28 // chown the full directory path if it exists 29 var paths []string 30 31 stat, err := system.Stat(path) 32 if err == nil { 33 if !stat.IsDir() { 34 return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} 35 } 36 if !chownExisting { 37 return nil 38 } 39 40 // short-circuit--we were called with an existing directory and chown was requested 41 return lazyChown(path, ownerUID, ownerGID, stat) 42 } 43 44 if os.IsNotExist(err) { 45 paths = []string{path} 46 } 47 48 if mkAll { 49 // walk back to "/" looking for directories which do not exist 50 // and add them to the paths array for chown after creation 51 dirPath := path 52 for { 53 dirPath = filepath.Dir(dirPath) 54 if dirPath == "/" { 55 break 56 } 57 if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) { 58 paths = append(paths, dirPath) 59 } 60 } 61 if err := system.MkdirAll(path, mode, ""); err != nil { 62 return err 63 } 64 } else { 65 if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) { 66 return err 67 } 68 } 69 // even if it existed, we will chown the requested path + any subpaths that 70 // didn't exist when we called MkdirAll 71 for _, pathComponent := range paths { 72 if err := lazyChown(pathComponent, ownerUID, ownerGID, nil); err != nil { 73 return err 74 } 75 } 76 return nil 77 } 78 79 // CanAccess takes a valid (existing) directory and a uid, gid pair and determines 80 // if that uid, gid pair has access (execute bit) to the directory 81 func CanAccess(path string, pair IDPair) bool { 82 statInfo, err := system.Stat(path) 83 if err != nil { 84 return false 85 } 86 fileMode := os.FileMode(statInfo.Mode()) 87 permBits := fileMode.Perm() 88 return accessible(statInfo.UID() == uint32(pair.UID), 89 statInfo.GID() == uint32(pair.GID), permBits) 90 } 91 92 func accessible(isOwner, isGroup bool, perms os.FileMode) bool { 93 if isOwner && (perms&0100 == 0100) { 94 return true 95 } 96 if isGroup && (perms&0010 == 0010) { 97 return true 98 } 99 if perms&0001 == 0001 { 100 return true 101 } 102 return false 103 } 104 105 // LookupUser uses traditional local system files lookup (from libcontainer/user) on a username, 106 // followed by a call to `getent` for supporting host configured non-files passwd and group dbs 107 func LookupUser(username string) (user.User, error) { 108 // first try a local system files lookup using existing capabilities 109 usr, err := user.LookupUser(username) 110 if err == nil { 111 return usr, nil 112 } 113 // local files lookup failed; attempt to call `getent` to query configured passwd dbs 114 usr, err = getentUser(fmt.Sprintf("%s %s", "passwd", username)) 115 if err != nil { 116 return user.User{}, err 117 } 118 return usr, nil 119 } 120 121 // LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid, 122 // followed by a call to `getent` for supporting host configured non-files passwd and group dbs 123 func LookupUID(uid int) (user.User, error) { 124 // first try a local system files lookup using existing capabilities 125 usr, err := user.LookupUid(uid) 126 if err == nil { 127 return usr, nil 128 } 129 // local files lookup failed; attempt to call `getent` to query configured passwd dbs 130 return getentUser(fmt.Sprintf("%s %d", "passwd", uid)) 131 } 132 133 func getentUser(args string) (user.User, error) { 134 reader, err := callGetent(args) 135 if err != nil { 136 return user.User{}, err 137 } 138 users, err := user.ParsePasswd(reader) 139 if err != nil { 140 return user.User{}, err 141 } 142 if len(users) == 0 { 143 return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", strings.Split(args, " ")[1]) 144 } 145 return users[0], nil 146 } 147 148 // LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name, 149 // followed by a call to `getent` for supporting host configured non-files passwd and group dbs 150 func LookupGroup(groupname string) (user.Group, error) { 151 // first try a local system files lookup using existing capabilities 152 group, err := user.LookupGroup(groupname) 153 if err == nil { 154 return group, nil 155 } 156 // local files lookup failed; attempt to call `getent` to query configured group dbs 157 return getentGroup(fmt.Sprintf("%s %s", "group", groupname)) 158 } 159 160 // LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID, 161 // followed by a call to `getent` for supporting host configured non-files passwd and group dbs 162 func LookupGID(gid int) (user.Group, error) { 163 // first try a local system files lookup using existing capabilities 164 group, err := user.LookupGid(gid) 165 if err == nil { 166 return group, nil 167 } 168 // local files lookup failed; attempt to call `getent` to query configured group dbs 169 return getentGroup(fmt.Sprintf("%s %d", "group", gid)) 170 } 171 172 func getentGroup(args string) (user.Group, error) { 173 reader, err := callGetent(args) 174 if err != nil { 175 return user.Group{}, err 176 } 177 groups, err := user.ParseGroup(reader) 178 if err != nil { 179 return user.Group{}, err 180 } 181 if len(groups) == 0 { 182 return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", strings.Split(args, " ")[1]) 183 } 184 return groups[0], nil 185 } 186 187 func callGetent(args string) (io.Reader, error) { 188 entOnce.Do(func() { getentCmd, _ = resolveBinary("getent") }) 189 // if no `getent` command on host, can't do anything else 190 if getentCmd == "" { 191 return nil, fmt.Errorf("") 192 } 193 out, err := execCmd(getentCmd, args) 194 if err != nil { 195 exitCode, errC := system.GetExitCode(err) 196 if errC != nil { 197 return nil, err 198 } 199 switch exitCode { 200 case 1: 201 return nil, fmt.Errorf("getent reported invalid parameters/database unknown") 202 case 2: 203 terms := strings.Split(args, " ") 204 return nil, fmt.Errorf("getent unable to find entry %q in %s database", terms[1], terms[0]) 205 case 3: 206 return nil, fmt.Errorf("getent database doesn't support enumeration") 207 default: 208 return nil, err 209 } 210 211 } 212 return bytes.NewReader(out), nil 213 } 214 215 // lazyChown performs a chown only if the uid/gid don't match what's requested 216 // Normally a Chown is a no-op if uid/gid match, but in some cases this can still cause an error, e.g. if the 217 // dir is on an NFS share, so don't call chown unless we absolutely must. 218 func lazyChown(p string, uid, gid int, stat *system.StatT) error { 219 if stat == nil { 220 var err error 221 stat, err = system.Stat(p) 222 if err != nil { 223 return err 224 } 225 } 226 if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) { 227 return nil 228 } 229 return os.Chown(p, uid, gid) 230 }