diff --git a/.gitignore b/.gitignore index 1313c71..f0eea18 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,4 @@ bin/ dist/ main quickpassthrough -debug.log -quickpassthrough_debug.log +debug.log \ No newline at end of file diff --git a/README.md b/README.md index e75085d..78b893e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A project to simplify setting up GPU passthrough on your Linux host for libvirt/ You can use it by simply downloading the latest [release](https://github.com/HikariKnight/quickpassthrough/releases/) and run it inside a terminal or by downloading and compiling it yourself with the commands below. -This project is aimed at **systems with 2 GPUs** and **headless servers**, where the only GPU is not needed. +This project is aimed at **desktops with 2 GPUs** and **headless servers**, where the only GPU is not needed. **Note:** Quickpassthrough is not designed to be installed by a package manager! As you would usually have to only run it once, unless you change the GPU. @@ -47,18 +47,16 @@ go get -u ./cmd CGO_ENABLED=0 go build -ldflags="-X github.com/HikariKnight/quickpassthrough/internal/version.Version=$(git rev-parse --short HEAD)" -o quickpassthrough cmd/main.go ``` -## Does this work on atomic or immutable systems? -Currently no, however [Bazzite](https://bazzite.gg), [Bluefin](https://projectbluefin.io) and [Aurora](https://getaurora.dev) has an `ujust` command that does a very similar job. - -In Bazzite you run `ujust setup-virtualization` and follow the prompts to `Enable Virtualization` and `Enable VFIO drivers`.
-In Bluefin and Aurora you run `ujust setup-vfio` and follow the prompts. +## Does this work on immutable systems? +Currently no, however [Bazzite](https://bazzite.gg) has an `ujust` command that does a very similar job. +In Bazzite you run `ujust setup-virtualization` and follow the prompts to `Enable Virtualization` and `Enable VFIO drivers`. ## How do I undo the changes? There is a `backup/` folder generated on the first run that will have a copy of all your files (and their paths) from before we edited anything. Compare that folder with the `config/` folder to see which files you need to delete in addition to copying the files from `backup/` to your system before rebuilding your initramfs and updating your bootloader config. ## How do I just disable vfio for 1 boot? -Remove the vfio kernel arguments from your bootloader by pressing E on the boot menu. The kernel arguments added to the bootloader can be found in the `config/kernel_args` file.
+Remove the vfio kernel arguments from your bootloader by pressing E on the boot menu. The kernel arguments added to the bootloader can be found in the config/kernel_args file.
NOTE: You can also just remove them from your bootloader permanently and update your bootloader if you want to keep the config files on your system. ## What this project does NOT do @@ -66,6 +64,7 @@ NOTE: You can also just remove them from your bootloader permanently and update * Optimize your Virtual Machine for Passthrough (again this is your job) * Optimize your host machine for Passthrough or Virtualization (out of this projects scope) * Setup and configure GPU Passthrough on systems with 1 graphic card (iGPU counts as 1 Graphic Card by itself, so iGPU with another GPU will work) +* Does not configure passthrough of 3D controllers, [as it will not work](https://lantian.pub/en/article/modify-computer/laptop-intel-nvidia-optimus-passthrough.lantian/) (this is most gaming laptops so do not even think about it). If you try run this on a laptop with a 3D controller, the "2nd GPU" will not show up. ## Contributing diff --git a/internal/common/errors.go b/internal/common/errors.go deleted file mode 100644 index 0c83407..0000000 --- a/internal/common/errors.go +++ /dev/null @@ -1,50 +0,0 @@ -package common - -import ( - "errors" - "os" - "time" - - "github.com/HikariKnight/ls-iommu/pkg/errorcheck" - "github.com/gookit/color" -) - -const PermissionNotice = ` -Permissions error occured during file operations. - -Hint: - - If you initially ran QuickPassthrough as root or using sudo, - but are now running it as a normal user, this is expected behavior. - - Try running QuickPassthrough as root or using sudo if so. - - If this does not work, double check your filesystem's permissions, - and be sure to check the debug log for more information. -` - -// ErrorCheck serves as a wrapper for HikariKnight/ls-iommu/pkg/common.ErrorCheck that allows for visibile error messages -func ErrorCheck(err error, msg ...string) { - _, _ = os.Stdout.WriteString("\033[H\033[2J") // clear the screen - if err == nil { - return - } - if errors.Is(err, os.ErrPermission) { - color.Printf(PermissionNotice) - } - oneMsg := "" - if len(msg) < 1 { - oneMsg = "" - } else { - for _, v := range msg { - oneMsg += v + "\n" - } - } - color.Printf("\nFATAL: %s\n%s\nAborting", err.Error(), oneMsg) - for i := 0; i < 10; i++ { - time.Sleep(1 * time.Second) - print(".") - } - print("\n") - errorcheck.ErrorCheck(err, msg...) -} diff --git a/internal/configs/config_bootloaders.go b/internal/configs/config_bootloaders.go index 281cc4d..2f8607d 100644 --- a/internal/configs/config_bootloaders.go +++ b/internal/configs/config_bootloaders.go @@ -1,19 +1,16 @@ package configs import ( - "errors" "fmt" "os" - "os/exec" "regexp" "strings" - "github.com/klauspost/cpuid/v2" - - "github.com/HikariKnight/quickpassthrough/internal/common" + "github.com/HikariKnight/ls-iommu/pkg/errorcheck" "github.com/HikariKnight/quickpassthrough/internal/logger" "github.com/HikariKnight/quickpassthrough/pkg/command" "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 @@ -73,32 +70,39 @@ func Set_Cmdline(gpu_IDs []string) { fileio.AppendContent(fmt.Sprintf(" vfio_pci.ids=%s", strings.Join(gpu_IDs, ",")), config.Path.CMDLINE) } -// Set_KernelStub configures systemd-boot using kernelstub. -func Set_KernelStub(isRoot bool) { +// Configures systemd-boot using kernelstub +func Set_KernelStub() string { // Get the config config := GetConfig() // Get the kernel args kernel_args := fileio.ReadFile(config.Path.CMDLINE) - // Run and log, check for errors - common.ErrorCheck( - command.ExecAndLogSudo(isRoot, true, "kernelstub", "-a", kernel_args), - "Error, kernelstub command returned exit code 1", - ) + // Write to logger + logger.Printf("Running command:\nsudo kernelstub -a \"%s\"\n", kernel_args) + + // Run the command + _, err := command.Run("sudo", "kernelstub", "-a", kernel_args) + errorcheck.ErrorCheck(err, "Error, kernelstub command returned exit code 1") + + // Return what we did + return fmt.Sprintf("Executed: sudo kernelstub -a \"%s\"", kernel_args) } -// Set_Grubby configures grub2 and/or systemd-boot using grubby -func Set_Grubby(isRoot bool) string { +// Configures grub2 and/or systemd-boot using grubby +func Set_Grubby() string { // Get the config config := GetConfig() // Get the kernel args kernel_args := fileio.ReadFile(config.Path.CMDLINE) - // Run and log, check for errors - err := command.ExecAndLogSudo(isRoot, true, "grubby", "--update-kernel=ALL", fmt.Sprintf("--args=%s", kernel_args)) - common.ErrorCheck(err, "Error, grubby command returned exit code 1") + // Write to logger + logger.Printf("Running command:\nsudo grubby --update-kernel=ALL --args=\"%s\"\n", 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") // Return what we did return fmt.Sprintf("Executed: sudo grubby --update-kernel=ALL --args=\"%s\"", kernel_args) @@ -112,8 +116,8 @@ func Configure_Grub2() { conffile := fmt.Sprintf("%s/grub", config.Path.DEFAULT) // Make sure we start from scratch by deleting any old file - if exists, _ := fileio.FileExist(conffile); exists { - _ = os.Remove(conffile) + if fileio.FileExist(conffile) { + os.Remove(conffile) } // Make a regex to get the system path instead of the config path @@ -197,8 +201,8 @@ func clean_Grub2_Args(old_kernel_args []string) []string { return clean_kernel_args } -// Set_Grub2 copies our config to /etc/default/grub and updates grub -func Set_Grub2(isRoot bool) error { +// This function copies our config to /etc/default/grub and updates grub +func Set_Grub2() ([]string, error) { // Get the config config := GetConfig() @@ -209,45 +213,38 @@ func Set_Grub2(isRoot bool) error { sysfile_re := regexp.MustCompile(`^config`) sysfile := sysfile_re.ReplaceAllString(conffile, "") - // [CopyToSystem] will log the operation - // logger.Printf("Executing command:\nsudo cp -v \"%s\" %s\n", conffile, sysfile) + // Write to logger + logger.Printf("Executing command:\nsudo cp -v \"%s\" %s\n", conffile, sysfile) - // Copy files to system, logging and error checking is done in the function - CopyToSystem(isRoot, conffile, sysfile) + // Make our output slice + var output []string + + // Copy files to system + output = append(output, CopyToSystem(conffile, sysfile)) // Set a variable for the mkconfig command - var mkconfig string - var grubPath = "/boot/grub/grub.cfg" - var lpErr error - + mkconfig := "grub-mkconfig" // Check for grub-mkconfig - mkconfig, lpErr = exec.LookPath("grub-mkconfig") - switch { - case errors.Is(lpErr, exec.ErrNotFound) || mkconfig == "": - // Check for grub2-mkconfig - mkconfig, lpErr = exec.LookPath("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 [common.ErrorCheck]. - lpErr = errors.New("neither grub-mkconfig or grub2-mkconfig found") - } - common.ErrorCheck(lpErr, lpErr.Error()+"\n") - return lpErr // note: unreachable as [common.ErrorCheck] calls fatal - default: + _, err := command.Run("which", "grub-mkconfig") + if err == nil { + // Set binary as grub-mkconfig + mkconfig = "grub-mkconfig" + } else { + mkconfig = "grub2-mkconfig" } - _, mklog, err := command.RunErrSudo(isRoot, mkconfig, "-o", grubPath) + // Update grub.cfg + 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") + } - // tabulate the output, [command.RunErrSudo] logged the execution. - logger.Printf("\t" + strings.Join(mklog, "\n\t")) - common.ErrorCheck(err, "Failed to update /boot/grub/grub.cfg") - - // always returns nil as [common.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 + return output, err } diff --git a/internal/configs/config_dracut.go b/internal/configs/config_dracut.go index bc0b503..f31a2ff 100644 --- a/internal/configs/config_dracut.go +++ b/internal/configs/config_dracut.go @@ -9,7 +9,7 @@ import ( "github.com/HikariKnight/quickpassthrough/pkg/fileio" ) -// Set_Dracut writes a dracut configuration file for `/etc/dracut.conf.d/`. +// This function writes a dracut configuration file for /etc/dracut.conf.d/ func Set_Dracut() { config := GetConfig() @@ -17,23 +17,23 @@ func Set_Dracut() { dracutConf := fmt.Sprintf("%s/vfio.conf", config.Path.DRACUT) // If the file already exists then delete it - if exists, _ := fileio.FileExist(dracutConf); exists { - _ = os.Remove(dracutConf) + if fileio.FileExist(dracutConf) { + os.Remove(dracutConf) } // Write to logger - logger.Printf("Writing to %s:\nforce_drivers+=\" %s \"\n", dracutConf, strings.Join(vfio_modules(), " ")) + logger.Printf("Writing to %s:\nadd_drivers+=\" %s \"\n", dracutConf, strings.Join(vfio_modules(), " ")) // Write the dracut config file - fileio.AppendContent(fmt.Sprintf("force_drivers+=\" %s \"\n", strings.Join(vfio_modules(), " ")), dracutConf) + fileio.AppendContent(fmt.Sprintf("add_drivers+=\" %s \"\n", strings.Join(vfio_modules(), " ")), dracutConf) // Get the current kernel arguments we have generated kernel_args := fileio.ReadFile(config.Path.CMDLINE) // If the kernel argument is not already in the file - if !strings.Contains(kernel_args, "rd.driver.pre=vfio-pci") { + if !strings.Contains(kernel_args, "rd.driver.pre=vfio_pci") { // Add to our kernel arguments file that vfio_pci should load early (dracut does this using kernel arguments) - fileio.AppendContent(" rd.driver.pre=vfio-pci", config.Path.CMDLINE) + fileio.AppendContent(" rd.driver.pre=vfio_pci", config.Path.CMDLINE) } // Make a backup of dracutConf if there is one there diff --git a/internal/configs/config_initramfstools.go b/internal/configs/config_initramfstools.go index d3a7bd5..f198959 100644 --- a/internal/configs/config_initramfstools.go +++ b/internal/configs/config_initramfstools.go @@ -7,7 +7,7 @@ import ( "regexp" "strings" - "github.com/HikariKnight/quickpassthrough/internal/common" + "github.com/HikariKnight/ls-iommu/pkg/errorcheck" "github.com/HikariKnight/quickpassthrough/pkg/fileio" ) @@ -15,7 +15,7 @@ import ( func initramfs_readHeader(lines int, fileName string) string { // Open the file f, err := os.Open(fileName) - common.ErrorCheck(err, fmt.Sprintf("Error opening %s", fileName)) + errorcheck.ErrorCheck(err, fmt.Sprintf("Error opening %s", fileName)) defer f.Close() header_re := regexp.MustCompile(`^#`) @@ -50,7 +50,7 @@ func initramfs_addModules(conffile string) { // Open the system file for reading sysfile, err := os.Open(syspath) - common.ErrorCheck(err, fmt.Sprintf("Error opening file for reading %s", syspath)) + errorcheck.ErrorCheck(err, fmt.Sprintf("Error opening file for reading %s", syspath)) defer sysfile.Close() // Check if user has vendor-reset installed/enabled and make sure that is first diff --git a/internal/configs/config_mkinitcpio.go b/internal/configs/config_mkinitcpio.go index 6c95f8f..219c16b 100644 --- a/internal/configs/config_mkinitcpio.go +++ b/internal/configs/config_mkinitcpio.go @@ -10,14 +10,14 @@ import ( "github.com/HikariKnight/quickpassthrough/pkg/fileio" ) -// Set_Mkinitcpio copies the content of /etc/mkinitcpio.conf to the config folder and does an inline replace/insert on the MODULES=() line +// This function copies the content of /etc/mkinitcpio.conf to the config folder and does an inline replace/insert on the MODULES=() line func Set_Mkinitcpio() { // Get the config struct config := GetConfig() // Make sure we start from scratch by deleting any old file - if exists, _ := fileio.FileExist(config.Path.MKINITCPIO); exists { - _ = os.Remove(config.Path.MKINITCPIO) + if fileio.FileExist(config.Path.MKINITCPIO) { + os.Remove(config.Path.MKINITCPIO) } // Make a regex to get the system path instead of the config path diff --git a/internal/configs/config_modprobe.go b/internal/configs/config_modprobe.go index 715a267..63a5dbc 100644 --- a/internal/configs/config_modprobe.go +++ b/internal/configs/config_modprobe.go @@ -30,9 +30,9 @@ func Set_Modprobe(gpu_IDs []string) { conffile := fmt.Sprintf("%s/vfio.conf", config.Path.MODPROBE) // If the file exists - if exists, _ := fileio.FileExist(conffile); exists { + if fileio.FileExist(conffile) { // Delete the old file - _ = os.Remove(conffile) + os.Remove(conffile) } content := fmt.Sprint( diff --git a/internal/configs/config_vbios_dumper.go b/internal/configs/config_vbios_dumper.go index 775cf7a..b075018 100644 --- a/internal/configs/config_vbios_dumper.go +++ b/internal/configs/config_vbios_dumper.go @@ -6,7 +6,7 @@ import ( "path/filepath" "strings" - "github.com/HikariKnight/quickpassthrough/internal/common" + "github.com/HikariKnight/ls-iommu/pkg/errorcheck" "github.com/HikariKnight/quickpassthrough/internal/logger" ) @@ -55,12 +55,12 @@ func GenerateVBIOSDumper(vbios_path string) { // Make the script file scriptfile, err := os.Create("utils/dump_vbios.sh") - common.ErrorCheck(err, "Cannot create file \"utils/dump_vbios.sh\"") + errorcheck.ErrorCheck(err, "Cannot create file \"utils/dump_vbios.sh\"") defer scriptfile.Close() // Make the script executable scriptfile.Chmod(0775) - common.ErrorCheck(err, "Could not change permissions of \"utils/dump_vbios.sh\"") + errorcheck.ErrorCheck(err, "Could not change permissions of \"utils/dump_vbios.sh\"") // Write to logger logger.Printf("Writing utils/dump_vbios.sh\n") diff --git a/internal/configs/config_vfio_video.go b/internal/configs/config_vfio_video.go index ae7dbeb..63230b1 100644 --- a/internal/configs/config_vfio_video.go +++ b/internal/configs/config_vfio_video.go @@ -5,7 +5,7 @@ import ( "os" "strings" - "github.com/HikariKnight/quickpassthrough/internal/common" + "github.com/HikariKnight/ls-iommu/pkg/errorcheck" "github.com/HikariKnight/quickpassthrough/internal/logger" "github.com/HikariKnight/quickpassthrough/pkg/fileio" ) @@ -26,7 +26,7 @@ func DisableVFIOVideo(i int) { if strings.Contains(kernel_args, "vfio_pci.disable_vga") { // Remove the old file err := os.Remove(config.Path.CMDLINE) - common.ErrorCheck(err, fmt.Sprintf("Could not rewrite %s", config.Path.CMDLINE)) + errorcheck.ErrorCheck(err, fmt.Sprintf("Could not rewrite %s", config.Path.CMDLINE)) // Enable or disable the VGA based on our given value if i == 0 { diff --git a/internal/configs/configs.go b/internal/configs/configs.go index c9b851b..50d6174 100644 --- a/internal/configs/configs.go +++ b/internal/configs/configs.go @@ -1,19 +1,16 @@ package configs import ( - "errors" "fmt" "os" - "path/filepath" "regexp" - "github.com/klauspost/cpuid/v2" - - "github.com/HikariKnight/quickpassthrough/internal/common" + "github.com/HikariKnight/ls-iommu/pkg/errorcheck" "github.com/HikariKnight/quickpassthrough/internal/logger" "github.com/HikariKnight/quickpassthrough/pkg/command" "github.com/HikariKnight/quickpassthrough/pkg/fileio" "github.com/HikariKnight/quickpassthrough/pkg/uname" + "github.com/klauspost/cpuid/v2" ) type Path struct { @@ -33,10 +30,9 @@ type Config struct { Path *Path Gpu_Group string Gpu_IDs []string - IsRoot bool } -// GetConfigPaths retrieves the path to all the config files. +// Gets the path to all the config files func GetConfigPaths() *Path { Paths := &Path{ CMDLINE: "config/kernel_args", @@ -52,7 +48,7 @@ func GetConfigPaths() *Path { return Paths } -// GetConfig retrieves all the configs and returns the struct. +// Gets all the configs and returns the struct func GetConfig() *Config { config := &Config{ Bootloader: "unknown", @@ -68,7 +64,7 @@ func GetConfig() *Config { return config } -// InitConfigs constructs the empty config files and folders based on what exists on the system +// Constructs the empty config files and folders based on what exists on the system func InitConfigs() { config := GetConfig() @@ -81,17 +77,10 @@ func InitConfigs() { } // Remove old config - if err := os.RemoveAll("config"); err != nil && !errors.Is(err, os.ErrNotExist) { - - // won't be called if the error is ErrNotExist - common.ErrorCheck(err, "\nError removing old config") - } + os.RemoveAll("config") // Make the config folder - if err := os.Mkdir("config", os.ModePerm); err != nil && !errors.Is(err, os.ErrExist) { - // won't be called if the error is ErrExist - common.ErrorCheck(err, "\nError making config folder") - } + os.Mkdir("config", os.ModePerm) // Make a regex to get the system path instead of the config path syspath_re := regexp.MustCompile(`^config`) @@ -101,16 +90,8 @@ func InitConfigs() { // Get the system path syspath := syspath_re.ReplaceAllString(confpath, "") - exists, err := fileio.FileExist(syspath) - - // If we received an error that is not ErrNotExist - if err != nil { - common.ErrorCheck(err, "\nError checking for directory: "+syspath) - continue // note: unreachable due to ErrorCheck calling fatal - } - // If the path exists - if exists { + if fileio.FileExist(syspath) { // Write to log logger.Printf( "%s found on the system\n"+ @@ -123,10 +104,8 @@ func InitConfigs() { makeBackupDir(syspath) // Create the directories for our configs - if err = os.MkdirAll(confpath, os.ModePerm); err != nil && !errors.Is(err, os.ErrExist) { - common.ErrorCheck(err, "\nError making directory: "+confpath) - return // note: unreachable due to ErrorCheck calling fatal - } + err := os.MkdirAll(confpath, os.ModePerm) + errorcheck.ErrorCheck(err) } } @@ -149,15 +128,7 @@ func InitConfigs() { sysfile := syspath_re.ReplaceAllString(conffile, "") // If the file exists - exists, err := fileio.FileExist(sysfile) - - // If we received an error that is not ErrNotExist - if err != nil { - common.ErrorCheck(err, "\nError checking for file: "+sysfile) - continue // note: unreachable due to ErrorCheck calling fatal - } - - if exists { + if fileio.FileExist(sysfile) { // Write to log logger.Printf( "%s found on the system\n"+ @@ -168,22 +139,16 @@ func InitConfigs() { // Create the directories for our configs file, err := os.Create(conffile) - common.ErrorCheck(err) + errorcheck.ErrorCheck(err) // Close the file so we can edit it - _ = file.Close() + file.Close() // Backup the sysfile if we do not have a backup backupFile(sysfile) } - exists, err = fileio.FileExist(conffile) - if err != nil { - common.ErrorCheck(err, "\nError checking for file: "+conffile) - continue // note: unreachable - } - // If we now have a config that exists - if exists { + if fileio.FileExist(conffile) { switch conffile { case config.Path.ETCMODULES: // Write to logger @@ -221,7 +186,7 @@ func vfio_modules() []string { // If we are on a kernel older than 6.2 sysinfo := uname.New() - kernel_re := regexp.MustCompile(`^(6\.1|6\.0|[1-5]\.\d{1,2})\.`) + kernel_re := regexp.MustCompile(`^(6\.1|6\.0|[1-5]\.)`) if kernel_re.MatchString(sysinfo.Kernel) { // Write to the debug log logger.Printf("Linux kernel version %s detected!\nIncluding vfio_virqfd module\n", sysinfo.Kernel) @@ -239,28 +204,14 @@ func backupFile(source string) { // Make a destination path 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 { - common.ErrorCheck(configFileError, "\nError checking for file: "+source) - return // note: unreachable - } - } - - switch { // If the file exists in the config but not on the system it is a file we make - case configExists && !sysExists: + if fileio.FileExist(fmt.Sprintf("config%s", source)) && !fileio.FileExist(source) { // Create the blank file so that a copy of the backup folder to /etc file, err := os.Create(dest) - common.ErrorCheck(err, "Error creating file %s\n", dest) - _ = file.Close() - + errorcheck.ErrorCheck(err, "Error creating file %s\n", dest) + file.Close() + } else if !fileio.FileExist(dest) { // If a backup of the file does not exist - case sysExists && !destExists: // Write to the logger logger.Printf("No first time backup of %s detected.\nCreating a backup at %s\n", source, dest) @@ -272,64 +223,29 @@ func backupFile(source string) { func makeBackupDir(dest string) { // If a backup directory does not exist - exists, err := fileio.FileExist("backup/") - if err != nil { - // If we received an error that is not ErrNotExist - common.ErrorCheck(err, "Error checking for backup/ folder") - return // note: unreachable - } - - if !exists { + if !fileio.FileExist("backup/") { // Write to the logger logger.Printf("Backup directory does not exist!\nCreating backup directory for first run backup") } // Make the empty directories - if err = os.MkdirAll(fmt.Sprintf("backup/%s", dest), os.ModePerm); errors.Is(err, os.ErrExist) { - // ignore if the directory already exists - err = nil - } - // will return without incident if there's no error - common.ErrorCheck(err, "Error making backup/ folder") + err := os.MkdirAll(fmt.Sprintf("backup/%s", dest), os.ModePerm) + errorcheck.ErrorCheck(err, "Error making backup/ folder") } -// CopyToSystem copies a file to the system. -func CopyToSystem(isRoot bool, conffile, sysfile string) { +// Copy a file to the system, make sure you have run command.Elevate() recently +func CopyToSystem(conffile, sysfile string) string { // 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) + output, _ := command.Run("sudo", "cp", "-v", conffile, sysfile) - // ExecAndLogSudo will write to the logger, so just print here - fmt.Printf("Copying: %s to %s\n", conffile, sysfile) + // Clean the output + clean_re := regexp.MustCompile(`\n`) + clean_output := clean_re.ReplaceAllString(output[0], "") - if isRoot { - logger.Printf("Copying %s to %s\n", conffile, sysfile) - fmt.Printf("Copying %s to %s\n", conffile, sysfile) - fDat, err := os.ReadFile(conffile) - common.ErrorCheck(err, fmt.Sprintf("Failed to read %s", conffile)) - err = os.WriteFile(sysfile, fDat, 0644) - common.ErrorCheck(err, fmt.Sprintf("Failed to write %s", sysfile)) - logger.Printf("Copied %s to %s\n", conffile, sysfile) - return - } + // Write output to logger + logger.Printf("%s\n", clean_output) - if !filepath.IsAbs(conffile) { - conffile, _ = filepath.Abs(conffile) - } - - err := command.ExecAndLogSudo(isRoot, false, "cp", "-v", conffile, sysfile) - - errMsg := "" - if err != nil { - errMsg = err.Error() - } - - // [command.ExecAndLogSudo] will log the command's output - common.ErrorCheck(err, fmt.Sprintf("Failed to copy %s to %s:\n%s", conffile, sysfile, errMsg)) - - // --------------------------------------------------------------------------------- - // note that if we failed the error check, the following will not appear in the log! - // this is because the [common.ErrorCheck] function will call [log.Fatalf] and exit - // --------------------------------------------------------------------------------- - - logger.Printf("Copied %s to %s\n", conffile, sysfile) + // Return the output + return fmt.Sprintf("Copying: %s", clean_output) } diff --git a/internal/configs/configs_test.go b/internal/configs/configs_test.go deleted file mode 100644 index 8cc6290..0000000 --- a/internal/configs/configs_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package configs - -import ( - "os" - "path/filepath" - "testing" -) - -func TestCopyToSystem(t *testing.T) { - if err := os.Mkdir("testdir", 0755); err != nil { - t.Fatal(err) - } - tFilePath := filepath.Join("testdir", "testfile") - if err := os.WriteFile(tFilePath, []byte("test"), 0644); err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - if err := os.RemoveAll("testdir"); err != nil { - t.Fatal(err) - } - }) - isRoot := os.Getuid() == 0 - switch isRoot { - case true: - t.Run("TestCopyToSystem_AsRoot", func(t *testing.T) { - CopyToSystem(true, tFilePath, "/etc/testfile") - }) - default: - t.Run("TestCopyToSystem_AsUser", func(t *testing.T) { - CopyToSystem(false, tFilePath, "/etc/testfile") - }) - } -} diff --git a/internal/ls_iommu_downloader/ls_iommu_downloader.go b/internal/ls_iommu_downloader/ls_iommu_downloader.go index 0c26cfd..d8114f0 100644 --- a/internal/ls_iommu_downloader/ls_iommu_downloader.go +++ b/internal/ls_iommu_downloader/ls_iommu_downloader.go @@ -12,11 +12,10 @@ import ( "strings" "time" - "github.com/cavaliergopher/grab/v3" - - "github.com/HikariKnight/quickpassthrough/internal/common" + "github.com/HikariKnight/ls-iommu/pkg/errorcheck" "github.com/HikariKnight/quickpassthrough/pkg/fileio" "github.com/HikariKnight/quickpassthrough/pkg/untar" + "github.com/cavaliergopher/grab/v3" ) // Generated from github API response using https://mholt.github.io/json-to-go/ @@ -96,14 +95,14 @@ type Response struct { func CheckLsIOMMU() { // Check the API for releases resp, err := http.Get("https://api.github.com/repos/hikariknight/ls-iommu/releases/latest") - common.ErrorCheck(err) + errorcheck.ErrorCheck(err) // Close the response when function ends defer resp.Body.Close() // Get the response body body, err := io.ReadAll(resp.Body) - common.ErrorCheck(err) + errorcheck.ErrorCheck(err) var result Response if err := json.Unmarshal(body, &result); err != nil { @@ -112,9 +111,9 @@ func CheckLsIOMMU() { // Make the directory for ls-iommu if it does not exist path := "utils" - if exists, _ := fileio.FileExist(path); !exists { + if !fileio.FileExist(path) { err := os.Mkdir(path, os.ModePerm) - common.ErrorCheck(err) + errorcheck.ErrorCheck(err) } // Generate the download url @@ -134,30 +133,30 @@ func CheckLsIOMMU() { // Get the checksum data checksums, err := http.Get(checkSumsUrl) - common.ErrorCheck(err) + errorcheck.ErrorCheck(err) defer checksums.Body.Close() checksums_txt, err := io.ReadAll(checksums.Body) - common.ErrorCheck(err) + errorcheck.ErrorCheck(err) // Check if the tar.gz exists - if exists, _ := fileio.FileExist(fileName); !exists { + if !fileio.FileExist(fileName) { downloadNewVersion(path, fileName, downloadUrl) if checkSum(string(checksums_txt), fileName) { err = untar.Untar(fmt.Sprintf("%s/", path), fileName) - common.ErrorCheck(err) + errorcheck.ErrorCheck(err) } } else { if !checkSum(string(checksums_txt), fileName) { downloadNewVersion(path, fileName, downloadUrl) err = untar.Untar(fmt.Sprintf("%s/", path), fileName) - common.ErrorCheck(err) + errorcheck.ErrorCheck(err) } } } func checkSum(checksums string, fileName string) bool { r, err := os.Open(fileName) - common.ErrorCheck(err) + errorcheck.ErrorCheck(err) defer r.Close() hasher := sha256.New() @@ -183,7 +182,7 @@ func downloadNewVersion(path, fileName, downloadUrl string) { // check for errors if err := download.Err(); err != nil { fmt.Fprintf(os.Stderr, "Download failed: %v\n", err) - if exists, _ := fileio.FileExist("utils/ls-iommu"); !exists { + if !fileio.FileExist("utils/ls-iommu") { 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 { fmt.Println("Existing ls-iommu binary detected in \"utils/\", will use that instead as the GitHub API did not respond.") diff --git a/internal/pages/02_select_gpu.go b/internal/pages/02_select_gpu.go index a035f65..1b270c5 100644 --- a/internal/pages/02_select_gpu.go +++ b/internal/pages/02_select_gpu.go @@ -4,14 +4,13 @@ import ( "fmt" "os" - "github.com/gookit/color" - - "github.com/HikariKnight/quickpassthrough/internal/common" + "github.com/HikariKnight/ls-iommu/pkg/errorcheck" "github.com/HikariKnight/quickpassthrough/internal/configs" - "github.com/HikariKnight/quickpassthrough/internal/lsiommu" + lsiommu "github.com/HikariKnight/quickpassthrough/internal/lsiommu" "github.com/HikariKnight/quickpassthrough/pkg/command" "github.com/HikariKnight/quickpassthrough/pkg/fileio" "github.com/HikariKnight/quickpassthrough/pkg/menu" + "github.com/gookit/color" ) func SelectGPU(config *configs.Config) { @@ -95,10 +94,10 @@ func viewGPU(config *configs.Config, ext ...int) { config.Gpu_IDs = lsiommu.GetIOMMU("-g", mode, "-i", config.Gpu_Group, "--id") // If the kernel_args file already exists - if exists, _ := fileio.FileExist(config.Path.CMDLINE); exists { + if fileio.FileExist(config.Path.CMDLINE) { // Delete it as we will have to make a new one anyway err := os.Remove(config.Path.CMDLINE) - common.ErrorCheck(err, fmt.Sprintf("Could not remove %s", config.Path.CMDLINE)) + errorcheck.ErrorCheck(err, fmt.Sprintf("Could not remove %s", config.Path.CMDLINE)) } // Write initial kernel_arg file @@ -115,10 +114,10 @@ func viewGPU(config *configs.Config, ext ...int) { ) // If the kernel_args file already exists - if exists, _ := fileio.FileExist(config.Path.CMDLINE); exists { + if fileio.FileExist(config.Path.CMDLINE) { // Delete it as we will have to make a new one anyway err := os.Remove(config.Path.CMDLINE) - common.ErrorCheck(err, fmt.Sprintf("Could not remove %s", config.Path.CMDLINE)) + errorcheck.ErrorCheck(err, fmt.Sprintf("Could not remove %s", config.Path.CMDLINE)) } // Write initial kernel_arg file diff --git a/internal/pages/03_vbios_extract.go b/internal/pages/03_vbios_extract.go index 44dcc04..598b584 100644 --- a/internal/pages/03_vbios_extract.go +++ b/internal/pages/03_vbios_extract.go @@ -26,11 +26,9 @@ func genVBIOS_dumper(config *configs.Config) { scriptdir, _ = os.Getwd() } - // Search for a vbios path and generate the vbios dumping script if found - vbios_paths := lsiommu.GetIOMMU("-g", "-i", config.Gpu_Group, "--rom") - if len(vbios_paths) != 0 { - configs.GenerateVBIOSDumper(vbios_paths[0]) - } + // Get the vbios path and generate the vbios dumping script + vbios_path := lsiommu.GetIOMMU("-g", "-i", config.Gpu_Group, "--rom")[0] + configs.GenerateVBIOSDumper(vbios_path) // Make the qemu config folder os.Mkdir(fmt.Sprintf("%s/%s", scriptdir, config.Path.QEMU), os.ModePerm) @@ -53,7 +51,7 @@ func genVBIOS_dumper(config *configs.Config) { "rom to the VM along with the card in order to get a functional passthrough.\n", "In many cases you can find your vbios at https://www.techpowerup.com/vgabios/\n", "\n", - "If we found a romfile for your GPU you can also attempt to dump your own vbios from TTY using the script in\n", + "You can also attempt to dump your own vbios from TTY using the script in\n", fmt.Sprintf("%s/utils/dump_vbios.sh\n", scriptdir), "\n", ) diff --git a/internal/pages/05_select_usbctrl.go b/internal/pages/05_select_usbctrl.go index 2df9583..3264f7a 100644 --- a/internal/pages/05_select_usbctrl.go +++ b/internal/pages/05_select_usbctrl.go @@ -4,12 +4,11 @@ import ( "fmt" "os" - "github.com/gookit/color" - "github.com/HikariKnight/quickpassthrough/internal/configs" - "github.com/HikariKnight/quickpassthrough/internal/lsiommu" + lsiommu "github.com/HikariKnight/quickpassthrough/internal/lsiommu" "github.com/HikariKnight/quickpassthrough/pkg/command" "github.com/HikariKnight/quickpassthrough/pkg/menu" + "github.com/gookit/color" ) func selectUSB(config *configs.Config) { diff --git a/internal/pages/06_finalize.go b/internal/pages/06_finalize.go index a1ab656..232bd26 100644 --- a/internal/pages/06_finalize.go +++ b/internal/pages/06_finalize.go @@ -6,34 +6,34 @@ import ( "log" "os" "os/user" + "strings" "syscall" - "github.com/gookit/color" - "golang.org/x/term" - "github.com/HikariKnight/quickpassthrough/internal/configs" "github.com/HikariKnight/quickpassthrough/internal/logger" "github.com/HikariKnight/quickpassthrough/pkg/command" "github.com/HikariKnight/quickpassthrough/pkg/fileio" "github.com/HikariKnight/quickpassthrough/pkg/menu" "github.com/HikariKnight/quickpassthrough/pkg/uname" + "github.com/gookit/color" + "golang.org/x/term" ) func prepModules(config *configs.Config) { // If we have files for modprobe - if exists, _ := fileio.FileExist(config.Path.MODPROBE); exists { + if fileio.FileExist(config.Path.MODPROBE) { // Configure modprobe configs.Set_Modprobe(config.Gpu_IDs) } // If we have a folder for dracut - if exists, _ := fileio.FileExist(config.Path.DRACUT); exists { + if fileio.FileExist(config.Path.DRACUT) { // Configure dracut configs.Set_Dracut() } // If we have a mkinitcpio.conf file - if exists, _ := fileio.FileExist(config.Path.MKINITCPIO); exists { + if fileio.FileExist(config.Path.MKINITCPIO) { configs.Set_Mkinitcpio() } @@ -48,43 +48,6 @@ func prepModules(config *configs.Config) { finalize(config) } -func finalizeNotice(isRoot bool) { - color.Print(` -The configuration files have been generated and are located inside the "config" folder - - * The "kernel_args" file contains kernel arguments that your bootloader needs - * The "qemu" folder contains files that may be needed for passthrough - * The files inside the "etc" folder must be copied to your system. - - Verify that these files are correctly formated/edited! - -Once all files have been copied, the following steps must be taken: - - * bootloader configuration must be updated - * initramfs must be rebuilt - -`) - switch isRoot { - case true: - color.Print("This program can do this for you, if desired.\n") - default: - color.Print(`This program can do this for you, however your sudo password is required. -To avoid this: - - * press CTRL+C and perform the steps mentioned above manually. - OR - * run ` + os.Args[0] + ` as root. - -`) - } - - color.Print(` -If you want to go back and change something, choose Back. - -NOTE: A backup of the original files from the first run can be found in the backup folder -`) -} - func finalize(config *configs.Config) { // Clear the screen command.Clear() @@ -93,43 +56,60 @@ func finalize(config *configs.Config) { title := color.New(color.BgHiBlue, color.White, color.Bold) title.Println("Finalizing configuration") - config.IsRoot = os.Getuid() == 0 + color.Print( + "The configuration files have been generated and are\n", + "located inside the \"config\" folder\n", + "\n", + "* The \"kernel_args\" file contains kernel arguments that your bootloader needs\n", + "* The \"qemu\" folder contains files that might be\n neccessary for passing through the GPU\n", + "* The files inside the \"etc\" folder must be copied to your system.\n", + " NOTE: Verify that these files are correctly formated/edited!\n", + "* Once all files have been copied, you need to update your bootloader and rebuild\n", + " your initramfs using the tools to do so by your system.\n", + "\n", + "This program can do this for you, however the program will have to\n", + "type your password to sudo using STDIN, to avoid using STDIN press CTRL+C\n", + "and copy the files, update your bootloader and rebuild your initramfs manually.\n", + "If you want to go back and change something, choose Back\n", + "\nNOTE: A backup of the original files from the first run can be found in the backup folder\n", + ) - finalizeNotice(config.IsRoot) + // Make a choice of going next or back + choice := menu.Next("Press Next to continue with sudo using STDIN, ESC to exit or Back to go back.") - // 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.") { + // Parse the choice + switch choice { case "next": installPassthrough(config) + case "back": // Go back disableVideo(config) } + } func installPassthrough(config *configs.Config) { // Get the user data - currentUser, err := user.Current() + user, err := user.Current() if err != nil { log.Fatalf(err.Error()) } - if !config.IsRoot { - // Provide a password prompt - fmt.Printf("[sudo] password for %s: ", currentUser.Username) - bytep, err := term.ReadPassword(syscall.Stdin) - if err != nil { - os.Exit(1) - } - fmt.Print("\n") - - // Elevate with sudo - command.Elevate( - base64.StdEncoding.EncodeToString( - bytep, - ), - ) + // Provide a password prompt + fmt.Printf("[sudo] password for %s: ", user.Username) + bytep, err := term.ReadPassword(int(syscall.Stdin)) + if err != nil { + os.Exit(1) } + fmt.Print("\n") + + // Elevate with sudo + command.Elevate( + base64.StdEncoding.EncodeToString( + bytep, + ), + ) // Make an output string var output string @@ -140,24 +120,22 @@ func installPassthrough(config *configs.Config) { logger.Printf("Configuring systemd-boot using kernelstub\n") // Configure kernelstub - // callee logs the output and checks for errors - configs.Set_KernelStub(config.IsRoot) + output = configs.Set_KernelStub() + fmt.Printf("%s\n", output) } else if config.Bootloader == "grubby" { // Write to logger logger.Printf("Configuring bootloader using grubby\n") // Configure kernelstub - output = configs.Set_Grubby(config.IsRoot) + output = configs.Set_Grubby() fmt.Printf("%s\n", output) } else if config.Bootloader == "grub2" { // Write to logger logger.Printf("Applying grub2 changes\n") - _ = configs.Set_Grub2(config.IsRoot) // note: we set config.IsRoot earlier - - // we'll print the output in the [configs.Set_Grub2] method - // fmt.Printf("%s\n", strings.Join(grub_output, "\n")) + grub_output, _ := configs.Set_Grub2() + fmt.Printf("%s\n", strings.Join(grub_output, "\n")) } else { kernel_args := fileio.ReadFile(config.Path.CMDLINE) @@ -167,62 +145,68 @@ func installPassthrough(config *configs.Config) { // A lot of linux systems support modprobe along with their own module system // So copy the modprobe files if we have them modprobeFile := fmt.Sprintf("%s/vfio.conf", config.Path.MODPROBE) - - // lets hope by now we've already handled any permissions issues... - // TODO: verify that we actually can drop the errors on [fileio.FileExist] call below - - if exists, _ := fileio.FileExist(modprobeFile); exists { - // Copy initramfs-tools module to system, note that CopyToSystem will log the command and output - // as well as check for errors - configs.CopyToSystem(config.IsRoot, modprobeFile, "/etc/modprobe.d/vfio.conf") + if fileio.FileExist(modprobeFile) { + // Copy initramfs-tools module to system + output = configs.CopyToSystem(modprobeFile, "/etc/modprobe.d/vfio.conf") + fmt.Printf("%s\n", output) } // Copy the config files for the system we have initramfsFile := fmt.Sprintf("%s/modules", config.Path.INITRAMFS) 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 { - case initramFsExists: + if fileio.FileExist(initramfsFile) { // Copy initramfs-tools module to system - configs.CopyToSystem(config.IsRoot, initramfsFile, "/etc/initramfs-tools/modules") + output = configs.CopyToSystem(initramfsFile, "/etc/initramfs-tools/modules") + fmt.Printf("%s\n", output) // Copy the modules file to /etc/modules - configs.CopyToSystem(config.IsRoot, config.Path.ETCMODULES, "/etc/modules") + output = configs.CopyToSystem(config.Path.ETCMODULES, "/etc/modules") + fmt.Printf("%s\n", output) - if err = command.ExecAndLogSudo(config.IsRoot, true, "update-initramfs", "-u"); err != nil { - log.Fatalf("Failed to update initramfs: %s", err) - } + // Write to logger + logger.Printf("Executing: sudo update-initramfs -u\n") - case dracutExists: + // Update initramfs + fmt.Println("Executed: sudo update-initramfs -u\nSee debug.log for detailed output") + cmd_out, cmd_err, _ := command.RunErr("sudo", "update-initramfs", "-u") + + cmd_out = append(cmd_out, cmd_err...) + + // Write to logger + logger.Printf(strings.Join(cmd_out, "\n")) + } else if fileio.FileExist(dracutFile) { // Copy dracut config to /etc/dracut.conf.d/vfio - configs.CopyToSystem(config.IsRoot, dracutFile, "/etc/dracut.conf.d/vfio") + output = configs.CopyToSystem(dracutFile, "/etc/dracut.conf.d/vfio") + fmt.Printf("%s\n", output) // Get systeminfo sysinfo := uname.New() - if err = command.ExecAndLogSudo(config.IsRoot, true, "dracut", "-f", "-v", "--kver", sysinfo.Release); err != nil { - log.Fatalf("Failed to update initramfs: %s", err) - } + // Write to logger + logger.Printf("Executing: sudo dracut -f -v --kver %s\n", sysinfo.Release) - case mkinitcpioExists: + // Update initramfs + fmt.Printf("Executed: sudo dracut -f -v --kver %s\nSee debug.log for detailed output", sysinfo.Release) + _, cmd_err, _ := command.RunErr("sudo", "dracut", "-f", "-v", "--kver", sysinfo.Release) + + // Write to logger + logger.Printf(strings.Join(cmd_err, "\n")) + } else if fileio.FileExist(config.Path.MKINITCPIO) { // Copy dracut config to /etc/dracut.conf.d/vfio - configs.CopyToSystem(config.IsRoot, config.Path.MKINITCPIO, "/etc/mkinitcpio.conf") + output = configs.CopyToSystem(config.Path.MKINITCPIO, "/etc/mkinitcpio.conf") + fmt.Printf("%s\n", output) - if err = command.ExecAndLogSudo(config.IsRoot, true, "mkinitcpio", "-P"); err != nil { - log.Fatalf("Failed to update initramfs: %s", err) - } + // Write to logger + logger.Printf("Executing: sudo mkinitcpio -P") + + // Update initramfs + fmt.Println("Executed: sudo mkinitcpio -P\nSee debug.log for detailed output") + cmd_out, cmd_err, _ := command.RunErr("sudo", "mkinitcpio", "-P") + + cmd_out = append(cmd_out, cmd_err...) + + // Write to logger + logger.Printf(strings.Join(cmd_out, "\n")) } // Make sure prompt end up on next line diff --git a/internal/pages/06_finalize_test.go b/internal/pages/06_finalize_test.go deleted file mode 100644 index df9a7d0..0000000 --- a/internal/pages/06_finalize_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package pages - -import ( - "strings" - "testing" -) - -func TestFinalizeNotice(t *testing.T) { - msg := "\n%s\nprinting the finalize notice for manual review, this test should always pass.\n%s\n\n" - divider := strings.Repeat("-", len(msg)-12) - t.Logf(msg, divider, divider) - t.Log("\n\nWith isRoot == true:\n\n") - - finalizeNotice(true) - - println("\n\n") - - t.Log("\n\nWith isRoot == false:\n\n") - - finalizeNotice(false) - - println("\n\n") -} diff --git a/internal/ui_main.go b/internal/ui_main.go index 5bf650d..9b0db17 100644 --- a/internal/ui_main.go +++ b/internal/ui_main.go @@ -6,18 +6,17 @@ package internal import ( "os" - tea "github.com/charmbracelet/bubbletea" - - "github.com/HikariKnight/quickpassthrough/internal/common" + "github.com/HikariKnight/ls-iommu/pkg/errorcheck" "github.com/HikariKnight/quickpassthrough/internal/pages" + tea "github.com/charmbracelet/bubbletea" ) // This is where we build everything func Tui() { // Log all errors to a new logfile (super useful feature of BubbleTea!) - _ = os.Rename("quickpassthrough_debug.log", "quickpassthrough_debug_old.log") - logfile, err := tea.LogToFile("quickpassthrough_debug.log", "") - common.ErrorCheck(err, "Error creating log file") + os.Remove("debug.log") + logfile, err := tea.LogToFile("debug.log", "") + errorcheck.ErrorCheck(err, "Error creating log file") defer logfile.Close() // New WIP Tui diff --git a/pkg/command/command.go b/pkg/command/command.go index e6ba915..e7c6fca 100644 --- a/pkg/command/command.go +++ b/pkg/command/command.go @@ -3,15 +3,12 @@ package command import ( "bytes" "encoding/base64" - "fmt" "io" "os" "os/exec" - "strings" "time" - "github.com/HikariKnight/quickpassthrough/internal/common" - "github.com/HikariKnight/quickpassthrough/internal/logger" + "github.com/HikariKnight/ls-iommu/pkg/errorcheck" ) // Run a command and return STDOUT @@ -30,14 +27,14 @@ func Run(binary string, args ...string) ([]string, error) { output, _ := io.ReadAll(&stdout) // Get the output - outputs := make([]string, 0, 1) + outputs := []string{} outputs = append(outputs, string(output)) // Return our list of items return outputs, err } -// RunErr is just like command.Run() but also returns STDERR +// This function is just like command.Run() but also returns STDERR func RunErr(binary string, args ...string) ([]string, []string, error) { var stdout, stderr bytes.Buffer @@ -62,18 +59,8 @@ func RunErr(binary string, args ...string) ([]string, []string, error) { 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", -// this forces sudo to re-authenticate and lets us enter the password to STDIN +// This functions runs the command "sudo -Sk -- echo", this forces sudo +// to re-authenticate and lets us enter the password to STDIN // giving us the ability to run sudo commands func Elevate(password string) { // Do a simple sudo command to just authenticate with sudo @@ -84,83 +71,29 @@ func Elevate(password string) { // Open STDIN stdin, err := cmd.StdinPipe() - common.ErrorCheck(err, "\nFailed to get sudo STDIN") + errorcheck.ErrorCheck(err, "\nFailed to get sudo STDIN") // Start the authentication - err = cmd.Start() - common.ErrorCheck(err, "\nFailed to start sudo command") + cmd.Start() // Get the passed password pw, _ := base64.StdEncoding.DecodeString(password) _, err = stdin.Write([]byte(string(pw) + "\n")) - common.ErrorCheck(err, "\nFailed at typing to STDIN") + errorcheck.ErrorCheck(err, "\nFailed at typing to STDIN") // Clear the password pw = nil password = "" - _ = stdin.Close() + stdin.Close() // Wait for the sudo prompt (If the correct password was given, it will not stay behind) err = cmd.Wait() - common.ErrorCheck(err, "\nError, password given was wrong") + errorcheck.ErrorCheck(err, "\nError, password given was wrong") } -// Clear clears the terminal. +// Function to just clear the terminal func Clear() { c := exec.Command("clear") c.Stdout = os.Stdout - _ = 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, exe string, args ...string) error { - if !isRoot && exe != "sudo" { - og := exe - exe = "sudo" - newArgs := make([]string, 0) - newArgs = append(newArgs, og) - newArgs = append(newArgs, args...) - args = newArgs - } - - // Write to logger - logger.Printf("Executing (elevated): %s %s\n", exe, strings.Join(args, " ")) - - if noisy { - // Print to the user - fmt.Printf("Executing (elevated): %s %s\nSee debug.log for detailed output\n", exe, strings.Join(args, " ")) - } - - wd, err := os.Getwd() - if err != nil { - return err - } - - r := exec.Command(exe, args...) - r.Dir = wd - - 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) - } - - if err != nil { - err = fmt.Errorf("failed to execute %s: %w\n%s", exe, err, outStr) - } - - return err + c.Run() } diff --git a/pkg/command/command_test.go b/pkg/command/command_test.go deleted file mode 100644 index f6fa119..0000000 --- a/pkg/command/command_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package command - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -const fakeSudo = `#!/bin/sh -"$@" -qptest` - -const fakeUtil = `#!/bin/sh -echo "$@" -if [ "$4" = "-qptest" ]; then exit 0; else exit 1; fi` - -func setupExecTestEnv(t *testing.T) (string, string) { - t.Helper() - - tmpDir := t.TempDir() - fakeSudoPath := filepath.Join(tmpDir, "sudo") - fakeUtilPath := filepath.Join(tmpDir, "util") - - if err := os.WriteFile(fakeSudoPath, []byte(fakeSudo), 0755); err != nil { - t.Fatalf("failed to write fake sudo stub: %s", err.Error()) - } - if err := os.WriteFile(fakeUtilPath, []byte(fakeUtil), 0755); err != nil { - t.Fatalf("failed to write fake util stub: %s", err.Error()) - } - t.Setenv("PATH", tmpDir+":"+os.Getenv("PATH")) - - return fakeSudoPath, fakeUtilPath -} - -func TestExecAndLogSudo(t *testing.T) { - _, fakeUtilPath := setupExecTestEnv(t) - - args := []string{"i am a string with spaces", "i came to ruin parsers and chew bubble gum", "and I'm all out of bubblegum."} - - t.Run("is_not_root", func(t *testing.T) { - if err := ExecAndLogSudo(false, false, "util", args...); err != nil { - t.Errorf("unexpected error: %s", err.Error()) - } - }) - - t.Run("is_root", func(t *testing.T) { - newFakeUtil := strings.Replace(fakeUtil, "exit 1", "exit 0", 1) - newFakeUtil = strings.Replace(newFakeUtil, "exit 0", "exit 1", 1) - if err := os.WriteFile(fakeUtilPath, []byte(newFakeUtil), 0755); err != nil { - t.Fatalf("failed to overwrite fake util with modified stub: %s", err.Error()) - } - if err := ExecAndLogSudo(false, false, "util", args...); err == nil { - t.Errorf("expected error when using modified util with sudo, got nil") - } - - if err := ExecAndLogSudo(true, true, "util", args...); err != nil { - t.Errorf("unexpected error: %s", err.Error()) - } - }) - -} diff --git a/pkg/fileio/fileio.go b/pkg/fileio/fileio.go index b727ede..38c5a7a 100644 --- a/pkg/fileio/fileio.go +++ b/pkg/fileio/fileio.go @@ -7,31 +7,29 @@ import ( "io" "os" - "github.com/HikariKnight/quickpassthrough/internal/common" + "github.com/HikariKnight/ls-iommu/pkg/errorcheck" ) /* * This just implements repetetive tasks I have to do with files */ -// AppendContent creates a file and appends the content to the file. -// (ending newline must be supplied with content string) +// Creates a file and appends the content to the file (ending newline must be supplied with content string) func AppendContent(content string, fileName string) { // Open the file f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm) - - common.ErrorCheck(err, fmt.Sprintf("Error opening \"%s\" for writing", fileName)) + errorcheck.ErrorCheck(err, fmt.Sprintf("Error opening \"%s\" for writing", fileName)) defer f.Close() // Write the content _, err = f.WriteString(content) - common.ErrorCheck(err, fmt.Sprintf("Error writing to %s", fileName)) + errorcheck.ErrorCheck(err, fmt.Sprintf("Error writing to %s", fileName)) } -// ReadLines reads the file and returns a stringlist with each line. +// Reads the file and returns a stringlist with each line func ReadLines(fileName string) []string { content, err := os.Open(fileName) - common.ErrorCheck(err, fmt.Sprintf("Error reading file %s", fileName)) + errorcheck.ErrorCheck(err, fmt.Sprintf("Error reading file %s", fileName)) defer content.Close() // Make a list of lines @@ -48,55 +46,54 @@ func ReadLines(fileName string) []string { } -// ReadFile reads a file and returns all the content as a string. +// Reads a file and returns all the content as a string func ReadFile(fileName string) string { // Read the whole file content, err := os.ReadFile(fileName) - common.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 string(content) } -// FileExist checks if a file exists and returns a bool and any error that isn't os.ErrNotExist. -func FileExist(fileName string) (bool, error) { +// Checks if a file exists and returns a bool +func FileExist(fileName string) bool { var exist bool // Check if the file exists - _, err := os.Stat(fileName) - switch { - case err == nil: - exist = true - case errors.Is(err, os.ErrNotExist): + if _, err := os.Stat(fileName); !errors.Is(err, os.ErrNotExist) { // Set the value to true + exist = true + } else { + // Set the value to false exist = false - err = nil } + // Return if the file exists - return exist, err + return exist } -// FileCopy copies a FILE from source to dest. +// Copies a FILE from source to dest func FileCopy(sourceFile, destFile string) { // Get the file info filestat, err := os.Stat(sourceFile) - common.ErrorCheck(err, "Error getting fileinfo of: %s", sourceFile) + errorcheck.ErrorCheck(err, "Error getting fileinfo of: %s", sourceFile) // If the file is a regular file if filestat.Mode().IsRegular() { // Open the source file for reading source, err := os.Open(sourceFile) - common.ErrorCheck(err, "Error opening %s for copying", sourceFile) + errorcheck.ErrorCheck(err, "Error opening %s for copying", sourceFile) defer source.Close() // Create the destination file dest, err := os.Create(destFile) - common.ErrorCheck(err, "Error creating %s", destFile) + errorcheck.ErrorCheck(err, "Error creating %s", destFile) defer dest.Close() // Copy the contents of source to dest using io _, err = io.Copy(dest, source) - common.ErrorCheck(err, "Failed to copy \"%s\" to \"%s\"", sourceFile, destFile) + errorcheck.ErrorCheck(err, "Failed to copy \"%s\" to \"%s\"", sourceFile, destFile) } } diff --git a/pkg/menu/manual.go b/pkg/menu/manual.go index 5db9da7..5e0b06c 100644 --- a/pkg/menu/manual.go +++ b/pkg/menu/manual.go @@ -4,9 +4,8 @@ import ( "fmt" "strings" + "github.com/HikariKnight/ls-iommu/pkg/errorcheck" "github.com/gookit/color" - - "github.com/HikariKnight/quickpassthrough/internal/common" ) func ManualInput(msg string, format string) []string { @@ -19,7 +18,7 @@ func ManualInput(msg string, format string) []string { // Get the user input var input string _, err := fmt.Scan(&input) - common.ErrorCheck(err) + errorcheck.ErrorCheck(err) input_list := strings.Split(input, ",") diff --git a/pkg/untar/untar.go b/pkg/untar/untar.go index f94b399..96ab454 100644 --- a/pkg/untar/untar.go +++ b/pkg/untar/untar.go @@ -8,7 +8,7 @@ import ( "os" "path/filepath" - "github.com/HikariKnight/quickpassthrough/internal/common" + "github.com/HikariKnight/ls-iommu/pkg/errorcheck" ) // Slightly modified from source: https://medium.com/@skdomino/taring-untaring-files-in-go-6b07cf56bc07 @@ -17,7 +17,7 @@ import ( // creating the file structure at 'dst' along the way, and writing any files func Untar(dst string, fileName string) error { r, err := os.Open(fileName) - common.ErrorCheck(err, fmt.Sprintf("Failed to open: %s", fileName)) + errorcheck.ErrorCheck(err, fmt.Sprintf("Failed to open: %s", fileName)) defer r.Close() gzr, err := gzip.NewReader(r)