at 25.11-pre 18 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 9 udev = config.systemd.package; 10 11 cfg = config.services.udev; 12 13 initrdUdevRules = pkgs.runCommand "initrd-udev-rules" { } '' 14 mkdir -p $out/etc/udev/rules.d 15 for f in 60-cdrom_id 60-persistent-storage 75-net-description 80-drivers 80-net-setup-link; do 16 ln -s ${config.boot.initrd.systemd.package}/lib/udev/rules.d/$f.rules $out/etc/udev/rules.d 17 done 18 ''; 19 20 extraUdevRules = pkgs.writeTextFile { 21 name = "extra-udev-rules"; 22 text = cfg.extraRules; 23 destination = "/etc/udev/rules.d/99-local.rules"; 24 }; 25 26 extraHwdbFile = pkgs.writeTextFile { 27 name = "extra-hwdb-file"; 28 text = cfg.extraHwdb; 29 destination = "/etc/udev/hwdb.d/99-local.hwdb"; 30 }; 31 32 nixosRules = '' 33 # Needed for gpm. 34 SUBSYSTEM=="input", KERNEL=="mice", TAG+="systemd" 35 ''; 36 37 nixosInitrdRules = '' 38 # Mark dm devices as db_persist so that they are kept active after switching root 39 SUBSYSTEM=="block", KERNEL=="dm-[0-9]*", ACTION=="add|change", OPTIONS+="db_persist" 40 ''; 41 42 # Perform substitutions in all udev rules files. 43 udevRulesFor = 44 { 45 name, 46 udevPackages, 47 udevPath, 48 udev, 49 systemd, 50 binPackages, 51 initrdBin ? null, 52 }: 53 pkgs.runCommand name 54 { 55 preferLocalBuild = true; 56 allowSubstitutes = false; 57 packages = lib.unique (map toString udevPackages); 58 59 nativeBuildInputs = [ 60 # We only include the out output here to avoid needing to include all 61 # other outputs in the installer tests as well 62 # We only need the udevadm command anyway 63 pkgs.systemdMinimal.out 64 ]; 65 } 66 '' 67 mkdir -p $out 68 shopt -s nullglob 69 set +o pipefail 70 71 # Set a reasonable $PATH for programs called by udev rules. 72 echo 'ENV{PATH}="${udevPath}/bin:${udevPath}/sbin"' > $out/00-path.rules 73 74 # Add the udev rules from other packages. 75 for i in $packages; do 76 echo "Adding rules for package $i" 77 for j in $i/{etc,lib}/udev/rules.d/*; do 78 echo "Copying $j to $out/$(basename $j)" 79 cat $j > $out/$(basename $j) 80 done 81 done 82 83 # Fix some paths in the standard udev rules. Hacky. 84 for i in $out/*.rules; do 85 substituteInPlace $i \ 86 --replace-quiet \"/sbin/modprobe \"${pkgs.kmod}/bin/modprobe \ 87 --replace-quiet \"/sbin/mdadm \"${pkgs.mdadm}/sbin/mdadm \ 88 --replace-quiet \"/sbin/blkid \"${pkgs.util-linux}/sbin/blkid \ 89 --replace-quiet \"/bin/mount \"${pkgs.util-linux}/bin/mount \ 90 --replace-quiet /usr/bin/readlink ${pkgs.coreutils}/bin/readlink \ 91 --replace-quiet /usr/bin/cat ${pkgs.coreutils}/bin/cat \ 92 --replace-quiet /usr/bin/basename ${pkgs.coreutils}/bin/basename 2>/dev/null 93 ${lib.optionalString (initrdBin != null) '' 94 substituteInPlace $i --replace-quiet '/run/current-system/systemd' "${lib.removeSuffix "/bin" initrdBin}" 95 ''} 96 done 97 98 echo -n "Checking that all programs called by relative paths in udev rules exist in ${udev}/lib/udev... " 99 import_progs=$(grep 'IMPORT{program}="[^/$]' $out/* | 100 sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq) 101 run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="[^/$]' | 102 sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq) 103 for i in $import_progs $run_progs; do 104 if [[ ! -x ${udev}/lib/udev/$i && ! $i =~ socket:.* ]]; then 105 echo "FAIL" 106 echo "$i is called in udev rules but not installed by udev" 107 exit 1 108 fi 109 done 110 echo "OK" 111 112 echo -n "Checking that all programs called by absolute paths in udev rules exist... " 113 import_progs=$(grep 'IMPORT{program}="/' $out/* | 114 sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq) 115 run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="/' | 116 sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq) 117 for i in $import_progs $run_progs; do 118 # if the path refers to /run/current-system/systemd, replace with config.systemd.package 119 if [[ $i == /run/current-system/systemd* ]]; then 120 i="${systemd}/''${i#/run/current-system/systemd/}" 121 fi 122 123 if [[ ! -x $i ]]; then 124 echo "FAIL" 125 echo "$i is called in udev rules but is not executable or does not exist" 126 exit 1 127 fi 128 done 129 echo "OK" 130 131 filesToFixup="$(for i in "$out"/*; do 132 # list all files referring to (/usr)/bin paths, but allow references to /bin/sh. 133 grep -P -l '\B(?!\/bin\/sh\b)(\/usr)?\/bin(?:\/.*)?' "$i" || : 134 done)" 135 136 if [ -n "$filesToFixup" ]; then 137 echo "Consider fixing the following udev rules:" 138 echo "$filesToFixup" | while read localFile; do 139 remoteFile="origin unknown" 140 for i in ${toString binPackages}; do 141 for j in "$i"/*/udev/rules.d/*; do 142 [ -e "$out/$(basename "$j")" ] || continue 143 [ "$(basename "$j")" = "$(basename "$localFile")" ] || continue 144 remoteFile="originally from $j" 145 break 2 146 done 147 done 148 refs="$( 149 grep -o '\B\(/usr\)\?/s\?bin/[^ "]\+' "$localFile" \ 150 | sed -e ':r;N;''${s/\n/ and /;br};s/\n/, /g;br' 151 )" 152 echo "$localFile ($remoteFile) contains references to $refs." 153 done 154 exit 1 155 fi 156 157 # Verify all the udev rules 158 echo "Verifying udev rules using udevadm verify..." 159 udevadm verify --resolve-names=never --no-style $out 160 echo "OK" 161 162 # If auto-configuration is disabled, then remove 163 # udev's 80-drivers.rules file, which contains rules for 164 # automatically calling modprobe. 165 ${lib.optionalString (!config.boot.hardwareScan) '' 166 ln -s /dev/null $out/80-drivers.rules 167 ''} 168 ''; 169 170 hwdbBin = 171 pkgs.runCommand "hwdb.bin" 172 { 173 preferLocalBuild = true; 174 allowSubstitutes = false; 175 packages = lib.unique (map toString ([ udev ] ++ cfg.packages)); 176 } 177 '' 178 mkdir -p etc/udev/hwdb.d 179 for i in $packages; do 180 echo "Adding hwdb files for package $i" 181 for j in $i/{etc,lib}/udev/hwdb.d/*; do 182 ln -s $j etc/udev/hwdb.d/$(basename $j) 183 done 184 done 185 186 echo "Generating hwdb database..." 187 # hwdb --update doesn't return error code even on errors! 188 res="$(${pkgs.buildPackages.systemd}/bin/systemd-hwdb --root=$(pwd) update 2>&1)" 189 echo "$res" 190 [ -z "$(echo "$res" | egrep '^Error')" ] 191 mv etc/udev/hwdb.bin $out 192 ''; 193 194 compressFirmware = 195 firmware: 196 if 197 config.hardware.firmwareCompression == "none" || (firmware.compressFirmware or true) == false 198 then 199 firmware 200 else if config.hardware.firmwareCompression == "zstd" then 201 pkgs.compressFirmwareZstd firmware 202 else 203 pkgs.compressFirmwareXz firmware; 204 205 # Udev has a 512-character limit for ENV{PATH}, so create a symlink 206 # tree to work around this. 207 udevPath = pkgs.buildEnv { 208 name = "udev-path"; 209 paths = cfg.path; 210 pathsToLink = [ 211 "/bin" 212 "/sbin" 213 ]; 214 ignoreCollisions = true; 215 }; 216 217in 218 219{ 220 221 ###### interface 222 223 options = { 224 boot.hardwareScan = lib.mkOption { 225 type = lib.types.bool; 226 default = true; 227 description = '' 228 Whether to try to load kernel modules for all detected hardware. 229 Usually this does a good job of providing you with the modules 230 you need, but sometimes it can crash the system or cause other 231 nasty effects. 232 ''; 233 }; 234 235 services.udev = { 236 enable = lib.mkEnableOption "udev, a device manager for the Linux kernel" // { 237 default = true; 238 }; 239 240 packages = lib.mkOption { 241 type = lib.types.listOf lib.types.path; 242 default = [ ]; 243 description = '' 244 List of packages containing {command}`udev` rules. 245 All files found in 246 {file}`«pkg»/etc/udev/rules.d` and 247 {file}`«pkg»/lib/udev/rules.d` 248 will be included. 249 ''; 250 apply = map lib.getBin; 251 }; 252 253 path = lib.mkOption { 254 type = lib.types.listOf lib.types.path; 255 default = [ ]; 256 description = '' 257 Packages added to the {env}`PATH` environment variable when 258 executing programs from Udev rules. 259 260 coreutils, gnu{sed,grep}, util-linux and config.systemd.package are 261 automatically included. 262 ''; 263 }; 264 265 extraRules = lib.mkOption { 266 default = ""; 267 example = '' 268 ENV{ID_VENDOR_ID}=="046d", ENV{ID_MODEL_ID}=="0825", ENV{PULSE_IGNORE}="1" 269 ''; 270 type = lib.types.lines; 271 description = '' 272 Additional {command}`udev` rules. They'll be written 273 into file {file}`99-local.rules`. Thus they are 274 read and applied after all other rules. 275 ''; 276 }; 277 278 extraHwdb = lib.mkOption { 279 default = ""; 280 example = '' 281 evdev:input:b0003v05AFp8277* 282 KEYBOARD_KEY_70039=leftalt 283 KEYBOARD_KEY_700e2=leftctrl 284 ''; 285 type = lib.types.lines; 286 description = '' 287 Additional {command}`hwdb` files. They'll be written 288 into file {file}`99-local.hwdb`. Thus they are 289 read after all other files. 290 ''; 291 }; 292 293 }; 294 295 hardware.firmware = lib.mkOption { 296 type = lib.types.listOf lib.types.package; 297 default = [ ]; 298 description = '' 299 List of packages containing firmware files. Such files 300 will be loaded automatically if the kernel asks for them 301 (i.e., when it has detected specific hardware that requires 302 firmware to function). If multiple packages contain firmware 303 files with the same name, the first package in the list takes 304 precedence. Note that you must rebuild your system if you add 305 files to any of these directories. 306 ''; 307 apply = 308 list: 309 pkgs.buildEnv { 310 name = "firmware"; 311 paths = map compressFirmware list; 312 pathsToLink = [ "/lib/firmware" ]; 313 ignoreCollisions = true; 314 }; 315 }; 316 317 hardware.firmwareCompression = lib.mkOption { 318 type = lib.types.enum [ 319 "xz" 320 "zstd" 321 "none" 322 ]; 323 default = 324 if config.boot.kernelPackages.kernelAtLeast "5.19" then 325 "zstd" 326 else if config.boot.kernelPackages.kernelAtLeast "5.3" then 327 "xz" 328 else 329 "none"; 330 defaultText = "auto"; 331 description = '' 332 Whether to compress firmware files. 333 Defaults depend on the kernel version. 334 For kernels older than 5.3, firmware files are not compressed. 335 For kernels 5.3 and newer, firmware files are compressed with xz. 336 For kernels 5.19 and newer, firmware files are compressed with zstd. 337 ''; 338 }; 339 340 networking.usePredictableInterfaceNames = lib.mkOption { 341 default = true; 342 type = lib.types.bool; 343 description = '' 344 Whether to assign [predictable names to network interfaces](https://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/). 345 If enabled, interfaces 346 are assigned names that contain topology information 347 (e.g. `wlp3s0`) and thus should be stable 348 across reboots. If disabled, names depend on the order in 349 which interfaces are discovered by the kernel, which may 350 change randomly across reboots; for instance, you may find 351 `eth0` and `eth1` flipping 352 unpredictably. 353 ''; 354 }; 355 356 boot.initrd.services.udev = { 357 358 packages = lib.mkOption { 359 type = lib.types.listOf lib.types.path; 360 default = [ ]; 361 description = '' 362 *This will only be used when systemd is used in stage 1.* 363 364 List of packages containing {command}`udev` rules that will be copied to stage 1. 365 All files found in 366 {file}`«pkg»/etc/udev/rules.d` and 367 {file}`«pkg»/lib/udev/rules.d` 368 will be included. 369 ''; 370 }; 371 372 binPackages = lib.mkOption { 373 type = lib.types.listOf lib.types.path; 374 default = [ ]; 375 description = '' 376 *This will only be used when systemd is used in stage 1.* 377 378 Packages to search for binaries that are referenced by the udev rules in stage 1. 379 This list always contains /bin of the initrd. 380 ''; 381 apply = map lib.getBin; 382 }; 383 384 rules = lib.mkOption { 385 default = ""; 386 example = '' 387 SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:1D:60:B9:6D:4F", KERNEL=="eth*", NAME="my_fast_network_card" 388 ''; 389 type = lib.types.lines; 390 description = '' 391 {command}`udev` rules to include in the initrd 392 *only*. They'll be written into file 393 {file}`99-local.rules`. Thus they are read and applied 394 after the essential initrd rules. 395 ''; 396 }; 397 398 }; 399 400 }; 401 402 ###### implementation 403 404 config = lib.mkIf cfg.enable { 405 406 assertions = [ 407 { 408 assertion = 409 config.hardware.firmwareCompression == "zstd" -> config.boot.kernelPackages.kernelAtLeast "5.19"; 410 message = '' 411 The firmware compression method is set to zstd, but the kernel version is too old. 412 The kernel version must be at least 5.3 to use zstd compression. 413 ''; 414 } 415 { 416 assertion = 417 config.hardware.firmwareCompression == "xz" -> config.boot.kernelPackages.kernelAtLeast "5.3"; 418 message = '' 419 The firmware compression method is set to xz, but the kernel version is too old. 420 The kernel version must be at least 5.3 to use xz compression. 421 ''; 422 } 423 ]; 424 425 services.udev.extraRules = nixosRules; 426 427 services.udev.packages = [ 428 extraUdevRules 429 extraHwdbFile 430 ]; 431 432 services.udev.path = [ 433 pkgs.coreutils 434 pkgs.gnused 435 pkgs.gnugrep 436 pkgs.util-linux 437 udev 438 ]; 439 440 boot.kernelParams = lib.mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ]; 441 442 boot.initrd.extraUdevRulesCommands = 443 lib.mkIf (!config.boot.initrd.systemd.enable && config.boot.initrd.services.udev.rules != "") 444 '' 445 cat <<'EOF' > $out/99-local.rules 446 ${config.boot.initrd.services.udev.rules} 447 EOF 448 ''; 449 450 boot.initrd.services.udev.rules = nixosInitrdRules; 451 452 boot.initrd.systemd.additionalUpstreamUnits = [ 453 "initrd-udevadm-cleanup-db.service" 454 "systemd-udevd-control.socket" 455 "systemd-udevd-kernel.socket" 456 "systemd-udevd.service" 457 "systemd-udev-settle.service" 458 "systemd-udev-trigger.service" 459 ]; 460 boot.initrd.systemd.storePaths = [ 461 "${config.boot.initrd.systemd.package}/lib/systemd/systemd-udevd" 462 "${config.boot.initrd.systemd.package}/lib/udev/ata_id" 463 "${config.boot.initrd.systemd.package}/lib/udev/cdrom_id" 464 "${config.boot.initrd.systemd.package}/lib/udev/scsi_id" 465 "${config.boot.initrd.systemd.package}/lib/udev/rules.d" 466 ] ++ map (x: "${x}/bin") config.boot.initrd.services.udev.binPackages; 467 468 # Generate the udev rules for the initrd 469 boot.initrd.systemd.contents = { 470 "/etc/udev/rules.d".source = udevRulesFor { 471 name = "initrd-udev-rules"; 472 initrdBin = config.boot.initrd.systemd.contents."/bin".source; 473 udevPackages = config.boot.initrd.services.udev.packages; 474 udevPath = config.boot.initrd.systemd.contents."/bin".source; 475 udev = config.boot.initrd.systemd.package; 476 systemd = config.boot.initrd.systemd.package; 477 binPackages = config.boot.initrd.services.udev.binPackages ++ [ 478 config.boot.initrd.systemd.contents."/bin".source 479 ]; 480 }; 481 }; 482 # Insert initrd rules 483 boot.initrd.services.udev.packages = [ 484 initrdUdevRules 485 (lib.mkIf (config.boot.initrd.services.udev.rules != "") ( 486 pkgs.writeTextFile { 487 name = "initrd-udev-rules"; 488 destination = "/etc/udev/rules.d/99-local.rules"; 489 text = config.boot.initrd.services.udev.rules; 490 } 491 )) 492 ]; 493 494 environment.etc = 495 { 496 "udev/rules.d".source = udevRulesFor { 497 name = "udev-rules"; 498 udevPackages = cfg.packages; 499 systemd = config.systemd.package; 500 binPackages = cfg.packages; 501 inherit udevPath udev; 502 }; 503 "udev/hwdb.bin".source = hwdbBin; 504 } 505 // lib.optionalAttrs config.boot.modprobeConfig.enable { 506 # We don't place this into `extraModprobeConfig` so that stage-1 ramdisk doesn't bloat. 507 "modprobe.d/firmware.conf".text = 508 "options firmware_class path=${config.hardware.firmware}/lib/firmware"; 509 }; 510 511 system.requiredKernelConfig = with config.lib.kernelConfig; [ 512 (isEnabled "UNIX") 513 (isYes "INOTIFY_USER") 514 (isYes "NET") 515 ]; 516 517 system.activationScripts.udevd = lib.mkIf config.boot.kernel.enable '' 518 # The deprecated hotplug uevent helper is not used anymore 519 if [ -e /proc/sys/kernel/hotplug ]; then 520 echo "" > /proc/sys/kernel/hotplug 521 fi 522 523 # Allow the kernel to find our firmware. 524 if [ -e /sys/module/firmware_class/parameters/path ]; then 525 echo -n "${config.hardware.firmware}/lib/firmware" > /sys/module/firmware_class/parameters/path 526 fi 527 ''; 528 529 systemd.services.systemd-udevd = { 530 restartTriggers = [ config.environment.etc."udev/rules.d".source ]; 531 notSocketActivated = true; 532 stopIfChanged = false; 533 }; 534 }; 535 536 imports = [ 537 (lib.mkRenamedOptionModule 538 [ "services" "udev" "initrdRules" ] 539 [ "boot" "initrd" "services" "udev" "rules" ] 540 ) 541 ]; 542}