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.
---
 lib/PublicInbox/LeiToMail.pm | 24 ++++++++++++++++--------
 t/lei_to_mail.t              | 29 ++++++++++++++++++++++++++++-
 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;
--
unsubscribe: one-click, see List-Unsubscribe header
archive: https://public-inbox.org/meta/

Reply via email to