package user import ( "fmt" "os" ) // MkdirOpt is a type for options to pass to Mkdir calls type MkdirOpt func(*mkdirOptions) type mkdirOptions struct { onlyNew bool } // WithOnlyNew is an option for MkdirAllAndChown that will only change ownership and permissions // on newly created directories. If the directory already exists, it will not be modified func WithOnlyNew(o *mkdirOptions) { o.onlyNew = true } // MkdirAllAndChown creates a directory (include any along the path) and then modifies // ownership to the requested uid/gid. By default, if the directory already exists, this // function will still change ownership and permissions. If WithOnlyNew is passed as an // option, then only the newly created directories will have ownership and permissions changed. func MkdirAllAndChown(path string, mode os.FileMode, uid, gid int, opts ...MkdirOpt) error { var options mkdirOptions for _, opt := range opts { opt(&options) } return mkdirAs(path, mode, uid, gid, true, options.onlyNew) } // MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid. // By default, if the directory already exists, this function still changes ownership and permissions. // If WithOnlyNew is passed as an option, then only the newly created directory will have ownership // and permissions changed. // Note that unlike os.Mkdir(), this function does not return IsExist error // in case path already exists. func MkdirAndChown(path string, mode os.FileMode, uid, gid int, opts ...MkdirOpt) error { var options mkdirOptions for _, opt := range opts { opt(&options) } return mkdirAs(path, mode, uid, gid, false, options.onlyNew) } // getRootUIDGID retrieves the remapped root uid/gid pair from the set of maps. // If the maps are empty, then the root uid/gid will default to "real" 0/0 func getRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) { uid, err := toHost(0, uidMap) if err != nil { return -1, -1, err } gid, err := toHost(0, gidMap) if err != nil { return -1, -1, err } return uid, gid, nil } // toContainer takes an id mapping, and uses it to translate a // host ID to the remapped ID. If no map is provided, then the translation // assumes a 1-to-1 mapping and returns the passed in id func toContainer(hostID int, idMap []IDMap) (int, error) { if idMap == nil { return hostID, nil } for _, m := range idMap { if (int64(hostID) >= m.ParentID) && (int64(hostID) <= (m.ParentID + m.Count - 1)) { contID := int(m.ID + (int64(hostID) - m.ParentID)) return contID, nil } } return -1, fmt.Errorf("host ID %d cannot be mapped to a container ID", hostID) } // toHost takes an id mapping and a remapped ID, and translates the // ID to the mapped host ID. If no map is provided, then the translation // assumes a 1-to-1 mapping and returns the passed in id # func toHost(contID int, idMap []IDMap) (int, error) { if idMap == nil { return contID, nil } for _, m := range idMap { if (int64(contID) >= m.ID) && (int64(contID) <= (m.ID + m.Count - 1)) { hostID := int(m.ParentID + (int64(contID) - m.ID)) return hostID, nil } } return -1, fmt.Errorf("container ID %d cannot be mapped to a host ID", contID) } // IdentityMapping contains a mappings of UIDs and GIDs. // The zero value represents an empty mapping. type IdentityMapping struct { UIDMaps []IDMap `json:"UIDMaps"` GIDMaps []IDMap `json:"GIDMaps"` } // RootPair returns a uid and gid pair for the root user. The error is ignored // because a root user always exists, and the defaults are correct when the uid // and gid maps are empty. func (i IdentityMapping) RootPair() (int, int) { uid, gid, _ := getRootUIDGID(i.UIDMaps, i.GIDMaps) return uid, gid } // ToHost returns the host UID and GID for the container uid, gid. // Remapping is only performed if the ids aren't already the remapped root ids func (i IdentityMapping) ToHost(uid, gid int) (int, int, error) { var err error ruid, rgid := i.RootPair() if uid != ruid { ruid, err = toHost(uid, i.UIDMaps) if err != nil { return ruid, rgid, err } } if gid != rgid { rgid, err = toHost(gid, i.GIDMaps) } return ruid, rgid, err } // ToContainer returns the container UID and GID for the host uid and gid func (i IdentityMapping) ToContainer(uid, gid int) (int, int, error) { ruid, err := toContainer(uid, i.UIDMaps) if err != nil { return -1, -1, err } rgid, err := toContainer(gid, i.GIDMaps) return ruid, rgid, err } // Empty returns true if there are no id mappings func (i IdentityMapping) Empty() bool { return len(i.UIDMaps) == 0 && len(i.GIDMaps) == 0 }