github.com/cdoern/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  }