github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/snapdtool/tool_linux.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package snapdtool
    21  
    22  import (
    23  	"log"
    24  	"os"
    25  	"path/filepath"
    26  	"strings"
    27  	"syscall"
    28  
    29  	"github.com/snapcore/snapd/dirs"
    30  	"github.com/snapcore/snapd/logger"
    31  	"github.com/snapcore/snapd/osutil"
    32  	"github.com/snapcore/snapd/release"
    33  	"github.com/snapcore/snapd/strutil"
    34  )
    35  
    36  // The SNAP_REEXEC environment variable controls whether the command
    37  // will attempt to re-exec itself from inside an ubuntu-core snap
    38  // present on the system. If not present in the environ it's assumed
    39  // to be set to 1 (do re-exec); that is: set it to 0 to disable.
    40  const reExecKey = "SNAP_REEXEC"
    41  
    42  var (
    43  	// snapdSnap is the place to look for the snapd snap; we will re-exec
    44  	// here
    45  	snapdSnap = "/snap/snapd/current"
    46  
    47  	// coreSnap is the place to look for the core snap; we will re-exec
    48  	// here if there is no snapd snap
    49  	coreSnap = "/snap/core/current"
    50  
    51  	// selfExe is the path to a symlink pointing to the current executable
    52  	selfExe = "/proc/self/exe"
    53  
    54  	syscallExec = syscall.Exec
    55  	osReadlink  = os.Readlink
    56  )
    57  
    58  // distroSupportsReExec returns true if the distribution we are running on can use re-exec.
    59  //
    60  // This is true by default except for a "core/all" snap system where it makes
    61  // no sense and in certain distributions that we don't want to enable re-exec
    62  // yet because of missing validation or other issues.
    63  func distroSupportsReExec() bool {
    64  	if !release.OnClassic {
    65  		return false
    66  	}
    67  	if !release.DistroLike("debian", "ubuntu") {
    68  		logger.Debugf("re-exec not supported on distro %q yet", release.ReleaseInfo.ID)
    69  		return false
    70  	}
    71  	return true
    72  }
    73  
    74  // coreSupportsReExec returns true if the given core/snapd snap should be used as re-exec target.
    75  //
    76  // Ensure we do not use older version of snapd, look for info file and ignore
    77  // version of core that do not yet have it.
    78  func coreSupportsReExec(coreOrSnapdPath string) bool {
    79  	infoPath := filepath.Join(coreOrSnapdPath, filepath.Join(dirs.CoreLibExecDir, "info"))
    80  	ver, err := SnapdVersionFromInfoFile(infoPath)
    81  	if err != nil {
    82  		logger.Noticef("%v", err)
    83  		return false
    84  	}
    85  
    86  	// > 0 means our Version is bigger than the version of snapd in core
    87  	res, err := strutil.VersionCompare(Version, ver)
    88  	if err != nil {
    89  		logger.Debugf("cannot version compare %q and %q: %v", Version, ver, err)
    90  		return false
    91  	}
    92  	if res > 0 {
    93  		logger.Debugf("snap (at %q) is older (%q) than distribution package (%q)", coreOrSnapdPath, ver, Version)
    94  		return false
    95  	}
    96  	return true
    97  }
    98  
    99  // InternalToolPath returns the path of an internal snapd tool. The tool
   100  // *must* be located inside the same tree as the current binary.
   101  //
   102  // The return value is either the path of the tool in the current distribution
   103  // or in the core/snapd snap (or the ubuntu-core snap) if the current binary is
   104  // ran from that location.
   105  func InternalToolPath(tool string) (string, error) {
   106  	distroTool := filepath.Join(dirs.DistroLibExecDir, tool)
   107  
   108  	// find the internal path relative to the running snapd, this
   109  	// ensure we don't rely on the state of the system (like
   110  	// having a valid "current" symlink).
   111  	exe, err := osReadlink("/proc/self/exe")
   112  	if err != nil {
   113  		return "", err
   114  	}
   115  
   116  	if !strings.HasPrefix(exe, dirs.DistroLibExecDir) {
   117  		// either running from mounted location or /usr/bin/snap*
   118  
   119  		// find the local prefix to the snap:
   120  		// /snap/snapd/123/usr/bin/snap       -> /snap/snapd/123
   121  		// /snap/core/234/usr/lib/snapd/snapd -> /snap/core/234
   122  		idx := strings.LastIndex(exe, "/usr/")
   123  		if idx > 0 {
   124  			// only assume mounted location when path contains
   125  			// /usr/, but does not start with one
   126  			prefix := exe[:idx]
   127  			return filepath.Join(prefix, "/usr/lib/snapd", tool), nil
   128  		}
   129  		if idx == -1 {
   130  			// or perhaps some other random location, make sure the tool
   131  			// exists there and is an executable
   132  			maybeTool := filepath.Join(filepath.Dir(exe), tool)
   133  			if osutil.IsExecutable(maybeTool) {
   134  				return maybeTool, nil
   135  			}
   136  		}
   137  	}
   138  
   139  	// fallback to distro tool
   140  	return distroTool, nil
   141  }
   142  
   143  // mustUnsetenv will unset the given environment key or panic if it
   144  // cannot do that
   145  func mustUnsetenv(key string) {
   146  	if err := os.Unsetenv(key); err != nil {
   147  		log.Panicf("cannot unset %s: %s", key, err)
   148  	}
   149  }
   150  
   151  // ExecInSnapdOrCoreSnap makes sure you're executing the binary that ships in
   152  // the snapd/core snap.
   153  func ExecInSnapdOrCoreSnap() {
   154  	// Which executable are we?
   155  	exe, err := os.Readlink(selfExe)
   156  	if err != nil {
   157  		logger.Noticef("cannot read /proc/self/exe: %v", err)
   158  		return
   159  	}
   160  
   161  	// Special case for snapd re-execing from 2.21. In this
   162  	// version of snap/snapd we did set SNAP_REEXEC=0 when we
   163  	// re-execed. In this case we need to unset the reExecKey to
   164  	// ensure that subsequent run of snap/snapd (e.g. when using
   165  	// classic confinement) will *not* prevented from re-execing.
   166  	if strings.HasPrefix(exe, dirs.SnapMountDir) && !osutil.GetenvBool(reExecKey, true) {
   167  		mustUnsetenv(reExecKey)
   168  		return
   169  	}
   170  
   171  	// If we are asked not to re-execute use distribution packages. This is
   172  	// "spiritual" re-exec so use the same environment variable to decide.
   173  	if !osutil.GetenvBool(reExecKey, true) {
   174  		logger.Debugf("re-exec disabled by user")
   175  		return
   176  	}
   177  
   178  	// Did we already re-exec?
   179  	if strings.HasPrefix(exe, dirs.SnapMountDir) {
   180  		return
   181  	}
   182  
   183  	// If the distribution doesn't support re-exec or run-from-core then don't do it.
   184  	if !distroSupportsReExec() {
   185  		return
   186  	}
   187  
   188  	// Is this executable in the core snap too?
   189  	coreOrSnapdPath := snapdSnap
   190  	full := filepath.Join(snapdSnap, exe)
   191  	if !osutil.FileExists(full) {
   192  		coreOrSnapdPath = coreSnap
   193  		full = filepath.Join(coreSnap, exe)
   194  		if !osutil.FileExists(full) {
   195  			return
   196  		}
   197  	}
   198  
   199  	// If the core snap doesn't support re-exec or run-from-core then don't do it.
   200  	if !coreSupportsReExec(coreOrSnapdPath) {
   201  		return
   202  	}
   203  
   204  	logger.Debugf("restarting into %q", full)
   205  	panic(syscallExec(full, os.Args, os.Environ()))
   206  }
   207  
   208  // IsReexecd returns true when the current process binary is running from a snap.
   209  func IsReexecd() (bool, error) {
   210  	exe, err := osReadlink(selfExe)
   211  	if err != nil {
   212  		return false, err
   213  	}
   214  	if strings.HasPrefix(exe, dirs.SnapMountDir) {
   215  		return true, nil
   216  	}
   217  	return false, nil
   218  }
   219  
   220  // MockOsReadlink is for use in tests
   221  func MockOsReadlink(f func(string) (string, error)) func() {
   222  	realOsReadlink := osReadlink
   223  	osReadlink = f
   224  	return func() {
   225  		osReadlink = realOsReadlink
   226  	}
   227  }