github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/util/utils.go (about)

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