github.com/sshnaidm/storage@v1.12.13/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 75 if runtimeDir != "" { 76 return runtimeDir, nil 77 } 78 tmpDir := fmt.Sprintf("/run/user/%d", rootlessUid) 79 st, err := system.Stat(tmpDir) 80 if err == nil && int(st.UID()) == os.Getuid() && st.Mode()&0700 == 0700 && st.Mode()&0066 == 0000 { 81 return tmpDir, nil 82 } 83 tmpDir = fmt.Sprintf("%s/%d", os.TempDir(), rootlessUid) 84 if err := os.MkdirAll(tmpDir, 0700); err != nil { 85 logrus.Errorf("failed to create %s: %v", tmpDir, err) 86 } else { 87 return tmpDir, nil 88 } 89 home, err := homeDir() 90 if err != nil { 91 return "", errors.Wrapf(err, "neither XDG_RUNTIME_DIR nor HOME was set non-empty") 92 } 93 resolvedHome, err := filepath.EvalSymlinks(home) 94 if err != nil { 95 return "", errors.Wrapf(err, "cannot resolve %s", home) 96 } 97 return filepath.Join(resolvedHome, "rundir"), nil 98 } 99 100 // getRootlessDirInfo returns the parent path of where the storage for containers and 101 // volumes will be in rootless mode 102 func getRootlessDirInfo(rootlessUid int) (string, string, error) { 103 rootlessRuntime, err := GetRootlessRuntimeDir(rootlessUid) 104 if err != nil { 105 return "", "", err 106 } 107 108 dataDir := os.Getenv("XDG_DATA_HOME") 109 if dataDir == "" { 110 home, err := homeDir() 111 if err != nil { 112 return "", "", errors.Wrapf(err, "neither XDG_DATA_HOME nor HOME was set non-empty") 113 } 114 // runc doesn't like symlinks in the rootfs path, and at least 115 // on CoreOS /home is a symlink to /var/home, so resolve any symlink. 116 resolvedHome, err := filepath.EvalSymlinks(home) 117 if err != nil { 118 return "", "", errors.Wrapf(err, "cannot resolve %s", home) 119 } 120 dataDir = filepath.Join(resolvedHome, ".local", "share") 121 } 122 return dataDir, rootlessRuntime, nil 123 } 124 125 // getRootlessStorageOpts returns the storage opts for containers running as non root 126 func getRootlessStorageOpts(rootlessUid int) (StoreOptions, error) { 127 var opts StoreOptions 128 129 dataDir, rootlessRuntime, err := getRootlessDirInfo(rootlessUid) 130 if err != nil { 131 return opts, err 132 } 133 opts.RunRoot = rootlessRuntime 134 opts.GraphRoot = filepath.Join(dataDir, "containers", "storage") 135 if path, err := exec.LookPath("fuse-overlayfs"); err == nil { 136 opts.GraphDriverName = "overlay" 137 opts.GraphDriverOptions = []string{fmt.Sprintf("overlay.mount_program=%s", path)} 138 } else { 139 opts.GraphDriverName = "vfs" 140 } 141 return opts, nil 142 } 143 144 type tomlOptionsConfig struct { 145 MountProgram string `toml:"mount_program"` 146 } 147 148 func getTomlStorage(storeOptions *StoreOptions) *tomlConfig { 149 config := new(tomlConfig) 150 151 config.Storage.Driver = storeOptions.GraphDriverName 152 config.Storage.RunRoot = storeOptions.RunRoot 153 config.Storage.GraphRoot = storeOptions.GraphRoot 154 for _, i := range storeOptions.GraphDriverOptions { 155 s := strings.Split(i, "=") 156 if s[0] == "overlay.mount_program" { 157 config.Storage.Options.MountProgram = s[1] 158 } 159 } 160 161 return config 162 } 163 164 func getRootlessUID() int { 165 uidEnv := os.Getenv("_CONTAINERS_ROOTLESS_UID") 166 if uidEnv != "" { 167 u, _ := strconv.Atoi(uidEnv) 168 return u 169 } 170 return os.Geteuid() 171 } 172 173 // DefaultStoreOptionsAutoDetectUID returns the default storage ops for containers 174 func DefaultStoreOptionsAutoDetectUID() (StoreOptions, error) { 175 uid := getRootlessUID() 176 return DefaultStoreOptions(uid != 0, uid) 177 } 178 179 // DefaultStoreOptions returns the default storage ops for containers 180 func DefaultStoreOptions(rootless bool, rootlessUid int) (StoreOptions, error) { 181 var ( 182 defaultRootlessRunRoot string 183 defaultRootlessGraphRoot string 184 err error 185 ) 186 storageOpts := defaultStoreOptions 187 if rootless && rootlessUid != 0 { 188 storageOpts, err = getRootlessStorageOpts(rootlessUid) 189 if err != nil { 190 return storageOpts, err 191 } 192 } 193 194 storageConf, err := DefaultConfigFile(rootless && rootlessUid != 0) 195 if err != nil { 196 return storageOpts, err 197 } 198 _, err = os.Stat(storageConf) 199 if err != nil && !os.IsNotExist(err) { 200 return storageOpts, errors.Wrapf(err, "cannot stat %s", storageConf) 201 } 202 if err == nil { 203 defaultRootlessRunRoot = storageOpts.RunRoot 204 defaultRootlessGraphRoot = storageOpts.GraphRoot 205 storageOpts = StoreOptions{} 206 ReloadConfigurationFile(storageConf, &storageOpts) 207 } 208 209 if rootless && rootlessUid != 0 { 210 if err == nil { 211 // If the file did not specify a graphroot or runroot, 212 // set sane defaults so we don't try and use root-owned 213 // directories 214 if storageOpts.RunRoot == "" { 215 storageOpts.RunRoot = defaultRootlessRunRoot 216 } 217 if storageOpts.GraphRoot == "" { 218 storageOpts.GraphRoot = defaultRootlessGraphRoot 219 } 220 } else { 221 if err := os.MkdirAll(filepath.Dir(storageConf), 0755); err != nil { 222 return storageOpts, errors.Wrapf(err, "cannot make directory %s", filepath.Dir(storageConf)) 223 } 224 file, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) 225 if err != nil { 226 return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf) 227 } 228 229 tomlConfiguration := getTomlStorage(&storageOpts) 230 defer file.Close() 231 enc := toml.NewEncoder(file) 232 if err := enc.Encode(tomlConfiguration); err != nil { 233 os.Remove(storageConf) 234 235 return storageOpts, errors.Wrapf(err, "failed to encode %s", storageConf) 236 } 237 } 238 } 239 return storageOpts, nil 240 } 241 242 func homeDir() (string, error) { 243 home := os.Getenv("HOME") 244 if home == "" { 245 usr, err := user.Current() 246 if err != nil { 247 return "", errors.Wrapf(err, "neither XDG_RUNTIME_DIR nor HOME was set non-empty") 248 } 249 home = usr.HomeDir 250 } 251 return home, nil 252 }