summaryrefslogtreecommitdiff
path: root/src/callproc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/callproc.c')
-rw-r--r--src/callproc.c518
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. */