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