at master 7.2 kB view raw
1{ 2 lib, 3 config, 4 utils, 5 pkgs, 6 ... 7}: 8 9let 10 inherit (lib) 11 all 12 any 13 concatLines 14 concatStringsSep 15 escapeShellArg 16 flatten 17 floatToString 18 foldl' 19 head 20 isAttrs 21 isDerivation 22 isFloat 23 isList 24 length 25 listToAttrs 26 match 27 mapAttrsToList 28 nameValuePair 29 removePrefix 30 tail 31 throwIf 32 ; 33 34 inherit (lib.options) 35 showDefs 36 showOption 37 ; 38 39 inherit (lib.strings) 40 escapeC 41 isConvertibleWithToString 42 ; 43 44 inherit (lib.path.subpath) join; 45 46 inherit (utils) escapeSystemdPath; 47 48 cfg = config.boot.kernel.sysfs; 49 50 sysfsAttrs = with lib.types; nullOr (either sysfsValue (attrsOf sysfsAttrs)); 51 sysfsValue = lib.mkOptionType { 52 name = "sysfs value"; 53 description = "sysfs attribute value"; 54 descriptionClass = "noun"; 55 check = v: isConvertibleWithToString v; 56 merge = 57 loc: defs: 58 if length defs == 1 then 59 (head defs).value 60 else 61 (foldl' ( 62 first: def: 63 # merge definitions if they produce the same value string 64 throwIf (mkValueString first.value != mkValueString def.value) 65 "The option \"${showOption loc}\" has conflicting definition values:${ 66 showDefs [ 67 first 68 def 69 ] 70 }" 71 first 72 ) (head defs) (tail defs)).value; 73 }; 74 75 mapAttrsToListRecursive = 76 fn: set: 77 let 78 recurse = 79 p: v: 80 if isAttrs v && !isDerivation v then mapAttrsToList (n: v: recurse (p ++ [ n ]) v) v else fn p v; 81 in 82 flatten (recurse [ ] set); 83 84 mkPath = p: "/sys" + removePrefix "." (join p); 85 hasGlob = p: any (n: match ''(.*[^\\])?[*?[].*'' n != null) p; 86 87 mkValueString = 88 v: 89 # true will be converted to "1" by toString, saving one branch 90 if v == false then 91 "0" 92 else if isFloat v then 93 floatToString v # warn about loss of precision 94 else if isList v then 95 concatStringsSep "," (map mkValueString v) 96 else 97 toString v; 98 99 # escape whitespace and linebreaks, as well as the escape character itself, 100 # to ensure that field boundaries are always preserved 101 escapeTmpfiles = escapeC [ 102 "\t" 103 "\n" 104 "\r" 105 " " 106 "\\" 107 ]; 108 109 tmpfiles = pkgs.runCommand "nixos-sysfs-tmpfiles.d" { } ( 110 '' 111 mkdir "$out" 112 '' 113 + concatLines ( 114 mapAttrsToListRecursive ( 115 p: v: 116 let 117 path = mkPath p; 118 in 119 if v == null then 120 [ ] 121 else 122 '' 123 printf 'w %s - - - - %s\n' \ 124 ${escapeShellArg (escapeTmpfiles path)} \ 125 ${escapeShellArg (escapeTmpfiles (mkValueString v))} \ 126 >"$out"/${escapeShellArg (escapeSystemdPath path)}.conf 127 '' 128 ) cfg 129 ) 130 ); 131in 132{ 133 options = { 134 boot.kernel.sysfs = lib.mkOption { 135 type = lib.types.submodule { 136 freeformType = lib.types.attrsOf sysfsAttrs // { 137 description = "nested attribute set of null or sysfs attribute values"; 138 }; 139 }; 140 141 description = '' 142 sysfs attributes to be set as soon as they become available. 143 144 Attribute names represent path components in the sysfs filesystem and 145 cannot be `.` or `..` nor contain any slash character (`/`). 146 147 Names may contain shellstyle glob patterns (`*`, `?` and `[]`) 148 matching a single path component, these should however be used with 149 caution, as they may produce unexpected results if attribute paths 150 overlap. 151 152 Values will be converted to strings, with list elements concatenated 153 with commata and booleans converted to numeric values (`0` or `1`). 154 155 `null` values are ignored, allowing removal of values defined in other 156 modules, as are empty attribute sets. 157 158 List values defined in different modules will _not_ be concatenated. 159 160 This option may only be used for attributes which can be set 161 idempotently, as the configured values might be written more than once. 162 ''; 163 164 default = { }; 165 166 example = lib.literalExpression '' 167 { 168 # enable transparent hugepages with deferred defragmentaion 169 kernel.mm.transparent_hugepage = { 170 enabled = "always"; 171 defrag = "defer"; 172 shmem_enabled = "within_size"; 173 }; 174 175 devices.system.cpu = { 176 # configure powesave frequency governor for all CPUs 177 # the [0-9]* glob pattern ensures that other paths 178 # like cpufreq or cpuidle are not matched 179 "cpu[0-9]*" = { 180 scaling_governor = "powersave"; 181 energy_performance_preference = 8; 182 }; 183 184 # disable frequency boost 185 intel_pstate.no_turbo = true; 186 }; 187 } 188 ''; 189 }; 190 }; 191 192 config = lib.mkIf (cfg != { }) { 193 systemd = { 194 paths = { 195 "nixos-sysfs@" = { 196 description = "/%I attribute watcher"; 197 pathConfig.PathExistsGlob = "/%I"; 198 unitConfig.DefaultDependencies = false; 199 }; 200 } 201 // listToAttrs ( 202 mapAttrsToListRecursive ( 203 p: v: 204 if v == null then 205 [ ] 206 else 207 nameValuePair "nixos-sysfs@${escapeSystemdPath (mkPath p)}" { 208 overrideStrategy = "asDropin"; 209 wantedBy = [ "sysinit.target" ]; 210 before = [ "sysinit.target" ]; 211 } 212 ) cfg 213 ); 214 215 services."nixos-sysfs@" = { 216 description = "/%I attribute setter"; 217 218 unitConfig = { 219 DefaultDependencies = false; 220 AssertPathIsMountPoint = "/sys"; 221 AssertPathExistsGlob = "/%I"; 222 }; 223 224 serviceConfig = { 225 Type = "oneshot"; 226 RemainAfterExit = true; 227 228 # while we could be tempted to use simple shell script to set the 229 # sysfs attributes specified by the path or glob pattern, it is 230 # almost impossible to properly escape a glob pattern so that it 231 # can be used safely in a shell script 232 ExecStart = "${lib.getExe' config.systemd.package "systemd-tmpfiles"} --prefix=/sys --create ${tmpfiles}/%i.conf"; 233 234 # hardening may be overkill for such a simple and short‐lived 235 # service, the following settings would however be suitable to deny 236 # access to anything but /sys 237 #ProtectProc = "noaccess"; 238 #ProcSubset = "pid"; 239 #ProtectSystem = "strict"; 240 #PrivateDevices = true; 241 #SystemCallErrorNumber = "EPERM"; 242 #SystemCallFilter = [ 243 # "@basic-io" 244 # "@file-system" 245 #]; 246 }; 247 }; 248 }; 249 250 warnings = mapAttrsToListRecursive ( 251 p: v: 252 if hasGlob p then 253 "Attribute path \"${concatStringsSep "." p}\" contains glob patterns. Please ensure that it does not overlap with other paths." 254 else 255 [ ] 256 ) cfg; 257 258 assertions = mapAttrsToListRecursive (p: v: { 259 assertion = all (n: match ''(\.\.?|.*/.*)'' n == null) p; 260 message = "Attribute path \"${concatStringsSep "." p}\" has invalid components."; 261 }) cfg; 262 }; 263 264 meta.maintainers = with lib.maintainers; [ mvs ]; 265}