]>
Commit | Line | Data |
---|---|---|
eae43687 | 1 | package PVE::Systemd; |
f024a872 WB |
2 | |
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use Net::DBus qw(dbus_uint32 dbus_uint64); | |
7 | use Net::DBus::Callback; | |
8 | use Net::DBus::Reactor; | |
9 | ||
10 | # NOTE: This calls the dbus main loop and must not be used when another dbus | |
11 | # main loop is being used as we need to wait for the JobRemoved signal. | |
12 | # Polling the job status instead doesn't work because this doesn't give us the | |
13 | # distinction between success and failure. | |
14 | # | |
15 | # Note that the description is mandatory for security reasons. | |
16 | sub enter_systemd_scope { | |
17 | my ($unit, $description, %extra) = @_; | |
18 | die "missing description\n" if !defined($description); | |
19 | ||
20 | my $timeout = delete $extra{timeout}; | |
21 | ||
22 | $unit .= '.scope'; | |
23 | my $properties = [ [PIDs => [dbus_uint32($$)]] ]; | |
24 | ||
25 | foreach my $key (keys %extra) { | |
26 | if ($key eq 'Slice' || $key eq 'KillMode') { | |
27 | push @{$properties}, [$key, $extra{$key}]; | |
28 | } elsif ($key eq 'CPUShares') { | |
29 | push @{$properties}, [$key, dbus_uint64($extra{$key})]; | |
30 | } elsif ($key eq 'CPUQuota') { | |
31 | push @{$properties}, ['CPUQuotaPerSecUSec', | |
32 | dbus_uint64($extra{$key} * 10_000)]; | |
33 | } else { | |
34 | die "Don't know how to encode $key for systemd scope\n"; | |
35 | } | |
36 | } | |
37 | ||
38 | my $job; | |
39 | my $done = 0; | |
40 | ||
41 | my $bus = Net::DBus->system(); | |
42 | my $reactor = Net::DBus::Reactor->main(); | |
43 | ||
44 | my $service = $bus->get_service('org.freedesktop.systemd1'); | |
45 | my $if = $service->get_object('/org/freedesktop/systemd1', 'org.freedesktop.systemd1.Manager'); | |
46 | # Connect to the JobRemoved signal since we want to wait for it to finish | |
47 | my $sigid; | |
48 | my $timer; | |
49 | my $cleanup = sub { | |
50 | my ($no_shutdown) = @_; | |
51 | $if->disconnect_from_signal('JobRemoved', $sigid) if defined($if); | |
52 | $if = undef; | |
53 | $sigid = undef; | |
54 | $reactor->remove_timeout($timer) if defined($timer); | |
55 | $timer = undef; | |
56 | return if $no_shutdown; | |
57 | $reactor->shutdown(); | |
58 | }; | |
59 | ||
60 | $sigid = $if->connect_to_signal('JobRemoved', sub { | |
61 | my ($id, $removed_job, $signaled_unit, $result) = @_; | |
62 | return if $signaled_unit ne $unit || $removed_job ne $job; | |
63 | $cleanup->(0); | |
64 | die "systemd job failed\n" if $result ne 'done'; | |
65 | $done = 1; | |
66 | }); | |
67 | ||
68 | my $on_timeout = sub { | |
69 | $cleanup->(0); | |
70 | die "systemd job timed out\n"; | |
71 | }; | |
72 | ||
73 | $timer = $reactor->add_timeout($timeout * 1000, Net::DBus::Callback->new(method => $on_timeout)) | |
74 | if defined($timeout); | |
75 | $job = $if->StartTransientUnit($unit, 'fail', $properties, []); | |
76 | $reactor->run(); | |
77 | $cleanup->(1); | |
78 | die "systemd job never completed\n" if !$done; | |
79 | } | |
80 | ||
81 | 1; |