From: Filip Schauer <f.schauer@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH container] add container console scrollback buffer
Date: Wed, 21 Jan 2026 12:23:31 +0100 [thread overview]
Message-ID: <20260121112335.84473-1-f.schauer@proxmox.com> (raw)
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
reply other threads:[~2026-01-21 11:23 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260121112335.84473-1-f.schauer@proxmox.com \
--to=f.schauer@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.