github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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  }