Compare commits
18 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c1f11ce1c3 | ||
![]() |
0efcf4ee19 | ||
![]() |
276cfd953a | ||
![]() |
4f51fd5c5f | ||
![]() |
05afa8c543 | ||
![]() |
d1e44d94d5 | ||
![]() |
3f934351fc | ||
![]() |
380c875213 | ||
![]() |
6c48a35180 | ||
![]() |
4d0086df41 | ||
![]() |
1002f4d64c | ||
![]() |
0273aabecc | ||
![]() |
47567c11fb | ||
![]() |
3bda7cadfc | ||
![]() |
a9b889e5ac | ||
![]() |
82e21005d5 | ||
![]() |
16fe298cb9 | ||
![]() |
8bd34b293e |
26 changed files with 681 additions and 317 deletions
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
@ -1,6 +1,6 @@
|
||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: HikariKnight # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
patreon: # Replace with patreon username
|
patreon: # Replace with patreon username
|
||||||
open_collective: # Replace with a single Open Collective username
|
open_collective: # Replace with a single Open Collective username
|
||||||
ko_fi: HikariKnight
|
ko_fi: HikariKnight
|
||||||
|
|
3
.github/workflows/release.yaml
vendored
3
.github/workflows/release.yaml
vendored
|
@ -1,7 +1,6 @@
|
||||||
name: goreleaser
|
name: Build & Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
|
||||||
push:
|
push:
|
||||||
# run only against tags
|
# run only against tags
|
||||||
tags:
|
tags:
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,3 +7,4 @@ dist/
|
||||||
main
|
main
|
||||||
quickpassthrough
|
quickpassthrough
|
||||||
debug.log
|
debug.log
|
||||||
|
quickpassthrough_debug.log
|
||||||
|
|
80
README.md
80
README.md
|
@ -3,44 +3,11 @@
|
||||||
A project to simplify setting up GPU passthrough on your Linux host for libvirt/virt-manager
|
A project to simplify setting up GPU passthrough on your Linux host for libvirt/virt-manager
|
||||||

|

|
||||||
|
|
||||||
You can use it by simply downloading the latest [release](https://github.com/HikariKnight/quickpassthrough/releases/) and run it inside a terminal/shell or by downloading and compiling it yourself with the commands below.
|
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.
|
||||||
|
|
||||||
## Build with current dependencies
|
This project is aimed at **systems with 2 GPUs** and **headless servers**, where the only GPU is not needed.
|
||||||
```bash
|
|
||||||
git clone https://github.com/HikariKnight/quickpassthrough.git
|
|
||||||
cd quickpassthrough
|
|
||||||
go mod download
|
|
||||||
CGO_ENABLED=0 go build -o quickpassthrough cmd/main.go
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build with newest dependencies (may break)
|
**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.
|
||||||
```bash
|
|
||||||
git clone https://github.com/HikariKnight/quickpassthrough.git
|
|
||||||
cd quickpassthrough
|
|
||||||
go mod download
|
|
||||||
go get -u ./cmd
|
|
||||||
CGO_ENABLED=0 go build -o quickpassthrough cmd/main.go
|
|
||||||
```
|
|
||||||
|
|
||||||
## Does this work on immutable systems?
|
|
||||||
Currently no, there might be support for ostree (fedora silverblue, kinoite, etc) at a later time.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
Copy the files back to your system (blank files inside .d/ folders will be used to "undo" any new config files we wrote) and rebuild your initramfs then remove the kernel arguments listed in config/kernel_args from your bootloader (if your system use kernelstub, grubby or you had to manually add them).
|
|
||||||
|
|
||||||
## 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. <br>
|
|
||||||
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
|
|
||||||
* Setup or configure your Virtual Machine (that is your job)
|
|
||||||
* 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 (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.
|
|
||||||
|
|
||||||
NOTE: This project is aimed at desktops and headless servers.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
* Show general warning to user and inform about making a backup and general expectations
|
* Show general warning to user and inform about making a backup and general expectations
|
||||||
|
@ -56,18 +23,55 @@ NOTE: This project is aimed at desktops and headless servers.
|
||||||
* Make sure [vendor-reset](https://github.com/gnif/vendor-reset) module is loaded before vfio, check the repository for the list of cards that require it!
|
* Make sure [vendor-reset](https://github.com/gnif/vendor-reset) module is loaded before vfio, check the repository for the list of cards that require it!
|
||||||
* Provides you with the correct kernel arguments to add to your bootloader entry if a supported bootloader is not found
|
* Provides you with the correct kernel arguments to add to your bootloader entry if a supported bootloader is not found
|
||||||
|
|
||||||
## Features now handled by [ls-iommu](https://github.com/HikariKnight/ls-iommu)
|
## Features handled by [ls-iommu](https://github.com/HikariKnight/ls-iommu)
|
||||||
* Automatically handle GPUs where parts of it might be in separate IOMMU groups (ex: RX6600XT)
|
* Automatically handle GPUs where parts of it might be in separate IOMMU groups (ex: RX6600XT)
|
||||||
* Fetch the ID and PCI Address of devices
|
* Fetch the ID and PCI Address of devices
|
||||||
* Locate the vbios rom path on the system
|
* Locate the vbios rom path on the system
|
||||||
* Tell the user to enable IOMMU (VT-d/AMD-v) on their motherboard and bootloader
|
* Tell the user to enable IOMMU (VT-d/AMD-v) on their motherboard and bootloader
|
||||||
* Get a list of devices, their IOMMU groups and various other information
|
* Get a list of devices, their IOMMU groups and various other information
|
||||||
|
|
||||||
|
## Build with current dependencies
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/HikariKnight/quickpassthrough.git
|
||||||
|
cd quickpassthrough
|
||||||
|
go mod download
|
||||||
|
CGO_ENABLED=0 go build -ldflags="-X github.com/HikariKnight/quickpassthrough/internal/version.Version=$(git rev-parse --short HEAD)" -o quickpassthrough cmd/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build with newest dependencies (may break)
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/HikariKnight/quickpassthrough.git
|
||||||
|
cd quickpassthrough
|
||||||
|
go mod download
|
||||||
|
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`.<br>
|
||||||
|
In Bluefin and Aurora you run `ujust setup-vfio` and follow the prompts.
|
||||||
|
|
||||||
|
## 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. <br>
|
||||||
|
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
|
||||||
|
* Setup or configure your Virtual Machine (that is your job)
|
||||||
|
* 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)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
<img src="https://user-images.githubusercontent.com/2557889/156038229-4e70352f-9182-4474-8e32-d14d3ad67566.png" width="250px">
|
<img src="https://user-images.githubusercontent.com/2557889/156038229-4e70352f-9182-4474-8e32-d14d3ad67566.png" width="250px">
|
||||||
|
|
||||||
This project originally started out as a bash only project, upon completing the proof of concept it became very clear that bash would become very messy with all the weird quirks and regex and inline editing of files. <br>
|
This project originally started out as a bash only project, upon completing the proof of concept it became very clear that bash would become very messy with all the weird quirks and regex and inline editing of files. <br>
|
||||||
So the project moved over to golang, this lets us utilize TUI toolkits like to build a proper menu system for the project. <br>
|
So the project moved over to golang, this lets us utilize TUI toolkits to build a proper menu system for the project. <br>
|
||||||
|
|
||||||
If you know golang, passthrough or qemu, you are welcome to help! Just make a pull request!<br>
|
If you know golang, passthrough or qemu, you are welcome to help! Just make a pull request!<br>
|
||||||
Just remember to add comments to document the work and explain it for people who are less familiar with the golang syntax or anything else you use. 😄
|
Just remember to add comments to document the work and explain it for people who are less familiar with the golang syntax or anything else you use. 😄
|
||||||
|
|
50
internal/common/errors.go
Normal file
50
internal/common/errors.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
||||||
|
"github.com/gookit/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PermissionNotice = `
|
||||||
|
<yellowB>Permissions error occured during file operations.</>
|
||||||
|
|
||||||
|
<blue_b>Hint</>:
|
||||||
|
|
||||||
|
If you initially ran QuickPassthrough as root or using sudo,
|
||||||
|
but are now running it as a normal user, this is expected behavior.
|
||||||
|
|
||||||
|
<us>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("\n<red_b>FATAL</>: %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...)
|
||||||
|
}
|
|
@ -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/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"
|
||||||
"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,39 +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)
|
common.ErrorCheck(
|
||||||
|
command.ExecAndLogSudo(isRoot, true, "kernelstub", "-a", kernel_args),
|
||||||
// Run the command
|
"Error, kernelstub command returned exit code 1",
|
||||||
_, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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))
|
||||||
|
common.ErrorCheck(err, "Error, grubby command returned exit code 1")
|
||||||
// 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 what we did
|
||||||
return fmt.Sprintf("Executed: sudo grubby --update-kernel=ALL --args=\"%s\"", kernel_args)
|
return fmt.Sprintf("Executed: sudo grubby --update-kernel=ALL --args=\"%s\"", kernel_args)
|
||||||
|
@ -116,8 +112,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 +197,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 +209,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 [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:
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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"))
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,23 +17,23 @@ 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
|
||||||
logger.Printf("Writing to %s:\nadd_drivers+=\" %s \"\n", dracutConf, strings.Join(vfio_modules(), " "))
|
logger.Printf("Writing to %s:\nforce_drivers+=\" %s \"\n", dracutConf, strings.Join(vfio_modules(), " "))
|
||||||
|
|
||||||
// Write the dracut config file
|
// Write the dracut config file
|
||||||
fileio.AppendContent(fmt.Sprintf("add_drivers+=\" %s \"\n", strings.Join(vfio_modules(), " ")), dracutConf)
|
fileio.AppendContent(fmt.Sprintf("force_drivers+=\" %s \"\n", strings.Join(vfio_modules(), " ")), dracutConf)
|
||||||
|
|
||||||
// Get the current kernel arguments we have generated
|
// Get the current kernel arguments we have generated
|
||||||
kernel_args := fileio.ReadFile(config.Path.CMDLINE)
|
kernel_args := fileio.ReadFile(config.Path.CMDLINE)
|
||||||
|
|
||||||
// If the kernel argument is not already in the file
|
// 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)
|
// 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
|
// Make a backup of dracutConf if there is one there
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
"github.com/HikariKnight/quickpassthrough/internal/common"
|
||||||
"github.com/HikariKnight/quickpassthrough/pkg/fileio"
|
"github.com/HikariKnight/quickpassthrough/pkg/fileio"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import (
|
||||||
func initramfs_readHeader(lines int, fileName string) string {
|
func initramfs_readHeader(lines int, fileName string) string {
|
||||||
// Open the file
|
// Open the file
|
||||||
f, err := os.Open(fileName)
|
f, err := os.Open(fileName)
|
||||||
errorcheck.ErrorCheck(err, fmt.Sprintf("Error opening %s", fileName))
|
common.ErrorCheck(err, fmt.Sprintf("Error opening %s", fileName))
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
header_re := regexp.MustCompile(`^#`)
|
header_re := regexp.MustCompile(`^#`)
|
||||||
|
@ -50,7 +50,7 @@ func initramfs_addModules(conffile string) {
|
||||||
|
|
||||||
// Open the system file for reading
|
// Open the system file for reading
|
||||||
sysfile, err := os.Open(syspath)
|
sysfile, err := os.Open(syspath)
|
||||||
errorcheck.ErrorCheck(err, fmt.Sprintf("Error opening file for reading %s", syspath))
|
common.ErrorCheck(err, fmt.Sprintf("Error opening file for reading %s", syspath))
|
||||||
defer sysfile.Close()
|
defer sysfile.Close()
|
||||||
|
|
||||||
// Check if user has vendor-reset installed/enabled and make sure that is first
|
// Check if user has vendor-reset installed/enabled and make sure that is first
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
"github.com/HikariKnight/quickpassthrough/internal/common"
|
||||||
"github.com/HikariKnight/quickpassthrough/internal/logger"
|
"github.com/HikariKnight/quickpassthrough/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ func GenerateVBIOSDumper(vbios_path string) {
|
||||||
vbios_script_template := fmt.Sprint(
|
vbios_script_template := fmt.Sprint(
|
||||||
"#!/bin/bash\n",
|
"#!/bin/bash\n",
|
||||||
"# THIS FILE IS AUTO GENERATED!\n",
|
"# THIS FILE IS AUTO GENERATED!\n",
|
||||||
"# IF YOU HAVE CHANGED GPU, PLEASE RE-RUN QUICKPASSTHROUGH!\n",
|
"# IF YOU HAVE CHANGED THE GPU, PLEASE RE-RUN QUICKPASSTHROUGH!\n",
|
||||||
"mkdir -p \"%s\"\n",
|
"mkdir -p \"%s\"\n",
|
||||||
"echo Attempting to enable reading from rom\n",
|
"echo Attempting to enable reading from rom\n",
|
||||||
"echo 1 | sudo tee %s\n",
|
"echo 1 | sudo tee %s\n",
|
||||||
|
@ -43,24 +43,24 @@ func GenerateVBIOSDumper(vbios_path string) {
|
||||||
|
|
||||||
vbios_script := fmt.Sprintf(
|
vbios_script := fmt.Sprintf(
|
||||||
vbios_script_template,
|
vbios_script_template,
|
||||||
config.Path.QUICKEMU,
|
config.Path.QEMU,
|
||||||
vbios_path,
|
vbios_path,
|
||||||
vbios_path,
|
vbios_path,
|
||||||
scriptdir,
|
scriptdir,
|
||||||
config.Path.QUICKEMU,
|
config.Path.QEMU,
|
||||||
scriptdir,
|
scriptdir,
|
||||||
config.Path.QUICKEMU,
|
config.Path.QEMU,
|
||||||
vbios_path,
|
vbios_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Make the script file
|
// Make the script file
|
||||||
scriptfile, err := os.Create("utils/dump_vbios.sh")
|
scriptfile, err := os.Create("utils/dump_vbios.sh")
|
||||||
errorcheck.ErrorCheck(err, "Cannot create file \"utils/dump_vbios.sh\"")
|
common.ErrorCheck(err, "Cannot create file \"utils/dump_vbios.sh\"")
|
||||||
defer scriptfile.Close()
|
defer scriptfile.Close()
|
||||||
|
|
||||||
// Make the script executable
|
// Make the script executable
|
||||||
scriptfile.Chmod(0775)
|
scriptfile.Chmod(0775)
|
||||||
errorcheck.ErrorCheck(err, "Could not change permissions of \"utils/dump_vbios.sh\"")
|
common.ErrorCheck(err, "Could not change permissions of \"utils/dump_vbios.sh\"")
|
||||||
|
|
||||||
// Write to logger
|
// Write to logger
|
||||||
logger.Printf("Writing utils/dump_vbios.sh\n")
|
logger.Printf("Writing utils/dump_vbios.sh\n")
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
"github.com/HikariKnight/quickpassthrough/internal/common"
|
||||||
"github.com/HikariKnight/quickpassthrough/internal/logger"
|
"github.com/HikariKnight/quickpassthrough/internal/logger"
|
||||||
"github.com/HikariKnight/quickpassthrough/pkg/fileio"
|
"github.com/HikariKnight/quickpassthrough/pkg/fileio"
|
||||||
)
|
)
|
||||||
|
@ -26,7 +26,7 @@ func DisableVFIOVideo(i int) {
|
||||||
if strings.Contains(kernel_args, "vfio_pci.disable_vga") {
|
if strings.Contains(kernel_args, "vfio_pci.disable_vga") {
|
||||||
// Remove the old file
|
// Remove the old file
|
||||||
err := os.Remove(config.Path.CMDLINE)
|
err := os.Remove(config.Path.CMDLINE)
|
||||||
errorcheck.ErrorCheck(err, fmt.Sprintf("Could not rewrite %s", config.Path.CMDLINE))
|
common.ErrorCheck(err, fmt.Sprintf("Could not rewrite %s", config.Path.CMDLINE))
|
||||||
|
|
||||||
// Enable or disable the VGA based on our given value
|
// Enable or disable the VGA based on our given value
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
package configs
|
package configs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
"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"
|
||||||
"github.com/HikariKnight/quickpassthrough/pkg/uname"
|
"github.com/HikariKnight/quickpassthrough/pkg/uname"
|
||||||
"github.com/klauspost/cpuid/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Path struct {
|
type Path struct {
|
||||||
|
@ -19,7 +22,7 @@ type Path struct {
|
||||||
INITRAMFS string
|
INITRAMFS string
|
||||||
ETCMODULES string
|
ETCMODULES string
|
||||||
DEFAULT string
|
DEFAULT string
|
||||||
QUICKEMU string
|
QEMU string
|
||||||
DRACUT string
|
DRACUT string
|
||||||
MKINITCPIO string
|
MKINITCPIO string
|
||||||
}
|
}
|
||||||
|
@ -30,9 +33,10 @@ type Config struct {
|
||||||
Path *Path
|
Path *Path
|
||||||
Gpu_Group string
|
Gpu_Group string
|
||||||
Gpu_IDs []string
|
Gpu_IDs []string
|
||||||
|
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",
|
||||||
|
@ -40,7 +44,7 @@ func GetConfigPaths() *Path {
|
||||||
INITRAMFS: "config/etc/initramfs-tools",
|
INITRAMFS: "config/etc/initramfs-tools",
|
||||||
ETCMODULES: "config/etc/modules",
|
ETCMODULES: "config/etc/modules",
|
||||||
DEFAULT: "config/etc/default",
|
DEFAULT: "config/etc/default",
|
||||||
QUICKEMU: "config/quickemu",
|
QEMU: "config/qemu",
|
||||||
DRACUT: "config/etc/dracut.conf.d",
|
DRACUT: "config/etc/dracut.conf.d",
|
||||||
MKINITCPIO: "config/etc/mkinitcpio.conf",
|
MKINITCPIO: "config/etc/mkinitcpio.conf",
|
||||||
}
|
}
|
||||||
|
@ -48,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",
|
||||||
|
@ -64,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()
|
||||||
|
|
||||||
|
@ -77,10 +81,17 @@ func InitConfigs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove old config
|
// Remove old config
|
||||||
os.RemoveAll("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")
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
|
// won't be called if the error is ErrExist
|
||||||
|
common.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`)
|
||||||
|
@ -90,8 +101,16 @@ 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 {
|
||||||
|
common.ErrorCheck(err, "\nError checking for directory: "+syspath)
|
||||||
|
continue // note: unreachable due to ErrorCheck calling fatal
|
||||||
|
}
|
||||||
|
|
||||||
// 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"+
|
||||||
|
@ -104,8 +123,10 @@ 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)
|
common.ErrorCheck(err, "\nError making directory: "+confpath)
|
||||||
|
return // note: unreachable due to ErrorCheck calling fatal
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +149,15 @@ 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 {
|
||||||
|
common.ErrorCheck(err, "\nError checking for file: "+sysfile)
|
||||||
|
continue // note: unreachable due to ErrorCheck calling fatal
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
// Write to log
|
// Write to log
|
||||||
logger.Printf(
|
logger.Printf(
|
||||||
"%s found on the system\n"+
|
"%s found on the system\n"+
|
||||||
|
@ -139,16 +168,22 @@ func InitConfigs() {
|
||||||
|
|
||||||
// Create the directories for our configs
|
// Create the directories for our configs
|
||||||
file, err := os.Create(conffile)
|
file, err := os.Create(conffile)
|
||||||
errorcheck.ErrorCheck(err)
|
common.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 {
|
||||||
|
common.ErrorCheck(err, "\nError checking for file: "+conffile)
|
||||||
|
continue // note: 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
|
||||||
|
@ -186,7 +221,7 @@ func vfio_modules() []string {
|
||||||
|
|
||||||
// If we are on a kernel older than 6.2
|
// If we are on a kernel older than 6.2
|
||||||
sysinfo := uname.New()
|
sysinfo := uname.New()
|
||||||
kernel_re := regexp.MustCompile(`^(6\.1|6\.0|[1-5]\.)`)
|
kernel_re := regexp.MustCompile(`^(6\.1|6\.0|[1-5]\.\d{1,2})\.`)
|
||||||
if kernel_re.MatchString(sysinfo.Kernel) {
|
if kernel_re.MatchString(sysinfo.Kernel) {
|
||||||
// Write to the debug log
|
// Write to the debug log
|
||||||
logger.Printf("Linux kernel version %s detected!\nIncluding vfio_virqfd module\n", sysinfo.Kernel)
|
logger.Printf("Linux kernel version %s detected!\nIncluding vfio_virqfd module\n", sysinfo.Kernel)
|
||||||
|
@ -204,14 +239,28 @@ 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 {
|
||||||
|
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
|
// 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)
|
common.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)
|
||||||
|
|
||||||
|
@ -223,29 +272,64 @@ 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
|
||||||
|
common.ErrorCheck(err, "Error checking for backup/ folder")
|
||||||
|
return // note: 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) {
|
||||||
errorcheck.ErrorCheck(err, "Error making backup/ folder")
|
// ignore if the directory already exists
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
// will return without incident if there's no error
|
||||||
|
common.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
|
if isRoot {
|
||||||
logger.Printf("%s\n", clean_output)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// Return the output
|
if !filepath.IsAbs(conffile) {
|
||||||
return fmt.Sprintf("Copying: %s", clean_output)
|
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)
|
||||||
}
|
}
|
||||||
|
|
33
internal/configs/configs_test.go
Normal file
33
internal/configs/configs_test.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,10 +12,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
"github.com/cavaliergopher/grab/v3"
|
||||||
|
|
||||||
|
"github.com/HikariKnight/quickpassthrough/internal/common"
|
||||||
"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/
|
||||||
|
@ -95,14 +96,14 @@ type Response struct {
|
||||||
func CheckLsIOMMU() {
|
func CheckLsIOMMU() {
|
||||||
// Check the API for releases
|
// Check the API for releases
|
||||||
resp, err := http.Get("https://api.github.com/repos/hikariknight/ls-iommu/releases/latest")
|
resp, err := http.Get("https://api.github.com/repos/hikariknight/ls-iommu/releases/latest")
|
||||||
errorcheck.ErrorCheck(err)
|
common.ErrorCheck(err)
|
||||||
|
|
||||||
// Close the response when function ends
|
// Close the response when function ends
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Get the response body
|
// Get the response body
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
errorcheck.ErrorCheck(err)
|
common.ErrorCheck(err)
|
||||||
|
|
||||||
var result Response
|
var result Response
|
||||||
if err := json.Unmarshal(body, &result); err != nil {
|
if err := json.Unmarshal(body, &result); err != nil {
|
||||||
|
@ -111,9 +112,9 @@ 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)
|
common.ErrorCheck(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the download url
|
// Generate the download url
|
||||||
|
@ -124,7 +125,8 @@ func CheckLsIOMMU() {
|
||||||
|
|
||||||
// Generate checksums.txt url
|
// Generate checksums.txt url
|
||||||
checkSumsUrl := fmt.Sprintf(
|
checkSumsUrl := fmt.Sprintf(
|
||||||
"https://github.com/HikariKnight/ls-iommu/releases/download/%s/checksums.txt",
|
"https://github.com/HikariKnight/ls-iommu/releases/download/%s/ls-iommu_%s_checksums.txt",
|
||||||
|
result.TagName,
|
||||||
result.TagName,
|
result.TagName,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -132,30 +134,30 @@ func CheckLsIOMMU() {
|
||||||
|
|
||||||
// Get the checksum data
|
// Get the checksum data
|
||||||
checksums, err := http.Get(checkSumsUrl)
|
checksums, err := http.Get(checkSumsUrl)
|
||||||
errorcheck.ErrorCheck(err)
|
common.ErrorCheck(err)
|
||||||
defer checksums.Body.Close()
|
defer checksums.Body.Close()
|
||||||
checksums_txt, err := io.ReadAll(checksums.Body)
|
checksums_txt, err := io.ReadAll(checksums.Body)
|
||||||
errorcheck.ErrorCheck(err)
|
common.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)
|
||||||
errorcheck.ErrorCheck(err)
|
common.ErrorCheck(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !checkSum(string(checksums_txt), fileName) {
|
if !checkSum(string(checksums_txt), fileName) {
|
||||||
downloadNewVersion(path, fileName, downloadUrl)
|
downloadNewVersion(path, fileName, downloadUrl)
|
||||||
err = untar.Untar(fmt.Sprintf("%s/", path), fileName)
|
err = untar.Untar(fmt.Sprintf("%s/", path), fileName)
|
||||||
errorcheck.ErrorCheck(err)
|
common.ErrorCheck(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSum(checksums string, fileName string) bool {
|
func checkSum(checksums string, fileName string) bool {
|
||||||
r, err := os.Open(fileName)
|
r, err := os.Open(fileName)
|
||||||
errorcheck.ErrorCheck(err)
|
common.ErrorCheck(err)
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
hasher := sha256.New()
|
hasher := sha256.New()
|
||||||
|
@ -181,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.")
|
||||||
|
|
|
@ -4,13 +4,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
"github.com/gookit/color"
|
||||||
|
|
||||||
|
"github.com/HikariKnight/quickpassthrough/internal/common"
|
||||||
"github.com/HikariKnight/quickpassthrough/internal/configs"
|
"github.com/HikariKnight/quickpassthrough/internal/configs"
|
||||||
lsiommu "github.com/HikariKnight/quickpassthrough/internal/lsiommu"
|
"github.com/HikariKnight/quickpassthrough/internal/lsiommu"
|
||||||
"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/HikariKnight/quickpassthrough/pkg/menu"
|
"github.com/HikariKnight/quickpassthrough/pkg/menu"
|
||||||
"github.com/gookit/color"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func SelectGPU(config *configs.Config) {
|
func SelectGPU(config *configs.Config) {
|
||||||
|
@ -94,10 +95,10 @@ 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))
|
common.ErrorCheck(err, fmt.Sprintf("Could not remove %s", config.Path.CMDLINE))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write initial kernel_arg file
|
// Write initial kernel_arg file
|
||||||
|
@ -114,10 +115,10 @@ 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))
|
common.ErrorCheck(err, fmt.Sprintf("Could not remove %s", config.Path.CMDLINE))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write initial kernel_arg file
|
// Write initial kernel_arg file
|
||||||
|
|
|
@ -26,21 +26,34 @@ func genVBIOS_dumper(config *configs.Config) {
|
||||||
scriptdir, _ = os.Getwd()
|
scriptdir, _ = os.Getwd()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the vbios path and generate the vbios dumping script
|
// Search for a vbios path and generate the vbios dumping script if found
|
||||||
vbios_path := lsiommu.GetIOMMU("-g", "-i", config.Gpu_Group, "--rom")[0]
|
vbios_paths := lsiommu.GetIOMMU("-g", "-i", config.Gpu_Group, "--rom")
|
||||||
configs.GenerateVBIOSDumper(vbios_path)
|
if len(vbios_paths) != 0 {
|
||||||
|
configs.GenerateVBIOSDumper(vbios_paths[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the qemu config folder
|
||||||
|
os.Mkdir(fmt.Sprintf("%s/%s", scriptdir, config.Path.QEMU), os.ModePerm)
|
||||||
|
|
||||||
|
// Generate a dummy rom (1MB rom of zeroes) for use with AMD RX 7000 series cards by recommendation from Gnif
|
||||||
|
// Source: https://forum.level1techs.com/t/the-state-of-amd-rx-7000-series-vfio-passthrough-april-2024/210242
|
||||||
|
command.Run("dd", "if=/dev/zero", fmt.Sprintf("of=%s/%s/dummy.rom", scriptdir, config.Path.QEMU), "bs=1M", "count=1")
|
||||||
|
|
||||||
// Write a title
|
// Write a title
|
||||||
title := color.New(color.BgHiBlue, color.White, color.Bold)
|
title := color.New(color.BgHiBlue, color.White, color.Bold)
|
||||||
title.Println("Generated \"dump VBIOS\" script")
|
title.Println("VBIOS roms for Passthrough")
|
||||||
|
|
||||||
// Tell users about the VBIOS dumper script
|
// Tell users about the VBIOS dumper script and dummy rom for RX 7000 series cards
|
||||||
fmt.Print(
|
fmt.Print(
|
||||||
"For some GPUs, you will need to dump the VBIOS and pass the\n",
|
"If you have an RX 7000 series (and possibly newer AMD cards) GPUs, please use the dummy.rom file\n",
|
||||||
|
fmt.Sprintf("%s/%s/dummy.rom\n", scriptdir, config.Path.QEMU),
|
||||||
|
"Or disable ROM BAR for the card in qemu/libvirt\n",
|
||||||
|
"\n",
|
||||||
|
"For some other GPUs, you will need to instead dump the VBIOS (and possibly patch it) and pass the\n",
|
||||||
"rom to the VM along with the card in order to get a functional passthrough.\n",
|
"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",
|
"In many cases you can find your vbios at https://www.techpowerup.com/vgabios/\n",
|
||||||
"\n",
|
"\n",
|
||||||
"You can also attempt to dump your own vbios using the script in\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",
|
||||||
fmt.Sprintf("%s/utils/dump_vbios.sh\n", scriptdir),
|
fmt.Sprintf("%s/utils/dump_vbios.sh\n", scriptdir),
|
||||||
"\n",
|
"\n",
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/gookit/color"
|
||||||
|
|
||||||
"github.com/HikariKnight/quickpassthrough/internal/configs"
|
"github.com/HikariKnight/quickpassthrough/internal/configs"
|
||||||
lsiommu "github.com/HikariKnight/quickpassthrough/internal/lsiommu"
|
"github.com/HikariKnight/quickpassthrough/internal/lsiommu"
|
||||||
"github.com/HikariKnight/quickpassthrough/pkg/command"
|
"github.com/HikariKnight/quickpassthrough/pkg/command"
|
||||||
"github.com/HikariKnight/quickpassthrough/pkg/menu"
|
"github.com/HikariKnight/quickpassthrough/pkg/menu"
|
||||||
"github.com/gookit/color"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func selectUSB(config *configs.Config) {
|
func selectUSB(config *configs.Config) {
|
||||||
|
|
|
@ -6,34 +6,34 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/gookit/color"
|
||||||
|
"golang.org/x/term"
|
||||||
|
|
||||||
"github.com/HikariKnight/quickpassthrough/internal/configs"
|
"github.com/HikariKnight/quickpassthrough/internal/configs"
|
||||||
"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/HikariKnight/quickpassthrough/pkg/menu"
|
"github.com/HikariKnight/quickpassthrough/pkg/menu"
|
||||||
"github.com/HikariKnight/quickpassthrough/pkg/uname"
|
"github.com/HikariKnight/quickpassthrough/pkg/uname"
|
||||||
"github.com/gookit/color"
|
|
||||||
"golang.org/x/term"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,43 @@ func prepModules(config *configs.Config) {
|
||||||
finalize(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.
|
||||||
|
|
||||||
|
<red>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) {
|
func finalize(config *configs.Config) {
|
||||||
// Clear the screen
|
// Clear the screen
|
||||||
command.Clear()
|
command.Clear()
|
||||||
|
@ -56,60 +93,43 @@ 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")
|
||||||
|
|
||||||
color.Print(
|
config.IsRoot = os.Getuid() == 0
|
||||||
"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 \"quickemu\" folder contains files that might be\n useable for quickemu in the future\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",
|
|
||||||
)
|
|
||||||
|
|
||||||
// Make a choice of going next or back
|
finalizeNotice(config.IsRoot)
|
||||||
choice := menu.Next("Press Next to continue with sudo using STDIN, ESC to exit or Back to go back.")
|
|
||||||
|
|
||||||
// Parse the choice
|
// Make a choice of going next or back and parse the choice
|
||||||
switch choice {
|
switch menu.Next("Press Next to continue with sudo using STDIN, ESC to exit or Back to go back.") {
|
||||||
case "next":
|
case "next":
|
||||||
installPassthrough(config)
|
installPassthrough(config)
|
||||||
|
|
||||||
case "back":
|
case "back":
|
||||||
// Go back
|
// Go back
|
||||||
disableVideo(config)
|
disableVideo(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func installPassthrough(config *configs.Config) {
|
func installPassthrough(config *configs.Config) {
|
||||||
// Get the user data
|
// Get the user data
|
||||||
user, err := user.Current()
|
currentUser, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf(err.Error())
|
log.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide a password prompt
|
if !config.IsRoot {
|
||||||
fmt.Printf("[sudo] password for %s: ", user.Username)
|
// Provide a password prompt
|
||||||
bytep, err := term.ReadPassword(int(syscall.Stdin))
|
fmt.Printf("[sudo] password for %s: ", currentUser.Username)
|
||||||
if err != nil {
|
bytep, err := term.ReadPassword(syscall.Stdin)
|
||||||
os.Exit(1)
|
if err != nil {
|
||||||
}
|
os.Exit(1)
|
||||||
fmt.Print("\n")
|
}
|
||||||
|
fmt.Print("\n")
|
||||||
|
|
||||||
// Elevate with sudo
|
// Elevate with sudo
|
||||||
command.Elevate(
|
command.Elevate(
|
||||||
base64.StdEncoding.EncodeToString(
|
base64.StdEncoding.EncodeToString(
|
||||||
bytep,
|
bytep,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Make an output string
|
// Make an output string
|
||||||
var output string
|
var output string
|
||||||
|
@ -120,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)
|
||||||
|
@ -145,68 +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
|
// lets hope by now we've already handled any permissions issues...
|
||||||
output = configs.CopyToSystem(modprobeFile, "/etc/modprobe.d/vfio.conf")
|
// TODO: verify that we actually can drop the errors on [fileio.FileExist] call below
|
||||||
fmt.Printf("%s\n", output)
|
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
if fileio.FileExist(initramfsFile) {
|
|
||||||
|
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:
|
||||||
// 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)
|
|
||||||
|
|
||||||
// Write to logger
|
if err = command.ExecAndLogSudo(config.IsRoot, true, "update-initramfs", "-u"); err != nil {
|
||||||
logger.Printf("Executing: sudo update-initramfs -u\n")
|
log.Fatalf("Failed to update initramfs: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Update initramfs
|
case dracutExists:
|
||||||
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
|
// 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()
|
||||||
|
|
||||||
// Write to logger
|
if err = command.ExecAndLogSudo(config.IsRoot, true, "dracut", "-f", "-v", "--kver", sysinfo.Release); err != nil {
|
||||||
logger.Printf("Executing: sudo dracut -f -v --kver %s\n", sysinfo.Release)
|
log.Fatalf("Failed to update initramfs: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Update initramfs
|
case mkinitcpioExists:
|
||||||
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
|
// 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)
|
|
||||||
|
|
||||||
// Write to logger
|
if err = command.ExecAndLogSudo(config.IsRoot, true, "mkinitcpio", "-P"); err != nil {
|
||||||
logger.Printf("Executing: sudo mkinitcpio -P")
|
log.Fatalf("Failed to update initramfs: %s", err)
|
||||||
|
}
|
||||||
// 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
|
// Make sure prompt end up on next line
|
||||||
|
|
23
internal/pages/06_finalize_test.go
Normal file
23
internal/pages/06_finalize_test.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
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")
|
||||||
|
}
|
|
@ -6,17 +6,18 @@ package internal
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
|
||||||
"github.com/HikariKnight/quickpassthrough/internal/pages"
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
|
||||||
|
"github.com/HikariKnight/quickpassthrough/internal/common"
|
||||||
|
"github.com/HikariKnight/quickpassthrough/internal/pages"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is where we build everything
|
// This is where we build everything
|
||||||
func Tui() {
|
func Tui() {
|
||||||
// Log all errors to a new logfile (super useful feature of BubbleTea!)
|
// Log all errors to a new logfile (super useful feature of BubbleTea!)
|
||||||
os.Remove("debug.log")
|
_ = os.Rename("quickpassthrough_debug.log", "quickpassthrough_debug_old.log")
|
||||||
logfile, err := tea.LogToFile("debug.log", "")
|
logfile, err := tea.LogToFile("quickpassthrough_debug.log", "")
|
||||||
errorcheck.ErrorCheck(err, "Error creating log file")
|
common.ErrorCheck(err, "Error creating log file")
|
||||||
defer logfile.Close()
|
defer logfile.Close()
|
||||||
|
|
||||||
// New WIP Tui
|
// New WIP Tui
|
||||||
|
|
|
@ -3,14 +3,18 @@ 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/quickpassthrough/internal/common"
|
||||||
|
"github.com/HikariKnight/quickpassthrough/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Run a command and return STDOUT
|
||||||
func Run(binary string, args ...string) ([]string, error) {
|
func Run(binary string, args ...string) ([]string, error) {
|
||||||
var stdout, stderr bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
|
|
||||||
|
@ -26,14 +30,14 @@ 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
|
||||||
return outputs, err
|
return outputs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is just like command.Run() but also returns STDERR
|
// RunErr is just like command.Run() but also returns STDERR
|
||||||
func RunErr(binary string, args ...string) ([]string, []string, error) {
|
func RunErr(binary string, args ...string) ([]string, []string, error) {
|
||||||
var stdout, stderr bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
|
|
||||||
|
@ -58,8 +62,18 @@ func RunErr(binary string, args ...string) ([]string, []string, error) {
|
||||||
return outputs, outerrs, err
|
return outputs, outerrs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// This functions runs the command "sudo -Sk -- echo", this forces sudo
|
func RunErrSudo(isRoot bool, binary string, args ...string) ([]string, []string, error) {
|
||||||
// to re-authenticate and lets us enter the password to STDIN
|
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
|
||||||
// giving us the ability to run sudo commands
|
// giving us the ability to run sudo commands
|
||||||
func Elevate(password string) {
|
func Elevate(password string) {
|
||||||
// Do a simple sudo command to just authenticate with sudo
|
// Do a simple sudo command to just authenticate with sudo
|
||||||
|
@ -70,29 +84,83 @@ func Elevate(password string) {
|
||||||
|
|
||||||
// Open STDIN
|
// Open STDIN
|
||||||
stdin, err := cmd.StdinPipe()
|
stdin, err := cmd.StdinPipe()
|
||||||
errorcheck.ErrorCheck(err, "\nFailed to get sudo STDIN")
|
common.ErrorCheck(err, "\nFailed to get sudo STDIN")
|
||||||
|
|
||||||
// Start the authentication
|
// Start the authentication
|
||||||
cmd.Start()
|
err = cmd.Start()
|
||||||
|
common.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)
|
||||||
_, err = stdin.Write([]byte(string(pw) + "\n"))
|
_, err = stdin.Write([]byte(string(pw) + "\n"))
|
||||||
errorcheck.ErrorCheck(err, "\nFailed at typing to STDIN")
|
common.ErrorCheck(err, "\nFailed at typing to STDIN")
|
||||||
// Clear the password
|
// Clear the password
|
||||||
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")
|
common.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, 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
|
||||||
}
|
}
|
||||||
|
|
61
pkg/command/command_test.go
Normal file
61
pkg/command/command_test.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
|
@ -7,29 +7,31 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"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))
|
|
||||||
|
common.ErrorCheck(err, fmt.Sprintf("Error opening \"%s\" for writing", fileName))
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
// Write the content
|
// Write the content
|
||||||
_, err = f.WriteString(content)
|
_, err = f.WriteString(content)
|
||||||
errorcheck.ErrorCheck(err, fmt.Sprintf("Error writing to %s", fileName))
|
common.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))
|
common.ErrorCheck(err, fmt.Sprintf("Error reading file %s", fileName))
|
||||||
defer content.Close()
|
defer content.Close()
|
||||||
|
|
||||||
// Make a list of lines
|
// Make a list of lines
|
||||||
|
@ -46,54 +48,55 @@ 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)
|
||||||
errorcheck.ErrorCheck(err, fmt.Sprintf("Failed to ReadFile on %s", fileName))
|
common.ErrorCheck(err, fmt.Sprintf("Failed to ReadFile on %s", fileName))
|
||||||
|
|
||||||
// Return all the lines as one string
|
// Return all the lines as one string
|
||||||
return string(content)
|
return string(content)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
errorcheck.ErrorCheck(err, "Error getting fileinfo of: %s", sourceFile)
|
common.ErrorCheck(err, "Error getting fileinfo of: %s", sourceFile)
|
||||||
|
|
||||||
// If the file is a regular file
|
// If the file is a regular file
|
||||||
if filestat.Mode().IsRegular() {
|
if filestat.Mode().IsRegular() {
|
||||||
// Open the source file for reading
|
// Open the source file for reading
|
||||||
source, err := os.Open(sourceFile)
|
source, err := os.Open(sourceFile)
|
||||||
errorcheck.ErrorCheck(err, "Error opening %s for copying", sourceFile)
|
common.ErrorCheck(err, "Error opening %s for copying", sourceFile)
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
// Create the destination file
|
// Create the destination file
|
||||||
dest, err := os.Create(destFile)
|
dest, err := os.Create(destFile)
|
||||||
errorcheck.ErrorCheck(err, "Error creating %s", destFile)
|
common.ErrorCheck(err, "Error creating %s", destFile)
|
||||||
defer dest.Close()
|
defer dest.Close()
|
||||||
|
|
||||||
// Copy the contents of source to dest using io
|
// Copy the contents of source to dest using io
|
||||||
_, err = io.Copy(dest, source)
|
_, err = io.Copy(dest, source)
|
||||||
errorcheck.ErrorCheck(err, "Failed to copy \"%s\" to \"%s\"", sourceFile, destFile)
|
common.ErrorCheck(err, "Failed to copy \"%s\" to \"%s\"", sourceFile, destFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
|
||||||
"github.com/gookit/color"
|
"github.com/gookit/color"
|
||||||
|
|
||||||
|
"github.com/HikariKnight/quickpassthrough/internal/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ManualInput(msg string, format string) []string {
|
func ManualInput(msg string, format string) []string {
|
||||||
|
@ -18,7 +19,7 @@ func ManualInput(msg string, format string) []string {
|
||||||
// Get the user input
|
// Get the user input
|
||||||
var input string
|
var input string
|
||||||
_, err := fmt.Scan(&input)
|
_, err := fmt.Scan(&input)
|
||||||
errorcheck.ErrorCheck(err)
|
common.ErrorCheck(err)
|
||||||
|
|
||||||
input_list := strings.Split(input, ",")
|
input_list := strings.Split(input, ",")
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
"github.com/HikariKnight/quickpassthrough/internal/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Slightly modified from source: https://medium.com/@skdomino/taring-untaring-files-in-go-6b07cf56bc07
|
// 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
|
// creating the file structure at 'dst' along the way, and writing any files
|
||||||
func Untar(dst string, fileName string) error {
|
func Untar(dst string, fileName string) error {
|
||||||
r, err := os.Open(fileName)
|
r, err := os.Open(fileName)
|
||||||
errorcheck.ErrorCheck(err, fmt.Sprintf("Failed to open: %s", fileName))
|
common.ErrorCheck(err, fmt.Sprintf("Failed to open: %s", fileName))
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
gzr, err := gzip.NewReader(r)
|
gzr, err := gzip.NewReader(r)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue