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