From a6e5c048ba9a5aa68da68b4fb5cf6bb7faead262 Mon Sep 17 00:00:00 2001 From: maximumadmin <61138453+maximumadmin@users.noreply.github.com> Date: Fri, 2 Apr 2021 12:30:57 +0900 Subject: [PATCH 1/3] Move logic to separate package and VM detection --- src/system/system.go | 26 ++++++++++++++++++++++++++ src/zramd.go | 12 +++--------- 2 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 src/system/system.go diff --git a/src/system/system.go b/src/system/system.go new file mode 100644 index 0000000..8acd598 --- /dev/null +++ b/src/system/system.go @@ -0,0 +1,26 @@ +package system + +import ( + "os" + "os/exec" + "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 +} + +// IsVM detects if we are currently running inside a VM, if systemd-detect-virt +// is missing (i.e. on non systemd systems), the result will be false, see also +// https://man.archlinux.org/man/systemd-detect-virt.1.en. +func IsVM() bool { + cmd := exec.Command("systemd-detect-virt", "--vm") + out, err := cmd.Output() + if err != nil { + return false + } + return strings.TrimSpace(string(out)) != "none" +} diff --git a/src/zramd.go b/src/zramd.go index 3a31a59..06f90ce 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" @@ -139,13 +140,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) @@ -203,7 +197,7 @@ func run() int { errorf("the zram module is already loaded") return 1 } - if !canRun() { + if !system.IsRoot() { errorf("root privileges are required") return 1 } @@ -214,7 +208,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 } From 7b23034b57714423df1424d41839f236cb501c52 Mon Sep 17 00:00:00 2001 From: maximumadmin <61138453+maximumadmin@users.noreply.github.com> Date: Fri, 2 Apr 2021 14:23:49 +0900 Subject: [PATCH 2/3] Improve VM detection --- cspell.json | 1 + src/system/system.go | 29 ++++++++++++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) 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/src/system/system.go b/src/system/system.go index 8acd598..df35b5d 100644 --- a/src/system/system.go +++ b/src/system/system.go @@ -1,8 +1,10 @@ package system import ( + "errors" "os" "os/exec" + "regexp" "strings" ) @@ -13,14 +15,31 @@ func IsRoot() bool { return os.Getppid() == 1 || os.Geteuid() == 0 } -// IsVM detects if we are currently running inside a VM, if systemd-detect-virt -// is missing (i.e. on non systemd systems), the result will be false, see also +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 false + 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 } - return strings.TrimSpace(string(out)) != "none" + panic(err) } From f72a810811fb0c4904ec468c75fc12aa22cb9ac0 Mon Sep 17 00:00:00 2001 From: maximumadmin <61138453+maximumadmin@users.noreply.github.com> Date: Fri, 2 Apr 2021 16:14:44 +0900 Subject: [PATCH 3/3] Add skip initialization option --- README.md | 4 +++- extra/zramd.default | 3 +++ src/system/system.go | 6 +++--- src/zramd.go | 9 +++++++++ 4 files changed, 18 insertions(+), 4 deletions(-) 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/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 index df35b5d..e2a93a5 100644 --- a/src/system/system.go +++ b/src/system/system.go @@ -15,12 +15,12 @@ func IsRoot() bool { return os.Getppid() == 1 || os.Geteuid() == 0 } -func cpuInfo() []byte { +func cpuInfo() *[]byte { data, err := os.ReadFile("/proc/cpuinfo") if err != nil { panic(err) } - return data + return &data } // IsVM detects if we are currently running inside a VM, see also @@ -38,7 +38,7 @@ func IsVM() bool { if errors.Is(err, exec.ErrNotFound) { info := cpuInfo() pattern := "(?m)^flags\\s*\\:.*\\s+hypervisor(?:\\s+.*)?$" - match, _ := regexp.Match(pattern, info) + match, _ := regexp.Match(pattern, *info) return match } panic(err) diff --git a/src/zramd.go b/src/zramd.go index 06f90ce..c2bc1d1 100644 --- a/src/zramd.go +++ b/src/zramd.go @@ -27,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 { @@ -193,6 +194,14 @@ 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