1 # LXC command socket client.
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)
7 package PVE
::LXC
::Command
;
13 use Socket
qw(SOCK_STREAM SOL_SOCKET SO_PASSCRED);
18 LXC_CMD_GET_CGROUP
=> 6,
19 LXC_CMD_GET_LIMITING_CGROUP
=> 19,
23 raw_command_transaction
28 # Get the command socket for a container.
29 my sub _get_command_socket
($) {
32 my $sock = IO
::Socket
::UNIX-
>new(
33 Type
=> SOCK_STREAM
(),
34 Peer
=> "\0/var/lib/lxc/$vmid/command",
36 if (!defined($sock)) {
37 return undef if $!{ECONNREFUSED
};
38 die "failed to connect to command socket: $!\n";
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";
50 # Create an lxc_cmd_req struct.
51 my sub _lxc_cmd_req
($$) {
52 my ($cmd, $datalen) = @_;
54 # struct lxc_cmd_req {
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);
66 # Unpack an lxc_cmd_rsp into result into its result and payload length.
67 my sub _unpack_lxc_cmd_rsp
($) {
71 # int ret; /* 0 on success, -errno on failure */
76 # We drop the pointless pointer value.
77 my ($ret, $len, undef) = unpack("i!i!L!", $packet);
82 # Send a complete packet:
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);
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;
100 # Receive a response from an lxc command socket.
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
105 my sub _recv_response
($) {
108 my $buf = pack('i!i!L!', 0, 0, 0); # struct lxc_cmd_rsp
109 _do_recv
($socket, $buf, length($buf));
111 my ($res, $datalen) = _unpack_lxc_cmd_rsp
($buf);
113 _do_recv
($socket, $data, $datalen)
118 die "command failed: $!\n";
121 return wantarray ?
($res, $data) : $data;
124 # Perform a command transaction: Send command & payload, receive and unpack the
126 sub raw_command_transaction
($$;$) {
127 my ($socket, $cmd, $data) = @_;
131 my $req = _lxc_cmd_req
($cmd, length($data));
132 _do_send
($socket, $req);
133 if (length($data) > 0) {
134 _do_send
($socket, $data);
137 return _recv_response
($socket);
140 # Perform a command transaction for a VMID where no command socket has been
143 # Returns ($ret, $data):
144 # $ret: numeric return value (typically 0)
145 # $data: optional data returned for the command, if any, otherwise undef
147 # Returns undef if the container is not running, dies on errors.
148 sub simple_command
($$;$) {
149 my ($vmid, $cmd, $data) = @_;
151 my $socket = _get_command_socket
($vmid)
153 return raw_command_transaction
($socket, $cmd, $data);
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.
160 # Returns undef if the container is not running, dies on errors.
161 sub get_cgroup_path
($;$$) {
162 my ($vmid, $subsystem, $limiting) = @_;
164 # subsystem name must be a zero-terminated C string.
165 my ($res, $data) = simple_command
(
167 $limiting ? LXC_CMD_GET_LIMITING_CGROUP
: LXC_CMD_GET_CGROUP
,
168 defined($subsystem) && pack('Z*', $subsystem),
170 return undef if !defined $res;
172 # data is a zero-terminated string:
173 return unpack('Z*', $data);