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