diff options
Diffstat (limited to 'src/callproc.c')
-rw-r--r-- | src/callproc.c | 518 |
1 files changed, 316 insertions, 202 deletions
diff --git a/src/callproc.c b/src/callproc.c index e3346e2eabb..3ecd6880274 100644 --- a/src/callproc.c +++ b/src/callproc.c @@ -100,6 +100,15 @@ enum }; static Lisp_Object call_process (ptrdiff_t, Lisp_Object *, int, ptrdiff_t); + +#ifdef DOS_NT +# define CHILD_SETUP_TYPE int +#else +# define CHILD_SETUP_TYPE _Noreturn void +#endif + +static CHILD_SETUP_TYPE child_setup (int, int, int, char **, char **, + const char *); /* Return the current buffer's working directory, or the home directory if it's unreachable, as a string suitable for a system call. @@ -300,8 +309,7 @@ call_process (ptrdiff_t nargs, Lisp_Object *args, int filefd, #ifdef MSDOS /* Demacs 1.1.1 91/10/16 HIRANO Satoshi */ char *tempfile = NULL; #else - sigset_t oldset; - pid_t pid; + pid_t pid = -1; #endif int child_errno; int fd_output, fd_error; @@ -405,9 +413,8 @@ call_process (ptrdiff_t nargs, Lisp_Object *args, int filefd, if (! (NILP (buffer) || EQ (buffer, Qt) || FIXNUMP (buffer))) { - Lisp_Object spec_buffer; - spec_buffer = buffer; - buffer = Fget_buffer_create (buffer); + Lisp_Object spec_buffer = buffer; + buffer = Fget_buffer_create (buffer, Qnil); /* Mention the buffer name for a better error message. */ if (NILP (buffer)) CHECK_BUFFER (spec_buffer); @@ -542,8 +549,11 @@ call_process (ptrdiff_t nargs, Lisp_Object *args, int filefd, callproc_fd[CALLPROC_STDERR] = fd_error; } + char **env = make_environment_block (current_dir); + #ifdef MSDOS /* MW, July 1993 */ - status = child_setup (filefd, fd_output, fd_error, new_argv, 0, current_dir); + status = child_setup (filefd, fd_output, fd_error, new_argv, env, + SSDATA (current_dir)); if (status < 0) { @@ -586,73 +596,10 @@ call_process (ptrdiff_t nargs, Lisp_Object *args, int filefd, #ifndef MSDOS - block_input (); - block_child_signal (&oldset); - -#ifdef WINDOWSNT - pid = child_setup (filefd, fd_output, fd_error, new_argv, 0, current_dir); -#else /* not WINDOWSNT */ - - /* vfork, and prevent local vars from being clobbered by the vfork. */ - { - Lisp_Object volatile buffer_volatile = buffer; - Lisp_Object volatile coding_systems_volatile = coding_systems; - Lisp_Object volatile current_dir_volatile = current_dir; - bool volatile display_p_volatile = display_p; - int volatile fd_error_volatile = fd_error; - int volatile filefd_volatile = filefd; - ptrdiff_t volatile count_volatile = count; - ptrdiff_t volatile sa_avail_volatile = sa_avail; - ptrdiff_t volatile sa_count_volatile = sa_count; - char **volatile new_argv_volatile = new_argv; - int volatile callproc_fd_volatile[CALLPROC_FDS]; - for (i = 0; i < CALLPROC_FDS; i++) - callproc_fd_volatile[i] = callproc_fd[i]; - - pid = vfork (); - - buffer = buffer_volatile; - coding_systems = coding_systems_volatile; - current_dir = current_dir_volatile; - display_p = display_p_volatile; - fd_error = fd_error_volatile; - filefd = filefd_volatile; - count = count_volatile; - sa_avail = sa_avail_volatile; - sa_count = sa_count_volatile; - new_argv = new_argv_volatile; - - for (i = 0; i < CALLPROC_FDS; i++) - callproc_fd[i] = callproc_fd_volatile[i]; - fd_output = callproc_fd[CALLPROC_STDOUT]; - } - - if (pid == 0) - { -#ifdef DARWIN_OS - /* Work around a macOS bug, where SIGCHLD is apparently - delivered to a vforked child instead of to its parent. See: - https://lists.gnu.org/r/emacs-devel/2017-05/msg00342.html - */ - signal (SIGCHLD, SIG_DFL); -#endif - - unblock_child_signal (&oldset); - dissociate_controlling_tty (); - - /* Emacs ignores SIGPIPE, but the child should not. */ - signal (SIGPIPE, SIG_DFL); - /* Likewise for SIGPROF. */ -#ifdef SIGPROF - signal (SIGPROF, SIG_DFL); -#endif - - child_setup (filefd, fd_output, fd_error, new_argv, 0, current_dir); - } - -#endif /* not WINDOWSNT */ - - child_errno = errno; + child_errno + = emacs_spawn (&pid, filefd, fd_output, fd_error, new_argv, env, + SSDATA (current_dir), NULL); + eassert ((child_errno == 0) == (0 < pid)); if (pid > 0) { @@ -672,9 +619,6 @@ call_process (ptrdiff_t nargs, Lisp_Object *args, int filefd, } } - unblock_child_signal (&oldset); - unblock_input (); - if (pid < 0) report_file_errno (CHILD_SETUP_ERROR_DESC, Qnil, child_errno); @@ -1188,16 +1132,6 @@ exec_failed (char const *name, int err) _exit (err == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE); } -#else - -/* Do nothing. There is no need to fail, as DOS_NT platforms do not - fork and exec, and handle alloca exhaustion in a different way. */ - -static void -exec_failed (char const *name, int err) -{ -} - #endif /* This is the last thing run in a newly forked inferior @@ -1206,8 +1140,6 @@ exec_failed (char const *name, int err) Initialize inferior's priority, pgrp, connected dir and environment. then exec another program based on new_argv. - If SET_PGRP, put the subprocess into a separate process group. - CURRENT_DIR is an elisp string giving the path of the current directory the subprocess should have. Since we can't really signal a decent error from within the child, this should be verified as an @@ -1217,12 +1149,10 @@ exec_failed (char const *name, int err) On MS-Windows, either return a pid or return -1 and set errno. On MS-DOS, either return an exit status or signal an error. */ -CHILD_SETUP_TYPE -child_setup (int in, int out, int err, char **new_argv, bool set_pgrp, - Lisp_Object current_dir) +static CHILD_SETUP_TYPE +child_setup (int in, int out, int err, char **new_argv, char **env, + const char *current_dir) { - char **env; - char *pwd_var; #ifdef WINDOWSNT int cpid; HANDLE handles[3]; @@ -1236,24 +1166,6 @@ child_setup (int in, int out, int err, char **new_argv, bool set_pgrp, src/alloca.c) it is safe because that changes the superior's static variables as if the superior had done alloca and will be cleaned up in the usual way. */ - { - char *temp; - ptrdiff_t i; - - i = SBYTES (current_dir); -#ifdef MSDOS - /* MSDOS must have all environment variables malloc'ed, because - low-level libc functions that launch subsidiary processes rely - on that. */ - pwd_var = xmalloc (i + 5); -#else - if (MAX_ALLOCA - 5 < i) - exec_failed (new_argv[0], ENOMEM); - pwd_var = alloca (i + 5); -#endif - temp = pwd_var + 4; - memcpy (pwd_var, "PWD=", 4); - lispstpcpy (temp, current_dir); #ifndef DOS_NT /* We can't signal an Elisp error here; we're in a vfork. Since @@ -1261,101 +1173,13 @@ child_setup (int in, int out, int err, char **new_argv, bool set_pgrp, should only return an error if the directory's permissions are changed between the check and this chdir, but we should at least check. */ - if (chdir (temp) < 0) + if (chdir (current_dir) < 0) _exit (EXIT_CANCELED); -#else /* DOS_NT */ - /* Get past the drive letter, so that d:/ is left alone. */ - if (i > 2 && IS_DEVICE_SEP (temp[1]) && IS_DIRECTORY_SEP (temp[2])) - { - temp += 2; - i -= 2; - } -#endif /* DOS_NT */ - - /* Strip trailing slashes for PWD, but leave "/" and "//" alone. */ - while (i > 2 && IS_DIRECTORY_SEP (temp[i - 1])) - temp[--i] = 0; - } - - /* Set `env' to a vector of the strings in the environment. */ - { - register Lisp_Object tem; - register char **new_env; - char **p, **q; - register int new_length; - Lisp_Object display = Qnil; - - new_length = 0; - - for (tem = Vprocess_environment; - CONSP (tem) && STRINGP (XCAR (tem)); - tem = XCDR (tem)) - { - if (strncmp (SSDATA (XCAR (tem)), "DISPLAY", 7) == 0 - && (SDATA (XCAR (tem)) [7] == '\0' - || SDATA (XCAR (tem)) [7] == '=')) - /* DISPLAY is specified in process-environment. */ - display = Qt; - new_length++; - } - - /* If not provided yet, use the frame's DISPLAY. */ - if (NILP (display)) - { - Lisp_Object tmp = Fframe_parameter (selected_frame, Qdisplay); - if (!STRINGP (tmp) && CONSP (Vinitial_environment)) - /* If still not found, Look for DISPLAY in Vinitial_environment. */ - tmp = Fgetenv_internal (build_string ("DISPLAY"), - Vinitial_environment); - if (STRINGP (tmp)) - { - display = tmp; - new_length++; - } - } - - /* new_length + 2 to include PWD and terminating 0. */ - if (MAX_ALLOCA / sizeof *env - 2 < new_length) - exec_failed (new_argv[0], ENOMEM); - env = new_env = alloca ((new_length + 2) * sizeof *env); - /* If we have a PWD envvar, pass one down, - but with corrected value. */ - if (egetenv ("PWD")) - *new_env++ = pwd_var; - - if (STRINGP (display)) - { - if (MAX_ALLOCA - sizeof "DISPLAY=" < SBYTES (display)) - exec_failed (new_argv[0], ENOMEM); - char *vdata = alloca (sizeof "DISPLAY=" + SBYTES (display)); - lispstpcpy (stpcpy (vdata, "DISPLAY="), display); - new_env = add_env (env, new_env, vdata); - } - - /* Overrides. */ - for (tem = Vprocess_environment; - CONSP (tem) && STRINGP (XCAR (tem)); - tem = XCDR (tem)) - new_env = add_env (env, new_env, SSDATA (XCAR (tem))); - - *new_env = 0; - - /* Remove variable names without values. */ - p = q = env; - while (*p != 0) - { - while (*q != 0 && strchr (*q, '=') == NULL) - q++; - *p = *q++; - if (*p != 0) - p++; - } - } - +#endif #ifdef WINDOWSNT prepare_standard_handles (in, out, err, handles); - set_process_dir (SSDATA (current_dir)); + set_process_dir (current_dir); /* Spawn the child. (See w32proc.c:sys_spawnve). */ cpid = spawnve (_P_NOWAIT, new_argv[0], new_argv, env); reset_standard_handles (in, out, err, handles); @@ -1391,6 +1215,183 @@ child_setup (int in, int out, int err, char **new_argv, bool set_pgrp, #endif /* not WINDOWSNT */ } +/* Start a new asynchronous subprocess. If successful, return zero + and store the process identifier of the new process in *NEWPID. + Use STDIN, STDOUT, and STDERR as standard streams for the new + process. Use ARGV as argument vector for the new process; use + process image file ARGV[0]. Use ENVP for the environment block for + the new process. Use CWD as working directory for the new process. + If PTY is not NULL, it must be a pseudoterminal device. If PTY is + NULL, don't perform any terminal setup. */ + +int +emacs_spawn (pid_t *newpid, int std_in, int std_out, int std_err, + char **argv, char **envp, const char *cwd, const char *pty) +{ + sigset_t oldset; + int pid; + + block_input (); + block_child_signal (&oldset); + +#ifndef WINDOWSNT + /* vfork, and prevent local vars from being clobbered by the vfork. */ + pid_t *volatile newpid_volatile = newpid; + const char *volatile cwd_volatile = cwd; + const char *volatile pty_volatile = pty; + char **volatile argv_volatile = argv; + int volatile stdin_volatile = std_in; + int volatile stdout_volatile = std_out; + int volatile stderr_volatile = std_err; + char **volatile envp_volatile = envp; + +#ifdef DARWIN_OS + /* Darwin doesn't let us run setsid after a vfork, so use fork when + necessary. Below, we reset SIGCHLD handling after a vfork, as + apparently macOS can mistakenly deliver SIGCHLD to the child. */ + if (pty != NULL) + pid = fork (); + else + pid = vfork (); +#else + pid = vfork (); +#endif + + newpid = newpid_volatile; + cwd = cwd_volatile; + pty = pty_volatile; + argv = argv_volatile; + std_in = stdin_volatile; + std_out = stdout_volatile; + std_err = stderr_volatile; + envp = envp_volatile; + + if (pid == 0) +#endif /* not WINDOWSNT */ + { + bool pty_flag = pty != NULL; + /* Make the pty be the controlling terminal of the process. */ +#ifdef HAVE_PTYS + dissociate_controlling_tty (); + + /* Make the pty's terminal the controlling terminal. */ + if (pty_flag && std_in >= 0) + { +#ifdef TIOCSCTTY + /* We ignore the return value + because faith@cs.unc.edu says that is necessary on Linux. */ + ioctl (std_in, TIOCSCTTY, 0); +#endif + } +#if defined (LDISC1) + if (pty_flag && std_in >= 0) + { + struct termios t; + tcgetattr (std_in, &t); + t.c_lflag = LDISC1; + if (tcsetattr (std_in, TCSANOW, &t) < 0) + emacs_perror ("create_process/tcsetattr LDISC1"); + } +#else +#if defined (NTTYDISC) && defined (TIOCSETD) + if (pty_flag && std_in >= 0) + { + /* Use new line discipline. */ + int ldisc = NTTYDISC; + ioctl (std_in, TIOCSETD, &ldisc); + } +#endif +#endif + +#if !defined (DONT_REOPEN_PTY) +/*** There is a suggestion that this ought to be a + conditional on TIOCSPGRP, or !defined TIOCSCTTY. + Trying the latter gave the wrong results on Debian GNU/Linux 1.1; + that system does seem to need this code, even though + both TIOCSCTTY is defined. */ + /* Now close the pty (if we had it open) and reopen it. + This makes the pty the controlling terminal of the subprocess. */ + if (pty_flag) + { + + /* I wonder if emacs_close (emacs_open (pty, ...)) + would work? */ + if (std_in >= 0) + emacs_close (std_in); + std_out = std_in = emacs_open (pty, O_RDWR, 0); + + if (std_in < 0) + { + emacs_perror (pty); + _exit (EXIT_CANCELED); + } + + } +#endif /* not DONT_REOPEN_PTY */ + +#ifdef SETUP_SLAVE_PTY + if (pty_flag) + { + SETUP_SLAVE_PTY; + } +#endif /* SETUP_SLAVE_PTY */ +#endif /* HAVE_PTYS */ + +#ifdef DARWIN_OS + /* Work around a macOS bug, where SIGCHLD is apparently + delivered to a vforked child instead of to its parent. See: + https://lists.gnu.org/r/emacs-devel/2017-05/msg00342.html + */ + signal (SIGCHLD, SIG_DFL); +#endif + + signal (SIGINT, SIG_DFL); + signal (SIGQUIT, SIG_DFL); +#ifdef SIGPROF + signal (SIGPROF, SIG_DFL); +#endif + + /* Emacs ignores SIGPIPE, but the child should not. */ + signal (SIGPIPE, SIG_DFL); + /* Likewise for SIGPROF. */ +#ifdef SIGPROF + signal (SIGPROF, SIG_DFL); +#endif + + /* Stop blocking SIGCHLD in the child. */ + unblock_child_signal (&oldset); + + if (pty_flag) + child_setup_tty (std_out); + + if (std_err < 0) + std_err = std_out; +#ifdef WINDOWSNT + pid = child_setup (std_in, std_out, std_err, argv, envp, cwd); +#else /* not WINDOWSNT */ + child_setup (std_in, std_out, std_err, argv, envp, cwd); +#endif /* not WINDOWSNT */ + } + + /* Back in the parent process. */ + + int vfork_error = pid < 0 ? errno : 0; + + /* Stop blocking in the parent. */ + unblock_child_signal (&oldset); + unblock_input (); + + if (pid < 0) + { + eassert (0 < vfork_error); + return vfork_error; + } + + eassert (0 < pid); + *newpid = pid; + return 0; +} + static bool getenv_internal_1 (const char *var, ptrdiff_t varlen, char **value, ptrdiff_t *valuelen, Lisp_Object env) @@ -1514,6 +1515,119 @@ egetenv_internal (const char *var, ptrdiff_t len) return 0; } +/* Create a new environment block. You can pass the returned pointer + to `execve'. Add unwind protections for all newly-allocated + objects. Don't call any Lisp code or the garbage collector while + the block is active. */ + +char ** +make_environment_block (Lisp_Object current_dir) +{ + char **env; + char *pwd_var; + + { + char *temp; + ptrdiff_t i; + + i = SBYTES (current_dir); + pwd_var = xmalloc (i + 5); + record_unwind_protect_ptr (xfree, pwd_var); + temp = pwd_var + 4; + memcpy (pwd_var, "PWD=", 4); + lispstpcpy (temp, current_dir); + +#ifdef DOS_NT + /* Get past the drive letter, so that d:/ is left alone. */ + if (i > 2 && IS_DEVICE_SEP (temp[1]) && IS_DIRECTORY_SEP (temp[2])) + { + temp += 2; + i -= 2; + } +#endif /* DOS_NT */ + + /* Strip trailing slashes for PWD, but leave "/" and "//" alone. */ + while (i > 2 && IS_DIRECTORY_SEP (temp[i - 1])) + temp[--i] = 0; + } + + /* Set `env' to a vector of the strings in the environment. */ + + { + register Lisp_Object tem; + register char **new_env; + char **p, **q; + register int new_length; + Lisp_Object display = Qnil; + + new_length = 0; + + for (tem = Vprocess_environment; + CONSP (tem) && STRINGP (XCAR (tem)); + tem = XCDR (tem)) + { + if (strncmp (SSDATA (XCAR (tem)), "DISPLAY", 7) == 0 + && (SDATA (XCAR (tem)) [7] == '\0' + || SDATA (XCAR (tem)) [7] == '=')) + /* DISPLAY is specified in process-environment. */ + display = Qt; + new_length++; + } + + /* If not provided yet, use the frame's DISPLAY. */ + if (NILP (display)) + { + Lisp_Object tmp = Fframe_parameter (selected_frame, Qdisplay); + if (!STRINGP (tmp) && CONSP (Vinitial_environment)) + /* If still not found, Look for DISPLAY in Vinitial_environment. */ + tmp = Fgetenv_internal (build_string ("DISPLAY"), + Vinitial_environment); + if (STRINGP (tmp)) + { + display = tmp; + new_length++; + } + } + + /* new_length + 2 to include PWD and terminating 0. */ + env = new_env = xnmalloc (new_length + 2, sizeof *env); + record_unwind_protect_ptr (xfree, env); + /* If we have a PWD envvar, pass one down, + but with corrected value. */ + if (egetenv ("PWD")) + *new_env++ = pwd_var; + + if (STRINGP (display)) + { + char *vdata = xmalloc (sizeof "DISPLAY=" + SBYTES (display)); + record_unwind_protect_ptr (xfree, vdata); + lispstpcpy (stpcpy (vdata, "DISPLAY="), display); + new_env = add_env (env, new_env, vdata); + } + + /* Overrides. */ + for (tem = Vprocess_environment; + CONSP (tem) && STRINGP (XCAR (tem)); + tem = XCDR (tem)) + new_env = add_env (env, new_env, SSDATA (XCAR (tem))); + + *new_env = 0; + + /* Remove variable names without values. */ + p = q = env; + while (*p != 0) + { + while (*q != 0 && strchr (*q, '=') == NULL) + q++; + *p = *q++; + if (*p != 0) + p++; + } + } + + return env; +} + /* This is run before init_cmdargs. */ |