3
0
mirror of https://github.com/pragma-/pbot.git synced 2025-10-14 15:07:22 +02:00

applets/pbot-vm: Improve input to VM

- use JSONL encoding for input (use \n instead of 🔚)
- split VM input into 4k byte chunks to prevent truncation due to
  4096 byte line limitation
- produce error message instead of timing out when guest fails to
  parse input JSON
- minor refactoring of info/debug logging
This commit is contained in:
Pragmatic Software 2025-09-28 07:00:38 -07:00
parent 99c9c3b262
commit 3606aa8cc5
No known key found for this signature in database
GPG Key ID: CC916B6E3C84ECCE
5 changed files with 66 additions and 59 deletions

View File

@ -26,10 +26,9 @@ use Data::Dumper;
sub read_input($input, $buffer, $tag) { sub read_input($input, $buffer, $tag) {
my $line; my $line;
my $total_read = 0;
print STDERR "$tag waiting for input...\n"; print STDERR "$tag waiting for input...\n";
my $ret = sysread($input, my $buf, 16384); my $ret = sysread($input, my $buf, 4096);
if (not defined $ret) { if (not defined $ret) {
print STDERR "Error reading $tag: $!\n"; print STDERR "Error reading $tag: $!\n";
@ -41,19 +40,16 @@ sub read_input($input, $buffer, $tag) {
return 0; return 0;
} }
$total_read += $ret; print STDERR "$tag read $ret bytes [$buf]\n";
print STDERR "$tag read $ret bytes [$total_read total] [$buf]\n"; if ($buf ne "\n") {
chomp $buf;
$$buffer .= $buf; $$buffer .= $buf;
return undef;
return undef if $$buffer !~ s/\s*:end:\s*$//m; }
$line = $$buffer; $line = $$buffer;
chomp $line;
$$buffer = ''; $$buffer = '';
$total_read = 0;
print STDERR "-" x 40, "\n"; print STDERR "-" x 40, "\n";
print STDERR "$tag got [$line]\n"; print STDERR "$tag got [$line]\n";
@ -62,7 +58,16 @@ sub read_input($input, $buffer, $tag) {
if ($@) { if ($@) {
print STDERR "Failed to decode JSON: $@\n"; print STDERR "Failed to decode JSON: $@\n";
return undef; return {
arguments => '',
cmdline => 'sh prog.sh',
code => "echo 'Failed to decode JSON: $@'",
date => 0,
execfile => 'prog.sh',
input => '',
lang => 'sh',
sourcefile => 'prog.sh'
};
} }
$command->{arguments} //= ''; $command->{arguments} //= '';

View File

@ -24,8 +24,6 @@ sub split_line($line, %opts) {
strip_commas => 0, strip_commas => 0,
); );
print STDERR "split: [$line]\n";
%opts = (%default_opts, %opts); %opts = (%default_opts, %opts);
return () if not length $line; return () if not length $line;
@ -149,8 +147,6 @@ sub split_line($line, %opts) {
$token .= $ch; $token .= $ch;
} }
use Data::Dumper;
print STDERR "split: ", Dumper(\@args), "\n";
return @args; return @args;
} }

View File

@ -19,6 +19,9 @@ use Getopt::Long qw(GetOptionsFromArray :config pass_through no_ignore_case no_a
use Time::HiRes qw(gettimeofday); use Time::HiRes qw(gettimeofday);
use POSIX; use POSIX;
use Data::Dumper;
$Data::Dumper::Useqq = 1;
use FindBin qw($RealBin); use FindBin qw($RealBin);
use InteractiveEdit; use InteractiveEdit;
@ -136,8 +139,8 @@ sub preprocess_code($self, %opts) {
} }
unless($self->{got_run} and $self->{copy_code}) { unless($self->{got_run} and $self->{copy_code}) {
$self->debug("---- preprocess\n"); $self->info("---- preprocess\n");
$self->debug("$self->{nick} $self->{channel}: [$self->{arguments}] $self->{cmdline_options}\n$self->{code}\n", 0); $self->info("$self->{nick} $self->{channel}: [$self->{arguments}] $self->{cmdline_options}\n$self->{code}\n",0);
} }
# replace \n outside of quotes with literal newline # replace \n outside of quotes with literal newline
@ -253,8 +256,8 @@ sub execute {
$cmdline =~ s/\$options\s+//; $cmdline =~ s/\$options\s+//;
} }
$self->debug("---- executing\n"); $self->info("---- executing\n");
$self->debug("$cmdline\n$stdin\n$pretty_code\n", 0); $self->info("$cmdline\n$stdin\n$pretty_code\n", 0);
my $compile_in = { my $compile_in = {
lang => $self->{lang}, lang => $self->{lang},
@ -270,44 +273,45 @@ sub execute {
$compile_in->{'factoid'} = $self->{'factoid'} if length $self->{'factoid'}; $compile_in->{'factoid'} = $self->{'factoid'} if length $self->{'factoid'};
$compile_in->{'persist-key'} = $self->{'persist-key'} if length $self->{'persist-key'}; $compile_in->{'persist-key'} = $self->{'persist-key'} if length $self->{'persist-key'};
$self->debug("compile_in: ", Dumper($compile_in), "\n") if $self->{debug} > 5;
my $compile_json = encode_json($compile_in); my $compile_json = encode_json($compile_in);
$compile_json .= "\n:end:\n"; my $chunk_size = 4000;
my $length = length $compile_json; $self->debug("compile_json: ", Dumper($compile_json), "\n") if $self->{debug} > 5;
my $sent = 0;
my $chunk_max = 16384;
my $chunk_size = $length < $chunk_max ? $length : $chunk_max;
my $chunks_sent = 0;
# $self->debug("Sending $length bytes [$compile_json] to vm_server\n"); my @lines = unpack("(A$chunk_size)*", $compile_json);
push @lines, '';
$chunk_size -= 1; # account for newline in syswrite $self->debug("Lines:\n" . (Dumper(\@lines))) if $self->{debug} > 1;
while ($chunks_sent < $length) { foreach my $line (@lines) {
my $chunk = substr $compile_json, $chunks_sent, $chunk_size; $line .= "\n";
my $length = length $line;
my $sent = 0;
$chunks_sent += length $chunk; $self->debug("writing [$line]\n") if $self->{debug} > 1;
my $ret = syswrite($input, $chunk); while ($sent < $length) {
my $ret = syswrite($input, $line, $chunk_size + 1, $sent);
$self->debug("sent: $sent, length: $length, ret: $ret\n");
if (not defined $ret) { if (not defined $ret) {
my $error = $!; $self->info("Error sending: $!\n");
print STDERR "Error sending: $error\n"; last;
$self->debug("Error sending: $error\n"); }
last;
if ($ret == 0) {
$self->info("Sent 0 bytes. Sleep 1 sec and try again\n");
sleep 1;
next;
}
$sent += $ret;
} }
if ($ret == 0) {
print STDERR "Sent 0 bytes. Sleep 1 sec and try again\n";
$self->debug("Sent 0 bytes. Sleep 1 sec and try again\n");
sleep 1;
next;
}
$sent += $ret;
} }
my $result = ""; my $result = '';
my $got_result = 0; my $got_result = 0;
while (my $line = decode('UTF-8', <$output>, sub { sprintf '\\\\x%02X', shift })) { while (my $line = decode('UTF-8', <$output>, sub { sprintf '\\\\x%02X', shift })) {
@ -337,8 +341,8 @@ sub execute {
sub postprocess_output($self) { sub postprocess_output($self) {
unless($self->{got_run} and $self->{copy_code}) { unless($self->{got_run} and $self->{copy_code}) {
$self->debug("---- post-processing\n"); $self->info("---- post-processing\n");
$self->debug("$self->{output}\n", 0); $self->info("$self->{output}\n", 0);
} }
# backspace # backspace
@ -374,7 +378,6 @@ sub postprocess_output($self) {
# "\n" => '<nl>', # "\n" => '<nl>',
# \t is left alone # \t is left alone
); );
$self->{output} =~ s/([\e\f])/$escapes{$1}/gs; $self->{output} =~ s/([\e\f])/$escapes{$1}/gs;
@ -397,9 +400,9 @@ sub show_output($self) {
my $output = $self->{output}; my $output = $self->{output};
unless ($self->{got_run} and $self->{copy_code}) { unless ($self->{got_run} and $self->{copy_code}) {
$self->debug("---- show output\n"); $self->info("---- show output\n");
$self->debug("$output\n", 0); $self->info("$output\n", 0);
$self->debug("=========================\n", 0); $self->info("=========================\n", 0);
} }
if (exists $self->{options}->{'-paste'} or (defined $self->{got_run} and $self->{got_run} eq 'paste')) { if (exists $self->{options}->{'-paste'} or (defined $self->{got_run} and $self->{got_run} eq 'paste')) {
@ -490,6 +493,11 @@ sub show_output($self) {
} }
sub debug($self, $text, $timestamp = 1) { sub debug($self, $text, $timestamp = 1) {
return if not $self->{debug};
$self->info($text, $timestamp);
}
sub info($self, $text, $timestamp = 1) {
if (not exists $self->{logh}) { if (not exists $self->{logh}) {
open $self->{logh}, '>>:encoding(UTF-8)', "$RealBin/../log.txt" or die "Could not open log file: $!"; open $self->{logh}, '>>:encoding(UTF-8)', "$RealBin/../log.txt" or die "Could not open log file: $!";
} }
@ -498,8 +506,10 @@ sub debug($self, $text, $timestamp = 1) {
my ($sec, $usec) = gettimeofday; my ($sec, $usec) = gettimeofday;
my $time = strftime "%a %b %e %Y %H:%M:%S", localtime $sec; my $time = strftime "%a %b %e %Y %H:%M:%S", localtime $sec;
$time .= sprintf ".%03d", $usec / 1000; $time .= sprintf ".%03d", $usec / 1000;
print STDERR "$time :: $text";
print { $self->{logh} } "$time :: $text"; print { $self->{logh} } "$time :: $text";
} else { } else {
print STDERR $text;
print { $self->{logh} } $text; print { $self->{logh} } $text;
} }
} }

View File

@ -24,8 +24,6 @@ sub split_line($line, %opts) {
strip_commas => 0, strip_commas => 0,
); );
print STDERR "split: [$line]\n";
%opts = (%default_opts, %opts); %opts = (%default_opts, %opts);
return () if not length $line; return () if not length $line;
@ -149,8 +147,6 @@ sub split_line($line, %opts) {
$token .= $ch; $token .= $ch;
} }
use Data::Dumper;
print STDERR "split: ", Dumper(\@args), "\n";
return @args; return @args;
} }

View File

@ -25,8 +25,8 @@ use PBot::Imports;
# These are set by the /misc/update_version script # These are set by the /misc/update_version script
use constant { use constant {
BUILD_NAME => "PBot", BUILD_NAME => "PBot",
BUILD_REVISION => 4897, BUILD_REVISION => 4898,
BUILD_DATE => "2025-09-25", BUILD_DATE => "2025-09-28",
}; };
sub initialize {} sub initialize {}