1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
|
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2014-2015 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package release
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
"unicode"
"github.com/snapcore/snapd/strutil"
)
// Series holds the Ubuntu Core series for snapd to use.
var Series = "16"
// OS contains information about the system extracted from /etc/os-release.
type OS struct {
ID string `json:"id"`
IDLike []string `json:"-"`
VariantID string `json:"variant-id,omitempty"`
VersionID string `json:"version-id,omitempty"`
}
// DistroLike checks if the distribution ID or ID_LIKE matches one of the given names.
func DistroLike(distros ...string) bool {
for _, distro := range distros {
if ReleaseInfo.ID == distro || strutil.ListContains(ReleaseInfo.IDLike, distro) {
return true
}
}
return false
}
var (
osReleasePath = "/etc/os-release"
fallbackOsReleasePath = "/usr/lib/os-release"
initrdReleasePath = "/etc/initrd-release"
)
// readOSRelease returns the os-release information of the current system.
func readOSReleaseFromRoot(rootdir string) OS {
// TODO: separate this out into its own thing maybe (if made more general)
osRelease := OS{
// from os-release(5): If not set, defaults to "ID=linux".
ID: "linux",
}
var f *os.File
var err error
for _, candidate := range []string{
// is a default described in os-release(5) and should be valid for both
// booted system and an initrd
osReleasePath,
// this fallback is as per os-release(5)
fallbackOsReleasePath,
// maybe we're in an initrd which is missing a symlink to
// /etc/initrd-release?
initrdReleasePath,
} {
f, err = os.Open(filepath.Join(rootdir, candidate))
if err == nil {
break
}
}
if f == nil {
// no os-release information source, return a default
return osRelease
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
ws := strings.SplitN(scanner.Text(), "=", 2)
if len(ws) < 2 {
continue
}
k := strings.TrimSpace(ws[0])
v := strings.TrimFunc(ws[1], func(r rune) bool { return r == '"' || r == '\'' || unicode.IsSpace(r) })
// XXX: should also unquote things as per os-release(5) but not needed yet in practice
switch k {
case "ID":
// ID should be “A lower-case string (no spaces or
// other characters outside of 0–9, a–z, ".", "_" and
// "-") identifying the operating system, excluding any
// version information and suitable for processing by
// scripts or usage in generated filenames.”
//
// So we mangle it a little bit to account for people
// not being too good at reading comprehension.
// Works around e.g. lp:1602317
osRelease.ID = strings.Fields(strings.ToLower(v))[0]
case "ID_LIKE":
// This is like ID, except it's a space separated list... hooray?
osRelease.IDLike = strings.Fields(strings.ToLower(v))
case "VARIANT_ID":
osRelease.VariantID = v
case "VERSION_ID":
osRelease.VersionID = v
}
}
return osRelease
}
// Note that osutil.FileExists cannot be used here as an osutil import will create a cyclic import
var fileExists = func(path string) bool {
_, err := os.Stat(path)
return err == nil
}
var procMountsPath = "/proc/mounts"
// filesystemRootType returns the filesystem type mounted at "/".
func filesystemRootType() (string, error) {
// We scan /proc/mounts, which contains space-separated values:
// [irrelevant] [mount point] [fstype] [irrelevant...]
// Here are some examples on some platforms:
// WSL1 : rootfs / wslfs rw,noatime 0 0
// WSL2 : /dev/sdc / ext4 rw,relatime,discard,errors=remount-ro,data=ordered 0 0
// lxc on WSL2: /dev/loop0 / btrfs rw,relatime,idmapped,space_cache,user_subvol_rm_allowed,subvolid=259,subvol=/containers/testlxd 0 0
// We search for mount point = "/", and return the fstype.
//
// This should be done by osutil.LoadMountInfo but that would cause a dependency cycle
file, err := os.Open(procMountsPath)
if err != nil {
return "", fmt.Errorf("cannot find root filesystem type: %v", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
data := strings.Fields(scanner.Text())
if len(data) < 3 || data[1] != "/" {
continue
}
return data[2], nil
}
if err = scanner.Err(); err != nil {
return "", fmt.Errorf("cannot find root filesystem type: %v", err)
}
return "", fmt.Errorf("cannot find root filesystem type: not in list")
}
// We detect WSL via the existence of /proc/sys/fs/binfmt_misc/WSLInterop
// Under some undocumented circumstances this file may be missing. We have /run/WSL as a backup.
//
// We detect WSL1 via the root filesystem type:
// - wslfs or lxfs mean WSL1
// - Anything else means WSL2
// After knowing we're in WSL, if any error occurs we assume WSL2 as it is the more flexible version
func getWSLVersion() int {
if !fileExists("/proc/sys/fs/binfmt_misc/WSLInterop") && !fileExists("/run/WSL") {
return 0
}
fstype, err := filesystemRootType()
if err != nil {
// TODO log error here once logger can be imported without circular imports
return 2
}
if fstype == "wslfs" || fstype == "lxfs" {
return 1
}
return 2
}
// SystemctlSupportsUserUnits returns true if the systemctl utility
// supports user units.
func SystemctlSupportsUserUnits() bool {
// Ubuntu 14.04's systemctl does not support the arguments
// needed to enable user session units. Further more, it does
// not ship with a systemd user instance.
return !(ReleaseInfo.ID == "ubuntu" && ReleaseInfo.VersionID == "14.04")
}
// OnClassic states whether the process is running inside a
// classic Ubuntu system or a native Ubuntu Core image.
var OnClassic bool
// OnCoreDesktop states whether the process is running inside a Core Desktop image.
var OnCoreDesktop bool
// OnWSL states whether the process is running inside the Windows
// Subsystem for Linux
var OnWSL bool
// If the previous is true, WSLVersion states whether the process is running inside WSL1 or WSL2
// Otherwise it is set to 0
var WSLVersion int
// ReleaseInfo contains data loaded from /etc/os-release on startup.
var ReleaseInfo OS
func init() {
ReleaseInfo = readOSReleaseFromRoot("/")
OnClassic = (ReleaseInfo.ID != "ubuntu-core")
OnCoreDesktop = (ReleaseInfo.ID == "ubuntu-core" && ReleaseInfo.VariantID == "desktop")
WSLVersion = getWSLVersion()
OnWSL = WSLVersion != 0
}
// MockOnClassic forces the process to appear inside a classic
// Ubuntu system or a native image for testing purposes.
func MockOnClassic(onClassic bool) (restore func()) {
old := OnClassic
OnClassic = onClassic
return func() { OnClassic = old }
}
// MockOnCoreDesktop forces the process to appear inside a core desktop
// system for testing purposes.
func MockOnCoreDesktop(onCoreDesktop bool) (restore func()) {
old := OnCoreDesktop
OnCoreDesktop = onCoreDesktop
return func() { OnCoreDesktop = old }
}
// MockReleaseInfo fakes a given information to appear in ReleaseInfo,
// as if it was read /etc/os-release on startup.
func MockReleaseInfo(osRelease *OS) (restore func()) {
old := ReleaseInfo
ReleaseInfo = *osRelease
return func() { ReleaseInfo = old }
}
|