/* * Copyright © 2019 – 2020 Red Hat Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package utils import ( "errors" "fmt" "os" "os/exec" "os/user" "sort" "syscall" "github.com/containers/toolbox/pkg/shell" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) const ( idTruncLength = 12 ) var ( preservedEnvironmentVariables = []string{ "COLORTERM", "DBUS_SESSION_BUS_ADDRESS", "DBUS_SYSTEM_BUS_ADDRESS", "DESKTOP_SESSION", "DISPLAY", "LANG", "SHELL", "SSH_AUTH_SOCK", "TERM", "TOOLBOX_PATH", "VTE_VERSION", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP", "XDG_DATA_DIRS", "XDG_MENU_PREFIX", "XDG_RUNTIME_DIR", "XDG_SEAT", "XDG_SESSION_DESKTOP", "XDG_SESSION_ID", "XDG_SESSION_TYPE", "XDG_VTNR", } ) func ForwardToHost() (int, error) { envOptions := GetEnvOptionsForPreservedVariables() toolboxPath := os.Getenv("TOOLBOX_PATH") commandLineArgs := os.Args[1:] var flatpakSpawnArgs []string flatpakSpawnArgs = append(flatpakSpawnArgs, envOptions...) flatpakSpawnArgs = append(flatpakSpawnArgs, []string{ "--host", toolboxPath, }...) flatpakSpawnArgs = append(flatpakSpawnArgs, commandLineArgs...) logrus.Debug("Forwarding to host:") logrus.Debugf("%s", toolboxPath) for _, arg := range commandLineArgs { logrus.Debugf("%s", arg) } exitCode, err := shell.RunWithExitCode("flatpak-spawn", os.Stdin, os.Stdout, nil, flatpakSpawnArgs...) if err != nil { return exitCode, err } return exitCode, nil } // GetCgroupsVersion returns the cgroups version of the host // // Based on the IsCgroup2UnifiedMode function in: // https://github.com/containers/libpod/tree/master/pkg/cgroups func GetCgroupsVersion() (int, error) { var st syscall.Statfs_t if err := syscall.Statfs("/sys/fs/cgroup", &st); err != nil { return -1, err } version := 1 if st.Type == unix.CGROUP2_SUPER_MAGIC { version = 2 } return version, nil } func GetEnvOptionsForPreservedVariables() []string { logrus.Debug("Creating list of environment variables to forward") var envOptions []string for _, variable := range preservedEnvironmentVariables { value, found := os.LookupEnv(variable) if !found { logrus.Debugf("%s is unset", variable) continue } logrus.Debugf("%s=%s", variable, value) envOptions = append(envOptions, fmt.Sprintf("--env=%s=%s", variable, value)) } return envOptions } // GetGroupForSudo returns the name of the sudoers group. // // Some distros call it 'sudo' (eg. Ubuntu) and some call it 'wheel' (eg. Fedora). func GetGroupForSudo() (string, error) { logrus.Debug("Looking up group for sudo") groups := []string{"sudo", "wheel"} for _, group := range groups { if _, err := user.LookupGroup(group); err == nil { logrus.Debugf("Group for sudo is %s", group) return group, nil } } return "", errors.New("group for sudo not found") } // ShortID shortens provided id to first 12 characters. func ShortID(id string) string { if len(id) > idTruncLength { return id[:idTruncLength] } return id } // PathExists wraps around os.Stat providing a nice interface for checking an existence of a path. func PathExists(path string) bool { if _, err := os.Stat(path); !os.IsNotExist(err) { return true } return false } func IsInsideContainer() bool { if PathExists("/run/.containerenv") { return true } return false } func IsInsideToolboxContainer() bool { if PathExists("/run/.toolboxenv") { return true } return false } func JoinJSON(joinkey string, maps ...[]map[string]interface{}) []map[string]interface{} { var json []map[string]interface{} found := make(map[string]bool) // Iterate over every json provided and check if it is already in the final json // If it contains some invalid entry (equals nil), then skip that entry for _, m := range maps { for _, entry := range m { if entry["names"] == nil && entry["Names"] == nil { continue } key := entry[joinkey].(string) if _, ok := found[key]; !ok { found[key] = true json = append(json, entry) } } } return json } func ShowManual(manual string) error { manBinary, err := exec.LookPath("man") if err != nil { if errors.Is(err, exec.ErrNotFound) { return errors.New("man(1) not found") } return errors.New("failed to lookup man(1)") } manualArgs := []string{"man", manual} env := os.Environ() stderrFd := os.Stderr.Fd() stderrFdInt := int(stderrFd) stdoutFd := os.Stdout.Fd() stdoutFdInt := int(stdoutFd) if err := syscall.Dup2(stdoutFdInt, stderrFdInt); err != nil { return errors.New("failed to redirect standard error to standard output") } if err := syscall.Exec(manBinary, manualArgs, env); err != nil { return errors.New("failed to invoke man(1)") } return nil } func SortJSON(json []map[string]interface{}, key string, hasInterface bool) []map[string]interface{} { sort.Slice(json, func(i, j int) bool { if hasInterface { return json[i][key].([]interface{})[0].(string) < json[j][key].([]interface{})[0].(string) } return json[i][key].(string) < json[j][key].(string) }) return json }