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