zram-generator-1.2.1/.cargo_vcs_info.json0000644000000001360000000000100137720ustar { "git": { "sha1": "c2685d18e596c5843b7d9027f6b8379168d753dc" }, "path_in_vcs": "" }zram-generator-1.2.1/.github/workflows/ci.yml000064400000000000000000000043521046102023000173010ustar 00000000000000# SPDX-License-Identifier: MIT name: ci on: push: pull_request: schedule: - cron: "0 4 * * *" permissions: contents: read env: CARGO_TERM_COLOR: always jobs: test: name: Build and run tests (rust ${{ matrix.rust }}) runs-on: ubuntu-latest strategy: matrix: rust: - stable - beta - nightly steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install linux-modules-extra-$(uname -r) uses: tecolicom/actions-use-apt-tools@main with: tools: linux-modules-extra-$(uname -r) - name: Insert required modules run: | sudo depmod sudo modprobe -v zram sudo modprobe -v zstd - name: Install Rust run: | rm -f ~/.cargo/bin/*fmt ~/.cargo/bin/rust-analyzer curl https://sh.rustup.rs -sSf | sh -s -- -y - name: Install toolchain run: | rustup toolchain install ${{ matrix.rust }} rustup default ${{ matrix.rust }} - name: Build run: make program CARGOFLAGS="--verbose" - name: Run tests run: make check CARGOFLAGS="--verbose" - name: Check program invocation run: tests/test-invocations.sh target/release/zram-generator rustfmt: name: rustfmt runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust run: | rm -f ~/.cargo/bin/*fmt ~/.cargo/bin/rust-analyzer curl https://sh.rustup.rs -sSf | sh -s -- -y - name: Install toolchain run: | rustup toolchain install stable rustup default stable - name: Check formatting run: cargo fmt -- --check clippy: name: clippy runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust run: | rm -f ~/.cargo/bin/*fmt ~/.cargo/bin/rust-analyzer curl https://sh.rustup.rs -sSf | sh -s -- -y - name: Install toolchain run: | rustup toolchain install nightly rustup default nightly - name: Validate clippy run: make clippy CARGOFLAGS="-- -D warnings" zram-generator-1.2.1/.gitignore000064400000000000000000000000421046102023000145460ustar 00000000000000*~ /Cargo.lock /target **/*.rs.bk zram-generator-1.2.1/Cargo.lock0000644000000251220000000000100117470ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ahash" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", "version_check", ] [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anyhow" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "cc" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "ctor" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", "syn", ] [[package]] name = "dlv-list" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fasteval" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f4cdac9e4065d7c48e30770f8665b8cef9a3a73a63a4056a33a5f395bc7cf75" [[package]] name = "fastrand" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] [[package]] name = "libc" version = "0.2.166" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" [[package]] name = "liboverdrop" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08e5373d7512834e2fbbe4100111483a99c28ca3818639f67ab2337672301f8e" dependencies = [ "log", ] [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memoffset" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] [[package]] name = "nix" version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ "bitflags 1.3.2", "cc", "cfg-if", "libc", "memoffset", ] [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "ordered-multimap" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" dependencies = [ "dlv-list", "hashbrown", ] [[package]] name = "proc-macro2" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rust-ini" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" dependencies = [ "cfg-if", "ordered-multimap", ] [[package]] name = "rustix" version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "syn" version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "zram-generator" version = "1.2.1" dependencies = [ "anyhow", "clap", "ctor", "fasteval", "fs_extra", "liboverdrop", "log", "nix", "rust-ini", "tempfile", ] zram-generator-1.2.1/Cargo.toml0000644000000034540000000000100117760ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "zram-generator" version = "1.2.1" authors = [ "Zbigniew Jędrzejewski-Szmek ", "Igor Raits ", "наб ", ] build = false exclude = [ "tests/07a-mount-point-excl", "tests/10-example", ] autobins = false autoexamples = false autotests = false autobenches = false description = "Systemd unit generator for zram swap devices." homepage = "https://github.com/systemd/zram-generator" readme = "README.md" license = "MIT" [profile.release] opt-level = "z" lto = true codegen-units = 1 panic = "abort" [lib] name = "zram_generator" path = "src/lib.rs" [[bin]] name = "zram-generator" path = "src/main.rs" [[test]] name = "test_cases" path = "tests/test_cases.rs" [dependencies.anyhow] version = "1.0.12" [dependencies.clap] version = "4.5" features = [ "std", "cargo", "help", "error-context", ] default-features = false [dependencies.fasteval] version = "0.2" default-features = false [dependencies.liboverdrop] version = "0.1.0" [dependencies.log] version = "0.4" features = ["std"] [dependencies.rust-ini] version = ">=0.15, <0.19" [dev-dependencies.ctor] version = "0.2" [dev-dependencies.fs_extra] version = "1.3" [dev-dependencies.nix] version = ">=0.22, <0.24" [dev-dependencies.tempfile] version = "3" zram-generator-1.2.1/Cargo.toml.orig000064400000000000000000000016411046102023000154530ustar 00000000000000# SPDX-License-Identifier: MIT [package] name = "zram-generator" version = "1.2.1" authors = ["Zbigniew Jędrzejewski-Szmek ", "Igor Raits ", "наб "] license = "MIT" description = "Systemd unit generator for zram swap devices." homepage = "https://github.com/systemd/zram-generator" edition = "2021" exclude = ["tests/07a-mount-point-excl", "tests/10-example"] [dependencies] anyhow = "1.0.12" clap = { version = "4.5", default-features = false, features = ["std", "cargo", "help", "error-context"] } liboverdrop = "0.1.0" rust-ini = ">=0.15, <0.19" log = { version = "0.4", features = ["std"] } fasteval = { version = "0.2", default-features = false } [dev-dependencies] tempfile = "3" fs_extra = "1.3" nix = ">=0.22, <0.24" ctor = "0.2" [profile.release] lto = true opt-level = "z" codegen-units = 1 panic = "abort" zram-generator-1.2.1/LICENSE000064400000000000000000000017771046102023000136030ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. zram-generator-1.2.1/Makefile000064400000000000000000000036361046102023000142320ustar 00000000000000# SPDX-License-Identifier: MIT INSTALL ?= install CARGO ?= cargo CARGOFLAGS ?= RONN ?= ronn PKG_CONFIG ?= pkg-config PREFIX ?= /usr BUILDTYPE ?= release SYSTEMD_UTIL_DIR := $(shell $(PKG_CONFIG) --variable=systemdutildir systemd) SYSTEMD_SYSTEM_UNIT_DIR := $(shell $(PKG_CONFIG) --variable=systemdsystemunitdir systemd) SYSTEMD_SYSTEM_GENERATOR_DIR := $(shell $(PKG_CONFIG) --variable=systemdsystemgeneratordir systemd) export SYSTEMD_UTIL_DIR ifeq ($(BUILDTYPE),release) override CARGOFLAGS := --release $(CARGOFLAGS) endif require_env = @[ -n "$($(1))" ] || { echo "\$$$(1) empty!" >&2; exit 1; } .DEFAULT: build .PHONY: build systemd-service program man check clean install build: program systemd-service ifndef NOMAN build: man endif program: $(call require_env,SYSTEMD_UTIL_DIR) $(CARGO) build $(CARGOFLAGS) systemd-service: $(call require_env,SYSTEMD_SYSTEM_GENERATOR_DIR) sed -e 's,@SYSTEMD_SYSTEM_GENERATOR_DIR@,$(SYSTEMD_SYSTEM_GENERATOR_DIR),' \ units/systemd-zram-setup@.service man: $(RONN) --organization="zram-generator developers" man/*.md check: program $(CARGO) test $(CARGOFLAGS) clippy: $(call require_env,SYSTEMD_UTIL_DIR) $(CARGO) clippy $(CARGOFLAGS) clean: $(CARGO) clean rm -f units/systemd-zram-setup@.service ifndef NOBUILD install: build endif install: $(call require_env,SYSTEMD_SYSTEM_GENERATOR_DIR) $(call require_env,SYSTEMD_SYSTEM_UNIT_DIR) $(call require_env,PREFIX) $(INSTALL) -Dpm755 target/$(BUILDTYPE)/zram-generator -t $(DESTDIR)$(SYSTEMD_SYSTEM_GENERATOR_DIR)/ $(INSTALL) -Dpm644 units/systemd-zram-setup@.service -t $(DESTDIR)$(SYSTEMD_SYSTEM_UNIT_DIR)/ $(INSTALL) -Dpm644 zram-generator.conf.example -t $(DESTDIR)$(PREFIX)/share/doc/zram-generator/ ifndef NOMAN $(INSTALL) -Dpm644 man/zram-generator.8 -t $(DESTDIR)$(PREFIX)/share/man/man8/ $(INSTALL) -Dpm644 man/zram-generator.conf.5 -t $(DESTDIR)$(PREFIX)/share/man/man5/ endif zram-generator-1.2.1/README.md000064400000000000000000000114021046102023000140370ustar 00000000000000# `systemd-zram-setup@.service` generator for zram devices Packaging status This generator provides a simple and fast mechanism to configure swap on `/dev/zram*` devices. The main use case is create **swap** devices, but devices with a file system can be created too, see below. ### Configuration A default config file may be located in /usr. This generator checks the following locations: * `/run/systemd/zram-generator.conf` * `/etc/systemd/zram-generator.conf` * `/usr/local/lib/systemd/zram-generator.conf` * `/usr/lib/systemd/zram-generator.conf` … and the first file found in that list wins. In addition, "drop-ins" will be loaded from `.conf` files in `/etc/systemd/zram-generator.conf.d/`, `/usr/lib/systemd/zram-generator.conf.d/`, etc. The main configuration file is read before any of the drop-ins and has the lowest precedence; entries in the drop-in files override entries in the main configuration file. See systemd.unit(5) for a detailed description of this logic. See `zram-generator.conf.example` for a list of available settings. ### Swap devices Create `/etc/systemd/zram-generator.conf`: ```ini # /etc/systemd/zram-generator.conf [zram0] zram-size = ram / 2 ``` A zram device will be created for each section. No actual configuration is necessary (the default of `zram-size = min(ram / 2, 4096)` will be used unless overriden), but the configuration file with at least one section must exist. ### Mount points ```ini # /etc/systemd/zram-generator.conf [zram1] mount-point = /var/compressed ``` This will set up a /dev/zram1 with ext2 and generate a mount unit for /var/compressed. In case you want this path to be user-writable, since util-linux v2.39 you can use ```ini [zram1] options = X-mount.mode=1777 ``` (and/or the relevant `X.mount.{owner,group}=` arguments, cf. mount(8)). Otherwise, you can use the following "high-quality hack": for the above example, create an override for `systemd-zram-setup@zram1.service`, for example with `systemctl edit`, containing the following (note the sticky bit as required for [/var]/tmp): ```ini [Service] ExecStartPost=/bin/sh -c 'd=$(mktemp -d); mount "$1" "$d"; chmod 1777 "$d"; umount "$d"; rmdir "$d"' _ /dev/%i ``` ### Rust The second purpose of this program is to serve as an example of a systemd generator in rust. ### Installation It is recommended to use an existing package: * Fedora: `sudo dnf install zram-generator-defaults` (or `sudo dnf install zram-generator` to install without the default configuration) * Debian: packages provided by nabijaczleweli, see https://debian.nabijaczleweli.xyz/README. * Arch: `sudo pacman -S zram-generator` (or https://aur.archlinux.org/packages/zram-generator-git/ for the latest git commit) To install directly from sources, execute `make build && sudo make install NOBUILD=true`: * `zram-generator` binary is installed in the systemd system generator directory (usually `/usr/lib/systemd/system-generators/`) * `zram-generator(8)` and `zram-generator.conf(5)` manpages are installed into `/usr/share/man/manN/`, this requires [`ronn`](https://github.com/apjanke/ronn-ng). * `units/systemd-zram-setup@.service` is copied into the systemd system unit directory (usually `/usr/lib/systemd/system/`) * `zram-generator.conf.example` is copied into `/usr/share/doc/zram-generator/` You need though create your own config file at one of the locations listed above. To install and configure with puppet [puppet-zram_generator](https://github.com/voxpupuli/puppet-zram_generator) is available. #### tl;dr - Install `zram-generator` using one of the methods listed above. - Create a `zram-generator.conf` config file. - Run `systemctl daemon-reload` to create new device units. - Run `systemctl start /dev/zram0` (adjust the name as appropriate to match the config). - Call `zramctl` or `swapon` to confirm that the device has been created and is in use. Once installed and configured, the generator will be invoked by systemd early at boot, there is no need to do anything else. ### Testing The tests require either the `zram` module to be loaded, or root to run `modprobe zram`. Set the `ZRAM_GENERATOR_ROOT` environment variable to use that instead of `/` as root. The "{generator}" template in `units/systemd-zram-setup@.service.d/binary-location.conf` can be substituted for a non-standard location of the binary for testing. ### Authors Written by Zbigniew Jędrzejewski-Szmek <>, Igor Raits <>, наб <>, and others. See https://github.com/systemd/zram-generator/graphs/contributors for the full list. zram-generator-1.2.1/man/.gitignore000064400000000000000000000000271046102023000153240ustar 00000000000000/*.[1-8] /*.[1-8].html zram-generator-1.2.1/man/index.txt000064400000000000000000000014121046102023000152030ustar 00000000000000zram-generator(8) zram-generator.8.ronn zram-generator.conf(5) zram-generator.conf.5.ronn modprobe(8) https://man7.org/linux/man-pages/man8/modprobe.8.html proc(5) https://man7.org/linux/man-pages/man5/proc.5.html system(3) https://man7.org/linux/man-pages/man3/system.3.html systemd-detect-virt(1) https://freedesktop.org/software/systemd/man/systemd-detect-virt.html systemd.generator(7) https://freedesktop.org/software/systemd/man/systemd.generator.html systemd.swap(5) https://freedesktop.org/software/systemd/man/systemd.swap.html systemd-makefs(8) https://freedesktop.org/software/systemd/man/systemd-makefs.html systemd.syntax(5) https://freedesktop.org/software/systemd/man/systemd.syntax.html zram-generator-1.2.1/man/zram-generator.conf.md000064400000000000000000000224641046102023000175500ustar 00000000000000 zram-generator.conf(5) -- Systemd unit generator for zram swap devices (configuration) ====================================================================================== ## SYNOPSIS `/usr/lib/systemd/zram-generator.conf`
`/usr/local/lib/systemd/zram-generator.conf`
`/etc/systemd/zram-generator.conf`
`/run/systemd/zram-generator.conf` `/usr/lib/systemd/zram-generator.conf.d/*.conf`
`/usr/local/lib/systemd/zram-generator.conf.d/*.conf`
`/etc/systemd/zram-generator.conf.d/*.conf`
`/run/systemd/zram-generator.conf.d/*.conf` ## DESCRIPTION These files configure devices created by zram-generator(8). See systemd.syntax(5) for a general description of the syntax. ## CONFIGURATION DIRECTORIES AND PRECEDENCE The default configuration doesn't specify any devices. Consult */usr/share/zram-generator/zram-generator.conf.example* for an example configuration file. When packages need to customize the configuration, they can install configuration snippets in */usr/lib/systemd/zram-generator.conf.d/*. Files in */etc/* are reserved for the local administrator, who may use this logic to override the configuration files installed by vendor packages. The main configuration file is read before any of the configuration directories, and has the lowest precedence; entries in a file in any configuration directory override entries in the single configuration file. Files in the *\*.conf.d/* configuration subdirectories are sorted by their filename in lexicographic order, regardless of which of the subdirectories they reside in. When multiple files specify the same option, for options which accept just a single value, the entry in the file with the lexicographically latest name takes precedence. It is recommended to prefix all filenames in those subdirectories with a two-digit number and a dash, to simplify the ordering of the files. To disable a configuration file supplied by the vendor, the recommended way is to place a symlink to */dev/null* in the configuration directory in */etc/*, with the same filename as the vendor configuration file. The generator understands the following option on the kernel command-line: `systemd.zram[=0|1]`. When specified with a true argument (or no argument), the `zram0` device will be created. Default options apply, but may be overridden by configuration on disk if present. When specified with a false argument, no zram devices will be created by the generator. This option thus has higher priority than the configuration files. ## OPTIONS Each device is configured independently in its `[zramN]` section, where N is a nonnegative integer. The global section may contain [DIRECTIVES]. Other sections are ignored. Devices with the final size of *0* will be discarded. * `host-memory-limit`= Sets the upper limit on the total usable RAM (as defined by *MemTotal* in `/proc/meminfo`, confer proc(5)) above which the device will *not* be created. This takes a nonnegative number, representing that limit in megabytes, or the literal string *none*, which can be used to override a limit set earlier. Defaults to *none*. * `zram-size`= Sets the size of the zram device as a function of *MemTotal*, available as the `ram` variable. Additional variables may be provided by [DIRECTIVES]. Arithmetic operators (^%/\*-+), e, π, SI suffixes, log(), int(), ceil(), floor(), round(), abs(), min(), max(), and trigonometric functions are supported. Defaults to *min(ram / 2, 4096)*. * `zram-resident-limit`= Sets the maximum resident memory limit of the zram device (or *0* for no limit) as a function of *MemTotal*, available as the `ram` variable. Same format as `zram-size`. Defaults to *0*. * `compression-algorithm`= Specifies the algorithm used to compress the zram device. This takes a whitespace-separated list string, representing the algorithms to use, and parameters in parenteses.
Consult */sys/block/zram0/comp_algorithm* (and *.../recomp_algorithm*) for a list of currently loaded compression algorithms, but note that additional ones may be loaded on demand. If unset, none will be configured and the kernel's default will be used.
If more than one is given, and recompression is enabled in the kernel, subsequent ones will be set as the recompression algorithms, with decreasing priority. If a compression algorithm is suffixed with a parenthesised comma-separated list of parameters, those are given to `.../algorithm_params` (and `.../recompress`). A parenthesised parameter list *without* a compression algorithm is set as the global recompression parameters. * `writeback-device`= Write incompressible pages, for which no gain was achieved, to the specified device under memory pressure. This corresponds to the */sys/block/zramX/backing_dev* parameter. Takes a path to a block device, like */dev/disk/by-partuuid/2d54ffa0-01* or */dev/zvol/tarta-zoot/swap-writeback*. If unset, none is used, and incompressible pages are kept in RAM. * `swap-priority`= Controls the relative swap priority, a value between -1 and 32767. Higher numbers indicate higher priority. If unset, 100 is used. * `mount-point`= Format the device with a file system (not as swap) and mount this file system over the specified directory. When neither this option nor `fs-type`= is specified, the device will be formatted as swap. Note that the device is temporary: contents will be destroyed automatically after the file system is unmounted (to release the backing memory). * `fs-type`= Specifies how the device shall be formatted. The default is *ext2* if `mount-point` is specified, and *swap* otherwise. (Effectively, the device will be formatted as swap, if neither `fs-type`= nor `mount-point`= are specified.) Note that the device is temporary: contents will be destroyed automatically after the file system is unmounted (to release the backing memory). Also see systemd-makefs(8). * `options`= Sets mount or swapon options. Availability depends on `fs-type`. Defaults to *discard*. ## DIRECTIVES The global section (before any section header) may contain directives in the following form: * `set!`*variable*=*program* *program* is executed by the shell as-if by system(3), its standard output stream parsed as an arithmetic expression (like `zram-size`/`zram-resident-limit`), then the result is remembered into *variable*, usable in later `set!`s and `zram-size`s/`zram-resident-limit`s. ## ENVIRONMENT VARIABLES Setting `ZRAM_GENERATOR_ROOT` during parsing will cause */proc/meminfo* to be read from *$ZRAM_GENERATOR_ROOT/proc/meminfo* instead, and *{/usr/lib,/usr/local/lib,/etc,/run}/systemd/zram-generator.conf* to be read from *$ZRAM_GENERATOR_ROOT/{/usr/lib,/usr/local/lib,/etc,/run}/systemd/zram-generator.conf*. ## EXAMPLES The default configuration will yield the following: zram device size ^ │ 4G>│ ooooooooooooo │ o │ o │ o 2G>│ o │ o │ o 512M>│ o 0───────────────────────> total usable RAM ^ ^ ^ 1G 4G 8G A piecewise-linear size 1:1 for the first 4G, then 1:2 above, up to a max of 32G:
  `zram-size = min(min(ram, 4096) + max(ram - 4096, 0) / 2, 32 * 1024)` zram device size ^ 32G>| oooooooooooooo | o 30G>| o | /=/ | 8G>│ o │ o │ o │ o │ o 4G>│ o │ o │ o 1G>│ o 0───────────────────────────────────||──────────────────────> total usable RAM ^ ^ ^ ^ ^ ^ ^ 1G 4G 8G 12G 56G 60G 64G ## OBSOLETE OPTIONS * `memory-limit`= Compatibility alias for `host-memory-limit`. * `zram-fraction`= Defines the scaling factor of the zram device's size with relation to the total usable RAM. This takes a nonnegative floating-point number representing that factor. Defaulted to *0.5*. Setting this or `max-zram-size` overrides `zram-size`. * `max-zram-size`= Sets the limit on the zram device's size obtained by `zram-fraction`. This takes a nonnegative number, representing that limit in megabytes, or the literal string *none*, which can be used to override a limit set earlier. Defaulted to *4096*. Setting this or `zram-fraction` overrides `zram-size`. ## REPORTING BUGS ## SEE ALSO zram-generator(8), systemd.syntax(5), proc(5) Linux documentation of zram:
and the zram sysfs ABI: `fasteval` documentation for the entire `zram-size` arithmetic DSL: zram-generator-1.2.1/man/zram-generator.md000064400000000000000000000074511046102023000166230ustar 00000000000000 zram-generator(8) -- Systemd unit generator for zram swap devices ================================================================= ## SYNOPSIS `/usr/lib/systemd/system-generators/zram-generator` `TARGET_DIR` [*2RGET_DIR* *3RGET_DIR*]
`/usr/lib/systemd/system-generators/zram-generator` --setup-device `DEVICE`
`/usr/lib/systemd/system-generators/zram-generator` --reset-device `DEVICE` ## DESCRIPTION `zram-generator` is a generator that creates systemd units to format and use compressed RAM devices, either as swap or a file system. The generator will be invoked by systemd early at boot. The generator will then: 1. read configuration files from *{/etc,/lib}/systemd/zram-generator.conf[.d]* (see zram-generator.conf(5) for details); 2. generate systemd.swap(5) and/or systemd.mount(5) units into `TARGET_DIR` and connect them to `swap.target` or `local-fs.target` as appropriate; 3. ensure the `zram` module is loaded and create the requested devices. The generator does nothing if run inside a container (as determined by *systemd-detect-virt(8) --container*). The generator also understands the kernel command-line option `systemd.zram`. See zram-generator.conf(5) for details. Setting the `ZRAM_GENERATOR_ROOT` environment variable makes the generator run in test mode, in which case containerisation is ignored and step `3` is skipped.
For the ramifications of `ZRAM_GENERATOR_ROOT` on config handling, see zram-generator.conf(5). Generated *dev-zramN.swap* units depend on `systemd-zram-setup@zramN.service`, which will: 1. read configuration files from *{/etc,/lib}/systemd/zram-generator.conf[.d]* (see zram-generator.conf(5) for details); 2. set the desired compression algorithm, if any; if the current kernel doesn't understand the specified algorithm, a warning is issued, but execution continues; 3. set the desired blockdev size and format it as swap with *systemd-makefs(8)*. Generated *path-to-mount-point.mount* units depend on `systemd-zram-setup@zramN.service`. The effect is similar to what happens for swap units, but of course they are formatted with a file system. When the unit is stopped, the zram device is reset, freeing memory and allowing the device to be reused. `zram-generator` implements systemd.generator(7). ### Applying config changes This generator is invoked in early boot, and the devices it configures will be created very early too, so the easiest way to apply config changes is to simply reboot the machine. Nevertheless, sometimes it may be useful to add new devices or apply config changes at runtime. Applying new configuration means restarting the units, and that in turn means recreating the zram devices. This means that *file systems are temporarily unmounted and their contents lost*, and *pages are moved out of the compressed swap device* into other memory. If this is acceptable, `systemctl restart systemd-zram-setup@zramN` or `systemctl restart systemd-zram-setup@*` may be used to recreate a specific device or all configured devices. (If the device didn't exist, `restart` will create it.) If the way the device is used (e.g. the mount point or file system type) is changed, `systemctl daemon-reload` needs to be called first to recreate systemd units. If a device or mount point is removed from configuration, the unit should be stopped before calling `daemon-reload`. Otherwise, systemd will not know how to stop the unit properly. ## REPORTING BUGS ## SEE ALSO zram-generator.conf(5), systemd.generator(7), systemd.swap(5) Linux documentation of zram:
and the zram sysfs ABI: zram-generator-1.2.1/src/config.rs000064400000000000000000000547411046102023000151770ustar 00000000000000/* SPDX-License-Identifier: MIT */ use anyhow::{anyhow, Context, Result}; use fasteval::Evaler; use ini::Ini; use log::{info, warn}; use std::borrow::Cow; use std::collections::{BTreeMap, HashMap}; use std::ffi::OsString; use std::fmt; use std::fs; use std::io::{prelude::*, BufReader}; use std::os::unix::process::ExitStatusExt; use std::path::{Component, Path, PathBuf}; use std::process::{Command, Stdio}; const DEFAULT_ZRAM_SIZE: &str = "min(ram / 2, 4096)"; const DEFAULT_RESIDENT_LIMIT: &str = "0"; pub struct Device { pub name: String, pub host_memory_limit_mb: Option, /// Default: `DEFAULT_ZRAM_SIZE` pub zram_size: Option<(String, fasteval::ExpressionI, fasteval::Slab)>, pub compression_algorithms: Algorithms, pub writeback_dev: Option, pub disksize: u64, /// /sys/block/zramX/mem_limit; default: `DEFAULT_RESIDENT_LIMIT` pub zram_resident_limit: Option<(String, fasteval::ExpressionI, fasteval::Slab)>, pub mem_limit: u64, pub swap_priority: i32, /// when set, a mount unit will be created pub mount_point: Option, /// useful mostly for mounts, /// None is the same as "swap" when mount_point is not set pub fs_type: Option, pub options: Cow<'static, str>, /// deprecated, overrides zram_size pub zram_fraction: Option, /// deprecated, overrides zram_size pub max_zram_size_mb: Option>, } impl Device { fn new(name: String) -> Device { Device { name, host_memory_limit_mb: None, zram_size: None, compression_algorithms: Default::default(), writeback_dev: None, disksize: 0, zram_resident_limit: None, mem_limit: 0, swap_priority: 100, mount_point: None, fs_type: None, options: "discard".into(), zram_fraction: None, max_zram_size_mb: None, } } pub fn is_swap(&self) -> bool { self.mount_point.is_none() && (self.fs_type.is_none() || self.fs_type.as_ref().unwrap() == "swap") } fn is_enabled(&self, memtotal_mb: u64) -> bool { match self.host_memory_limit_mb { Some(limit_mb) if limit_mb < memtotal_mb => { info!( "{}: system has too much memory ({:.1}MB), limit is {}MB, ignoring.", self.name, memtotal_mb, self.host_memory_limit_mb.unwrap() ); false } _ => true, } } pub fn effective_fs_type(&self) -> &str { match (self.fs_type.as_ref(), self.is_swap()) { (Some(fs_type), _) => fs_type, (None, true) => "swap", (None, false) => "ext2", } } fn process_size( &self, zram_option: &Option<(String, fasteval::ExpressionI, fasteval::Slab)>, ctx: &mut EvalContext, default_size: f64, label: &str, ) -> Result { Ok((match zram_option { Some(zs) => { zs.1.from(&zs.2.ps) .eval(&zs.2, ctx) .with_context(|| format!("{} {}", self.name, label)) .and_then(|f| { if f >= 0. { Ok(f) } else { Err(anyhow!("{}: {}={} < 0", self.name, label, f)) } })? } None => default_size, } * 1024.0 * 1024.0) as u64) } fn set_disksize_if_enabled(&mut self, ctx: &mut EvalContext) -> Result<()> { if !self.is_enabled(ctx.memtotal_mb) { return Ok(()); } if self.zram_fraction.is_some() || self.max_zram_size_mb.is_some() { // deprecated path let max_mb = self.max_zram_size_mb.unwrap_or(None).unwrap_or(u64::MAX); self.disksize = ((self.zram_fraction.unwrap_or(0.5) * ctx.memtotal_mb as f64) as u64) .min(max_mb) * (1024 * 1024); } else { self.disksize = self.process_size( &self.zram_size, ctx, (ctx.memtotal_mb as f64 / 2.).min(4096.), // DEFAULT_ZRAM_SIZE "zram-size", )?; } self.mem_limit = self.process_size( &self.zram_resident_limit, ctx, 0., // DEFAULT_RESIDENT_LIMIT "zram-resident-limit", )?; Ok(()) } } impl fmt::Display for Device { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}: host-memory-limit={} zram-size={} zram-resident-limit={} compression-algorithm={} writeback-device={} options={}", self.name, OptMB(self.host_memory_limit_mb), self.zram_size .as_ref() .map(|zs| &zs.0[..]) .unwrap_or(DEFAULT_ZRAM_SIZE), self.zram_resident_limit .as_ref() .map(|zs| &zs.0[..]) .unwrap_or(DEFAULT_RESIDENT_LIMIT), self.compression_algorithms, self.writeback_dev.as_deref().unwrap_or_else(|| Path::new("")).display(), self.options )?; if self.zram_fraction.is_some() || self.max_zram_size_mb.is_some() { f.write_str(" (")?; if let Some(zf) = self.zram_fraction { write!(f, "zram-fraction={}", zf)?; } if self.max_zram_size_mb.is_some() { f.write_str(" ")?; } if let Some(mzs) = self.max_zram_size_mb { write!(f, "max-zram-size={}", OptMB(mzs))?; } f.write_str(")")?; } Ok(()) } } struct OptMB(Option); impl fmt::Display for OptMB { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { Some(val) => write!(f, "{}MB", val), None => f.write_str(""), } } } #[derive(Default, Debug, PartialEq, Eq)] pub struct Algorithms { pub compression_algorithms: Vec<(String, String)>, // algorithm, params; first one is real compression, later ones are recompression pub recompression_global: String, // params } impl fmt::Display for Algorithms { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.compression_algorithms[..] { [] => f.write_str("")?, [(first, firstparams), more @ ..] => { f.write_str(first)?; if !firstparams.is_empty() { write!(f, " ({})", firstparams)?; } for (algo, params) in more { write!(f, " then {}", algo)?; if !params.is_empty() { write!(f, " ({})", params)?; } } } } if !self.recompression_global.is_empty() { write!(f, "(global recompress: {})", self.recompression_global)?; } Ok(()) } } struct EvalContext { memtotal_mb: u64, additional: BTreeMap, } impl fasteval::EvalNamespace for EvalContext { fn lookup(&mut self, name: &str, args: Vec, _: &mut String) -> Option { if !args.is_empty() { None } else if name == "ram" { Some(self.memtotal_mb as f64) } else { self.additional.get(name).copied() } } } pub fn read_device(root: &Path, kernel_override: bool, name: &str) -> Result> { let memtotal_mb = get_total_memory_kb(root)? as f64 / 1024.; Ok(read_devices(root, kernel_override, memtotal_mb as u64)? .remove(name) .filter(|dev| dev.disksize > 0)) } pub fn read_all_devices(root: &Path, kernel_override: bool) -> Result> { let memtotal_mb = get_total_memory_kb(root)? as f64 / 1024.; Ok(read_devices(root, kernel_override, memtotal_mb as u64)? .into_iter() .filter(|(_, dev)| dev.disksize > 0) .map(|(_, dev)| dev) .collect()) } fn toplevel_line( path: &Path, k: &str, val: &str, slab: &mut fasteval::Slab, ctx: &mut EvalContext, ) -> Result<()> { let (op, arg) = if let Some(colon) = k.find('!') { k.split_at(colon + 1) } else { warn!( "{}: invalid outside-of-section key {}, ignoring.", path.display(), k ); return Ok(()); }; match op { "set!" => { let out = Command::new("/bin/sh") .args(["-c", "--", val]) .stdin(Stdio::null()) .stderr(Stdio::inherit()) .output() .with_context(|| format!("{}: {}: {}", path.display(), k, val))?; let exit = out .status .code() .unwrap_or_else(|| 128 + out.status.signal().unwrap()); if exit != 0 { warn!("{}: {} exited {}", k, val, exit); } let expr = String::from_utf8(out.stdout) .with_context(|| format!("{}: {}: {}", path.display(), k, val))?; let evalled = fasteval::Parser::new() .parse(&expr, &mut slab.ps) .and_then(|p| p.from(&slab.ps).eval(slab, ctx)) .with_context(|| format!("{}: {}: {}: {}", path.display(), k, val, expr))?; ctx.additional.insert(arg.to_string(), evalled); } _ => warn!( "{}: unknown outside-of-section operation {}, ignoring.", path.display(), op ), } Ok(()) } fn read_devices( root: &Path, kernel_override: bool, memtotal_mb: u64, ) -> Result> { let fragments = locate_fragments(root); if fragments.is_empty() && !kernel_override { info!("No configuration found."); } let mut devices: HashMap = HashMap::new(); let mut slab = fasteval::Slab::new(); let mut ctx = EvalContext { memtotal_mb, additional: BTreeMap::new(), }; for (_, path) in fragments { let ini = Ini::load_from_file(&path)?; for (sname, props) in ini.iter() { let sname = match sname { None => { for (k, v) in props.iter() { toplevel_line(&path, k, v, &mut slab, &mut ctx)?; } continue; } Some(sname) if sname.starts_with("zram") && sname[4..].parse::().is_ok() => { sname.to_string() } Some(sname) => { warn!("{}: Ignoring section \"{}\"", path.display(), sname); continue; } }; let dev = devices .entry(sname.clone()) .or_insert_with(|| Device::new(sname)); for (k, v) in props.iter() { parse_line(dev, k, v)?; } } } if kernel_override { devices .entry("zram0".to_string()) .or_insert_with(|| Device::new("zram0".to_string())); } for dev in devices.values_mut() { dev.set_disksize_if_enabled(&mut ctx)?; } Ok(devices) } fn locate_fragments(root: &Path) -> BTreeMap { let base_dirs = [ root.join("usr/lib"), root.join("usr/local/lib"), root.join("etc"), root.join("run"), // We look at /run to allow temporary overriding // of configuration. There is no expectation of // programatic creation of config there. ]; let mut fragments = liboverdrop::scan(&base_dirs, "systemd/zram-generator.conf.d", &["conf"], true); if let Some(path) = base_dirs .into_iter() .rev() .map(|mut p| { p.push("systemd/zram-generator.conf"); p }) .find(|p| p.exists()) { fragments.insert(OsString::new(), path); // The empty string shall sort earliest } fragments } fn parse_optional_size(val: &str) -> Result> { Ok(if val == "none" { None } else { Some( val.parse() .with_context(|| format!("Failed to parse optional size \"{}\"", val))?, ) }) } fn parse_swap_priority(val: &str) -> Result { let val = val .parse() .with_context(|| format!("Failed to parse priority \"{}\"", val))?; /* See --priority in swapon(8). */ match val { -1..=0x7FFF => Ok(val), _ => Err(anyhow!("Swap priority {} out of range", val)), } } fn verify_mount_point(key: &str, val: &str) -> Result { let path = Path::new(val); if path.is_relative() { return Err(anyhow!("{} {} is not absolute", key, val)); } if path.components().any(|c| c == Component::ParentDir) { return Err(anyhow!("{} {:#?} is not normalized", key, path)); } Ok(path.components().collect()) // normalise away /./ components } fn parse_size_expr( dev: &Device, key: &str, value: &str, ) -> Result<(String, fasteval::ExpressionI, fasteval::Slab)> { let mut sl = fasteval::Slab::new(); Ok(( value.to_string(), fasteval::Parser::new() .parse_noclear(value, &mut sl.ps) .with_context(|| format!("{} {}", key, dev.name))?, sl, )) } fn parse_compression_algorithm_params(whole: &str) -> (String, String) { if let Some(paren) = whole.find('(') { let (algo, mut params) = whole.split_at(paren); params = ¶ms[1..]; if params.ends_with(')') { params = ¶ms[..params.len() - 1]; } (algo.to_string(), params.replace(',', " ")) } else { (whole.to_string(), String::new()) } } fn parse_line(dev: &mut Device, key: &str, value: &str) -> Result<()> { match key { "host-memory-limit" | "memory-limit" => { /* memory-limit is for backwards compat. host-memory-limit name is preferred. */ dev.host_memory_limit_mb = parse_optional_size(value)?; } "zram-size" => { dev.zram_size = Some(parse_size_expr(dev, key, value)?); } "zram-resident-limit" => { dev.zram_resident_limit = Some(parse_size_expr(dev, key, value)?); } "compression-algorithm" => { dev.compression_algorithms = value .split_whitespace() .fold(Default::default(), |mut algos, s| { let (algo, params) = parse_compression_algorithm_params(s); if algo.is_empty() { algos.recompression_global = params; } else { algos.compression_algorithms.push((algo, params)); } algos }); } "writeback-device" => { dev.writeback_dev = Some(verify_mount_point(key, value)?); } "swap-priority" => { dev.swap_priority = parse_swap_priority(value)?; } "mount-point" => { dev.mount_point = Some(verify_mount_point(key, value)?); } "fs-type" => { dev.fs_type = Some(value.to_string()); } "options" => { dev.options = value.to_string().into(); } "zram-fraction" => { /* zram-fraction is for backwards compat. zram-size = is preferred. */ dev.zram_fraction = Some( value .parse() .with_context(|| format!("Failed to parse zram-fraction \"{}\"", value)) .and_then(|f| { if f >= 0. { Ok(f) } else { Err(anyhow!("{}: zram-fraction={} < 0", dev.name, f)) } })?, ); } "max-zram-size" => { /* zram-fraction is for backwards compat. zram-size = is preferred. */ dev.max_zram_size_mb = Some(parse_optional_size(value)?); } _ => { warn!("{}: unknown key {}, ignoring.", dev.name, key); } } Ok(()) } fn _get_total_memory_kb(path: &Path) -> Result { for line in BufReader::new(fs::File::open(path).with_context(|| { format!("Failed to read memory information from {}", path.display()) })?) .lines() { let line = line?; let mut fields = line.split_whitespace(); if let (Some("MemTotal:"), Some(val)) = (fields.next(), fields.next()) { return Ok(val.parse()?); } } Err(anyhow!("Couldn't find MemTotal in {}", path.display())) } fn get_total_memory_kb(root: &Path) -> Result { let path = root.join("proc/meminfo"); _get_total_memory_kb(&path) } fn _kernel_has_option(path: &Path, word: &str) -> Result> { let text = fs::read_to_string(path)?; // Last argument wins Ok(text .split_whitespace() .rev() .filter(|w| w.starts_with(word)) .flat_map(|w| match &w[word.len()..] { "" | "=1" | "=yes" | "=true" | "=on" => Some(true), "=0" | "=no" | "=false" | "=off" => Some(false), _ => None, }) .next()) } pub fn kernel_has_option(root: &Path, word: &str) -> Result> { let path = root.join("proc/cmdline"); _kernel_has_option(&path, word) } pub fn kernel_zram_option(root: &Path) -> Option { match kernel_has_option(root, "systemd.zram") { Ok(r @ Some(true)) | Ok(r @ None) => r, Ok(Some(false)) => { info!("Disabled by systemd.zram option in /proc/cmdline."); Some(false) } Err(e) => { warn!("Failed to parse /proc/cmdline ({}), ignoring.", e); None } } } #[cfg(test)] mod tests { use super::*; fn file_with(data: &[u8]) -> tempfile::NamedTempFile { let mut file = tempfile::NamedTempFile::new().unwrap(); file.write(data).unwrap(); file.flush().unwrap(); file } #[test] fn test_get_total_memory_kb() { let file = file_with( b"\ MemTotal: 8013220 kB MemFree: 721288 kB MemAvailable: 1740336 kB Buffers: 292752 kB ", ); let mem = _get_total_memory_kb(file.path()).unwrap(); assert_eq!(mem, 8013220); } #[test] #[should_panic(expected = "Couldn't find MemTotal")] fn test_get_total_memory_not_found() { let file = file_with( b"\ MemTotala: 8013220 kB aMemTotal: 8013220 kB MemTotal:: 8013220 kB ", ); _get_total_memory_kb(file.path()).unwrap(); } #[test] fn test_kernel_has_option() { let file = file_with(b"foo=1 foo=0 foo=on foo=off foo\n"); assert_eq!(_kernel_has_option(file.path(), "foo").unwrap(), Some(true)); } #[test] fn test_kernel_has_no_option() { let file = file_with( b"\ foo=1 foo=0 ", ); assert_eq!(_kernel_has_option(file.path(), "foo").unwrap(), Some(false)); } #[test] fn test_verify_mount_point() { for e in ["foo/bar", "/foo/../bar", "/foo/.."] { assert!(verify_mount_point("test", e).is_err(), "{}", e); } for (p, o) in [ ("/foobar", "/foobar"), ("/", "/"), ("//", "/"), ("///", "/"), ("/foo/./bar/", "/foo/bar"), ] { assert_eq!( verify_mount_point("test", p).unwrap(), Path::new(o), "{} vs {}", p, o ); } } fn dev_with_zram_size_size(val: Option<&str>, memtotal_mb: u64) -> u64 { let mut dev = Device::new("zram0".to_string()); if let Some(val) = val { parse_line(&mut dev, "zram-size", val).unwrap(); } assert!(dev.is_enabled(memtotal_mb)); dev.set_disksize_if_enabled(&mut EvalContext { memtotal_mb, additional: vec![("two".to_string(), 2.)].into_iter().collect(), }) .unwrap(); dev.disksize } #[test] fn test_eval_size_expression() { assert_eq!( dev_with_zram_size_size(Some("0.5 * ram"), 100), 50 * 1024 * 1024 ); } #[test] fn test_eval_size_expression_with_additional() { assert_eq!( dev_with_zram_size_size(Some("0.5 * ram * two"), 100), 50 * 2 * 1024 * 1024 ); } #[test] fn test_eval_size_expression_500() { assert_eq!( dev_with_zram_size_size(Some("500"), 5000), 500 * 1024 * 1024 ); } #[test] fn test_eval_size_expression_500k() { assert_eq!( dev_with_zram_size_size(Some("500k"), 5000), 500 * 1000 * 1024 * 1024 ); } #[test] fn test_eval_size_expression_32g() { assert_eq!( dev_with_zram_size_size(Some("32G"), 5000), 32 * 1000_000_000 * 1024 * 1024 ); } #[test] fn test_eval_size_expression_default() { assert_eq!(dev_with_zram_size_size(None, 100), 50 * 1024 * 1024); assert_eq!(dev_with_zram_size_size(None, 10000), 4096 * 1024 * 1024); } #[test] fn test_eval_size_expression_default_equivalent() { assert_eq!( dev_with_zram_size_size(Some(DEFAULT_ZRAM_SIZE), 100), 50 * 1024 * 1024 ); assert_eq!( dev_with_zram_size_size(Some(DEFAULT_ZRAM_SIZE), 10000), 4096 * 1024 * 1024 ); } #[test] #[should_panic(expected = "Undefined(\"array\")")] fn test_eval_size_expression_unknown_variable() { dev_with_zram_size_size(Some("array(1,2)"), 100); } #[test] #[should_panic(expected = "zram-size=NaN")] fn test_eval_size_expression_nan() { dev_with_zram_size_size(Some("(ram-100)/0"), 100); } #[test] fn test_eval_size_expression_inf() { assert_eq!(dev_with_zram_size_size(Some("(ram-99)/0"), 100), u64::MAX); // +∞ } #[test] fn test_eval_size_expression_min() { assert_eq!( dev_with_zram_size_size(Some("min(0.5 * ram, 4000)"), 3000), 1500 * 1024 * 1024 ); } } zram-generator-1.2.1/src/generator.rs000064400000000000000000000271431046102023000157140ustar 00000000000000/* SPDX-License-Identifier: MIT */ use crate::config::Device; use anyhow::{anyhow, Context, Result}; use log::{debug, log, warn, Level}; use std::cmp; use std::collections::BTreeSet; use std::fs; use std::io::{self, Write}; use std::os::unix::fs::symlink; use std::path::Path; use std::process::Command; fn make_parent(of: &Path) -> Result<()> { let parent = of .parent() .ok_or_else(|| anyhow!("Couldn't get parent of {}", of.display()))?; fs::create_dir_all(parent)?; Ok(()) } fn make_symlink(dst: &str, src: &Path) -> Result<()> { make_parent(src)?; symlink(dst, src) .with_context(|| format!("Failed to create symlink {}→{}", src.display(), dst))?; Ok(()) } fn virtualization_container() -> Result { let mut child = match Command::new("systemd-detect-virt") .arg("--quiet") .arg("--container") .spawn() { Ok(child) => child, Err(e) => { warn!( "systemd-detect-virt call failed, assuming we're not in a container: {}", e ); return Ok(false); } }; match child.wait() { Ok(status) => Ok(status.success()), Err(e) => Err(anyhow!("systemd-detect-virt call failed: {}", e)), } } fn modprobe(modname: &str, required: bool) { match Command::new("modprobe").arg(modname).status() { Err(e) => { let level = match !required && e.kind() == io::ErrorKind::NotFound { true => Level::Debug, false => Level::Warn, }; log!( level, "modprobe \"{}\" cannot be spawned, ignoring: {}", modname, e ); } Ok(status) => { if !status.success() { warn!("modprobe \"{}\" failed, ignoring: code {}", modname, status); } } }; } pub fn run_generator(devices: &[Device], output_directory: &Path, fake_mode: bool) -> Result<()> { if devices.is_empty() { debug!("No devices configured, exiting."); return Ok(()); } if virtualization_container()? && !fake_mode { debug!("Running in a container, exiting."); return Ok(()); } for device in devices { handle_device(output_directory, device)?; } if !devices.is_empty() && !fake_mode { /* We created some units, let's make sure the module is loaded and the devices exist */ if !Path::new("/sys/class/zram-control").exists() { modprobe("zram", true); } let max_device = devices .iter() .map(|device| { device.name[4..] .parse() .expect("already verified in read_devices()") }) .fold(0, cmp::max); if !Path::new("/dev") .join(format!("zram{}", max_device)) .exists() { while fs::read_to_string("/sys/class/zram-control/hot_add") .context("Adding zram device")? .trim_end() .parse::() .context("Fresh zram device number")? < max_device {} } } let compressors: BTreeSet<_> = devices .iter() .flat_map(|device| { device .compression_algorithms .compression_algorithms .iter() .map(|(a, _)| a.as_ref()) }) .collect(); if !compressors.is_empty() { let proc_crypto = fs::read_to_string("/proc/crypto").unwrap_or_else(|e| { warn!("Failed to read /proc/crypto, proceeding as if empty: {}", e); String::new() }); let known = parse_known_compressors(&proc_crypto); for comp in compressors.difference(&known) { modprobe(&format!("crypto-{}", comp), false); } } Ok(()) } // Returns a list of names of loaded compressors fn parse_known_compressors(proc_crypto: &str) -> BTreeSet<&str> { // Extract algorithm names (this includes non-compression algorithms too) proc_crypto .lines() .filter(|line| line.starts_with("name")) .map(|m| m.rsplit(':').next().unwrap().trim()) .collect() } fn write_contents(output_directory: &Path, filename: &str, contents: &str) -> Result<()> { let path = output_directory.join(filename); make_parent(&path)?; let contents = format!( "\ # Automatically generated by {exe_name} {contents}", exe_name = std::env::current_exe().unwrap().display(), contents = contents ); fs::write(&path, contents).with_context(|| format!("Failed to write {}", path.display())) } fn handle_device(output_directory: &Path, device: &Device) -> Result<()> { if device.is_swap() { handle_zram_swap(output_directory, device) } else { handle_zram_mount_point(output_directory, device) } } fn handle_zram_bindings(output_directory: &Path, device: &Device, specific: &str) -> Result<()> { let wb_unit = device .writeback_dev .as_ref() .map(|wd| unit_name_from_path(wd, ".device")) .unwrap_or_default(); /* systemd-zram-setup@.service. * We use the packaged unit, and only need to provide a small drop-in. */ write_contents( output_directory, &format!("systemd-zram-setup@{}.service.d/bindings.conf", device.name), &format!( "\ [Unit] BindsTo={}{}{}{}{} ", specific, &" "[device.writeback_dev.is_none() as usize..], wb_unit, device .writeback_dev .as_ref() .map(|_| "\nAfter=") .unwrap_or_default(), wb_unit, ), ) } fn handle_zram_swap(output_directory: &Path, device: &Device) -> Result<()> { let swap_name = format!("dev-{}.swap", device.name); debug!( "Creating unit file {} (/dev/{} with {}MB)", swap_name, device.name, device.disksize / 1024 / 1024 ); handle_zram_bindings(output_directory, device, "dev-%i.swap")?; let shutdown_conflicts = if device.writeback_dev.is_some() { // We need to shut down the zram device to disconnect the writeback device. // Once https://github.com/systemd/systemd/issues/35303 is resolved, we // may revisit this and rely on the systemd to pull down the device stack // if appropriate. "Conflicts=shutdown.target\n" } else { "" }; /* dev-zramX.swap */ write_contents( output_directory, &swap_name, &format!( "\ [Unit] Description=Compressed Swap on /dev/{zram_device} Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@{zram_device}.service After=systemd-zram-setup@{zram_device}.service Before=swap.target {shutdown_conflicts} [Swap] What=/dev/{zram_device} Priority={swap_priority} Options={options} ", zram_device = device.name, swap_priority = device.swap_priority, options = device.options.replace('%', "%%"), shutdown_conflicts = shutdown_conflicts, ), )?; /* enablement symlink */ let symlink_path = output_directory.join("swap.target.wants").join(&swap_name); let target_path = format!("../{}", swap_name); make_symlink(&target_path, &symlink_path)?; Ok(()) } /// Path escaping as described in systemd.unit(5) /// /// `/./` components stripped away when parsing `mount-point =` fn unit_name_from_path(path: &Path, suffix: &str) -> String { assert!(path.is_absolute()); let trimmed = path.to_str().unwrap().trim_matches('/'); if trimmed.is_empty() { format!("-{}", suffix) } else { let mut obuf = Vec::with_capacity(path.as_os_str().len() + suffix.len()); let mut just_slash = false; for (i, &b) in trimmed.as_bytes().iter().enumerate() { if b == b'/' && just_slash { continue; } just_slash = b == b'/'; match b { b'/' => obuf.push(b'-'), b'.' if i == 0 => write!(obuf, "\\x{:02x}", b'.').unwrap(), b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b':' | b'_' | b'.' => obuf.push(b), _ => write!(obuf, "\\x{:02x}", b).unwrap(), } } obuf.extend_from_slice(suffix.as_bytes()); String::from_utf8(obuf).unwrap() } } fn handle_zram_mount_point(output_directory: &Path, device: &Device) -> Result<()> { if device.mount_point.is_none() { /* In this case we don't need to generate any units. */ return Ok(()); } let mount_name = &unit_name_from_path(device.mount_point.as_ref().unwrap(), ".mount"); debug!( "Creating unit file {} (/dev/{} with {}MB)", mount_name, device.name, device.disksize / 1024 / 1024 ); handle_zram_bindings(output_directory, device, mount_name)?; write_contents( output_directory, mount_name, &format!( "\ [Unit] Description=Compressed Storage on /dev/{zram_device} Documentation=man:zram-generator(8) man:zram-generator.conf(5) Requires=systemd-zram-setup@{zram_device}.service After=systemd-zram-setup@{zram_device}.service [Mount] What=/dev/{zram_device} Where={mount_point} Options={options} ", zram_device = device.name, mount_point = device.mount_point.as_ref().unwrap().to_str().unwrap(), options = device.options.replace('%', "%%"), ), )?; /* enablement symlink */ let symlink_path = output_directory .join("local-fs.target.wants") .join(mount_name); let target_path = format!("../{}", mount_name); make_symlink(&target_path, &symlink_path)?; Ok(()) } #[cfg(test)] mod tests { use super::*; use std::iter::FromIterator; #[test] fn test_parse_known_compressors() { let data = "\ name : zstd driver : zstd-scomp module : zstd priority : 0 refcnt : 1 selftest : passed internal : no type : scomp name : zstd driver : zstd-generic module : zstd priority : 0 refcnt : 1 selftest : passed internal : no type : compression name : ccm(aes) driver : ccm_base(ctr(aes-aesni),cbcmac(aes-aesni)) module : ccm priority : 300 refcnt : 2 selftest : passed internal : no type : aead async : no geniv : name : ctr(aes) driver : ctr(aes-aesni) module : kernel priority : 300 refcnt : 2 selftest : passed internal : no type : skcipher "; let expected = ["zstd", "ccm(aes)", "ctr(aes)"]; assert_eq!(parse_known_compressors(data), BTreeSet::from_iter(expected)); } #[test] fn test_unit_name_from_path() { assert_eq!( unit_name_from_path(&Path::new("/waldo"), ".mount"), "waldo.mount" ); assert_eq!( unit_name_from_path(&Path::new("/waldo/quuix"), ".mount"), "waldo-quuix.mount" ); assert_eq!( unit_name_from_path(&Path::new("/waldo/quuix/"), ".mount"), "waldo-quuix.mount" ); assert_eq!( unit_name_from_path(&Path::new("/waldo/quuix//"), ".mount"), "waldo-quuix.mount" ); assert_eq!(unit_name_from_path(&Path::new("/"), ".mount"), "-.mount"); assert_eq!(unit_name_from_path(&Path::new("//"), ".mount"), "-.mount"); assert_eq!(unit_name_from_path(&Path::new("///"), ".mount"), "-.mount"); } } zram-generator-1.2.1/src/kernlog.rs000064400000000000000000000044511046102023000153640ustar 00000000000000/* SPDX-License-Identifier: MIT */ //! Logger implementation for low level kernel log (using `/dev/kmsg`) //! //! Borrowed and cut down from https://github.com/kstep/kernlog.rs/pull/2, //! consider merging changes back when fixing something here; //! this automatically falls back to stdout and ignores problems with opening "/dev/kmsg". use std::fs::{File, OpenOptions}; use std::io::{self, Write}; use std::process::id; use std::sync::Mutex; /// Kernel logger implementation pub struct KernelLog { kmsg: Mutex>, maxlevel: log::LevelFilter, } impl KernelLog { /// Create new kernel logger with error level filter pub fn with_level(level: log::LevelFilter) -> KernelLog { KernelLog { kmsg: Mutex::new(OpenOptions::new().write(true).open("/dev/kmsg").ok()), maxlevel: level, } } } fn _write_kmsg(kmsg: &mut File, record: &log::Record) { let level: u8 = match record.level() { log::Level::Error => 3, log::Level::Warn => 4, log::Level::Info => 5, log::Level::Debug => 6, log::Level::Trace => 7, }; let mut buf = Vec::new(); writeln!( buf, "<{}>{}[{}]: {}", level, record.target(), id(), record.args() ) .unwrap(); let _ = kmsg.write(&buf); let _ = kmsg.flush(); } fn _write_stdout(record: &log::Record) { let stdout = io::stdout(); let mut stdout = stdout.lock(); let _ = writeln!(stdout, "{}", record.args()); let _ = stdout.flush(); } impl log::Log for KernelLog { fn enabled(&self, meta: &log::Metadata) -> bool { meta.level() <= self.maxlevel } fn log(&self, record: &log::Record) { if record.level() > self.maxlevel { return; } if let Ok(mut kmsg) = self.kmsg.lock() { let output = kmsg.as_mut(); match output { Some(kmsg) => _write_kmsg(kmsg, record), None => _write_stdout(record), } } } fn flush(&self) {} } /// Setup kernel logger with specified error level as the default logger pub fn init_with_level(level: log::LevelFilter) -> Result<(), log::SetLoggerError> { log::set_boxed_logger(Box::new(KernelLog::with_level(level)))?; log::set_max_level(level); Ok(()) } zram-generator-1.2.1/src/lib.rs000064400000000000000000000001261046102023000144640ustar 00000000000000/* SPDX-License-Identifier: MIT */ pub mod config; pub mod generator; pub mod setup; zram-generator-1.2.1/src/main.rs000064400000000000000000000102251046102023000146430ustar 00000000000000/* SPDX-License-Identifier: MIT */ mod config; mod generator; mod kernlog; mod setup; use anyhow::Result; use log::{info, LevelFilter}; use std::borrow::Cow; use std::env; use std::path::{Path, PathBuf}; #[derive(Debug)] enum Opts { /// Generate units into the directory GenerateUnits(String), /// Set up a single device SetupDevice(String), /// Reset (destroy) a device ResetDevice(String), } #[rustfmt::skip] fn command() -> clap::Command { clap::command!() .override_usage("\ \tzram-generator --setup-device \n\ \tzram-generator --reset-device \n\ \tzram-generator dir1 [dir2 dir3]\ ") .arg( clap::arg!(--"setup-device" "Set up a single device") .conflicts_with("reset-device") ) .arg( clap::arg!(--"reset-device" "Reset (destroy) a device") ) .arg( clap::arg!([dir] "Target directory to write output to and two optional\n\ unused directories to satisfy systemd.generator(5)") .num_args(1..=3) .conflicts_with_all(["setup-device", "reset-device"]) .required_unless_present_any(["setup-device", "reset-device"]) ) .after_help(setup::AFTER_HELP) } fn get_opts() -> Opts { let opts = command().get_matches(); if let Some(val) = opts.get_one::("setup-device") { Opts::SetupDevice(val.clone()) } else if let Some(val) = opts.get_one::("reset-device") { Opts::ResetDevice(val.clone()) } else { let val = opts.get_one::("dir").expect("clap invariant"); Opts::GenerateUnits(val.clone()) } } fn main() -> Result<()> { let (root, have_env_var, log_level) = match env::var_os("ZRAM_GENERATOR_ROOT") { Some(val) => (PathBuf::from(val).into(), true, LevelFilter::Trace), None => (Cow::from(Path::new("/")), false, LevelFilter::Info), }; let _ = kernlog::init_with_level(log_level); let kernel_override = || match config::kernel_zram_option(&root) { Some(false) => { info!("Disabled by kernel cmdline option, exiting."); std::process::exit(0); } None => false, Some(true) => true, }; match get_opts() { Opts::GenerateUnits(target) => { let devices = config::read_all_devices(&root, kernel_override())?; let output_directory = PathBuf::from(target); generator::run_generator(&devices, &output_directory, have_env_var) } Opts::SetupDevice(dev) => { let device = config::read_device(&root, kernel_override(), &dev)?; setup::run_device_setup(device, &dev) } Opts::ResetDevice(dev) => { // We don't read the config here, so that it's possible to remove a device // even after the config has been removed. setup::run_device_reset(&dev) } } } #[cfg(test)] mod tests { use super::*; #[test] fn verify_app() { command().debug_assert(); } #[test] fn parse_setup_device() { let m = command().get_matches_from(vec!["prog", "--setup-device", "/dev/zram1"]); assert_eq!(m.get_one::("setup-device").unwrap(), "/dev/zram1"); } #[test] fn parse_reset_device() { let m = command().get_matches_from(vec!["prog", "--reset-device", "/dev/zram1"]); assert_eq!(m.get_one::("reset-device").unwrap(), "/dev/zram1"); } #[test] fn parse_with_dir() { let m = command().get_matches_from(vec!["prog", "/dir1"]); assert!(m.get_one::("setup-device").is_none()); assert!(m.get_one::("reset-device").is_none()); assert_eq!(m.get_one::("dir").unwrap(), "/dir1"); } #[test] fn parse_with_dirs() { let m = command().get_matches_from(vec!["prog", "/dir1", "/dir2", "/dir3"]); assert!(m.get_one::("setup-device").is_none()); assert!(m.get_one::("reset-device").is_none()); assert_eq!(m.get_one::("dir").unwrap(), "/dir1"); } } zram-generator-1.2.1/src/setup.rs000064400000000000000000000130771046102023000150670ustar 00000000000000/* SPDX-License-Identifier: MIT */ use crate::config::Device; use anyhow::{anyhow, Context, Result}; use log::warn; use std::fs; use std::io::ErrorKind; use std::os::unix::ffi::OsStrExt; use std::os::unix::process::ExitStatusExt; use std::path::Path; use std::process::Command; pub const SYSTEMD_MAKEFS_COMMAND: &str = concat!( env!( "SYSTEMD_UTIL_DIR", "Define $SYSTEMD_UTIL_DIR to the result of \ $(pkg-config --variable=systemdutildir systemd) (e.g. /usr/lib/systemd/)" ), "/systemd-makefs" ); /// A constant string for use in clap --help output. #[rustfmt::skip] pub const AFTER_HELP: &str = concat!( "Uses ", env!("SYSTEMD_UTIL_DIR"), "/systemd-makefs", "." ); pub fn run_device_setup(device: Option, device_name: &str) -> Result<()> { let device = device.ok_or_else(|| anyhow!("Device {} not found", device_name))?; let device_sysfs_path = Path::new("/sys/block").join(device_name); for (prio, (algo, params)) in device .compression_algorithms .compression_algorithms .iter() .enumerate() { let params = if params.is_empty() { None } else { Some(params) }; let (path, data, add_pathdata) = if prio == 0 { ( device_sysfs_path.join("comp_algorithm"), algo, params.as_ref().map(|p| { ( device_sysfs_path.join("algorithm_params"), format!("algo={} {}", algo, p), ) }), ) } else { ( device_sysfs_path.join("recomp_algorithm"), &format!("algo={} priority={}", algo, prio), params.as_ref().map(|p| { ( device_sysfs_path.join("recompress"), format!("{} priority={}", p, prio), ) }), ) }; match fs::write(&path, data) { Ok(_) => { if let Some((add_path, add_data)) = add_pathdata { match fs::write(add_path, add_data) { Ok(_) => {} Err(err) => { warn!( "Warning: algorithm {:?} supplemental data {:?} not written: {}", algo, data, err, ); } } } } Err(err) if err.kind() == ErrorKind::InvalidInput => { warn!( "Warning: algorithm {:?} not recognised; consult {} for a list of available ones", algo, path.display(), ); } Err(err) if err.kind() == ErrorKind::PermissionDenied && prio != 0 => { warn!( "Warning: recompression algorithm {:?} requested but recompression not available ({} doesn't exist)", algo, path.display(), ); } err @ Err(_) => err.with_context(|| { format!( "Failed to configure compression algorithm into {}", path.display() ) })?, } } if let Some(ref wb_dev) = device.writeback_dev { let writeback_path = device_sysfs_path.join("backing_dev"); if writeback_path.exists() { fs::write(&writeback_path, wb_dev.as_os_str().as_bytes()).with_context(|| { format!( "Failed to configure write-back device into {}", writeback_path.display() ) })?; } else { warn!("Warning: writeback-device={} set for {}, but system doesn't support write-back. Ignoring.", writeback_path.display(), device_name) } } let resident_memory = device_sysfs_path.join("mem_limit"); fs::write(&resident_memory, format!("{}", device.mem_limit)).with_context(|| { format!( "Failed to configure resident memory limit into {}", resident_memory.display() ) })?; let disksize_path = device_sysfs_path.join("disksize"); fs::write(&disksize_path, format!("{}", device.disksize)).with_context(|| { format!( "Failed to configure disk size into {}", disksize_path.display() ) })?; let fs_type = device.effective_fs_type(); match Command::new(SYSTEMD_MAKEFS_COMMAND).arg(fs_type).arg(Path::new("/dev").join(device_name)).status() { Ok(status) => match status.code() { Some(0) => Ok(()), Some(code) => Err(anyhow!("{} failed with exit code {}", SYSTEMD_MAKEFS_COMMAND, code)), None => Err(anyhow!("{} terminated by signal {}", SYSTEMD_MAKEFS_COMMAND, status.signal().expect("on unix, status status.code() is None iff status.signal() isn't; \ this expect() will never panic, save for an stdlib bug"))), }, Err(e) => Err(e).with_context(|| { format!( "{} call failed for /dev/{}", SYSTEMD_MAKEFS_COMMAND, device_name ) }), } } pub fn run_device_reset(device_name: &str) -> Result<()> { let reset = Path::new("/sys/block").join(device_name).join("reset"); fs::write(reset, b"1")?; Ok(()) } zram-generator-1.2.1/tests/01-basic/proc/meminfo000064400000000000000000000001241046102023000175400ustar 00000000000000MemTotal: 801322 kB MemFree: 611992 kB MemAvailable: 139764 kB zram-generator-1.2.1/tests/01-basic/run.expected/units/dev-zram0.swap000064400000000000000000000005171046102023000234750ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram0 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram0.service After=systemd-zram-setup@zram0.service Before=swap.target [Swap] What=/dev/zram0 Priority=100 Options=discard zram-generator-1.2.1/tests/01-basic/run.expected/units/swap.target.wants/dev-zram0.swap000064400000000000000000000005171046102023000270670ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram0 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram0.service After=systemd-zram-setup@zram0.service Before=swap.target [Swap] What=/dev/zram0 Priority=100 Options=discard ././@LongLink00006440000000000000000000000150000000000000007767Lustar zram-generator-1.2.1/tests/01-basic/run.expected/units/systemd-zram-setup@zram0.service.d/bindings.confzram-generator-1.2.1/tests/01-basic/run.expected/units/systemd-zram-setup@zram0.service.d/bindings.c000064400000000000000000000001101046102023000314520ustar 00000000000000# Automatically generated by zram-generator [Unit] BindsTo=dev-%i.swap zram-generator-1.2.1/tests/01-basic/usr/lib/systemd/zram-generator.conf000064400000000000000000000000101046102023000240650ustar 00000000000000[zram0] zram-generator-1.2.1/tests/02-zstd/etc/systemd/zram-generator.conf000064400000000000000000000002671046102023000232030ustar 00000000000000set!top = echo 3 set!bottom = echo 4 set!ratio = ! echo top / bottom [zram0] compression-algorithm = zstd host-memory-limit = 2050 zram-resident-limit = 9999 zram-size = ram * ratio zram-generator-1.2.1/tests/02-zstd/proc/meminfo000064400000000000000000000001241046102023000174440ustar 00000000000000MemTotal: 801322 kB MemFree: 611992 kB MemAvailable: 139764 kB zram-generator-1.2.1/tests/02-zstd/run.expected/units/dev-zram0.swap000064400000000000000000000005171046102023000234010ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram0 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram0.service After=systemd-zram-setup@zram0.service Before=swap.target [Swap] What=/dev/zram0 Priority=100 Options=discard zram-generator-1.2.1/tests/02-zstd/run.expected/units/swap.target.wants/dev-zram0.swap000064400000000000000000000005171046102023000267730ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram0 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram0.service After=systemd-zram-setup@zram0.service Before=swap.target [Swap] What=/dev/zram0 Priority=100 Options=discard ././@LongLink00006440000000000000000000000147000000000000007775Lustar zram-generator-1.2.1/tests/02-zstd/run.expected/units/systemd-zram-setup@zram0.service.d/bindings.confzram-generator-1.2.1/tests/02-zstd/run.expected/units/systemd-zram-setup@zram0.service.d/bindings.co000064400000000000000000000001101046102023000315350ustar 00000000000000# Automatically generated by zram-generator [Unit] BindsTo=dev-%i.swap zram-generator-1.2.1/tests/03-too-much-memory/etc/systemd/zram-generator.conf000064400000000000000000000000371046102023000252540ustar 00000000000000[zram0] host-memory-limit=7824 zram-generator-1.2.1/tests/03-too-much-memory/proc/meminfo000064400000000000000000000002141046102023000215220ustar 00000000000000MemTotal: 8013220 kB MemFree: 1856196 kB MemAvailable: 2254912 kB Buffers: 94188 kB Cached: 1532436 kB zram-generator-1.2.1/tests/03-too-much-memory/run.expected/units/.empty000064400000000000000000000000001046102023000241000ustar 00000000000000zram-generator-1.2.1/tests/04-dropins/etc/systemd/zram-generator.conf.d/03-drop3.conf000064400000000000000000000000001046102023000262750ustar 00000000000000zram-generator-1.2.1/tests/04-dropins/etc/systemd/zram-generator.conf.d/04-drop4.conf000064400000000000000000000000651046102023000263120ustar 00000000000000[zram2] zram-size=ram*0.8 swap-priority=200 options= zram-generator-1.2.1/tests/04-dropins/etc/systemd/zram-generator.conf.d/zram-generator.conf000064400000000000000000000002271046102023000277760ustar 00000000000000# This one is empty on purpose. The goal is to make sure that a dropin # with the same name as the main config fragment is not confused with the # it. zram-generator-1.2.1/tests/04-dropins/proc/meminfo000064400000000000000000000001241046102023000201400ustar 00000000000000MemTotal: 801322 kB MemFree: 611992 kB MemAvailable: 139764 kB zram-generator-1.2.1/tests/04-dropins/run.expected/units/dev-zram0.swap000064400000000000000000000005171046102023000240750ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram0 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram0.service After=systemd-zram-setup@zram0.service Before=swap.target [Swap] What=/dev/zram0 Priority=100 Options=discard zram-generator-1.2.1/tests/04-dropins/run.expected/units/dev-zram2.swap000064400000000000000000000005101046102023000240700ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram2 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram2.service After=systemd-zram-setup@zram2.service Before=swap.target [Swap] What=/dev/zram2 Priority=200 Options= zram-generator-1.2.1/tests/04-dropins/run.expected/units/swap.target.wants/dev-zram0.swap000064400000000000000000000005171046102023000274670ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram0 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram0.service After=systemd-zram-setup@zram0.service Before=swap.target [Swap] What=/dev/zram0 Priority=100 Options=discard zram-generator-1.2.1/tests/04-dropins/run.expected/units/swap.target.wants/dev-zram2.swap000064400000000000000000000005101046102023000274620ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram2 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram2.service After=systemd-zram-setup@zram2.service Before=swap.target [Swap] What=/dev/zram2 Priority=200 Options= ././@LongLink00006440000000000000000000000152000000000000007771Lustar zram-generator-1.2.1/tests/04-dropins/run.expected/units/systemd-zram-setup@zram0.service.d/bindings.confzram-generator-1.2.1/tests/04-dropins/run.expected/units/systemd-zram-setup@zram0.service.d/bindings000064400000000000000000000001101046102023000316310ustar 00000000000000# Automatically generated by zram-generator [Unit] BindsTo=dev-%i.swap ././@LongLink00006440000000000000000000000152000000000000007771Lustar zram-generator-1.2.1/tests/04-dropins/run.expected/units/systemd-zram-setup@zram2.service.d/bindings.confzram-generator-1.2.1/tests/04-dropins/run.expected/units/systemd-zram-setup@zram2.service.d/bindings000064400000000000000000000001101046102023000316330ustar 00000000000000# Automatically generated by zram-generator [Unit] BindsTo=dev-%i.swap zram-generator-1.2.1/tests/04-dropins/usr/lib/systemd/zram-generator.conf000064400000000000000000000000101046102023000244650ustar 00000000000000[zram0] zram-generator-1.2.1/tests/04-dropins/usr/lib/systemd/zram-generator.conf.d/01-drop1.conf000064400000000000000000000000371046102023000271070ustar 00000000000000[zram0] host-memory-limit=1234 zram-generator-1.2.1/tests/04-dropins/usr/lib/systemd/zram-generator.conf.d/02-drop2.conf000064400000000000000000000000371046102023000271110ustar 00000000000000[zram0] host-memory-limit=1235 zram-generator-1.2.1/tests/04-dropins/usr/lib/systemd/zram-generator.conf.d/03-drop3.conf000064400000000000000000000000721046102023000271120ustar 00000000000000[zram0] host-memory-limit=1236 [zram1] zram-size=ram*0.7 zram-generator-1.2.1/tests/05-kernel-disabled/proc/cmdline000064400000000000000000000000511046102023000214700ustar 00000000000000asfdasdf=foobar bar=foood systemd.zram=0 zram-generator-1.2.1/tests/05-kernel-disabled/proc/meminfo000064400000000000000000000002141046102023000215100ustar 00000000000000MemTotal: 8013220 kB MemFree: 1856196 kB MemAvailable: 2254912 kB Buffers: 94188 kB Cached: 1532436 kB ././@LongLink00006440000000000000000000000162000000000000007772Lustar zram-generator-1.2.1/tests/05-kernel-disabled/run.expected/units/systemd-zram-setup@zram0.service.d/bindings.confzram-generator-1.2.1/tests/05-kernel-disabled/run.expected/units/systemd-zram-setup@zram0.service.d/000064400000000000000000000001071046102023000314710ustar 00000000000000# Automatically generated by zram-generator [Unit] BindsTo=dev-%i.swap zram-generator-1.2.1/tests/05-kernel-disabled/usr/lib/systemd/zram-generator.conf000064400000000000000000000000101046102023000260350ustar 00000000000000[zram0] zram-generator-1.2.1/tests/06-kernel-enabled/proc/cmdline000064400000000000000000000000201046102023000213100ustar 00000000000000 systemd.zram zram-generator-1.2.1/tests/06-kernel-enabled/proc/meminfo000064400000000000000000000002141046102023000213340ustar 00000000000000MemTotal: 8013220 kB MemFree: 1856196 kB MemAvailable: 2254912 kB Buffers: 94188 kB Cached: 1532436 kB zram-generator-1.2.1/tests/06-kernel-enabled/run.expected/units/dev-zram0.swap000064400000000000000000000005171046102023000252710ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram0 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram0.service After=systemd-zram-setup@zram0.service Before=swap.target [Swap] What=/dev/zram0 Priority=100 Options=discard zram-generator-1.2.1/tests/06-kernel-enabled/run.expected/units/swap.target.wants/dev-zram0.swap000064400000000000000000000005171046102023000306630ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram0 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram0.service After=systemd-zram-setup@zram0.service Before=swap.target [Swap] What=/dev/zram0 Priority=100 Options=discard ././@LongLink00006440000000000000000000000161000000000000007771Lustar zram-generator-1.2.1/tests/06-kernel-enabled/run.expected/units/systemd-zram-setup@zram0.service.d/bindings.confzram-generator-1.2.1/tests/06-kernel-enabled/run.expected/units/systemd-zram-setup@zram0.service.d/b000064400000000000000000000001101046102023000314510ustar 00000000000000# Automatically generated by zram-generator [Unit] BindsTo=dev-%i.swap zram-generator-1.2.1/tests/07-mount-point/etc/systemd/zram-generator.conf000064400000000000000000000003771046102023000245170ustar 00000000000000[zram11] mount-point = /var/compressed fs-type = ext4 [zram12] mount-point = /var/folded fs-type = ext4 options = discard,casefold # systemd.unit(5) example [zram13] mount-point = /foo//bar/baz/ fs-type = ext4 [zram15] mount-point = /// fs-type = ext4 zram-generator-1.2.1/tests/07-mount-point/proc/meminfo000064400000000000000000000001241046102023000207560ustar 00000000000000MemTotal: 801322 kB MemFree: 611992 kB MemAvailable: 139764 kB zram-generator-1.2.1/tests/07-mount-point/run.expected/units/-.mount000064400000000000000000000004461046102023000234330ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Storage on /dev/zram15 Documentation=man:zram-generator(8) man:zram-generator.conf(5) Requires=systemd-zram-setup@zram15.service After=systemd-zram-setup@zram15.service [Mount] What=/dev/zram15 Where=/ Options=discard zram-generator-1.2.1/tests/07-mount-point/run.expected/units/foo-bar-baz.mount000064400000000000000000000004611046102023000253730ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Storage on /dev/zram13 Documentation=man:zram-generator(8) man:zram-generator.conf(5) Requires=systemd-zram-setup@zram13.service After=systemd-zram-setup@zram13.service [Mount] What=/dev/zram13 Where=/foo/bar/baz Options=discard zram-generator-1.2.1/tests/07-mount-point/run.expected/units/local-fs.target.wants/-.mount000064400000000000000000000004461046102023000275530ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Storage on /dev/zram15 Documentation=man:zram-generator(8) man:zram-generator.conf(5) Requires=systemd-zram-setup@zram15.service After=systemd-zram-setup@zram15.service [Mount] What=/dev/zram15 Where=/ Options=discard zram-generator-1.2.1/tests/07-mount-point/run.expected/units/local-fs.target.wants/foo-bar-baz.mount000064400000000000000000000004611046102023000315130ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Storage on /dev/zram13 Documentation=man:zram-generator(8) man:zram-generator.conf(5) Requires=systemd-zram-setup@zram13.service After=systemd-zram-setup@zram13.service [Mount] What=/dev/zram13 Where=/foo/bar/baz Options=discard ././@LongLink00006440000000000000000000000150000000000000007767Lustar zram-generator-1.2.1/tests/07-mount-point/run.expected/units/local-fs.target.wants/var-compressed.mountzram-generator-1.2.1/tests/07-mount-point/run.expected/units/local-fs.target.wants/var-compressed.mo000064400000000000000000000004641046102023000316220ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Storage on /dev/zram11 Documentation=man:zram-generator(8) man:zram-generator.conf(5) Requires=systemd-zram-setup@zram11.service After=systemd-zram-setup@zram11.service [Mount] What=/dev/zram11 Where=/var/compressed Options=discard zram-generator-1.2.1/tests/07-mount-point/run.expected/units/local-fs.target.wants/var-folded.mount000064400000000000000000000004711046102023000314400ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Storage on /dev/zram12 Documentation=man:zram-generator(8) man:zram-generator.conf(5) Requires=systemd-zram-setup@zram12.service After=systemd-zram-setup@zram12.service [Mount] What=/dev/zram12 Where=/var/folded Options=discard,casefold ././@LongLink00006440000000000000000000000157000000000000007776Lustar zram-generator-1.2.1/tests/07-mount-point/run.expected/units/systemd-zram-setup@zram11.service.d/bindings.confzram-generator-1.2.1/tests/07-mount-point/run.expected/units/systemd-zram-setup@zram11.service.d/bin000064400000000000000000000001211046102023000315060ustar 00000000000000# Automatically generated by zram-generator [Unit] BindsTo=var-compressed.mount ././@LongLink00006440000000000000000000000157000000000000007776Lustar zram-generator-1.2.1/tests/07-mount-point/run.expected/units/systemd-zram-setup@zram12.service.d/bindings.confzram-generator-1.2.1/tests/07-mount-point/run.expected/units/systemd-zram-setup@zram12.service.d/bin000064400000000000000000000001151046102023000315120ustar 00000000000000# Automatically generated by zram-generator [Unit] BindsTo=var-folded.mount ././@LongLink00006440000000000000000000000157000000000000007776Lustar zram-generator-1.2.1/tests/07-mount-point/run.expected/units/systemd-zram-setup@zram13.service.d/bindings.confzram-generator-1.2.1/tests/07-mount-point/run.expected/units/systemd-zram-setup@zram13.service.d/bin000064400000000000000000000001161046102023000315140ustar 00000000000000# Automatically generated by zram-generator [Unit] BindsTo=foo-bar-baz.mount ././@LongLink00006440000000000000000000000157000000000000007776Lustar zram-generator-1.2.1/tests/07-mount-point/run.expected/units/systemd-zram-setup@zram15.service.d/bindings.confzram-generator-1.2.1/tests/07-mount-point/run.expected/units/systemd-zram-setup@zram15.service.d/bin000064400000000000000000000001041046102023000315130ustar 00000000000000# Automatically generated by zram-generator [Unit] BindsTo=-.mount zram-generator-1.2.1/tests/07-mount-point/run.expected/units/var-compressed.mount000064400000000000000000000004641046102023000262310ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Storage on /dev/zram11 Documentation=man:zram-generator(8) man:zram-generator.conf(5) Requires=systemd-zram-setup@zram11.service After=systemd-zram-setup@zram11.service [Mount] What=/dev/zram11 Where=/var/compressed Options=discard zram-generator-1.2.1/tests/07-mount-point/run.expected/units/var-folded.mount000064400000000000000000000004711046102023000253200ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Storage on /dev/zram12 Documentation=man:zram-generator(8) man:zram-generator.conf(5) Requires=systemd-zram-setup@zram12.service After=systemd-zram-setup@zram12.service [Mount] What=/dev/zram12 Where=/var/folded Options=discard,casefold zram-generator-1.2.1/tests/08-plain-device/etc/systemd/zram-generator.conf000064400000000000000000000000301046102023000245510ustar 00000000000000[zram11] fs-type = ext2 zram-generator-1.2.1/tests/08-plain-device/proc/meminfo000064400000000000000000000001241046102023000210260ustar 00000000000000MemTotal: 801322 kB MemFree: 611992 kB MemAvailable: 139764 kB zram-generator-1.2.1/tests/08-plain-device/proc/self/mountinfo000064400000000000000000000000701046102023000223430ustar 0000000000000015 1 253:1 / / rw,relatime shared:1 - ext4 /dev/root ro zram-generator-1.2.1/tests/08-plain-device/run.expected/units/.empty000064400000000000000000000000001046102023000234040ustar 00000000000000zram-generator-1.2.1/tests/09-zram-size/etc/systemd/zram-generator.conf000064400000000000000000000002151046102023000241400ustar 00000000000000[zram0] compression-algorithm = zstd(dictionary=/etc/gaming,level=9) (recompargs) host-memory-limit = 2050 zram-size = min(0.75 * ram, 6000) zram-generator-1.2.1/tests/09-zram-size/proc/meminfo000064400000000000000000000001241046102023000204100ustar 00000000000000MemTotal: 801322 kB MemFree: 611992 kB MemAvailable: 139764 kB zram-generator-1.2.1/tests/09-zram-size/run.expected/units/dev-zram0.swap000064400000000000000000000005171046102023000243450ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram0 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram0.service After=systemd-zram-setup@zram0.service Before=swap.target [Swap] What=/dev/zram0 Priority=100 Options=discard zram-generator-1.2.1/tests/09-zram-size/run.expected/units/swap.target.wants/dev-zram0.swap000064400000000000000000000005171046102023000277370ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram0 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram0.service After=systemd-zram-setup@zram0.service Before=swap.target [Swap] What=/dev/zram0 Priority=100 Options=discard ././@LongLink00006440000000000000000000000154000000000000007773Lustar zram-generator-1.2.1/tests/09-zram-size/run.expected/units/systemd-zram-setup@zram0.service.d/bindings.confzram-generator-1.2.1/tests/09-zram-size/run.expected/units/systemd-zram-setup@zram0.service.d/bindin000064400000000000000000000001101046102023000315470ustar 00000000000000# Automatically generated by zram-generator [Unit] BindsTo=dev-%i.swap zram-generator-1.2.1/tests/11-obsolete/etc/systemd/zram-generator.conf000064400000000000000000000003611046102023000240260ustar 00000000000000[zram0] zram-fraction = 0.10 max-zram-size = 2048 memory-limit = 100000 [zram1] host-memory-limit = 2 [zram1] zram-fraction = 0.10 max-zram-size = none memory-limit = none [zram2] zram-fraction = 0.10 max-zram-size = 2048 memory-limit = 1 zram-generator-1.2.1/tests/11-obsolete/proc/meminfo000064400000000000000000000001241046102023000202740ustar 00000000000000MemTotal: 801322 kB MemFree: 611992 kB MemAvailable: 139764 kB zram-generator-1.2.1/tests/11-obsolete/run.expected/units/dev-zram0.swap000064400000000000000000000005171046102023000242310ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram0 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram0.service After=systemd-zram-setup@zram0.service Before=swap.target [Swap] What=/dev/zram0 Priority=100 Options=discard zram-generator-1.2.1/tests/11-obsolete/run.expected/units/dev-zram1.swap000064400000000000000000000005171046102023000242320ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram1 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram1.service After=systemd-zram-setup@zram1.service Before=swap.target [Swap] What=/dev/zram1 Priority=100 Options=discard zram-generator-1.2.1/tests/11-obsolete/run.expected/units/swap.target.wants/dev-zram0.swap000064400000000000000000000005171046102023000276230ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram0 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram0.service After=systemd-zram-setup@zram0.service Before=swap.target [Swap] What=/dev/zram0 Priority=100 Options=discard zram-generator-1.2.1/tests/11-obsolete/run.expected/units/swap.target.wants/dev-zram1.swap000064400000000000000000000005171046102023000276240ustar 00000000000000# Automatically generated by zram-generator [Unit] Description=Compressed Swap on /dev/zram1 Documentation=man:zram-generator(8) man:zram-generator.conf(5) DefaultDependencies=no Requires=systemd-zram-setup@zram1.service After=systemd-zram-setup@zram1.service Before=swap.target [Swap] What=/dev/zram1 Priority=100 Options=discard ././@LongLink00006440000000000000000000000153000000000000007772Lustar zram-generator-1.2.1/tests/11-obsolete/run.expected/units/systemd-zram-setup@zram0.service.d/bindings.confzram-generator-1.2.1/tests/11-obsolete/run.expected/units/systemd-zram-setup@zram0.service.d/binding000064400000000000000000000001101046102023000316020ustar 00000000000000# Automatically generated by zram-generator [Unit] BindsTo=dev-%i.swap ././@LongLink00006440000000000000000000000153000000000000007772Lustar zram-generator-1.2.1/tests/11-obsolete/run.expected/units/systemd-zram-setup@zram1.service.d/bindings.confzram-generator-1.2.1/tests/11-obsolete/run.expected/units/systemd-zram-setup@zram1.service.d/binding000064400000000000000000000001101046102023000316030ustar 00000000000000# Automatically generated by zram-generator [Unit] BindsTo=dev-%i.swap zram-generator-1.2.1/tests/test-invocations.sh000075500000000000000000000011761046102023000176010ustar 00000000000000# SPDX-License-Identifier: MIT set -e program=${1:?} dir=$(mktemp -d 'zram-test.XXXXXX') trap 'rm -r $dir' EXIT set -x $program --help $program --version # This should pass mkdir $dir/{a,b,c} $program $dir/{a,b,c} # Those should fail with option parsing error (2) set +e $program dir1 dir2 dir3 dir4 ret=$? set -e test $ret -eq 2 set +e $program dir1 --setup-device ret=$? set -e test $ret -eq 2 # Those should fail because the device doesn't exist (1) set +e $program --setup-device /no/such/file/or/directory ret=$? set -e test $ret -eq 1 set +e $program --reset-device /no/such/file/or/directory ret=$? set -e test $ret -eq 1 zram-generator-1.2.1/tests/test_cases.rs000064400000000000000000000276231046102023000164410ustar 00000000000000/* SPDX-License-Identifier: MIT */ use zram_generator::{config, generator}; use anyhow::Result; use fs_extra::dir::{copy, CopyOptions}; use std::env; use std::ffi::OsString; use std::fs; use std::io::{self, Write}; use std::os::unix::ffi::OsStringExt; use std::path::Path; use std::process::{exit, Command}; use tempfile::TempDir; #[ctor::ctor] fn unshorn() { use nix::{errno, mount, sched, unistd}; use std::os::unix::fs::symlink; let (uid, gid) = (unistd::geteuid(), unistd::getegid()); if !uid.is_root() { match sched::unshare(sched::CloneFlags::CLONE_NEWUSER) { Err(errno::Errno::EPERM) => { eprintln!("unshare(NEWUSER) forbidden and not running as root: skipping tests"); exit(0); } r => r.expect("unshare(NEWUSER)"), } fs::write("/proc/self/setgroups", b"deny").unwrap(); fs::write("/proc/self/uid_map", format!("0 {} 1", uid)).unwrap(); fs::write("/proc/self/gid_map", format!("0 {} 1", gid)).unwrap(); } sched::unshare(sched::CloneFlags::CLONE_NEWNS).expect("unshare(NEWNS)"); mount::mount::<_, _, str, str>( Some("none"), "/", None, mount::MsFlags::MS_REC | mount::MsFlags::MS_PRIVATE, None, ) .unwrap(); mount::mount::(None, "/proc", Some("tmpfs"), mount::MsFlags::empty(), None) .unwrap(); fs::create_dir("/proc/self").unwrap(); symlink("zram-generator", "/proc/self/exe").unwrap(); let mut path = env::var_os("PATH") .map(|p| p.to_os_string().into_vec()) .unwrap_or(b"/usr/bin:/bin".to_vec()); // _PATH_DEFPATH path.insert(0, b':'); for &b in "tests/10-example/bin".as_bytes().into_iter().rev() { path.insert(0, b); } env::set_var("PATH", OsString::from_vec(path)); } fn prepare_directory(srcroot: &Path) -> Result { let rootdir = TempDir::new()?; let root = rootdir.path(); let opts = CopyOptions::new(); for p in ["etc", "usr", "proc"] .iter() .map(|p| srcroot.join(p)) .filter(|p| p.exists()) { copy(p, root, &opts)?; } let output_directory = root.join("run/units"); fs::create_dir_all(output_directory)?; Ok(rootdir) } fn test_generation(path: &str) -> Result> { let srcroot = Path::new(path); let rootdir = prepare_directory(&srcroot)?; let root = rootdir.path(); let kernel_override = match config::kernel_zram_option(root) { Some(true) => true, Some(false) => { return Ok(vec![]); } _ => false, }; let devices = config::read_all_devices(root, kernel_override)?; let output_directory = root.join("run/units"); generator::run_generator(&devices, &output_directory, true)?; // Compare output directory to expected value. // ExecStart lines include the full path to the generating binary, // so exclude them from comparison. let diff = Command::new("diff") .arg("-u") .arg("--recursive") .arg("--exclude=.empty") .arg(srcroot.join("run.expected")) .arg(root.join("run")) .output()?; for (h, d) in [("stdout", &diff.stdout), ("stderr", &diff.stderr)] { if !d.is_empty() { println!("{}:{}", h, String::from_utf8_lossy(d)); } } assert!(diff.status.success()); Ok(devices) } fn z_s_name(zram_size: &(String, fasteval::ExpressionI, fasteval::Slab)) -> &str { &zram_size.0 } #[test] fn test_01_basic() { let devices = test_generation("tests/01-basic").unwrap(); assert_eq!(devices.len(), 1); let d = &devices[0]; assert!(d.is_swap()); assert_eq!(d.host_memory_limit_mb, None); assert_eq!(d.zram_size.as_ref().map(z_s_name), None); assert_eq!(d.options, "discard"); assert_eq!(d.disksize, 391 * 1024 * 1024); assert_eq!(d.mem_limit, 0); } #[test] fn test_02_zstd() { let devices = test_generation("tests/02-zstd").unwrap(); assert_eq!(devices.len(), 1); let d = &devices[0]; assert!(d.is_swap()); assert_eq!(d.host_memory_limit_mb, Some(2050)); assert_eq!(d.zram_size.as_ref().map(z_s_name), Some("ram * ratio")); assert_eq!( d.compression_algorithms, config::Algorithms { compression_algorithms: vec![("zstd".into(), "".into())], ..Default::default() } ); assert_eq!(d.options, "discard"); assert_eq!(d.disksize, 782 * 1024 * 1024 * 3 / 4); assert_eq!(d.mem_limit, 9999 * 1024 * 1024); } #[test] fn test_03_too_much_memory() { let devices = test_generation("tests/03-too-much-memory").unwrap(); assert_eq!(devices.len(), 0); } #[test] fn test_04_dropins() { let devices = test_generation("tests/04-dropins").unwrap(); assert_eq!(devices.len(), 2); for d in &devices { assert!(d.is_swap()); match &d.name[..] { "zram0" => { assert_eq!(d.host_memory_limit_mb, Some(1235)); assert_eq!(d.zram_size.as_ref().map(z_s_name), None); assert_eq!(d.options, "discard"); assert_eq!(d.disksize, 782 * 1024 * 1024 / 2); assert_eq!(d.mem_limit, 0); } "zram2" => { assert_eq!(d.host_memory_limit_mb, None); assert_eq!(d.zram_size.as_ref().map(z_s_name), Some("ram*0.8")); assert_eq!(d.options, ""); assert_eq!(d.disksize, 782 * 1024 * 1024 * 8 / 10); assert_eq!(d.mem_limit, 0); } _ => panic!("Unexpected device {}", d), } } } #[test] fn test_05_kernel_disabled() { let devices = test_generation("tests/05-kernel-disabled").unwrap(); assert_eq!(devices.len(), 0); } #[test] fn test_06_kernel_enabled() { let devices = test_generation("tests/06-kernel-enabled").unwrap(); assert_eq!(devices.len(), 1); let d = &devices[0]; assert!(d.is_swap()); assert_eq!(d.host_memory_limit_mb, None); assert_eq!(d.zram_size.as_ref().map(z_s_name), None); assert_eq!(d.options, "discard"); } #[test] fn test_07_mount_point() { let devices = test_generation("tests/07-mount-point").unwrap(); assert_eq!(devices.len(), 4); test_07_devices(devices); } /// cargo-package refuses to pack files with `\`s in them, /// so we split them off to be able to push to crates.io #[test] fn test_07a_mount_point_excl() { if !Path::new("tests/07a-mount-point-excl").exists() { io::stdout() .write_all(b"07a-mount-point-excl doesn't exist: assuming package, skipping\n") .unwrap(); return; } let devices = test_generation("tests/07a-mount-point-excl").unwrap(); assert_eq!(devices.len(), 1); test_07_devices(devices); } fn test_07_devices(devices: Vec) { for d in &devices { assert!(!d.is_swap()); assert_eq!(d.host_memory_limit_mb, None); assert_eq!(d.zram_size.as_ref().map(z_s_name), None); assert_eq!(d.fs_type.as_ref().unwrap(), "ext4"); assert_eq!(d.effective_fs_type(), "ext4"); match &d.name[..] { "zram11" => { assert_eq!( d.mount_point.as_ref().unwrap(), Path::new("/var/compressed") ); assert_eq!(d.options, "discard"); } "zram12" => { assert_eq!(d.mount_point.as_ref().unwrap(), Path::new("/var/folded")); assert_eq!(d.options, "discard,casefold"); } "zram13" => { assert_eq!(d.mount_point.as_ref().unwrap(), Path::new("/foo//bar/baz/")); assert_eq!(d.options, "discard"); } "zram14" => { assert_eq!(d.mount_point.as_ref().unwrap(), Path::new("/.żupan-ci3pły")); assert_eq!(d.options, "discard"); } "zram15" => { assert_eq!(d.mount_point.as_ref().unwrap(), Path::new("///")); assert_eq!(d.options, "discard"); } _ => panic!("Unexpected device {}", d), } } } #[test] fn test_08_plain_device() { let devices = test_generation("tests/08-plain-device").unwrap(); assert_eq!(devices.len(), 1); let d = &devices[0]; assert!(!d.is_swap()); assert_eq!(d.host_memory_limit_mb, None); assert_eq!(d.zram_size.as_ref().map(z_s_name), None); assert!(d.mount_point.is_none()); assert_eq!(d.fs_type.as_ref().unwrap(), "ext2"); assert_eq!(d.effective_fs_type(), "ext2"); assert_eq!(d.options, "discard"); } #[test] fn test_09_zram_size() { let devices = test_generation("tests/09-zram-size").unwrap(); assert_eq!(devices.len(), 1); let d = &devices[0]; assert!(d.is_swap()); assert_eq!(d.host_memory_limit_mb, Some(2050)); assert_eq!( d.zram_size.as_ref().map(z_s_name), Some("min(0.75 * ram, 6000)") ); assert_eq!( d.compression_algorithms, config::Algorithms { compression_algorithms: vec![("zstd".into(), "dictionary=/etc/gaming level=9".into())], recompression_global: "recompargs".into() } ); } #[test] fn test_10_example() { if !Path::new("tests/10-example").exists() { io::stdout() .write_all(b"10-example doesn't exist: assuming package, skipping\n") .unwrap(); return; } let devices = test_generation("tests/10-example").unwrap(); assert_eq!(devices.len(), 2); for d in &devices { match d.name.as_str() { "zram0" => { assert!(d.is_swap()); assert_eq!(d.host_memory_limit_mb, Some(9048)); assert_eq!( d.zram_size.as_ref().map(z_s_name), Some("min(ram / 10, 2048)") ); assert_eq!( d.compression_algorithms, config::Algorithms { compression_algorithms: vec![ ("lzo-rle".into(), "".into()), ("zstd".into(), "level=3".into()) ], recompression_global: "type=idle".into(), } ); assert_eq!(d.options, ""); assert_eq!( d.zram_resident_limit.as_ref().map(z_s_name), Some("maxhotplug * 3/4") ); assert_eq!(d.disksize, 782 * 1024 * 1024 / 10); // This is the combination of tests/10-example/bin/xenstore-read and // zram-resident-limit= in tests/10-example/etc/systemd/zram-generator.conf. assert_eq!(d.mem_limit, 8 * 1024 * 1024 * 1024 * 3 / 4); } "zram1" => { assert_eq!(d.fs_type.as_ref().unwrap(), "ext2"); assert_eq!(d.effective_fs_type(), "ext2"); assert_eq!(d.zram_size.as_ref().map(z_s_name), Some("ram / 10")); assert_eq!(d.options, "discard"); assert_eq!(d.disksize, 782 * 1024 * 1024 / 10); assert_eq!(d.mem_limit, 0); } _ => panic!("Unexpected device {}", d), } } } #[test] fn test_11_obsolete() { let devices = test_generation("tests/11-obsolete").unwrap(); assert_eq!(devices.len(), 2); for d in &devices { assert!(d.is_swap()); assert_eq!(d.options, "discard"); match d.name.as_str() { "zram0" => { assert_eq!(d.host_memory_limit_mb, Some(100000)); assert_eq!(d.zram_fraction, Some(0.1)); assert_eq!(d.max_zram_size_mb, Some(Some(2048))); } "zram1" => { assert_eq!(d.host_memory_limit_mb, None); assert_eq!(d.zram_fraction, Some(0.1)); assert_eq!(d.max_zram_size_mb, Some(None)); } _ => panic!("Unexpected device {}", d), } } } zram-generator-1.2.1/units/.gitignore000064400000000000000000000000351046102023000157120ustar 00000000000000/systemd-zram-setup@.service zram-generator-1.2.1/units/systemd-zram-setup@.service.d/binary-location.conf000064400000000000000000000002751046102023000254470ustar 00000000000000# SPDX-License-Identifier: MIT # This file is part of the zram-generator project # https://github.com/systemd/zram-generator [Service] ExecStart= ExecStart={generator} --setup-device '%i' zram-generator-1.2.1/units/systemd-zram-setup@.service.in000064400000000000000000000007301046102023000216100ustar 00000000000000# SPDX-License-Identifier: MIT # This file is part of the zram-generator project # https://github.com/systemd/zram-generator [Unit] Description=Create swap on /dev/%i Documentation=man:zram-generator(8) man:zram-generator.conf(5) After=dev-%i.device DefaultDependencies=false [Service] Type=oneshot RemainAfterExit=yes ExecStart=@SYSTEMD_SYSTEM_GENERATOR_DIR@/zram-generator --setup-device '%i' ExecStop=@SYSTEMD_SYSTEM_GENERATOR_DIR@/zram-generator --reset-device '%i' zram-generator-1.2.1/zram-generator.conf.example000064400000000000000000000050771046102023000200310ustar 00000000000000# This file is part of the zram-generator project # https://github.com/systemd/zram-generator # At the top level, a set!variable = program # directive executes /bin/sh -c program, # parses the output as an expression, and remembers it in variable, # usable in later set! and zram-size/zram-resident-limit. set!maxhotplug = xenstore-read /local/domain/$(xenstore-read domid)/memory/hotplug-max [zram0] # This section describes the settings for /dev/zram0. # # The maximum amount of memory (in MiB). If the machine has more RAM # than this, zram device will not be created. # # "host-memory-limit = none" may be used to disable this limit. This # is also the default. host-memory-limit = 9048 # The size of the zram device, as a function of MemTotal, both in MB. # For example, if the machine has 1 GiB, and zram-size=ram/4, # then the zram device will have 256 MiB. # Fractions in the range 0.1–0.5 are recommended. # # The default is "min(ram / 2, 4096)". zram-size = min(ram / 10, 2048) # The maximum memory resident for this zram device, as a function of MemTotal, # both in MB. # For example, if the machine has 1 GiB, and zram-resident-limit=ram/8, # then this device will not consume more than 128 MiB. # # 0 means no limit; this is the default. zram-resident-limit = maxhotplug * 3/4 # The compression algorithm to use for the zram device, # or leave unspecified to keep the kernel default. # # Subsequent algorithms are used for recompression. # Comma-separated parameters may be specified in parentheses. # # Parameters without a compression algorithm are set as # global recompression parameters. compression-algorithm = lzo-rle zstd(level=3) (type=idle) # By default, file systems and swap areas are trimmed on-the-go # by setting "discard". # Setting this to the empty string clears the option. options = # Write incompressible pages to this device, # as there's no gain from keeping them in RAM writeback-device = /dev/zvol/tarta-zoot/swap-writeback # The following options are deprecated, and override zram-size. # These values would be equivalent to the zram-size setting above. #zram-fraction = 0.10 #max-zram-size = 2048 [zram1] # This section describes the settings for /dev/zram1. # # host-memory-limit is not specified, so this device will always be created. # Size the device to a tenth of RAM. zram-size = ram / 10 # The file system to put on the device. If not specified, ext2 will be used. fs-type = ext2 # Where to mount the file system. If a mount point is not specified, # the device will be initialized, but will not be used for anything. mount-point = /run/compressed-mount-point