github.com/endophage/docker@v1.4.2-0.20161027011718-242853499895/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 func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { 41 return mkdirAs(path, mode, ownerUID, ownerGID, true, true) 42 } 43 44 // MkdirAllNewAs creates a directory (include any along the path) and then modifies 45 // ownership ONLY of newly created directories to the requested uid/gid. If the 46 // directories along the path exist, no change of ownership will be performed 47 func MkdirAllNewAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { 48 return mkdirAs(path, mode, ownerUID, ownerGID, true, false) 49 } 50 51 // MkdirAs creates a directory and then modifies ownership to the requested uid/gid. 52 // If the directory already exists, this function still changes ownership 53 func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { 54 return mkdirAs(path, mode, ownerUID, ownerGID, false, true) 55 } 56 57 // GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps. 58 // If the maps are empty, then the root uid/gid will default to "real" 0/0 59 func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) { 60 var uid, gid int 61 62 if uidMap != nil { 63 xUID, err := ToHost(0, uidMap) 64 if err != nil { 65 return -1, -1, err 66 } 67 uid = xUID 68 } 69 if gidMap != nil { 70 xGID, err := ToHost(0, gidMap) 71 if err != nil { 72 return -1, -1, err 73 } 74 gid = xGID 75 } 76 return uid, gid, nil 77 } 78 79 // ToContainer takes an id mapping, and uses it to translate a 80 // host ID to the remapped ID. If no map is provided, then the translation 81 // assumes a 1-to-1 mapping and returns the passed in id 82 func ToContainer(hostID int, idMap []IDMap) (int, error) { 83 if idMap == nil { 84 return hostID, nil 85 } 86 for _, m := range idMap { 87 if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) { 88 contID := m.ContainerID + (hostID - m.HostID) 89 return contID, nil 90 } 91 } 92 return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID) 93 } 94 95 // ToHost takes an id mapping and a remapped ID, and translates the 96 // ID to the mapped host ID. If no map is provided, then the translation 97 // assumes a 1-to-1 mapping and returns the passed in id # 98 func ToHost(contID int, idMap []IDMap) (int, error) { 99 if idMap == nil { 100 return contID, nil 101 } 102 for _, m := range idMap { 103 if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) { 104 hostID := m.HostID + (contID - m.ContainerID) 105 return hostID, nil 106 } 107 } 108 return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID) 109 } 110 111 // CreateIDMappings takes a requested user and group name and 112 // using the data from /etc/sub{uid,gid} ranges, creates the 113 // proper uid and gid remapping ranges for that user/group pair 114 func CreateIDMappings(username, groupname string) ([]IDMap, []IDMap, error) { 115 subuidRanges, err := parseSubuid(username) 116 if err != nil { 117 return nil, nil, err 118 } 119 subgidRanges, err := parseSubgid(groupname) 120 if err != nil { 121 return nil, nil, err 122 } 123 if len(subuidRanges) == 0 { 124 return nil, nil, fmt.Errorf("No subuid ranges found for user %q", username) 125 } 126 if len(subgidRanges) == 0 { 127 return nil, nil, fmt.Errorf("No subgid ranges found for group %q", groupname) 128 } 129 130 return createIDMap(subuidRanges), createIDMap(subgidRanges), nil 131 } 132 133 func createIDMap(subidRanges ranges) []IDMap { 134 idMap := []IDMap{} 135 136 // sort the ranges by lowest ID first 137 sort.Sort(subidRanges) 138 containerID := 0 139 for _, idrange := range subidRanges { 140 idMap = append(idMap, IDMap{ 141 ContainerID: containerID, 142 HostID: idrange.Start, 143 Size: idrange.Length, 144 }) 145 containerID = containerID + idrange.Length 146 } 147 return idMap 148 } 149 150 func parseSubuid(username string) (ranges, error) { 151 return parseSubidFile(subuidFileName, username) 152 } 153 154 func parseSubgid(username string) (ranges, error) { 155 return parseSubidFile(subgidFileName, username) 156 } 157 158 // parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid) 159 // and return all found ranges for a specified username. If the special value 160 // "ALL" is supplied for username, then all ranges in the file will be returned 161 func parseSubidFile(path, username string) (ranges, error) { 162 var rangeList ranges 163 164 subidFile, err := os.Open(path) 165 if err != nil { 166 return rangeList, err 167 } 168 defer subidFile.Close() 169 170 s := bufio.NewScanner(subidFile) 171 for s.Scan() { 172 if err := s.Err(); err != nil { 173 return rangeList, err 174 } 175 176 text := strings.TrimSpace(s.Text()) 177 if text == "" || strings.HasPrefix(text, "#") { 178 continue 179 } 180 parts := strings.Split(text, ":") 181 if len(parts) != 3 { 182 return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path) 183 } 184 if parts[0] == username || username == "ALL" { 185 startid, err := strconv.Atoi(parts[1]) 186 if err != nil { 187 return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err) 188 } 189 length, err := strconv.Atoi(parts[2]) 190 if err != nil { 191 return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err) 192 } 193 rangeList = append(rangeList, subIDRange{startid, length}) 194 } 195 } 196 return rangeList, nil 197 }