NativeCall code for using Posix forking and piping is not working

153 views Asked by At

OK, so I'm new to Perl, and Perl 6. I thought I'd see if I could get forking and piping working, but so far have been unable to. Here's my Perl 6 code:

use NativeCall;

# http://www.perlmonks.org/?node_id=989766
our sub c_close(int32) returns int32 is native is symbol('close') { * }
sub pipe(CArray[int32]) returns int32 is native { ... }
our sub c_read(int32, Str is encoded('utf8'), size_t) returns ssize_t is native is symbol('read') { *}
our sub c_write(int32, Str is encoded('utf8'), size_t) returns ssize_t is native is symbol('write') { *}
our sub c_wait(int32 is rw) is native is symbol('wait') { * }
sub c_puts(Str) is native is symbol("puts") { * }
sub waitpid(int32, Pointer, int32) returns int32 is native {*};

my @fd := CArray[int32].new;
#my ($child, $parent);
my $pok = pipe(@fd);
if ($pok == -1) { die "Pipe failed" ; }

sub fork() returns int32 is native { ... };

# See:
# https://rosettacode.org/wiki/Fork#Perl_6
my $pid = fork();
if ( $pid < 0) { die "Fork failed" ; }
if ( $pid == 0) {
        print "C: I am the child\n";
        if (c_close(@fd[1]) == -1) { die "Child couldn't close fd[1]" };
        my $msg_in = "";
        say "C: starting read";
        my $nread = c_read(@fd[0], $msg_in, 80);
        print "C: nread=$nread\n";
        print "C: message:$msg_in.\n";
        c_close(@fd[0]);
} else {
        print "P: I am the parent of $pid\n";
        if (c_close(@fd[0]) == -1) { die "Parent couldn't close fd[0]"; } ;
        my $msg  = "Hello from parent";
        my $len =  $msg.encode('utf8').bytes + 1;
        print "P: test put string: ";
        c_puts($msg);
        #print "P: len=$len\n";
        my $nwritten =c_write(@fd[1], $msg, $len);
        print "P: len $len, wrote $nwritten\n";
        say "P: Finished writing";
        c_close(@fd[1]);
        #my $null= 0;
        #c_wait($null);
        my $stat_loc;
        waitpid($pid, $stat_loc ,0);
}

This is the result of running it:

P: I am the parent of 25809
C: I am the child
C: starting read
C: nread=-1
C: message:.
P: test put string: Hello from parent
P: len 18, wrote -1
P: Finished writing

It seems that the c_read() function is not blocking for some reason, which AFAIK is impossible. Not that I know much about forking anyway.

Any ideas what the fix is?

Answer

Update 19-Dec-2016

Thanks to @timotimo, I was able to get a working solution. It seems likely my effort could be improved upon. I don't think it would work properly with UTF-8, for example. Anyhoo, at least "it works".

use NativeCall;

# http://www.perlmonks.org/?node_id=989766
our sub c_close(int32) returns int32 is native is symbol('close') { * }
our sub c_fork() returns int32 is native is symbol('fork') { ... };
our sub c_pipe(CArray[int32]) returns int32 is native is symbol('pipe') { ... }
our sub c_puts(Str) is native is symbol("puts") { * }
our sub c_read(int32, CArray[uint8], size_t) returns ssize_t is native is symbol('read') { *}
our sub c_wait(int32 is rw) is native is symbol('wait') { * }
our sub c_waitpid(int32, Pointer, int32) returns int32 is native is symbol('waitpid') {*};
our sub c_write(int32, Str is encoded('utf8'), size_t) returns ssize_t is native is symbol('write') { *}


my @fd := CArray[int32].new(0, 0);
my $pok = c_pipe(@fd);
if ($pok == -1) { die "Pipe failed" ; }


# See:
# https://rosettacode.org/wiki/Fork#Perl_6
my $pid = c_fork();
if ( $pid < 0) { die "Fork failed" ; }
if ( $pid == 0) {
        print "C: I am the child\n";
        if (c_close(@fd[1]) == -1) { die "Child couldn't close fd[1]" };
        my uint8 $b0 = 0;
        my @buf := CArray[uint8].new( $b0 xx 80);
        say "C: starting read";
        my $nread = c_read(@fd[0], @buf, 80);
        print "C: nread=$nread\n";
        my $msg = "";
        for (0..$nread-1) -> $i { $msg = $msg ~ chr(@buf[$i]); } ;
        print "C: message:$msg.\n";
        c_close(@fd[0]);
} else {
        print "P: I am the parent of $pid\n";
        if (c_close(@fd[0]) == -1) { die "Parent couldn't close fd[0]"; } ;
        my $msg  = "Hello from parent";
        my $len =  $msg.encode('utf8').bytes;
        print "P: test put string: ";
        c_puts($msg);
        my $nwritten =c_write(@fd[1], $msg, $len);
        print "P: len $len, wrote $nwritten\n";
        say "P: Finished writing";
        c_close(@fd[1]);
        my $stat_loc;
        c_waitpid($pid, $stat_loc ,0);
}

with the output now as expected:

P: I am the parent of 22448
C: I am the child
P: test put string: Hello from parent
C: starting read
P: len 17, wrote 17
P: Finished writing
C: nread=17
C: message:Hello from parent.

I have created a gist, revising the solution as appropriate.

1

There are 1 answers

0
timotimo On BEST ANSWER

Your problem was somewhere entirely different.

You created the CArray, but you didn't actually make space for the two ints that pipe wants to write. The writes went into who-knows-where, and your @fd had just the contents [0, 0], so you were getting BADF (Bad File Descriptor) on your reads and writes, and so they immediately returned.

strace -f is a brilliant tool whenever you work with posix api stuff. it was what gave me the right idea.

Here's the code you need to make the @fd thing work:

my @fd := CArray[int32].new(0, 0);
pipe made
here's the pipe fds
17
18
P: I am the parent of 13943
C: I am the child
C: starting read
P: test put string: Hello from parent
P: len 18, wrote 18
P: Finished writing
C: nread=18
C: message:.

PS: The message doesn't get written properly, because the Str argument to c_read doesn't work like you expect it to. You'll have to do the same CArray thing again, give it the right size (either by assigning 0 xx $size or doing @result[$size + 1] = 0), and then you'll have to decode it as utf8 or latin1 or what-have-you.