Imported Upstream version 4.4
[platform/upstream/make.git] / src / posixos.c
index 525f292..44aeb34 100644 (file)
@@ -1,5 +1,5 @@
 /* POSIX-based operating system interface for GNU Make.
-Copyright (C) 2016-2020 Free Software Foundation, Inc.
+Copyright (C) 2016-2022 Free Software Foundation, Inc.
 This file is part of GNU Make.
 
 GNU Make is free software; you can redistribute it and/or modify it under the
@@ -12,7 +12,7 @@ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License along with
-this program.  If not, see <http://www.gnu.org/licenses/>.  */
+this program.  If not, see <https://www.gnu.org/licenses/>.  */
 
 #include "makeint.h"
 
@@ -20,10 +20,15 @@ this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #ifdef HAVE_FCNTL_H
 # include <fcntl.h>
+# define FD_OK(_f) (fcntl ((_f), F_GETFD) != -1)
 #elif defined(HAVE_SYS_FILE_H)
 # include <sys/file.h>
 #endif
 
+#if !defined(FD_OK)
+# define FD_OK(_f) 1
+#endif
+
 #if defined(HAVE_PSELECT) && defined(HAVE_SYS_SELECT_H)
 # include <sys/select.h>
 #endif
@@ -32,10 +37,47 @@ this program.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "job.h"
 #include "os.h"
 
-#ifdef MAKE_JOBSERVER
+#define STREAM_OK(_s) ((fcntl (fileno (_s), F_GETFD) != -1) || (errno != EBADF))
+
+unsigned int
+check_io_state ()
+{
+  static unsigned int state = IO_UNKNOWN;
+
+  /* We only need to compute this once per process.  */
+  if (state != IO_UNKNOWN)
+    return state;
+
+  if (STREAM_OK (stdin))
+    state |= IO_STDIN_OK;
+  if (STREAM_OK (stdout))
+    state |= IO_STDOUT_OK;
+  if (STREAM_OK (stderr))
+    state |= IO_STDERR_OK;
+
+  if (ALL_SET (state, IO_STDOUT_OK|IO_STDERR_OK))
+    {
+      struct stat stbuf_o, stbuf_e;
+
+      if (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)
+        state |= IO_COMBINED_OUTERR;
+    }
+
+  return state;
+}
+
+#if defined(MAKE_JOBSERVER)
+
+#define FIFO_PREFIX    "fifo:"
 
 /* This section provides OS-specific functions to support the jobserver.  */
 
+/* True if this is the root make instance.  */
+static unsigned char job_root = 0;
+
 /* These track the state of the jobserver pipe.  Passed to child instances.  */
 static int job_fds[2] = { -1, -1 };
 
@@ -47,8 +89,21 @@ static int job_rfd = -1;
 /* Token written to the pipe (could be any character...)  */
 static char token = '+';
 
+/* The type of jobserver we're using.  */
+enum js_type
+  {
+    js_none = 0,        /* No jobserver.  */
+    js_pipe,            /* Use a simple pipe as the jobserver.  */
+    js_fifo             /* Use a named pipe as the jobserver.  */
+  };
+
+static enum js_type js_type = js_none;
+
+/* The name of the named pipe (if used).  */
+static char *fifo_name = NULL;
+
 static int
-make_job_rfd (void)
+make_job_rfd ()
 {
 #ifdef HAVE_PSELECT
   /* Pretend we succeeded.  */
@@ -81,13 +136,61 @@ set_blocking (int fd, int blocking)
 }
 
 unsigned int
-jobserver_setup (int slots)
+jobserver_setup (int slots, const char *style)
 {
   int r;
 
-  EINTRLOOP (r, pipe (job_fds));
-  if (r < 0)
-    pfatal_with_name (_("creating jobs pipe"));
+#if HAVE_MKFIFO
+  if (style == NULL || strcmp (style, "fifo") == 0)
+    {
+  /* Unfortunately glibc warns about uses of mktemp even though we aren't
+     using it in dangerous way here.  So avoid this by generating our own
+     temporary file name.  */
+# define  FNAME_PREFIX "GMfifo"
+      const char *tmpdir = get_tmpdir ();
+
+      fifo_name = xmalloc (strlen (tmpdir) + CSTRLEN (FNAME_PREFIX)
+                           + INTSTR_LENGTH + 2);
+      sprintf (fifo_name, "%s/" FNAME_PREFIX "%" MK_PRI64_PREFIX "d",
+               tmpdir, (long long)make_pid ());
+
+      EINTRLOOP (r, mkfifo (fifo_name, 0600));
+      if (r < 0)
+        {
+          perror_with_name("jobserver mkfifo: ", fifo_name);
+          free (fifo_name);
+          fifo_name = NULL;
+        }
+      else
+        {
+          /* We have to open the read side in non-blocking mode, else it will
+             hang until the write side is open.  */
+          EINTRLOOP (job_fds[0], open (fifo_name, O_RDONLY|O_NONBLOCK));
+          if (job_fds[0] < 0)
+            OSS (fatal, NILF, _("Cannot open jobserver %s: %s"),
+                 fifo_name, strerror (errno));
+
+          EINTRLOOP (job_fds[1], open (fifo_name, O_WRONLY));
+          if (job_fds[0] < 0)
+            OSS (fatal, NILF, _("Cannot open jobserver %s: %s"),
+                 fifo_name, strerror (errno));
+
+          js_type = js_fifo;
+        }
+    }
+#endif
+
+  if (js_type == js_none)
+    {
+      if (style && strcmp (style, "pipe") != 0)
+        OS (fatal, NILF, _("Unknown jobserver auth style '%s'"), style);
+
+      EINTRLOOP (r, pipe (job_fds));
+      if (r < 0)
+        pfatal_with_name (_("creating jobs pipe"));
+
+      js_type = js_pipe;
+    }
 
   /* By default we don't send the job pipe FDs to our children.
      See jobserver_pre_child() and jobserver_post_child().  */
@@ -107,37 +210,68 @@ jobserver_setup (int slots)
   /* When using pselect() we want the read to be non-blocking.  */
   set_blocking (job_fds[0], 0);
 
+  job_root = 1;
+
   return 1;
 }
 
 unsigned int
 jobserver_parse_auth (const char *auth)
 {
+  int rfd, wfd;
+
   /* Given the command-line parameter, parse it.  */
-  if (sscanf (auth, "%d,%d", &job_fds[0], &job_fds[1]) != 2)
-    OS (fatal, NILF,
-        _("internal error: invalid --jobserver-auth string '%s'"), auth);
 
-  DB (DB_JOBS,
-      (_("Jobserver client (fds %d,%d)\n"), job_fds[0], job_fds[1]));
+  /* First see if we're using a named pipe.  */
+  if (strncmp (auth, FIFO_PREFIX, CSTRLEN (FIFO_PREFIX)) == 0)
+    {
+      fifo_name = xstrdup (auth + CSTRLEN (FIFO_PREFIX));
 
-#ifdef HAVE_FCNTL_H
-# define FD_OK(_f) (fcntl ((_f), F_GETFD) != -1)
-#else
-# define FD_OK(_f) 1
-#endif
+      EINTRLOOP (job_fds[0], open (fifo_name, O_RDONLY));
+      if (job_fds[0] < 0)
+        OSS (fatal, NILF,
+             _("Cannot open jobserver %s: %s"), fifo_name, strerror (errno));
+
+      EINTRLOOP (job_fds[1], open (fifo_name, O_WRONLY));
+      if (job_fds[0] < 0)
+        OSS (fatal, NILF,
+             _("Cannot open jobserver %s: %s"), fifo_name, strerror (errno));
+
+      js_type = js_fifo;
+    }
+  /* If not, it must be a simple pipe.  */
+  else if (sscanf (auth, "%d,%d", &rfd, &wfd) == 2)
+    {
+      /* The parent overrode our FDs because we aren't a recursive make.  */
+      if (rfd == -2 || wfd == -2)
+        return 0;
+
+      /* Make sure our pipeline is valid.  */
+      if (!FD_OK (rfd) || !FD_OK (wfd))
+        return 0;
+
+      job_fds[0] = rfd;
+      job_fds[1] = wfd;
+
+      js_type = js_pipe;
+    }
+  /* Who knows what it is?  */
+  else
+    {
+      OS (error, NILF, _("invalid --jobserver-auth string '%s'"), auth);
+      return 0;
+    }
 
-  /* Make sure our pipeline is valid, and (possibly) create a duplicate pipe,
-     that will be closed in the SIGCHLD handler.  If this fails with EBADF,
-     the parent has closed the pipe on us because it didn't think we were a
-     submake.  If so, warn and default to -j1.  */
+  /* Create a duplicate pipe, if needed, that will be closed in the SIGCHLD
+     handler.  If this fails with EBADF, the parent closed the pipe on us as
+     it didn't think we were a submake.  If so, warn and default to -j1.  */
 
-  if (!FD_OK (job_fds[0]) || !FD_OK (job_fds[1]) || make_job_rfd () < 0)
+  if (make_job_rfd () < 0)
     {
       if (errno != EBADF)
-        pfatal_with_name (_("jobserver pipeline"));
+        pfatal_with_name ("jobserver readfd");
 
-      job_fds[0] = job_fds[1] = -1;
+      jobserver_clear ();
 
       return 0;
     }
@@ -145,25 +279,51 @@ jobserver_parse_auth (const char *auth)
   /* When using pselect() we want the read to be non-blocking.  */
   set_blocking (job_fds[0], 0);
 
+  /* By default we don't send the job pipe FDs to our children.
+     See jobserver_pre_child() and jobserver_post_child().  */
+  fd_noinherit (job_fds[0]);
+  fd_noinherit (job_fds[1]);
+
   return 1;
 }
 
 char *
-jobserver_get_auth (void)
+jobserver_get_auth ()
 {
-  char *auth = xmalloc ((INTSTR_LENGTH * 2) + 2);
-  sprintf (auth, "%d,%d", job_fds[0], job_fds[1]);
+  char *auth;
+
+  if (js_type == js_fifo) {
+    auth = xmalloc (strlen (fifo_name) + CSTRLEN (FIFO_PREFIX) + 1);
+    sprintf (auth, FIFO_PREFIX "%s", fifo_name);
+  } else {
+    auth = xmalloc ((INTSTR_LENGTH * 2) + 2);
+    sprintf (auth, "%d,%d", job_fds[0], job_fds[1]);
+  }
+
   return auth;
 }
 
+const char *
+jobserver_get_invalid_auth ()
+{
+  /* If we're using a named pipe we don't need to invalidate the jobserver.  */
+  if (js_type == js_fifo) {
+    return NULL;
+  }
+
+  /* It's not really great that we are assuming the command line option
+     here but other alternatives are also gross.  */
+  return " --" JOBSERVER_AUTH_OPT "=-2,-2";
+}
+
 unsigned int
-jobserver_enabled (void)
+jobserver_enabled ()
 {
-  return job_fds[0] >= 0;
+  return js_type != js_none;
 }
 
 void
-jobserver_clear (void)
+jobserver_clear ()
 {
   if (job_fds[0] >= 0)
     close (job_fds[0]);
@@ -173,6 +333,23 @@ jobserver_clear (void)
     close (job_rfd);
 
   job_fds[0] = job_fds[1] = job_rfd = -1;
+
+  if (fifo_name)
+    {
+      if (job_root)
+        {
+          int r;
+          EINTRLOOP (r, unlink (fifo_name));
+        }
+
+      if (!handling_fatal_signal)
+        {
+          free (fifo_name);
+          fifo_name = NULL;
+        }
+    }
+
+  js_type = js_none;
 }
 
 void
@@ -189,8 +366,9 @@ jobserver_release (int is_fatal)
 }
 
 unsigned int
-jobserver_acquire_all (void)
+jobserver_acquire_all ()
 {
+  int r;
   unsigned int tokens = 0;
 
   /* Use blocking reads to wait for all outstanding jobs.  */
@@ -203,19 +381,24 @@ jobserver_acquire_all (void)
   while (1)
     {
       char intake;
-      int r;
       EINTRLOOP (r, read (job_fds[0], &intake, 1));
       if (r != 1)
-        return tokens;
+        break;
       ++tokens;
     }
+
+  DB (DB_JOBS, ("Acquired all %u jobserver tokens.\n", tokens));
+
+  jobserver_clear ();
+
+  return tokens;
 }
 
 /* Prepare the jobserver to start a child process.  */
 void
 jobserver_pre_child (int recursive)
 {
-  if (recursive && job_fds[0] >= 0)
+  if (recursive && js_type == js_pipe)
     {
       fd_inherit (job_fds[0]);
       fd_inherit (job_fds[1]);
@@ -226,7 +409,7 @@ jobserver_pre_child (int recursive)
 void
 jobserver_post_child (int recursive)
 {
-  if (recursive && job_fds[0] >= 0)
+  if (recursive && js_type == js_pipe)
     {
       fd_noinherit (job_fds[0]);
       fd_noinherit (job_fds[1]);
@@ -234,7 +417,7 @@ jobserver_post_child (int recursive)
 }
 
 void
-jobserver_signal (void)
+jobserver_signal ()
 {
   if (job_rfd >= 0)
     {
@@ -244,7 +427,7 @@ jobserver_signal (void)
 }
 
 void
-jobserver_pre_acquire (void)
+jobserver_pre_acquire ()
 {
   /* Make sure we have a dup'd FD.  */
   if (job_rfd < 0 && job_fds[0] >= 0 && make_job_rfd () < 0)
@@ -297,7 +480,7 @@ jobserver_acquire (int timeout)
           case EBADF:
             /* Someone closed the jobs pipe.
                That shouldn't happen but if it does we're done.  */
-              O (fatal, NILF, _("job server shut down"));
+            O (fatal, NILF, _("job server shut down"));
 
           default:
             pfatal_with_name (_("pselect jobs pipe"));
@@ -319,7 +502,7 @@ jobserver_acquire (int timeout)
           pfatal_with_name (_("read jobs pipe"));
         }
 
-      /* read() should never return 0: only the master make can reap all the
+      /* read() should never return 0: only the parent make can reap all the
          tokens and close the write side...??  */
       return r > 0;
     }
@@ -351,7 +534,7 @@ jobserver_acquire (int timeout)
    during the section mentioned above, the read(2) will be invoked with an
    invalid FD and will return immediately with EBADF.  */
 
-static RETSIGTYPE
+static void
 job_noop (int sig UNUSED)
 {
 }
@@ -442,9 +625,130 @@ jobserver_acquire (int timeout)
 
 #endif /* MAKE_JOBSERVER */
 
+#if !defined(NO_OUTPUT_SYNC)
+
+#define MUTEX_PREFIX    "fnm:"
+
+static int osync_handle = -1;
+
+static char *osync_tmpfile = NULL;
+
+static unsigned int sync_root = 0;
+
+unsigned int
+osync_enabled ()
+{
+  return osync_handle >= 0;
+}
+
+void
+osync_setup ()
+{
+  osync_handle = get_tmpfd (&osync_tmpfile);
+  fd_noinherit (osync_handle);
+  sync_root = 1;
+}
+
+char *
+osync_get_mutex ()
+{
+  char *mutex = NULL;
+
+  if (osync_enabled ())
+    {
+      /* Prepare the mutex handle string for our children.  */
+      mutex = xmalloc (strlen (osync_tmpfile) + CSTRLEN (MUTEX_PREFIX) + 1);
+      sprintf (mutex, MUTEX_PREFIX "%s", osync_tmpfile);
+    }
+
+  return mutex;
+}
+
+unsigned int
+osync_parse_mutex (const char *mutex)
+{
+  if (strncmp (mutex, MUTEX_PREFIX, CSTRLEN (MUTEX_PREFIX)) != 0)
+    {
+      OS (error, NILF, _("invalid --sync-mutex string '%s'"), mutex);
+      return 0;
+    }
+
+  free (osync_tmpfile);
+  osync_tmpfile = xstrdup (mutex + CSTRLEN (MUTEX_PREFIX));
+
+  EINTRLOOP (osync_handle, open (osync_tmpfile, O_WRONLY));
+  if (osync_handle < 0)
+    OSS (fatal, NILF, _("cannot open output sync mutex %s: %s"),
+         osync_tmpfile, strerror (errno));
+
+  fd_noinherit (osync_handle);
+
+  return 1;
+}
+
+void
+osync_clear ()
+{
+  if (osync_handle >= 0)
+    {
+      close (osync_handle);
+      osync_handle = -1;
+    }
+
+  if (sync_root && osync_tmpfile)
+    {
+      int r;
+
+      EINTRLOOP (r, unlink (osync_tmpfile));
+      free (osync_tmpfile);
+      osync_tmpfile = NULL;
+    }
+}
+
+unsigned int
+osync_acquire ()
+{
+  if (osync_enabled())
+    {
+      struct flock fl;
+
+      fl.l_type = F_WRLCK;
+      fl.l_whence = SEEK_SET;
+      fl.l_start = 0;
+      fl.l_len = 1;
+      /* We don't want to keep waiting on EINTR.  */
+      if (fcntl (osync_handle, F_SETLKW, &fl) == -1)
+        {
+          perror ("fcntl()");
+          return 0;
+        }
+    }
+
+  return 1;
+}
+
+void
+osync_release ()
+{
+  if (osync_enabled())
+    {
+      struct flock fl;
+
+      fl.l_type = F_UNLCK;
+      fl.l_whence = SEEK_SET;
+      fl.l_start = 0;
+      fl.l_len = 1;
+      /* We don't want to keep waiting on EINTR.  */
+      if (fcntl (osync_handle, F_SETLKW, &fl) == -1)
+        perror ("fcntl()");
+    }
+}
+
+#endif
+
 /* Create a "bad" file descriptor for stdin when parallel jobs are run.  */
 int
-get_bad_stdin (void)
+get_bad_stdin ()
 {
   static int bad_stdin = -1;
 
@@ -458,7 +762,7 @@ get_bad_stdin (void)
       if (pipe (pd) == 0)
         {
           /* Close the write side.  */
-          (void) close (pd[1]);
+          close (pd[1]);
           /* Save the read side.  */
           bad_stdin = pd[0];
 
@@ -501,12 +805,77 @@ void
 fd_noinherit (int fd)
 {
     int flags;
-    EINTRLOOP(flags, fcntl(fd, F_GETFD));
+    EINTRLOOP (flags, fcntl(fd, F_GETFD));
     if (flags >= 0)
       {
         int r;
         flags |= FD_CLOEXEC;
-        EINTRLOOP(r, fcntl(fd, F_SETFD, flags));
+        EINTRLOOP (r, fcntl(fd, F_SETFD, flags));
       }
 }
 #endif
+
+/* Set a file descriptor referring to a regular file to be in O_APPEND mode.
+   If it fails, just ignore it.  */
+
+void
+fd_set_append (int fd)
+{
+#if defined(F_GETFL) && defined(F_SETFL) && defined(O_APPEND)
+  struct stat stbuf;
+  int flags;
+  if (fstat (fd, &stbuf) == 0 && S_ISREG (stbuf.st_mode))
+    {
+      flags = fcntl (fd, F_GETFL, 0);
+      if (flags >= 0)
+        {
+          int r;
+          EINTRLOOP(r, fcntl (fd, F_SETFL, flags | O_APPEND));
+        }
+    }
+#endif
+}
+
+/* Return a file descriptor for a new anonymous temp file, or -1.  */
+int
+os_anontmp ()
+{
+  const char *tdir = get_tmpdir ();
+  int fd = -1;
+
+#ifdef O_TMPFILE
+  static unsigned int tmpfile_works = 1;
+
+  if (tmpfile_works)
+    {
+      EINTRLOOP (fd, open (tdir, O_RDWR | O_TMPFILE | O_EXCL, 0600));
+      if (fd >= 0)
+        return fd;
+
+      DB (DB_BASIC, (_("Cannot open '%s' with O_TMPFILE: %s.\n"),
+                     tdir, strerror (errno)));
+      tmpfile_works = 0;
+    }
+#endif
+
+#if HAVE_DUP
+  /* If we can dup and we are creating temp files in the default location then
+     try tmpfile() + dup() + fclose() to avoid ever having a named file.  */
+  if (streq (tdir, DEFAULT_TMPDIR))
+    {
+      mode_t mask = umask (0077);
+      FILE *tfile;
+      ENULLLOOP (tfile, tmpfile ());
+      if (!tfile)
+        pfatal_with_name ("tmpfile");
+      umask (mask);
+
+      EINTRLOOP (fd, dup (fileno (tfile)));
+      if (fd < 0)
+        pfatal_with_name ("dup");
+      fclose (tfile);
+    }
+#endif
+
+  return fd;
+}