nixos/config/sysfs: init module

Changed files
+305
nixos
+266
nixos/modules/config/sysfs.nix
···
+
{
+
lib,
+
config,
+
utils,
+
pkgs,
+
...
+
}:
+
+
let
+
inherit (lib)
+
all
+
any
+
concatLines
+
concatStringsSep
+
escapeShellArg
+
flatten
+
floatToString
+
foldl'
+
head
+
isAttrs
+
isDerivation
+
isFloat
+
isList
+
length
+
listToAttrs
+
match
+
mapAttrsToList
+
nameValuePair
+
removePrefix
+
tail
+
throwIf
+
;
+
+
inherit (lib.options)
+
showDefs
+
showOption
+
;
+
+
inherit (lib.strings)
+
escapeC
+
isConvertibleWithToString
+
;
+
+
inherit (lib.path.subpath) join;
+
+
inherit (utils) escapeSystemdPath;
+
+
cfg = config.boot.kernel.sysfs;
+
+
sysfsAttrs = with lib.types; nullOr (either sysfsValue (attrsOf sysfsAttrs));
+
sysfsValue = lib.mkOptionType {
+
name = "sysfs value";
+
description = "sysfs attribute value";
+
descriptionClass = "noun";
+
check = v: isConvertibleWithToString v;
+
merge =
+
loc: defs:
+
if length defs == 1 then
+
(head defs).value
+
else
+
(foldl' (
+
first: def:
+
# merge definitions if they produce the same value string
+
throwIf (mkValueString first.value != mkValueString def.value)
+
"The option \"${showOption loc}\" has conflicting definition values:${
+
showDefs [
+
first
+
def
+
]
+
}"
+
first
+
) (head defs) (tail defs)).value;
+
};
+
+
mapAttrsToListRecursive =
+
fn: set:
+
let
+
recurse =
+
p: v:
+
if isAttrs v && !isDerivation v then mapAttrsToList (n: v: recurse (p ++ [ n ]) v) v else fn p v;
+
in
+
flatten (recurse [ ] set);
+
+
mkPath = p: "/sys" + removePrefix "." (join p);
+
hasGlob = p: any (n: match ''(.*[^\\])?[*?[].*'' n != null) p;
+
+
mkValueString =
+
v:
+
# true will be converted to "1" by toString, saving one branch
+
if v == false then
+
"0"
+
else if isFloat v then
+
floatToString v # warn about loss of precision
+
else if isList v then
+
concatStringsSep "," (map mkValueString v)
+
else
+
toString v;
+
+
# escape whitespace and linebreaks, as well as the escape character itself,
+
# to ensure that field boundaries are always preserved
+
escapeTmpfiles = escapeC [
+
"\t"
+
"\n"
+
"\r"
+
" "
+
"\\"
+
];
+
+
tmpfiles = pkgs.runCommand "nixos-sysfs-tmpfiles.d" { } (
+
''
+
mkdir "$out"
+
''
+
+ concatLines (
+
mapAttrsToListRecursive (
+
p: v:
+
let
+
path = mkPath p;
+
in
+
if v == null then
+
[ ]
+
else
+
''
+
printf 'w %s - - - - %s\n' \
+
${escapeShellArg (escapeTmpfiles path)} \
+
${escapeShellArg (escapeTmpfiles (mkValueString v))} \
+
>"$out"/${escapeShellArg (escapeSystemdPath path)}.conf
+
''
+
) cfg
+
)
+
);
+
in
+
{
+
options = {
+
boot.kernel.sysfs = lib.mkOption {
+
type = lib.types.submodule {
+
freeformType = lib.types.attrsOf sysfsAttrs // {
+
description = "nested attribute set of null or sysfs attribute values";
+
};
+
};
+
+
description = ''
+
sysfs attributes to be set as soon as they become available.
+
+
Attribute names represent path components in the sysfs filesystem and
+
cannot be `.` or `..` nor contain any slash character (`/`).
+
+
Names may contain shell‐style glob patterns (`*`, `?` and `[…]`)
+
matching a single path component, these should however be used with
+
caution, as they may produce unexpected results if attribute paths
+
overlap.
+
+
Values will be converted to strings, with list elements concatenated
+
with commata and booleans converted to numeric values (`0` or `1`).
+
+
`null` values are ignored, allowing removal of values defined in other
+
modules, as are empty attribute sets.
+
+
List values defined in different modules will _not_ be concatenated.
+
+
This option may only be used for attributes which can be set
+
idempotently, as the configured values might be written more than once.
+
'';
+
+
default = { };
+
+
example = lib.literalExpression ''
+
{
+
# enable transparent hugepages with deferred defragmentaion
+
kernel.mm.transparent_hugepage = {
+
enabled = "always";
+
defrag = "defer";
+
shmem_enabled = "within_size";
+
};
+
+
devices.system.cpu = {
+
# configure powesave frequency governor for all CPUs
+
# the [0-9]* glob pattern ensures that other paths
+
# like cpufreq or cpuidle are not matched
+
"cpu[0-9]*" = {
+
scaling_governor = "powersave";
+
energy_performance_preference = 8;
+
};
+
+
# disable frequency boost
+
intel_pstate.no_turbo = true;
+
};
+
}
+
'';
+
};
+
};
+
+
config = lib.mkIf (cfg != { }) {
+
systemd = {
+
paths =
+
{
+
"nixos-sysfs@" = {
+
description = "/%I attribute watcher";
+
pathConfig.PathExistsGlob = "/%I";
+
unitConfig.DefaultDependencies = false;
+
};
+
}
+
// listToAttrs (
+
mapAttrsToListRecursive (
+
p: v:
+
if v == null then
+
[ ]
+
else
+
nameValuePair "nixos-sysfs@${escapeSystemdPath (mkPath p)}" {
+
overrideStrategy = "asDropin";
+
wantedBy = [ "sysinit.target" ];
+
before = [ "sysinit.target" ];
+
}
+
) cfg
+
);
+
+
services."nixos-sysfs@" = {
+
description = "/%I attribute setter";
+
+
unitConfig = {
+
DefaultDependencies = false;
+
AssertPathIsMountPoint = "/sys";
+
AssertPathExistsGlob = "/%I";
+
};
+
+
serviceConfig = {
+
Type = "oneshot";
+
RemainAfterExit = true;
+
+
# while we could be tempted to use simple shell script to set the
+
# sysfs attributes specified by the path or glob pattern, it is
+
# almost impossible to properly escape a glob pattern so that it
+
# can be used safely in a shell script
+
ExecStart = "${lib.getExe' config.systemd.package "systemd-tmpfiles"} --prefix=/sys --create ${tmpfiles}/%i.conf";
+
+
# hardening may be overkill for such a simple and short‐lived
+
# service, the following settings would however be suitable to deny
+
# access to anything but /sys
+
#ProtectProc = "noaccess";
+
#ProcSubset = "pid";
+
#ProtectSystem = "strict";
+
#PrivateDevices = true;
+
#SystemCallErrorNumber = "EPERM";
+
#SystemCallFilter = [
+
# "@basic-io"
+
# "@file-system"
+
#];
+
};
+
};
+
};
+
+
warnings = mapAttrsToListRecursive (
+
p: v:
+
if hasGlob p then
+
"Attribute path \"${concatStringsSep "." p}\" contains glob patterns. Please ensure that it does not overlap with other paths."
+
else
+
[ ]
+
) cfg;
+
+
assertions = mapAttrsToListRecursive (p: v: {
+
assertion = all (n: match ''(\.\.?|.*/.*)'' n == null) p;
+
message = "Attribute path \"${concatStringsSep "." p}\" has invalid components.";
+
}) cfg;
+
};
+
+
meta.maintainers = with lib.maintainers; [ mvs ];
+
}
+1
nixos/modules/module-list.nix
···
./config/stub-ld.nix
./config/swap.nix
./config/sysctl.nix
+
./config/sysfs.nix
./config/system-environment.nix
./config/system-path.nix
./config/terminfo.nix
+1
nixos/tests/all-tests.nix
···
syncthing-folders = runTest ./syncthing-folders.nix;
syncthing-relay = runTest ./syncthing-relay.nix;
sysinit-reactivation = runTest ./sysinit-reactivation.nix;
+
sysfs = runTest ./sysfs.nix;
systemd = runTest ./systemd.nix;
systemd-analyze = runTest ./systemd-analyze.nix;
systemd-binfmt = handleTestOn [ "x86_64-linux" ] ./systemd-binfmt.nix { };
+37
nixos/tests/sysfs.nix
···
+
{ lib, ... }:
+
+
{
+
name = "sysfs";
+
meta.maintainers = with lib.maintainers; [ mvs ];
+
+
nodes.machine = {
+
boot.kernel.sysfs = {
+
kernel.mm.transparent_hugepage = {
+
enabled = "always";
+
defrag = "defer";
+
shmem_enabled = "within_size";
+
};
+
+
block."*".queue.scheduler = "none";
+
};
+
};
+
+
testScript =
+
{ nodes, ... }:
+
let
+
inherit (nodes.machine.boot.kernel) sysfs;
+
in
+
''
+
from shlex import quote
+
+
def check(filename, contents):
+
machine.succeed('grep -F -q {} {}'.format(quote(contents), quote(filename)))
+
+
check('/sys/kernel/mm/transparent_hugepage/enabled',
+
'[${sysfs.kernel.mm.transparent_hugepage.enabled}]')
+
check('/sys/kernel/mm/transparent_hugepage/defrag',
+
'[${sysfs.kernel.mm.transparent_hugepage.defrag}]')
+
check('/sys/kernel/mm/transparent_hugepage/shmem_enabled',
+
'[${sysfs.kernel.mm.transparent_hugepage.shmem_enabled}]')
+
'';
+
}