all lists on lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH container] add container console scrollback buffer
@ 2026-01-21 11:23 Filip Schauer
  0 siblings, 0 replies; only message in thread
From: Filip Schauer @ 2026-01-21 11:23 UTC (permalink / raw)
  To: pve-devel

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 <f.schauer@proxmox.com>
---
 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 pve-container@.service pve-container-debug@.service \
     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
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-01-21 11:23 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-01-21 11:23 [pve-devel] [PATCH container] add container console scrollback buffer Filip Schauer

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal