diff --git a/README.md b/README.md index 37d131b..3cb00d8 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ See also https://fedoraproject.org/wiki/Changes/SwapOnZRAM#Benefit_to_Fedora * zramd start --help ``` - Usage: zramd start [--algorithm ALGORITHM] [--fraction FRACTION] [--max-size MAX_SIZE] [--num-devices NUM_DEVICES] [--priority PRIORITY] + Usage: zramd start [--algorithm ALGORITHM] [--fraction FRACTION] [--max-size MAX_SIZE] [--num-devices NUM_DEVICES] [--priority PRIORITY] [--skip-vm] Options: --algorithm ALGORITHM, -a ALGORITHM @@ -66,6 +66,7 @@ See also https://fedoraproject.org/wiki/Changes/SwapOnZRAM#Benefit_to_Fedora maximum number of zram devices to create [default: 1] --priority PRIORITY, -p PRIORITY swap priority [default: 100] + --skip-vm, -s skip initialization if running on a VM [default: false] --help, -h display this help and exit ``` @@ -106,6 +107,7 @@ See also https://fedoraproject.org/wiki/Changes/SwapOnZRAM#Benefit_to_Fedora * **Avoid** using other zram-related packages along this one, `zramd` loads and unloads the zram kernel module assuming that the system is not using zram for other stuff e.g. tmpfs. * Do **not** use zswap with zram, it would unnecessarily cause data to be [compressed and decompressed back and forth](https://www.phoronix.com/forums/forum/software/distributions/1231542-fedora-34-looking-to-tweak-default-zram-configuration/page5#post1232327). * When dealing with virtual machines, zram should be used on the **host** OS so guest memory can be compressed transparently, see also comments on original zram [implementation](https://code.google.com/archive/p/compcache/). + * If you boot the same system on a real computer as well as on a virtual machine, you can use the `--skip-vm` parameter to avoid initialization when running inside a virtual machine. * For best results install `systemd-oomd` or `earlyoom` (they may not be available on all distributions). * You can use `swapon -show` or `zramctl` to see all swap devices currently in use, this is useful if you want to confirm that all of the zram devices were setup correctly. * To quickly fill the memory, you can use `tail /dev/zero` but keep in mind that your system may become unresponsive if you do not have an application like `earlyoom` to kill `tail` just before it reaches the memory limit. diff --git a/cspell.json b/cspell.json index 006bf7d..ecb99f5 100644 --- a/cspell.json +++ b/cspell.json @@ -5,6 +5,7 @@ "itertools", "lsmod", "mkswap", + "virt", "zram", "zramd", "zstd" diff --git a/extra/zramd.default b/extra/zramd.default index 4264ae9..1890b30 100644 --- a/extra/zramd.default +++ b/extra/zramd.default @@ -12,3 +12,6 @@ # Swap priority # PRIORITY=100 + +# Skip initialization if running inside a virtual machine +# SKIP_VM=false diff --git a/src/system/system.go b/src/system/system.go new file mode 100644 index 0000000..e2a93a5 --- /dev/null +++ b/src/system/system.go @@ -0,0 +1,45 @@ +package system + +import ( + "errors" + "os" + "os/exec" + "regexp" + "strings" +) + +// IsRoot will check if current process was started by the init system (e.g. +// systemd) from which we expect to handle this process' capabilities, otherwise +// check if the current process is running as root. +func IsRoot() bool { + return os.Getppid() == 1 || os.Geteuid() == 0 +} + +func cpuInfo() *[]byte { + data, err := os.ReadFile("/proc/cpuinfo") + if err != nil { + panic(err) + } + return &data +} + +// IsVM detects if we are currently running inside a VM, see also +// https://man.archlinux.org/man/systemd-detect-virt.1.en. +func IsVM() bool { + // Try to run systemd-detect-virt which is more accurate but is not present on + // all systems. + cmd := exec.Command("systemd-detect-virt", "--vm") + out, err := cmd.Output() + if err == nil { + return strings.TrimSpace(string(out)) != "none" + } + // If error happened because systemd-detect-virt is not available on the + // system, try to use cpuinfo (less accurate but available everywhere). + if errors.Is(err, exec.ErrNotFound) { + info := cpuInfo() + pattern := "(?m)^flags\\s*\\:.*\\s+hypervisor(?:\\s+.*)?$" + match, _ := regexp.Match(pattern, *info) + return match + } + panic(err) +} diff --git a/src/zramd.go b/src/zramd.go index 3a31a59..c2bc1d1 100644 --- a/src/zramd.go +++ b/src/zramd.go @@ -7,6 +7,7 @@ import ( "sync" "zramd/src/kernelversion" "zramd/src/memory" + "zramd/src/system" "zramd/src/zram" "github.com/alexflint/go-arg" @@ -26,6 +27,7 @@ type startCmd struct { MaxSizeMB int `arg:"-m,--max-size,env:MAX_SIZE" default:"8192" placeholder:"MAX_SIZE" help:"maximum total MB of swap to allocate"` NumDevices int `arg:"-n,--num-devices,env:NUM_DEVICES" default:"1" placeholder:"NUM_DEVICES" help:"maximum number of zram devices to create"` SwapPriority int `arg:"-p,--priority,env:PRIORITY" default:"100" placeholder:"PRIORITY" help:"swap priority"` + SkipVM bool `arg:"-s,--skip-vm,env:SKIP_VM" default:"false" help:"skip initialization if running on a VM"` } type stopCmd struct { @@ -139,13 +141,6 @@ func deinitializeZram() int { return ret } -// canRun will check if current process was started by the init system (e.g. -// systemd) from which we expect to handle this process' capabilities, otherwise -// check if the current process is running as root. -func canRun() bool { - return os.Getppid() == 1 || os.Geteuid() == 0 -} - func run() int { if len(os.Args) > 1 && os.Args[1] == "--version" { fmt.Printf("zramd %s %s %s\n", Version, CommitDate, runtime.GOARCH) @@ -199,11 +194,19 @@ func run() int { if args.Start.SwapPriority < -1 || args.Start.SwapPriority > 32767 { parser.Fail("--priority must have a value between -1 and 32767") } + // Avoid initializing zram if running on a virtual machine, we are not + // relying on systemd's "ConditionVirtualization=!vm" because it requires + // systemd, also we want this to be an opt-in setting unlike + // ConditionVirtualization which would behave the other way around. + if args.Start.SkipVM && system.IsVM() { + fmt.Println("virtual machine detected, initialization skipped") + return 0 + } if zram.IsLoaded() { errorf("the zram module is already loaded") return 1 } - if !canRun() { + if !system.IsRoot() { errorf("root privileges are required") return 1 } @@ -214,7 +217,7 @@ func run() int { errorf("the zram module is not loaded") return 1 } - if !canRun() { + if !system.IsRoot() { errorf("root privileges are required") return 1 }