Send a scrollback buffer of the previous 8192 bytes to new clients
connecting to a container console.

This is achieved with a basic Perl re-implementation of the dtach master
with a scrollback patch applied. [0] This dtach-scrollback-master is
started alongside the container at startup. When dtach attaches to this
master it receives the scrollback buffer.

This improves the user experience for containers and is especially
useful for many application containers that print important diagnostic
messages at startup, that would otherwise be easily missed by users
opening the container console slightly too late.

This change is backwards-compatible: upgrading does not break consoles
for already running containers, since we fall back to the previous dtach
behaviour when dtach-scrollback-master is not running.

As an alternative to implementing our own dtach-scrollback-master, we
could instead apply the scrollback patch [0] directly to dtach.

[0] https://367015.bugs.gentoo.org/attachment.cgi?id=272965

Signed-off-by: Filip Schauer <[email protected]>
---
 src/Makefile                |  2 +
 src/PVE/LXC.pm              |  5 ++
 src/dtach-scrollback-master | 99 +++++++++++++++++++++++++++++++++++++
 3 files changed, 106 insertions(+)
 create mode 100755 src/dtach-scrollback-master

diff --git a/src/Makefile b/src/Makefile
index 2baa782..07a6802 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -45,6 +45,8 @@ install: pct lxc-pve.conf pct.1 pct.conf.5 
pct.bash-completion pct.zsh-completio
     pve-userns.seccomp [email protected] [email protected] \
     lxc-pve-prestart-hook lxc-pve-autodev-hook lxc-pve-poststop-hook 
lxcnetaddbr
        PVE_GENERATING_DOCS=1 perl -I. -T -e "use PVE::CLI::pct; 
PVE::CLI::pct->verify_api();"
+       install -d $(BINDIR)
+       install -m 0755 dtach-scrollback-master $(BINDIR)
        install -d $(SBINDIR)
        install -m 0755 pct $(SBINDIR)
        install -d $(LXC_SCRIPT_DIR)
diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm
index c19c772..bab27b8 100644
--- a/src/PVE/LXC.pm
+++ b/src/PVE/LXC.pm
@@ -3100,6 +3100,11 @@ sub vm_start {
     eval {
         run_command($cmd);
 
+        my $concmd = PVE::LXC::get_console_command($vmid, $conf, -1);
+        run_command([
+            'dtach-scrollback-master', "/var/run/dtach/vzctlconsole$vmid", 
@$concmd,
+        ]);
+
         monitor_start($monitor_socket, $vmid) if defined($monitor_socket);
 
         # if debug is requested, print the log it also when the start succeeded
diff --git a/src/dtach-scrollback-master b/src/dtach-scrollback-master
new file mode 100755
index 0000000..8d348e2
--- /dev/null
+++ b/src/dtach-scrollback-master
@@ -0,0 +1,99 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use IO::Select;
+use IO::Socket::UNIX;
+use POSIX qw(EAGAIN WNOHANG setsid);
+
+use PVE::PTY;
+
+use constant {
+    MSG_PUSH => 0,
+    MSG_WINCH => 3,
+    MSG_REDRAW => 4,
+    PACKET_SIZE => 10,
+    SCROLLSIZE => 8192,
+    BUFSIZE => 4096,
+};
+
+my $socketpath = shift or die "Usage: $0 <socket> <cmd> [args]\n";
+unlink($socketpath);
+
+# Daemonize
+my $pid = fork() // die "fork failed: $!\n";
+POSIX::_exit(0) if $pid;
+POSIX::setsid();
+my $pid2 = fork() // die "fork failed: $!\n";
+POSIX::_exit(0) if $pid2;
+open STDIN, '<', '/dev/null';
+open STDOUT, '>', '/dev/null';
+open STDERR, '>', '/dev/null';
+
+$SIG{PIPE} = 'IGNORE'; # Prevent crash on client disconnect
+$SIG{INT} = $SIG{TERM} = sub { unlink $socketpath; exit(0); };
+
+my $server = IO::Socket::UNIX->new(Local => $socketpath, Listen => 128, 
Blocking => 0)
+    or die "Cannot create socket - $IO::Socket::errstr\n";
+my $pty = PVE::PTY->new();
+my $pty_fh = $pty->master();
+my $select = IO::Select->new($server, $pty_fh);
+my $scrollback = '';
+my %clientbufs;
+
+# Spawn Child
+my $cpid = fork() // die "fork failed: $!";
+if ($cpid == 0) {
+    close $server;
+    $pty->make_controlling_terminal();
+    exec(@ARGV) or die;
+}
+
+while (waitpid($cpid, WNOHANG) <= 0) {
+    for my $fh ($select->can_read(1)) {
+        if ($fh == $server) {
+            # Accept new client
+            my $client = $server->accept();
+            $client->blocking(0);
+            $select->add($client);
+            syswrite($client, $scrollback);
+            $clientbufs{$client} = '';
+        } elsif ($fh == $pty_fh) {
+            # PTY Output
+            sysread($fh, my $output, BUFSIZE) or last;
+            $scrollback = substr($scrollback . $output, -SCROLLSIZE);
+
+            for my $client (grep { $_ != $server && $_ != $pty_fh } 
$select->handles()) {
+                disconnect_client($client) if !defined(syswrite($client, 
$output)) && $! != EAGAIN;
+            }
+        } else {
+            # Client Input
+            sysread($fh, $clientbufs{$fh}, 500, length($clientbufs{$fh})) or 
do {
+                disconnect_client($fh);
+                next;
+            };
+
+            while (length($clientbufs{$fh}) >= PACKET_SIZE) {
+                my $packet = substr($clientbufs{$fh}, 0, PACKET_SIZE, '');
+                my ($type, $len, $data) = unpack('CCa8', $packet);
+                if ($type == MSG_PUSH) {
+                    syswrite($pty_fh, substr($data, 0, $len));
+                } elsif ($type == MSG_WINCH || $type == MSG_REDRAW) {
+                    my ($rows, $cols) = unpack('S2', $data);
+                    $pty->set_size($cols, $rows) if $rows > 0 && $cols > 0;
+                    kill('WINCH', $pty->get_foreground_pid());
+                }
+            }
+        }
+    }
+}
+
+unlink $socketpath;
+
+sub disconnect_client {
+    my $fh = shift;
+    $select->remove($fh);
+    delete $clientbufs{$fh};
+    close $fh;
+}
-- 
2.47.3



_______________________________________________
pve-devel mailing list
[email protected]
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to