github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/util/utils.go (about)

     1  package util
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"os/user"
     8  	"path/filepath"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  	"syscall"
    13  	"time"
    14  
    15  	"github.com/BurntSushi/toml"
    16  	"github.com/containers/image/v5/types"
    17  	"github.com/containers/libpod/cmd/podman/cliconfig"
    18  	"github.com/containers/libpod/pkg/errorhandling"
    19  	"github.com/containers/libpod/pkg/namespaces"
    20  	"github.com/containers/libpod/pkg/rootless"
    21  	"github.com/containers/libpod/pkg/signal"
    22  	"github.com/containers/storage"
    23  	"github.com/containers/storage/pkg/idtools"
    24  	v1 "github.com/opencontainers/image-spec/specs-go/v1"
    25  	"github.com/pkg/errors"
    26  	"github.com/sirupsen/logrus"
    27  	"github.com/spf13/pflag"
    28  	"golang.org/x/crypto/ssh/terminal"
    29  )
    30  
    31  // Helper function to determine the username/password passed
    32  // in the creds string.  It could be either or both.
    33  func parseCreds(creds string) (string, string) {
    34  	if creds == "" {
    35  		return "", ""
    36  	}
    37  	up := strings.SplitN(creds, ":", 2)
    38  	if len(up) == 1 {
    39  		return up[0], ""
    40  	}
    41  	return up[0], up[1]
    42  }
    43  
    44  // ParseRegistryCreds takes a credentials string in the form USERNAME:PASSWORD
    45  // and returns a DockerAuthConfig
    46  func ParseRegistryCreds(creds string) (*types.DockerAuthConfig, error) {
    47  	username, password := parseCreds(creds)
    48  	if username == "" {
    49  		fmt.Print("Username: ")
    50  		fmt.Scanln(&username)
    51  	}
    52  	if password == "" {
    53  		fmt.Print("Password: ")
    54  		termPassword, err := terminal.ReadPassword(0)
    55  		if err != nil {
    56  			return nil, errors.Wrapf(err, "could not read password from terminal")
    57  		}
    58  		password = string(termPassword)
    59  	}
    60  
    61  	return &types.DockerAuthConfig{
    62  		Username: username,
    63  		Password: password,
    64  	}, nil
    65  }
    66  
    67  // StringInSlice determines if a string is in a string slice, returns bool
    68  func StringInSlice(s string, sl []string) bool {
    69  	for _, i := range sl {
    70  		if i == s {
    71  			return true
    72  		}
    73  	}
    74  	return false
    75  }
    76  
    77  // ImageConfig is a wrapper around the OCIv1 Image Configuration struct exported
    78  // by containers/image, but containing additional fields that are not supported
    79  // by OCIv1 (but are by Docker v2) - notably OnBuild.
    80  type ImageConfig struct {
    81  	v1.ImageConfig
    82  	OnBuild []string
    83  }
    84  
    85  // GetImageConfig produces a v1.ImageConfig from the --change flag that is
    86  // accepted by several Podman commands. It accepts a (limited subset) of
    87  // Dockerfile instructions.
    88  func GetImageConfig(changes []string) (ImageConfig, error) {
    89  	// Valid changes:
    90  	// USER
    91  	// EXPOSE
    92  	// ENV
    93  	// ENTRYPOINT
    94  	// CMD
    95  	// VOLUME
    96  	// WORKDIR
    97  	// LABEL
    98  	// STOPSIGNAL
    99  	// ONBUILD
   100  
   101  	config := ImageConfig{}
   102  
   103  	for _, change := range changes {
   104  		// First, let's assume proper Dockerfile format - space
   105  		// separator between instruction and value
   106  		split := strings.SplitN(change, " ", 2)
   107  
   108  		if len(split) != 2 {
   109  			split = strings.SplitN(change, "=", 2)
   110  			if len(split) != 2 {
   111  				return ImageConfig{}, errors.Errorf("invalid change %q - must be formatted as KEY VALUE", change)
   112  			}
   113  		}
   114  
   115  		outerKey := strings.ToUpper(strings.TrimSpace(split[0]))
   116  		value := strings.TrimSpace(split[1])
   117  		switch outerKey {
   118  		case "USER":
   119  			// Assume literal contents are the user.
   120  			if value == "" {
   121  				return ImageConfig{}, errors.Errorf("invalid change %q - must provide a value to USER", change)
   122  			}
   123  			config.User = value
   124  		case "EXPOSE":
   125  			// EXPOSE is either [portnum] or
   126  			// [portnum]/[proto]
   127  			// Protocol must be "tcp" or "udp"
   128  			splitPort := strings.Split(value, "/")
   129  			if len(splitPort) > 2 {
   130  				return ImageConfig{}, errors.Errorf("invalid change %q - EXPOSE port must be formatted as PORT[/PROTO]", change)
   131  			}
   132  			portNum, err := strconv.Atoi(splitPort[0])
   133  			if err != nil {
   134  				return ImageConfig{}, errors.Wrapf(err, "invalid change %q - EXPOSE port must be an integer", change)
   135  			}
   136  			if portNum > 65535 || portNum <= 0 {
   137  				return ImageConfig{}, errors.Errorf("invalid change %q - EXPOSE port must be a valid port number", change)
   138  			}
   139  			proto := "tcp"
   140  			if len(splitPort) > 1 {
   141  				testProto := strings.ToLower(splitPort[1])
   142  				switch testProto {
   143  				case "tcp", "udp":
   144  					proto = testProto
   145  				default:
   146  					return ImageConfig{}, errors.Errorf("invalid change %q - EXPOSE protocol must be TCP or UDP", change)
   147  				}
   148  			}
   149  			if config.ExposedPorts == nil {
   150  				config.ExposedPorts = make(map[string]struct{})
   151  			}
   152  			config.ExposedPorts[fmt.Sprintf("%d/%s", portNum, proto)] = struct{}{}
   153  		case "ENV":
   154  			// Format is either:
   155  			// ENV key=value
   156  			// ENV key=value key=value ...
   157  			// ENV key value
   158  			// Both keys and values can be surrounded by quotes to group them.
   159  			// For now: we only support key=value
   160  			// We will attempt to strip quotation marks if present.
   161  
   162  			var (
   163  				key, val string
   164  			)
   165  
   166  			splitEnv := strings.SplitN(value, "=", 2)
   167  			key = splitEnv[0]
   168  			// We do need a key
   169  			if key == "" {
   170  				return ImageConfig{}, errors.Errorf("invalid change %q - ENV must have at least one argument", change)
   171  			}
   172  			// Perfectly valid to not have a value
   173  			if len(splitEnv) == 2 {
   174  				val = splitEnv[1]
   175  			}
   176  
   177  			if strings.HasPrefix(key, `"`) && strings.HasSuffix(key, `"`) {
   178  				key = strings.TrimPrefix(strings.TrimSuffix(key, `"`), `"`)
   179  			}
   180  			if strings.HasPrefix(val, `"`) && strings.HasSuffix(val, `"`) {
   181  				val = strings.TrimPrefix(strings.TrimSuffix(val, `"`), `"`)
   182  			}
   183  			config.Env = append(config.Env, fmt.Sprintf("%s=%s", key, val))
   184  		case "ENTRYPOINT":
   185  			// Two valid forms.
   186  			// First, JSON array.
   187  			// Second, not a JSON array - we interpret this as an
   188  			// argument to `sh -c`, unless empty, in which case we
   189  			// just use a blank entrypoint.
   190  			testUnmarshal := []string{}
   191  			if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil {
   192  				// It ain't valid JSON, so assume it's an
   193  				// argument to sh -c if not empty.
   194  				if value != "" {
   195  					config.Entrypoint = []string{"/bin/sh", "-c", value}
   196  				} else {
   197  					config.Entrypoint = []string{}
   198  				}
   199  			} else {
   200  				// Valid JSON
   201  				config.Entrypoint = testUnmarshal
   202  			}
   203  		case "CMD":
   204  			// Same valid forms as entrypoint.
   205  			// However, where ENTRYPOINT assumes that 'ENTRYPOINT '
   206  			// means no entrypoint, CMD assumes it is 'sh -c' with
   207  			// no third argument.
   208  			testUnmarshal := []string{}
   209  			if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil {
   210  				// It ain't valid JSON, so assume it's an
   211  				// argument to sh -c.
   212  				// Only include volume if it's not ""
   213  				config.Cmd = []string{"/bin/sh", "-c"}
   214  				if value != "" {
   215  					config.Cmd = append(config.Cmd, value)
   216  				}
   217  			} else {
   218  				// Valid JSON
   219  				config.Cmd = testUnmarshal
   220  			}
   221  		case "VOLUME":
   222  			// Either a JSON array or a set of space-separated
   223  			// paths.
   224  			// Acts rather similar to ENTRYPOINT and CMD, but always
   225  			// appends rather than replacing, and no sh -c prepend.
   226  			testUnmarshal := []string{}
   227  			if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil {
   228  				// Not valid JSON, so split on spaces
   229  				testUnmarshal = strings.Split(value, " ")
   230  			}
   231  			if len(testUnmarshal) == 0 {
   232  				return ImageConfig{}, errors.Errorf("invalid change %q - must provide at least one argument to VOLUME", change)
   233  			}
   234  			for _, vol := range testUnmarshal {
   235  				if vol == "" {
   236  					return ImageConfig{}, errors.Errorf("invalid change %q - VOLUME paths must not be empty", change)
   237  				}
   238  				if config.Volumes == nil {
   239  					config.Volumes = make(map[string]struct{})
   240  				}
   241  				config.Volumes[vol] = struct{}{}
   242  			}
   243  		case "WORKDIR":
   244  			// This can be passed multiple times.
   245  			// Each successive invocation is treated as relative to
   246  			// the previous one - so WORKDIR /A, WORKDIR b,
   247  			// WORKDIR c results in /A/b/c
   248  			// Just need to check it's not empty...
   249  			if value == "" {
   250  				return ImageConfig{}, errors.Errorf("invalid change %q - must provide a non-empty WORKDIR", change)
   251  			}
   252  			config.WorkingDir = filepath.Join(config.WorkingDir, value)
   253  		case "LABEL":
   254  			// Same general idea as ENV, but we no longer allow " "
   255  			// as a separator.
   256  			// We didn't do that for ENV either, so nice and easy.
   257  			// Potentially problematic: LABEL might theoretically
   258  			// allow an = in the key? If people really do this, we
   259  			// may need to investigate more advanced parsing.
   260  			var (
   261  				key, val string
   262  			)
   263  
   264  			splitLabel := strings.SplitN(value, "=", 2)
   265  			// Unlike ENV, LABEL must have a value
   266  			if len(splitLabel) != 2 {
   267  				return ImageConfig{}, errors.Errorf("invalid change %q - LABEL must be formatted key=value", change)
   268  			}
   269  			key = splitLabel[0]
   270  			val = splitLabel[1]
   271  
   272  			if strings.HasPrefix(key, `"`) && strings.HasSuffix(key, `"`) {
   273  				key = strings.TrimPrefix(strings.TrimSuffix(key, `"`), `"`)
   274  			}
   275  			if strings.HasPrefix(val, `"`) && strings.HasSuffix(val, `"`) {
   276  				val = strings.TrimPrefix(strings.TrimSuffix(val, `"`), `"`)
   277  			}
   278  			// Check key after we strip quotations
   279  			if key == "" {
   280  				return ImageConfig{}, errors.Errorf("invalid change %q - LABEL must have a non-empty key", change)
   281  			}
   282  			if config.Labels == nil {
   283  				config.Labels = make(map[string]string)
   284  			}
   285  			config.Labels[key] = val
   286  		case "STOPSIGNAL":
   287  			// Check the provided signal for validity.
   288  			killSignal, err := ParseSignal(value)
   289  			if err != nil {
   290  				return ImageConfig{}, errors.Wrapf(err, "invalid change %q - KILLSIGNAL must be given a valid signal", change)
   291  			}
   292  			config.StopSignal = fmt.Sprintf("%d", killSignal)
   293  		case "ONBUILD":
   294  			// Onbuild always appends.
   295  			if value == "" {
   296  				return ImageConfig{}, errors.Errorf("invalid change %q - ONBUILD must be given an argument", change)
   297  			}
   298  			config.OnBuild = append(config.OnBuild, value)
   299  		default:
   300  			return ImageConfig{}, errors.Errorf("invalid change %q - invalid instruction %s", change, outerKey)
   301  		}
   302  	}
   303  
   304  	return config, nil
   305  }
   306  
   307  // ParseSignal parses and validates a signal name or number.
   308  func ParseSignal(rawSignal string) (syscall.Signal, error) {
   309  	// Strip off leading dash, to allow -1 or -HUP
   310  	basename := strings.TrimPrefix(rawSignal, "-")
   311  
   312  	sig, err := signal.ParseSignal(basename)
   313  	if err != nil {
   314  		return -1, err
   315  	}
   316  	// 64 is SIGRTMAX; wish we could get this from a standard Go library
   317  	if sig < 1 || sig > 64 {
   318  		return -1, errors.Errorf("valid signals are 1 through 64")
   319  	}
   320  	return sig, nil
   321  }
   322  
   323  // ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping
   324  func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*storage.IDMappingOptions, error) {
   325  	options := storage.IDMappingOptions{
   326  		HostUIDMapping: true,
   327  		HostGIDMapping: true,
   328  	}
   329  
   330  	if mode.IsAuto() {
   331  		var err error
   332  		options.HostUIDMapping = false
   333  		options.HostGIDMapping = false
   334  		options.AutoUserNs = true
   335  		opts, err := mode.GetAutoOptions()
   336  		if err != nil {
   337  			return nil, err
   338  		}
   339  		options.AutoUserNsOpts = *opts
   340  		return &options, nil
   341  	}
   342  	if mode.IsKeepID() {
   343  		if len(uidMapSlice) > 0 || len(gidMapSlice) > 0 {
   344  			return nil, errors.New("cannot specify custom mappings with --userns=keep-id")
   345  		}
   346  		if len(subUIDMap) > 0 || len(subGIDMap) > 0 {
   347  			return nil, errors.New("cannot specify subuidmap or subgidmap with --userns=keep-id")
   348  		}
   349  		if rootless.IsRootless() {
   350  			min := func(a, b int) int {
   351  				if a < b {
   352  					return a
   353  				}
   354  				return b
   355  			}
   356  
   357  			uid := rootless.GetRootlessUID()
   358  			gid := rootless.GetRootlessGID()
   359  
   360  			uids, gids, err := rootless.GetConfiguredMappings()
   361  			if err != nil {
   362  				return nil, errors.Wrapf(err, "cannot read mappings")
   363  			}
   364  			maxUID, maxGID := 0, 0
   365  			for _, u := range uids {
   366  				maxUID += u.Size
   367  			}
   368  			for _, g := range gids {
   369  				maxGID += g.Size
   370  			}
   371  
   372  			options.UIDMap, options.GIDMap = nil, nil
   373  
   374  			options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)})
   375  			options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1})
   376  			if maxUID > uid {
   377  				options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid})
   378  			}
   379  
   380  			options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)})
   381  			options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1})
   382  			if maxGID > gid {
   383  				options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid})
   384  			}
   385  
   386  			options.HostUIDMapping = false
   387  			options.HostGIDMapping = false
   388  		}
   389  		// Simply ignore the setting and do not setup an inner namespace for root as it is a no-op
   390  		return &options, nil
   391  	}
   392  
   393  	if subGIDMap == "" && subUIDMap != "" {
   394  		subGIDMap = subUIDMap
   395  	}
   396  	if subUIDMap == "" && subGIDMap != "" {
   397  		subUIDMap = subGIDMap
   398  	}
   399  	if len(gidMapSlice) == 0 && len(uidMapSlice) != 0 {
   400  		gidMapSlice = uidMapSlice
   401  	}
   402  	if len(uidMapSlice) == 0 && len(gidMapSlice) != 0 {
   403  		uidMapSlice = gidMapSlice
   404  	}
   405  	if len(uidMapSlice) == 0 && subUIDMap == "" && os.Getuid() != 0 {
   406  		uidMapSlice = []string{fmt.Sprintf("0:%d:1", os.Getuid())}
   407  	}
   408  	if len(gidMapSlice) == 0 && subGIDMap == "" && os.Getuid() != 0 {
   409  		gidMapSlice = []string{fmt.Sprintf("0:%d:1", os.Getgid())}
   410  	}
   411  
   412  	if subUIDMap != "" && subGIDMap != "" {
   413  		mappings, err := idtools.NewIDMappings(subUIDMap, subGIDMap)
   414  		if err != nil {
   415  			return nil, err
   416  		}
   417  		options.UIDMap = mappings.UIDs()
   418  		options.GIDMap = mappings.GIDs()
   419  	}
   420  	parsedUIDMap, err := idtools.ParseIDMap(uidMapSlice, "UID")
   421  	if err != nil {
   422  		return nil, err
   423  	}
   424  	parsedGIDMap, err := idtools.ParseIDMap(gidMapSlice, "GID")
   425  	if err != nil {
   426  		return nil, err
   427  	}
   428  	options.UIDMap = append(options.UIDMap, parsedUIDMap...)
   429  	options.GIDMap = append(options.GIDMap, parsedGIDMap...)
   430  	if len(options.UIDMap) > 0 {
   431  		options.HostUIDMapping = false
   432  	}
   433  	if len(options.GIDMap) > 0 {
   434  		options.HostGIDMapping = false
   435  	}
   436  	return &options, nil
   437  }
   438  
   439  var (
   440  	rootlessConfigHomeDirOnce sync.Once
   441  	rootlessConfigHomeDir     string
   442  	rootlessRuntimeDirOnce    sync.Once
   443  	rootlessRuntimeDir        string
   444  )
   445  
   446  type tomlOptionsConfig struct {
   447  	MountProgram string `toml:"mount_program"`
   448  }
   449  
   450  type tomlConfig struct {
   451  	Storage struct {
   452  		Driver    string                      `toml:"driver"`
   453  		RunRoot   string                      `toml:"runroot"`
   454  		GraphRoot string                      `toml:"graphroot"`
   455  		Options   struct{ tomlOptionsConfig } `toml:"options"`
   456  	} `toml:"storage"`
   457  }
   458  
   459  func getTomlStorage(storeOptions *storage.StoreOptions) *tomlConfig {
   460  	config := new(tomlConfig)
   461  
   462  	config.Storage.Driver = storeOptions.GraphDriverName
   463  	config.Storage.RunRoot = storeOptions.RunRoot
   464  	config.Storage.GraphRoot = storeOptions.GraphRoot
   465  	for _, i := range storeOptions.GraphDriverOptions {
   466  		s := strings.Split(i, "=")
   467  		if s[0] == "overlay.mount_program" {
   468  			config.Storage.Options.MountProgram = s[1]
   469  		}
   470  	}
   471  
   472  	return config
   473  }
   474  
   475  // WriteStorageConfigFile writes the configuration to a file
   476  func WriteStorageConfigFile(storageOpts *storage.StoreOptions, storageConf string) error {
   477  	if err := os.MkdirAll(filepath.Dir(storageConf), 0755); err != nil {
   478  		return err
   479  	}
   480  	storageFile, err := os.OpenFile(storageConf, os.O_RDWR|os.O_TRUNC, 0600)
   481  	if err != nil {
   482  		return errors.Wrapf(err, "cannot open %s", storageConf)
   483  	}
   484  	tomlConfiguration := getTomlStorage(storageOpts)
   485  	defer errorhandling.CloseQuiet(storageFile)
   486  	enc := toml.NewEncoder(storageFile)
   487  	if err := enc.Encode(tomlConfiguration); err != nil {
   488  		if err := os.Remove(storageConf); err != nil {
   489  			logrus.Errorf("unable to remove file %s", storageConf)
   490  		}
   491  		return err
   492  	}
   493  	return nil
   494  }
   495  
   496  // ParseInputTime takes the users input and to determine if it is valid and
   497  // returns a time format and error.  The input is compared to known time formats
   498  // or a duration which implies no-duration
   499  func ParseInputTime(inputTime string) (time.Time, error) {
   500  	timeFormats := []string{time.RFC3339Nano, time.RFC3339, "2006-01-02T15:04:05", "2006-01-02T15:04:05.999999999",
   501  		"2006-01-02Z07:00", "2006-01-02"}
   502  	// iterate the supported time formats
   503  	for _, tf := range timeFormats {
   504  		t, err := time.Parse(tf, inputTime)
   505  		if err == nil {
   506  			return t, nil
   507  		}
   508  	}
   509  
   510  	// input might be a duration
   511  	duration, err := time.ParseDuration(inputTime)
   512  	if err != nil {
   513  		return time.Time{}, errors.Errorf("unable to interpret time value")
   514  	}
   515  	return time.Now().Add(-duration), nil
   516  }
   517  
   518  // GetGlobalOpts checks all global flags and generates the command string
   519  // FIXME: Port input to config.Config
   520  // TODO: Is there a "better" way to reverse values to flags? This seems brittle.
   521  func GetGlobalOpts(c *cliconfig.RunlabelValues) string {
   522  	globalFlags := map[string]bool{
   523  		"cgroup-manager": true, "cni-config-dir": true, "conmon": true, "default-mounts-file": true,
   524  		"hooks-dir": true, "namespace": true, "root": true, "runroot": true,
   525  		"runtime": true, "storage-driver": true, "storage-opt": true, "syslog": true,
   526  		"trace": true, "network-cmd-path": true, "config": true, "cpu-profile": true,
   527  		"log-level": true, "tmpdir": true}
   528  	const stringSliceType string = "stringSlice"
   529  
   530  	var optsCommand []string
   531  	c.PodmanCommand.Command.Flags().VisitAll(func(f *pflag.Flag) {
   532  		if !f.Changed {
   533  			return
   534  		}
   535  		if _, exist := globalFlags[f.Name]; exist {
   536  			if f.Value.Type() == stringSliceType {
   537  				flagValue := strings.TrimSuffix(strings.TrimPrefix(f.Value.String(), "["), "]")
   538  				for _, value := range strings.Split(flagValue, ",") {
   539  					optsCommand = append(optsCommand, fmt.Sprintf("--%s %s", f.Name, value))
   540  				}
   541  			} else {
   542  				optsCommand = append(optsCommand, fmt.Sprintf("--%s %s", f.Name, f.Value.String()))
   543  			}
   544  		}
   545  	})
   546  	return strings.Join(optsCommand, " ")
   547  }
   548  
   549  // OpenExclusiveFile opens a file for writing and ensure it doesn't already exist
   550  func OpenExclusiveFile(path string) (*os.File, error) {
   551  	baseDir := filepath.Dir(path)
   552  	if baseDir != "" {
   553  		if _, err := os.Stat(baseDir); err != nil {
   554  			return nil, err
   555  		}
   556  	}
   557  	return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
   558  }
   559  
   560  // PullType whether to pull new image
   561  type PullType int
   562  
   563  const (
   564  	// PullImageAlways always try to pull new image when create or run
   565  	PullImageAlways PullType = iota
   566  	// PullImageMissing pulls image if it is not locally
   567  	PullImageMissing
   568  	// PullImageNever will never pull new image
   569  	PullImageNever
   570  )
   571  
   572  // ValidatePullType check if the pullType from CLI is valid and returns the valid enum type
   573  // if the value from CLI is invalid returns the error
   574  func ValidatePullType(pullType string) (PullType, error) {
   575  	switch pullType {
   576  	case "always":
   577  		return PullImageAlways, nil
   578  	case "missing":
   579  		return PullImageMissing, nil
   580  	case "never":
   581  		return PullImageNever, nil
   582  	case "":
   583  		return PullImageMissing, nil
   584  	default:
   585  		return PullImageMissing, errors.Errorf("invalid pull type %q", pullType)
   586  	}
   587  }
   588  
   589  // ExitCode reads the error message when failing to executing container process
   590  // and then returns 0 if no error, 126 if command does not exist, or 127 for
   591  // all other errors
   592  func ExitCode(err error) int {
   593  	if err == nil {
   594  		return 0
   595  	}
   596  	e := strings.ToLower(err.Error())
   597  	if strings.Contains(e, "file not found") ||
   598  		strings.Contains(e, "no such file or directory") {
   599  		return 127
   600  	}
   601  
   602  	return 126
   603  }
   604  
   605  // HomeDir returns the home directory for the current user.
   606  func HomeDir() (string, error) {
   607  	home := os.Getenv("HOME")
   608  	if home == "" {
   609  		usr, err := user.LookupId(fmt.Sprintf("%d", rootless.GetRootlessUID()))
   610  		if err != nil {
   611  			return "", errors.Wrapf(err, "unable to resolve HOME directory")
   612  		}
   613  		home = usr.HomeDir
   614  	}
   615  	return home, nil
   616  }
   617  
   618  func Tmpdir() string {
   619  	tmpdir := os.Getenv("TMPDIR")
   620  	if tmpdir == "" {
   621  		tmpdir = "/var/tmp"
   622  	}
   623  
   624  	return tmpdir
   625  }
   626  
   627  // ValidateSysctls validates a list of sysctl and returns it.
   628  func ValidateSysctls(strSlice []string) (map[string]string, error) {
   629  	sysctl := make(map[string]string)
   630  	validSysctlMap := map[string]bool{
   631  		"kernel.msgmax":          true,
   632  		"kernel.msgmnb":          true,
   633  		"kernel.msgmni":          true,
   634  		"kernel.sem":             true,
   635  		"kernel.shmall":          true,
   636  		"kernel.shmmax":          true,
   637  		"kernel.shmmni":          true,
   638  		"kernel.shm_rmid_forced": true,
   639  	}
   640  	validSysctlPrefixes := []string{
   641  		"net.",
   642  		"fs.mqueue.",
   643  	}
   644  
   645  	for _, val := range strSlice {
   646  		foundMatch := false
   647  		arr := strings.Split(val, "=")
   648  		if len(arr) < 2 {
   649  			return nil, errors.Errorf("%s is invalid, sysctl values must be in the form of KEY=VALUE", val)
   650  		}
   651  		if validSysctlMap[arr[0]] {
   652  			sysctl[arr[0]] = arr[1]
   653  			continue
   654  		}
   655  
   656  		for _, prefix := range validSysctlPrefixes {
   657  			if strings.HasPrefix(arr[0], prefix) {
   658  				sysctl[arr[0]] = arr[1]
   659  				foundMatch = true
   660  				break
   661  			}
   662  		}
   663  		if !foundMatch {
   664  			return nil, errors.Errorf("sysctl '%s' is not whitelisted", arr[0])
   665  		}
   666  	}
   667  	return sysctl, nil
   668  }