at 18.09-beta 10 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.utillinux}/sbin/blkid \ 61 --replace \"/bin/mount \"${pkgs.utillinux}/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 [[ ! -x $i ]]; then 87 echo "FAIL" 88 echo "$i is called in udev rules but not installed by udev" 89 exit 1 90 fi 91 done 92 echo "OK" 93 94 filesToFixup="$(for i in "$out"/*; do 95 grep -l '\B\(/usr\)\?/s\?bin' "$i" || : 96 done)" 97 98 if [ -n "$filesToFixup" ]; then 99 echo "Consider fixing the following udev rules:" 100 echo "$filesToFixup" | while read localFile; do 101 remoteFile="origin unknown" 102 for i in ${toString cfg.packages}; do 103 for j in "$i"/*/udev/rules.d/*; do 104 [ -e "$out/$(basename "$j")" ] || continue 105 [ "$(basename "$j")" = "$(basename "$localFile")" ] || continue 106 remoteFile="originally from $j" 107 break 2 108 done 109 done 110 refs="$( 111 grep -o '\B\(/usr\)\?/s\?bin/[^ "]\+' "$localFile" \ 112 | sed -e ':r;N;''${s/\n/ and /;br};s/\n/, /g;br' 113 )" 114 echo "$localFile ($remoteFile) contains references to $refs." 115 done 116 exit 1 117 fi 118 119 ${optionalString config.networking.usePredictableInterfaceNames '' 120 cp ${./80-net-setup-link.rules} $out/80-net-setup-link.rules 121 ''} 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 extraRules = mkOption { 206 default = ""; 207 example = '' 208 KERNEL=="eth*", ATTR{address}=="00:1D:60:B9:6D:4F", NAME="my_fast_network_card" 209 ''; 210 type = types.lines; 211 description = '' 212 Additional <command>udev</command> rules. They'll be written 213 into file <filename>99-local.rules</filename>. Thus they are 214 read and applied after all other rules. 215 ''; 216 }; 217 218 extraHwdb = mkOption { 219 default = ""; 220 example = '' 221 evdev:input:b0003v05AFp8277* 222 KEYBOARD_KEY_70039=leftalt 223 KEYBOARD_KEY_700e2=leftctrl 224 ''; 225 type = types.lines; 226 description = '' 227 Additional <command>hwdb</command> files. They'll be written 228 into file <filename>10-local.hwdb</filename>. Thus they are 229 read before all other files. 230 ''; 231 }; 232 233 }; 234 235 hardware.firmware = mkOption { 236 type = types.listOf types.package; 237 default = []; 238 description = '' 239 List of packages containing firmware files. Such files 240 will be loaded automatically if the kernel asks for them 241 (i.e., when it has detected specific hardware that requires 242 firmware to function). If multiple packages contain firmware 243 files with the same name, the first package in the list takes 244 precedence. Note that you must rebuild your system if you add 245 files to any of these directories. 246 ''; 247 apply = list: pkgs.buildEnv { 248 name = "firmware"; 249 paths = list; 250 pathsToLink = [ "/lib/firmware" ]; 251 ignoreCollisions = true; 252 }; 253 }; 254 255 networking.usePredictableInterfaceNames = mkOption { 256 default = true; 257 type = types.bool; 258 description = '' 259 Whether to assign <link 260 xlink:href='http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames'>predictable 261 names to network interfaces</link>. If enabled, interfaces 262 are assigned names that contain topology information 263 (e.g. <literal>wlp3s0</literal>) and thus should be stable 264 across reboots. If disabled, names depend on the order in 265 which interfaces are discovered by the kernel, which may 266 change randomly across reboots; for instance, you may find 267 <literal>eth0</literal> and <literal>eth1</literal> flipping 268 unpredictably. 269 ''; 270 }; 271 272 }; 273 274 275 ###### implementation 276 277 config = mkIf (!config.boot.isContainer) { 278 279 services.udev.extraRules = nixosRules; 280 281 services.udev.packages = [ extraUdevRules extraHwdbFile ]; 282 283 services.udev.path = [ pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.utillinux udev ]; 284 285 environment.etc = 286 [ { source = udevRules; 287 target = "udev/rules.d"; 288 } 289 { source = hwdbBin; 290 target = "udev/hwdb.bin"; 291 } 292 ]; 293 294 system.requiredKernelConfig = with config.lib.kernelConfig; [ 295 (isEnabled "UNIX") 296 (isYes "INOTIFY_USER") 297 (isYes "NET") 298 ]; 299 300 boot.extraModprobeConfig = "options firmware_class path=${config.hardware.firmware}/lib/firmware"; 301 302 system.activationScripts.udevd = 303 '' 304 # The deprecated hotplug uevent helper is not used anymore 305 if [ -e /proc/sys/kernel/hotplug ]; then 306 echo "" > /proc/sys/kernel/hotplug 307 fi 308 309 # Allow the kernel to find our firmware. 310 if [ -e /sys/module/firmware_class/parameters/path ]; then 311 echo -n "${config.hardware.firmware}/lib/firmware" > /sys/module/firmware_class/parameters/path 312 fi 313 ''; 314 315 systemd.services.systemd-udevd = 316 { restartTriggers = cfg.packages; 317 }; 318 319 }; 320}