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