github.com/thy00/storage@v1.12.8/utils.go (about) 1 package storage 2 3 import ( 4 "fmt" 5 "os" 6 "os/exec" 7 "os/user" 8 "path/filepath" 9 "strconv" 10 "strings" 11 12 "github.com/BurntSushi/toml" 13 "github.com/containers/storage/pkg/idtools" 14 "github.com/containers/storage/pkg/system" 15 "github.com/pkg/errors" 16 "github.com/sirupsen/logrus" 17 ) 18 19 // ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping 20 func ParseIDMapping(UIDMapSlice, GIDMapSlice []string, subUIDMap, subGIDMap string) (*IDMappingOptions, error) { 21 options := IDMappingOptions{ 22 HostUIDMapping: true, 23 HostGIDMapping: true, 24 } 25 if subGIDMap == "" && subUIDMap != "" { 26 subGIDMap = subUIDMap 27 } 28 if subUIDMap == "" && subGIDMap != "" { 29 subUIDMap = subGIDMap 30 } 31 if len(GIDMapSlice) == 0 && len(UIDMapSlice) != 0 { 32 GIDMapSlice = UIDMapSlice 33 } 34 if len(UIDMapSlice) == 0 && len(GIDMapSlice) != 0 { 35 UIDMapSlice = GIDMapSlice 36 } 37 if len(UIDMapSlice) == 0 && subUIDMap == "" && os.Getuid() != 0 { 38 UIDMapSlice = []string{fmt.Sprintf("0:%d:1", os.Getuid())} 39 } 40 if len(GIDMapSlice) == 0 && subGIDMap == "" && os.Getuid() != 0 { 41 GIDMapSlice = []string{fmt.Sprintf("0:%d:1", os.Getgid())} 42 } 43 44 if subUIDMap != "" && subGIDMap != "" { 45 mappings, err := idtools.NewIDMappings(subUIDMap, subGIDMap) 46 if err != nil { 47 return nil, errors.Wrapf(err, "failed to create NewIDMappings for uidmap=%s gidmap=%s", subUIDMap, subGIDMap) 48 } 49 options.UIDMap = mappings.UIDs() 50 options.GIDMap = mappings.GIDs() 51 } 52 parsedUIDMap, err := idtools.ParseIDMap(UIDMapSlice, "UID") 53 if err != nil { 54 return nil, errors.Wrapf(err, "failed to create ParseUIDMap UID=%s", UIDMapSlice) 55 } 56 parsedGIDMap, err := idtools.ParseIDMap(GIDMapSlice, "GID") 57 if err != nil { 58 return nil, errors.Wrapf(err, "failed to create ParseGIDMap GID=%s", UIDMapSlice) 59 } 60 options.UIDMap = append(options.UIDMap, parsedUIDMap...) 61 options.GIDMap = append(options.GIDMap, parsedGIDMap...) 62 if len(options.UIDMap) > 0 { 63 options.HostUIDMapping = false 64 } 65 if len(options.GIDMap) > 0 { 66 options.HostGIDMapping = false 67 } 68 return &options, nil 69 } 70 71 // GetRootlessRuntimeDir returns the runtime directory when running as non root 72 func GetRootlessRuntimeDir(rootlessUid int) (string, error) { 73 runtimeDir := os.Getenv("XDG_RUNTIME_DIR") 74 if runtimeDir == "" { 75 tmpDir := fmt.Sprintf("/run/user/%d", rootlessUid) 76 st, err := system.Stat(tmpDir) 77 if err == nil && int(st.UID()) == os.Getuid() && st.Mode()&0700 == 0700 && st.Mode()&0066 == 0000 { 78 return tmpDir, nil 79 } 80 } 81 tmpDir := fmt.Sprintf("%s/%d", os.TempDir(), rootlessUid) 82 if err := os.MkdirAll(tmpDir, 0700); err != nil { 83 logrus.Errorf("failed to create %s: %v", tmpDir, err) 84 } else { 85 return tmpDir, nil 86 } 87 home, err := homeDir() 88 if err != nil { 89 return "", errors.Wrapf(err, "neither XDG_RUNTIME_DIR nor HOME was set non-empty") 90 } 91 resolvedHome, err := filepath.EvalSymlinks(home) 92 if err != nil { 93 return "", errors.Wrapf(err, "cannot resolve %s", home) 94 } 95 return filepath.Join(resolvedHome, "rundir"), nil 96 } 97 98 // getRootlessDirInfo returns the parent path of where the storage for containers and 99 // volumes will be in rootless mode 100 func getRootlessDirInfo(rootlessUid int) (string, string, error) { 101 rootlessRuntime, err := GetRootlessRuntimeDir(rootlessUid) 102 if err != nil { 103 return "", "", err 104 } 105 106 dataDir := os.Getenv("XDG_DATA_HOME") 107 if dataDir == "" { 108 home, err := homeDir() 109 if err != nil { 110 return "", "", errors.Wrapf(err, "neither XDG_DATA_HOME nor HOME was set non-empty") 111 } 112 // runc doesn't like symlinks in the rootfs path, and at least 113 // on CoreOS /home is a symlink to /var/home, so resolve any symlink. 114 resolvedHome, err := filepath.EvalSymlinks(home) 115 if err != nil { 116 return "", "", errors.Wrapf(err, "cannot resolve %s", home) 117 } 118 dataDir = filepath.Join(resolvedHome, ".local", "share") 119 } 120 return dataDir, rootlessRuntime, nil 121 } 122 123 // getRootlessStorageOpts returns the storage opts for containers running as non root 124 func getRootlessStorageOpts(rootlessUid int) (StoreOptions, error) { 125 var opts StoreOptions 126 127 dataDir, rootlessRuntime, err := getRootlessDirInfo(rootlessUid) 128 if err != nil { 129 return opts, err 130 } 131 opts.RunRoot = rootlessRuntime 132 opts.GraphRoot = filepath.Join(dataDir, "containers", "storage") 133 if path, err := exec.LookPath("fuse-overlayfs"); err == nil { 134 opts.GraphDriverName = "overlay" 135 opts.GraphDriverOptions = []string{fmt.Sprintf("overlay.mount_program=%s", path)} 136 } else { 137 opts.GraphDriverName = "vfs" 138 } 139 return opts, nil 140 } 141 142 type tomlOptionsConfig struct { 143 MountProgram string `toml:"mount_program"` 144 } 145 146 func getTomlStorage(storeOptions *StoreOptions) *tomlConfig { 147 config := new(tomlConfig) 148 149 config.Storage.Driver = storeOptions.GraphDriverName 150 config.Storage.RunRoot = storeOptions.RunRoot 151 config.Storage.GraphRoot = storeOptions.GraphRoot 152 for _, i := range storeOptions.GraphDriverOptions { 153 s := strings.Split(i, "=") 154 if s[0] == "overlay.mount_program" { 155 config.Storage.Options.MountProgram = s[1] 156 } 157 } 158 159 return config 160 } 161 162 func getRootlessUID() int { 163 uidEnv := os.Getenv("_CONTAINERS_ROOTLESS_UID") 164 if uidEnv != "" { 165 u, _ := strconv.Atoi(uidEnv) 166 return u 167 } 168 return os.Geteuid() 169 } 170 171 // DefaultStoreOptionsAutoDetectUID returns the default storage ops for containers 172 func DefaultStoreOptionsAutoDetectUID() (StoreOptions, error) { 173 uid := getRootlessUID() 174 return DefaultStoreOptions(uid != 0, uid) 175 } 176 177 // DefaultStoreOptions returns the default storage ops for containers 178 func DefaultStoreOptions(rootless bool, rootlessUid int) (StoreOptions, error) { 179 var ( 180 defaultRootlessRunRoot string 181 defaultRootlessGraphRoot string 182 err error 183 ) 184 storageOpts := defaultStoreOptions 185 if rootless && rootlessUid != 0 { 186 storageOpts, err = getRootlessStorageOpts(rootlessUid) 187 if err != nil { 188 return storageOpts, err 189 } 190 } 191 192 storageConf, err := DefaultConfigFile(rootless && rootlessUid != 0) 193 if err != nil { 194 return storageOpts, err 195 } 196 if _, err = os.Stat(storageConf); err == nil { 197 defaultRootlessRunRoot = storageOpts.RunRoot 198 defaultRootlessGraphRoot = storageOpts.GraphRoot 199 storageOpts = StoreOptions{} 200 ReloadConfigurationFile(storageConf, &storageOpts) 201 } 202 203 if !os.IsNotExist(err) { 204 return storageOpts, errors.Wrapf(err, "cannot stat %s", storageConf) 205 } 206 207 if rootless && rootlessUid != 0 { 208 if err == nil { 209 // If the file did not specify a graphroot or runroot, 210 // set sane defaults so we don't try and use root-owned 211 // directories 212 if storageOpts.RunRoot == "" { 213 storageOpts.RunRoot = defaultRootlessRunRoot 214 } 215 if storageOpts.GraphRoot == "" { 216 storageOpts.GraphRoot = defaultRootlessGraphRoot 217 } 218 } else { 219 if err := os.MkdirAll(filepath.Dir(storageConf), 0755); err != nil { 220 return storageOpts, errors.Wrapf(err, "cannot make directory %s", filepath.Dir(storageConf)) 221 } 222 file, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) 223 if err != nil { 224 return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf) 225 } 226 227 tomlConfiguration := getTomlStorage(&storageOpts) 228 defer file.Close() 229 enc := toml.NewEncoder(file) 230 if err := enc.Encode(tomlConfiguration); err != nil { 231 os.Remove(storageConf) 232 233 return storageOpts, errors.Wrapf(err, "failed to encode %s", storageConf) 234 } 235 } 236 } 237 return storageOpts, nil 238 } 239 240 func homeDir() (string, error) { 241 home := os.Getenv("HOME") 242 if home == "" { 243 usr, err := user.Current() 244 if err != nil { 245 return "", errors.Wrapf(err, "neither XDG_RUNTIME_DIR nor HOME was set non-empty") 246 } 247 home = usr.HomeDir 248 } 249 return home, nil 250 }