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