summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Stapelberg <stapelberg@debian.org>2013-07-24 20:07:21 +0200
committerMichael Stapelberg <michael@stapelberg.de>2013-07-24 20:07:21 +0200
commitefc67e156a202779d5f2f635fe4bbcfb9f5e5712 (patch)
treef0500e372fc30690c867457a19097fc781d35ac9
parent3416d0c8bdaa1ecce0c23710d950be5e91162065 (diff)
downloadinit-system-helpers-efc67e156a202779d5f2f635fe4bbcfb9f5e5712.tar.gz
refactor; implement update-state
-rwxr-xr-xscript/deb-systemd-helper132
1 files changed, 83 insertions, 49 deletions
diff --git a/script/deb-systemd-helper b/script/deb-systemd-helper
index f8587c2..3973584 100755
--- a/script/deb-systemd-helper
+++ b/script/deb-systemd-helper
@@ -35,7 +35,7 @@ deb-systemd-helper - subset of systemctl for machines not running systemd
=head1 SYNOPSIS
-B<deb-systemd-helper> enable|disable|is-enabled|was-enabled|debian-installed|reenable S<I<unit file> ...>
+B<deb-systemd-helper> enable|disable|is-enabled|was-enabled|debian-installed|update-state|reenable S<I<unit file> ...>
=head1 DESCRIPTION
@@ -54,6 +54,11 @@ updated service file. See http://bugs.debian.org/717603 for details.
The "debian-installed" action is also not present in systemctl. It returns 0 if
the state file of at least one of the given units is present.
+The "update-state" action is also not present in systemctl. It updates
+B<deb-systemd-helper>'s state file, removing obsolete entries (e.g. service
+files that are no longer shipped by the package) and adding new entries (e.g.
+new service files shipped by the package) without enabling them.
+
B<deb-systemd-helper> is intended to be used from maintscripts to enable
systemd unit files. It is specifically NOT intended to be used interactively by
users. Instead, users should run systemd and use systemctl, or not bother about
@@ -120,6 +125,8 @@ sub find_unit {
sub record_in_statefile {
my ($statefile, $service_link) = @_;
+# TODO: make this an atomic update.
+
make_path(dirname($statefile));
open(my $fh, '+>>', $statefile) or error("unable to write to $statefile");
seek($fh, 0, 0);
@@ -134,20 +141,60 @@ sub record_in_statefile {
close($fh);
}
-sub make_link {
- my ($service_path, $service_link, $action, $orig_statename) = @_;
- my $already_enabled = 1;
+# Gets the transitive closure of links, i.e. all links that need to be created
+# when enabling this service file. Not straight-forward because service files
+# can refer to other service files using Also=.
+sub get_link_closure {
+ my ($scriptname, $service_path) = @_;
- if ($action eq 'is-enabled') {
- $already_enabled = 0 if ! -l $service_link;
- } else {
- record_in_statefile("$state_dir/$orig_statename", $service_link);
+ my @links;
+
+ open my $fh, '<', $service_path or error("unable to read $service_path");
+ while (my $line = <$fh>) {
+ chomp($line);
+ my $service_link;
+
+ if ($line =~ /^\s*(WantedBy|RequiredBy)=(.+)$/i) {
+ for my $value (shellwords($2)) {
+ my $wants_dir = "/etc/systemd/system/$value";
+ $wants_dir .= '.wants' if $1 eq 'WantedBy';
+ $wants_dir .= '.requires' if $1 eq 'RequiredBy';
+ push @links, { dest => $service_path, src => "$wants_dir/$scriptname" };
+ }
+ }
+
+ if ($line =~ /^\s*Also=(.+)$/i) {
+ for my $value (shellwords($1)) {
+ push @links, get_link_closure($value, find_unit($value));
+ }
+ }
+
+ if ($line =~ /^\s*Alias=(.+)$/i) {
+ for my $value (shellwords($1)) {
+ push @links, { dest => $service_path, src => "/etc/systemd/system/$1" };
+ }
+ }
+ }
+ close($fh);
+
+ return @links;
+}
+
+sub make_systemd_links {
+ my ($scriptname, $service_path) = @_;
+
+ my $statepath = $state_dir . '/' . basename($scriptname) . '.dsh-also';
+
+ my @links = get_link_closure($scriptname, $service_path);
+ for my $link (@links) {
+ my $service_path = $link->{dest};
+ my $service_link = $link->{src};
+
+ record_in_statefile($statepath, $service_link);
my $statefile = $service_link;
$statefile =~ s,^/etc/systemd/system/,$state_dir/,;
- if (-e $statefile) {
- return $already_enabled;
- }
+ next if -e $statefile;
if (! -l $service_link) {
make_path(dirname($service_link));
@@ -167,48 +214,29 @@ sub make_link {
close($fh);
}
- return $already_enabled;
}
-sub make_systemd_links {
- my ($scriptname, $service_path, $action, $statename) = @_;
-
- $statename //= basename($scriptname) . '.dsh-also';
-
- my $already_enabled = 1;
- open my $fh, '<', $service_path or error("unable to read $service_path");
- while (my $line = <$fh>) {
- chomp($line);
- my $service_link;
-
- if ($line =~ /^\s*(WantedBy|RequiredBy)=(.+)$/i) {
- for my $value (shellwords($2)) {
- my $wants_dir = "/etc/systemd/system/$value";
- $wants_dir .= '.wants' if $1 eq 'WantedBy';
- $wants_dir .= '.requires' if $1 eq 'RequiredBy';
- $already_enabled = 0 if
- !make_link($service_path, "$wants_dir/$scriptname", $action, $statename);
- }
- }
+# In contrary to make_systemd_links(), which only modifies the state file in an
+# append-only fashion, update_state() can also remove entries from the state
+# file.
+#
+# The distinction is important because update_state() should only be called
+# when the unit file(s) are guaranteed to be on-disk, e.g. on package updates,
+# but not on package removals.
+sub update_state {
+ my ($scriptname, $service_path) = @_;
- if ($line =~ /^\s*Also=(.+)$/i) {
- for my $value (shellwords($1)) {
- $already_enabled = 0 if
- !make_systemd_links($value, find_unit($value), $action, $statename);
- }
- }
+ my @links = get_link_closure($scriptname, $service_path);
+ my $statepath = $state_dir . '/' . basename($scriptname) . '.dsh-also';
- if ($line =~ /^\s*Alias=(.+)$/i) {
- for my $value (shellwords($1)) {
- $already_enabled = 0 if
- !make_link($service_path, "/etc/systemd/system/$1", $action, $statename);
- }
- }
+# TODO: make this atomic
+ # TODO: read the old state file and dump it
+ open(my $fh, '>', $statepath);
+ for my $link (@links) {
+ print $fh $link->{src} . "\n";
}
close($fh);
-
- return $already_enabled;
}
sub was_enabled {
@@ -343,7 +371,9 @@ for my $scriptname (@ARGV) {
debug "action = $action, scriptname = $scriptname, service_path = $service_path";
if ($action eq 'is-enabled') {
- my $enabled = make_systemd_links($scriptname, $service_path, $action);
+ my @links = get_link_closure($scriptname, $service_path);
+ my @missing_links = grep { ! -l $_->{src} } @links;
+ my $enabled = (@missing_links == 0);
print STDERR ($enabled ? "enabled\n" : "disabled\n");
$rc = 0 if $enabled;
}
@@ -363,13 +393,17 @@ for my $scriptname (@ARGV) {
$rc = 0 if $enabled;
}
+ if ($action eq 'update-state') {
+ update_state($scriptname, $service_path);
+ }
+
if ($action eq 'debian-installed') {
$rc = 0 if debian_installed($service_path);
}
if ($action eq 'reenable') {
remove_links($service_path);
- make_systemd_links($scriptname, $service_path, $action, undef);
+ make_systemd_links($scriptname, $service_path);
}
if ($action eq 'disable') {
@@ -385,7 +419,7 @@ for my $scriptname (@ARGV) {
}
if ($action eq 'enable') {
- make_systemd_links($scriptname, $service_path, $action, undef);
+ make_systemd_links($scriptname, $service_path);
}
}