blob: fc07663def68ad96ba4c9a02b61748a024809db3 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"bufio"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
pb "chromiumos/vm_tools/tremplin_proto"
"github.com/lxc/lxd/client"
)
const (
lsbReleasePath = "/etc/lsb-release"
osReleasePrimaryPath = "/etc/os-release"
osReleaseSecondaryPath = "/usr/lib/os-release"
milestoneKey = "CHROMEOS_RELEASE_CHROME_MILESTONE"
)
// dequote strips shell-style quotes from a string. It does not do any
// validation of the string, and only removes all instances of single and
// double-quote characters.
func dequote(s string) string {
return strings.Replace(strings.Replace(s, "'", "", -1), "\"", "", -1)
}
// getMilestone returns the Chrome OS milestone of the Termina VM.
func getMilestone() (int, error) {
f, err := os.Open(lsbReleasePath)
if err != nil {
return 0, err
}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
s := strings.Split(scanner.Text(), "=")
if len(s) != 2 {
return 0, errors.New("failed to parse lsb-release entry")
}
if s[0] == milestoneKey {
val, err := strconv.Atoi(dequote(s[1]))
if err != nil {
return 0, fmt.Errorf("%q is not a valid milestone number: %v", s[1], err)
}
return val, nil
}
}
return 0, errors.New("no milestone key in lsb-release file")
}
// OsRelease encapsulates a subset of the os-release info as documented
// at https://www.freedesktop.org/software/systemd/man/os-release.html
type OsRelease struct {
prettyName string
name string
version string
versionID string
id string
}
// getFileResolvingSymlinks will get the contents of a container file while
// resolving any symlinks.
func getFileResolvingSymlinks(lxd lxd.ContainerServer, name, p string) (io.ReadCloser, *lxd.ContainerFileResponse, error) {
// include/linux/namei.h defines MAXSYMLINKS
for i := 0; i < 40; i++ {
r, resp, err := lxd.GetContainerFile(name, p)
if err != nil {
return nil, nil, fmt.Errorf("failed to read guest path %q: %v", p, err)
}
if resp.Type != "symlink" {
return r, resp, nil
}
b, err := ioutil.ReadAll(r)
symlink := strings.TrimSpace(string(b))
if path.IsAbs(symlink) {
p = symlink
} else {
p = path.Join(path.Dir(p), symlink)
}
}
return nil, nil, errors.New("too many symlinks")
}
// getGuestOSRelease returns the os-release information for the given container.
func getGuestOSRelease(lxd lxd.ContainerServer, name string) (*OsRelease, error) {
r, _, err := getFileResolvingSymlinks(lxd, name, osReleasePrimaryPath)
if err != nil {
// Assume on error that the primary os-release path doesn't exist.
r, _, err = getFileResolvingSymlinks(lxd, name, osReleaseSecondaryPath)
if err != nil {
return nil, fmt.Errorf("failed to find os-release file: %v", err)
}
}
defer r.Close()
osRelease := &OsRelease{}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
s := strings.Split(line, "=")
if len(s) != 2 {
return nil, fmt.Errorf("failed to parse os-release entry %q", line)
}
switch s[0] {
case "PRETTY_NAME":
osRelease.prettyName = dequote(s[1])
case "NAME":
osRelease.name = dequote(s[1])
case "VERSION":
osRelease.version = dequote(s[1])
case "VERSION_ID":
osRelease.versionID = dequote(s[1])
case "ID":
osRelease.id = dequote(s[1])
}
}
return osRelease, nil
}
func (o *OsRelease) toProto() *pb.OsRelease {
return &pb.OsRelease{
PrettyName: o.prettyName,
Name: o.name,
Version: o.version,
VersionId: o.versionID,
Id: o.id,
}
}
func createAptSourceList(milestone int, osVersion string) string {
if milestone < 70 {
return "deb https://storage.googleapis.com/cros-packages stretch main\n"
}
return fmt.Sprintf("deb https://storage.googleapis.com/cros-packages/%d %s main\n", milestone, osVersion)
}