Some details about erlang:open_port/2

erlang:open_port({spawn, Command}, …)
erlang:open_port({spawn_executable, Command}, …)

The following source code is from otp_src_R14B01/erts/emulator/sys/unix/sys.c
/*
  [arndt] In most Unix systems, including Solaris 2.5, 'fork' allocates memory
  in swap space for the child of a 'fork', whereas 'vfork' does not do this.
  The natural call to use here is therefore 'vfork'. Due to a bug in
  'vfork' in Solaris 2.5 (apparently fixed in 2.6), using 'vfork'
  can be dangerous in what seems to be these circumstances:
      If the child code under a vfork sets the signal action to SIG_DFL
      (or SIG_IGN)
      for any signal which was previously set to a signal handler, the
      state of the parent is clobbered, so that the later arrival of
      such a signal yields a sigsegv in the parent. If the signal was
      not set to a signal handler, but ignored, all seems to work.
  If you change the forking code below, beware of this.
 */

static ErlDrvData
spawn_start(ErlDrvPort port_num, char* name, SysDriverOpts* opts)
{
#define CMD_LINE_PREFIX_STR "exec "
#define CMD_LINE_PREFIX_STR_SZ (sizeof(CMD_LINE_PREFIX_STR) - 1)

    int ifd[2], ofd[2], len, pid, i;
    char **volatile new_environ; /* volatile since a vfork() then cannot
                                    cause 'new_environ' to be clobbered
                                    in the parent process. */
    int saved_errno;
    long res;
    char *cmd_line;
#ifndef QNX
    int unbind;
#endif
#if !DISABLE_VFORK
    int no_vfork;
    size_t no_vfork_sz = sizeof(no_vfork);

    no_vfork = (erts_sys_getenv("ERL_NO_VFORK",
                (char *) &no_vfork,
                &no_vfork_sz) >= 0);
#endif

    switch (opts->read_write) {
    case DO_READ:
        if (pipe(ifd) < 0)
            return ERL_DRV_ERROR_ERRNO;
        if (ifd[0] >= max_files) {
            close_pipes(ifd, ofd, opts->read_write);
            errno = EMFILE;
            return ERL_DRV_ERROR_ERRNO;
        }
        ofd[1] = -1;        /* keep purify happy */
        break;
    case DO_WRITE:
        if (pipe(ofd) < 0)
            return ERL_DRV_ERROR_ERRNO;
        if (ofd[1] >= max_files) {
            close_pipes(ifd, ofd, opts->read_write);
            errno = EMFILE;
            return ERL_DRV_ERROR_ERRNO;
        }
        ifd[0] = -1;        /* keep purify happy */
        break;
    case DO_READ|DO_WRITE:
        if (pipe(ifd) < 0)
            return ERL_DRV_ERROR_ERRNO;
        errno = EMFILE;        /* default for next two conditions */
        if (ifd[0] >= max_files || pipe(ofd) < 0) {
            close_pipes(ifd, ofd, DO_READ);
            return ERL_DRV_ERROR_ERRNO;
        }
        if (ofd[1] >= max_files) {
            close_pipes(ifd, ofd, opts->read_write);
            errno = EMFILE;
            return ERL_DRV_ERROR_ERRNO;
        }
        break;
    default:
        ASSERT(0);
        return ERL_DRV_ERROR_GENERAL;
    }

    if (opts->spawn_type == ERTS_SPAWN_EXECUTABLE) {
        /* started with spawn_executable, not with spawn */
        len = strlen(name);
        cmd_line = (char *) erts_alloc_fnf(ERTS_ALC_T_TMP, len + 1);
        if (!cmd_line) {
            close_pipes(ifd, ofd, opts->read_write);
            errno = ENOMEM;
            return ERL_DRV_ERROR_ERRNO;
        }
        memcpy((void *) cmd_line,(void *) name, len);
        cmd_line[len] = '';
        if (access(cmd_line,X_OK) != 0) {
            int save_errno = errno;
            erts_free(ERTS_ALC_T_TMP, cmd_line);
            errno = save_errno;
            return ERL_DRV_ERROR_ERRNO;
        }
    } else {
        /* make the string suitable for giving to "sh" */
        len = strlen(name);
        cmd_line = (char *) erts_alloc_fnf(ERTS_ALC_T_TMP,
                           CMD_LINE_PREFIX_STR_SZ + len + 1);
        if (!cmd_line) {
            close_pipes(ifd, ofd, opts->read_write);
            errno = ENOMEM;
            return ERL_DRV_ERROR_ERRNO;
        }
        memcpy((void *) cmd_line,
               (void *) CMD_LINE_PREFIX_STR,
               CMD_LINE_PREFIX_STR_SZ);
        memcpy((void *) (cmd_line + CMD_LINE_PREFIX_STR_SZ),
               (void *) name, len);
        cmd_line[CMD_LINE_PREFIX_STR_SZ + len] = '';
    }

    erts_smp_rwmtx_rlock(&environ_rwmtx);

    if (opts->envir == NULL) {
        new_environ = environ;
    } else if ((new_environ = build_unix_environment(opts->envir)) == NULL) {
        erts_smp_rwmtx_runlock(&environ_rwmtx);
        erts_free(ERTS_ALC_T_TMP, (void *) cmd_line);
        errno = ENOMEM;
        return ERL_DRV_ERROR_ERRNO;
    }

#ifndef QNX
    /* Block child from SIGINT and SIGUSR1. Must be before fork()
       to be safe. */
    block_signals();

    CHLD_STAT_LOCK;

    unbind = erts_sched_bind_atfork_prepare();

#if !DISABLE_VFORK
    /* See fork/vfork discussion before this function. */
    if (no_vfork) {
#endif

    DEBUGF(("Using fork\n"));
    pid = fork();

    if (pid == 0) {
        /* The child! Setup child... */

        if (erts_sched_bind_atfork_child(unbind) != 0)
        goto child_error;

        /* OBSERVE!
         * Keep child setup after vfork() (implemented below and in
         * erl_child_setup.c) up to date if changes are made here.
         */

        if (opts->use_stdio) {
            if (opts->read_write & DO_READ) {
                /* stdout for process */
                if (dup2(ifd[1], 1) < 0)
                    goto child_error;
                if(opts->redir_stderr)
                    /* stderr for process */
                    if (dup2(ifd[1], 2) < 0)
                        goto child_error;
            }
            if (opts->read_write & DO_WRITE)
                /* stdin for process */
                if (dup2(ofd[0], 0) < 0)
                    goto child_error;
        }
        else {    /* XXX will fail if ofd[0] == 4 (unlikely..) */
            if (opts->read_write & DO_READ)
                if (dup2(ifd[1], 4) < 0)
                    goto child_error;
            if (opts->read_write & DO_WRITE)
                if (dup2(ofd[0], 3) < 0)
                    goto child_error;
        }

        for (i = opts->use_stdio ? 3 : 5; i < max_files; i++)
            (void) close(i);

        if (opts->wd && chdir(opts->wd) < 0)
            goto child_error;

#if defined(USE_SETPGRP_NOARGS)        /* SysV */
        (void) setpgrp();
#elif defined(USE_SETPGRP)        /* BSD */
        (void) setpgrp(0, getpid());
#else                    /* POSIX */
        (void) setsid();
#endif

        unblock_signals();

        if (opts->spawn_type == ERTS_SPAWN_EXECUTABLE) {
            if (opts->argv == NULL) {
                execle(cmd_line,cmd_line,(char *) NULL, new_environ);
            } else {
                if (opts->argv[0] == erts_default_arg0) {
                    opts->argv[0] = cmd_line;
                }
                execve(cmd_line, opts->argv, new_environ);
                if (opts->argv[0] == cmd_line) {
                    opts->argv[0] = erts_default_arg0;
                }
            }
        } else {
            execle("/bin/sh","sh","-c",cmd_line,(char *)NULL,new_environ);
        }
    child_error:
        _exit(1);
    }

    ...
}

Advertisements
Posted in erlang | Tagged | Leave a comment

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!

Posted in erlang | Tagged | Leave a comment

Hello world!

Welcome to WordPress.com. This is your first post. Edit or delete it and start blogging!

Posted in Uncategorized | 1 Comment