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