2016-09-04 08:42:04 +00:00
|
|
|
#!/usr/bin/perl
|
|
|
|
|
|
|
|
use Symbol qw(gensym);
|
|
|
|
use IPC::Open3;
|
|
|
|
use POSIX qw(:errno_h);
|
|
|
|
use IO::Select;
|
|
|
|
use strict;
|
|
|
|
use threads;
|
|
|
|
use threads::shared;
|
|
|
|
use Thread::Queue;
|
|
|
|
|
|
|
|
|
|
|
|
my $opened :shared;
|
|
|
|
my $q = Thread::Queue->new();
|
|
|
|
my $okq = Thread::Queue->new();
|
|
|
|
my @producers;
|
|
|
|
|
|
|
|
if(not @ARGV) {
|
2017-08-12 16:37:52 +00:00
|
|
|
if(-t *STDIN) {
|
|
|
|
print "Usage:\n";
|
|
|
|
print " parcat file(s)\n";
|
|
|
|
print " cat argfile | parcat\n";
|
|
|
|
} else {
|
|
|
|
# Read arguments from stdin
|
|
|
|
chomp(@ARGV = <STDIN>);
|
|
|
|
}
|
2016-09-04 08:42:04 +00:00
|
|
|
}
|
|
|
|
|
2017-08-12 16:37:52 +00:00
|
|
|
my $files_to_open = 0;
|
|
|
|
# Default: fd = stdout
|
|
|
|
my $fd = 1;
|
2016-09-04 08:42:04 +00:00
|
|
|
for (@ARGV) {
|
2017-08-12 16:37:52 +00:00
|
|
|
# --rm = remove file when opened
|
|
|
|
/^--rm$/ and do { $opt::rm = 1; next; };
|
|
|
|
# -1 = output to fd 1, -2 = output to fd 2
|
|
|
|
/^-(\d+)$/ and do { $fd = $1; next; };
|
|
|
|
push @producers, threads->create('producer', $_, $fd);
|
|
|
|
$files_to_open++;
|
2016-09-04 08:42:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub producer {
|
|
|
|
# Open a file/fifo, set non blocking, enqueue fileno of the file handle
|
|
|
|
my $file = shift;
|
2017-08-12 16:37:52 +00:00
|
|
|
my $output_fd = shift;
|
2016-09-04 08:42:04 +00:00
|
|
|
open(my $fh, "<", $file) || do {
|
|
|
|
print STDERR "parcat: Cannot open $file\n";
|
|
|
|
exit(1);
|
|
|
|
};
|
2017-08-12 16:37:52 +00:00
|
|
|
# Remove file when it has been opened
|
|
|
|
if($opt::rm) {
|
|
|
|
unlink $file;
|
|
|
|
}
|
2016-09-04 08:42:04 +00:00
|
|
|
set_fh_non_blocking($fh);
|
|
|
|
$opened++;
|
2017-08-12 16:37:52 +00:00
|
|
|
# Pass the fileno to parent
|
|
|
|
$q->enqueue(fileno($fh),$output_fd);
|
2016-09-04 08:42:04 +00:00
|
|
|
# Get an OK that the $fh is opened and we can release the $fh
|
|
|
|
while(1) {
|
|
|
|
my $ok = $okq->dequeue();
|
|
|
|
if($ok == fileno($fh)) { last; }
|
|
|
|
# Not ours - very unlikely to happen
|
|
|
|
$okq->enqueue($ok);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
my $s = IO::Select->new();
|
|
|
|
my %buffer;
|
|
|
|
|
|
|
|
sub add_file {
|
2017-08-12 16:37:52 +00:00
|
|
|
my $infd = shift;
|
|
|
|
my $outfd = shift;
|
|
|
|
open(my $infh, "<&=", $infd) || die;
|
|
|
|
open(my $outfh, ">&=", $outfd) || die;
|
|
|
|
$s->add($infh);
|
2016-09-04 08:42:04 +00:00
|
|
|
# Tell the producer now opened here and can be released
|
2017-08-12 16:37:52 +00:00
|
|
|
$okq->enqueue($infd);
|
2016-09-04 08:42:04 +00:00
|
|
|
# Initialize the buffer
|
2017-08-12 16:37:52 +00:00
|
|
|
@{$buffer{$infh}{$outfd}} = ();
|
|
|
|
$Global::fh{$outfd} = $outfh;
|
2016-09-04 08:42:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub add_files {
|
|
|
|
# Non-blocking dequeue
|
2017-08-12 16:37:52 +00:00
|
|
|
my ($infd,$outfd);
|
|
|
|
do {
|
|
|
|
($infd,$outfd) = $q->dequeue_nb(2);
|
|
|
|
if(defined($outfd)) {
|
|
|
|
add_file($infd,$outfd);
|
|
|
|
}
|
|
|
|
} while(defined($outfd));
|
2016-09-04 08:42:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub add_files_block {
|
|
|
|
# Blocking dequeue
|
2017-08-12 16:37:52 +00:00
|
|
|
my ($infd,$outfd) = $q->dequeue(2);
|
|
|
|
add_file($infd,$outfd);
|
2016-09-04 08:42:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
my $fd;
|
2017-08-12 16:37:52 +00:00
|
|
|
my (@ready,$infh,$rv,$buf);
|
2016-09-04 08:42:04 +00:00
|
|
|
do {
|
|
|
|
# Wait until at least one file is opened
|
|
|
|
add_files_block();
|
2016-09-22 22:57:30 +00:00
|
|
|
while($q->pending or keys %buffer) {
|
2016-09-04 08:42:04 +00:00
|
|
|
add_files();
|
2016-09-22 22:57:30 +00:00
|
|
|
while(keys %buffer) {
|
2016-09-04 08:42:04 +00:00
|
|
|
@ready = $s->can_read(0.01);
|
|
|
|
if(not @ready) {
|
|
|
|
add_files();
|
|
|
|
}
|
2017-08-12 16:37:52 +00:00
|
|
|
for $infh (@ready) {
|
|
|
|
# There is only one key, namely the output file descriptor
|
|
|
|
for my $outfd (keys %{$buffer{$infh}}) {
|
|
|
|
$rv = sysread($infh, $buf, 65536);
|
|
|
|
if (!$rv) {
|
|
|
|
if($! == EAGAIN) {
|
|
|
|
# Would block: Nothing read
|
|
|
|
next;
|
|
|
|
} else {
|
|
|
|
# Nothing read, but would not block:
|
|
|
|
# This file is done
|
|
|
|
$s->remove($infh);
|
2019-02-13 20:32:49 +00:00
|
|
|
for(@{$buffer{$infh}{$outfd}}) {
|
|
|
|
syswrite($Global::fh{$outfd},$_);
|
|
|
|
}
|
2017-08-12 16:37:52 +00:00
|
|
|
delete $buffer{$infh};
|
|
|
|
# Closing the $infh causes it to block
|
|
|
|
# close $infh;
|
|
|
|
add_files();
|
|
|
|
next;
|
|
|
|
}
|
2016-09-04 08:42:04 +00:00
|
|
|
}
|
2017-08-12 16:37:52 +00:00
|
|
|
# Something read.
|
|
|
|
# Find \n or \r for full line
|
|
|
|
my $i = (rindex($buf,"\n")+1);
|
|
|
|
if($i) {
|
|
|
|
# Print full line
|
|
|
|
for(@{$buffer{$infh}{$outfd}}, substr($buf,0,$i)) {
|
|
|
|
syswrite($Global::fh{$outfd},$_);
|
|
|
|
}
|
|
|
|
# @buffer = remaining half line
|
|
|
|
$buffer{$infh}{$outfd} = [substr($buf,$i,$rv-$i)];
|
|
|
|
} else {
|
|
|
|
# Something read, but not a full line
|
|
|
|
push @{$buffer{$infh}{$outfd}}, $buf;
|
2016-09-04 08:42:04 +00:00
|
|
|
}
|
|
|
|
redo;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-08-12 16:37:52 +00:00
|
|
|
} while($opened < $files_to_open);
|
2016-09-04 08:42:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
for (@producers) {
|
|
|
|
$_->join();
|
|
|
|
}
|
|
|
|
|
|
|
|
sub set_fh_non_blocking {
|
|
|
|
# Set filehandle as non-blocking
|
|
|
|
# Inputs:
|
|
|
|
# $fh = filehandle to be blocking
|
|
|
|
# Returns:
|
|
|
|
# N/A
|
|
|
|
my $fh = shift;
|
|
|
|
$Global::use{"Fcntl"} ||= eval "use Fcntl qw(:DEFAULT :flock); 1;";
|
|
|
|
my $flags;
|
|
|
|
fcntl($fh, &F_GETFL, $flags) || die $!; # Get the current flags on the filehandle
|
|
|
|
$flags |= &O_NONBLOCK; # Add non-blocking to the flags
|
|
|
|
fcntl($fh, &F_SETFL, $flags) || die $!; # Set the flags on the filehandle
|
|
|
|
}
|