blob: 848fef6e38a6ddf5a7175f69a66989a764c2d7cc [file] [log] [blame]
// Package cos provides functionality to read and configure system configs that are specific to COS images.
package cos
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"cos.googlesource.com/cos/tools.git/src/pkg/utils"
log "github.com/golang/glog"
"github.com/pkg/errors"
)
const (
espPartition = "/dev/sda12"
utsFilepath = "include/generated/utsrelease.h"
)
var (
execCommand = exec.Command
)
// CheckKernelModuleSigning checks whether kernel module signing related options present.
func CheckKernelModuleSigning(kernelCmdline string) bool {
log.Info("Checking kernel module signing.")
for _, kernelOption := range []string{
"loadpin.exclude=kernel-module",
"modules-load=loadpin_trigger",
"module.sig_enforce=1",
} {
if !strings.Contains(kernelCmdline, kernelOption) {
return false
}
}
return true
}
// SetCompilationEnv sets compilation environment variables (e.g. CC, CXX) for third-party kernel module compilation.
// TODO(mikewu): pass environment variables to the *exec.Cmd that runs the installer.
func SetCompilationEnv(downloader ArtifactsDownloader) error {
log.Info("Downloading compilation environment variables")
compilationEnvs := make(map[string]string)
if err := downloader.DownloadToolchainEnv(os.TempDir()); err != nil {
// Required to support COS builds not having toolchain_env file
log.Info("Using default compilation environment variables")
compilationEnvs["CC"] = "x86_64-cros-linux-gnu-gcc"
compilationEnvs["CXX"] = "x86_64-cros-linux-gnu-g++"
} else {
if compilationEnvs, err = utils.LoadEnvFromFile(os.TempDir(), toolchainEnv); err != nil {
return errors.Wrap(err, "failed to parse toolchain_env file")
}
}
log.Info("Setting compilation environment variables")
for key, value := range compilationEnvs {
log.Infof("%s=%s", key, value)
os.Setenv(key, value)
}
return nil
}
// InstallCrossToolchain installs COS toolchain to destination directory.
func InstallCrossToolchain(downloader ArtifactsDownloader, destDir string) error {
log.Info("Installing the toolchain")
if err := os.MkdirAll(destDir, 0755); err != nil {
return errors.Wrapf(err, "failed to create dir %s", destDir)
}
if empty, _ := utils.IsDirEmpty(destDir); !empty {
log.Info("Found existing toolchain. Skipping download and installation")
} else {
if err := downloader.DownloadToolchain(destDir); err != nil {
return errors.Wrap(err, "failed to download toolchain")
}
if err := exec.Command("tar", "xf", filepath.Join(destDir, toolchainArchive), "-C", destDir).Run(); err != nil {
return errors.Wrap(err, "failed to extract toolchain archive tarball")
}
}
log.Info("Configuring environment variables for cross-compilation")
os.Setenv("PATH", fmt.Sprintf("%s/bin:%s", destDir, os.Getenv("PATH")))
os.Setenv("SYSROOT", filepath.Join(destDir, "usr/x86_64-cros-linux-gnu"))
return nil
}
// InstallKernelSrcPkg installs COS kernel source package to destination directory.
func InstallKernelSrcPkg(downloader ArtifactsDownloader, destDir string) error {
log.Info("Installing the kernel source package")
if err := downloadKernelSrc(downloader, destDir); err != nil {
return errors.Wrap(err, "failed to download kernel source")
}
if err := configureKernel(destDir); err != nil {
return errors.Wrap(err, "failed to configure kernel source")
}
if err := correctKernelMagicVersionIfNeeded(destDir); err != nil {
return errors.Wrap(err, "failed to run correctKernelMagicVersionIfNeeded")
}
return nil
}
// InstallKernelHeaderPkg installs kernel header package to destination directory.
func InstallKernelHeaderPkg(downloader ArtifactsDownloader, destDir string) error {
log.Info("Installing the kernel header package")
if err := os.MkdirAll(destDir, 0755); err != nil {
return errors.Wrapf(err, "failed to create dir %s", destDir)
}
if empty, _ := utils.IsDirEmpty(destDir); !empty {
return nil
}
log.Info("Kernel headers not found locally, downloading")
if err := downloader.DownloadKernelHeaders(destDir); err != nil {
return errors.Wrap(err, "failed to download kernel headers")
}
if err := exec.Command("tar", "xf", filepath.Join(destDir, kernelHeaders), "-C", destDir).Run(); err != nil {
return errors.Wrap(err, "failed to extract kernel header tarball")
}
return nil
}
// ConfigureModuleSymvers copys Module.symvers file from kernel header dir to kernel source dir.
func ConfigureModuleSymvers(kernelHeaderDir, kernelSrcDir string) error {
log.Info("Configuring Module.symvers file")
if err := utils.CopyFile(filepath.Join(kernelHeaderDir, "Module.symvers"),
filepath.Join(kernelSrcDir, "Module.symvers")); err != nil {
return errors.Wrap(err, "failed to copy Module.symvers file")
}
return nil
}
func disableKernelOptionFromGrubCfg(kernelOption, grubCfg string) (newGrubCfg string, needReboot bool) {
newGrubCfg = grubCfg
needReboot = false
kernelOptionEnabled := fmt.Sprintf("%v=1", kernelOption)
kernelOptionDisabled := fmt.Sprintf("%v=0", kernelOption)
if strings.Contains(grubCfg, kernelOption) {
if strings.Contains(grubCfg, kernelOptionEnabled) {
newGrubCfg = strings.ReplaceAll(grubCfg, kernelOptionEnabled, kernelOptionDisabled)
needReboot = true
}
} else {
newGrubCfg = strings.ReplaceAll(grubCfg, "cros_efi", fmt.Sprintf("cros_efi %v", kernelOptionDisabled))
needReboot = true
}
return newGrubCfg, needReboot
}
func downloadKernelSrc(downloader ArtifactsDownloader, destDir string) error {
if err := os.MkdirAll(destDir, 0755); err != nil {
return errors.Wrapf(err, "failed to create dir %s", destDir)
}
if empty, _ := utils.IsDirEmpty(destDir); !empty {
return nil
}
log.Info("Kernel sources not found locally, downloading")
if err := downloader.DownloadKernelSrc(destDir); err != nil {
return errors.Wrap(err, "failed to download kernel sources")
}
if err := exec.Command("tar", "xf", filepath.Join(destDir, kernelSrcArchive), "-C", destDir).Run(); err != nil {
return errors.Wrap(err, "failed to extract kernel source tarball")
}
return nil
}
func configureKernel(kernelSrcDir string) error {
log.Info("Configuring kernel")
// TODO(mikewu): consider getting kernel configs from kernel headers.
kConfig, err := exec.Command("zcat", "/proc/config.gz").Output()
if err != nil {
return errors.Wrap(err, "failed to read kernel config")
}
if err := ioutil.WriteFile(filepath.Join(kernelSrcDir, ".config"), kConfig, 0644); err != nil {
return errors.Wrap(err, "failed to write kernel config file")
}
cmd := exec.Command("make", "olddefconfig")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = kernelSrcDir
if err := cmd.Run(); err != nil {
return errors.Wrap(err, "failed to run `make olddefconfig`")
}
cmd = exec.Command("make", "modules_prepare")
cmd.Dir = kernelSrcDir
if err := cmd.Run(); err != nil {
return errors.Wrap(err, "failed to run `make modules_prepare`")
}
// COS doesn't enable module versioning, disable Module.symvers file check.
os.Setenv("IGNORE_MISSING_MODULE_SYMVERS", "1")
return nil
}
func correctKernelMagicVersionIfNeeded(kernelSrcDir string) error {
// Normally COS kernel release version has a "+" in the end, e.g. "4.19.102+". But
// the utsrelease file generated here doesn't have it, e.g. "4.19.102". Thus we need
// to correct the utsrelease file to make it match the real COS kernel release version.
utsCmd, err := execCommand("uname", "-r").Output()
if err != nil {
return errors.Wrap(err, "failed to run `uname -r`")
}
kernelVersionCmd := strings.TrimSpace(string(utsCmd))
utsFile, err := ioutil.ReadFile(filepath.Join(kernelSrcDir, utsFilepath))
if err != nil {
return errors.Wrap(err, "failed to read utsrelease file")
}
kernelVersionFile := strings.Trim(strings.Fields(string(utsFile))[2], `"`)
if kernelVersionCmd != kernelVersionFile {
newUtsFile := strings.ReplaceAll(string(utsFile), kernelVersionFile, kernelVersionCmd)
log.Info("Modifying kernel release version magic string in source files")
if err := ioutil.WriteFile(filepath.Join(kernelSrcDir, utsFilepath), []byte(newUtsFile), 0644); err != nil {
return errors.Wrap(err, "failed to write to utsrelease file")
}
}
return nil
}