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