mirror of
				https://github.com/containers/podman.git
				synced 2025-11-01 02:42:11 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			1364 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1364 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package util
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io/fs"
 | |
| 	"math"
 | |
| 	"math/bits"
 | |
| 	"os"
 | |
| 	"os/user"
 | |
| 	"path/filepath"
 | |
| 	"regexp"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"syscall"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/BurntSushi/toml"
 | |
| 	"github.com/containers/common/pkg/config"
 | |
| 	"github.com/containers/image/v5/types"
 | |
| 	"github.com/containers/podman/v5/libpod/define"
 | |
| 	"github.com/containers/podman/v5/pkg/errorhandling"
 | |
| 	"github.com/containers/podman/v5/pkg/namespaces"
 | |
| 	"github.com/containers/podman/v5/pkg/rootless"
 | |
| 	"github.com/containers/podman/v5/pkg/signal"
 | |
| 	"github.com/containers/storage/pkg/directory"
 | |
| 	"github.com/containers/storage/pkg/fileutils"
 | |
| 	"github.com/containers/storage/pkg/idtools"
 | |
| 	"github.com/containers/storage/pkg/unshare"
 | |
| 	stypes "github.com/containers/storage/types"
 | |
| 	securejoin "github.com/cyphar/filepath-securejoin"
 | |
| 	ruser "github.com/moby/sys/user"
 | |
| 	"github.com/opencontainers/runtime-spec/specs-go"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| 	"golang.org/x/term"
 | |
| )
 | |
| 
 | |
| // The flags that an [ug]id mapping can have
 | |
| type idMapFlags struct {
 | |
| 	Extends  bool // The "+" flag
 | |
| 	UserMap  bool // The "u" flag
 | |
| 	GroupMap bool // The "g" flag
 | |
| }
 | |
| 
 | |
| var containerConfig *config.Config
 | |
| 
 | |
| func init() {
 | |
| 	var err error
 | |
| 	containerConfig, err = config.Default()
 | |
| 	if err != nil {
 | |
| 		logrus.Error(err)
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Helper function to determine the username/password passed
 | |
| // in the creds string.  It could be either or both.
 | |
| func parseCreds(creds string) (string, string) {
 | |
| 	username, password, _ := strings.Cut(creds, ":")
 | |
| 	return username, password
 | |
| }
 | |
| 
 | |
| // Takes build context and validates `.containerignore` or `.dockerignore`
 | |
| // if they are symlink outside of buildcontext. Returns list of files to be
 | |
| // excluded and resolved path to the ignore files inside build context or error
 | |
| func ParseDockerignore(containerfiles []string, root string) ([]string, string, error) {
 | |
| 	ignoreFile := ""
 | |
| 	path, err := securejoin.SecureJoin(root, ".containerignore")
 | |
| 	if err != nil {
 | |
| 		return nil, ignoreFile, err
 | |
| 	}
 | |
| 	// set resolved ignore file so imagebuildah
 | |
| 	// does not attempts to re-resolve it
 | |
| 	ignoreFile = path
 | |
| 	ignore, err := os.ReadFile(path)
 | |
| 	if err != nil {
 | |
| 		var dockerIgnoreErr error
 | |
| 		path, symlinkErr := securejoin.SecureJoin(root, ".dockerignore")
 | |
| 		if symlinkErr != nil {
 | |
| 			return nil, ignoreFile, symlinkErr
 | |
| 		}
 | |
| 		// set resolved ignore file so imagebuildah
 | |
| 		// does not attempts to re-resolve it
 | |
| 		ignoreFile = path
 | |
| 		ignore, dockerIgnoreErr = os.ReadFile(path)
 | |
| 		if os.IsNotExist(dockerIgnoreErr) {
 | |
| 			// In this case either ignorefile was not found
 | |
| 			// or it is a symlink to unexpected file in such
 | |
| 			// case manually set ignorefile to `/dev/null` so
 | |
| 			// internally imagebuildah does not attempts to re-resolve
 | |
| 			// this invalid symlink and instead reads a blank file.
 | |
| 			ignoreFile = "/dev/null"
 | |
| 		}
 | |
| 		// after https://github.com/containers/buildah/pull/4239 build supports
 | |
| 		// <Containerfile>.containerignore or <Containerfile>.dockerignore as ignore file
 | |
| 		// so remote must support parsing that.
 | |
| 		if dockerIgnoreErr != nil {
 | |
| 			for _, containerfile := range containerfiles {
 | |
| 				containerfile = strings.TrimPrefix(containerfile, root)
 | |
| 				if err := fileutils.Exists(filepath.Join(root, containerfile+".containerignore")); err == nil {
 | |
| 					path, symlinkErr = securejoin.SecureJoin(root, containerfile+".containerignore")
 | |
| 					if symlinkErr == nil {
 | |
| 						ignoreFile = path
 | |
| 						ignore, dockerIgnoreErr = os.ReadFile(path)
 | |
| 					}
 | |
| 				}
 | |
| 				if err := fileutils.Exists(filepath.Join(root, containerfile+".dockerignore")); err == nil {
 | |
| 					path, symlinkErr = securejoin.SecureJoin(root, containerfile+".dockerignore")
 | |
| 					if symlinkErr == nil {
 | |
| 						ignoreFile = path
 | |
| 						ignore, dockerIgnoreErr = os.ReadFile(path)
 | |
| 					}
 | |
| 				}
 | |
| 				if dockerIgnoreErr == nil {
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		if dockerIgnoreErr != nil && !os.IsNotExist(dockerIgnoreErr) {
 | |
| 			return nil, ignoreFile, err
 | |
| 		}
 | |
| 	}
 | |
| 	rawexcludes := strings.Split(string(ignore), "\n")
 | |
| 	excludes := make([]string, 0, len(rawexcludes))
 | |
| 	for _, e := range rawexcludes {
 | |
| 		if len(e) == 0 || e[0] == '#' {
 | |
| 			continue
 | |
| 		}
 | |
| 		excludes = append(excludes, e)
 | |
| 	}
 | |
| 	return excludes, ignoreFile, nil
 | |
| }
 | |
| 
 | |
| // ParseRegistryCreds takes a credentials string in the form USERNAME:PASSWORD
 | |
| // and returns a DockerAuthConfig
 | |
| func ParseRegistryCreds(creds string) (*types.DockerAuthConfig, error) {
 | |
| 	username, password := parseCreds(creds)
 | |
| 	if username == "" {
 | |
| 		fmt.Print("Username: ")
 | |
| 		fmt.Scanln(&username)
 | |
| 	}
 | |
| 	if password == "" {
 | |
| 		fmt.Print("Password: ")
 | |
| 		termPassword, err := term.ReadPassword(0)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("could not read password from terminal: %w", err)
 | |
| 		}
 | |
| 		password = string(termPassword)
 | |
| 	}
 | |
| 
 | |
| 	return &types.DockerAuthConfig{
 | |
| 		Username: username,
 | |
| 		Password: password,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // StringMatchRegexSlice determines if a given string matches one of the given regexes, returns bool
 | |
| func StringMatchRegexSlice(s string, re []string) bool {
 | |
| 	for _, r := range re {
 | |
| 		m, err := regexp.MatchString(r, s)
 | |
| 		if err == nil && m {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // ParseSignal parses and validates a signal name or number.
 | |
| func ParseSignal(rawSignal string) (syscall.Signal, error) {
 | |
| 	// Strip off leading dash, to allow -1 or -HUP
 | |
| 	basename := strings.TrimPrefix(rawSignal, "-")
 | |
| 
 | |
| 	sig, err := signal.ParseSignal(basename)
 | |
| 	if err != nil {
 | |
| 		return -1, err
 | |
| 	}
 | |
| 	// 64 is SIGRTMAX; wish we could get this from a standard Go library
 | |
| 	if sig < 1 || sig > 64 {
 | |
| 		return -1, errors.New("valid signals are 1 through 64")
 | |
| 	}
 | |
| 	return sig, nil
 | |
| }
 | |
| 
 | |
| func getRootlessKeepIDMapping(uid, gid int, uids, gids []idtools.IDMap) (*stypes.IDMappingOptions, int, int, error) {
 | |
| 	options := stypes.IDMappingOptions{
 | |
| 		HostUIDMapping: false,
 | |
| 		HostGIDMapping: false,
 | |
| 	}
 | |
| 	maxUID, maxGID := 0, 0
 | |
| 	for _, u := range uids {
 | |
| 		maxUID += u.Size
 | |
| 	}
 | |
| 	for _, g := range gids {
 | |
| 		maxGID += g.Size
 | |
| 	}
 | |
| 
 | |
| 	options.UIDMap, options.GIDMap = nil, nil
 | |
| 
 | |
| 	if len(uids) > 0 && uid != 0 {
 | |
| 		options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)})
 | |
| 	}
 | |
| 	options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1})
 | |
| 	if maxUID > uid {
 | |
| 		options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid})
 | |
| 	}
 | |
| 
 | |
| 	if len(gids) > 0 && gid != 0 {
 | |
| 		options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)})
 | |
| 	}
 | |
| 	options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1})
 | |
| 	if maxGID > gid {
 | |
| 		options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid})
 | |
| 	}
 | |
| 
 | |
| 	return &options, uid, gid, nil
 | |
| }
 | |
| 
 | |
| // GetKeepIDMapping returns the mappings and the user to use when keep-id is used
 | |
| func GetKeepIDMapping(opts *namespaces.KeepIDUserNsOptions) (*stypes.IDMappingOptions, int, int, error) {
 | |
| 	if !rootless.IsRootless() {
 | |
| 		options := stypes.IDMappingOptions{
 | |
| 			HostUIDMapping: false,
 | |
| 			HostGIDMapping: false,
 | |
| 		}
 | |
| 		uids, gids, err := unshare.GetHostIDMappings("")
 | |
| 		if err != nil {
 | |
| 			return nil, 0, 0, err
 | |
| 		}
 | |
| 		options.UIDMap = RuntimeSpecToIDtools(uids)
 | |
| 		options.GIDMap = RuntimeSpecToIDtools(gids)
 | |
| 
 | |
| 		uid, gid := 0, 0
 | |
| 		if opts.UID != nil {
 | |
| 			uid = int(*opts.UID)
 | |
| 		}
 | |
| 		if opts.GID != nil {
 | |
| 			gid = int(*opts.GID)
 | |
| 		}
 | |
| 
 | |
| 		return &options, uid, gid, nil
 | |
| 	}
 | |
| 
 | |
| 	uid := rootless.GetRootlessUID()
 | |
| 	gid := rootless.GetRootlessGID()
 | |
| 	if opts.UID != nil {
 | |
| 		uid = int(*opts.UID)
 | |
| 	}
 | |
| 	if opts.GID != nil {
 | |
| 		gid = int(*opts.GID)
 | |
| 	}
 | |
| 
 | |
| 	uids, gids, err := rootless.GetConfiguredMappings(true)
 | |
| 	if err != nil {
 | |
| 		return nil, -1, -1, fmt.Errorf("cannot read mappings: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return getRootlessKeepIDMapping(uid, gid, uids, gids)
 | |
| }
 | |
| 
 | |
| // GetNoMapMapping returns the mappings and the user to use when nomap is used
 | |
| func GetNoMapMapping() (*stypes.IDMappingOptions, int, int, error) {
 | |
| 	if !rootless.IsRootless() {
 | |
| 		return nil, -1, -1, errors.New("nomap is only supported in rootless mode")
 | |
| 	}
 | |
| 	options := stypes.IDMappingOptions{
 | |
| 		HostUIDMapping: false,
 | |
| 		HostGIDMapping: false,
 | |
| 	}
 | |
| 	uids, gids, err := rootless.GetConfiguredMappings(false)
 | |
| 	if err != nil {
 | |
| 		return nil, -1, -1, fmt.Errorf("cannot read mappings: %w", err)
 | |
| 	}
 | |
| 	if len(uids) == 0 || len(gids) == 0 {
 | |
| 		return nil, -1, -1, fmt.Errorf("nomap requires additional UIDs or GIDs defined in /etc/subuid and /etc/subgid to function correctly: %w", err)
 | |
| 	}
 | |
| 	options.UIDMap, options.GIDMap = nil, nil
 | |
| 	uid, gid := 0, 0
 | |
| 	for _, u := range uids {
 | |
| 		options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: uid + 1, Size: u.Size})
 | |
| 		uid += u.Size
 | |
| 	}
 | |
| 	for _, g := range gids {
 | |
| 		options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: gid + 1, Size: g.Size})
 | |
| 		gid += g.Size
 | |
| 	}
 | |
| 	return &options, 0, 0, nil
 | |
| }
 | |
| 
 | |
| // Map a given ID to the Parent/Host ID of a given mapping, and return
 | |
| // its corresponding ID/ContainerID.
 | |
| // Returns an error if the given ID is not found on the mapping parents
 | |
| func mapIDwithMapping(id uint64, mapping []ruser.IDMap, mapSetting string) (mappedid uint64, err error) {
 | |
| 	for _, v := range mapping {
 | |
| 		if v.Count == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		if id >= uint64(v.ParentID) && id < uint64(v.ParentID+v.Count) {
 | |
| 			offset := id - uint64(v.ParentID)
 | |
| 			return uint64(v.ID) + offset, nil
 | |
| 		}
 | |
| 	}
 | |
| 	return uint64(0), fmt.Errorf("parent ID %s %d is not mapped/delegated", mapSetting, id)
 | |
| }
 | |
| 
 | |
| // Parse flags from spec
 | |
| // The `u` and `g` flags can be used to enforce that the mapping applies
 | |
| // exclusively to UIDs or GIDs.
 | |
| //
 | |
| // The `+` flag is interpreted as if the mapping replaces previous mappings
 | |
| // removing any conflicting mapping from those before adding this one.
 | |
| func parseFlags(spec []string) (flags idMapFlags, read int, err error) {
 | |
| 	flags.Extends = false
 | |
| 	flags.UserMap = false
 | |
| 	flags.GroupMap = false
 | |
| 	for read, char := range spec[0] {
 | |
| 		switch {
 | |
| 		case '0' <= char && char <= '9':
 | |
| 			return flags, read, nil
 | |
| 		case char == '+':
 | |
| 			flags.Extends = true
 | |
| 		case char == 'u':
 | |
| 			flags.UserMap = true
 | |
| 		case char == 'g':
 | |
| 			flags.GroupMap = true
 | |
| 		case true:
 | |
| 			return flags, 0, fmt.Errorf("invalid mapping: %v. Unknown flag %v", spec, char)
 | |
| 		}
 | |
| 	}
 | |
| 	return flags, read, fmt.Errorf("invalid mapping: %v, parsing flags", spec)
 | |
| }
 | |
| 
 | |
| // Extension of idTools.parseTriple that parses idmap triples.
 | |
| // The triple should be a length 3 string array, containing:
 | |
| // - Flags and ContainerID
 | |
| // - HostID
 | |
| // - Size
 | |
| //
 | |
| // parseTriple returns the parsed mapping, the mapping flags and
 | |
| // any possible error. If the error is not-nil, the mapping and flags
 | |
| // are not well-defined.
 | |
| //
 | |
| // idTools.parseTriple is extended here with the following enhancements:
 | |
| //
 | |
| // HostID @ syntax:
 | |
| // =================
 | |
| // HostID may use the "@" syntax: The "101001:@1001:1" mapping
 | |
| // means "take the 1001 id from the parent namespace and map it to 101001"
 | |
| //
 | |
| // Flags:
 | |
| // ======
 | |
| // Flags can be used to tell the caller how should the mapping be interpreted
 | |
| func parseTriple(spec []string, parentMapping []ruser.IDMap, mapSetting string) (mappings []idtools.IDMap, flags idMapFlags, err error) {
 | |
| 	if len(spec[0]) == 0 {
 | |
| 		return mappings, flags, fmt.Errorf("invalid empty container id at %s map: %v", mapSetting, spec)
 | |
| 	}
 | |
| 	var cids, hids, sizes []uint64
 | |
| 	var cid, hid uint64
 | |
| 	var hidIsParent bool
 | |
| 	flags, i, err := parseFlags(spec)
 | |
| 	if err != nil {
 | |
| 		return mappings, flags, err
 | |
| 	}
 | |
| 	// If no "u" nor "g" flag is given, assume the mapping applies to both
 | |
| 	if !flags.UserMap && !flags.GroupMap {
 | |
| 		flags.UserMap = true
 | |
| 		flags.GroupMap = true
 | |
| 	}
 | |
| 	// Parse the container ID, which must be an integer:
 | |
| 	cid, err = strconv.ParseUint(spec[0][i:], 10, 32)
 | |
| 	if err != nil {
 | |
| 		return mappings, flags, fmt.Errorf("parsing id map value %q: %w", spec[0], err)
 | |
| 	}
 | |
| 	// Parse the host id, which may be integer or @<integer>
 | |
| 	if len(spec[1]) == 0 {
 | |
| 		return mappings, flags, fmt.Errorf("invalid empty host id at %s map: %v", mapSetting, spec)
 | |
| 	}
 | |
| 	if spec[1][0] != '@' {
 | |
| 		hidIsParent = false
 | |
| 		hid, err = strconv.ParseUint(spec[1], 10, 32)
 | |
| 	} else {
 | |
| 		// Parse @<id>, where <id> is an integer corresponding to the parent mapping
 | |
| 		hidIsParent = true
 | |
| 		hid, err = strconv.ParseUint(spec[1][1:], 10, 32)
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		return mappings, flags, fmt.Errorf("parsing id map value %q: %w", spec[1], err)
 | |
| 	}
 | |
| 	// Parse the size of the mapping, which must be an integer
 | |
| 	sz, err := strconv.ParseUint(spec[2], 10, 32)
 | |
| 	if err != nil {
 | |
| 		return mappings, flags, fmt.Errorf("parsing id map value %q: %w", spec[2], err)
 | |
| 	}
 | |
| 
 | |
| 	if hidIsParent {
 | |
| 		if (mapSetting == "UID" && flags.UserMap) || (mapSetting == "GID" && flags.GroupMap) {
 | |
| 			for i := uint64(0); i < sz; i++ {
 | |
| 				cids = append(cids, cid+i)
 | |
| 				mappedID, err := mapIDwithMapping(hid+i, parentMapping, mapSetting)
 | |
| 				if err != nil {
 | |
| 					return mappings, flags, err
 | |
| 				}
 | |
| 				hids = append(hids, mappedID)
 | |
| 				sizes = append(sizes, 1)
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		cids = []uint64{cid}
 | |
| 		hids = []uint64{hid}
 | |
| 		sizes = []uint64{sz}
 | |
| 	}
 | |
| 
 | |
| 	// Avoid possible integer overflow on 32bit builds
 | |
| 	if bits.UintSize == 32 {
 | |
| 		for i := range cids {
 | |
| 			if cids[i] > math.MaxInt32 || hids[i] > math.MaxInt32 || sizes[i] > math.MaxInt32 {
 | |
| 				return mappings, flags, fmt.Errorf("initializing ID mappings: %s setting is malformed expected [\"[+ug]uint32:[@]uint32[:uint32]\"] : %q", mapSetting, spec)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	for i := range cids {
 | |
| 		mappings = append(mappings, idtools.IDMap{
 | |
| 			ContainerID: int(cids[i]),
 | |
| 			HostID:      int(hids[i]),
 | |
| 			Size:        int(sizes[i]),
 | |
| 		})
 | |
| 	}
 | |
| 	return mappings, flags, nil
 | |
| }
 | |
| 
 | |
| func min(a, b int) int {
 | |
| 	if a < b {
 | |
| 		return a
 | |
| 	}
 | |
| 	return b
 | |
| }
 | |
| 
 | |
| func max(a, b int) int {
 | |
| 	if a > b {
 | |
| 		return a
 | |
| 	}
 | |
| 	return b
 | |
| }
 | |
| 
 | |
| // Remove any conflicting mapping from mapping present in extension, so
 | |
| // extension can be appended to mapping without conflicts.
 | |
| // Returns the resulting mapping, with extension appended to it.
 | |
| func breakInsert(mapping []idtools.IDMap, extension idtools.IDMap) (result []idtools.IDMap) {
 | |
| 	// Two steps:
 | |
| 	// 1. Remove extension regions from mapping
 | |
| 	//    For each element in mapping, remove those parts of the mapping
 | |
| 	//    that overlap with the extension, both in the container range
 | |
| 	//    or in the host range.
 | |
| 	// 2. Add extension to mapping
 | |
| 	// Step 1: Remove extension regions from mapping
 | |
| 	for _, mapPiece := range mapping {
 | |
| 		// Make container and host ranges comparable, by computing their
 | |
| 		// extension relative to the start of the mapPiece:
 | |
| 		range1Start := extension.ContainerID - mapPiece.ContainerID
 | |
| 		range2Start := extension.HostID - mapPiece.HostID
 | |
| 
 | |
| 		// Range end relative to mapPiece range
 | |
| 		range1End := range1Start + extension.Size
 | |
| 		range2End := range2Start + extension.Size
 | |
| 
 | |
| 		// mapPiece range:
 | |
| 		mapPieceStart := 0
 | |
| 		mapPieceEnd := mapPiece.Size
 | |
| 
 | |
| 		if range1End < mapPieceStart || range1Start >= mapPieceEnd {
 | |
| 			// out of range, forget about it
 | |
| 			range1End = -1
 | |
| 			range1Start = -1
 | |
| 		} else {
 | |
| 			// clip limits removal to mapPiece
 | |
| 			range1End = min(range1End, mapPieceEnd)
 | |
| 			range1Start = max(range1Start, mapPieceStart)
 | |
| 		}
 | |
| 
 | |
| 		if range2End < mapPieceStart || range2Start >= mapPieceEnd {
 | |
| 			// out of range, forget about it
 | |
| 			range2End = -1
 | |
| 			range2Start = -1
 | |
| 		} else {
 | |
| 			// clip limits removal to mapPiece
 | |
| 			range2End = min(range2End, mapPieceEnd)
 | |
| 			range2Start = max(range2Start, mapPieceStart)
 | |
| 		}
 | |
| 
 | |
| 		// If there is nothing to remove, append the original and continue:
 | |
| 		if range1Start == -1 && range2Start == -1 {
 | |
| 			result = append(result, mapPiece)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// If there is one range to remove, save it at range1:
 | |
| 		if range1Start == -1 && range2Start != -1 {
 | |
| 			range1Start = range2Start
 | |
| 			range1End = range2End
 | |
| 			range2Start = -1
 | |
| 			range2End = -1
 | |
| 		}
 | |
| 
 | |
| 		// If we have two valid ranges, merge them into range1 if possible
 | |
| 		if range2Start != -1 {
 | |
| 			// Swap ranges so always range1Start is <= range2Start
 | |
| 			if range2Start < range1Start {
 | |
| 				range1Start, range2Start = range2Start, range1Start
 | |
| 				range1End, range2End = range2End, range1End
 | |
| 			}
 | |
| 			// If there is overlap, merge them:
 | |
| 			if range1End >= range2Start {
 | |
| 				range1End = max(range1End, range2End)
 | |
| 				range2Start = -1
 | |
| 				range2End = -1
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if range1Start > 0 {
 | |
| 			// Append everything before range1Start
 | |
| 			result = append(result, idtools.IDMap{
 | |
| 				ContainerID: mapPiece.ContainerID,
 | |
| 				HostID:      mapPiece.HostID,
 | |
| 				Size:        range1Start,
 | |
| 			})
 | |
| 		}
 | |
| 		if range2Start == -1 {
 | |
| 			// Append everything after range1
 | |
| 			if mapPiece.Size-range1End > 0 {
 | |
| 				result = append(result, idtools.IDMap{
 | |
| 					ContainerID: mapPiece.ContainerID + range1End,
 | |
| 					HostID:      mapPiece.HostID + range1End,
 | |
| 					Size:        mapPiece.Size - range1End,
 | |
| 				})
 | |
| 			}
 | |
| 		} else {
 | |
| 			// Append everything between range1 and range2
 | |
| 			result = append(result, idtools.IDMap{
 | |
| 				ContainerID: mapPiece.ContainerID + range1End,
 | |
| 				HostID:      mapPiece.HostID + range1End,
 | |
| 				Size:        range2Start - range1End,
 | |
| 			})
 | |
| 			// Append everything after range2
 | |
| 			if mapPiece.Size-range2End > 0 {
 | |
| 				result = append(result, idtools.IDMap{
 | |
| 					ContainerID: mapPiece.ContainerID + range2End,
 | |
| 					HostID:      mapPiece.HostID + range2End,
 | |
| 					Size:        mapPiece.Size - range2End,
 | |
| 				})
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	// Step 2. Add extension to mapping
 | |
| 	result = append(result, extension)
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| // A multirange is a list of [start,end) ranges and is expressed as
 | |
| // an array of length-2 integers.
 | |
| //
 | |
| // This function computes availableRanges = fullRanges - usedRanges,
 | |
| // where all variables are multiranges.
 | |
| // The subtraction operation is defined as "return the multirange
 | |
| // containing all integers found in fullRanges and not found in usedRanges.
 | |
| func getAvailableIDRanges(fullRanges, usedRanges [][2]int) (availableRanges [][2]int) {
 | |
| 	// Sort them
 | |
| 	sort.Slice(fullRanges, func(i, j int) bool {
 | |
| 		return fullRanges[i][0] < fullRanges[j][0]
 | |
| 	})
 | |
| 
 | |
| 	if len(usedRanges) == 0 {
 | |
| 		return fullRanges
 | |
| 	}
 | |
| 
 | |
| 	sort.Slice(usedRanges, func(i, j int) bool {
 | |
| 		return usedRanges[i][0] < usedRanges[j][0]
 | |
| 	})
 | |
| 
 | |
| 	// To traverse usedRanges
 | |
| 	i := 0
 | |
| 	nextUsedID := usedRanges[i][0]
 | |
| 	nextUsedIDEnd := usedRanges[i][1]
 | |
| 
 | |
| 	for _, fullRange := range fullRanges {
 | |
| 		currentIDToProcess := fullRange[0]
 | |
| 		for currentIDToProcess < fullRange[1] {
 | |
| 			switch {
 | |
| 			case nextUsedID == -1:
 | |
| 				// No further used ids, append all the remaining ranges
 | |
| 				availableRanges = append(availableRanges, [2]int{currentIDToProcess, fullRange[1]})
 | |
| 				currentIDToProcess = fullRange[1]
 | |
| 			case currentIDToProcess < nextUsedID:
 | |
| 				// currentIDToProcess is not used, append:
 | |
| 				if fullRange[1] <= nextUsedID {
 | |
| 					availableRanges = append(availableRanges, [2]int{currentIDToProcess, fullRange[1]})
 | |
| 					currentIDToProcess = fullRange[1]
 | |
| 				} else {
 | |
| 					availableRanges = append(availableRanges, [2]int{currentIDToProcess, nextUsedID})
 | |
| 					currentIDToProcess = nextUsedID
 | |
| 				}
 | |
| 			case currentIDToProcess == nextUsedID:
 | |
| 				// currentIDToProcess and all ids until nextUsedIDEnd are used
 | |
| 				// Advance currentIDToProcess
 | |
| 				currentIDToProcess = min(fullRange[1], nextUsedIDEnd)
 | |
| 			default: // currentIDToProcess > nextUsedID
 | |
| 				// Increment nextUsedID so it is >= currentIDToProcess
 | |
| 				// Go to next used block if this one is all behind:
 | |
| 				if currentIDToProcess >= nextUsedIDEnd {
 | |
| 					i += 1
 | |
| 					if i == len(usedRanges) {
 | |
| 						// No more used ranges
 | |
| 						nextUsedID = -1
 | |
| 					} else {
 | |
| 						nextUsedID = usedRanges[i][0]
 | |
| 						nextUsedIDEnd = usedRanges[i][1]
 | |
| 					}
 | |
| 					continue
 | |
| 				} else { // currentIDToProcess < nextUsedIDEnd
 | |
| 					currentIDToProcess = min(fullRange[1], nextUsedIDEnd)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return availableRanges
 | |
| }
 | |
| 
 | |
| // Gets the multirange of subordinated ids from parentMapping and the
 | |
| // multirange of already assigned ids from idmap, and returns the
 | |
| // multirange of unassigned subordinated ids.
 | |
| func getAvailableIDRangesFromMappings(idmap []idtools.IDMap, parentMapping []ruser.IDMap) (availableRanges [][2]int) {
 | |
| 	// Get all subordinated ids from parentMapping:
 | |
| 	fullRanges := [][2]int{} // {Multirange: [start, end), [start, end), ...}
 | |
| 	for _, mapPiece := range parentMapping {
 | |
| 		fullRanges = append(fullRanges, [2]int{int(mapPiece.ID), int(mapPiece.ID + mapPiece.Count)})
 | |
| 	}
 | |
| 
 | |
| 	// Get the ids already mapped:
 | |
| 	usedRanges := [][2]int{}
 | |
| 	for _, mapPiece := range idmap {
 | |
| 		usedRanges = append(usedRanges, [2]int{mapPiece.HostID, mapPiece.HostID + mapPiece.Size})
 | |
| 	}
 | |
| 
 | |
| 	// availableRanges = fullRanges - usedRanges
 | |
| 	availableRanges = getAvailableIDRanges(fullRanges, usedRanges)
 | |
| 	return availableRanges
 | |
| }
 | |
| 
 | |
| // Fills unassigned idmap ContainerIDs, starting from zero with all
 | |
| // the available ids given by availableRanges.
 | |
| // Returns the filled idmap.
 | |
| func fillIDMap(idmap []idtools.IDMap, availableRanges [][2]int) (output []idtools.IDMap) {
 | |
| 	idmapByCid := append([]idtools.IDMap{}, idmap...)
 | |
| 	sort.Slice(idmapByCid, func(i, j int) bool {
 | |
| 		return idmapByCid[i].ContainerID < idmapByCid[j].ContainerID
 | |
| 	})
 | |
| 
 | |
| 	if len(availableRanges) == 0 {
 | |
| 		return idmapByCid
 | |
| 	}
 | |
| 
 | |
| 	i := 0 // to iterate through availableRanges
 | |
| 	nextCid := 0
 | |
| 	nextAvailHid := availableRanges[i][0]
 | |
| 
 | |
| 	for _, mapPiece := range idmapByCid {
 | |
| 		// While there are available IDs to map and unassigned
 | |
| 		// container ids, map the available ids:
 | |
| 		for nextCid < mapPiece.ContainerID && nextAvailHid != -1 {
 | |
| 			size := min(mapPiece.ContainerID-nextCid, availableRanges[i][1]-nextAvailHid)
 | |
| 			output = append(output, idtools.IDMap{
 | |
| 				ContainerID: nextCid,
 | |
| 				HostID:      nextAvailHid,
 | |
| 				Size:        size,
 | |
| 			})
 | |
| 			nextCid += size
 | |
| 			if nextAvailHid+size < availableRanges[i][1] {
 | |
| 				nextAvailHid += size
 | |
| 			} else {
 | |
| 				i += 1
 | |
| 				if i == len(availableRanges) {
 | |
| 					nextAvailHid = -1
 | |
| 					continue
 | |
| 				}
 | |
| 				nextAvailHid = availableRanges[i][0]
 | |
| 			}
 | |
| 		}
 | |
| 		// The given mapping does not change
 | |
| 		output = append(output, mapPiece)
 | |
| 		nextCid += mapPiece.Size
 | |
| 	}
 | |
| 	// After the last given mapping is mapped, we use all the remaining
 | |
| 	// ids to map the rest of the space
 | |
| 	for nextAvailHid != -1 {
 | |
| 		size := availableRanges[i][1] - nextAvailHid
 | |
| 		output = append(output, idtools.IDMap{
 | |
| 			ContainerID: nextCid,
 | |
| 			HostID:      nextAvailHid,
 | |
| 			Size:        size,
 | |
| 		})
 | |
| 		nextCid += size
 | |
| 		i += 1
 | |
| 		if i == len(availableRanges) {
 | |
| 			nextAvailHid = -1
 | |
| 			continue
 | |
| 		}
 | |
| 		nextAvailHid = availableRanges[i][0]
 | |
| 	}
 | |
| 	return output
 | |
| }
 | |
| 
 | |
| func addOneMapping(idmap []idtools.IDMap, fillMap bool, mapping idtools.IDMap, flags idMapFlags, mapSetting string) ([]idtools.IDMap, bool) {
 | |
| 	// If we are mapping uids and the spec doesn't have the usermap flag, ignore it
 | |
| 	if mapSetting == "UID" && !flags.UserMap {
 | |
| 		return idmap, fillMap
 | |
| 	}
 | |
| 	// If we are mapping gids and the spec doesn't have the groupmap flag, ignore it
 | |
| 	if mapSetting == "GID" && !flags.GroupMap {
 | |
| 		return idmap, fillMap
 | |
| 	}
 | |
| 
 | |
| 	// Zero-size mapping is ignored
 | |
| 	if mapping.Size == 0 {
 | |
| 		return idmap, fillMap
 | |
| 	}
 | |
| 
 | |
| 	// Not extending, just append:
 | |
| 	if !flags.Extends {
 | |
| 		idmap = append(idmap, mapping)
 | |
| 		return idmap, fillMap
 | |
| 	}
 | |
| 	// Break and extend the last mapping:
 | |
| 
 | |
| 	// Extending without any mapping, if rootless, we will fill
 | |
| 	// the space with the remaining IDs:
 | |
| 	if len(idmap) == 0 && rootless.IsRootless() {
 | |
| 		fillMap = true
 | |
| 	}
 | |
| 
 | |
| 	idmap = breakInsert(idmap, mapping)
 | |
| 	return idmap, fillMap
 | |
| }
 | |
| 
 | |
| // Extension of idTools.ParseIDMap that parses idmap triples from string.
 | |
| // This extension accepts additional flags that control how the mapping is done
 | |
| func ParseIDMap(mapSpec []string, mapSetting string, parentMapping []ruser.IDMap) (idmap []idtools.IDMap, err error) {
 | |
| 	stdErr := fmt.Errorf("initializing ID mappings: %s setting is malformed expected [\"[+ug]uint32:[@]uint32[:uint32]\"] : %q", mapSetting, mapSpec)
 | |
| 	// When fillMap is true, the given mapping will be filled with the remaining subordinate available ids
 | |
| 	fillMap := false
 | |
| 	for _, idMapSpec := range mapSpec {
 | |
| 		if idMapSpec == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 		idSpec := strings.Split(idMapSpec, ":")
 | |
| 		// if it's a length-2 list assume the size is 1:
 | |
| 		if len(idSpec) == 2 {
 | |
| 			idSpec = append(idSpec, "1")
 | |
| 		}
 | |
| 		if len(idSpec)%3 != 0 {
 | |
| 			return nil, stdErr
 | |
| 		}
 | |
| 		for i := range idSpec {
 | |
| 			if i%3 != 0 {
 | |
| 				continue
 | |
| 			}
 | |
| 			if len(idSpec[i]) == 0 {
 | |
| 				return nil, stdErr
 | |
| 			}
 | |
| 			// Parse this mapping:
 | |
| 			mappings, flags, err := parseTriple(idSpec[i:i+3], parentMapping, mapSetting)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			for _, mapping := range mappings {
 | |
| 				idmap, fillMap = addOneMapping(idmap, fillMap, mapping, flags, mapSetting)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if fillMap {
 | |
| 		availableRanges := getAvailableIDRangesFromMappings(idmap, parentMapping)
 | |
| 		idmap = fillIDMap(idmap, availableRanges)
 | |
| 	}
 | |
| 
 | |
| 	if len(idmap) == 0 {
 | |
| 		return idmap, nil
 | |
| 	}
 | |
| 	idmap = sortAndMergeConsecutiveMappings(idmap)
 | |
| 	return idmap, nil
 | |
| }
 | |
| 
 | |
| // Given a mapping, sort all entries by their ContainerID then and merge
 | |
| // entries that are consecutive.
 | |
| func sortAndMergeConsecutiveMappings(idmap []idtools.IDMap) (finalIDMap []idtools.IDMap) {
 | |
| 	idmapByCid := append([]idtools.IDMap{}, idmap...)
 | |
| 	sort.Slice(idmapByCid, func(i, j int) bool {
 | |
| 		return idmapByCid[i].ContainerID < idmapByCid[j].ContainerID
 | |
| 	})
 | |
| 	for i, mapPiece := range idmapByCid {
 | |
| 		if i == 0 {
 | |
| 			finalIDMap = append(finalIDMap, mapPiece)
 | |
| 			continue
 | |
| 		}
 | |
| 		lastMap := finalIDMap[len(finalIDMap)-1]
 | |
| 		containersMatch := lastMap.ContainerID+lastMap.Size == mapPiece.ContainerID
 | |
| 		hostsMatch := lastMap.HostID+lastMap.Size == mapPiece.HostID
 | |
| 		if containersMatch && hostsMatch {
 | |
| 			finalIDMap[len(finalIDMap)-1].Size += mapPiece.Size
 | |
| 		} else {
 | |
| 			finalIDMap = append(finalIDMap, mapPiece)
 | |
| 		}
 | |
| 	}
 | |
| 	return finalIDMap
 | |
| }
 | |
| 
 | |
| // Extension of idTools.parseAutoTriple that parses idmap triples.
 | |
| // The triple should be a length 3 string array, containing:
 | |
| // - Flags and ContainerID
 | |
| // - HostID
 | |
| // - Size
 | |
| //
 | |
| // parseAutoTriple returns the parsed mapping and any possible error.
 | |
| // If the error is not-nil, the mapping is not well-defined.
 | |
| //
 | |
| // idTools.parseAutoTriple is extended here with the following enhancements:
 | |
| //
 | |
| // HostID @ syntax:
 | |
| // =================
 | |
| // HostID may use the "@" syntax: The "101001:@1001:1" mapping
 | |
| // means "take the 1001 id from the parent namespace and map it to 101001"
 | |
| func parseAutoTriple(spec []string, parentMapping []ruser.IDMap, mapSetting string) (mappings []idtools.IDMap, err error) {
 | |
| 	if len(spec[0]) == 0 {
 | |
| 		return mappings, fmt.Errorf("invalid empty container id at %s map: %v", mapSetting, spec)
 | |
| 	}
 | |
| 	var cids, hids, sizes []uint64
 | |
| 	var cid, hid uint64
 | |
| 	var hidIsParent bool
 | |
| 	// Parse the container ID, which must be an integer:
 | |
| 	cid, err = strconv.ParseUint(spec[0][0:], 10, 32)
 | |
| 	if err != nil {
 | |
| 		return mappings, fmt.Errorf("parsing id map value %q: %w", spec[0], err)
 | |
| 	}
 | |
| 	// Parse the host id, which may be integer or @<integer>
 | |
| 	if len(spec[1]) == 0 {
 | |
| 		return mappings, fmt.Errorf("invalid empty host id at %s map: %v", mapSetting, spec)
 | |
| 	}
 | |
| 	if spec[1][0] != '@' {
 | |
| 		hidIsParent = false
 | |
| 		hid, err = strconv.ParseUint(spec[1], 10, 32)
 | |
| 	} else {
 | |
| 		// Parse @<id>, where <id> is an integer corresponding to the parent mapping
 | |
| 		hidIsParent = true
 | |
| 		hid, err = strconv.ParseUint(spec[1][1:], 10, 32)
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		return mappings, fmt.Errorf("parsing id map value %q: %w", spec[1], err)
 | |
| 	}
 | |
| 	// Parse the size of the mapping, which must be an integer
 | |
| 	sz, err := strconv.ParseUint(spec[2], 10, 32)
 | |
| 	if err != nil {
 | |
| 		return mappings, fmt.Errorf("parsing id map value %q: %w", spec[2], err)
 | |
| 	}
 | |
| 
 | |
| 	if hidIsParent {
 | |
| 		for i := uint64(0); i < sz; i++ {
 | |
| 			cids = append(cids, cid+i)
 | |
| 			mappedID, err := mapIDwithMapping(hid+i, parentMapping, mapSetting)
 | |
| 			if err != nil {
 | |
| 				return mappings, err
 | |
| 			}
 | |
| 			hids = append(hids, mappedID)
 | |
| 			sizes = append(sizes, 1)
 | |
| 		}
 | |
| 	} else {
 | |
| 		cids = []uint64{cid}
 | |
| 		hids = []uint64{hid}
 | |
| 		sizes = []uint64{sz}
 | |
| 	}
 | |
| 
 | |
| 	// Avoid possible integer overflow on 32bit builds
 | |
| 	if bits.UintSize == 32 {
 | |
| 		for i := range cids {
 | |
| 			if cids[i] > math.MaxInt32 || hids[i] > math.MaxInt32 || sizes[i] > math.MaxInt32 {
 | |
| 				return mappings, fmt.Errorf("initializing ID mappings: %s setting is malformed expected [\"[+ug]uint32:[@]uint32[:uint32]\"] : %q", mapSetting, spec)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	for i := range cids {
 | |
| 		mappings = append(mappings, idtools.IDMap{
 | |
| 			ContainerID: int(cids[i]),
 | |
| 			HostID:      int(hids[i]),
 | |
| 			Size:        int(sizes[i]),
 | |
| 		})
 | |
| 	}
 | |
| 	return mappings, nil
 | |
| }
 | |
| 
 | |
| // Extension of idTools.ParseIDMap that parses idmap triples from string.
 | |
| // This extension accepts additional flags that control how the mapping is done
 | |
| func parseAutoIDMap(mapSpec string, mapSetting string, parentMapping []ruser.IDMap) (idmap []idtools.IDMap, err error) {
 | |
| 	stdErr := fmt.Errorf("initializing ID mappings: %s setting is malformed expected [\"uint32:[@]uint32[:uint32]\"] : %q", mapSetting, mapSpec)
 | |
| 	idSpec := strings.Split(mapSpec, ":")
 | |
| 	// if it's a length-2 list assume the size is 1:
 | |
| 	if len(idSpec) == 2 {
 | |
| 		idSpec = append(idSpec, "1")
 | |
| 	}
 | |
| 	if len(idSpec) != 3 {
 | |
| 		return nil, stdErr
 | |
| 	}
 | |
| 	// Parse this mapping:
 | |
| 	mappings, err := parseAutoTriple(idSpec, parentMapping, mapSetting)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	idmap = sortAndMergeConsecutiveMappings(mappings)
 | |
| 	return idmap, nil
 | |
| }
 | |
| 
 | |
| // GetAutoOptions returns an AutoUserNsOptions with the settings to automatically set up
 | |
| // a user namespace.
 | |
| func GetAutoOptions(n namespaces.UsernsMode) (*stypes.AutoUserNsOptions, error) {
 | |
| 	mode, opts, hasOpts := strings.Cut(string(n), ":")
 | |
| 	if mode != "auto" {
 | |
| 		return nil, fmt.Errorf("wrong user namespace mode")
 | |
| 	}
 | |
| 	options := stypes.AutoUserNsOptions{}
 | |
| 	if !hasOpts {
 | |
| 		return &options, nil
 | |
| 	}
 | |
| 
 | |
| 	parentUIDMap, parentGIDMap, err := rootless.GetAvailableIDMaps()
 | |
| 	if err != nil {
 | |
| 		if errors.Is(err, fs.ErrNotExist) {
 | |
| 			// The kernel-provided files only exist if user namespaces are supported
 | |
| 			logrus.Debugf("User or group ID mappings not available: %s", err)
 | |
| 		} else {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, o := range strings.Split(opts, ",") {
 | |
| 		key, val, hasVal := strings.Cut(o, "=")
 | |
| 		if !hasVal {
 | |
| 			return nil, fmt.Errorf("invalid option specified: %q", o)
 | |
| 		}
 | |
| 		switch key {
 | |
| 		case "size":
 | |
| 			s, err := strconv.ParseUint(val, 10, 32)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			options.Size = uint32(s)
 | |
| 		case "uidmapping":
 | |
| 			mapping, err := parseAutoIDMap(val, "UID", parentUIDMap)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			options.AdditionalUIDMappings = append(options.AdditionalUIDMappings, mapping...)
 | |
| 		case "gidmapping":
 | |
| 			mapping, err := parseAutoIDMap(val, "GID", parentGIDMap)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			options.AdditionalGIDMappings = append(options.AdditionalGIDMappings, mapping...)
 | |
| 		default:
 | |
| 			return nil, fmt.Errorf("unknown option specified: %q", key)
 | |
| 		}
 | |
| 	}
 | |
| 	return &options, nil
 | |
| }
 | |
| 
 | |
| // ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping
 | |
| func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*stypes.IDMappingOptions, error) {
 | |
| 	options := stypes.IDMappingOptions{
 | |
| 		HostUIDMapping: true,
 | |
| 		HostGIDMapping: true,
 | |
| 	}
 | |
| 
 | |
| 	if mode.IsAuto() {
 | |
| 		var err error
 | |
| 		options.HostUIDMapping = false
 | |
| 		options.HostGIDMapping = false
 | |
| 		options.AutoUserNs = true
 | |
| 		opts, err := GetAutoOptions(mode)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		options.AutoUserNsOpts = *opts
 | |
| 		return &options, nil
 | |
| 	}
 | |
| 	if mode.IsKeepID() || mode.IsNoMap() {
 | |
| 		options.HostUIDMapping = false
 | |
| 		options.HostGIDMapping = false
 | |
| 		return &options, nil
 | |
| 	}
 | |
| 
 | |
| 	if subGIDMap == "" && subUIDMap != "" {
 | |
| 		subGIDMap = subUIDMap
 | |
| 	}
 | |
| 	if subUIDMap == "" && subGIDMap != "" {
 | |
| 		subUIDMap = subGIDMap
 | |
| 	}
 | |
| 	if len(gidMapSlice) == 0 && len(uidMapSlice) != 0 {
 | |
| 		gidMapSlice = uidMapSlice
 | |
| 	}
 | |
| 	if len(uidMapSlice) == 0 && len(gidMapSlice) != 0 {
 | |
| 		uidMapSlice = gidMapSlice
 | |
| 	}
 | |
| 
 | |
| 	if subUIDMap != "" && subGIDMap != "" {
 | |
| 		mappings, err := idtools.NewIDMappings(subUIDMap, subGIDMap)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		options.UIDMap = mappings.UIDs()
 | |
| 		options.GIDMap = mappings.GIDs()
 | |
| 	}
 | |
| 
 | |
| 	parentUIDMap, parentGIDMap, err := rootless.GetAvailableIDMaps()
 | |
| 	if err != nil {
 | |
| 		if errors.Is(err, fs.ErrNotExist) {
 | |
| 			// The kernel-provided files only exist if user namespaces are supported
 | |
| 			logrus.Debugf("User or group ID mappings not available: %s", err)
 | |
| 		} else {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	parsedUIDMap, err := ParseIDMap(uidMapSlice, "UID", parentUIDMap)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	parsedGIDMap, err := ParseIDMap(gidMapSlice, "GID", parentGIDMap)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// When running rootless, if one of UID/GID mappings is provided, fill the other one:
 | |
| 	if rootless.IsRootless() {
 | |
| 		switch {
 | |
| 		case len(parsedUIDMap) != 0 && len(parsedGIDMap) == 0:
 | |
| 			availableRanges := getAvailableIDRangesFromMappings(parsedGIDMap, parentGIDMap)
 | |
| 			parsedGIDMap = fillIDMap(parsedGIDMap, availableRanges)
 | |
| 		case len(parsedUIDMap) == 0 && len(parsedGIDMap) != 0:
 | |
| 			availableRanges := getAvailableIDRangesFromMappings(parsedUIDMap, parentUIDMap)
 | |
| 			parsedUIDMap = fillIDMap(parsedUIDMap, availableRanges)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	options.UIDMap = append(options.UIDMap, parsedUIDMap...)
 | |
| 	options.GIDMap = append(options.GIDMap, parsedGIDMap...)
 | |
| 	if len(options.UIDMap) > 0 {
 | |
| 		options.HostUIDMapping = false
 | |
| 	}
 | |
| 	if len(options.GIDMap) > 0 {
 | |
| 		options.HostGIDMapping = false
 | |
| 	}
 | |
| 	return &options, nil
 | |
| }
 | |
| 
 | |
| type tomlOptionsConfig struct {
 | |
| 	MountProgram string `toml:"mount_program"`
 | |
| }
 | |
| 
 | |
| type tomlConfig struct {
 | |
| 	Storage struct {
 | |
| 		Driver    string                      `toml:"driver"`
 | |
| 		RunRoot   string                      `toml:"runroot"`
 | |
| 		GraphRoot string                      `toml:"graphroot"`
 | |
| 		Options   struct{ tomlOptionsConfig } `toml:"options"`
 | |
| 	} `toml:"storage"`
 | |
| }
 | |
| 
 | |
| func getTomlStorage(storeOptions *stypes.StoreOptions) *tomlConfig {
 | |
| 	config := new(tomlConfig)
 | |
| 
 | |
| 	config.Storage.Driver = storeOptions.GraphDriverName
 | |
| 	config.Storage.RunRoot = storeOptions.RunRoot
 | |
| 	config.Storage.GraphRoot = storeOptions.GraphRoot
 | |
| 	for _, i := range storeOptions.GraphDriverOptions {
 | |
| 		program, hasPrefix := strings.CutPrefix(i, "overlay.mount_program=")
 | |
| 		if hasPrefix {
 | |
| 			config.Storage.Options.MountProgram = program
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return config
 | |
| }
 | |
| 
 | |
| // WriteStorageConfigFile writes the configuration to a file
 | |
| func WriteStorageConfigFile(storageOpts *stypes.StoreOptions, storageConf string) error {
 | |
| 	if err := os.MkdirAll(filepath.Dir(storageConf), 0755); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	storageFile, err := os.OpenFile(storageConf, os.O_RDWR|os.O_TRUNC, 0600)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	tomlConfiguration := getTomlStorage(storageOpts)
 | |
| 	defer errorhandling.CloseQuiet(storageFile)
 | |
| 	enc := toml.NewEncoder(storageFile)
 | |
| 	if err := enc.Encode(tomlConfiguration); err != nil {
 | |
| 		if err := os.Remove(storageConf); err != nil {
 | |
| 			logrus.Error(err)
 | |
| 		}
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // ParseInputTime takes the users input and to determine if it is valid and
 | |
| // returns a time format and error.  The input is compared to known time formats
 | |
| // or a duration which implies no-duration
 | |
| func ParseInputTime(inputTime string, since bool) (time.Time, error) {
 | |
| 	timeFormats := []string{time.RFC3339Nano, time.RFC3339, "2006-01-02T15:04:05", "2006-01-02T15:04:05.999999999",
 | |
| 		"2006-01-02Z07:00", "2006-01-02"}
 | |
| 	// iterate the supported time formats
 | |
| 	for _, tf := range timeFormats {
 | |
| 		t, err := time.Parse(tf, inputTime)
 | |
| 		if err == nil {
 | |
| 			return t, nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	unixTimestamp, err := strconv.ParseFloat(inputTime, 64)
 | |
| 	if err == nil {
 | |
| 		iPart, fPart := math.Modf(unixTimestamp)
 | |
| 		return time.Unix(int64(iPart), int64(fPart*1_000_000_000)).UTC(), nil
 | |
| 	}
 | |
| 
 | |
| 	// input might be a duration
 | |
| 	duration, err := time.ParseDuration(inputTime)
 | |
| 	if err != nil {
 | |
| 		return time.Time{}, errors.New("unable to interpret time value")
 | |
| 	}
 | |
| 	if since {
 | |
| 		return time.Now().Add(-duration), nil
 | |
| 	}
 | |
| 	return time.Now().Add(duration), nil
 | |
| }
 | |
| 
 | |
| // OpenExclusiveFile opens a file for writing and ensure it doesn't already exist
 | |
| func OpenExclusiveFile(path string) (*os.File, error) {
 | |
| 	baseDir := filepath.Dir(path)
 | |
| 	if baseDir != "" {
 | |
| 		if err := fileutils.Exists(baseDir); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
 | |
| }
 | |
| 
 | |
| // ExitCode reads the error message when failing to executing container process
 | |
| // and then returns 0 if no error, 126 if command does not exist, or 127 for
 | |
| // all other errors
 | |
| func ExitCode(err error) int {
 | |
| 	if err == nil {
 | |
| 		return 0
 | |
| 	}
 | |
| 	e := strings.ToLower(err.Error())
 | |
| 	if strings.Contains(e, "file not found") ||
 | |
| 		strings.Contains(e, "no such file or directory") {
 | |
| 		return 127
 | |
| 	}
 | |
| 
 | |
| 	return 126
 | |
| }
 | |
| 
 | |
| func Tmpdir() string {
 | |
| 	tmpdir := os.Getenv("TMPDIR")
 | |
| 	if tmpdir == "" {
 | |
| 		tmpdir = "/var/tmp"
 | |
| 	}
 | |
| 
 | |
| 	return tmpdir
 | |
| }
 | |
| 
 | |
| // ValidateSysctls validates a list of sysctl and returns it.
 | |
| func ValidateSysctls(strSlice []string) (map[string]string, error) {
 | |
| 	sysctl := make(map[string]string)
 | |
| 	validSysctlMap := map[string]bool{
 | |
| 		"kernel.msgmax":          true,
 | |
| 		"kernel.msgmnb":          true,
 | |
| 		"kernel.msgmni":          true,
 | |
| 		"kernel.sem":             true,
 | |
| 		"kernel.shmall":          true,
 | |
| 		"kernel.shmmax":          true,
 | |
| 		"kernel.shmmni":          true,
 | |
| 		"kernel.shm_rmid_forced": true,
 | |
| 	}
 | |
| 	validSysctlPrefixes := []string{
 | |
| 		"net.",
 | |
| 		"fs.mqueue.",
 | |
| 	}
 | |
| 
 | |
| 	for _, val := range strSlice {
 | |
| 		foundMatch := false
 | |
| 		arr := strings.Split(val, "=")
 | |
| 		if len(arr) < 2 {
 | |
| 			return nil, fmt.Errorf("%s is invalid, sysctl values must be in the form of KEY=VALUE", val)
 | |
| 		}
 | |
| 
 | |
| 		trimmed := fmt.Sprintf("%s=%s", strings.TrimSpace(arr[0]), strings.TrimSpace(arr[1]))
 | |
| 		if trimmed != val {
 | |
| 			return nil, fmt.Errorf("'%s' is invalid, extra spaces found", val)
 | |
| 		}
 | |
| 
 | |
| 		if validSysctlMap[arr[0]] {
 | |
| 			sysctl[arr[0]] = arr[1]
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		for _, prefix := range validSysctlPrefixes {
 | |
| 			if strings.HasPrefix(arr[0], prefix) {
 | |
| 				sysctl[arr[0]] = arr[1]
 | |
| 				foundMatch = true
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 		if !foundMatch {
 | |
| 			return nil, fmt.Errorf("sysctl '%s' is not allowed", arr[0])
 | |
| 		}
 | |
| 	}
 | |
| 	return sysctl, nil
 | |
| }
 | |
| 
 | |
| func DefaultContainerConfig() *config.Config {
 | |
| 	return containerConfig
 | |
| }
 | |
| 
 | |
| func CreateIDFile(path string, id string) error {
 | |
| 	idFile, err := os.Create(path)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("creating idfile: %w", err)
 | |
| 	}
 | |
| 	defer idFile.Close()
 | |
| 	if _, err = idFile.WriteString(id); err != nil {
 | |
| 		return fmt.Errorf("writing idfile: %w", err)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // DefaultCPUPeriod is the default CPU period (100ms) in microseconds, which is
 | |
| // the same default as Kubernetes.
 | |
| const DefaultCPUPeriod uint64 = 100000
 | |
| 
 | |
| // CoresToPeriodAndQuota converts a fraction of cores to the equivalent
 | |
| // Completely Fair Scheduler (CFS) parameters period and quota.
 | |
| //
 | |
| // Cores is a fraction of the CFS period that a container may use. Period and
 | |
| // Quota are in microseconds.
 | |
| func CoresToPeriodAndQuota(cores float64) (uint64, int64) {
 | |
| 	return DefaultCPUPeriod, int64(cores * float64(DefaultCPUPeriod))
 | |
| }
 | |
| 
 | |
| // PeriodAndQuotaToCores takes the CFS parameters period and quota and returns
 | |
| // a fraction that represents the limit to the number of cores that can be
 | |
| // utilized over the scheduling period.
 | |
| //
 | |
| // Cores is a fraction of the CFS period that a container may use. Period and
 | |
| // Quota are in microseconds.
 | |
| func PeriodAndQuotaToCores(period uint64, quota int64) float64 {
 | |
| 	return float64(quota) / float64(period)
 | |
| }
 | |
| 
 | |
| // IDtoolsToRuntimeSpec converts idtools ID mapping to the one of the runtime spec.
 | |
| func IDtoolsToRuntimeSpec(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) {
 | |
| 	for _, idmap := range idMaps {
 | |
| 		tempIDMap := specs.LinuxIDMapping{
 | |
| 			ContainerID: uint32(idmap.ContainerID),
 | |
| 			HostID:      uint32(idmap.HostID),
 | |
| 			Size:        uint32(idmap.Size),
 | |
| 		}
 | |
| 		convertedIDMap = append(convertedIDMap, tempIDMap)
 | |
| 	}
 | |
| 	return convertedIDMap
 | |
| }
 | |
| 
 | |
| // RuntimeSpecToIDtoolsTo converts runtime spec to the one of the idtools ID mapping
 | |
| func RuntimeSpecToIDtools(idMaps []specs.LinuxIDMapping) (convertedIDMap []idtools.IDMap) {
 | |
| 	for _, idmap := range idMaps {
 | |
| 		tempIDMap := idtools.IDMap{
 | |
| 			ContainerID: int(idmap.ContainerID),
 | |
| 			HostID:      int(idmap.HostID),
 | |
| 			Size:        int(idmap.Size),
 | |
| 		}
 | |
| 		convertedIDMap = append(convertedIDMap, tempIDMap)
 | |
| 	}
 | |
| 	return convertedIDMap
 | |
| }
 | |
| 
 | |
| func LookupUser(name string) (*user.User, error) {
 | |
| 	// Assume UID lookup first, if it fails look up by username
 | |
| 	if u, err := user.LookupId(name); err == nil {
 | |
| 		return u, nil
 | |
| 	}
 | |
| 	return user.Lookup(name)
 | |
| }
 | |
| 
 | |
| // SizeOfPath determines the file usage of a given path. it was called volumeSize in v1
 | |
| // and now is made to be generic and take a path instead of a libpod volume
 | |
| // Deprecated: use github.com/containers/storage/pkg/directory.Size() instead.
 | |
| func SizeOfPath(path string) (uint64, error) {
 | |
| 	size, err := directory.Size(path)
 | |
| 	return uint64(size), err
 | |
| }
 | |
| 
 | |
| // ParseRestartPolicy parses the value given to the --restart flag and returns the policy
 | |
| // and restart retries value
 | |
| func ParseRestartPolicy(policy string) (string, uint, error) {
 | |
| 	var (
 | |
| 		retriesUint uint
 | |
| 		policyType  string
 | |
| 	)
 | |
| 	splitRestart := strings.Split(policy, ":")
 | |
| 	switch len(splitRestart) {
 | |
| 	case 1:
 | |
| 		// No retries specified
 | |
| 		policyType = splitRestart[0]
 | |
| 		if strings.ToLower(splitRestart[0]) == "never" {
 | |
| 			policyType = define.RestartPolicyNo
 | |
| 		}
 | |
| 	case 2:
 | |
| 		if strings.ToLower(splitRestart[0]) != "on-failure" {
 | |
| 			return "", 0, errors.New("restart policy retries can only be specified with on-failure restart policy")
 | |
| 		}
 | |
| 		retries, err := strconv.Atoi(splitRestart[1])
 | |
| 		if err != nil {
 | |
| 			return "", 0, fmt.Errorf("parsing restart policy retry count: %w", err)
 | |
| 		}
 | |
| 		if retries < 0 {
 | |
| 			return "", 0, errors.New("must specify restart policy retry count as a number greater than 0")
 | |
| 		}
 | |
| 		retriesUint = uint(retries)
 | |
| 		policyType = splitRestart[0]
 | |
| 	default:
 | |
| 		return "", 0, errors.New("invalid restart policy: may specify retries at most once")
 | |
| 	}
 | |
| 	return policyType, retriesUint, nil
 | |
| }
 | |
| 
 | |
| // ConvertTimeout converts negative timeout to MaxUint32, which indicates approximately infinity, waiting to stop containers
 | |
| func ConvertTimeout(timeout int) uint {
 | |
| 	if timeout < 0 {
 | |
| 		return math.MaxUint32
 | |
| 	}
 | |
| 	return uint(timeout)
 | |
| }
 | |
| 
 | |
| // ExecAddTERM when container does not have a TERM environment variable and
 | |
| // caller wants a tty, then leak the existing TERM environment into
 | |
| // the container.
 | |
| func ExecAddTERM(existingEnv []string, execEnvs map[string]string) {
 | |
| 	if _, ok := execEnvs["TERM"]; ok {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	for _, val := range existingEnv {
 | |
| 		if strings.HasPrefix(val, "TERM=") {
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	execEnvs["TERM"] = "xterm"
 | |
| }
 | 
