Search code examples
cprocessposixrakunativecall

NativeCall code for using Posix forking and piping is not working


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.


Solution

  • 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.