]>
Commit | Line | Data |
---|---|---|
80c7e72f WB |
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, | |
5bbcdd91 WB |
19 | LXC_CMD_FREEZE => 15, |
20 | LXC_CMD_UNFREEZE => 16, | |
80c7e72f WB |
21 | LXC_CMD_GET_LIMITING_CGROUP => 19, |
22 | }; | |
23 | ||
24 | our @EXPORT_OK = qw( | |
25 | raw_command_transaction | |
26 | simple_command | |
27 | get_cgroup_path | |
28 | ); | |
29 | ||
30 | # Get the command socket for a container. | |
31 | my sub _get_command_socket($) { | |
32 | my ($vmid) = @_; | |
33 | ||
34 | my $sock = IO::Socket::UNIX->new( | |
35 | Type => SOCK_STREAM(), | |
36 | Peer => "\0/var/lib/lxc/$vmid/command", | |
37 | ); | |
38 | if (!defined($sock)) { | |
39 | return undef if $!{ECONNREFUSED}; | |
40 | die "failed to connect to command socket: $!\n"; | |
41 | } | |
42 | ||
43 | # The documentation for this talks more about the receiving end, and it | |
44 | # also *mostly works without, but then the kernel *sometimes* fails to | |
45 | # provide correct credentials. | |
46 | setsockopt($sock, SOL_SOCKET, SO_PASSCRED, 1) | |
47 | or die "failed to pass credentials to command socket: $!\n"; | |
48 | ||
49 | return $sock; | |
50 | } | |
51 | ||
52 | # Create an lxc_cmd_req struct. | |
53 | my sub _lxc_cmd_req($$) { | |
54 | my ($cmd, $datalen) = @_; | |
55 | ||
56 | # struct lxc_cmd_req { | |
57 | # lxc_cmd_t cmd; | |
58 | # int datalen; | |
59 | # const void *data; | |
60 | # }; | |
61 | # | |
62 | # Obviously the pointer makes no sense in the payload so we just use NULL. | |
63 | my $packet = pack('i!i!L!', $cmd, $datalen, 0); | |
64 | ||
65 | return $packet; | |
66 | } | |
67 | ||
68 | # Unpack an lxc_cmd_rsp into result into its result and payload length. | |
69 | my sub _unpack_lxc_cmd_rsp($) { | |
70 | my ($packet) = @_; | |
71 | ||
72 | #struct lxc_cmd_rsp { | |
73 | # int ret; /* 0 on success, -errno on failure */ | |
74 | # int datalen; | |
75 | # void *data; | |
76 | #}; | |
77 | ||
78 | # We drop the pointless pointer value. | |
79 | my ($ret, $len, undef) = unpack("i!i!L!", $packet); | |
80 | ||
81 | return ($ret, $len); | |
82 | } | |
83 | ||
84 | # Send a complete packet: | |
85 | my sub _do_send($$) { | |
86 | my ($sock, $data) = @_; | |
87 | my $sent = send($sock, $data, 0) | |
88 | // die "failed to send to command socket: $!\n"; | |
89 | die "short write on command socket ($sent != ".length($data).")\n" | |
90 | if $sent != length($data); | |
91 | } | |
92 | ||
6973a214 | 93 | # Receive a complete packet: |
80c7e72f WB |
94 | my sub _do_recv($\$$) { |
95 | my ($sock, $scalar, $len) = @_; | |
96 | my $got = recv($sock, $$scalar, $len, 0) | |
97 | // die "failed to read from command socket: $!\n"; | |
98 | die "short read on command socket ($len != ".length($$scalar).")\n" | |
99 | if length($$scalar) != $len; | |
100 | } | |
101 | ||
102 | # Receive a response from an lxc command socket. | |
103 | # | |
104 | # Performs the return value check (negative errno values) and returns the | |
105 | # return value and payload in array context, or just the payload in scalar | |
106 | # context. | |
107 | my sub _recv_response($) { | |
108 | my ($socket) = @_; | |
109 | ||
110 | my $buf = pack('i!i!L!', 0, 0, 0); # struct lxc_cmd_rsp | |
111 | _do_recv($socket, $buf, length($buf)); | |
112 | ||
113 | my ($res, $datalen) = _unpack_lxc_cmd_rsp($buf); | |
114 | my $data; | |
115 | _do_recv($socket, $data, $datalen) | |
116 | if $datalen > 0; | |
117 | ||
118 | if ($res < 0) { | |
119 | $! = -$res; | |
120 | die "command failed: $!\n"; | |
121 | } | |
122 | ||
123 | return wantarray ? ($res, $data) : $data; | |
124 | } | |
125 | ||
126 | # Perform a command transaction: Send command & payload, receive and unpack the | |
127 | # response. | |
128 | sub raw_command_transaction($$;$) { | |
129 | my ($socket, $cmd, $data) = @_; | |
130 | ||
131 | $data //= ''; | |
132 | ||
abdddd41 | 133 | my $req = _lxc_cmd_req($cmd, length($data)); |
80c7e72f WB |
134 | _do_send($socket, $req); |
135 | if (length($data) > 0) { | |
136 | _do_send($socket, $data); | |
137 | } | |
138 | ||
139 | return _recv_response($socket); | |
140 | } | |
141 | ||
142 | # Perform a command transaction for a VMID where no command socket has been | |
143 | # established yet. | |
144 | # | |
145 | # Returns ($ret, $data): | |
146 | # $ret: numeric return value (typically 0) | |
147 | # $data: optional data returned for the command, if any, otherwise undef | |
148 | # | |
149 | # Returns undef if the container is not running, dies on errors. | |
150 | sub simple_command($$;$) { | |
151 | my ($vmid, $cmd, $data) = @_; | |
152 | ||
153 | my $socket = _get_command_socket($vmid) | |
154 | or return undef; | |
155 | return raw_command_transaction($socket, $cmd, $data); | |
156 | } | |
157 | ||
158 | # Retrieve the cgroup path for a running container. | |
159 | # If $limiting is set, get the payload path without the namespace subdirectory, | |
160 | # otherwise return the full namespaced path. | |
161 | # | |
162 | # Returns undef if the container is not running, dies on errors. | |
163 | sub get_cgroup_path($;$$) { | |
164 | my ($vmid, $subsystem, $limiting) = @_; | |
165 | ||
166 | # subsystem name must be a zero-terminated C string. | |
167 | my ($res, $data) = simple_command( | |
168 | $vmid, | |
169 | $limiting ? LXC_CMD_GET_LIMITING_CGROUP : LXC_CMD_GET_CGROUP, | |
0aec4412 | 170 | defined($subsystem) && pack('Z*', $subsystem), |
80c7e72f | 171 | ); |
4dd635cc WB |
172 | if (!defined($res) && defined($subsystem)) { |
173 | # If the container was started with an older lxc the above command | |
174 | # failed as it does not have an LXC_CMD_GET_LIMITING_CGROUP command | |
175 | # yet. Instead, we had this as an additional parameter in the subsystem | |
176 | # name. | |
177 | ($res, $data) = simple_command( | |
178 | $vmid, | |
179 | LXC_CMD_GET_CGROUP, | |
180 | pack('Z*C', $subsystem, 1), | |
181 | ); | |
182 | } | |
80c7e72f WB |
183 | return undef if !defined $res; |
184 | ||
185 | # data is a zero-terminated string: | |
186 | return unpack('Z*', $data); | |
187 | } | |
188 | ||
5bbcdd91 WB |
189 | # Send a freeze a container. This only makes sense on a pure cgroupv2 host. |
190 | sub freeze($$) { | |
191 | my ($vmid, $timeout) = @_; | |
192 | ||
193 | my ($res, undef) = | |
194 | simple_command($vmid, LXC_CMD_FREEZE, pack('l!', $timeout)); | |
195 | ||
196 | return $res; | |
197 | } | |
198 | ||
199 | # Send an unfreeze a container. This only makes sense on a pure cgroupv2 host. | |
200 | sub unfreeze($$) { | |
201 | my ($vmid, $timeout) = @_; | |
202 | ||
203 | my ($res, undef) = | |
204 | simple_command($vmid, LXC_CMD_UNFREEZE, pack('l!', $timeout)); | |
205 | ||
206 | return $res; | |
207 | } | |
208 | ||
80c7e72f | 209 | 1; |