fix #1819: fork_worker: ensure sync'ed workers control terminal
[pve-common.git] / src / PVE / Systemd.pm
1 package PVE::Systemd;
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;