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