summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClément Hermann (nodens) <nodens@nodens.org>2017-10-09 21:19:36 +0200
committerClément Hermann (nodens) <nodens@nodens.org>2017-10-09 21:19:36 +0200
commitf46b5c8109124ca40695210d4795d5d669dcc24e (patch)
tree08e1aa364f479159f4c931996456cd278c9b314a
parent61d4e242a6f2c8a82c257940d180e06d6b8b9383 (diff)
Fix long-standing bug #6398 - hangs on large text
Adapt fix from Nyko Tyni <ntyni@debian.org> for Mail::GnuPG (https://rt.cpan.org/Public/Bug/Display.html?id=21276#txn-407915). That is: using select(2) based loop to communicate with gpg process, interleaving reads and write, thus eliminating any buffer problem. As a side effect, eliminates the need for read_err_out().
-rwxr-xr-xbin/openpgp-applet140
1 files changed, 116 insertions, 24 deletions
diff --git a/bin/openpgp-applet b/bin/openpgp-applet
index bc9dd61..dd957a2 100755
--- a/bin/openpgp-applet
+++ b/bin/openpgp-applet
@@ -51,6 +51,8 @@ use 5.10.0;
our $VERSION = 1.0;
+my $DEBUG = 0;
+
use Glib qw{TRUE FALSE};
use Gtk3 qw{-init};
use Gtk3::SimpleList;
@@ -63,6 +65,8 @@ use I18N::Langinfo qw{langinfo CODESET};
use List::MoreUtils qw{none};
use DateTime;
use File::ShareDir;
+use IO::Select;
+use Errno qw(EPIPE);
use Locale::TextDomain ("OpenPGP_Applet");
use POSIX;
@@ -732,12 +736,15 @@ sub gpg_operate_on_text {
my $in_h = IO::Handle->new();
my $err_h = IO::Handle->new();
my $out_h = IO::Handle->new();
+
+
my $handles = GnuPG::Handles->new(
stdin => $in_h,
stderr => $err_h,
stdout => $out_h
);
+
my $args = {
handles => $handles,
input => $text,
@@ -748,14 +755,14 @@ sub gpg_operate_on_text {
my $pid = $operation->($args) or return;
- # We assume the sender/recipient uses the same charset as us :/
- # PGP/MIME was invented for a reason.
- print $in_h $encoding->encode($text);
- close $in_h;
+ my $read = _gpg_communicate([$out_h,$err_h],
+ [$in_h],
+ # We assume the sender/recipient uses the same charset as us :/
+ # PGP/MIME was invented for a reason.
+ { $in_h => $encoding->encode($text) });
- my ($err, $out) = read_err_out($err_h, $out_h);
- my @raw_stderr = @{$err};
- my @raw_stdout = @{$out};
+ my @raw_stderr = split(/^/m, $read->{$err_h});
+ my @raw_stdout = split(/^/m, $read->{$out_h});
waitpid $pid, 0; # Clean up the finished GnuPG process.
@@ -920,26 +927,111 @@ sub display_output {
return 1;
}
-# Read stdout and stderr at the same time, one line at a time, to
-# avoid dead-locking due to one of the buffers being full.
-sub read_err_out {
- my $err_h = shift;
- my $out_h = shift;
-
- my $err = [];
- my $out = [];
+# interleave reads and writes from gpg process
+# stolen from Mail::GnuPG
+# input parameters:
+# $rhandles - array ref with a list of file handles for reading
+# $whandles - array ref with a list of file handles for writing
+# $wbuf_of - hash ref indexed by the stringified handles
+# containing the data to write
+# return value:
+# $rbuf_of - hash ref indexed by the stringified handles
+# containing the data that has been read
+#
+# read and write errors due to EPIPE (gpg exit) are skipped silently on the
+# assumption that gpg will explain the problem on the error handle
+#
+# other errors cause a non-fatal warning, processing continues on the rest
+# of the file handles
+#
+# NOTE: all the handles get closed inside this function
+
+sub _gpg_communicate {
+ my $blocksize = 2048;
+ my ($rhandles, $whandles, $wbuf_of) = @_;
+ my $rbuf_of = {};
+
+ # the current write offsets, again indexed by the stringified handle
+ my $woffset_of;
+
+ my $reader = IO::Select->new;
+ for (@$rhandles) {
+ $reader->add($_);
+ $rbuf_of->{$_} = '';
+ }
- while (1) {
- my $err_l = <$err_h>;
- my $out_l = <$out_h>;
- push @{$err}, $err_l if defined $err_l;
- push @{$out}, $out_l if defined $out_l;
- last unless ($err_l || $out_l);
+ my $writer = IO::Select->new;
+ for (@$whandles) {
+ die("no data supplied for handle " . fileno($_)) if !exists $wbuf_of->{$_};
+ if ($wbuf_of->{$_}) {
+ $writer->add($_);
+ } else { # nothing to write
+ close $_;
+ }
}
- close $err_h;
- close $out_h;
- return ($err, $out);
+ # we'll handle EPIPE explicitly below
+ local $SIG{PIPE} = 'IGNORE';
+
+ while ($reader->handles || $writer->handles) {
+ my @ready = IO::Select->select($reader, $writer, undef, undef);
+ if (!@ready) {
+ die("error doing select: $!");
+ }
+ my ($rready, $wready, $eready) = @ready;
+ if (@$eready) {
+ die("select returned an unexpected exception handle, this shouldn't happen");
+ }
+ for my $rhandle (@$rready) {
+ my $n = fileno($rhandle);
+ my $count = sysread($rhandle, $rbuf_of->{$rhandle},
+ $blocksize, length($rbuf_of->{$rhandle}));
+ warn("read $count bytes from handle $n") if $DEBUG;
+ if (!defined $count) { # read error
+ if ($!{EPIPE}) {
+ warn("read failure (gpg exited?) from handle $n: $!")
+ if $DEBUG;
+ } else {
+ warn("read failure from handle $n: $!");
+ }
+ $reader->remove($rhandle);
+ close $rhandle;
+ next;
+ }
+ if ($count == 0) { # EOF
+ warn("read done from handle $n") if $DEBUG;
+ $reader->remove($rhandle);
+ close $rhandle;
+ next;
+ }
+ }
+ for my $whandle (@$wready) {
+ my $n = fileno($whandle);
+ $woffset_of->{$whandle} = 0 if !exists $woffset_of->{$whandle};
+ my $count = syswrite($whandle, $wbuf_of->{$whandle},
+ $blocksize, $woffset_of->{$whandle});
+ if (!defined $count) {
+ if ($!{EPIPE}) { # write error
+ warn("write failure (gpg exited?) from handle $n: $!")
+ if $DEBUG;
+ } else {
+ warn("write failure from handle $n: $!");
+ }
+ $writer->remove($whandle);
+ close $whandle;
+ next;
+ }
+ warn("wrote $count bytes to handle $n") if $DEBUG;
+ $woffset_of->{$whandle} += $count;
+ if ($woffset_of->{$whandle} >= length($wbuf_of->{$whandle})) {
+ warn("write done to handle $n") if $DEBUG;
+ $writer->remove($whandle);
+ close $whandle;
+ next;
+ }
+ }
+ }
+ return $rbuf_of;
}
sub update_icon {