Is there any way to read from integer file descriptors returned by the operating system open systemcall?

I opened a device file (“/dev/net/tun”) using NIFs(with help of rustler) and it returns an integer file descriptor (and also done the configuration to setup a tun device using NIFs). I want to perform read and write operations on the device file using the integer file descriptor in erlang without using NIFs but i don’t know how to and i don’t know if its possible at all.


  1. Is there any way to read from integer file descriptors returned by the operating system open systemcall ? or convert the integer file descriptor to something that erlang accepts for read and write?

  2. if it’s not possible at all, what is the reason for it? is there something related to BEAM that does not allow reading from integer file descriptors returned by operating system open systemcall ?

file:io_device/0 is documented as including file:fd/0, and maybe that name used to fit, but now it’s something like:

  #{handle => #Ref<0.2155372308.1540227106.15047>,
  owner => <0.84.0>,   
  r_buffer => #Ref<0.2155372308.1540227078.15114>,
  r_ahead_size => 0}}  

The undocumented prim_file:file_desc_to_ref/2 yields one of those:

#! /usr/bin/env escript
main(_) ->
    {ok, Input} = prim_file:file_desc_to_ref(0, [read, raw]),
    {ok, Output} = prim_file:file_desc_to_ref(1, [write, raw]),
    file:write(Output, "name: "),
    {ok, Name} = file:read_line(Input),
    file:write(Output, "age: "),
    {ok, Age} = file:read_line(Input),
    file:write(Output, ["\nHello, ", string:chomp(Name), " (", string:chomp(Age), ")\n"]).

This isn’t a pleasant script: you have to type a name, then hit enter, then type an age, then hit ctrl-d so that both of those inputs can finally be read. Maybe this will work better with /dev/net/tun

socket:open also takes an fd, but this won’t work for you as socket operations are immediately applied and these will fail.


The problem with raw file descriptors, and the reason that we don’t support them without hacky things like NIFs, is that they are inherently unsafe the same way a raw memory address is.

If we’re reading or writing to one, the descriptor could have been closed by another thread a moment before, giving us an EINVAL return in the best case or a valid-looking result for a different file at worst. Should the file descriptor happen to collide with one of the memory handles in the JIT or similar, some very fun crashes might ensue.

Hence, we don’t allow them in raw form, instead requiring that they be wrapped in a NIF that can safely manage their lifetime.