github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/release/release.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2015 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 release 21 22 import ( 23 "bufio" 24 "bytes" 25 "io/ioutil" 26 "os" 27 "strings" 28 "unicode" 29 30 "github.com/snapcore/snapd/strutil" 31 ) 32 33 // Series holds the Ubuntu Core series for snapd to use. 34 var Series = "16" 35 36 // OS contains information about the system extracted from /etc/os-release. 37 type OS struct { 38 ID string `json:"id"` 39 IDLike []string `json:"-"` 40 VersionID string `json:"version-id,omitempty"` 41 } 42 43 // DistroLike checks if the distribution ID or ID_LIKE matches one of the given names. 44 func DistroLike(distros ...string) bool { 45 for _, distro := range distros { 46 if ReleaseInfo.ID == distro || strutil.ListContains(ReleaseInfo.IDLike, distro) { 47 return true 48 } 49 } 50 return false 51 } 52 53 var ( 54 osReleasePath = "/etc/os-release" 55 fallbackOsReleasePath = "/usr/lib/os-release" 56 ) 57 58 // readOSRelease returns the os-release information of the current system. 59 func readOSRelease() OS { 60 // TODO: separate this out into its own thing maybe (if made more general) 61 osRelease := OS{ 62 // from os-release(5): If not set, defaults to "ID=linux". 63 ID: "linux", 64 } 65 66 f, err := os.Open(osReleasePath) 67 if err != nil { 68 // this fallback is as per os-release(5) 69 f, err = os.Open(fallbackOsReleasePath) 70 if err != nil { 71 return osRelease 72 } 73 } 74 75 scanner := bufio.NewScanner(f) 76 for scanner.Scan() { 77 ws := strings.SplitN(scanner.Text(), "=", 2) 78 if len(ws) < 2 { 79 continue 80 } 81 82 k := strings.TrimSpace(ws[0]) 83 v := strings.TrimFunc(ws[1], func(r rune) bool { return r == '"' || r == '\'' || unicode.IsSpace(r) }) 84 // XXX: should also unquote things as per os-release(5) but not needed yet in practice 85 switch k { 86 case "ID": 87 // ID should be “A lower-case string (no spaces or 88 // other characters outside of 0–9, a–z, ".", "_" and 89 // "-") identifying the operating system, excluding any 90 // version information and suitable for processing by 91 // scripts or usage in generated filenames.” 92 // 93 // So we mangle it a little bit to account for people 94 // not being too good at reading comprehension. 95 // Works around e.g. lp:1602317 96 osRelease.ID = strings.Fields(strings.ToLower(v))[0] 97 case "ID_LIKE": 98 // This is like ID, except it's a space separated list... hooray? 99 osRelease.IDLike = strings.Fields(strings.ToLower(v)) 100 case "VERSION_ID": 101 osRelease.VersionID = v 102 } 103 } 104 105 return osRelease 106 } 107 108 var ioutilReadFile = ioutil.ReadFile 109 110 func isWSL() bool { 111 version, err := ioutilReadFile("/proc/version") 112 if err == nil && bytes.Contains(version, []byte("Microsoft")) { 113 return true 114 } 115 116 return false 117 } 118 119 // SystemctlSupportsUserUnits returns true if the systemctl utility 120 // supports user units. 121 func SystemctlSupportsUserUnits() bool { 122 // Ubuntu 14.04's systemctl does not support the arguments 123 // needed to enable user session units. Further more, it does 124 // not ship with a systemd user instance. 125 return !(ReleaseInfo.ID == "ubuntu" && ReleaseInfo.VersionID == "14.04") 126 } 127 128 // OnClassic states whether the process is running inside a 129 // classic Ubuntu system or a native Ubuntu Core image. 130 var OnClassic bool 131 132 // OnWSL states whether the process is running inside the Windows 133 // Subsystem for Linux 134 var OnWSL bool 135 136 // ReleaseInfo contains data loaded from /etc/os-release on startup. 137 var ReleaseInfo OS 138 139 func init() { 140 ReleaseInfo = readOSRelease() 141 142 OnClassic = (ReleaseInfo.ID != "ubuntu-core") 143 144 OnWSL = isWSL() 145 } 146 147 // MockOnClassic forces the process to appear inside a classic 148 // Ubuntu system or a native image for testing purposes. 149 func MockOnClassic(onClassic bool) (restore func()) { 150 old := OnClassic 151 OnClassic = onClassic 152 return func() { OnClassic = old } 153 } 154 155 // MockReleaseInfo fakes a given information to appear in ReleaseInfo, 156 // as if it was read /etc/os-release on startup. 157 func MockReleaseInfo(osRelease *OS) (restore func()) { 158 old := ReleaseInfo 159 ReleaseInfo = *osRelease 160 return func() { ReleaseInfo = old } 161 }