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  }