From aed1c8b536534f45aad352ee47ed75a3aa18bc39 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Sat, 28 Feb 2026 09:03:27 -0800 Subject: [PATCH] =?UTF-8?q?Don=E2=80=99t=20stuff=20keyboard=20input=20usel?= =?UTF-8?q?essly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, document stuffing better. * src/keyboard.c (stuff_buffered_input): Give up on stuffing if it fails. * src/sysdep.c (stuff_char): Return failure indication. --- admin/notes/multi-tty | 2 ++ doc/lispref/os.texi | 18 ++++++++--------- src/emacs.c | 6 +++--- src/keyboard.c | 46 ++++++++++++++++++------------------------- src/lisp.h | 2 +- src/sysdep.c | 17 ++++++++-------- 6 files changed, 42 insertions(+), 49 deletions(-) diff --git a/admin/notes/multi-tty b/admin/notes/multi-tty index fff20a1769b..c4273fd4431 100644 --- a/admin/notes/multi-tty +++ b/admin/notes/multi-tty @@ -504,6 +504,8 @@ THINGS TO DO ** flow-ctrl.el must be updated. ** Fix stuff_char for multi-tty. Doesn't seem to be of high priority. + For security reasons stuff_char does not work by default on many + platforms, which makes such a fix even lower priority. DIARY OF CHANGES ---------------- diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi index bb768fe2da8..38d7ad44c4a 100644 --- a/doc/lispref/os.texi +++ b/doc/lispref/os.texi @@ -711,12 +711,11 @@ If @var{exit-data} is an integer, that is used as the exit status of the Emacs process. (This is useful primarily in batch operation; see @ref{Batch Mode}.) -If @var{exit-data} is a string, its contents are stuffed into the -terminal input buffer so that the shell (or whatever program next reads -input) can read them. This is only possible on some systems. Note that -recent versions of GNU/Linux disable the system API required for -stuffing the string into the terminal input, so this might not work on -your system without special privileges. +If @var{exit-data} is a string, its contents (followed by a newline) are +stuffed into the terminal input buffer so that the shell (or whatever +program next reads input) can read them. However, stuffing is silently +skipped if Emacs is not running interactively on a terminal, or if +stuffing requires special privileges or is not supported on this platform. If @var{exit-data} is neither an integer nor a string, or is omitted, that means to use the (system-specific) exit status which indicates @@ -816,6 +815,9 @@ before suspending Emacs, or this function signals an error. If @var{string} is non-@code{nil}, its characters are sent to Emacs's superior shell, to be read as terminal input. +However, this action is skipped if Emacs is not running interactively on +a terminal, or if stuffing characters into the terminal input requires +special privileges or is not supported on this platform. Before suspending, @code{suspend-emacs} runs the normal hook @code{suspend-hook}. After the user resumes Emacs, @@ -840,7 +842,7 @@ Here is an example of how you could use these hooks: @c The sit-for prevents the @code{nil} that suspend-emacs returns @c hiding the message. -Here is what you would see upon evaluating @code{(suspend-emacs "pwd")}: +Here is what you would see upon evaluating @code{(suspend-emacs)}: @smallexample @group @@ -851,8 +853,6 @@ Really suspend? @kbd{y} @group ---------- Parent Shell ---------- -bash$ pwd -/home/username bash$ fg @end group diff --git a/src/emacs.c b/src/emacs.c index 777ade9d825..6929886424f 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -2947,9 +2947,9 @@ sort_args (int argc, char **argv) DEFUN ("kill-emacs", Fkill_emacs, Skill_emacs, 0, 2, "P", doc: /* Exit the Emacs job and kill it. If ARG is an integer, return ARG as the exit program code. -If ARG is a string, stuff it as keyboard input. (This might -not work on modern systems due to security considerations, or -not at all.) +If ARG is a string, stuff it and then a newline as keyboard input, +if Emacs is running interactively on a terminal and the platform +supports and allows stuffing; this may need special privileges. Any other value of ARG, or ARG omitted, means return an exit code that indicates successful program termination. diff --git a/src/keyboard.c b/src/keyboard.c index 648dd81660a..55fb2401f2b 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -12421,19 +12421,15 @@ DEFUN ("suspend-emacs", Fsuspend_emacs, Ssuspend_emacs, 0, 1, "", If `cannot-suspend' is non-nil, or if the system doesn't support job control, run a subshell instead. -If optional arg STUFFSTRING is non-nil, its characters are stuffed -to be read as terminal input by Emacs's parent, after suspension. +If optional arg STUFFSTRING is non-nil, stuff it and then a newline as +keyboard input, if Emacs is running interactively on a terminal and the +platform supports and allows stuffing; this may need special privileges. Before suspending, run the normal hook `suspend-hook'. After resumption run the normal hook `suspend-resume-hook'. Some operating systems cannot stop the Emacs process and resume it later. -On such systems, Emacs starts a subshell instead of suspending. - -On some operating systems, stuffing characters into terminal input -buffer requires special privileges or is not supported at all. -On such systems, calling this function with non-nil STUFFSTRING might -either signal an error or silently fail to stuff the characters. */) +On such systems, Emacs starts a subshell instead of suspending. */) (Lisp_Object stuffstring) { specpdl_ref count = SPECPDL_INDEX (); @@ -12472,38 +12468,34 @@ either signal an error or silently fail to stuff the characters. */) return Qnil; } -/* If STUFFSTRING is a string, stuff its contents as pending terminal input. - Then in any case stuff anything Emacs has read ahead and not used. */ +/* If STUFFSTRING is a string, stuff its contents and then a newline as + pending terminal input. Then stuff anything Emacs has read ahead and + not used. However, do nothing if stuffing does not work. */ void stuff_buffered_input (Lisp_Object stuffstring) { #ifdef SIGTSTP /* stuff_char is defined if SIGTSTP. */ - register unsigned char *p; + int bad_stuff = 0; if (STRINGP (stuffstring)) { - register ptrdiff_t count; - - p = SDATA (stuffstring); - count = SBYTES (stuffstring); - while (count-- > 0) - stuff_char (*p++); - stuff_char ('\n'); + char *p = SSDATA (stuffstring); + for (ptrdiff_t i = SBYTES (stuffstring); !bad_stuff && 0 < i; i--) + bad_stuff = stuff_char (*p++); + if (!bad_stuff) + bad_stuff = stuff_char ('\n'); } - /* Anything we have read ahead, put back for the shell to read. */ - /* ?? What should this do when we have multiple keyboards?? - Should we ignore anything that was typed in at the "wrong" kboard? - - rms: we should stuff everything back into the kboard - it came from. */ + /* Anything we have read ahead, put back for the shell to read. + When we have multiple keyboards, we should stuff everything back + into the keyboard it came from, but fixing this is low priority as + many systems prohibit stuffing for security reasons. */ for (; kbd_fetch_ptr != kbd_store_ptr; kbd_fetch_ptr = next_kbd_event (kbd_fetch_ptr)) { - - if (kbd_fetch_ptr->kind == ASCII_KEYSTROKE_EVENT) - stuff_char (kbd_fetch_ptr->ie.code); + if (kbd_fetch_ptr->kind == ASCII_KEYSTROKE_EVENT && !bad_stuff) + bad_stuff = stuff_char (kbd_fetch_ptr->ie.code); clear_event (&kbd_fetch_ptr->ie); } diff --git a/src/lisp.h b/src/lisp.h index cefca3d6fbb..37c34dfbe84 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -5280,7 +5280,7 @@ maybe_disable_address_randomization (int argc, char **argv) extern int emacs_exec_file (char const *, char *const *, char *const *); extern void init_standard_fds (void); extern char *emacs_get_current_dir_name (void); -extern void stuff_char (char c); +extern int stuff_char (char c); extern void init_foreground_group (void); extern void sys_subshell (void); extern void sys_suspend (void); diff --git a/src/sysdep.c b/src/sysdep.c index 2f5572009fb..8895655566e 100644 --- a/src/sysdep.c +++ b/src/sysdep.c @@ -390,23 +390,22 @@ discard_tty_input (void) /* Arrange for character C to be read as the next input from the terminal. + Return 0 on success, -1 otherwise. XXX What if we have multiple ttys? */ -void +int stuff_char (char c) { - if (! (FRAMEP (selected_frame) - && FRAME_LIVE_P (XFRAME (selected_frame)) - && FRAME_TERMCAP_P (XFRAME (selected_frame)))) - return; - /* Should perhaps error if in batch mode */ #ifdef TIOCSTI - ioctl (fileno (CURTTY()->input), TIOCSTI, &c); -#else /* no TIOCSTI */ - error ("Cannot stuff terminal input characters in this version of Unix"); + if (FRAMEP (selected_frame) + && FRAME_LIVE_P (XFRAME (selected_frame)) + && FRAME_TERMCAP_P (XFRAME (selected_frame))) + return ioctl (fileno (CURTTY()->input), TIOCSTI, &c); #endif /* no TIOCSTI */ + + return -1; } #endif /* SIGTSTP */