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