Subject: bug#77389: 31.0.50; Restarting Emacs with (kill-emacs ... t) looses noninteractivity

From: Jens Schmidt <[email-masked]>
Date: Tue, 1 Apr 2025 22:52:48 +0200

On 2025-03-30  20:29, Eli Zaretskii wrote:
>> Date: Sun, 30 Mar 2025 19:20:58 +0200
>> From:  Jens Schmidt via "Bug reports for GNU Emacs,
>>  the Swiss army knife of text editors" <[email-masked]>
>>
>> [ Disclaimer:
>>
>>   - This is one of my more exotic bug reports, so I won't argue
>>     (at least not much) if you close this as "won't fix".
>>
>>   - In particular since an Emacs restart, added by Lars in
>>     5be9a9ca, probably has only been meant to be used
>>     interactively.
>>
>>   - But I do have a valid use case for a non-interactive restart,
>>     so I created this bug report ... ]
> 
> Please describe your use case.

TL;DR:

1. Start foo.el in batch mode with "emacs --script foo.el".
2. foo.el reads the user init file and writes a modified copy of
   it to init-new.el.
3. Then foo.el calls (kill-emacs ... t), thus restarting itself,
   (hopefully) still in batch mode.
4. The newly started foo.el senses the presence of init-new.el
   and, hence, processes that in a final step.

In the above it is important that steps 2 and 4 are executed
from different, newly started Emacs processes which share STDOUT
and STDERR.

The bigger picture is my package bc-dot-emacs.el

  bc-dot-emacs.el --- Check Emacs init file syntax

  https://git.sr.ht/~jschmidt/bc-dot-emacs

which I sooner or later wanted to present on emacs-devel for
inclusion to ELPA.

>> Probably Fkill_emacs should call execvp not on the command line
>> arguments in initial_argv, which already got modified by main,
>> but on a copy of the *real* initial command line arguments as
>> they have been passed into main?
> 
> Yes.  And on MS-Windows, it already does.

Indeed, I missed that bit.  The only other non-Posix case seems
to be Android, which a) IIRC does not support non-interactive
operation and b) restarts Emacs with some mechanism in Java-land
that does not require Emacs itself to keep a copy of the initial
arguments.

So this leaves the Posix/execvp case, I think, which I hope to
have covered in the attached patch.

The only thing that bothers me a bit is that there is the following
memory-related initialization being executed in main *after*
copy_raw_args (or also the present sort_args) have already been
allocating memory:

#if !defined SYSTEM_MALLOC
  /* Arrange to get warning messages as memory fills up.  */
  memory_warnings (0, malloc_warning);

  /* Call malloc at least once, to run malloc_initialize_hook.
     Also call realloc and free for consistency.  */
  free (realloc (malloc (4), 4));

#endif	/* not SYSTEM_MALLOC */

Thanks.

From 8f4085327b904ff97758b49c919189cd4e7fae4f Mon Sep 17 00:00:00 2001
From: Jens Schmidt <[email-masked]>
Date: Tue, 1 Apr 2025 22:16:56 +0200
Subject: [PATCH] Use raw argv as passed into main to restart Emacs

The cooked argv as left after main has done its job can differ both in
order and contents of the command-line arguments, which can lead to
surprising results when restarting emacs through `kill-emacs'.

* src/emacs.c: Add new variables raw_initial_argv, raw_initial_argc.
(copy_raw_args): Initialize them here ...
(main): ... when called from here.
(Fkill_emacs): Use the new variables as arguments to execvp. (Bug#77389)
---
 src/emacs.c | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

diff --git a/src/emacs.c b/src/emacs.c
index 6ff7b632c0f..7b5986024a3 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -238,7 +238,12 @@ #define MAIN_PROGRAM
 HANDLE w32_daemon_event;
 #endif
 
-/* Save argv and argc.  */
+/* Save argv and argc.  The raw_* variants correspond to argv and argc
+   as they have been passed into main, while the non-raw variants
+   correspond to what the command-line has become after main's
+   processing.  */
+char **raw_initial_argv;
+int raw_initial_argc;
 char **initial_argv;
 int initial_argc;
 static char *initial_emacs_executable = NULL;
@@ -246,6 +251,7 @@ #define MAIN_PROGRAM
 /* The name of the working directory, or NULL if this info is unavailable.  */
 char const *emacs_wd;
 
+static void copy_raw_args (int argc, char **argv);
 static void sort_args (int argc, char **argv);
 static void syms_of_emacs (void);
 
@@ -1430,6 +1436,7 @@ android_emacs_init (int argc, char **argv, char *dump_file)
      'command-line-args-left' in 'command-line-1'.  */
 
   bool only_version = false;
+  copy_raw_args (argc, argv);
   sort_args (argc, argv);
   old_argc = argc, argc = 0;
   /* Don't allow going past argv.  */
@@ -2696,6 +2703,19 @@ android_emacs_init (int argc, char **argv, char *dump_file)
   { "-kill", "--kill", -10, 0 },
 };
 
+static void
+copy_raw_args (int argc, char **argv)
+{
+  char **new = xmalloc ((argc + 1) * sizeof *new);
+  int i;
+  new[0] = argv[0];
+  for (i = 1; i < argc; i++)
+    new[i] = xstrdup (argv[i]);
+  new[argc] = argv[argc];
+  raw_initial_argv = new;
+  raw_initial_argc = argc;
+}
+
 /* Reorder the elements of ARGV (assumed to have ARGC elements)
    so that the highest priority ones come first.
    Do not change the order of elements of equal priority.
@@ -2975,8 +2995,8 @@ DEFUN ("kill-emacs", Fkill_emacs, Skill_emacs, 0, 2, "P",
 #ifdef WINDOWSNT
       if (w32_reexec_emacs (initial_cmdline, initial_wd) < 0)
 #else
-      initial_argv[0] = initial_emacs_executable;
-      if (execvp (*initial_argv, initial_argv) < 1)
+      raw_initial_argv[0] = initial_emacs_executable;
+      if (execvp (*raw_initial_argv, raw_initial_argv) < 1)
 #endif
 	emacs_perror ("Unable to re-execute Emacs");
     }
-- 
2.39.5