gitee.com/mysnapcore/mysnapd@v0.1.0/cmd/snap/cmd_run.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  /*
     3   * Copyright (C) 2014-2022 Canonical Ltd
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License version 3 as
     7   * published by the Free Software Foundation.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   *
    17   */
    18  
    19  package main
    20  
    21  import (
    22  	"bufio"
    23  	"encoding/json"
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  	"net"
    29  	"os"
    30  	"os/exec"
    31  	"os/user"
    32  	"path/filepath"
    33  	"regexp"
    34  	"strconv"
    35  	"strings"
    36  	"syscall"
    37  	"time"
    38  
    39  	"github.com/godbus/dbus"
    40  	"github.com/jessevdk/go-flags"
    41  
    42  	"gitee.com/mysnapcore/mysnapd/client"
    43  	"gitee.com/mysnapcore/mysnapd/desktop/portal"
    44  	"gitee.com/mysnapcore/mysnapd/dirs"
    45  	"gitee.com/mysnapcore/mysnapd/features"
    46  	"gitee.com/mysnapcore/mysnapd/i18n"
    47  	"gitee.com/mysnapcore/mysnapd/interfaces"
    48  	"gitee.com/mysnapcore/mysnapd/logger"
    49  	"gitee.com/mysnapcore/mysnapd/osutil"
    50  	"gitee.com/mysnapcore/mysnapd/osutil/strace"
    51  	"gitee.com/mysnapcore/mysnapd/sandbox/cgroup"
    52  	"gitee.com/mysnapcore/mysnapd/sandbox/selinux"
    53  	"gitee.com/mysnapcore/mysnapd/snap"
    54  	"gitee.com/mysnapcore/mysnapd/snap/snapenv"
    55  	"gitee.com/mysnapcore/mysnapd/strutil/shlex"
    56  	"gitee.com/mysnapcore/mysnapd/timeutil"
    57  	"gitee.com/mysnapcore/mysnapd/x11"
    58  )
    59  
    60  var (
    61  	syscallExec              = syscall.Exec
    62  	userCurrent              = user.Current
    63  	osGetenv                 = os.Getenv
    64  	timeNow                  = time.Now
    65  	selinuxIsEnabled         = selinux.IsEnabled
    66  	selinuxVerifyPathContext = selinux.VerifyPathContext
    67  	selinuxRestoreContext    = selinux.RestoreContext
    68  )
    69  
    70  type cmdRun struct {
    71  	clientMixin
    72  	Command  string `long:"command" hidden:"yes"`
    73  	HookName string `long:"hook" hidden:"yes"`
    74  	Revision string `short:"r" default:"unset" hidden:"yes"`
    75  	Shell    bool   `long:"shell" `
    76  	DebugLog bool   `long:"debug-log"`
    77  
    78  	// This options is both a selector (use or don't use strace) and it
    79  	// can also carry extra options for strace. This is why there is
    80  	// "default" and "optional-value" to distinguish this.
    81  	Strace string `long:"strace" optional:"true" optional-value:"with-strace" default:"no-strace" default-mask:"-"`
    82  	// deprecated in favor of Gdbserver
    83  	Gdb                   bool   `long:"gdb" hidden:"yes"`
    84  	Gdbserver             string `long:"gdbserver" default:"no-gdbserver" optional-value:":0" optional:"true"`
    85  	ExperimentalGdbserver string `long:"experimental-gdbserver" default:"no-gdbserver" optional-value:":0" optional:"true" hidden:"yes"`
    86  	TraceExec             bool   `long:"trace-exec"`
    87  
    88  	// not a real option, used to check if cmdRun is initialized by
    89  	// the parser
    90  	ParserRan int    `long:"parser-ran" default:"1" hidden:"yes"`
    91  	Timer     string `long:"timer" hidden:"yes"`
    92  }
    93  
    94  func init() {
    95  	addCommand("run",
    96  		i18n.G("Run the given snap command"),
    97  		i18n.G(`
    98  The run command executes the given snap command with the right confinement
    99  and environment.
   100  `),
   101  		func() flags.Commander {
   102  			return &cmdRun{}
   103  		}, map[string]string{
   104  			// TRANSLATORS: This should not start with a lowercase letter.
   105  			"command": i18n.G("Alternative command to run"),
   106  			// TRANSLATORS: This should not start with a lowercase letter.
   107  			"hook": i18n.G("Hook to run"),
   108  			// TRANSLATORS: This should not start with a lowercase letter.
   109  			"r": i18n.G("Use a specific snap revision when running hook"),
   110  			// TRANSLATORS: This should not start with a lowercase letter.
   111  			"shell": i18n.G("Run a shell instead of the command (useful for debugging)"),
   112  			// TRANSLATORS: This should not start with a lowercase letter.
   113  			"strace": i18n.G("Run the command under strace (useful for debugging). Extra strace options can be specified as well here. Pass --raw to strace early snap helpers."),
   114  			// TRANSLATORS: This should not start with a lowercase letter.
   115  			"gdb": i18n.G("Run the command with gdb (deprecated, use --gdbserver instead)"),
   116  			// TRANSLATORS: This should not start with a lowercase letter.
   117  			"gdbserver":              i18n.G("Run the command with gdbserver"),
   118  			"experimental-gdbserver": "",
   119  			// TRANSLATORS: This should not start with a lowercase letter.
   120  			"timer": i18n.G("Run as a timer service with given schedule"),
   121  			// TRANSLATORS: This should not start with a lowercase letter.
   122  			"trace-exec": i18n.G("Display exec calls timing data"),
   123  			// TRANSLATORS: This should not start with a lowercase letter.
   124  			"debug-log":  i18n.G("Enable debug logging during early snap startup phases"),
   125  			"parser-ran": "",
   126  		}, nil)
   127  }
   128  
   129  // isStopping returns true if the system is shutting down.
   130  func isStopping() (bool, error) {
   131  	// Make sure, just in case, that systemd doesn't localize the output string.
   132  	env, err := osutil.OSEnvironment()
   133  	if err != nil {
   134  		return false, err
   135  	}
   136  	env["LC_MESSAGES"] = "C"
   137  	// Check if systemd is stopping (shutting down or rebooting).
   138  	cmd := exec.Command("systemctl", "is-system-running")
   139  	cmd.Env = env.ForExec()
   140  	stdout, err := cmd.Output()
   141  	// systemctl is-system-running returns non-zero for outcomes other than "running"
   142  	// As such, ignore any ExitError and just process the stdout buffer.
   143  	if _, ok := err.(*exec.ExitError); ok {
   144  		return string(stdout) == "stopping\n", nil
   145  	}
   146  	return false, err
   147  }
   148  
   149  func maybeWaitForSecurityProfileRegeneration(cli *client.Client) error {
   150  	// check if the security profiles key has changed, if so, we need
   151  	// to wait for snapd to re-generate all profiles
   152  	mismatch, err := interfaces.SystemKeyMismatch()
   153  	if err == nil && !mismatch {
   154  		return nil
   155  	}
   156  	// something went wrong with the system-key compare, try to
   157  	// reach snapd before continuing
   158  	if err != nil {
   159  		logger.Debugf("SystemKeyMismatch returned an error: %v", err)
   160  	}
   161  
   162  	// We have a mismatch but maybe it is only because systemd is shutting down
   163  	// and core or snapd were already unmounted and we failed to re-execute.
   164  	// For context see: https://bugs.launchpad.net/snapd/+bug/1871652
   165  	stopping, err := isStopping()
   166  	if err != nil {
   167  		logger.Debugf("cannot check if system is stopping: %s", err)
   168  	}
   169  	if stopping {
   170  		logger.Debugf("ignoring system key mismatch during system shutdown/reboot")
   171  		return nil
   172  	}
   173  
   174  	// We have a mismatch, try to connect to snapd, once we can
   175  	// connect we just continue because that usually means that
   176  	// a new snapd is ready and has generated profiles.
   177  	//
   178  	// There is a corner case if an upgrade leaves the old snapd
   179  	// running and we connect to the old snapd. Handling this
   180  	// correctly is tricky because our "snap run" pipeline may
   181  	// depend on profiles written by the new snapd. So for now we
   182  	// just continue and hope for the best. The real fix for this
   183  	// is to fix the packaging so that snapd is stopped, upgraded
   184  	// and started.
   185  	//
   186  	// connect timeout for client is 5s on each try, so 12*5s = 60s
   187  	timeout := 12
   188  	if timeoutEnv := os.Getenv("SNAPD_DEBUG_SYSTEM_KEY_RETRY"); timeoutEnv != "" {
   189  		if i, err := strconv.Atoi(timeoutEnv); err == nil {
   190  			timeout = i
   191  		}
   192  	}
   193  
   194  	logger.Debugf("system key mismatch detected, waiting for snapd to start responding...")
   195  
   196  	for i := 0; i < timeout; i++ {
   197  		// TODO: we could also check cli.Maintenance() here too in case snapd is
   198  		// down semi-permanently for a refresh, but what message do we show to
   199  		// the user or what do we do if we know snapd is down for maintenance?
   200  		if _, err := cli.SysInfo(); err == nil {
   201  			return nil
   202  		}
   203  		// sleep a little bit for good measure
   204  		time.Sleep(1 * time.Second)
   205  	}
   206  
   207  	return fmt.Errorf("timeout waiting for snap system profiles to get updated")
   208  }
   209  
   210  func (x *cmdRun) Usage() string {
   211  	return "[run-OPTIONS] <NAME-OF-SNAP>.<NAME-OF-APP> [<SNAP-APP-ARG>...]"
   212  }
   213  
   214  func (x *cmdRun) Execute(args []string) error {
   215  	if len(args) == 0 {
   216  		return fmt.Errorf(i18n.G("need the application to run as argument"))
   217  	}
   218  	snapApp := args[0]
   219  	args = args[1:]
   220  
   221  	// Catch some invalid parameter combinations, provide helpful errors
   222  	optionsSet := 0
   223  	for _, param := range []string{x.HookName, x.Command, x.Timer} {
   224  		if param != "" {
   225  			optionsSet++
   226  		}
   227  	}
   228  	if optionsSet > 1 {
   229  		return fmt.Errorf("you can only use one of --hook, --command, and --timer")
   230  	}
   231  
   232  	if x.Revision != "unset" && x.Revision != "" && x.HookName == "" {
   233  		return fmt.Errorf(i18n.G("-r can only be used with --hook"))
   234  	}
   235  	if x.HookName != "" && len(args) > 0 {
   236  		// TRANSLATORS: %q is the hook name; %s a space-separated list of extra arguments
   237  		return fmt.Errorf(i18n.G("too many arguments for hook %q: %s"), x.HookName, strings.Join(args, " "))
   238  	}
   239  
   240  	logger.StartupStageTimestamp("start")
   241  
   242  	if err := maybeWaitForSecurityProfileRegeneration(x.client); err != nil {
   243  		return err
   244  	}
   245  
   246  	// Now actually handle the dispatching
   247  	if x.HookName != "" {
   248  		return x.snapRunHook(snapApp)
   249  	}
   250  
   251  	if x.Command == "complete" {
   252  		snapApp, args = antialias(snapApp, args)
   253  	}
   254  
   255  	if x.Timer != "" {
   256  		return x.snapRunTimer(snapApp, x.Timer, args)
   257  	}
   258  
   259  	return x.snapRunApp(snapApp, args)
   260  }
   261  
   262  func maybeWaitWhileInhibited(snapName string) error {
   263  	// If the snap is inhibited from being used then postpone running it until
   264  	// that condition passes. Inhibition UI can be dismissed by the user, in
   265  	// which case we don't run the application at all.
   266  	if features.RefreshAppAwareness.IsEnabled() {
   267  		return waitWhileInhibited(snapName)
   268  	}
   269  	return nil
   270  }
   271  
   272  // antialias changes snapApp and args if snapApp is actually an alias
   273  // for something else. If not, or if the args aren't what's expected
   274  // for completion, it returns them unchanged.
   275  func antialias(snapApp string, args []string) (string, []string) {
   276  	if len(args) < 7 {
   277  		// NOTE if len(args) < 7, Something is Wrong (at least WRT complete.sh and etelpmoc.sh)
   278  		return snapApp, args
   279  	}
   280  
   281  	actualApp, err := resolveApp(snapApp)
   282  	if err != nil || actualApp == snapApp {
   283  		// no alias! woop.
   284  		return snapApp, args
   285  	}
   286  
   287  	compPoint, err := strconv.Atoi(args[2])
   288  	if err != nil {
   289  		// args[2] is not COMP_POINT
   290  		return snapApp, args
   291  	}
   292  
   293  	if compPoint <= len(snapApp) {
   294  		// COMP_POINT is inside $0
   295  		return snapApp, args
   296  	}
   297  
   298  	if compPoint > len(args[5]) {
   299  		// COMP_POINT is bigger than $#
   300  		return snapApp, args
   301  	}
   302  
   303  	if args[6] != snapApp {
   304  		// args[6] is not COMP_WORDS[0]
   305  		return snapApp, args
   306  	}
   307  
   308  	// it _should_ be COMP_LINE followed by one of
   309  	// COMP_WORDBREAKS, but that's hard to do
   310  	re, err := regexp.Compile(`^` + regexp.QuoteMeta(snapApp) + `\b`)
   311  	if err != nil || !re.MatchString(args[5]) {
   312  		// (weird regexp error, or) args[5] is not COMP_LINE
   313  		return snapApp, args
   314  	}
   315  
   316  	argsOut := make([]string, len(args))
   317  	copy(argsOut, args)
   318  
   319  	argsOut[2] = strconv.Itoa(compPoint - len(snapApp) + len(actualApp))
   320  	argsOut[5] = re.ReplaceAllLiteralString(args[5], actualApp)
   321  	argsOut[6] = actualApp
   322  
   323  	return actualApp, argsOut
   324  }
   325  
   326  func getSnapInfo(snapName string, revision snap.Revision) (info *snap.Info, err error) {
   327  	if revision.Unset() {
   328  		info, err = snap.ReadCurrentInfo(snapName)
   329  	} else {
   330  		info, err = snap.ReadInfo(snapName, &snap.SideInfo{
   331  			Revision: revision,
   332  		})
   333  	}
   334  
   335  	return info, err
   336  }
   337  
   338  func createOrUpdateUserDataSymlink(info *snap.Info, usr *user.User, opts *dirs.SnapDirOptions) error {
   339  	// 'current' symlink for user data (SNAP_USER_DATA)
   340  	userData := info.UserDataDir(usr.HomeDir, opts)
   341  	wantedSymlinkValue := filepath.Base(userData)
   342  	currentActiveSymlink := filepath.Join(userData, "..", "current")
   343  
   344  	var err error
   345  	var currentSymlinkValue string
   346  	for i := 0; i < 5; i++ {
   347  		currentSymlinkValue, err = os.Readlink(currentActiveSymlink)
   348  		// Failure other than non-existing symlink is fatal
   349  		if err != nil && !os.IsNotExist(err) {
   350  			// TRANSLATORS: %v the error message
   351  			return fmt.Errorf(i18n.G("cannot read symlink: %v"), err)
   352  		}
   353  
   354  		if currentSymlinkValue == wantedSymlinkValue {
   355  			break
   356  		}
   357  
   358  		if err == nil {
   359  			// We may be racing with other instances of snap-run that try to do the same thing
   360  			// If the symlink is already removed then we can ignore this error.
   361  			err = os.Remove(currentActiveSymlink)
   362  			if err != nil && !os.IsNotExist(err) {
   363  				// abort with error
   364  				break
   365  			}
   366  		}
   367  
   368  		err = os.Symlink(wantedSymlinkValue, currentActiveSymlink)
   369  		// Error other than symlink already exists will abort and be propagated
   370  		if err == nil || !os.IsExist(err) {
   371  			break
   372  		}
   373  		// If we arrived here it means the symlink couldn't be created because it got created
   374  		// in the meantime by another instance, so we will try again.
   375  	}
   376  	if err != nil {
   377  		return fmt.Errorf(i18n.G("cannot update the 'current' symlink of %q: %v"), currentActiveSymlink, err)
   378  	}
   379  	return nil
   380  }
   381  
   382  func createUserDataDirs(info *snap.Info, opts *dirs.SnapDirOptions) error {
   383  	if opts == nil {
   384  		opts = &dirs.SnapDirOptions{}
   385  	}
   386  
   387  	// Adjust umask so that the created directories have the permissions we
   388  	// expect and are unaffected by the initial umask. While go runtime creates
   389  	// threads at will behind the scenes, the setting of umask applies to the
   390  	// entire process so it doesn't need any special handling to lock the
   391  	// executing goroutine to a single thread.
   392  	oldUmask := syscall.Umask(0)
   393  	defer syscall.Umask(oldUmask)
   394  
   395  	usr, err := userCurrent()
   396  	if err != nil {
   397  		return fmt.Errorf(i18n.G("cannot get the current user: %v"), err)
   398  	}
   399  
   400  	snapDir := snap.SnapDir(usr.HomeDir, opts)
   401  	if err := os.MkdirAll(snapDir, 0700); err != nil {
   402  		return fmt.Errorf(i18n.G("cannot create snap home dir: %w"), err)
   403  	}
   404  	// see snapenv.User
   405  	instanceUserData := info.UserDataDir(usr.HomeDir, opts)
   406  	instanceCommonUserData := info.UserCommonDataDir(usr.HomeDir, opts)
   407  	createDirs := []string{instanceUserData, instanceCommonUserData}
   408  
   409  	if info.InstanceKey != "" {
   410  		// parallel instance snaps get additional mapping in their mount
   411  		// namespace, namely /home/joe/snap/foo_bar ->
   412  		// /home/joe/snap/foo, make sure that the mount point exists and
   413  		// is owned by the user
   414  		snapUserDir := snap.UserSnapDir(usr.HomeDir, info.SnapName(), opts)
   415  		createDirs = append(createDirs, snapUserDir)
   416  	}
   417  	for _, d := range createDirs {
   418  		if err := os.MkdirAll(d, 0755); err != nil {
   419  			// TRANSLATORS: %q is the directory whose creation failed, %v the error message
   420  			return fmt.Errorf(i18n.G("cannot create %q: %v"), d, err)
   421  		}
   422  	}
   423  
   424  	if err := createOrUpdateUserDataSymlink(info, usr, opts); err != nil {
   425  		return err
   426  	}
   427  
   428  	return maybeRestoreSecurityContext(usr, opts)
   429  }
   430  
   431  // maybeRestoreSecurityContext attempts to restore security context of ~/snap on
   432  // systems where it's applicable
   433  func maybeRestoreSecurityContext(usr *user.User, opts *dirs.SnapDirOptions) error {
   434  	snapUserHome := snap.SnapDir(usr.HomeDir, opts)
   435  	enabled, err := selinuxIsEnabled()
   436  	if err != nil {
   437  		return fmt.Errorf("cannot determine SELinux status: %v", err)
   438  	}
   439  	if !enabled {
   440  		logger.Debugf("SELinux not enabled")
   441  		return nil
   442  	}
   443  
   444  	match, err := selinuxVerifyPathContext(snapUserHome)
   445  	if err != nil {
   446  		return fmt.Errorf("failed to verify SELinux context of %v: %v", snapUserHome, err)
   447  	}
   448  	if match {
   449  		return nil
   450  	}
   451  	logger.Noticef("restoring default SELinux context of %v", snapUserHome)
   452  
   453  	if err := selinuxRestoreContext(snapUserHome, selinux.RestoreMode{Recursive: true}); err != nil {
   454  		return fmt.Errorf("cannot restore SELinux context of %v: %v", snapUserHome, err)
   455  	}
   456  	return nil
   457  }
   458  
   459  func (x *cmdRun) useStrace() bool {
   460  	// make sure the go-flag parser ran and assigned default values
   461  	return x.ParserRan == 1 && x.Strace != "no-strace"
   462  }
   463  
   464  func (x *cmdRun) straceOpts() (opts []string, raw bool, err error) {
   465  	if x.Strace == "with-strace" {
   466  		return nil, false, nil
   467  	}
   468  
   469  	split, err := shlex.Split(x.Strace)
   470  	if err != nil {
   471  		return nil, false, err
   472  	}
   473  
   474  	opts = make([]string, 0, len(split))
   475  	for _, opt := range split {
   476  		if opt == "--raw" {
   477  			raw = true
   478  			continue
   479  		}
   480  		opts = append(opts, opt)
   481  	}
   482  	return opts, raw, nil
   483  }
   484  
   485  func (x *cmdRun) snapRunApp(snapApp string, args []string) error {
   486  	if x.DebugLog {
   487  		os.Setenv("SNAPD_DEBUG", "1")
   488  		logger.Debugf("enabled debug logging of early snap startup")
   489  	}
   490  	snapName, appName := snap.SplitSnapApp(snapApp)
   491  	info, err := getSnapInfo(snapName, snap.R(0))
   492  	if err != nil {
   493  		return err
   494  	}
   495  
   496  	app := info.Apps[appName]
   497  	if app == nil {
   498  		return fmt.Errorf(i18n.G("cannot find app %q in %q"), appName, snapName)
   499  	}
   500  
   501  	if !app.IsService() {
   502  		if err := maybeWaitWhileInhibited(snapName); err != nil {
   503  			return err
   504  		}
   505  	}
   506  
   507  	return x.runSnapConfine(info, app.SecurityTag(), snapApp, "", args)
   508  }
   509  
   510  func (x *cmdRun) snapRunHook(snapName string) error {
   511  	revision, err := snap.ParseRevision(x.Revision)
   512  	if err != nil {
   513  		return err
   514  	}
   515  
   516  	info, err := getSnapInfo(snapName, revision)
   517  	if err != nil {
   518  		return err
   519  	}
   520  
   521  	hook := info.Hooks[x.HookName]
   522  	if hook == nil {
   523  		return fmt.Errorf(i18n.G("cannot find hook %q in %q"), x.HookName, snapName)
   524  	}
   525  
   526  	return x.runSnapConfine(info, hook.SecurityTag(), snapName, hook.Name, nil)
   527  }
   528  
   529  func (x *cmdRun) snapRunTimer(snapApp, timer string, args []string) error {
   530  	schedule, err := timeutil.ParseSchedule(timer)
   531  	if err != nil {
   532  		return fmt.Errorf("invalid timer format: %v", err)
   533  	}
   534  
   535  	now := timeNow()
   536  	if !timeutil.Includes(schedule, now) {
   537  		fmt.Fprintf(Stderr, "%s: attempted to run %q timer outside of scheduled time %q\n", now.Format(time.RFC3339), snapApp, timer)
   538  		return nil
   539  	}
   540  
   541  	return x.snapRunApp(snapApp, args)
   542  }
   543  
   544  var osReadlink = os.Readlink
   545  
   546  // snapdHelperPath return the path of a helper like "snap-confine" or
   547  // "snap-exec" based on if snapd is re-execed or not
   548  func snapdHelperPath(toolName string) (string, error) {
   549  	exe, err := osReadlink("/proc/self/exe")
   550  	if err != nil {
   551  		return "", fmt.Errorf("cannot read /proc/self/exe: %v", err)
   552  	}
   553  	// no re-exec
   554  	if !strings.HasPrefix(exe, dirs.SnapMountDir) {
   555  		return filepath.Join(dirs.DistroLibExecDir, toolName), nil
   556  	}
   557  	// The logic below only works if the last two path components
   558  	// are /usr/bin
   559  	// FIXME: use a snap warning?
   560  	if !strings.HasSuffix(exe, "/usr/bin/"+filepath.Base(exe)) {
   561  		logger.Noticef("(internal error): unexpected exe input in snapdHelperPath: %v", exe)
   562  		return filepath.Join(dirs.DistroLibExecDir, toolName), nil
   563  	}
   564  	// snapBase will be "/snap/{core,snapd}/$rev/" because
   565  	// the snap binary is always at $root/usr/bin/snap
   566  	snapBase := filepath.Clean(filepath.Join(filepath.Dir(exe), "..", ".."))
   567  	// Run snap-confine from the core/snapd snap.  The tools in
   568  	// core/snapd snap are statically linked, or mostly
   569  	// statically, with the exception of libraries such as libudev
   570  	// and libc.
   571  	return filepath.Join(snapBase, dirs.CoreLibExecDir, toolName), nil
   572  }
   573  
   574  func migrateXauthority(info *snap.Info) (string, error) {
   575  	u, err := userCurrent()
   576  	if err != nil {
   577  		return "", fmt.Errorf(i18n.G("cannot get the current user: %s"), err)
   578  	}
   579  
   580  	// If our target directory (XDG_RUNTIME_DIR) doesn't exist we
   581  	// don't attempt to create it.
   582  	baseTargetDir := filepath.Join(dirs.XdgRuntimeDirBase, u.Uid)
   583  	if !osutil.FileExists(baseTargetDir) {
   584  		return "", nil
   585  	}
   586  
   587  	xauthPath := osGetenv("XAUTHORITY")
   588  	if len(xauthPath) == 0 || !osutil.FileExists(xauthPath) {
   589  		// Nothing to do for us. Most likely running outside of any
   590  		// graphical X11 session.
   591  		return "", nil
   592  	}
   593  
   594  	fin, err := os.Open(xauthPath)
   595  	if err != nil {
   596  		return "", err
   597  	}
   598  	defer fin.Close()
   599  
   600  	// Abs() also calls Clean(); see https://golang.org/pkg/path/filepath/#Abs
   601  	xauthPathAbs, err := filepath.Abs(fin.Name())
   602  	if err != nil {
   603  		return "", nil
   604  	}
   605  
   606  	// Remove all symlinks from path
   607  	xauthPathCan, err := filepath.EvalSymlinks(xauthPathAbs)
   608  	if err != nil {
   609  		return "", nil
   610  	}
   611  
   612  	// Ensure the XAUTHORITY env is not abused by checking that
   613  	// it point to exactly the file we just opened (no symlinks,
   614  	// no funny "../.." etc)
   615  	if fin.Name() != xauthPathCan {
   616  		logger.Noticef("WARNING: XAUTHORITY environment value is not a clean path: %q", xauthPathCan)
   617  		return "", nil
   618  	}
   619  
   620  	// Only do the migration from /tmp since the real /tmp is not visible for snaps
   621  	if !strings.HasPrefix(fin.Name(), "/tmp/") {
   622  		return "", nil
   623  	}
   624  
   625  	// We are performing a Stat() here to make sure that the user can't
   626  	// steal another user's Xauthority file. Note that while Stat() uses
   627  	// fstat() on the file descriptor created during Open(), the file might
   628  	// have changed ownership between the Open() and the Stat(). That's ok
   629  	// because we aren't trying to block access that the user already has:
   630  	// if the user has the privileges to chown another user's Xauthority
   631  	// file, we won't block that since the user can just steal it without
   632  	// having to use snap run. This code is just to ensure that a user who
   633  	// doesn't have those privileges can't steal the file via snap run
   634  	// (also note that the (potentially untrusted) snap isn't running yet).
   635  	fi, err := fin.Stat()
   636  	if err != nil {
   637  		return "", err
   638  	}
   639  	sys := fi.Sys()
   640  	if sys == nil {
   641  		return "", fmt.Errorf(i18n.G("cannot validate owner of file %s"), fin.Name())
   642  	}
   643  	// cheap comparison as the current uid is only available as a string
   644  	// but it is better to convert the uid from the stat result to a
   645  	// string than a string into a number.
   646  	if fmt.Sprintf("%d", sys.(*syscall.Stat_t).Uid) != u.Uid {
   647  		return "", fmt.Errorf(i18n.G("Xauthority file isn't owned by the current user %s"), u.Uid)
   648  	}
   649  
   650  	targetPath := filepath.Join(baseTargetDir, ".Xauthority")
   651  
   652  	// Only validate Xauthority file again when both files don't match
   653  	// otherwise we can continue using the existing Xauthority file.
   654  	// This is ok to do here because we aren't trying to protect against
   655  	// the user changing the Xauthority file in XDG_RUNTIME_DIR outside
   656  	// of snapd.
   657  	if osutil.FileExists(targetPath) {
   658  		var fout *os.File
   659  		if fout, err = os.Open(targetPath); err != nil {
   660  			return "", err
   661  		}
   662  		if osutil.StreamsEqual(fin, fout) {
   663  			fout.Close()
   664  			return targetPath, nil
   665  		}
   666  
   667  		fout.Close()
   668  		if err := os.Remove(targetPath); err != nil {
   669  			return "", err
   670  		}
   671  
   672  		// Ensure we're validating the Xauthority file from the beginning
   673  		if _, err := fin.Seek(int64(os.SEEK_SET), 0); err != nil {
   674  			return "", err
   675  		}
   676  	}
   677  
   678  	// To guard against setting XAUTHORITY to non-xauth files, check
   679  	// that we have a valid Xauthority. Specifically, the file must be
   680  	// parseable as an Xauthority file and not be empty.
   681  	if err := x11.ValidateXauthority(fin); err != nil {
   682  		return "", err
   683  	}
   684  
   685  	// Read data from the beginning of the file
   686  	if _, err = fin.Seek(int64(os.SEEK_SET), 0); err != nil {
   687  		return "", err
   688  	}
   689  
   690  	fout, err := os.OpenFile(targetPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
   691  	if err != nil {
   692  		return "", err
   693  	}
   694  	defer fout.Close()
   695  
   696  	// Read and write validated Xauthority file to its right location
   697  	if _, err = io.Copy(fout, fin); err != nil {
   698  		if err := os.Remove(targetPath); err != nil {
   699  			logger.Noticef("WARNING: cannot remove file at %s: %s", targetPath, err)
   700  		}
   701  		return "", fmt.Errorf(i18n.G("cannot write new Xauthority file at %s: %s"), targetPath, err)
   702  	}
   703  
   704  	return targetPath, nil
   705  }
   706  
   707  func activateXdgDocumentPortal(info *snap.Info, snapApp, hook string) error {
   708  	// Don't do anything for apps or hooks that don't plug the
   709  	// desktop interface
   710  	//
   711  	// NOTE: This check is imperfect because we don't really know
   712  	// if the interface is connected or not but this is an
   713  	// acceptable compromise for not having to communicate with
   714  	// snapd in snap run. In a typical desktop session the
   715  	// document portal can be in use by many applications, not
   716  	// just by snaps, so this is at most, pre-emptively using some
   717  	// extra memory.
   718  	var plugs map[string]*snap.PlugInfo
   719  	if hook != "" {
   720  		plugs = info.Hooks[hook].Plugs
   721  	} else {
   722  		_, appName := snap.SplitSnapApp(snapApp)
   723  		plugs = info.Apps[appName].Plugs
   724  	}
   725  	plugsDesktop := false
   726  	for _, plug := range plugs {
   727  		if plug.Interface == "desktop" {
   728  			plugsDesktop = true
   729  			break
   730  		}
   731  	}
   732  	if !plugsDesktop {
   733  		return nil
   734  	}
   735  
   736  	documentPortal := &portal.Document{}
   737  	expectedMountPoint, err := documentPortal.GetDefaultMountPoint()
   738  	if err != nil {
   739  		return err
   740  	}
   741  
   742  	// If $XDG_RUNTIME_DIR/doc appears to be a mount point, assume
   743  	// that the document portal is up and running.
   744  	if mounted, err := osutil.IsMounted(expectedMountPoint); err != nil {
   745  		logger.Noticef("Could not check document portal mount state: %s", err)
   746  	} else if mounted {
   747  		return nil
   748  	}
   749  
   750  	// If there is no session bus, our job is done.  We check this
   751  	// manually to avoid dbus.SessionBus() auto-launching a new
   752  	// bus.
   753  	busAddress := osGetenv("DBUS_SESSION_BUS_ADDRESS")
   754  	if len(busAddress) == 0 {
   755  		return nil
   756  	}
   757  
   758  	// We've previously tried to start the document portal and
   759  	// were told the service is unknown: don't bother connecting
   760  	// to the session bus again.
   761  	//
   762  	// As the file is in $XDG_RUNTIME_DIR, it will be cleared over
   763  	// full logout/login or reboot cycles.
   764  	xdgRuntimeDir, err := documentPortal.GetUserXdgRuntimeDir()
   765  	if err != nil {
   766  		return err
   767  	}
   768  
   769  	portalsUnavailableFile := filepath.Join(xdgRuntimeDir, ".portals-unavailable")
   770  	if osutil.FileExists(portalsUnavailableFile) {
   771  		return nil
   772  	}
   773  
   774  	actualMountPoint, err := documentPortal.GetMountPoint()
   775  	if err != nil {
   776  		// It is not considered an error if
   777  		// xdg-document-portal is not available on the system.
   778  		if dbusErr, ok := err.(dbus.Error); ok && dbusErr.Name == "org.freedesktop.DBus.Error.ServiceUnknown" {
   779  			// We ignore errors here: if writing the file
   780  			// fails, we'll just try connecting to D-Bus
   781  			// again next time.
   782  			if err = ioutil.WriteFile(portalsUnavailableFile, []byte(""), 0644); err != nil {
   783  				logger.Noticef("WARNING: cannot write file at %s: %s", portalsUnavailableFile, err)
   784  			}
   785  			return nil
   786  		}
   787  		return err
   788  	}
   789  
   790  	// Quick check to make sure the document portal is exposed
   791  	// where we think it is.
   792  	if actualMountPoint != expectedMountPoint {
   793  		return fmt.Errorf(i18n.G("Expected portal at %#v, got %#v"), expectedMountPoint, actualMountPoint)
   794  	}
   795  	return nil
   796  }
   797  
   798  type envForExecFunc func(extra map[string]string) []string
   799  
   800  var gdbServerWelcomeFmt = `
   801  Welcome to "snap run --gdbserver".
   802  You are right before your application is run.
   803  Please open a different terminal and run:
   804  
   805  gdb -ex="target remote %[1]s" -ex=continue -ex="signal SIGCONT"
   806  (gdb) continue
   807  
   808  or use your favorite gdb frontend and connect to %[1]s
   809  `
   810  
   811  func racyFindFreePort() (int, error) {
   812  	l, err := net.Listen("tcp", ":0")
   813  	if err != nil {
   814  		return 0, err
   815  	}
   816  	defer l.Close()
   817  	return l.Addr().(*net.TCPAddr).Port, nil
   818  }
   819  
   820  func (x *cmdRun) useGdbserver() bool {
   821  	// compatibility, can be removed after 2021
   822  	if x.ExperimentalGdbserver != "no-gdbserver" {
   823  		x.Gdbserver = x.ExperimentalGdbserver
   824  	}
   825  
   826  	// make sure the go-flag parser ran and assigned default values
   827  	return x.ParserRan == 1 && x.Gdbserver != "no-gdbserver"
   828  }
   829  
   830  func (x *cmdRun) runCmdUnderGdbserver(origCmd []string, envForExec envForExecFunc) error {
   831  	gcmd := exec.Command(origCmd[0], origCmd[1:]...)
   832  	gcmd.Stdin = os.Stdin
   833  	gcmd.Stdout = os.Stdout
   834  	gcmd.Stderr = os.Stderr
   835  	gcmd.Env = envForExec(map[string]string{"SNAP_CONFINE_RUN_UNDER_GDBSERVER": "1"})
   836  	if err := gcmd.Start(); err != nil {
   837  		return err
   838  	}
   839  	// wait for the child process executing gdb helper to raise SIGSTOP
   840  	// signalling readiness to attach a gdbserver process
   841  	var status syscall.WaitStatus
   842  	_, err := syscall.Wait4(gcmd.Process.Pid, &status, syscall.WSTOPPED, nil)
   843  	if err != nil {
   844  		return err
   845  	}
   846  
   847  	addr := x.Gdbserver
   848  	if addr == ":0" {
   849  		// XXX: run "gdbserver :0" instead and parse "Listening on port 45971"
   850  		//      on stderr instead?
   851  		port, err := racyFindFreePort()
   852  		if err != nil {
   853  			return fmt.Errorf("cannot find free port: %v", err)
   854  		}
   855  		addr = fmt.Sprintf(":%v", port)
   856  	}
   857  	// XXX: should we provide a helper here instead? something like
   858  	//      `snap run --attach-debugger` or similar? The downside
   859  	//      is that attaching a gdb frontend is harder?
   860  	fmt.Fprintf(Stdout, fmt.Sprintf(gdbServerWelcomeFmt, addr))
   861  	// note that only gdbserver needs to run as root, the application
   862  	// keeps running as the user
   863  	gdbSrvCmd := exec.Command("sudo", "-E", "gdbserver", "--attach", addr, strconv.Itoa(gcmd.Process.Pid))
   864  	if output, err := gdbSrvCmd.CombinedOutput(); err != nil {
   865  		return osutil.OutputErr(output, err)
   866  	}
   867  	return nil
   868  }
   869  
   870  func (x *cmdRun) runCmdUnderGdb(origCmd []string, envForExec envForExecFunc) error {
   871  	// the resulting application process runs as root
   872  	cmd := []string{"sudo", "-E", "gdb", "-ex=run", "-ex=catch exec", "-ex=continue", "--args"}
   873  	cmd = append(cmd, origCmd...)
   874  
   875  	gcmd := exec.Command(cmd[0], cmd[1:]...)
   876  	gcmd.Stdin = os.Stdin
   877  	gcmd.Stdout = os.Stdout
   878  	gcmd.Stderr = os.Stderr
   879  	gcmd.Env = envForExec(map[string]string{"SNAP_CONFINE_RUN_UNDER_GDB": "1"})
   880  	return gcmd.Run()
   881  }
   882  
   883  func (x *cmdRun) runCmdWithTraceExec(origCmd []string, envForExec envForExecFunc) error {
   884  	// setup private tmp dir with strace fifo
   885  	straceTmp, err := ioutil.TempDir("", "exec-trace")
   886  	if err != nil {
   887  		return err
   888  	}
   889  	defer os.RemoveAll(straceTmp)
   890  	straceLog := filepath.Join(straceTmp, "strace.fifo")
   891  	if err := syscall.Mkfifo(straceLog, 0640); err != nil {
   892  		return err
   893  	}
   894  	// ensure we have one writer on the fifo so that if strace fails
   895  	// nothing blocks
   896  	fw, err := os.OpenFile(straceLog, os.O_RDWR, 0640)
   897  	if err != nil {
   898  		return err
   899  	}
   900  	defer fw.Close()
   901  
   902  	// read strace data from fifo async
   903  	var slg *strace.ExecveTiming
   904  	var straceErr error
   905  	doneCh := make(chan bool, 1)
   906  	go func() {
   907  		// FIXME: make this configurable?
   908  		nSlowest := 10
   909  		slg, straceErr = strace.TraceExecveTimings(straceLog, nSlowest)
   910  		close(doneCh)
   911  	}()
   912  
   913  	cmd, err := strace.TraceExecCommand(straceLog, origCmd...)
   914  	if err != nil {
   915  		return err
   916  	}
   917  	// run
   918  	cmd.Env = envForExec(nil)
   919  	cmd.Stdin = Stdin
   920  	cmd.Stdout = Stdout
   921  	cmd.Stderr = Stderr
   922  	err = cmd.Run()
   923  	// ensure we close the fifo here so that the strace.TraceExecCommand()
   924  	// helper gets a EOF from the fifo (i.e. all writers must be closed
   925  	// for this)
   926  	fw.Close()
   927  
   928  	// wait for strace reader
   929  	<-doneCh
   930  	if straceErr == nil {
   931  		slg.Display(Stderr)
   932  	} else {
   933  		logger.Noticef("cannot extract runtime data: %v", straceErr)
   934  	}
   935  	return err
   936  }
   937  
   938  func (x *cmdRun) runCmdUnderStrace(origCmd []string, envForExec envForExecFunc) error {
   939  	extraStraceOpts, raw, err := x.straceOpts()
   940  	if err != nil {
   941  		return err
   942  	}
   943  	cmd, err := strace.Command(extraStraceOpts, origCmd...)
   944  	if err != nil {
   945  		return err
   946  	}
   947  
   948  	// run with filter
   949  	cmd.Env = envForExec(nil)
   950  	cmd.Stdin = Stdin
   951  	cmd.Stdout = Stdout
   952  	stderr, err := cmd.StderrPipe()
   953  	if err != nil {
   954  		return err
   955  	}
   956  	filterDone := make(chan bool, 1)
   957  	go func() {
   958  		defer func() { filterDone <- true }()
   959  
   960  		if raw {
   961  			// Passing --strace='--raw' disables the filtering of
   962  			// early strace output. This is useful when tracking
   963  			// down issues with snap helpers such as snap-confine,
   964  			// snap-exec ...
   965  			io.Copy(Stderr, stderr)
   966  			return
   967  		}
   968  
   969  		r := bufio.NewReader(stderr)
   970  
   971  		// The first thing from strace if things work is
   972  		// "exeve(" - show everything until we see this to
   973  		// not swallow real strace errors.
   974  		for {
   975  			s, err := r.ReadString('\n')
   976  			if err != nil {
   977  				break
   978  			}
   979  			if strings.Contains(s, "execve(") {
   980  				break
   981  			}
   982  			fmt.Fprint(Stderr, s)
   983  		}
   984  
   985  		// The last thing that snap-exec does is to
   986  		// execve() something inside the snap dir so
   987  		// we know that from that point on the output
   988  		// will be interessting to the user.
   989  		//
   990  		// We need check both /snap (which is where snaps
   991  		// are located inside the mount namespace) and the
   992  		// distro snap mount dir (which is different on e.g.
   993  		// fedora/arch) to fully work with classic snaps.
   994  		needle1 := fmt.Sprintf(`execve("%s`, dirs.SnapMountDir)
   995  		needle2 := `execve("/snap`
   996  		for {
   997  			s, err := r.ReadString('\n')
   998  			if err != nil {
   999  				if err != io.EOF {
  1000  					fmt.Fprintf(Stderr, "cannot read strace output: %s\n", err)
  1001  				}
  1002  				break
  1003  			}
  1004  			// Ensure we catch the execve but *not* the
  1005  			// exec into
  1006  			// /snap/core/current/usr/lib/snapd/snap-confine
  1007  			// which is just `snap run` using the core version
  1008  			// snap-confine.
  1009  			if (strings.Contains(s, needle1) || strings.Contains(s, needle2)) && !strings.Contains(s, "usr/lib/snapd/snap-confine") {
  1010  				fmt.Fprint(Stderr, s)
  1011  				break
  1012  			}
  1013  		}
  1014  		io.Copy(Stderr, r)
  1015  	}()
  1016  	if err := cmd.Start(); err != nil {
  1017  		return err
  1018  	}
  1019  	<-filterDone
  1020  	err = cmd.Wait()
  1021  	return err
  1022  }
  1023  
  1024  func (x *cmdRun) runSnapConfine(info *snap.Info, securityTag, snapApp, hook string, args []string) error {
  1025  	snapConfine, err := snapdHelperPath("snap-confine")
  1026  	if err != nil {
  1027  		return err
  1028  	}
  1029  	if !osutil.FileExists(snapConfine) {
  1030  		if hook != "" {
  1031  			logger.Noticef("WARNING: skipping running hook %q of snap %q: missing snap-confine", hook, info.InstanceName())
  1032  			return nil
  1033  		}
  1034  		return fmt.Errorf(i18n.G("missing snap-confine: try updating your core/snapd package"))
  1035  	}
  1036  
  1037  	logger.Debugf("executing snap-confine from %s", snapConfine)
  1038  
  1039  	snapName, _ := snap.SplitSnapApp(snapApp)
  1040  	opts, err := getSnapDirOptions(snapName)
  1041  	if err != nil {
  1042  		return fmt.Errorf("cannot get snap dir options: %w", err)
  1043  	}
  1044  
  1045  	if err := createUserDataDirs(info, opts); err != nil {
  1046  		logger.Noticef("WARNING: cannot create user data directory: %s", err)
  1047  	}
  1048  
  1049  	xauthPath, err := migrateXauthority(info)
  1050  	if err != nil {
  1051  		logger.Noticef("WARNING: cannot copy user Xauthority file: %s", err)
  1052  	}
  1053  
  1054  	if err := activateXdgDocumentPortal(info, snapApp, hook); err != nil {
  1055  		logger.Noticef("WARNING: cannot start document portal: %s", err)
  1056  	}
  1057  
  1058  	cmd := []string{snapConfine}
  1059  	if info.NeedsClassic() {
  1060  		cmd = append(cmd, "--classic")
  1061  	}
  1062  
  1063  	// this should never happen since we validate snaps with "base: none" and do not allow hooks/apps
  1064  	if info.Base == "none" {
  1065  		return fmt.Errorf(`cannot run hooks / applications with base "none"`)
  1066  	}
  1067  	if info.Base != "" {
  1068  		cmd = append(cmd, "--base", info.Base)
  1069  	} else {
  1070  		if info.Type() == snap.TypeKernel {
  1071  			// kernels have no explicit base, we use the boot base
  1072  			modelAssertion, err := x.client.CurrentModelAssertion()
  1073  			if err != nil {
  1074  				if hook != "" {
  1075  					return fmt.Errorf("cannot get model assertion to setup kernel hook run: %v", err)
  1076  				} else {
  1077  					return fmt.Errorf("cannot get model assertion to setup kernel app run: %v", err)
  1078  				}
  1079  			}
  1080  			modelBase := modelAssertion.Base()
  1081  			if modelBase != "" {
  1082  				cmd = append(cmd, "--base", modelBase)
  1083  			}
  1084  		}
  1085  	}
  1086  	cmd = append(cmd, securityTag)
  1087  
  1088  	// when under confinement, snap-exec is run from 'core' snap rootfs
  1089  	snapExecPath := filepath.Join(dirs.CoreLibExecDir, "snap-exec")
  1090  
  1091  	if info.NeedsClassic() {
  1092  		// running with classic confinement, carefully pick snap-exec we
  1093  		// are going to use
  1094  		snapExecPath, err = snapdHelperPath("snap-exec")
  1095  		if err != nil {
  1096  			return err
  1097  		}
  1098  	}
  1099  	cmd = append(cmd, snapExecPath)
  1100  
  1101  	if x.Shell {
  1102  		cmd = append(cmd, "--command=shell")
  1103  	}
  1104  	if x.Gdb {
  1105  		cmd = append(cmd, "--command=gdb")
  1106  	}
  1107  	if x.useGdbserver() {
  1108  		cmd = append(cmd, "--command=gdbserver")
  1109  	}
  1110  	if x.Command != "" {
  1111  		cmd = append(cmd, "--command="+x.Command)
  1112  	}
  1113  
  1114  	if hook != "" {
  1115  		cmd = append(cmd, "--hook="+hook)
  1116  	}
  1117  
  1118  	// snap-exec is POSIXly-- options must come before positionals.
  1119  	cmd = append(cmd, snapApp)
  1120  	cmd = append(cmd, args...)
  1121  
  1122  	env, err := osutil.OSEnvironment()
  1123  	if err != nil {
  1124  		return err
  1125  	}
  1126  	snapenv.ExtendEnvForRun(env, info, opts)
  1127  
  1128  	if len(xauthPath) > 0 {
  1129  		// Environment is not nil here because it comes from
  1130  		// osutil.OSEnvironment and that guarantees this
  1131  		// property.
  1132  		env["XAUTHORITY"] = xauthPath
  1133  	}
  1134  
  1135  	// on each run variant path this will be used once to get
  1136  	// the environment plus additions in the right form
  1137  	envForExec := func(extra map[string]string) []string {
  1138  		for varName, value := range extra {
  1139  			env[varName] = value
  1140  		}
  1141  		if !info.NeedsClassic() {
  1142  			return env.ForExec()
  1143  		}
  1144  		// For a classic snap, environment variables that are
  1145  		// usually stripped out by ld.so when starting a
  1146  		// setuid process are presevered by being renamed by
  1147  		// prepending PreservedUnsafePrefix -- which snap-exec
  1148  		// will remove, restoring the variables to their
  1149  		// original names.
  1150  		return env.ForExecEscapeUnsafe(snapenv.PreservedUnsafePrefix)
  1151  	}
  1152  
  1153  	// Systemd automatically places services under a unique cgroup encoding the
  1154  	// security tag, but for apps and hooks we need to create a transient scope
  1155  	// with similar purpose ourselves.
  1156  	//
  1157  	// The way this happens is as follows:
  1158  	//
  1159  	// 1) Services are implemented using systemd service units. Starting a
  1160  	// unit automatically places it in a cgroup named after the service unit
  1161  	// name. Snapd controls the name of the service units thus indirectly
  1162  	// controls the cgroup name.
  1163  	//
  1164  	// 2) Non-services, including hooks, are started inside systemd
  1165  	// transient scopes. Scopes are a systemd unit type that are defined
  1166  	// programmatically and are meant for groups of processes started and
  1167  	// stopped by an _arbitrary process_ (ie, not systemd). Systemd
  1168  	// requires that each scope is given a unique name. We employ a scheme
  1169  	// where random UUID is combined with the name of the security tag
  1170  	// derived from snap application or hook name. Multiple concurrent
  1171  	// invocations of "snap run" will use distinct UUIDs.
  1172  	//
  1173  	// Transient scopes allow launched snaps to integrate into
  1174  	// the systemd design. See:
  1175  	// https://www.freedesktop.org/wiki/Software/systemd/ControlGroupInterface/
  1176  	//
  1177  	// Programs running as root, like system-wide services and programs invoked
  1178  	// using tools like sudo are placed under system.slice. Programs running as
  1179  	// a non-root user are placed under user.slice, specifically in a scope
  1180  	// specific to a logind session.
  1181  	//
  1182  	// This arrangement allows for proper accounting and control of resources
  1183  	// used by snap application processes of each type.
  1184  	//
  1185  	// For more information about systemd cgroups, including unit types, see:
  1186  	// https://www.freedesktop.org/wiki/Software/systemd/ControlGroupInterface/
  1187  	_, appName := snap.SplitSnapApp(snapApp)
  1188  	needsTracking := true
  1189  	if app := info.Apps[appName]; hook == "" && app != nil && app.IsService() {
  1190  		// If we are running a service app then we do not need to use
  1191  		// application tracking. Services, both in the system and user scope,
  1192  		// do not need tracking because systemd already places them in a
  1193  		// tracking cgroup, named after the systemd unit name, and those are
  1194  		// sufficient to identify both the snap name and the app name.
  1195  		needsTracking = false
  1196  		// however it is still possible that the app (which is a
  1197  		// service) was invoked by the user, so it may be running inside
  1198  		// a user's scope cgroup, in which case separate tracking group
  1199  		// needs to be established
  1200  		if err := cgroupConfirmSystemdServiceTracking(securityTag); err != nil {
  1201  			if err == cgroup.ErrCannotTrackProcess {
  1202  				// we are not being tracked in a service cgroup
  1203  				// after all, go ahead and create a transient
  1204  				// scope
  1205  				needsTracking = true
  1206  				logger.Debugf("service app not tracked by systemd")
  1207  			} else {
  1208  				return err
  1209  			}
  1210  		}
  1211  	}
  1212  	// Allow using the session bus for all apps but not for hooks.
  1213  	allowSessionBus := hook == ""
  1214  	// Track, or confirm existing tracking from systemd.
  1215  	if needsTracking {
  1216  		opts := &cgroup.TrackingOptions{AllowSessionBus: allowSessionBus}
  1217  		if err = cgroupCreateTransientScopeForTracking(securityTag, opts); err != nil {
  1218  			if err != cgroup.ErrCannotTrackProcess {
  1219  				return err
  1220  			}
  1221  			// If we cannot track the process then log a debug message.
  1222  			// TODO: if we could, create a warning. Currently this is not possible
  1223  			// because only snapd can create warnings, internally.
  1224  			logger.Debugf("snapd cannot track the started application")
  1225  			logger.Debugf("snap refreshes will not be postponed by this process")
  1226  		}
  1227  	}
  1228  	logger.StartupStageTimestamp("snap to snap-confine")
  1229  	if x.TraceExec {
  1230  		return x.runCmdWithTraceExec(cmd, envForExec)
  1231  	} else if x.Gdb {
  1232  		return x.runCmdUnderGdb(cmd, envForExec)
  1233  	} else if x.useGdbserver() {
  1234  		if _, err := exec.LookPath("gdbserver"); err != nil {
  1235  			// TODO: use xerrors.Is(err, exec.ErrNotFound) once
  1236  			// we moved off from go-1.9
  1237  			if execErr, ok := err.(*exec.Error); ok {
  1238  				if execErr.Err == exec.ErrNotFound {
  1239  					return fmt.Errorf("please install gdbserver on your system")
  1240  				}
  1241  			}
  1242  			return err
  1243  		}
  1244  		return x.runCmdUnderGdbserver(cmd, envForExec)
  1245  	} else if x.useStrace() {
  1246  		return x.runCmdUnderStrace(cmd, envForExec)
  1247  	} else {
  1248  		return syscallExec(cmd[0], cmd, envForExec(nil))
  1249  	}
  1250  }
  1251  
  1252  func getSnapDirOptions(snap string) (*dirs.SnapDirOptions, error) {
  1253  	var opts dirs.SnapDirOptions
  1254  
  1255  	data, err := ioutil.ReadFile(filepath.Join(dirs.SnapSeqDir, snap+".json"))
  1256  	if errors.Is(err, os.ErrNotExist) {
  1257  		return &opts, nil
  1258  	} else if err != nil {
  1259  		return nil, err
  1260  	}
  1261  
  1262  	var seq struct {
  1263  		MigratedToHiddenDir   bool `json:"migrated-hidden"`
  1264  		MigratedToExposedHome bool `json:"migrated-exposed-home"`
  1265  	}
  1266  	if err := json.Unmarshal(data, &seq); err != nil {
  1267  		return nil, err
  1268  	}
  1269  
  1270  	opts.HiddenSnapDataDir = seq.MigratedToHiddenDir
  1271  	opts.MigratedToExposedHome = seq.MigratedToExposedHome
  1272  
  1273  	return &opts, nil
  1274  }
  1275  
  1276  var cgroupCreateTransientScopeForTracking = cgroup.CreateTransientScopeForTracking
  1277  var cgroupConfirmSystemdServiceTracking = cgroup.ConfirmSystemdServiceTracking