mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-11-10 01:33:11 +01:00
266 lines
7.1 KiB
Nix
266 lines
7.1 KiB
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 ];
|
||
}
|