about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2020-12-31 13:51:30 +0000
committerEric Wong <e@80x24.org>2021-01-01 05:00:39 +0000
commit0c15c16c0e1b0c3724c46137f83e1962fad261f6 (patch)
tree7386afa7b98e5bb831f828e912c7bb6fe05ee3df
parenta9622fb96df7d255d9ec11ca2bc676aaa98861cc (diff)
downloadpublic-inbox-0c15c16c0e1b0c3724c46137f83e1962fad261f6.tar.gz
Users may wish to pipe output to "git am", "spamc",
or similar, so we need to support those cases and
not bail out on lseek(2) or ftruncate(2) failures.
-rw-r--r--lib/PublicInbox/LeiToMail.pm24
-rw-r--r--t/lei_to_mail.t29
2 files changed, 44 insertions, 9 deletions
diff --git a/lib/PublicInbox/LeiToMail.pm b/lib/PublicInbox/LeiToMail.pm
index 1c0f3108..4476d84c 100644
--- a/lib/PublicInbox/LeiToMail.pm
+++ b/lib/PublicInbox/LeiToMail.pm
@@ -12,7 +12,7 @@ use PublicInbox::Spawn qw(which spawn popen_rd);
 use PublicInbox::LeiDedupe;
 use Symbol qw(gensym);
 use IO::Handle; # ->autoflush
-use Fcntl qw(SEEK_SET);
+use Fcntl qw(SEEK_SET SEEK_END);
 
 my %kw2char = ( # Maildir characters
         draft => 'D',
@@ -236,26 +236,34 @@ sub _mbox_write_cb ($$$$) {
         my ($cls, $mbox, $dst, $lei) = @_;
         my $m = "eml2$mbox";
         my $eml2mbox = $cls->can($m) or die "$cls->$m missing";
-        my ($out, $pipe_lk);
-        open $out, '+>>', $dst or die "open $dst: $!";
-        # Perl does SEEK_END even with O_APPEND :<
-        seek($out, 0, SEEK_SET) or die "seek $dst: $!";
+        my ($out, $pipe_lk, $seekable);
+        # XXX should we support /dev/stdout.gz ?
+        if ($dst eq '/dev/stdout') {
+                $out = $lei->{1};
+        } else { # TODO: mbox locking
+                open $out, '+>>', $dst or die "open $dst: $!";
+                # Perl does SEEK_END even with O_APPEND :<
+                $seekable = seek($out, 0, SEEK_SET);
+                die "seek $dst: $!\n" if !$seekable && !$!{ESPIPE};
+        }
         my $jobs = $lei->{opt}->{jobs} // 0;
         my $atomic = $jobs > 1;
         my $dedupe = $lei->{dedupe} = PublicInbox::LeiDedupe->new($lei);
         state $zsfx_allow = join('|', keys %zsfx2cmd);
         my ($zsfx) = ($dst =~ /\.($zsfx_allow)\z/);
         if ($lei->{opt}->{augment}) {
-                if (-s $out && $dedupe->prepare_dedupe) {
+                if ($seekable && -s $out && $dedupe->prepare_dedupe) {
                         my $rd = $zsfx ? decompress_src($out, $zsfx, $lei) :
                                         dup_src($out);
                         PublicInbox::MboxReader->$mbox($rd, \&_augment, $lei);
+                } elsif ($seekable && !$atomic) {
+                        seek($out, 0, SEEK_END) or die "seek: $!";
                 }
                 $dedupe->pause_dedupe if $jobs; # are we forking?
-        } else {
+        } elsif ($seekable) {
                 truncate($out, 0) or die "truncate $dst: $!";
-                $dedupe->prepare_dedupe if !$jobs;
         }
+        $dedupe->prepare_dedupe if !$jobs;
         ($out, $pipe_lk) = compress_dst($out, $zsfx, $lei) if $zsfx;
         sub {
                 my ($buf, $oid, $kw) = @_;
diff --git a/t/lei_to_mail.t b/t/lei_to_mail.t
index 5be4e285..f3cc71ad 100644
--- a/t/lei_to_mail.t
+++ b/t/lei_to_mail.t
@@ -6,6 +6,7 @@ use v5.10.1;
 use Test::More;
 use PublicInbox::TestCommon;
 use PublicInbox::Eml;
+use Fcntl qw(SEEK_SET);
 require_mods(qw(DBD::SQLite));
 use_ok 'PublicInbox::LeiToMail';
 my $from = "Content-Length: 10\nSubject: x\n\nFrom hell\n";
@@ -120,15 +121,41 @@ for my $zsfx (qw(gz bz2 xz)) { # XXX should we support zst, zz, lzo, lzma?
 }
 
 unlink $fn or BAIL_OUT $!;
+require PublicInbox::MboxReader;
 if ('default deduplication uses content_hash') {
         my $wcb = PublicInbox::LeiToMail->write_cb("mboxo:$fn", $lei);
         $wcb->(\(my $x = $buf), 'deadbeef', []) for (1..2);
         undef $wcb; # undef to commit changes
         my $cmp = '';
         open my $fh, '<', $fn or BAIL_OUT $!;
-        require PublicInbox::MboxReader;
         PublicInbox::MboxReader->mboxo($fh, sub { $cmp .= shift->as_string });
         is($cmp, $buf, 'only one message written');
 }
 
+{ # stdout support
+        open my $tmp, '+>', undef or BAIL_OUT $!;
+        local $lei->{1} = $tmp;
+        my $wcb = PublicInbox::LeiToMail->write_cb("mboxrd:/dev/stdout", $lei);
+        $wcb->(\(my $x = $buf), 'deadbeef', []);
+        undef $wcb; # commit
+        seek($tmp, 0, SEEK_SET) or BAIL_OUT $!;
+        my $cmp = '';
+        PublicInbox::MboxReader->mboxrd($tmp, sub { $cmp .= shift->as_string });
+        is($cmp, $buf, 'message written to stdout');
+}
+
+SKIP: { # FIFO support
+        use PublicInbox::Spawn qw(popen_rd which);
+        use POSIX qw(mkfifo);
+        my $fn = "$tmpdir/fifo";
+        mkfifo($fn, 0600) or skip("mkfifo not supported: $!", 1);
+        my $cat = popen_rd([which('cat'), $fn]);
+        my $wcb = PublicInbox::LeiToMail->write_cb("mboxo:$fn", $lei);
+        $wcb->(\(my $x = $buf), 'deadbeef', []);
+        undef $wcb; # commit
+        my $cmp = '';
+        PublicInbox::MboxReader->mboxo($cat, sub { $cmp .= shift->as_string });
+        is($cmp, $buf, 'message written to FIFO');
+}
+
 done_testing;