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 }