]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC/Command.pm
config: implement method to calculate derived properties from a config
[pve-container.git] / src / PVE / LXC / Command.pm
CommitLineData
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
7package PVE::LXC::Command;
8
9use strict;
10use warnings;
11
12use IO::Socket::UNIX;
13use Socket qw(SOCK_STREAM SOL_SOCKET SO_PASSCRED);
14
15use base 'Exporter';
16
17use 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
24our @EXPORT_OK = qw(
25 raw_command_transaction
26 simple_command
27 get_cgroup_path
28);
29
30# Get the command socket for a container.
31my 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.
53my 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.
69my 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:
85my 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
94my 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.
107my 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.
128sub 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.
150sub 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.
163sub 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.
190sub 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.
200sub 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 2091;