Reading device files in Erlang

Reading device files in Erlang is not very straightforword. Most devices files cannot be opened with file:open/2 except /dev/null.

1> file:open(“/dev/urandom”, [read, raw]).
{error,eisdir}
2> file:open(“/dev/zero”, [read, raw]).
{error,eisdir}
3> file:open(“/dev/null”, [read, raw]).
{ok,{file_descriptor,prim_file,{#Port<0.505>,7}}}

What’s eisdir? The man page of module `file’ says:

eisdir:
The named file is not a regular file. It  may  be  a  directory,  a
fifo, or a device.

This problem was discussed on erlang-questions.

Subject: Re: Efile_drv and non-regular files
From: Patrik Nyblom
Date: Thu, 02 Mar 2000 08:12:55 +0100

You are correct in that the error return value is misleading, there is
however a very good reason not to allow opening of other files than
regular ones; The efile driver expects the file to be regular in the
sense that operations won’t block (i.e. a “fast” device). If one could
open e.g a tape drive and write to it, the whole erlang machine will
block for several seconds/minutes/hours, which is not especially nice.
If one wants to operate on special files, one have to either write a
“port program” (which will spin in a separate process) or write a
loadable driver, which uses threads or the io multiplexing mechanisms to
avoid hanging the emulator.

Note that the reason why the efile driver is written in this way is that
one cannot io multiplex (i.e. use select/poll) on regular files in a
meaningful way on lots of u*x’es. Using threads is inefficient and hard
to port, so that idea is also dropped (regarding regular files). Of
course one could allow opening of “fast” devices through the efile
driver, but I know of no portable way of desciding if a file represents
a “fast” device.

(Refer to: http://www.erlang.org/cgi-bin/ezmlm-cgi/4/1051)

Now let’s explore some Erlang source code.

File: otp_src_R14B01/erts/emulator/drivers/unix/unix_efile.c

int efile_openfile(Efile_error* errInfo,    /* Where to return error codes. */
char* name,              /* Name of directory to open. */
int flags,               /* Flags to user for opening. */
int* pfd,                /* Where to store the file descriptor. */
Sint64 *pSize)           /* Where to store the size of the file. */
{
    struct stat statbuf;
    int fd;
    int mode;            /* Open mode. */

    CHECK_PATHLEN(name, errInfo);

    if (stat(name, &amp;statbuf) &gt;= 0 &amp;&amp; !ISREG(statbuf)) {
        /*
        * For UNIX only, here is some ugly code to allow
        * /dev/null to be opened as a file.
        *
        * Assumption: The i-node number for /dev/null cannot be zero.
        */
        static ino_t dev_null_ino = 0;

        if (dev_null_ino == 0) {
            struct stat nullstatbuf;

            if (stat("/dev/null", &amp;nullstatbuf) &gt;= 0) {
                dev_null_ino = nullstatbuf.st_ino;
            }
        }
        if (!(dev_null_ino &amp;&amp; statbuf.st_ino == dev_null_ino)) {
            errno = EISDIR;
            return check_error(-1, errInfo);
        }
    }
    ...
}

Finally I read a device file in this way:

%%--------------------------------------------------------------------
%% @doc
%% Produce a random binary whose size is Size.
%% Data of the binary is read from /dev/urandom.
%% @end
%%--------------------------------------------------------------------
-spec random_binary(integer()) -&gt; binary().

random_binary(Size) -&gt;
    Flag = process_flag(trap_exit, true),
    Cmd = lists:flatten(io_lib:format("head -c ~p /dev/urandom~n", [Size])),
    Port = open_port({spawn, Cmd}, [binary]),
    Data = random_binary(Port, []),
    process_flag(trap_exit, Flag),
    Data.

random_binary(Port, Sofar) -&gt;
    receive
        {Port, {data, Data}} -&gt;
                random_binary(Port, [Data|Sofar]);
        {'EXIT', Port, _Reason} -&gt;
                list_to_binary(lists:reverse(Sofar))
    end.

It works!

Advertisements
This entry was posted in erlang and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s