github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/pkg/idtools/idtools.go (about) 1 package idtools 2 3 import ( 4 "bufio" 5 "fmt" 6 "os" 7 "sort" 8 "strconv" 9 "strings" 10 ) 11 12 // IDMap contains a single entry for user namespace range remapping. An array 13 // of IDMap entries represents the structure that will be provided to the Linux 14 // kernel for creating a user namespace. 15 type IDMap struct { 16 ContainerID int `json:"container_id"` 17 HostID int `json:"host_id"` 18 Size int `json:"size"` 19 } 20 21 type subIDRange struct { 22 Start int 23 Length int 24 } 25 26 type ranges []subIDRange 27 28 func (e ranges) Len() int { return len(e) } 29 func (e ranges) Swap(i, j int) { e[i], e[j] = e[j], e[i] } 30 func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start } 31 32 const ( 33 subuidFileName string = "/etc/subuid" 34 subgidFileName string = "/etc/subgid" 35 ) 36 37 // MkdirAllAs creates a directory (include any along the path) and then modifies 38 // ownership to the requested uid/gid. If the directory already exists, this 39 // function will still change ownership to the requested uid/gid pair. 40 // Deprecated: Use MkdirAllAndChown 41 func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { 42 return mkdirAs(path, mode, ownerUID, ownerGID, true, true) 43 } 44 45 // MkdirAs creates a directory and then modifies ownership to the requested uid/gid. 46 // If the directory already exists, this function still changes ownership 47 // Deprecated: Use MkdirAndChown with a IDPair 48 func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { 49 return mkdirAs(path, mode, ownerUID, ownerGID, false, true) 50 } 51 52 // MkdirAllAndChown creates a directory (include any along the path) and then modifies 53 // ownership to the requested uid/gid. If the directory already exists, this 54 // function will still change ownership to the requested uid/gid pair. 55 func MkdirAllAndChown(path string, mode os.FileMode, ids IDPair) error { 56 return mkdirAs(path, mode, ids.UID, ids.GID, true, true) 57 } 58 59 // MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid. 60 // If the directory already exists, this function still changes ownership 61 func MkdirAndChown(path string, mode os.FileMode, ids IDPair) error { 62 return mkdirAs(path, mode, ids.UID, ids.GID, false, true) 63 } 64 65 // MkdirAllAndChownNew creates a directory (include any along the path) and then modifies 66 // ownership ONLY of newly created directories to the requested uid/gid. If the 67 // directories along the path exist, no change of ownership will be performed 68 func MkdirAllAndChownNew(path string, mode os.FileMode, ids IDPair) error { 69 return mkdirAs(path, mode, ids.UID, ids.GID, true, false) 70 } 71 72 // GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps. 73 // If the maps are empty, then the root uid/gid will default to "real" 0/0 74 func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) { 75 uid, err := toHost(0, uidMap) 76 if err != nil { 77 return -1, -1, err 78 } 79 gid, err := toHost(0, gidMap) 80 if err != nil { 81 return -1, -1, err 82 } 83 return uid, gid, nil 84 } 85 86 // toContainer takes an id mapping, and uses it to translate a 87 // host ID to the remapped ID. If no map is provided, then the translation 88 // assumes a 1-to-1 mapping and returns the passed in id 89 func toContainer(hostID int, idMap []IDMap) (int, error) { 90 if idMap == nil { 91 return hostID, nil 92 } 93 for _, m := range idMap { 94 if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) { 95 contID := m.ContainerID + (hostID - m.HostID) 96 return contID, nil 97 } 98 } 99 return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID) 100 } 101 102 // toHost takes an id mapping and a remapped ID, and translates the 103 // ID to the mapped host ID. If no map is provided, then the translation 104 // assumes a 1-to-1 mapping and returns the passed in id # 105 func toHost(contID int, idMap []IDMap) (int, error) { 106 if idMap == nil { 107 return contID, nil 108 } 109 for _, m := range idMap { 110 if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) { 111 hostID := m.HostID + (contID - m.ContainerID) 112 return hostID, nil 113 } 114 } 115 return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID) 116 } 117 118 // IDPair is a UID and GID pair 119 type IDPair struct { 120 UID int 121 GID int 122 } 123 124 // IDMappings contains a mappings of UIDs and GIDs 125 type IDMappings struct { 126 uids []IDMap 127 gids []IDMap 128 } 129 130 // NewIDMappings takes a requested user and group name and 131 // using the data from /etc/sub{uid,gid} ranges, creates the 132 // proper uid and gid remapping ranges for that user/group pair 133 func NewIDMappings(username, groupname string) (*IDMappings, error) { 134 subuidRanges, err := parseSubuid(username) 135 if err != nil { 136 return nil, err 137 } 138 subgidRanges, err := parseSubgid(groupname) 139 if err != nil { 140 return nil, err 141 } 142 if len(subuidRanges) == 0 { 143 return nil, fmt.Errorf("No subuid ranges found for user %q", username) 144 } 145 if len(subgidRanges) == 0 { 146 return nil, fmt.Errorf("No subgid ranges found for group %q", groupname) 147 } 148 149 return &IDMappings{ 150 uids: createIDMap(subuidRanges), 151 gids: createIDMap(subgidRanges), 152 }, nil 153 } 154 155 // NewIDMappingsFromMaps creates a new mapping from two slices 156 // Deprecated: this is a temporary shim while transitioning to IDMapping 157 func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IDMappings { 158 return &IDMappings{uids: uids, gids: gids} 159 } 160 161 // RootPair returns a uid and gid pair for the root user. The error is ignored 162 // because a root user always exists, and the defaults are correct when the uid 163 // and gid maps are empty. 164 func (i *IDMappings) RootPair() IDPair { 165 uid, gid, _ := GetRootUIDGID(i.uids, i.gids) 166 return IDPair{UID: uid, GID: gid} 167 } 168 169 // ToHost returns the host UID and GID for the container uid, gid. 170 // Remapping is only performed if the ids aren't already the remapped root ids 171 func (i *IDMappings) ToHost(pair IDPair) (IDPair, error) { 172 var err error 173 target := i.RootPair() 174 175 if pair.UID != target.UID { 176 target.UID, err = toHost(pair.UID, i.uids) 177 if err != nil { 178 return target, err 179 } 180 } 181 182 if pair.GID != target.GID { 183 target.GID, err = toHost(pair.GID, i.gids) 184 } 185 return target, err 186 } 187 188 // ToContainer returns the container UID and GID for the host uid and gid 189 func (i *IDMappings) ToContainer(pair IDPair) (int, int, error) { 190 uid, err := toContainer(pair.UID, i.uids) 191 if err != nil { 192 return -1, -1, err 193 } 194 gid, err := toContainer(pair.GID, i.gids) 195 return uid, gid, err 196 } 197 198 // Empty returns true if there are no id mappings 199 func (i *IDMappings) Empty() bool { 200 return len(i.uids) == 0 && len(i.gids) == 0 201 } 202 203 // UIDs return the UID mapping 204 // TODO: remove this once everything has been refactored to use pairs 205 func (i *IDMappings) UIDs() []IDMap { 206 return i.uids 207 } 208 209 // GIDs return the UID mapping 210 // TODO: remove this once everything has been refactored to use pairs 211 func (i *IDMappings) GIDs() []IDMap { 212 return i.gids 213 } 214 215 func createIDMap(subidRanges ranges) []IDMap { 216 idMap := []IDMap{} 217 218 // sort the ranges by lowest ID first 219 sort.Sort(subidRanges) 220 containerID := 0 221 for _, idrange := range subidRanges { 222 idMap = append(idMap, IDMap{ 223 ContainerID: containerID, 224 HostID: idrange.Start, 225 Size: idrange.Length, 226 }) 227 containerID = containerID + idrange.Length 228 } 229 return idMap 230 } 231 232 func parseSubuid(username string) (ranges, error) { 233 return parseSubidFile(subuidFileName, username) 234 } 235 236 func parseSubgid(username string) (ranges, error) { 237 return parseSubidFile(subgidFileName, username) 238 } 239 240 // parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid) 241 // and return all found ranges for a specified username. If the special value 242 // "ALL" is supplied for username, then all ranges in the file will be returned 243 func parseSubidFile(path, username string) (ranges, error) { 244 var rangeList ranges 245 246 subidFile, err := os.Open(path) 247 if err != nil { 248 return rangeList, err 249 } 250 defer subidFile.Close() 251 252 s := bufio.NewScanner(subidFile) 253 for s.Scan() { 254 if err := s.Err(); err != nil { 255 return rangeList, err 256 } 257 258 text := strings.TrimSpace(s.Text()) 259 if text == "" || strings.HasPrefix(text, "#") { 260 continue 261 } 262 parts := strings.Split(text, ":") 263 if len(parts) != 3 { 264 return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path) 265 } 266 if parts[0] == username || username == "ALL" { 267 startid, err := strconv.Atoi(parts[1]) 268 if err != nil { 269 return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err) 270 } 271 length, err := strconv.Atoi(parts[2]) 272 if err != nil { 273 return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err) 274 } 275 rangeList = append(rangeList, subIDRange{startid, length}) 276 } 277 } 278 return rangeList, nil 279 }