Search code examples
rakunativecall

Raku how to pass a pointer to a Buf to a native call for writing


I'm trying to wrap the read function from unistd.h, but can't get it to work. Here's what I have: (in file: read.raku)

use NativeCall;

# ssize_t read(int fd, void *buf, size_t count);
sub c_read(int32 $fd, Pointer $buf is rw, size_t $count --> ssize_t) is native is symbol('read') { * }

my $buf = Buf[byte].new(0 xx 5);
my $pbuf = nativecast(Pointer, $buf);
say c_read(3, $pbuf, 5);
say '---------------------';
say $buf;

I test it like this, from the command line (bash):

$ (exec 3< <(echo hello world); raku ./read.raku)

But I get:

5
---------------------
Buf[byte]:0x<00 00 00 00 00>

So it looks like the bytes read from FD 3 are not written to the Buf.

I also tried this instead:

use NativeCall;

# ssize_t read(int fd, void *buf, size_t count);

sub c_read(int32 $fd, Pointer $buf is rw, size_t $count --> ssize_t) is native is symbol('read') { * }
sub c_malloc(size_t $size --> Pointer) is native is symbol('malloc') { * }

my $pbuf = nativecast(Pointer[byte], c_malloc(5));

say c_read(3, $pbuf, 5);
say '---------------------';
say $pbuf[^5];

But I get a segmentation fault, I guess due to dereferencing into unauthorized memory location with $pbuf[^5]. But even just $pbuf.deref doesn't give the first byte read.

So I must have done something wrong or completely misunderstood how to work with native calls.

UPDATE: After playing around with it more, it looks like the problem with the second snippet above is with the is rw bit. This seems to work:

use NativeCall;
use NativeHelpers::Blob;

sub c_read(int32 $fd, Pointer $buf, size_t $count --> ssize_t) is native is symbol('read') { * }

sub c_malloc(size_t $size --> Pointer) is native is symbol('malloc') { * }
my $pbuf := nativecast(Pointer[byte], c_malloc(5));

say c_read(3, $pbuf, 5);
say '---------------------';
say $pbuf[^5];   # (104 101 108 108 111)

Solution

  • OK, so the problem is with the rw trait given to the Pointer $buf. I guess that results in the read function incrementing the pointer as it writes, and thus gives the wrong address when I use it later.

    Here's the working code for both cases:

    use NativeCall;
    
    # ssize_t read(int fd, void *buf, size_t count);
    
    sub c_read(int32 $fd, Pointer $buf, size_t $count --> ssize_t) is native is symbol('read') { * }
    
    
    # Passing a pointer to a Buf directly:
    {
        my $buf = Buf[byte].new(0 xx 5);
        my $pbuf = nativecast(Pointer[byte], $buf);
        say c_read(3, $pbuf, 5);
        say '---------------------';
        say $buf;
    
    }
    
    
    # Using malloc also works:
    {
        sub c_malloc(size_t $size --> Pointer) is native is symbol('malloc') { * }
        my $pbuf = nativecast(Pointer[byte], c_malloc(5));
    
        say c_read(3, $pbuf, 5);
        say '---------------------';
        say $pbuf[^5];
    }
    

    Tested it like this:

    $ (exec 3< <(echo hello world); perl6  ./read.raku)
    5
    ---------------------
    Buf[byte]:0x<68 65 6C 6C 6F>
    5
    ---------------------
    (32 119 111 114 108)