mirror of
https://github.com/clearlinux/clr-installer.git
synced 2026-09-02 19:51:30 +00:00
This package provides MakeIso(), which when called, will take files already created by the image making process and produce an ISO in the current directory. ISO generation requires that images already be EFI-enabled and that those images mount this EFI partition at /boot (in relation to the image root) - MakeIso requires some files from this process and will fail if those files are not present. Signed-off-by: Brian J Lovin <brian.j.lovin@intel.com>
401 lines
9.4 KiB
Go
401 lines
9.4 KiB
Go
// Copyright © 2019 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/user"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/digitalocean/go-smbios/smbios"
|
|
"github.com/leonelquinteros/gotext"
|
|
|
|
"github.com/clearlinux/clr-installer/errors"
|
|
)
|
|
|
|
// ClearVersion is running version of the OS
|
|
var ClearVersion string
|
|
|
|
// ParseOSClearVersion parses the current version of the Clear Linux OS
|
|
func ParseOSClearVersion() error {
|
|
var versionBuf []byte
|
|
var err error
|
|
|
|
// in order to avoid issues raised by format bumps between installers image
|
|
// version and the latest released we assume the installers host version
|
|
// in other words we use the same version swupd is based on
|
|
if versionBuf, err = ioutil.ReadFile("/usr/lib/os-release"); err != nil {
|
|
return errors.Errorf("Read version file /usr/lib/os-release: %v", err)
|
|
}
|
|
versionExp := regexp.MustCompile(`VERSION_ID=([0-9][0-9]*)`)
|
|
match := versionExp.FindSubmatch(versionBuf)
|
|
|
|
if len(match) < 2 {
|
|
return errors.Errorf("Version not found in /usr/lib/os-release")
|
|
}
|
|
|
|
ClearVersion = string(match[1])
|
|
|
|
return nil
|
|
}
|
|
|
|
// MkdirAll similar to go's standard os.MkdirAll() this function creates a directory
|
|
// named path, along with any necessary parents but also checks if path exists and
|
|
// takes no action if that's true.
|
|
func MkdirAll(path string, perm os.FileMode) error {
|
|
if _, err := os.Stat(path); err == nil {
|
|
return nil
|
|
}
|
|
|
|
if err := os.MkdirAll(path, perm); err != nil {
|
|
return errors.Errorf("mkdir %s: %v", path, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CopyAllFiles copy all of the files in a directory recursively
|
|
func CopyAllFiles(srcDir string, destDir string) error {
|
|
err := filepath.Walk(srcDir,
|
|
func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return errors.Errorf("Failure accessing a path %q: %v\n", path, err)
|
|
}
|
|
target := filepath.Join(destDir, path)
|
|
if info.IsDir() {
|
|
// Create the matching target directory
|
|
if err := MkdirAll(target, info.Mode()); err != nil {
|
|
return errors.Errorf("Failed to mkdir %q", target)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
if err := CopyFile(path, target); err != nil {
|
|
return errors.Errorf("Failed to copy file %q", target)
|
|
}
|
|
|
|
// Ensure all contents is owned correctly
|
|
sys := info.Sys()
|
|
stat, ok := sys.(*syscall.Stat_t)
|
|
if ok {
|
|
if err := os.Chown(target, int(stat.Uid), int(stat.Gid)); err != nil {
|
|
return errors.Errorf("Failed to change ownership of %q to UID:%d, GID:%d",
|
|
target, stat.Uid, stat.Gid)
|
|
}
|
|
} else {
|
|
return errors.Errorf("Could not stat file %q", path)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to copy files")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CopyFile copies src file to dest
|
|
func CopyFile(src string, dest string) error {
|
|
destDir := filepath.Dir(dest)
|
|
|
|
srcInfo, err := os.Stat(src)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return errors.Errorf("no such file: %s", src)
|
|
}
|
|
return errors.Wrap(err)
|
|
}
|
|
|
|
if _, err = os.Stat(destDir); err != nil {
|
|
if os.IsNotExist(err) {
|
|
return errors.Errorf("no such dest directory: %s", destDir)
|
|
}
|
|
return errors.Wrap(err)
|
|
}
|
|
|
|
data, err := ioutil.ReadFile(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = ioutil.WriteFile(dest, data, srcInfo.Mode()&os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// FileExists returns true if the file or directory exists
|
|
// else it returns false and the associated error
|
|
func FileExists(filePath string) (bool, error) {
|
|
_, err := os.Stat(filePath)
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
|
|
return true, err
|
|
}
|
|
|
|
// VerifyRootUser returns an error if we're not running as root
|
|
func VerifyRootUser() string {
|
|
// ProgName is the short name of this executable
|
|
progName := path.Base(os.Args[0])
|
|
|
|
user, err := user.Current()
|
|
if err != nil {
|
|
return fmt.Sprintf("%s MUST run as 'root' user to install! (user=%s)",
|
|
progName, "UNKNOWN")
|
|
}
|
|
|
|
if user.Uid != "0" {
|
|
return fmt.Sprintf("%s MUST run as 'root' user to install! (user=%s)",
|
|
progName, user.Uid)
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// IsClearLinux checks if the current OS is Clear by looking for Swupd
|
|
// Mostly used in Go Testing
|
|
func IsClearLinux() bool {
|
|
is := false
|
|
|
|
if runtime.GOOS == "linux" {
|
|
clearFile := "/usr/bin/swupd"
|
|
if _, err := os.Stat(clearFile); !os.IsNotExist(err) {
|
|
is = true
|
|
}
|
|
}
|
|
|
|
return is
|
|
}
|
|
|
|
// IsRoot checks if the current User is root (UID 0)
|
|
// Mostly used in Go Testing
|
|
func IsRoot() bool {
|
|
is := false
|
|
|
|
user, err := user.Current()
|
|
if err == nil {
|
|
if user.Uid == "0" {
|
|
is = true
|
|
}
|
|
}
|
|
|
|
return is
|
|
}
|
|
|
|
// StringSliceContains returns true if sl contains str, returns false otherwise
|
|
func StringSliceContains(sl []string, str string) bool {
|
|
for _, curr := range sl {
|
|
if curr == str {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IntSliceContains returns true if is contains value, returns false otherwise
|
|
func IntSliceContains(is []int, value int) bool {
|
|
for _, curr := range is {
|
|
if curr == value {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsCheckCoverage returns true if CHECK_COVERAGE variable is set
|
|
func IsCheckCoverage() bool {
|
|
return os.Getenv("CHECK_COVERAGE") != ""
|
|
}
|
|
|
|
// IsStdoutTTY returns true if the stdout is attached to a tty
|
|
func IsStdoutTTY() bool {
|
|
var termios syscall.Termios
|
|
|
|
fd := os.Stdout.Fd()
|
|
ptr := uintptr(unsafe.Pointer(&termios))
|
|
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, syscall.TCGETS, ptr, 0, 0, 0)
|
|
|
|
return err == 0
|
|
}
|
|
|
|
// ExpandVariables iterates over vars map and replace all the ocorrences of ${var} or
|
|
// $var in the str string
|
|
func ExpandVariables(vars map[string]string, str string) string {
|
|
// iterate over available variables
|
|
for k, v := range vars {
|
|
// tries to replace both ${var} and $var forms
|
|
for _, rep := range []string{fmt.Sprintf("$%s", k), fmt.Sprintf("${%s}", k)} {
|
|
if strings.Contains(str, rep) {
|
|
return strings.Replace(str, rep, v, -1)
|
|
}
|
|
}
|
|
}
|
|
|
|
// if no variables are expanded return the original string
|
|
return str
|
|
}
|
|
|
|
// IsVirtualBox returns true if the running system is executed
|
|
// from within VirtualBox
|
|
// Attempt to parse the System Management BIOS (SMBIOS) and
|
|
// Desktop Management Interface (DMI) to determine if we are
|
|
// executing inside a VirtualBox. Ignoring error conditions and
|
|
// assuming we are not VirtualBox.
|
|
func IsVirtualBox() bool {
|
|
virtualBox := false
|
|
|
|
// Find SMBIOS data in operating system-specific location.
|
|
rc, _, err := smbios.Stream()
|
|
if err != nil {
|
|
return virtualBox
|
|
}
|
|
|
|
// Be sure to close the stream!
|
|
defer func() { _ = rc.Close() }()
|
|
|
|
// Decode SMBIOS structures from the stream.
|
|
// https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.1.1.pdf
|
|
d := smbios.NewDecoder(rc)
|
|
ss, err := d.Decode()
|
|
if err != nil {
|
|
return virtualBox
|
|
}
|
|
|
|
for _, s := range ss {
|
|
// 7.2 System Information (Type 1)
|
|
if s.Header.Type == 1 {
|
|
for _, str := range s.Strings {
|
|
if strings.Contains(strings.ToLower(str), "virtualbox") {
|
|
virtualBox = true
|
|
return virtualBox
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return virtualBox
|
|
}
|
|
|
|
// LookupThemeDir returns the directory to use for reading
|
|
// theme files for the UI.
|
|
func LookupThemeDir() (string, error) {
|
|
return lookupDir("/usr/share/clr-installer/themes", "CLR_INSTALLER_THEME_DIR")
|
|
}
|
|
|
|
// LookupLocaleDir returns the directory to use for reading
|
|
// locale files for the UI.
|
|
func LookupLocaleDir() (string, error) {
|
|
return lookupDir("/usr/share/locale", "CLR_INSTALLER_LOCALE_DIR")
|
|
}
|
|
|
|
// lookupDir returns the full directory path of a directory.
|
|
// It will look in the local developers build area first,
|
|
// or the ENV variable, and finally the standard
|
|
// system install location
|
|
func lookupDir(dir, env string) (string, error) {
|
|
var result string
|
|
|
|
fullDir := []string{
|
|
os.Getenv(env),
|
|
}
|
|
|
|
src, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if strings.Contains(src, "/.gopath/bin") {
|
|
fullDir = append(fullDir, strings.Replace(src, "bin", "../"+filepath.Base(dir), 1))
|
|
}
|
|
|
|
fullDir = append(fullDir, dir+"/")
|
|
|
|
for _, curr := range fullDir {
|
|
if _, err := os.Stat(curr); os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
|
|
result = curr
|
|
break
|
|
}
|
|
|
|
if result == "" {
|
|
panic(errors.Errorf("Could not find a %s dir", dir))
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Locale is used to access the localization functions
|
|
var Locale *gotext.Locale
|
|
|
|
// SetLocale sets the locale of the installer based on the selected language
|
|
func SetLocale(language string) {
|
|
dir, err := LookupLocaleDir()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
Locale = nil
|
|
} else {
|
|
Locale = gotext.NewLocale(dir, language)
|
|
Locale.AddDomain("clr-installer")
|
|
}
|
|
}
|
|
|
|
// LookupISOTemplateDir returns the directory to use for reading
|
|
// template files for ISO creation. It will look in the local developers
|
|
// build area first, or the ENV variable, and finally the standard
|
|
// system install location
|
|
func LookupISOTemplateDir() (string, error) {
|
|
var result string
|
|
|
|
isoTemplateDirs := []string{
|
|
os.Getenv("CLR_INSTALLER_ISO_TEMPLATE_DIR"),
|
|
}
|
|
|
|
src, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if strings.Contains(src, "/.gopath/bin") {
|
|
isoTemplateDirs = append(isoTemplateDirs, strings.Replace(src, "bin", "../iso_templates", 1))
|
|
}
|
|
|
|
isoTemplateDirs = append(isoTemplateDirs, "/usr/share/clr-installer/iso_templates/")
|
|
|
|
for _, curr := range isoTemplateDirs {
|
|
if _, err := os.Stat(curr); os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
|
|
result = curr
|
|
break
|
|
}
|
|
|
|
if result == "" {
|
|
panic(errors.Errorf("Could not find a ISO templates dir"))
|
|
}
|
|
|
|
return result, nil
|
|
}
|