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