From 0375e50cce3eb2cb0e58ecbf468179d27c7d2ad7 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Mon, 13 Sep 2021 14:48:12 +1200
Subject: [PATCH] Fix -O portability.

Before trying to use the standard output descriptors as locks, check if
that works.  If it doesn't, create a lock file that is inherited by
submakes, and identified by --sync-handle=<fd> (a command line option
previously used only for Windows).  This fixes -O brokenness on macOS
and *BSD when writing to a pipe rather than a file, which is typically
the case on CI systems.

https://savannah.gnu.org/bugs/?52922
---
 src/main.c   |  8 ++++++--
 src/output.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++------
 src/output.h | 10 ++++-----
 3 files changed, 63 insertions(+), 13 deletions(-)

diff --git a/src/main.c b/src/main.c
index d16d17a..1f8f401 100644
--- a/src/main.c
+++ b/src/main.c
@@ -813,8 +813,6 @@ decode_output_sync_flags (void)
 #endif
 }
 
-#ifdef WINDOWS32
-
 #ifndef NO_OUTPUT_SYNC
 
 /* This is called from start_job_command when it detects that
@@ -829,13 +827,19 @@ prepare_mutex_handle_string (sync_handle_t handle)
       /* Prepare the mutex handle string for our children.  */
       /* 2 hex digits per byte + 2 characters for "0x" + null.  */
       sync_mutex = xmalloc ((2 * sizeof (sync_handle_t)) + 2 + 1);
+#ifdef WINDOWS32
       sprintf (sync_mutex, "0x%Ix", handle);
+#else
+      sprintf (sync_mutex, "0x%x", handle);
+#endif
       define_makeflags (1, 0);
     }
 }
 
 #endif  /* NO_OUTPUT_SYNC */
 
+#ifdef WINDOWS32
+
 /*
  * HANDLE runtime exceptions by avoiding a requestor on the GUI. Capture
  * exception and print it to stderr instead.
diff --git a/src/output.c b/src/output.c
index e222e0a..72147d1 100644
--- a/src/output.c
+++ b/src/output.c
@@ -172,6 +172,33 @@ static sync_handle_t sync_handle = -1;
 
 #define FD_NOT_EMPTY(_f) ((_f) != OUTPUT_NONE && lseek ((_f), 0, SEEK_END) > 0)
 
+#ifndef WINDOWS32
+/* If --sync-handle=<fd> is passed down, set it. */
+void
+record_sync_mutex(const char *s)
+{
+  sync_handle = strtol(s, NULL, 0);
+}
+
+/* Test if a descriptor can be used as a semaphore. */
+static int
+test_semaphore (int fd)
+{
+  static struct flock fl;
+
+  fl.l_type = F_WRLCK;
+  fl.l_whence = SEEK_SET;
+  fl.l_start = 0;
+  fl.l_len = 1;
+  if (fcntl (fd, F_SETLKW, &fl) < 0)
+    return 0;
+  fl.l_type = F_UNLCK;
+  if (fcntl (fd, F_SETLKW, &fl) < 0)
+    perror ("fcntl()");
+  return 1;
+}
+#endif
+
 /* Set up the sync handle.  Disables output_sync on error.  */
 static int
 sync_init (void)
@@ -196,18 +223,37 @@ sync_init (void)
     {
       struct stat stbuf_o, stbuf_e;
 
-      sync_handle = fileno (stdout);
       combined_output = (fstat (fileno (stdout), &stbuf_o) == 0
                          && fstat (fileno (stderr), &stbuf_e) == 0
                          && stbuf_o.st_dev == stbuf_e.st_dev
                          && stbuf_o.st_ino == stbuf_e.st_ino);
     }
-  else if (STREAM_OK (stderr))
-    sync_handle = fileno (stderr);
-  else
+  if (sync_handle == -1)
     {
-      perror_with_name ("output-sync suppressed: ", "stderr");
-      output_sync = 0;
+      int fd = -1;
+
+      if (STREAM_OK (stdout))
+        fd = fileno (stdout);
+      else if (STREAM_OK (stderr))
+        fd = fileno (stderr);
+      if (fd >= 0)
+        {
+          /* Check if this fd supports locking.  If it doesn't we'll
+             have to create our own lock file and pass the descriptor
+             down to subprocesses. */
+          if (test_semaphore(fd))
+            sync_handle = fd;
+          else
+            {
+              sync_handle = output_tmpfd();
+              prepare_mutex_handle_string(sync_handle);
+            }
+        }
+      else
+        {
+          perror_with_name ("output-sync suppressed: ", "stderr");
+          output_sync = 0;
+        }
     }
 #endif
 
diff --git a/src/output.h b/src/output.h
index ece1c70..905d705 100644
--- a/src/output.h
+++ b/src/output.h
@@ -102,14 +102,14 @@ int fcntl (intptr_t fd, int cmd, ...);
 intptr_t create_mutex (void);
 int same_stream (FILE *f1, FILE *f2);
 
-#  define RECORD_SYNC_MUTEX(m) record_sync_mutex(m)
-void record_sync_mutex (const char *str);
-void prepare_mutex_handle_string (intptr_t hdl);
 # else  /* !WINDOWS32 */
 
 typedef int sync_handle_t;      /* file descriptor */
 
-#  define RECORD_SYNC_MUTEX(m) (void)(m)
-
 # endif
+
+# define RECORD_SYNC_MUTEX(m) record_sync_mutex(m)
+void record_sync_mutex (const char *str);
+void prepare_mutex_handle_string (sync_handle_t hdl);
+
 #endif  /* !NO_OUTPUT_SYNC */
-- 
2.32.0

