]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/Command.pm
command: get_cgroup_path: handle undef subsystem
[pve-container.git] / src / PVE / LXC / Command.pm
1 # LXC command socket client.
2 #
3 # For now this is only used to fetch the cgroup paths.
4 # This can also be extended to replace a few more `lxc-*` CLI invocations.
5 # (such as lxc-stop, info, freeze, unfreeze, or getting the init pid)
6
7 package PVE::LXC::Command;
8
9 use strict;
10 use warnings;
11
12 use IO::Socket::UNIX;
13 use Socket qw(SOCK_STREAM SOL_SOCKET SO_PASSCRED);
14
15 use base 'Exporter';
16
17 use constant {
18 LXC_CMD_GET_CGROUP => 6,
19 LXC_CMD_GET_LIMITING_CGROUP => 19,
20 };
21
22 our @EXPORT_OK = qw(
23 raw_command_transaction
24 simple_command
25 get_cgroup_path
26 );
27
28 # Get the command socket for a container.
29 my sub _get_command_socket($) {
30 my ($vmid) = @_;
31
32 my $sock = IO::Socket::UNIX->new(
33 Type => SOCK_STREAM(),
34 Peer => "\0/var/lib/lxc/$vmid/command",
35 );
36 if (!defined($sock)) {
37 return undef if $!{ECONNREFUSED};
38 die "failed to connect to command socket: $!\n";
39 }
40
41 # The documentation for this talks more about the receiving end, and it
42 # also *mostly works without, but then the kernel *sometimes* fails to
43 # provide correct credentials.
44 setsockopt($sock, SOL_SOCKET, SO_PASSCRED, 1)
45 or die "failed to pass credentials to command socket: $!\n";
46
47 return $sock;
48 }
49
50 # Create an lxc_cmd_req struct.
51 my sub _lxc_cmd_req($$) {
52 my ($cmd, $datalen) = @_;
53
54 # struct lxc_cmd_req {
55 # lxc_cmd_t cmd;
56 # int datalen;
57 # const void *data;
58 # };
59 #
60 # Obviously the pointer makes no sense in the payload so we just use NULL.
61 my $packet = pack('i!i!L!', $cmd, $datalen, 0);
62
63 return $packet;
64 }
65
66 # Unpack an lxc_cmd_rsp into result into its result and payload length.
67 my sub _unpack_lxc_cmd_rsp($) {
68 my ($packet) = @_;
69
70 #struct lxc_cmd_rsp {
71 # int ret; /* 0 on success, -errno on failure */
72 # int datalen;
73 # void *data;
74 #};
75
76 # We drop the pointless pointer value.
77 my ($ret, $len, undef) = unpack("i!i!L!", $packet);
78
79 return ($ret, $len);
80 }
81
82 # Send a complete packet:
83 my sub _do_send($$) {
84 my ($sock, $data) = @_;
85 my $sent = send($sock, $data, 0)
86 // die "failed to send to command socket: $!\n";
87 die "short write on command socket ($sent != ".length($data).")\n"
88 if $sent != length($data);
89 }
90
91 # Receive a complete packet:
92 my sub _do_recv($\$$) {
93 my ($sock, $scalar, $len) = @_;
94 my $got = recv($sock, $$scalar, $len, 0)
95 // die "failed to read from command socket: $!\n";
96 die "short read on command socket ($len != ".length($$scalar).")\n"
97 if length($$scalar) != $len;
98 }
99
100 # Receive a response from an lxc command socket.
101 #
102 # Performs the return value check (negative errno values) and returns the
103 # return value and payload in array context, or just the payload in scalar
104 # context.
105 my sub _recv_response($) {
106 my ($socket) = @_;
107
108 my $buf = pack('i!i!L!', 0, 0, 0); # struct lxc_cmd_rsp
109 _do_recv($socket, $buf, length($buf));
110
111 my ($res, $datalen) = _unpack_lxc_cmd_rsp($buf);
112 my $data;
113 _do_recv($socket, $data, $datalen)
114 if $datalen > 0;
115
116 if ($res < 0) {
117 $! = -$res;
118 die "command failed: $!\n";
119 }
120
121 return wantarray ? ($res, $data) : $data;
122 }
123
124 # Perform a command transaction: Send command & payload, receive and unpack the
125 # response.
126 sub raw_command_transaction($$;$) {
127 my ($socket, $cmd, $data) = @_;
128
129 $data //= '';
130
131 my $req = _lxc_cmd_req($cmd, length($data));
132 _do_send($socket, $req);
133 if (length($data) > 0) {
134 _do_send($socket, $data);
135 }
136
137 return _recv_response($socket);
138 }
139
140 # Perform a command transaction for a VMID where no command socket has been
141 # established yet.
142 #
143 # Returns ($ret, $data):
144 # $ret: numeric return value (typically 0)
145 # $data: optional data returned for the command, if any, otherwise undef
146 #
147 # Returns undef if the container is not running, dies on errors.
148 sub simple_command($$;$) {
149 my ($vmid, $cmd, $data) = @_;
150
151 my $socket = _get_command_socket($vmid)
152 or return undef;
153 return raw_command_transaction($socket, $cmd, $data);
154 }
155
156 # Retrieve the cgroup path for a running container.
157 # If $limiting is set, get the payload path without the namespace subdirectory,
158 # otherwise return the full namespaced path.
159 #
160 # Returns undef if the container is not running, dies on errors.
161 sub get_cgroup_path($;$$) {
162 my ($vmid, $subsystem, $limiting) = @_;
163
164 # subsystem name must be a zero-terminated C string.
165 my ($res, $data) = simple_command(
166 $vmid,
167 $limiting ? LXC_CMD_GET_LIMITING_CGROUP : LXC_CMD_GET_CGROUP,
168 defined($subsystem) && pack('Z*', $subsystem),
169 );
170 return undef if !defined $res;
171
172 # data is a zero-terminated string:
173 return unpack('Z*', $data);
174 }
175
176 1;