1#! @perl@/bin/perl
2
3use strict;
4use warnings;
5use File::Path qw(make_path);
6use File::Basename;
7use File::Slurp;
8use Net::DBus;
9use Sys::Syslog qw(:standard :macros);
10use Cwd 'abs_path';
11
12my $out = "@out@";
13
14my $curSystemd = abs_path("/run/current-system/sw/bin");
15
16# To be robust against interruption, record what units need to be started etc.
17my $startListFile = "/run/nixos/start-list";
18my $restartListFile = "/run/nixos/restart-list";
19my $reloadListFile = "/run/nixos/reload-list";
20
21# Parse restart/reload requests by the activation script.
22# Activation scripts may write newline-separated units to this
23# file and switch-to-configuration will handle them. While
24# `stopIfChanged = true` is ignored, switch-to-configuration will
25# handle `restartIfChanged = false` and `reloadIfChanged = true`.
26# This also works for socket-activated units.
27my $restartByActivationFile = "/run/nixos/activation-restart-list";
28my $dryRestartByActivationFile = "/run/nixos/dry-activation-restart-list";
29
30make_path("/run/nixos", { mode => oct(755) });
31
32my $action = shift @ARGV;
33
34if ("@localeArchive@" ne "") {
35 $ENV{LOCALE_ARCHIVE} = "@localeArchive@";
36}
37
38if (!defined $action || ($action ne "switch" && $action ne "boot" && $action ne "test" && $action ne "dry-activate")) {
39 print STDERR <<EOF;
40Usage: $0 [switch|boot|test]
41
42switch: make the configuration the boot default and activate now
43boot: make the configuration the boot default
44test: activate the configuration, but don\'t make it the boot default
45dry-activate: show what would be done if this configuration were activated
46EOF
47 exit 1;
48}
49
50$ENV{NIXOS_ACTION} = $action;
51
52# This is a NixOS installation if it has /etc/NIXOS or a proper
53# /etc/os-release.
54die "This is not a NixOS installation!\n" unless
55 -f "/etc/NIXOS" || (read_file("/etc/os-release", err_mode => 'quiet') // "") =~ /ID=nixos/s;
56
57openlog("nixos", "", LOG_USER);
58
59# Install or update the bootloader.
60if ($action eq "switch" || $action eq "boot") {
61 system("@installBootLoader@ $out") == 0 or exit 1;
62}
63
64# Just in case the new configuration hangs the system, do a sync now.
65system("@coreutils@/bin/sync", "-f", "/nix/store") unless ($ENV{"NIXOS_NO_SYNC"} // "") eq "1";
66
67exit 0 if $action eq "boot";
68
69# Check if we can activate the new configuration.
70my $oldVersion = read_file("/run/current-system/init-interface-version", err_mode => 'quiet') // "";
71my $newVersion = read_file("$out/init-interface-version");
72
73if ($newVersion ne $oldVersion) {
74 print STDERR <<EOF;
75Warning: the new NixOS configuration has an ‘init’ that is
76incompatible with the current configuration. The new configuration
77won\'t take effect until you reboot the system.
78EOF
79 exit 100;
80}
81
82# Ignore SIGHUP so that we're not killed if we're running on (say)
83# virtual console 1 and we restart the "tty1" unit.
84$SIG{PIPE} = "IGNORE";
85
86sub getActiveUnits {
87 my $mgr = Net::DBus->system->get_service("org.freedesktop.systemd1")->get_object("/org/freedesktop/systemd1");
88 my $units = $mgr->ListUnitsByPatterns([], []);
89 my $res = {};
90 for my $item (@$units) {
91 my ($id, $description, $load_state, $active_state, $sub_state,
92 $following, $unit_path, $job_id, $job_type, $job_path) = @$item;
93 next unless $following eq '';
94 next if $job_id == 0 and $active_state eq 'inactive';
95 $res->{$id} = { load => $load_state, state => $active_state, substate => $sub_state };
96 }
97 return $res;
98}
99
100sub parseFstab {
101 my ($filename) = @_;
102 my ($fss, $swaps);
103 foreach my $line (read_file($filename, err_mode => 'quiet')) {
104 chomp $line;
105 $line =~ s/^\s*#.*//;
106 next if $line =~ /^\s*$/;
107 my @xs = split / /, $line;
108 if ($xs[2] eq "swap") {
109 $swaps->{$xs[0]} = { options => $xs[3] // "" };
110 } else {
111 $fss->{$xs[1]} = { device => $xs[0], fsType => $xs[2], options => $xs[3] // "" };
112 }
113 }
114 return ($fss, $swaps);
115}
116
117sub parseUnit {
118 my ($filename) = @_;
119 my $info = {};
120 parseKeyValues($info, read_file($filename)) if -f $filename;
121 parseKeyValues($info, read_file("${filename}.d/overrides.conf")) if -f "${filename}.d/overrides.conf";
122 return $info;
123}
124
125sub parseKeyValues {
126 my $info = shift;
127 foreach my $line (@_) {
128 # FIXME: not quite correct.
129 $line =~ /^([^=]+)=(.*)$/ or next;
130 $info->{$1} = $2;
131 }
132}
133
134sub boolIsTrue {
135 my ($s) = @_;
136 return $s eq "yes" || $s eq "true";
137}
138
139sub recordUnit {
140 my ($fn, $unit) = @_;
141 write_file($fn, { append => 1 }, "$unit\n") if $action ne "dry-activate";
142}
143
144# As a fingerprint for determining whether a unit has changed, we use
145# its absolute path. If it has an override file, we append *its*
146# absolute path as well.
147sub fingerprintUnit {
148 my ($s) = @_;
149 return abs_path($s) . (-f "${s}.d/overrides.conf" ? " " . abs_path "${s}.d/overrides.conf" : "");
150}
151
152sub handleModifiedUnit {
153 my ($unit, $baseName, $newUnitFile, $activePrev, $unitsToStop, $unitsToStart, $unitsToReload, $unitsToRestart, $unitsToSkip) = @_;
154
155 if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target" || $unit =~ /\.slice$/ || $unit =~ /\.path$/) {
156 # Do nothing. These cannot be restarted directly.
157 # Slices and Paths don't have to be restarted since
158 # properties (resource limits and inotify watches)
159 # seem to get applied on daemon-reload.
160 } elsif ($unit =~ /\.mount$/) {
161 # Reload the changed mount unit to force a remount.
162 $unitsToReload->{$unit} = 1;
163 recordUnit($reloadListFile, $unit);
164 } else {
165 my $unitInfo = parseUnit($newUnitFile);
166 if (boolIsTrue($unitInfo->{'X-ReloadIfChanged'} // "no")) {
167 $unitsToReload->{$unit} = 1;
168 recordUnit($reloadListFile, $unit);
169 }
170 elsif (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes") || boolIsTrue($unitInfo->{'RefuseManualStop'} // "no") || boolIsTrue($unitInfo->{'X-OnlyManualStart'} // "no")) {
171 $unitsToSkip->{$unit} = 1;
172 } else {
173 # If this unit is socket-activated, then stop it instead
174 # of restarting it to make sure the new version of it is
175 # socket-activated.
176 my $socketActivated = 0;
177 if ($unit =~ /\.service$/) {
178 my @sockets = split / /, ($unitInfo->{Sockets} // "");
179 if (scalar @sockets == 0) {
180 @sockets = ("$baseName.socket");
181 }
182 foreach my $socket (@sockets) {
183 if (-e "$out/etc/systemd/system/$socket") {
184 $socketActivated = 1;
185 $unitsToStop->{$unit} = 1;
186 # If the socket was not running previously,
187 # start it now.
188 if (not defined $activePrev->{$socket}) {
189 $unitsToStart->{$socket} = 1;
190 }
191 }
192 }
193 }
194
195 # Don't do the rest of this for socket-activated units
196 # because we handled these above where we stop the unit.
197 # Since only services can be socket-activated, the
198 # following condition always evaluates to `true` for
199 # non-service units.
200 if ($socketActivated) {
201 return;
202 }
203
204 # If we are restarting a socket, also stop the corresponding
205 # service. This is required because restarting a socket
206 # when the service is already activated fails.
207 if ($unit =~ /\.socket$/) {
208 my $service = $unitInfo->{Service} // "";
209 if ($service eq "") {
210 $service = "$baseName.service";
211 }
212 if (defined $activePrev->{$service}) {
213 $unitsToStop->{$service} = 1;
214 }
215 $unitsToRestart->{$unit} = 1;
216 recordUnit($restartListFile, $unit);
217 } else {
218 # Always restart non-services instead of stopping and starting them
219 # because it doesn't make sense to stop them with a config from
220 # the old evaluation.
221 if (!boolIsTrue($unitInfo->{'X-StopIfChanged'} // "yes") || $unit !~ /\.service$/) {
222 # This unit should be restarted instead of
223 # stopped and started.
224 $unitsToRestart->{$unit} = 1;
225 recordUnit($restartListFile, $unit);
226 } else {
227 # We write to a file to ensure that the
228 # service gets restarted if we're interrupted.
229 $unitsToStart->{$unit} = 1;
230 recordUnit($startListFile, $unit);
231 $unitsToStop->{$unit} = 1;
232 }
233 }
234 }
235 }
236}
237
238# Figure out what units need to be stopped, started, restarted or reloaded.
239my (%unitsToStop, %unitsToSkip, %unitsToStart, %unitsToRestart, %unitsToReload);
240
241my %unitsToFilter; # units not shown
242
243$unitsToStart{$_} = 1 foreach
244 split('\n', read_file($startListFile, err_mode => 'quiet') // "");
245
246$unitsToRestart{$_} = 1 foreach
247 split('\n', read_file($restartListFile, err_mode => 'quiet') // "");
248
249$unitsToReload{$_} = 1 foreach
250 split('\n', read_file($reloadListFile, err_mode => 'quiet') // "");
251
252my $activePrev = getActiveUnits;
253while (my ($unit, $state) = each %{$activePrev}) {
254 my $baseUnit = $unit;
255
256 my $prevUnitFile = "/etc/systemd/system/$baseUnit";
257 my $newUnitFile = "$out/etc/systemd/system/$baseUnit";
258
259 # Detect template instances.
260 if (!-e $prevUnitFile && !-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) {
261 $baseUnit = "$1\@.$2";
262 $prevUnitFile = "/etc/systemd/system/$baseUnit";
263 $newUnitFile = "$out/etc/systemd/system/$baseUnit";
264 }
265
266 my $baseName = $baseUnit;
267 $baseName =~ s/\.[a-z]*$//;
268
269 if (-e $prevUnitFile && ($state->{state} eq "active" || $state->{state} eq "activating")) {
270 if (! -e $newUnitFile || abs_path($newUnitFile) eq "/dev/null") {
271 my $unitInfo = parseUnit($prevUnitFile);
272 $unitsToStop{$unit} = 1 if boolIsTrue($unitInfo->{'X-StopOnRemoval'} // "yes");
273 }
274
275 elsif ($unit =~ /\.target$/) {
276 my $unitInfo = parseUnit($newUnitFile);
277
278 # Cause all active target units to be restarted below.
279 # This should start most changed units we stop here as
280 # well as any new dependencies (including new mounts and
281 # swap devices). FIXME: the suspend target is sometimes
282 # active after the system has resumed, which probably
283 # should not be the case. Just ignore it.
284 if ($unit ne "suspend.target" && $unit ne "hibernate.target" && $unit ne "hybrid-sleep.target") {
285 unless (boolIsTrue($unitInfo->{'RefuseManualStart'} // "no") || boolIsTrue($unitInfo->{'X-OnlyManualStart'} // "no")) {
286 $unitsToStart{$unit} = 1;
287 recordUnit($startListFile, $unit);
288 # Don't spam the user with target units that always get started.
289 $unitsToFilter{$unit} = 1;
290 }
291 }
292
293 # Stop targets that have X-StopOnReconfiguration set.
294 # This is necessary to respect dependency orderings
295 # involving targets: if unit X starts after target Y and
296 # target Y starts after unit Z, then if X and Z have both
297 # changed, then X should be restarted after Z. However,
298 # if target Y is in the "active" state, X and Z will be
299 # restarted at the same time because X's dependency on Y
300 # is already satisfied. Thus, we need to stop Y first.
301 # Stopping a target generally has no effect on other units
302 # (unless there is a PartOf dependency), so this is just a
303 # bookkeeping thing to get systemd to do the right thing.
304 if (boolIsTrue($unitInfo->{'X-StopOnReconfiguration'} // "no")) {
305 $unitsToStop{$unit} = 1;
306 }
307 }
308
309 elsif (fingerprintUnit($prevUnitFile) ne fingerprintUnit($newUnitFile)) {
310 handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, %unitsToSkip);
311 }
312 }
313}
314
315sub pathToUnitName {
316 my ($path) = @_;
317 # Use current version of systemctl binary before daemon is reexeced.
318 open my $cmd, "-|", "$curSystemd/systemd-escape", "--suffix=mount", "-p", $path
319 or die "Unable to escape $path!\n";
320 my $escaped = join "", <$cmd>;
321 chomp $escaped;
322 close $cmd or die;
323 return $escaped;
324}
325
326sub unique {
327 my %seen;
328 my @res;
329 foreach my $name (@_) {
330 next if $seen{$name};
331 $seen{$name} = 1;
332 push @res, $name;
333 }
334 return @res;
335}
336
337# Compare the previous and new fstab to figure out which filesystems
338# need a remount or need to be unmounted. New filesystems are mounted
339# automatically by starting local-fs.target. FIXME: might be nicer if
340# we generated units for all mounts; then we could unify this with the
341# unit checking code above.
342my ($prevFss, $prevSwaps) = parseFstab "/etc/fstab";
343my ($newFss, $newSwaps) = parseFstab "$out/etc/fstab";
344foreach my $mountPoint (keys %$prevFss) {
345 my $prev = $prevFss->{$mountPoint};
346 my $new = $newFss->{$mountPoint};
347 my $unit = pathToUnitName($mountPoint);
348 if (!defined $new) {
349 # Filesystem entry disappeared, so unmount it.
350 $unitsToStop{$unit} = 1;
351 } elsif ($prev->{fsType} ne $new->{fsType} || $prev->{device} ne $new->{device}) {
352 # Filesystem type or device changed, so unmount and mount it.
353 $unitsToStop{$unit} = 1;
354 $unitsToStart{$unit} = 1;
355 recordUnit($startListFile, $unit);
356 } elsif ($prev->{options} ne $new->{options}) {
357 # Mount options changes, so remount it.
358 $unitsToReload{$unit} = 1;
359 recordUnit($reloadListFile, $unit);
360 }
361}
362
363# Also handles swap devices.
364foreach my $device (keys %$prevSwaps) {
365 my $prev = $prevSwaps->{$device};
366 my $new = $newSwaps->{$device};
367 if (!defined $new) {
368 # Swap entry disappeared, so turn it off. Can't use
369 # "systemctl stop" here because systemd has lots of alias
370 # units that prevent a stop from actually calling
371 # "swapoff".
372 print STDERR "stopping swap device: $device\n";
373 system("@utillinux@/sbin/swapoff", $device);
374 }
375 # FIXME: update swap options (i.e. its priority).
376}
377
378
379# Should we have systemd re-exec itself?
380my $prevSystemd = abs_path("/proc/1/exe") // "/unknown";
381my $newSystemd = abs_path("@systemd@/lib/systemd/systemd") or die;
382my $restartSystemd = $prevSystemd ne $newSystemd;
383
384
385sub filterUnits {
386 my ($units) = @_;
387 my @res;
388 foreach my $unit (sort(keys %{$units})) {
389 push @res, $unit if !defined $unitsToFilter{$unit};
390 }
391 return @res;
392}
393
394my @unitsToStopFiltered = filterUnits(\%unitsToStop);
395
396# Show dry-run actions.
397if ($action eq "dry-activate") {
398 print STDERR "would stop the following units: ", join(", ", @unitsToStopFiltered), "\n"
399 if scalar @unitsToStopFiltered > 0;
400 print STDERR "would NOT stop the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n"
401 if scalar(keys %unitsToSkip) > 0;
402
403 print STDERR "would activate the configuration...\n";
404 system("$out/dry-activate", "$out");
405
406 # Handle the activation script requesting the restart or reload of a unit.
407 my %unitsToAlsoStop;
408 my %unitsToAlsoSkip;
409 foreach (split('\n', read_file($dryRestartByActivationFile, err_mode => 'quiet') // "")) {
410 my $unit = $_;
411 my $baseUnit = $unit;
412 my $newUnitFile = "$out/etc/systemd/system/$baseUnit";
413
414 # Detect template instances.
415 if (!-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) {
416 $baseUnit = "$1\@.$2";
417 $newUnitFile = "$out/etc/systemd/system/$baseUnit";
418 }
419
420 my $baseName = $baseUnit;
421 $baseName =~ s/\.[a-z]*$//;
422
423 handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToAlsoStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, %unitsToAlsoSkip);
424 }
425 unlink($dryRestartByActivationFile);
426
427 my @unitsToAlsoStopFiltered = filterUnits(\%unitsToAlsoStop);
428 if (scalar(keys %unitsToAlsoStop) > 0) {
429 print STDERR "would stop the following units as well: ", join(", ", @unitsToAlsoStopFiltered), "\n"
430 if scalar @unitsToAlsoStopFiltered;
431 }
432
433 print STDERR "would NOT restart the following changed units as well: ", join(", ", sort(keys %unitsToAlsoSkip)), "\n"
434 if scalar(keys %unitsToAlsoSkip) > 0;
435
436 print STDERR "would restart systemd\n" if $restartSystemd;
437 print STDERR "would reload the following units: ", join(", ", sort(keys %unitsToReload)), "\n"
438 if scalar(keys %unitsToReload) > 0;
439 print STDERR "would restart the following units: ", join(", ", sort(keys %unitsToRestart)), "\n"
440 if scalar(keys %unitsToRestart) > 0;
441 my @unitsToStartFiltered = filterUnits(\%unitsToStart);
442 print STDERR "would start the following units: ", join(", ", @unitsToStartFiltered), "\n"
443 if scalar @unitsToStartFiltered;
444 exit 0;
445}
446
447
448syslog(LOG_NOTICE, "switching to system configuration $out");
449
450if (scalar (keys %unitsToStop) > 0) {
451 print STDERR "stopping the following units: ", join(", ", @unitsToStopFiltered), "\n"
452 if scalar @unitsToStopFiltered;
453 # Use current version of systemctl binary before daemon is reexeced.
454 system("$curSystemd/systemctl", "stop", "--", sort(keys %unitsToStop));
455}
456
457print STDERR "NOT restarting the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n"
458 if scalar(keys %unitsToSkip) > 0;
459
460# Activate the new configuration (i.e., update /etc, make accounts,
461# and so on).
462my $res = 0;
463print STDERR "activating the configuration...\n";
464system("$out/activate", "$out") == 0 or $res = 2;
465
466# Handle the activation script requesting the restart or reload of a unit.
467# We can only restart and reload (not stop/start) because the units to be
468# stopped are already stopped before the activation script is run. We do however
469# make an exception for services that are socket-activated and that have to be stopped
470# instead of being restarted.
471my %unitsToAlsoStop;
472my %unitsToAlsoSkip;
473foreach (split('\n', read_file($restartByActivationFile, err_mode => 'quiet') // "")) {
474 my $unit = $_;
475 my $baseUnit = $unit;
476 my $newUnitFile = "$out/etc/systemd/system/$baseUnit";
477
478 # Detect template instances.
479 if (!-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) {
480 $baseUnit = "$1\@.$2";
481 $newUnitFile = "$out/etc/systemd/system/$baseUnit";
482 }
483
484 my $baseName = $baseUnit;
485 $baseName =~ s/\.[a-z]*$//;
486
487 handleModifiedUnit($unit, $baseName, $newUnitFile, $activePrev, \%unitsToAlsoStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, %unitsToAlsoSkip);
488}
489unlink($restartByActivationFile);
490
491my @unitsToAlsoStopFiltered = filterUnits(\%unitsToAlsoStop);
492if (scalar(keys %unitsToAlsoStop) > 0) {
493 print STDERR "stopping the following units as well: ", join(", ", @unitsToAlsoStopFiltered), "\n"
494 if scalar @unitsToAlsoStopFiltered;
495 system("$curSystemd/systemctl", "stop", "--", sort(keys %unitsToAlsoStop));
496}
497
498print STDERR "NOT restarting the following changed units as well: ", join(", ", sort(keys %unitsToAlsoSkip)), "\n"
499 if scalar(keys %unitsToAlsoSkip) > 0;
500
501# Restart systemd if necessary. Note that this is done using the
502# current version of systemd, just in case the new one has trouble
503# communicating with the running pid 1.
504if ($restartSystemd) {
505 print STDERR "restarting systemd...\n";
506 system("$curSystemd/systemctl", "daemon-reexec") == 0 or $res = 2;
507}
508
509# Forget about previously failed services.
510system("@systemd@/bin/systemctl", "reset-failed");
511
512# Make systemd reload its units.
513system("@systemd@/bin/systemctl", "daemon-reload") == 0 or $res = 3;
514
515# Reload user units
516open my $listActiveUsers, '-|', '@systemd@/bin/loginctl', 'list-users', '--no-legend';
517while (my $f = <$listActiveUsers>) {
518 next unless $f =~ /^\s*(?<uid>\d+)\s+(?<user>\S+)/;
519 my ($uid, $name) = ($+{uid}, $+{user});
520 print STDERR "reloading user units for $name...\n";
521
522 system("@su@", "-s", "@shell@", "-l", $name, "-c",
523 "export XDG_RUNTIME_DIR=/run/user/$uid; " .
524 "$curSystemd/systemctl --user daemon-reexec; " .
525 "@systemd@/bin/systemctl --user start nixos-activation.service");
526}
527
528close $listActiveUsers;
529
530# Set the new tmpfiles
531print STDERR "setting up tmpfiles\n";
532system("@systemd@/bin/systemd-tmpfiles", "--create", "--remove", "--exclude-prefix=/dev") == 0 or $res = 3;
533
534# Reload units that need it. This includes remounting changed mount
535# units.
536if (scalar(keys %unitsToReload) > 0) {
537 print STDERR "reloading the following units: ", join(", ", sort(keys %unitsToReload)), "\n";
538 system("@systemd@/bin/systemctl", "reload", "--", sort(keys %unitsToReload)) == 0 or $res = 4;
539 unlink($reloadListFile);
540}
541
542# Restart changed services (those that have to be restarted rather
543# than stopped and started).
544if (scalar(keys %unitsToRestart) > 0) {
545 print STDERR "restarting the following units: ", join(", ", sort(keys %unitsToRestart)), "\n";
546
547 # We split the units to be restarted into sockets and non-sockets.
548 # This is because restarting sockets may fail which is not bad by
549 # itself but which will prevent changes on the sockets. We usually
550 # restart the socket and stop the service before that. Restarting
551 # the socket will fail however when the service was re-activated
552 # in the meantime. There is no proper way to prevent that from happening.
553 my @unitsWithErrorHandling = grep { $_ !~ /\.socket$/ } sort(keys %unitsToRestart);
554 my @unitsWithoutErrorHandling = grep { $_ =~ /\.socket$/ } sort(keys %unitsToRestart);
555
556 if (scalar(@unitsWithErrorHandling) > 0) {
557 system("@systemd@/bin/systemctl", "restart", "--", @unitsWithErrorHandling) == 0 or $res = 4;
558 }
559 if (scalar(@unitsWithoutErrorHandling) > 0) {
560 # Don't print warnings from systemctl
561 no warnings 'once';
562 open(OLDERR, ">&", \*STDERR);
563 close(STDERR);
564
565 my $ret = system("@systemd@/bin/systemctl", "restart", "--", @unitsWithoutErrorHandling);
566
567 # Print stderr again
568 open(STDERR, ">&OLDERR");
569
570 if ($ret ne 0) {
571 print STDERR "warning: some sockets failed to restart. Please check your journal (journalctl -eb) and act accordingly.\n";
572 }
573 }
574 unlink($restartListFile);
575 unlink($restartByActivationFile);
576}
577
578# Start all active targets, as well as changed units we stopped above.
579# The latter is necessary because some may not be dependencies of the
580# targets (i.e., they were manually started). FIXME: detect units
581# that are symlinks to other units. We shouldn't start both at the
582# same time because we'll get a "Failed to add path to set" error from
583# systemd.
584my @unitsToStartFiltered = filterUnits(\%unitsToStart);
585print STDERR "starting the following units: ", join(", ", @unitsToStartFiltered), "\n"
586 if scalar @unitsToStartFiltered;
587system("@systemd@/bin/systemctl", "start", "--", sort(keys %unitsToStart)) == 0 or $res = 4;
588unlink($startListFile);
589
590
591# Print failed and new units.
592my (@failed, @new);
593my $activeNew = getActiveUnits;
594while (my ($unit, $state) = each %{$activeNew}) {
595 if ($state->{state} eq "failed") {
596 push @failed, $unit;
597 }
598 elsif ($state->{state} eq "auto-restart") {
599 # A unit in auto-restart state is a failure *if* it previously failed to start
600 my $lines = `@systemd@/bin/systemctl show '$unit'`;
601 my $info = {};
602 parseKeyValues($info, split("\n", $lines));
603
604 if ($info->{ExecMainStatus} ne '0') {
605 push @failed, $unit;
606 }
607 }
608 # Ignore scopes since they are not managed by this script but rather
609 # created and managed by third-party services via the systemd dbus API.
610 elsif ($state->{state} ne "failed" && !defined $activePrev->{$unit} && $unit !~ /\.scope$/) {
611 push @new, $unit;
612 }
613}
614
615print STDERR "the following new units were started: ", join(", ", sort(@new)), "\n"
616 if scalar @new > 0;
617
618if (scalar @failed > 0) {
619 print STDERR "warning: the following units failed: ", join(", ", sort(@failed)), "\n";
620 foreach my $unit (@failed) {
621 print STDERR "\n";
622 system("COLUMNS=1000 @systemd@/bin/systemctl status --no-pager '$unit' >&2");
623 }
624 $res = 4;
625}
626
627if ($res == 0) {
628 syslog(LOG_NOTICE, "finished switching to system configuration $out");
629} else {
630 syslog(LOG_ERR, "switching to system configuration $out failed (status $res)");
631}
632
633exit $res;