github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/domain/infra/runtime_libpod.go (about)

     1  // +build !remote
     2  
     3  package infra
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"os"
     9  	"os/signal"
    10  	"sync"
    11  
    12  	"github.com/containers/podman/v2/cmd/podman/utils"
    13  	"github.com/containers/podman/v2/libpod"
    14  	"github.com/containers/podman/v2/pkg/cgroups"
    15  	"github.com/containers/podman/v2/pkg/domain/entities"
    16  	"github.com/containers/podman/v2/pkg/namespaces"
    17  	"github.com/containers/podman/v2/pkg/rootless"
    18  	"github.com/containers/storage"
    19  	"github.com/containers/storage/pkg/idtools"
    20  	"github.com/pkg/errors"
    21  	"github.com/sirupsen/logrus"
    22  	flag "github.com/spf13/pflag"
    23  )
    24  
    25  var (
    26  	// runtimeSync only guards the non-specialized runtime
    27  	runtimeSync sync.Once
    28  	// The default GetRuntime() always returns the same object and error
    29  	runtimeLib *libpod.Runtime
    30  	runtimeErr error
    31  )
    32  
    33  type engineOpts struct {
    34  	name     string
    35  	renumber bool
    36  	migrate  bool
    37  	noStore  bool
    38  	withFDS  bool
    39  	config   *entities.PodmanConfig
    40  }
    41  
    42  // GetRuntimeMigrate gets a libpod runtime that will perform a migration of existing containers
    43  func GetRuntimeMigrate(ctx context.Context, fs *flag.FlagSet, cfg *entities.PodmanConfig, newRuntime string) (*libpod.Runtime, error) {
    44  	return getRuntime(ctx, fs, &engineOpts{
    45  		name:     newRuntime,
    46  		renumber: false,
    47  		migrate:  true,
    48  		noStore:  false,
    49  		withFDS:  true,
    50  		config:   cfg,
    51  	})
    52  }
    53  
    54  // GetRuntimeDisableFDs gets a libpod runtime that will disable sd notify
    55  func GetRuntimeDisableFDs(ctx context.Context, fs *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) {
    56  	return getRuntime(ctx, fs, &engineOpts{
    57  		renumber: false,
    58  		migrate:  false,
    59  		noStore:  false,
    60  		withFDS:  false,
    61  		config:   cfg,
    62  	})
    63  }
    64  
    65  // GetRuntimeRenumber gets a libpod runtime that will perform a lock renumber
    66  func GetRuntimeRenumber(ctx context.Context, fs *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) {
    67  	return getRuntime(ctx, fs, &engineOpts{
    68  		renumber: true,
    69  		migrate:  false,
    70  		noStore:  false,
    71  		withFDS:  true,
    72  		config:   cfg,
    73  	})
    74  }
    75  
    76  // GetRuntime generates a new libpod runtime configured by command line options
    77  func GetRuntime(ctx context.Context, flags *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) {
    78  	runtimeSync.Do(func() {
    79  		runtimeLib, runtimeErr = getRuntime(ctx, flags, &engineOpts{
    80  			renumber: false,
    81  			migrate:  false,
    82  			noStore:  false,
    83  			withFDS:  true,
    84  			config:   cfg,
    85  		})
    86  	})
    87  	return runtimeLib, runtimeErr
    88  }
    89  
    90  // GetRuntimeNoStore generates a new libpod runtime configured by command line options
    91  func GetRuntimeNoStore(ctx context.Context, fs *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) {
    92  	return getRuntime(ctx, fs, &engineOpts{
    93  		renumber: false,
    94  		migrate:  false,
    95  		noStore:  true,
    96  		withFDS:  true,
    97  		config:   cfg,
    98  	})
    99  }
   100  
   101  func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpod.Runtime, error) {
   102  	options := []libpod.RuntimeOption{}
   103  	storageOpts := storage.StoreOptions{}
   104  	cfg := opts.config
   105  
   106  	storageSet := false
   107  
   108  	uidmapFlag := fs.Lookup("uidmap")
   109  	gidmapFlag := fs.Lookup("gidmap")
   110  	subuidname := fs.Lookup("subuidname")
   111  	subgidname := fs.Lookup("subgidname")
   112  	if (uidmapFlag != nil && gidmapFlag != nil && subuidname != nil && subgidname != nil) &&
   113  		(uidmapFlag.Changed || gidmapFlag.Changed || subuidname.Changed || subgidname.Changed) {
   114  		userns, _ := fs.GetString("userns")
   115  		uidmapVal, _ := fs.GetStringSlice("uidmap")
   116  		gidmapVal, _ := fs.GetStringSlice("gidmap")
   117  		subuidVal, _ := fs.GetString("subuidname")
   118  		subgidVal, _ := fs.GetString("subgidname")
   119  		mappings, err := ParseIDMapping(namespaces.UsernsMode(userns), uidmapVal, gidmapVal, subuidVal, subgidVal)
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  		storageOpts.UIDMap = mappings.UIDMap
   124  		storageOpts.GIDMap = mappings.GIDMap
   125  
   126  		storageSet = true
   127  	}
   128  
   129  	if fs.Changed("root") {
   130  		storageSet = true
   131  		storageOpts.GraphRoot = cfg.Engine.StaticDir
   132  	}
   133  	if fs.Changed("runroot") {
   134  		storageSet = true
   135  		storageOpts.RunRoot = cfg.Runroot
   136  	}
   137  	if len(storageOpts.RunRoot) > 50 {
   138  		return nil, errors.New("the specified runroot is longer than 50 characters")
   139  	}
   140  	if fs.Changed("storage-driver") {
   141  		storageSet = true
   142  		storageOpts.GraphDriverName = cfg.StorageDriver
   143  		// Overriding the default storage driver caused GraphDriverOptions from storage.conf to be ignored
   144  		storageOpts.GraphDriverOptions = []string{}
   145  	}
   146  	// This should always be checked after storage-driver is checked
   147  	if len(cfg.StorageOpts) > 0 {
   148  		storageSet = true
   149  		storageOpts.GraphDriverOptions = cfg.StorageOpts
   150  	}
   151  	if opts.migrate {
   152  		options = append(options, libpod.WithMigrate())
   153  		if opts.name != "" {
   154  			options = append(options, libpod.WithMigrateRuntime(opts.name))
   155  		}
   156  	}
   157  
   158  	if opts.renumber {
   159  		options = append(options, libpod.WithRenumber())
   160  	}
   161  
   162  	if len(cfg.RuntimeFlags) > 0 {
   163  		runtimeFlags := []string{}
   164  		for _, arg := range cfg.RuntimeFlags {
   165  			runtimeFlags = append(runtimeFlags, "--"+arg)
   166  		}
   167  		options = append(options, libpod.WithRuntimeFlags(runtimeFlags))
   168  	}
   169  
   170  	// Only set this if the user changes storage config on the command line
   171  	if storageSet {
   172  		options = append(options, libpod.WithStorageConfig(storageOpts))
   173  	}
   174  
   175  	if !storageSet && opts.noStore {
   176  		options = append(options, libpod.WithNoStore())
   177  	}
   178  	// TODO CLI flags for image config?
   179  	// TODO CLI flag for signature policy?
   180  
   181  	if len(cfg.Engine.Namespace) > 0 {
   182  		options = append(options, libpod.WithNamespace(cfg.Engine.Namespace))
   183  	}
   184  
   185  	if fs.Changed("runtime") {
   186  		options = append(options, libpod.WithOCIRuntime(cfg.RuntimePath))
   187  	}
   188  
   189  	if fs.Changed("conmon") {
   190  		options = append(options, libpod.WithConmonPath(cfg.ConmonPath))
   191  	}
   192  	if fs.Changed("tmpdir") {
   193  		options = append(options, libpod.WithTmpDir(cfg.Engine.TmpDir))
   194  	}
   195  	if fs.Changed("network-cmd-path") {
   196  		options = append(options, libpod.WithNetworkCmdPath(cfg.Engine.NetworkCmdPath))
   197  	}
   198  
   199  	if fs.Changed("events-backend") {
   200  		options = append(options, libpod.WithEventsLogger(cfg.Engine.EventsLogger))
   201  	}
   202  
   203  	if fs.Changed("cgroup-manager") {
   204  		options = append(options, libpod.WithCgroupManager(cfg.Engine.CgroupManager))
   205  	} else {
   206  		unified, err := cgroups.IsCgroup2UnifiedMode()
   207  		if err != nil {
   208  			return nil, err
   209  		}
   210  		if rootless.IsRootless() && !unified {
   211  			options = append(options, libpod.WithCgroupManager("cgroupfs"))
   212  		}
   213  	}
   214  
   215  	// TODO flag to set libpod static dir?
   216  	// TODO flag to set libpod tmp dir?
   217  
   218  	if fs.Changed("cni-config-dir") {
   219  		options = append(options, libpod.WithCNIConfigDir(cfg.Network.NetworkConfigDir))
   220  	}
   221  	if fs.Changed("default-mounts-file") {
   222  		options = append(options, libpod.WithDefaultMountsFile(cfg.Containers.DefaultMountsFile))
   223  	}
   224  	if fs.Changed("hooks-dir") {
   225  		options = append(options, libpod.WithHooksDir(cfg.Engine.HooksDir...))
   226  	}
   227  	if fs.Changed("registries-conf") {
   228  		options = append(options, libpod.WithRegistriesConf(cfg.RegistriesConf))
   229  	}
   230  
   231  	// TODO flag to set CNI plugins dir?
   232  
   233  	if !opts.withFDS {
   234  		options = append(options, libpod.WithEnableSDNotify())
   235  	}
   236  	return libpod.NewRuntime(ctx, options...)
   237  }
   238  
   239  // ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping
   240  func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*storage.IDMappingOptions, error) {
   241  	options := storage.IDMappingOptions{
   242  		HostUIDMapping: true,
   243  		HostGIDMapping: true,
   244  	}
   245  
   246  	if mode.IsAuto() {
   247  		var err error
   248  		options.HostUIDMapping = false
   249  		options.HostGIDMapping = false
   250  		options.AutoUserNs = true
   251  		opts, err := mode.GetAutoOptions()
   252  		if err != nil {
   253  			return nil, err
   254  		}
   255  		options.AutoUserNsOpts = *opts
   256  		return &options, nil
   257  	}
   258  	if mode.IsKeepID() {
   259  		if len(uidMapSlice) > 0 || len(gidMapSlice) > 0 {
   260  			return nil, errors.New("cannot specify custom mappings with --userns=keep-id")
   261  		}
   262  		if len(subUIDMap) > 0 || len(subGIDMap) > 0 {
   263  			return nil, errors.New("cannot specify subuidmap or subgidmap with --userns=keep-id")
   264  		}
   265  		if rootless.IsRootless() {
   266  			min := func(a, b int) int {
   267  				if a < b {
   268  					return a
   269  				}
   270  				return b
   271  			}
   272  
   273  			uid := rootless.GetRootlessUID()
   274  			gid := rootless.GetRootlessGID()
   275  
   276  			uids, gids, err := rootless.GetConfiguredMappings()
   277  			if err != nil {
   278  				return nil, errors.Wrapf(err, "cannot read mappings")
   279  			}
   280  			maxUID, maxGID := 0, 0
   281  			for _, u := range uids {
   282  				maxUID += u.Size
   283  			}
   284  			for _, g := range gids {
   285  				maxGID += g.Size
   286  			}
   287  
   288  			options.UIDMap, options.GIDMap = nil, nil
   289  
   290  			options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)})
   291  			options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1})
   292  			if maxUID > uid {
   293  				options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid})
   294  			}
   295  
   296  			options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)})
   297  			options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1})
   298  			if maxGID > gid {
   299  				options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid})
   300  			}
   301  
   302  			options.HostUIDMapping = false
   303  			options.HostGIDMapping = false
   304  		}
   305  		// Simply ignore the setting and do not setup an inner namespace for root as it is a no-op
   306  		return &options, nil
   307  	}
   308  
   309  	if subGIDMap == "" && subUIDMap != "" {
   310  		subGIDMap = subUIDMap
   311  	}
   312  	if subUIDMap == "" && subGIDMap != "" {
   313  		subUIDMap = subGIDMap
   314  	}
   315  	if len(gidMapSlice) == 0 && len(uidMapSlice) != 0 {
   316  		gidMapSlice = uidMapSlice
   317  	}
   318  	if len(uidMapSlice) == 0 && len(gidMapSlice) != 0 {
   319  		uidMapSlice = gidMapSlice
   320  	}
   321  	if len(uidMapSlice) == 0 && subUIDMap == "" && os.Getuid() != 0 {
   322  		uidMapSlice = []string{fmt.Sprintf("0:%d:1", os.Getuid())}
   323  	}
   324  	if len(gidMapSlice) == 0 && subGIDMap == "" && os.Getuid() != 0 {
   325  		gidMapSlice = []string{fmt.Sprintf("0:%d:1", os.Getgid())}
   326  	}
   327  
   328  	if subUIDMap != "" && subGIDMap != "" {
   329  		mappings, err := idtools.NewIDMappings(subUIDMap, subGIDMap)
   330  		if err != nil {
   331  			return nil, err
   332  		}
   333  		options.UIDMap = mappings.UIDs()
   334  		options.GIDMap = mappings.GIDs()
   335  	}
   336  	parsedUIDMap, err := idtools.ParseIDMap(uidMapSlice, "UID")
   337  	if err != nil {
   338  		return nil, err
   339  	}
   340  	parsedGIDMap, err := idtools.ParseIDMap(gidMapSlice, "GID")
   341  	if err != nil {
   342  		return nil, err
   343  	}
   344  	options.UIDMap = append(options.UIDMap, parsedUIDMap...)
   345  	options.GIDMap = append(options.GIDMap, parsedGIDMap...)
   346  	if len(options.UIDMap) > 0 {
   347  		options.HostUIDMapping = false
   348  	}
   349  	if len(options.GIDMap) > 0 {
   350  		options.HostGIDMapping = false
   351  	}
   352  	return &options, nil
   353  }
   354  
   355  // StartWatcher starts a new SIGHUP go routine for the current config.
   356  func StartWatcher(rt *libpod.Runtime) {
   357  	// Setup the signal notifier
   358  	ch := make(chan os.Signal, 1)
   359  	signal.Notify(ch, utils.SIGHUP)
   360  
   361  	go func() {
   362  		for {
   363  			// Block until the signal is received
   364  			logrus.Debugf("waiting for SIGHUP to reload configuration")
   365  			<-ch
   366  			if err := rt.Reload(); err != nil {
   367  				logrus.Errorf("unable to reload configuration: %v", err)
   368  				continue
   369  			}
   370  		}
   371  	}()
   372  
   373  	logrus.Debugf("registered SIGHUP watcher for config")
   374  }