Heavy refactoring, see PR #28

This commit is contained in:
kayos@tcp.direct 2024-06-16 23:57:19 -07:00
parent ab104bb7bc
commit 3337efcb8f
No known key found for this signature in database
GPG key ID: 4B841471B4BEE979
11 changed files with 323 additions and 156 deletions

View file

@ -0,0 +1,5 @@
package common
const PermissionNotice = "\nPermissions error occured during file operation.\n" +
"This could mean you initially ran QuickPassthrough as root or with sudo, but are now running it as a normal user. " +
"Please try running QuickPassthrough as root/with sudo again."

View file

@ -1,16 +1,19 @@
package configs package configs
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"os/exec"
"regexp" "regexp"
"strings" "strings"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck" "github.com/HikariKnight/ls-iommu/pkg/errorcheck"
"github.com/klauspost/cpuid/v2"
"github.com/HikariKnight/quickpassthrough/internal/logger" "github.com/HikariKnight/quickpassthrough/internal/logger"
"github.com/HikariKnight/quickpassthrough/pkg/command" "github.com/HikariKnight/quickpassthrough/pkg/command"
"github.com/HikariKnight/quickpassthrough/pkg/fileio" "github.com/HikariKnight/quickpassthrough/pkg/fileio"
"github.com/klauspost/cpuid/v2"
) )
// This function just adds what bootloader the system has to our config.bootloader value // This function just adds what bootloader the system has to our config.bootloader value
@ -70,38 +73,32 @@ func Set_Cmdline(gpu_IDs []string) {
fileio.AppendContent(fmt.Sprintf(" vfio_pci.ids=%s", strings.Join(gpu_IDs, ",")), config.Path.CMDLINE) fileio.AppendContent(fmt.Sprintf(" vfio_pci.ids=%s", strings.Join(gpu_IDs, ",")), config.Path.CMDLINE)
} }
// Configures systemd-boot using kernelstub // Set_KernelStub configures systemd-boot using kernelstub.
func Set_KernelStub() string { func Set_KernelStub(isRoot bool) {
// Get the config // Get the config
config := GetConfig() config := GetConfig()
// Get the kernel args // Get the kernel args
kernel_args := fileio.ReadFile(config.Path.CMDLINE) kernel_args := fileio.ReadFile(config.Path.CMDLINE)
// Write to logger // Run and log, check for errors
logger.Printf("Running command:\nsudo kernelstub -a \"%s\"\n", kernel_args) errorcheck.ErrorCheck(command.ExecAndLogSudo(isRoot, true,
"kernelstub -a "+kernel_args,
// Run the command ),
_, err := command.Run("sudo", "kernelstub", "-a", kernel_args) "Error, kernelstub command returned exit code 1",
errorcheck.ErrorCheck(err, "Error, kernelstub command returned exit code 1") )
// Return what we did
return fmt.Sprintf("Executed: sudo kernelstub -a \"%s\"", kernel_args)
} }
// Configures grub2 and/or systemd-boot using grubby // Set_Grubby configures grub2 and/or systemd-boot using grubby
func Set_Grubby() string { func Set_Grubby(isRoot bool) string {
// Get the config // Get the config
config := GetConfig() config := GetConfig()
// Get the kernel args // Get the kernel args
kernel_args := fileio.ReadFile(config.Path.CMDLINE) kernel_args := fileio.ReadFile(config.Path.CMDLINE)
// Write to logger // Run and log, check for errors
logger.Printf("Running command:\nsudo grubby --update-kernel=ALL --args=\"%s\"\n", kernel_args) err := command.ExecAndLogSudo(isRoot, true, "grubby --update-kernel=ALL "+fmt.Sprintf("--args=%s", kernel_args))
// Run the command
_, err := command.Run("sudo", "grubby", "--update-kernel=ALL", fmt.Sprintf("--args=%s", kernel_args))
errorcheck.ErrorCheck(err, "Error, grubby command returned exit code 1") errorcheck.ErrorCheck(err, "Error, grubby command returned exit code 1")
// Return what we did // Return what we did
@ -116,8 +113,8 @@ func Configure_Grub2() {
conffile := fmt.Sprintf("%s/grub", config.Path.DEFAULT) conffile := fmt.Sprintf("%s/grub", config.Path.DEFAULT)
// Make sure we start from scratch by deleting any old file // Make sure we start from scratch by deleting any old file
if fileio.FileExist(conffile) { if exists, _ := fileio.FileExist(conffile); exists {
os.Remove(conffile) _ = os.Remove(conffile)
} }
// Make a regex to get the system path instead of the config path // Make a regex to get the system path instead of the config path
@ -201,8 +198,8 @@ func clean_Grub2_Args(old_kernel_args []string) []string {
return clean_kernel_args return clean_kernel_args
} }
// This function copies our config to /etc/default/grub and updates grub // Set_Grub2 copies our config to /etc/default/grub and updates grub
func Set_Grub2() ([]string, error) { func Set_Grub2(isRoot bool) error {
// Get the config // Get the config
config := GetConfig() config := GetConfig()
@ -213,38 +210,45 @@ func Set_Grub2() ([]string, error) {
sysfile_re := regexp.MustCompile(`^config`) sysfile_re := regexp.MustCompile(`^config`)
sysfile := sysfile_re.ReplaceAllString(conffile, "") sysfile := sysfile_re.ReplaceAllString(conffile, "")
// Write to logger // [CopyToSystem] will log the operation
logger.Printf("Executing command:\nsudo cp -v \"%s\" %s\n", conffile, sysfile) // logger.Printf("Executing command:\nsudo cp -v \"%s\" %s\n", conffile, sysfile)
// Make our output slice // Copy files to system, logging and error checking is done in the function
var output []string CopyToSystem(isRoot, conffile, sysfile)
// Copy files to system
output = append(output, CopyToSystem(conffile, sysfile))
// Set a variable for the mkconfig command // Set a variable for the mkconfig command
mkconfig := "grub-mkconfig" var mkconfig string
var grubPath = "/boot/grub/grub.cfg"
var lpErr error
// Check for grub-mkconfig // Check for grub-mkconfig
_, err := command.Run("which", "grub-mkconfig") mkconfig, lpErr = exec.LookPath("grub-mkconfig")
if err == nil { switch {
// Set binary as grub-mkconfig case errors.Is(lpErr, exec.ErrNotFound) || mkconfig == "":
mkconfig = "grub-mkconfig" // Check for grub2-mkconfig
} else { mkconfig, lpErr = exec.LookPath("grub2-mkconfig")
mkconfig = "grub2-mkconfig" if lpErr == nil && mkconfig != "" {
grubPath = "/boot/grub2/grub.cfg"
break // skip below, we found grub2-mkconfig
}
if lpErr == nil {
// we know mkconfig is empty despite no error;
// so set an error for [errorcheck.ErrorCheck].
lpErr = errors.New("neither grub-mkconfig or grub2-mkconfig found")
}
errorcheck.ErrorCheck(lpErr, lpErr.Error()+"\n")
return lpErr // note: unreachable as [errorcheck.ErrorCheck] calls fatal
default:
} }
// Update grub.cfg _, mklog, err := command.RunErrSudo(isRoot, mkconfig, "-o", grubPath)
if fileio.FileExist("/boot/grub/grub.cfg") {
output = append(output, fmt.Sprintf("Executed: sudo %s -o /boot/grub/grub.cfg\nSee debug.log for more detailed output", mkconfig))
_, mklog, err := command.RunErr("sudo", mkconfig, "-o", "/boot/grub/grub.cfg")
logger.Printf(strings.Join(mklog, "\n"))
errorcheck.ErrorCheck(err, "Failed to update /boot/grub/grub.cfg")
} else {
output = append(output, fmt.Sprintf("Executed: sudo %s -o /boot/grub/grub.cfg\nSee debug.log for more detailed output", mkconfig))
_, mklog, err := command.RunErr("sudo", mkconfig, "-o", "/boot/grub2/grub.cfg")
logger.Printf(strings.Join(mklog, "\n"))
errorcheck.ErrorCheck(err, "Failed to update /boot/grub/grub.cfg")
}
return output, err // tabulate the output, [command.RunErrSudo] logged the execution.
logger.Printf("\t" + strings.Join(mklog, "\n\t"))
errorcheck.ErrorCheck(err, "Failed to update /boot/grub/grub.cfg")
// always returns nil as [errorcheck.ErrorCheck] calls fatal
// keeping the ret signature, as we should consider passing down errors
// but that's a massive rabbit hole to go down for this codebase as a whole
return err
} }

View file

@ -9,7 +9,7 @@ import (
"github.com/HikariKnight/quickpassthrough/pkg/fileio" "github.com/HikariKnight/quickpassthrough/pkg/fileio"
) )
// This function writes a dracut configuration file for /etc/dracut.conf.d/ // Set_Dracut writes a dracut configuration file for `/etc/dracut.conf.d/`.
func Set_Dracut() { func Set_Dracut() {
config := GetConfig() config := GetConfig()
@ -17,8 +17,8 @@ func Set_Dracut() {
dracutConf := fmt.Sprintf("%s/vfio.conf", config.Path.DRACUT) dracutConf := fmt.Sprintf("%s/vfio.conf", config.Path.DRACUT)
// If the file already exists then delete it // If the file already exists then delete it
if fileio.FileExist(dracutConf) { if exists, _ := fileio.FileExist(dracutConf); exists {
os.Remove(dracutConf) _ = os.Remove(dracutConf)
} }
// Write to logger // Write to logger

View file

@ -10,14 +10,14 @@ import (
"github.com/HikariKnight/quickpassthrough/pkg/fileio" "github.com/HikariKnight/quickpassthrough/pkg/fileio"
) )
// This function copies the content of /etc/mkinitcpio.conf to the config folder and does an inline replace/insert on the MODULES=() line // Set_Mkinitcpio copies the content of /etc/mkinitcpio.conf to the config folder and does an inline replace/insert on the MODULES=() line
func Set_Mkinitcpio() { func Set_Mkinitcpio() {
// Get the config struct // Get the config struct
config := GetConfig() config := GetConfig()
// Make sure we start from scratch by deleting any old file // Make sure we start from scratch by deleting any old file
if fileio.FileExist(config.Path.MKINITCPIO) { if exists, _ := fileio.FileExist(config.Path.MKINITCPIO); exists {
os.Remove(config.Path.MKINITCPIO) _ = os.Remove(config.Path.MKINITCPIO)
} }
// Make a regex to get the system path instead of the config path // Make a regex to get the system path instead of the config path

View file

@ -30,9 +30,9 @@ func Set_Modprobe(gpu_IDs []string) {
conffile := fmt.Sprintf("%s/vfio.conf", config.Path.MODPROBE) conffile := fmt.Sprintf("%s/vfio.conf", config.Path.MODPROBE)
// If the file exists // If the file exists
if fileio.FileExist(conffile) { if exists, _ := fileio.FileExist(conffile); exists {
// Delete the old file // Delete the old file
os.Remove(conffile) _ = os.Remove(conffile)
} }
content := fmt.Sprint( content := fmt.Sprint(

View file

@ -1,6 +1,7 @@
package configs package configs
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"regexp" "regexp"
@ -8,6 +9,7 @@ import (
"github.com/HikariKnight/ls-iommu/pkg/errorcheck" "github.com/HikariKnight/ls-iommu/pkg/errorcheck"
"github.com/klauspost/cpuid/v2" "github.com/klauspost/cpuid/v2"
"github.com/HikariKnight/quickpassthrough/internal/common"
"github.com/HikariKnight/quickpassthrough/internal/logger" "github.com/HikariKnight/quickpassthrough/internal/logger"
"github.com/HikariKnight/quickpassthrough/pkg/command" "github.com/HikariKnight/quickpassthrough/pkg/command"
"github.com/HikariKnight/quickpassthrough/pkg/fileio" "github.com/HikariKnight/quickpassthrough/pkg/fileio"
@ -34,7 +36,7 @@ type Config struct {
IsRoot bool IsRoot bool
} }
// Gets the path to all the config files // GetConfigPaths retrieves the path to all the config files.
func GetConfigPaths() *Path { func GetConfigPaths() *Path {
Paths := &Path{ Paths := &Path{
CMDLINE: "config/kernel_args", CMDLINE: "config/kernel_args",
@ -50,7 +52,7 @@ func GetConfigPaths() *Path {
return Paths return Paths
} }
// Gets all the configs and returns the struct // GetConfig retrieves all the configs and returns the struct.
func GetConfig() *Config { func GetConfig() *Config {
config := &Config{ config := &Config{
Bootloader: "unknown", Bootloader: "unknown",
@ -66,7 +68,7 @@ func GetConfig() *Config {
return config return config
} }
// Constructs the empty config files and folders based on what exists on the system // InitConfigs constructs the empty config files and folders based on what exists on the system
func InitConfigs() { func InitConfigs() {
config := GetConfig() config := GetConfig()
@ -79,10 +81,24 @@ func InitConfigs() {
} }
// Remove old config // Remove old config
os.RemoveAll("config") if err := os.RemoveAll("config"); err != nil && !errors.Is(err, os.ErrNotExist) {
if errors.Is(err, os.ErrPermission) {
errorcheck.ErrorCheck(err, common.PermissionNotice)
return // note: unreachable due to ErrorCheck calling fatal
}
// won't be called if the error is ErrNotExist
errorcheck.ErrorCheck(err, "\nError removing old config")
}
// Make the config folder // Make the config folder
os.Mkdir("config", os.ModePerm) if err := os.Mkdir("config", os.ModePerm); err != nil && !errors.Is(err, os.ErrExist) {
if errors.Is(err, os.ErrPermission) {
errorcheck.ErrorCheck(err, common.PermissionNotice)
return // note: unreachable due to ErrorCheck calling fatal
}
// won't be called if the error is ErrExist
errorcheck.ErrorCheck(err, "\nError making config folder")
}
// Make a regex to get the system path instead of the config path // Make a regex to get the system path instead of the config path
syspath_re := regexp.MustCompile(`^config`) syspath_re := regexp.MustCompile(`^config`)
@ -92,8 +108,20 @@ func InitConfigs() {
// Get the system path // Get the system path
syspath := syspath_re.ReplaceAllString(confpath, "") syspath := syspath_re.ReplaceAllString(confpath, "")
exists, err := fileio.FileExist(syspath)
// If we received an error that is not ErrNotExist
if err != nil {
if errors.Is(err, os.ErrPermission) {
errorcheck.ErrorCheck(err, common.PermissionNotice)
return // note: unreachable due to ErrorCheck calling fatal
}
errorcheck.ErrorCheck(err, "\nError checking for directory: "+syspath)
continue // note: also unreachable
}
// If the path exists // If the path exists
if fileio.FileExist(syspath) { if exists {
// Write to log // Write to log
logger.Printf( logger.Printf(
"%s found on the system\n"+ "%s found on the system\n"+
@ -106,8 +134,13 @@ func InitConfigs() {
makeBackupDir(syspath) makeBackupDir(syspath)
// Create the directories for our configs // Create the directories for our configs
err := os.MkdirAll(confpath, os.ModePerm) if err = os.MkdirAll(confpath, os.ModePerm); err != nil && !errors.Is(err, os.ErrExist) {
errorcheck.ErrorCheck(err) if errors.Is(err, os.ErrPermission) {
errorcheck.ErrorCheck(err, common.PermissionNotice)
return // note: unreachable due to ErrorCheck calling fatal
}
errorcheck.ErrorCheck(err, "\nError making directory: "+confpath)
}
} }
} }
@ -130,7 +163,19 @@ func InitConfigs() {
sysfile := syspath_re.ReplaceAllString(conffile, "") sysfile := syspath_re.ReplaceAllString(conffile, "")
// If the file exists // If the file exists
if fileio.FileExist(sysfile) { exists, err := fileio.FileExist(sysfile)
// If we received an error that is not ErrNotExist
if err != nil {
if errors.Is(err, os.ErrPermission) {
errorcheck.ErrorCheck(err, common.PermissionNotice)
return // note: unreachable due to ErrorCheck calling fatal
}
errorcheck.ErrorCheck(err, "\nError checking for file: "+sysfile)
continue // note: also unreachable
}
if exists {
// Write to log // Write to log
logger.Printf( logger.Printf(
"%s found on the system\n"+ "%s found on the system\n"+
@ -143,14 +188,24 @@ func InitConfigs() {
file, err := os.Create(conffile) file, err := os.Create(conffile)
errorcheck.ErrorCheck(err) errorcheck.ErrorCheck(err)
// Close the file so we can edit it // Close the file so we can edit it
file.Close() _ = file.Close()
// Backup the sysfile if we do not have a backup // Backup the sysfile if we do not have a backup
backupFile(sysfile) backupFile(sysfile)
} }
exists, err = fileio.FileExist(conffile)
if err != nil {
if errors.Is(err, os.ErrPermission) {
errorcheck.ErrorCheck(err, common.PermissionNotice)
return // note: unreachable due to ErrorCheck calling fatal
}
errorcheck.ErrorCheck(err, "\nError checking for file: "+conffile)
continue // note: also unreachable
}
// If we now have a config that exists // If we now have a config that exists
if fileio.FileExist(conffile) { if exists {
switch conffile { switch conffile {
case config.Path.ETCMODULES: case config.Path.ETCMODULES:
// Write to logger // Write to logger
@ -206,14 +261,32 @@ func backupFile(source string) {
// Make a destination path // Make a destination path
dest := fmt.Sprintf("backup%s", source) dest := fmt.Sprintf("backup%s", source)
configExists, configFileError := fileio.FileExist(fmt.Sprintf("config%s", source))
sysExists, sysFileError := fileio.FileExist(source)
destExists, destFileError := fileio.FileExist(dest)
// If we received an error that is not ErrNotExist on any of the files
for _, err := range []error{configFileError, sysFileError, destFileError} {
if err != nil {
if errors.Is(configFileError, os.ErrPermission) {
errorcheck.ErrorCheck(configFileError, common.PermissionNotice)
return // note: unreachable due to ErrorCheck calling fatal
}
errorcheck.ErrorCheck(configFileError, "\nError checking for file: "+source)
return // note: also unreachable
}
}
switch {
// If the file exists in the config but not on the system it is a file we make // If the file exists in the config but not on the system it is a file we make
if fileio.FileExist(fmt.Sprintf("config%s", source)) && !fileio.FileExist(source) { case configExists && !sysExists:
// Create the blank file so that a copy of the backup folder to /etc // Create the blank file so that a copy of the backup folder to /etc
file, err := os.Create(dest) file, err := os.Create(dest)
errorcheck.ErrorCheck(err, "Error creating file %s\n", dest) errorcheck.ErrorCheck(err, "Error creating file %s\n", dest)
file.Close() _ = file.Close()
} else if !fileio.FileExist(dest) {
// If a backup of the file does not exist // If a backup of the file does not exist
case sysExists && !destExists:
// Write to the logger // Write to the logger
logger.Printf("No first time backup of %s detected.\nCreating a backup at %s\n", source, dest) logger.Printf("No first time backup of %s detected.\nCreating a backup at %s\n", source, dest)
@ -225,29 +298,52 @@ func backupFile(source string) {
func makeBackupDir(dest string) { func makeBackupDir(dest string) {
// If a backup directory does not exist // If a backup directory does not exist
if !fileio.FileExist("backup/") { exists, err := fileio.FileExist("backup/")
if err != nil {
// If we received an error that is not ErrNotExist
if errors.Is(err, os.ErrPermission) {
errorcheck.ErrorCheck(err, common.PermissionNotice)
return // note: unreachable due to ErrorCheck calling fatal
}
errorcheck.ErrorCheck(err, "Error checking for backup/ folder")
return // note: also unreachable
}
if !exists {
// Write to the logger // Write to the logger
logger.Printf("Backup directory does not exist!\nCreating backup directory for first run backup") logger.Printf("Backup directory does not exist!\nCreating backup directory for first run backup")
} }
// Make the empty directories // Make the empty directories
err := os.MkdirAll(fmt.Sprintf("backup/%s", dest), os.ModePerm) if err = os.MkdirAll(fmt.Sprintf("backup/%s", dest), os.ModePerm); errors.Is(err, os.ErrExist) {
err = nil
}
if errors.Is(err, os.ErrPermission) {
errorcheck.ErrorCheck(err, common.PermissionNotice)
return // note: unreachable due to ErrorCheck calling fatal
}
errorcheck.ErrorCheck(err, "Error making backup/ folder") errorcheck.ErrorCheck(err, "Error making backup/ folder")
} }
// Copy a file to the system, make sure you have run command.Elevate() recently // CopyToSystem copies a file to the system.
func CopyToSystem(conffile, sysfile string) string { func CopyToSystem(isRoot bool, conffile, sysfile string) {
// Since we should be elevated with our sudo token we will copy with cp // Since we should be elevated with our sudo token we will copy with cp
// (using built in functions will not work as we are running as the normal user) // (using built in functions will not work as we are running as the normal user)
output, _ := command.Run("sudo", "cp", "-v", conffile, sysfile)
// Clean the output // ExecAndLogSudo will write to the logger, so just print here
clean_re := regexp.MustCompile(`\n`) fmt.Printf("Copying: %s to %s\n", conffile, sysfile)
clean_output := clean_re.ReplaceAllString(output[0], "")
// Write output to logger // [command.ExecAndLogSudo] will log the command's output
logger.Printf("%s\n", clean_output) errorcheck.ErrorCheck(command.ExecAndLogSudo(isRoot, false,
fmt.Sprintf("cp -v \"%s\" %s", conffile, sysfile),
), // if error, log and exit
fmt.Sprintf("Failed to copy %s to %s", conffile, sysfile),
)
// Return the output // ---------------------------------------------------------------------------------
return fmt.Sprintf("Copying: %s", clean_output) // note that if we failed the error check, the following will not appear in the log!
// this is because the [errorcheck.ErrorCheck] function will call [log.Fatalf] and exit
// ---------------------------------------------------------------------------------
logger.Printf("Copied %s to %s\n", conffile, sysfile)
} }

View file

@ -13,9 +13,10 @@ import (
"time" "time"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck" "github.com/HikariKnight/ls-iommu/pkg/errorcheck"
"github.com/cavaliergopher/grab/v3"
"github.com/HikariKnight/quickpassthrough/pkg/fileio" "github.com/HikariKnight/quickpassthrough/pkg/fileio"
"github.com/HikariKnight/quickpassthrough/pkg/untar" "github.com/HikariKnight/quickpassthrough/pkg/untar"
"github.com/cavaliergopher/grab/v3"
) )
// Generated from github API response using https://mholt.github.io/json-to-go/ // Generated from github API response using https://mholt.github.io/json-to-go/
@ -111,7 +112,7 @@ func CheckLsIOMMU() {
// Make the directory for ls-iommu if it does not exist // Make the directory for ls-iommu if it does not exist
path := "utils" path := "utils"
if !fileio.FileExist(path) { if exists, _ := fileio.FileExist(path); !exists {
err := os.Mkdir(path, os.ModePerm) err := os.Mkdir(path, os.ModePerm)
errorcheck.ErrorCheck(err) errorcheck.ErrorCheck(err)
} }
@ -139,7 +140,7 @@ func CheckLsIOMMU() {
errorcheck.ErrorCheck(err) errorcheck.ErrorCheck(err)
// Check if the tar.gz exists // Check if the tar.gz exists
if !fileio.FileExist(fileName) { if exists, _ := fileio.FileExist(fileName); !exists {
downloadNewVersion(path, fileName, downloadUrl) downloadNewVersion(path, fileName, downloadUrl)
if checkSum(string(checksums_txt), fileName) { if checkSum(string(checksums_txt), fileName) {
err = untar.Untar(fmt.Sprintf("%s/", path), fileName) err = untar.Untar(fmt.Sprintf("%s/", path), fileName)
@ -182,7 +183,7 @@ func downloadNewVersion(path, fileName, downloadUrl string) {
// check for errors // check for errors
if err := download.Err(); err != nil { if err := download.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Download failed: %v\n", err) fmt.Fprintf(os.Stderr, "Download failed: %v\n", err)
if !fileio.FileExist("utils/ls-iommu") { if exists, _ := fileio.FileExist("utils/ls-iommu"); !exists {
log.Fatal("If the above error is 404, then we could not communicate with the GitHub API\n Please manually download and extract ls-iommu to: utils/\nYou can download it from: https://github.com/HikariKnight/ls-iommu/releases") log.Fatal("If the above error is 404, then we could not communicate with the GitHub API\n Please manually download and extract ls-iommu to: utils/\nYou can download it from: https://github.com/HikariKnight/ls-iommu/releases")
} else { } else {
fmt.Println("Existing ls-iommu binary detected in \"utils/\", will use that instead as the GitHub API did not respond.") fmt.Println("Existing ls-iommu binary detected in \"utils/\", will use that instead as the GitHub API did not respond.")

View file

@ -95,7 +95,7 @@ func viewGPU(config *configs.Config, ext ...int) {
config.Gpu_IDs = lsiommu.GetIOMMU("-g", mode, "-i", config.Gpu_Group, "--id") config.Gpu_IDs = lsiommu.GetIOMMU("-g", mode, "-i", config.Gpu_Group, "--id")
// If the kernel_args file already exists // If the kernel_args file already exists
if fileio.FileExist(config.Path.CMDLINE) { if exists, _ := fileio.FileExist(config.Path.CMDLINE); exists {
// Delete it as we will have to make a new one anyway // Delete it as we will have to make a new one anyway
err := os.Remove(config.Path.CMDLINE) err := os.Remove(config.Path.CMDLINE)
errorcheck.ErrorCheck(err, fmt.Sprintf("Could not remove %s", config.Path.CMDLINE)) errorcheck.ErrorCheck(err, fmt.Sprintf("Could not remove %s", config.Path.CMDLINE))
@ -115,7 +115,7 @@ func viewGPU(config *configs.Config, ext ...int) {
) )
// If the kernel_args file already exists // If the kernel_args file already exists
if fileio.FileExist(config.Path.CMDLINE) { if exists, _ := fileio.FileExist(config.Path.CMDLINE); exists {
// Delete it as we will have to make a new one anyway // Delete it as we will have to make a new one anyway
err := os.Remove(config.Path.CMDLINE) err := os.Remove(config.Path.CMDLINE)
errorcheck.ErrorCheck(err, fmt.Sprintf("Could not remove %s", config.Path.CMDLINE)) errorcheck.ErrorCheck(err, fmt.Sprintf("Could not remove %s", config.Path.CMDLINE))

View file

@ -5,9 +5,7 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"os/exec"
"os/user" "os/user"
"strings"
"syscall" "syscall"
"github.com/gookit/color" "github.com/gookit/color"
@ -23,19 +21,19 @@ import (
func prepModules(config *configs.Config) { func prepModules(config *configs.Config) {
// If we have files for modprobe // If we have files for modprobe
if fileio.FileExist(config.Path.MODPROBE) { if exists, _ := fileio.FileExist(config.Path.MODPROBE); exists {
// Configure modprobe // Configure modprobe
configs.Set_Modprobe(config.Gpu_IDs) configs.Set_Modprobe(config.Gpu_IDs)
} }
// If we have a folder for dracut // If we have a folder for dracut
if fileio.FileExist(config.Path.DRACUT) { if exists, _ := fileio.FileExist(config.Path.DRACUT); exists {
// Configure dracut // Configure dracut
configs.Set_Dracut() configs.Set_Dracut()
} }
// If we have a mkinitcpio.conf file // If we have a mkinitcpio.conf file
if fileio.FileExist(config.Path.MKINITCPIO) { if exists, _ := fileio.FileExist(config.Path.MKINITCPIO); exists {
configs.Set_Mkinitcpio() configs.Set_Mkinitcpio()
} }
@ -95,11 +93,9 @@ func finalize(config *configs.Config) {
title := color.New(color.BgHiBlue, color.White, color.Bold) title := color.New(color.BgHiBlue, color.White, color.Bold)
title.Println("Finalizing configuration") title.Println("Finalizing configuration")
isRoot := os.Getuid() == 0 config.IsRoot = os.Getuid() == 0
config.IsRoot = isRoot finalizeNotice(config.IsRoot)
finalizeNotice(isRoot)
// Make a choice of going next or back and parse the choice // Make a choice of going next or back and parse the choice
switch menu.Next("Press Next to continue with sudo using STDIN, ESC to exit or Back to go back.") { switch menu.Next("Press Next to continue with sudo using STDIN, ESC to exit or Back to go back.") {
@ -144,22 +140,24 @@ func installPassthrough(config *configs.Config) {
logger.Printf("Configuring systemd-boot using kernelstub\n") logger.Printf("Configuring systemd-boot using kernelstub\n")
// Configure kernelstub // Configure kernelstub
output = configs.Set_KernelStub() // callee logs the output and checks for errors
fmt.Printf("%s\n", output) configs.Set_KernelStub(config.IsRoot)
} else if config.Bootloader == "grubby" { } else if config.Bootloader == "grubby" {
// Write to logger // Write to logger
logger.Printf("Configuring bootloader using grubby\n") logger.Printf("Configuring bootloader using grubby\n")
// Configure kernelstub // Configure kernelstub
output = configs.Set_Grubby() output = configs.Set_Grubby(config.IsRoot)
fmt.Printf("%s\n", output) fmt.Printf("%s\n", output)
} else if config.Bootloader == "grub2" { } else if config.Bootloader == "grub2" {
// Write to logger // Write to logger
logger.Printf("Applying grub2 changes\n") logger.Printf("Applying grub2 changes\n")
grub_output, _ := configs.Set_Grub2() _ = configs.Set_Grub2(config.IsRoot) // note: we set config.IsRoot earlier
fmt.Printf("%s\n", strings.Join(grub_output, "\n"))
// we'll print the output in the [configs.Set_Grub2] method
// fmt.Printf("%s\n", strings.Join(grub_output, "\n"))
} else { } else {
kernel_args := fileio.ReadFile(config.Path.CMDLINE) kernel_args := fileio.ReadFile(config.Path.CMDLINE)
@ -169,62 +167,62 @@ func installPassthrough(config *configs.Config) {
// A lot of linux systems support modprobe along with their own module system // A lot of linux systems support modprobe along with their own module system
// So copy the modprobe files if we have them // So copy the modprobe files if we have them
modprobeFile := fmt.Sprintf("%s/vfio.conf", config.Path.MODPROBE) modprobeFile := fmt.Sprintf("%s/vfio.conf", config.Path.MODPROBE)
if fileio.FileExist(modprobeFile) {
// Copy initramfs-tools module to system
output = configs.CopyToSystem(modprobeFile, "/etc/modprobe.d/vfio.conf")
fmt.Printf("%s\n", output)
}
execAndLogSudo := func(cmd string) { // lets hope by now we've already handled any permissions issues...
if !config.IsRoot && !strings.HasPrefix(cmd, "sudo") { // TODO: verify that we actually can drop the errors on [fileio.FileExist] call below
cmd = fmt.Sprintf("sudo %s", cmd)
}
// Write to logger
logger.Printf("Executing: %s\n", cmd)
// Update initramfs if exists, _ := fileio.FileExist(modprobeFile); exists {
fmt.Printf("Executing: %s\nSee debug.log for detailed output\n", cmd) // Copy initramfs-tools module to system, note that CopyToSystem will log the command and output
cs := strings.Fields(cmd) // as well as check for errors
r := exec.Command(cs[0], cs[1:]...) configs.CopyToSystem(config.IsRoot, modprobeFile, "/etc/modprobe.d/vfio.conf")
cmd_out, _ := r.CombinedOutput()
// Write to logger
logger.Printf(string(cmd_out) + "\n")
} }
// Copy the config files for the system we have // Copy the config files for the system we have
initramfsFile := fmt.Sprintf("%s/modules", config.Path.INITRAMFS) initramfsFile := fmt.Sprintf("%s/modules", config.Path.INITRAMFS)
dracutFile := fmt.Sprintf("%s/vfio.conf", config.Path.DRACUT) dracutFile := fmt.Sprintf("%s/vfio.conf", config.Path.DRACUT)
initramFsExists, initramFsErr := fileio.FileExist(initramfsFile)
dracutExists, dracutErr := fileio.FileExist(dracutFile)
mkinitcpioExists, mkinitcpioErr := fileio.FileExist(config.Path.MKINITCPIO)
for _, err = range []error{initramFsErr, dracutErr, mkinitcpioErr} {
if err == nil {
continue
}
// we know this error isn't ErrNotExist, so we should throw it and exit
log.Fatalf("Failed to stat file: %s", err)
}
switch { switch {
case fileio.FileExist(initramfsFile): case initramFsExists:
// Copy initramfs-tools module to system // Copy initramfs-tools module to system
output = configs.CopyToSystem(initramfsFile, "/etc/initramfs-tools/modules") configs.CopyToSystem(config.IsRoot, initramfsFile, "/etc/initramfs-tools/modules")
fmt.Printf("%s\n", output)
// Copy the modules file to /etc/modules // Copy the modules file to /etc/modules
output = configs.CopyToSystem(config.Path.ETCMODULES, "/etc/modules") configs.CopyToSystem(config.IsRoot, config.Path.ETCMODULES, "/etc/modules")
fmt.Printf("%s\n", output)
execAndLogSudo("update-initramfs -u") if err = command.ExecAndLogSudo(config.IsRoot, true, "update-initramfs -u"); err != nil {
log.Fatalf("Failed to update initramfs: %s", err)
}
case fileio.FileExist(dracutFile): case dracutExists:
// Copy dracut config to /etc/dracut.conf.d/vfio // Copy dracut config to /etc/dracut.conf.d/vfio
output = configs.CopyToSystem(dracutFile, "/etc/dracut.conf.d/vfio") configs.CopyToSystem(config.IsRoot, dracutFile, "/etc/dracut.conf.d/vfio")
fmt.Printf("%s\n", output)
// Get systeminfo // Get systeminfo
sysinfo := uname.New() sysinfo := uname.New()
execAndLogSudo(fmt.Sprintf("dracut -f -v --kver %s\n", sysinfo.Release)) if err = command.ExecAndLogSudo(config.IsRoot, true, "dracut -f -v --kver "+sysinfo.Release); err != nil {
log.Fatalf("Failed to update initramfs: %s", err)
}
case fileio.FileExist(config.Path.MKINITCPIO): case mkinitcpioExists:
// Copy dracut config to /etc/dracut.conf.d/vfio // Copy dracut config to /etc/dracut.conf.d/vfio
output = configs.CopyToSystem(config.Path.MKINITCPIO, "/etc/mkinitcpio.conf") configs.CopyToSystem(config.IsRoot, config.Path.MKINITCPIO, "/etc/mkinitcpio.conf")
fmt.Printf("%s\n", output)
execAndLogSudo("mkinitcpio -P")
if err = command.ExecAndLogSudo(config.IsRoot, true, "mkinitcpio -P"); err != nil {
log.Fatalf("Failed to update initramfs: %s", err)
}
} }
// Make sure prompt end up on next line // Make sure prompt end up on next line

View file

@ -3,12 +3,16 @@ package command
import ( import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"fmt"
"io" "io"
"os" "os"
"os/exec" "os/exec"
"strings"
"time" "time"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck" "github.com/HikariKnight/ls-iommu/pkg/errorcheck"
"github.com/HikariKnight/quickpassthrough/internal/logger"
) )
// Run a command and return STDOUT // Run a command and return STDOUT
@ -27,7 +31,7 @@ func Run(binary string, args ...string) ([]string, error) {
output, _ := io.ReadAll(&stdout) output, _ := io.ReadAll(&stdout)
// Get the output // Get the output
outputs := []string{} outputs := make([]string, 0, 1)
outputs = append(outputs, string(output)) outputs = append(outputs, string(output))
// Return our list of items // Return our list of items
@ -59,6 +63,16 @@ func RunErr(binary string, args ...string) ([]string, []string, error) {
return outputs, outerrs, err return outputs, outerrs, err
} }
func RunErrSudo(isRoot bool, binary string, args ...string) ([]string, []string, error) {
if isRoot && binary != "sudo" {
args = append([]string{binary}, args...)
binary = "sudo"
}
logger.Printf("Executing (elevated): %s %s\n", binary, strings.Join(args, " "))
fmt.Printf("Executing (elevated): %s %s\n", binary, strings.Join(args, " "))
return RunErr(binary, args...)
}
// Elevate elevates this functions runs the command "sudo -Sk -- echo", // Elevate elevates this functions runs the command "sudo -Sk -- echo",
// this forces sudo to re-authenticate and lets us enter the password to STDIN // this forces sudo to re-authenticate and lets us enter the password to STDIN
// giving us the ability to run sudo commands // giving us the ability to run sudo commands
@ -74,7 +88,8 @@ func Elevate(password string) {
errorcheck.ErrorCheck(err, "\nFailed to get sudo STDIN") errorcheck.ErrorCheck(err, "\nFailed to get sudo STDIN")
// Start the authentication // Start the authentication
cmd.Start() err = cmd.Start()
errorcheck.ErrorCheck(err, "\nFailed to start sudo command")
// Get the passed password // Get the passed password
pw, _ := base64.StdEncoding.DecodeString(password) pw, _ := base64.StdEncoding.DecodeString(password)
@ -84,16 +99,55 @@ func Elevate(password string) {
pw = nil pw = nil
password = "" password = ""
stdin.Close() _ = stdin.Close()
// Wait for the sudo prompt (If the correct password was given, it will not stay behind) // Wait for the sudo prompt (If the correct password was given, it will not stay behind)
err = cmd.Wait() err = cmd.Wait()
errorcheck.ErrorCheck(err, "\nError, password given was wrong") errorcheck.ErrorCheck(err, "\nError, password given was wrong")
} }
// Function to just clear the terminal // Clear clears the terminal.
func Clear() { func Clear() {
c := exec.Command("clear") c := exec.Command("clear")
c.Stdout = os.Stdout c.Stdout = os.Stdout
c.Run() _ = c.Run()
}
// ExecAndLogSudo executes an elevated command and logs the output.
//
// * if we're root, the command is executed directly
// * if we're not root, the command is prefixed with "sudo"
//
// - noisy determines if we should print the command to the user
// noisy isn't set to true by our copy caller, as it logs differently,
// but other callers set it.
func ExecAndLogSudo(isRoot, noisy bool, cmd string) error {
if isRoot && !strings.HasPrefix(cmd, "sudo") {
cmd = fmt.Sprintf("sudo %s", cmd)
}
// Write to logger
logger.Printf("Executing (elevated): %s\n", cmd)
if noisy {
// Print to the user
fmt.Printf("Executing (elevated): %s\nSee debug.log for detailed output\n", cmd)
}
cs := strings.Fields(cmd)
r := exec.Command(cs[0], cs[1:]...)
cmdCombinedOut, err := r.CombinedOutput()
outStr := string(cmdCombinedOut)
// Write to logger, tabulate output
// tabulation denotes it's hierarchy as a child of the command
outStr = strings.ReplaceAll(outStr, "\n", "\n\t")
logger.Printf("\t" + string(cmdCombinedOut) + "\n")
if noisy {
// Print to the user
fmt.Printf("%s\n", outStr)
}
return err
} }

View file

@ -8,16 +8,20 @@ import (
"os" "os"
"github.com/HikariKnight/ls-iommu/pkg/errorcheck" "github.com/HikariKnight/ls-iommu/pkg/errorcheck"
"github.com/HikariKnight/quickpassthrough/internal/common"
) )
/* /*
* This just implements repetetive tasks I have to do with files * This just implements repetetive tasks I have to do with files
*/ */
// Creates a file and appends the content to the file (ending newline must be supplied with content string) // AppendContent creates a file and appends the content to the file.
// (ending newline must be supplied with content string)
func AppendContent(content string, fileName string) { func AppendContent(content string, fileName string) {
// Open the file // Open the file
f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm) f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm)
errorcheck.ErrorCheck(err, fmt.Sprintf("Error opening \"%s\" for writing", fileName)) errorcheck.ErrorCheck(err, fmt.Sprintf("Error opening \"%s\" for writing", fileName))
defer f.Close() defer f.Close()
@ -26,7 +30,7 @@ func AppendContent(content string, fileName string) {
errorcheck.ErrorCheck(err, fmt.Sprintf("Error writing to %s", fileName)) errorcheck.ErrorCheck(err, fmt.Sprintf("Error writing to %s", fileName))
} }
// Reads the file and returns a stringlist with each line // ReadLines reads the file and returns a stringlist with each line.
func ReadLines(fileName string) []string { func ReadLines(fileName string) []string {
content, err := os.Open(fileName) content, err := os.Open(fileName)
errorcheck.ErrorCheck(err, fmt.Sprintf("Error reading file %s", fileName)) errorcheck.ErrorCheck(err, fmt.Sprintf("Error reading file %s", fileName))
@ -46,10 +50,14 @@ func ReadLines(fileName string) []string {
} }
// Reads a file and returns all the content as a string // ReadFile reads a file and returns all the content as a string.
func ReadFile(fileName string) string { func ReadFile(fileName string) string {
// Read the whole file // Read the whole file
content, err := os.ReadFile(fileName) content, err := os.ReadFile(fileName)
if errors.Is(err, os.ErrPermission) {
errorcheck.ErrorCheck(err, common.PermissionNotice)
return "" // note: unreachable due to ErrorCheck calling fatal
}
errorcheck.ErrorCheck(err, fmt.Sprintf("Failed to ReadFile on %s", fileName)) errorcheck.ErrorCheck(err, fmt.Sprintf("Failed to ReadFile on %s", fileName))
// Return all the lines as one string // Return all the lines as one string
@ -57,24 +65,25 @@ func ReadFile(fileName string) string {
} }
// Checks if a file exists and returns a bool // FileExist checks if a file exists and returns a bool and any error that isn't os.ErrNotExist.
func FileExist(fileName string) bool { func FileExist(fileName string) (bool, error) {
var exist bool var exist bool
// Check if the file exists // Check if the file exists
if _, err := os.Stat(fileName); !errors.Is(err, os.ErrNotExist) { _, err := os.Stat(fileName)
// Set the value to true switch {
case err == nil:
exist = true exist = true
} else { case errors.Is(err, os.ErrNotExist):
// Set the value to false // Set the value to true
exist = false exist = false
err = nil
} }
// Return if the file exists // Return if the file exists
return exist return exist, err
} }
// Copies a FILE from source to dest // FileCopy copies a FILE from source to dest.
func FileCopy(sourceFile, destFile string) { func FileCopy(sourceFile, destFile string) {
// Get the file info // Get the file info
filestat, err := os.Stat(sourceFile) filestat, err := os.Stat(sourceFile)