cli: print_api_result: add/use new encoding option
[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;