From 949c0464a9f429e7778dbcf8ff78fc2d02911609 Mon Sep 17 00:00:00 2001
From: Dmitry Goncharov <dgoncharov@users.sf.net>
Date: Sat, 23 Apr 2022 15:33:41 -0400
Subject: [PATCH] [SV 62145] Remove a stdin temp file on re-exec failure.

If the re-exec fails, be sure to remove a temp makefile that was
created to read from stdin.

* src/job.c (exec_command): Return on failure.
(child_execute_job): Call exit if exec_command returns.
* src/job.h (exec_command): Don't mark as NORETURN.
* src/main.c (main): Unlink stdin temporary file if re-exec fails.
* tests/run_make_tests.pl: Get value for ERR_nonexe_file/ERR_exe_dir.
* tests/scripts/features/temp_stdin: Test that temp file unlink works.
---
 src/job.c                         |  16 +----
 src/job.h                         |   4 +-
 src/main.c                        |   9 ++-
 tests/run_make_tests.pl           |   4 +-
 tests/scripts/features/temp_stdin | 106 ++++++++++++++++++++++++++++++
 5 files changed, 118 insertions(+), 21 deletions(-)
 create mode 100644 tests/scripts/features/temp_stdin

diff --git a/src/job.c b/src/job.c
index 8d1bdefb..6060cad8 100644
--- a/src/job.c
+++ b/src/job.c
@@ -2309,6 +2309,7 @@ child_execute_job (struct childbase *child, int good_stdin, char **argv)
 
   /* Run the command.  */
   exec_command (argv, child->environment);
+  _exit (127);
 
 #else /* USE_POSIX_SPAWN */
 
@@ -2452,12 +2453,7 @@ child_execute_job (struct childbase *child, int good_stdin, char **argv)
 /* Replace the current process with one running the command in ARGV,
    with environment ENVP.  This function does not return.  */
 
-/* EMX: This function returns the pid of the child process.  */
-# ifdef __EMX__
 pid_t
-# else
-void
-# endif
 exec_command (char **argv, char **envp)
 {
 #ifdef VMS
@@ -2524,14 +2520,12 @@ exec_command (char **argv, char **envp)
         }
     }
 
-  /* return child's exit code as our exit code */
+  /* Use the child's exit code as our exit code */
   exit (exit_code);
 
 #else  /* !WINDOWS32 */
 
-# ifdef __EMX__
-  pid_t pid;
-# endif
+  pid_t pid = -1;
 
   /* Be the user, permanently.  */
   child_access ();
@@ -2630,11 +2624,7 @@ exec_command (char **argv, char **envp)
       break;
     }
 
-# ifdef __EMX__
   return pid;
-# else
-  _exit (127);
-# endif
 #endif /* !WINDOWS32 */
 #endif /* !VMS */
 }
diff --git a/src/job.h b/src/job.h
index 7c50f045..c32e84b0 100644
--- a/src/job.h
+++ b/src/job.h
@@ -81,10 +81,8 @@ pid_t child_execute_job (struct childbase *child, int good_stdin, char **argv);
 
 #ifdef _AMIGA
 void exec_command (char **argv) NORETURN;
-#elif defined(__EMX__)
-int exec_command (char **argv, char **envp);
 #else
-void exec_command (char **argv, char **envp) NORETURN;
+pid_t exec_command (char **argv, char **envp);
 #endif
 
 void unblock_all_sigs (void);
diff --git a/src/main.c b/src/main.c
index 66086029..fc94b084 100644
--- a/src/main.c
+++ b/src/main.c
@@ -2655,10 +2655,13 @@ main (int argc, char **argv, char **envp)
 #endif
           exec_command ((char **)nargv, environ);
 #endif
-
-          /* We shouldn't get here but just in case.  */
           jobserver_post_child(1);
-          break;
+
+          /* Get rid of any stdin temp file.  */
+          if (stdin_offset >= 0)
+            unlink (makefiles->list[stdin_offset]);
+
+          _exit (127);
         }
 
       if (any_failed)
diff --git a/tests/run_make_tests.pl b/tests/run_make_tests.pl
index f64ff8f0..155cd2ba 100644
--- a/tests/run_make_tests.pl
+++ b/tests/run_make_tests.pl
@@ -141,14 +141,14 @@ $ERR_command_not_found = undef;
       $ERR_read_only_file = "$!";
   }
 
-  $_ = `./file.out 2>/dev/null`;
+  $_ = `./file.out 2>&1`;
   if ($? == 0) {
       print "Executed non-executable file!  Skipping related tests.\n";
   } else {
       $ERR_nonexe_file = "$!";
   }
 
-  $_ = `./. 2>/dev/null`;
+  $_ = `./. 2>&1`;
   if ($? == 0) {
       print "Executed directory!  Skipping related tests.\n";
   } else {
diff --git a/tests/scripts/features/temp_stdin b/tests/scripts/features/temp_stdin
new file mode 100644
index 00000000..a00b9848
--- /dev/null
+++ b/tests/scripts/features/temp_stdin
@@ -0,0 +1,106 @@
+#                                                              -*-mode: perl-*-
+
+$description = "Test handling of temporary file created from stdin.";
+
+use File::Temp qw /tempdir/;
+
+sub check_tempfile
+{
+    my ($tdir) = @_;
+    my @left = glob $tdir . '/Gm*';
+    scalar @left == 0 && return;
+    my $answer = "temporary file $left[0] is left behind\n";
+    compare_output($answer, &get_logfile(1));
+}
+
+create_file('input.mk', "world:=1\n");
+create_file('bye.mk', "moon:=2\n");
+
+# sv 62118,62145.
+# Test that makes leaves no temp file when make code is piped to stdin and -v,
+# -h or an invalid option is specified.
+my @opts = ('-v', '-h', '--nosuchopt');
+my @exit_codes = (0, 0, 512);
+for my $i (0 .. $#opts) {
+    my $tdir = tempdir(CLEANUP => 1);
+    $ENV{'TMPDIR'} = $tdir;
+    $ENV{'TMP'} = $tdir;
+    close(STDIN);
+    open(STDIN, "<", 'input.mk') || die "$0: cannot open input.mk for reading: $!";
+    run_make_test(q!
+all:; $(info hello world)
+!,
+                  "$opts[$i] -f-", "/uilt for /", $exit_codes[$i]);
+    check_tempfile($tdir);
+}
+
+# sv 62118,62145.
+# Test that a stdin temp file is removed.
+my $tdir = tempdir(CLEANUP => 1);
+$ENV{'TMPDIR'} = $tdir;
+$ENV{'TMP'} = $tdir;
+close(STDIN);
+open(STDIN, "<", 'input.mk') || die "$0: cannot open input.mk for reading: $!";
+run_make_test(q!
+all:; $(info world=$(world))
+!,
+              '-f-', "world=1\n#MAKE#: 'all' is up to date.\n");
+check_tempfile($tdir);
+
+# sv 62118,62145.
+# Test that a stdin temp file is removed, even when make re-execs.
+# Also test that make nohors TMPDIR to create the temp file.
+my $tdir = tempdir(CLEANUP => 1);
+$ENV{'TMPDIR'} = $tdir;
+$ENV{'TMP'} = $tdir;
+# Ensure touching bye.mk causes re-exec.
+&utouch(-600, 'bye.mk');
+close(STDIN);
+open(STDIN, "<", 'input.mk') || die "$0: cannot open input.mk for reading: $!";
+run_make_test(q!
+include bye.mk
+all:; $(info hello)
+$(MAKE_RESTARTS)bye.mk: force; touch $@
+force:
+!,
+              '-R --debug=b -f-', "/Re-executing.+?--temp-stdin=\Q$tdir\E/");
+check_tempfile($tdir);
+
+if ($port_type eq 'UNIX') {
+# sv 62118,62145.
+# Test that a stdin temp file is removed, when execvp fails to re-exec make.
+# In order to cause execvp to fail, copy the tested make binary to the temp
+# directory and take away the 'x' bit.
+use File::Copy;
+
+my $tdir = tempdir(CLEANUP => 1);
+$ENV{'TMPDIR'} = $tdir;
+$ENV{'TMP'} = $tdir;
+my $makecopy = "$tdir/make";
+copy("$mkpath", $makecopy);
+# Set file mode bits, because perl copy won't.
+chmod 0750, $makecopy;
+
+my @make_orig = @make_command;
+@make_command = ($makecopy);
+
+# Ensure touching bye.mk causes re-exec.
+&utouch(-600, 'bye.mk');
+close(STDIN);
+open(STDIN, "<", 'input.mk') || die "$0: cannot open input.mk for reading: $!";
+run_make_test("
+include bye.mk
+all:; \$(info hello)
+\$(MAKE_RESTARTS)bye.mk: force; touch \$@ && chmod -x $makecopy
+force:
+",
+              "-f-", "touch bye.mk && chmod -x $makecopy\nmake: $makecopy: $ERR_nonexe_file\n", 32512);
+check_tempfile($tdir);
+
+@make_command = @make_orig;
+}
+
+unlink('input.mk', 'bye.mk');
+
+# This tells the test driver that the perl test script executed properly.
+1;