| /* Output to stdout / stderr for GNU make |
| Copyright (C) 2013 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 |
| terms of the GNU General Public License as published by the Free Software |
| Foundation; either version 3 of the License, or (at your option) any later |
| version. |
| |
| GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY |
| 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/>. */ |
| |
| #include "makeint.h" |
| #include "job.h" |
| |
| /* GNU make no longer supports pre-ANSI89 environments. */ |
| |
| #include <assert.h> |
| #include <stdio.h> |
| #include <stdarg.h> |
| |
| #ifdef HAVE_UNISTD_H |
| # include <unistd.h> |
| #endif |
| |
| #ifdef HAVE_FCNTL_H |
| # include <fcntl.h> |
| #else |
| # include <sys/file.h> |
| #endif |
| |
| #ifdef WINDOWS32 |
| # include <windows.h> |
| # include <io.h> |
| # include "sub_proc.h" |
| #endif /* WINDOWS32 */ |
| |
| struct output *output_context = NULL; |
| unsigned int stdio_traced = 0; |
| |
| #define OUTPUT_NONE (-1) |
| |
| #define OUTPUT_ISSET(_out) ((_out)->out >= 0 || (_out)->err >= 0) |
| |
| #ifdef HAVE_FCNTL |
| # define STREAM_OK(_s) ((fcntl (fileno (_s), F_GETFD) != -1) || (errno != EBADF)) |
| #else |
| # define STREAM_OK(_s) 1 |
| #endif |
| |
| /* I really want to move to gnulib. However, this is a big undertaking |
| especially for non-UNIX platforms: how to get bootstrapping to work, etc. |
| I don't want to take the time to do it right now. Use a hack to get a |
| useful version of vsnprintf() for Windows. */ |
| #ifdef __VMS |
| # define va_copy(_d, _s) ((_d) = (_s)) |
| #endif |
| #ifdef _MSC_VER |
| # define va_copy(_d, _s) ((_d) = (_s)) |
| # define vsnprintf msc_vsnprintf |
| static int |
| msc_vsnprintf (char *str, size_t size, const char *format, va_list ap) |
| { |
| int len = -1; |
| |
| if (size > 0) |
| len = _vsnprintf_s (str, size, _TRUNCATE, format, ap); |
| if (len == -1) |
| len = _vscprintf (format, ap); |
| |
| return len; |
| } |
| #endif |
| |
| /* Write a string to the current STDOUT or STDERR. */ |
| static void |
| _outputs (struct output *out, int is_err, const char *msg) |
| { |
| if (! out || ! out->syncout) |
| { |
| FILE *f = is_err ? stderr : stdout; |
| fputs (msg, f); |
| fflush (f); |
| } |
| else |
| { |
| int fd = is_err ? out->err : out->out; |
| int len = strlen (msg); |
| int r; |
| |
| EINTRLOOP (r, lseek (fd, 0, SEEK_END)); |
| while (1) |
| { |
| EINTRLOOP (r, write (fd, msg, len)); |
| if (r == len || r <= 0) |
| break; |
| len -= r; |
| msg += r; |
| } |
| } |
| } |
| |
| /* Write a message indicating that we've just entered or |
| left (according to ENTERING) the current directory. */ |
| |
| static int |
| log_working_directory (int entering) |
| { |
| static char *buf = NULL; |
| static unsigned int len = 0; |
| unsigned int need; |
| const char *fmt; |
| char *p; |
| |
| /* Get enough space for the longest possible output. */ |
| need = strlen (program) + INTEGER_LENGTH + 2 + 1; |
| if (starting_directory) |
| need += strlen (starting_directory); |
| |
| /* Use entire sentences to give the translators a fighting chance. */ |
| if (makelevel == 0) |
| if (starting_directory == 0) |
| if (entering) |
| fmt = _("%s: Entering an unknown directory\n"); |
| else |
| fmt = _("%s: Leaving an unknown directory\n"); |
| else |
| if (entering) |
| fmt = _("%s: Entering directory '%s'\n"); |
| else |
| fmt = _("%s: Leaving directory '%s'\n"); |
| else |
| if (starting_directory == 0) |
| if (entering) |
| fmt = _("%s[%u]: Entering an unknown directory\n"); |
| else |
| fmt = _("%s[%u]: Leaving an unknown directory\n"); |
| else |
| if (entering) |
| fmt = _("%s[%u]: Entering directory '%s'\n"); |
| else |
| fmt = _("%s[%u]: Leaving directory '%s'\n"); |
| |
| need += strlen (fmt); |
| |
| if (need > len) |
| { |
| buf = xrealloc (buf, need); |
| len = need; |
| } |
| |
| p = buf; |
| if (print_data_base_flag) |
| { |
| *(p++) = '#'; |
| *(p++) = ' '; |
| } |
| |
| if (makelevel == 0) |
| if (starting_directory == 0) |
| sprintf (p, fmt , program); |
| else |
| sprintf (p, fmt, program, starting_directory); |
| else if (starting_directory == 0) |
| sprintf (p, fmt, program, makelevel); |
| else |
| sprintf (p, fmt, program, makelevel, starting_directory); |
| |
| _outputs (NULL, 0, buf); |
| |
| return 1; |
| } |
| |
| /* Set a file descriptor to be in O_APPEND mode. |
| If it fails, just ignore it. */ |
| |
| static void |
| set_append_mode (int fd) |
| { |
| #if defined(F_GETFL) && defined(F_SETFL) && defined(O_APPEND) |
| int flags = fcntl (fd, F_GETFL, 0); |
| if (flags >= 0) |
| fcntl (fd, F_SETFL, flags | O_APPEND); |
| #endif |
| } |
| |
| |
| #ifndef NO_OUTPUT_SYNC |
| |
| /* Semaphore for use in -j mode with output_sync. */ |
| static sync_handle_t sync_handle = -1; |
| |
| #define FD_NOT_EMPTY(_f) ((_f) != OUTPUT_NONE && lseek ((_f), 0, SEEK_END) > 0) |
| |
| /* Set up the sync handle. Disables output_sync on error. */ |
| static int |
| sync_init () |
| { |
| int combined_output = 0; |
| |
| #ifdef WINDOWS32 |
| if ((!STREAM_OK (stdout) && !STREAM_OK (stderr)) |
| || (sync_handle = create_mutex ()) == -1) |
| { |
| perror_with_name ("output-sync suppressed: ", "stderr"); |
| output_sync = 0; |
| } |
| else |
| { |
| combined_output = same_stream (stdout, stderr); |
| prepare_mutex_handle_string (sync_handle); |
| } |
| |
| #else |
| if (STREAM_OK (stdout)) |
| { |
| 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 |
| { |
| perror_with_name ("output-sync suppressed: ", "stderr"); |
| output_sync = 0; |
| } |
| #endif |
| |
| return combined_output; |
| } |
| |
| /* Support routine for output_sync() */ |
| static void |
| pump_from_tmp (int from, FILE *to) |
| { |
| static char buffer[8192]; |
| |
| #ifdef WINDOWS32 |
| int prev_mode; |
| |
| /* "from" is opened by open_tmpfd, which does it in binary mode, so |
| we need the mode of "to" to match that. */ |
| prev_mode = _setmode (fileno (to), _O_BINARY); |
| #endif |
| |
| if (lseek (from, 0, SEEK_SET) == -1) |
| perror ("lseek()"); |
| |
| while (1) |
| { |
| int len; |
| EINTRLOOP (len, read (from, buffer, sizeof (buffer))); |
| if (len < 0) |
| perror ("read()"); |
| if (len <= 0) |
| break; |
| if (fwrite (buffer, len, 1, to) < 1) |
| perror ("fwrite()"); |
| } |
| |
| #ifdef WINDOWS32 |
| /* Switch "to" back to its original mode, so that log messages by |
| Make have the same EOL format as without --output-sync. */ |
| _setmode (fileno (to), prev_mode); |
| #endif |
| } |
| |
| /* Obtain the lock for writing output. */ |
| static void * |
| acquire_semaphore (void) |
| { |
| static struct flock fl; |
| |
| fl.l_type = F_WRLCK; |
| fl.l_whence = SEEK_SET; |
| fl.l_start = 0; |
| fl.l_len = 1; |
| if (fcntl (sync_handle, F_SETLKW, &fl) != -1) |
| return &fl; |
| perror ("fcntl()"); |
| return NULL; |
| } |
| |
| /* Release the lock for writing output. */ |
| static void |
| release_semaphore (void *sem) |
| { |
| struct flock *flp = (struct flock *)sem; |
| flp->l_type = F_UNLCK; |
| if (fcntl (sync_handle, F_SETLKW, flp) == -1) |
| perror ("fcntl()"); |
| } |
| |
| /* Returns a file descriptor to a temporary file. The file is automatically |
| closed/deleted on exit. Don't use a FILE* stream. */ |
| int |
| output_tmpfd () |
| { |
| int fd = -1; |
| FILE *tfile = tmpfile (); |
| |
| if (! tfile) |
| pfatal_with_name ("tmpfile"); |
| |
| /* Create a duplicate so we can close the stream. */ |
| fd = dup (fileno (tfile)); |
| if (fd < 0) |
| pfatal_with_name ("dup"); |
| |
| fclose (tfile); |
| |
| set_append_mode (fd); |
| |
| return fd; |
| } |
| |
| /* Adds file descriptors to the child structure to support output_sync; one |
| for stdout and one for stderr as long as they are open. If stdout and |
| stderr share a device they can share a temp file too. |
| Will reset output_sync on error. */ |
| static void |
| setup_tmpfile (struct output *out) |
| { |
| /* Is make's stdout going to the same place as stderr? */ |
| static int combined_output = -1; |
| |
| if (combined_output < 0) |
| combined_output = sync_init (); |
| |
| if (STREAM_OK (stdout)) |
| { |
| int fd = output_tmpfd (); |
| if (fd < 0) |
| goto error; |
| CLOSE_ON_EXEC (fd); |
| out->out = fd; |
| } |
| |
| if (STREAM_OK (stderr)) |
| { |
| if (out->out != OUTPUT_NONE && combined_output) |
| out->err = out->out; |
| else |
| { |
| int fd = output_tmpfd (); |
| if (fd < 0) |
| goto error; |
| CLOSE_ON_EXEC (fd); |
| out->err = fd; |
| } |
| } |
| |
| return; |
| |
| /* If we failed to create a temp file, disable output sync going forward. */ |
| error: |
| output_close (out); |
| output_sync = 0; |
| } |
| |
| /* Synchronize the output of jobs in -j mode to keep the results of |
| each job together. This is done by holding the results in temp files, |
| one for stdout and potentially another for stderr, and only releasing |
| them to "real" stdout/stderr when a semaphore can be obtained. */ |
| |
| void |
| output_dump (struct output *out) |
| { |
| int outfd_not_empty = FD_NOT_EMPTY (out->out); |
| int errfd_not_empty = FD_NOT_EMPTY (out->err); |
| |
| if (outfd_not_empty || errfd_not_empty) |
| { |
| int traced = 0; |
| |
| /* Try to acquire the semaphore. If it fails, dump the output |
| unsynchronized; still better than silently discarding it. |
| We want to keep this lock for as little time as possible. */ |
| void *sem = acquire_semaphore (); |
| |
| /* Log the working directory for this dump. */ |
| if (print_directory_flag && output_sync != OUTPUT_SYNC_RECURSE) |
| traced = log_working_directory (1); |
| |
| if (outfd_not_empty) |
| pump_from_tmp (out->out, stdout); |
| if (errfd_not_empty && out->err != out->out) |
| pump_from_tmp (out->err, stderr); |
| |
| if (traced) |
| log_working_directory (0); |
| |
| /* Exit the critical section. */ |
| if (sem) |
| release_semaphore (sem); |
| |
| /* Truncate and reset the output, in case we use it again. */ |
| if (out->out != OUTPUT_NONE) |
| { |
| int e; |
| lseek (out->out, 0, SEEK_SET); |
| EINTRLOOP (e, ftruncate (out->out, 0)); |
| } |
| if (out->err != OUTPUT_NONE && out->err != out->out) |
| { |
| int e; |
| lseek (out->err, 0, SEEK_SET); |
| EINTRLOOP (e, ftruncate (out->err, 0)); |
| } |
| } |
| } |
| #endif /* NO_OUTPUT_SYNC */ |
| |
| |
| /* Provide support for temporary files. */ |
| |
| #ifndef HAVE_STDLIB_H |
| # ifdef HAVE_MKSTEMP |
| int mkstemp (char *template); |
| # else |
| char *mktemp (char *template); |
| # endif |
| #endif |
| |
| FILE * |
| output_tmpfile (char **name, const char *template) |
| { |
| #ifdef HAVE_FDOPEN |
| int fd; |
| #endif |
| |
| #if defined HAVE_MKSTEMP || defined HAVE_MKTEMP |
| # define TEMPLATE_LEN strlen (template) |
| #else |
| # define TEMPLATE_LEN L_tmpnam |
| #endif |
| *name = xmalloc (TEMPLATE_LEN + 1); |
| strcpy (*name, template); |
| |
| #if defined HAVE_MKSTEMP && defined HAVE_FDOPEN |
| /* It's safest to use mkstemp(), if we can. */ |
| fd = mkstemp (*name); |
| if (fd == -1) |
| return 0; |
| return fdopen (fd, "w"); |
| #else |
| # ifdef HAVE_MKTEMP |
| (void) mktemp (*name); |
| # else |
| (void) tmpnam (*name); |
| # endif |
| |
| # ifdef HAVE_FDOPEN |
| /* Can't use mkstemp(), but guard against a race condition. */ |
| fd = open (*name, O_CREAT|O_EXCL|O_WRONLY, 0600); |
| if (fd == -1) |
| return 0; |
| return fdopen (fd, "w"); |
| # else |
| /* Not secure, but what can we do? */ |
| return fopen (*name, "w"); |
| # endif |
| #endif |
| } |
| |
| |
| /* This code is stolen from gnulib. |
| If/when we abandon the requirement to work with K&R compilers, we can |
| remove this (and perhaps other parts of GNU make!) and migrate to using |
| gnulib directly. |
| |
| This is called only through atexit(), which means die() has already been |
| invoked. So, call exit() here directly. Apparently that works...? |
| */ |
| |
| /* Close standard output, exiting with status 'exit_failure' on failure. |
| If a program writes *anything* to stdout, that program should close |
| stdout and make sure that it succeeds before exiting. Otherwise, |
| suppose that you go to the extreme of checking the return status |
| of every function that does an explicit write to stdout. The last |
| printf can succeed in writing to the internal stream buffer, and yet |
| the fclose(stdout) could still fail (due e.g., to a disk full error) |
| when it tries to write out that buffered data. Thus, you would be |
| left with an incomplete output file and the offending program would |
| exit successfully. Even calling fflush is not always sufficient, |
| since some file systems (NFS and CODA) buffer written/flushed data |
| until an actual close call. |
| |
| Besides, it's wasteful to check the return value from every call |
| that writes to stdout -- just let the internal stream state record |
| the failure. That's what the ferror test is checking below. |
| |
| It's important to detect such failures and exit nonzero because many |
| tools (most notably 'make' and other build-management systems) depend |
| on being able to detect failure in other tools via their exit status. */ |
| |
| static void |
| close_stdout (void) |
| { |
| int prev_fail = ferror (stdout); |
| int fclose_fail = fclose (stdout); |
| |
| if (prev_fail || fclose_fail) |
| { |
| if (fclose_fail) |
| error (NILF, _("write error: %s"), strerror (errno)); |
| else |
| error (NILF, _("write error")); |
| exit (EXIT_FAILURE); |
| } |
| } |
| |
| |
| void |
| output_init (struct output *out) |
| { |
| if (out) |
| { |
| out->out = out->err = OUTPUT_NONE; |
| out->syncout = !!output_sync; |
| return; |
| } |
| |
| /* Configure this instance of make. Be sure stdout is line-buffered. */ |
| |
| #ifdef HAVE_SETVBUF |
| # ifdef SETVBUF_REVERSED |
| setvbuf (stdout, _IOLBF, xmalloc (BUFSIZ), BUFSIZ); |
| # else /* setvbuf not reversed. */ |
| /* Some buggy systems lose if we pass 0 instead of allocating ourselves. */ |
| setvbuf (stdout, 0, _IOLBF, BUFSIZ); |
| # endif /* setvbuf reversed. */ |
| #elif HAVE_SETLINEBUF |
| setlinebuf (stdout); |
| #endif /* setlinebuf missing. */ |
| |
| /* Force stdout/stderr into append mode. This ensures parallel jobs won't |
| lose output due to overlapping writes. */ |
| set_append_mode (fileno (stdout)); |
| set_append_mode (fileno (stderr)); |
| |
| #ifdef HAVE_ATEXIT |
| if (STREAM_OK (stdout)) |
| atexit (close_stdout); |
| #endif |
| } |
| |
| void |
| output_close (struct output *out) |
| { |
| if (! out) |
| { |
| if (stdio_traced) |
| log_working_directory (0); |
| return; |
| } |
| |
| #ifndef NO_OUTPUT_SYNC |
| output_dump (out); |
| #endif |
| |
| if (out->out >= 0) |
| close (out->out); |
| if (out->err >= 0 && out->err != out->out) |
| close (out->err); |
| |
| output_init (out); |
| } |
| |
| /* We're about to generate output: be sure it's set up. */ |
| void |
| output_start () |
| { |
| #ifndef NO_OUTPUT_SYNC |
| /* If we're syncing output make sure the temporary file is set up. */ |
| if (output_context && output_context->syncout) |
| if (! OUTPUT_ISSET(output_context)) |
| setup_tmpfile (output_context); |
| #endif |
| |
| /* If we're not syncing this output per-line or per-target, make sure we emit |
| the "Entering..." message where appropriate. */ |
| if (output_sync == OUTPUT_SYNC_NONE || output_sync == OUTPUT_SYNC_RECURSE) |
| if (! stdio_traced && print_directory_flag) |
| stdio_traced = log_working_directory (1); |
| } |
| |
| void |
| outputs (int is_err, const char *msg) |
| { |
| if (! msg || *msg == '\0') |
| return; |
| |
| output_start (); |
| |
| _outputs (output_context, is_err, msg); |
| } |
| |
| |
| /* Return formatted string buffers. |
| If we move to gnulib we can use vasnprintf() etc. to make this simpler. |
| Note these functions use a static buffer, so each call overwrites the |
| results of the previous call. */ |
| |
| static struct fmtstring |
| { |
| char *buffer; |
| unsigned int size; |
| unsigned int len; |
| } fmtbuf = { NULL, 0, 0 }; |
| |
| /* Concatenate a formatted string onto the format buffer. */ |
| static const char * |
| vfmtconcat (const char *fmt, va_list args) |
| { |
| va_list vcopy; |
| int tot; |
| int unused = fmtbuf.size - fmtbuf.len; |
| |
| va_copy (vcopy, args); |
| |
| tot = vsnprintf (&fmtbuf.buffer[fmtbuf.len], unused, fmt, args); |
| assert (tot >= 0); |
| |
| if (tot >= unused) |
| { |
| fmtbuf.size += tot * 2; |
| fmtbuf.buffer = xrealloc (fmtbuf.buffer, fmtbuf.size); |
| |
| unused = fmtbuf.size - fmtbuf.len; |
| tot = vsnprintf (&fmtbuf.buffer[fmtbuf.len], unused, fmt, vcopy); |
| } |
| |
| va_end (vcopy); |
| |
| fmtbuf.len += tot; |
| |
| return fmtbuf.buffer; |
| } |
| |
| static const char * |
| fmtconcat (const char *fmt, ...) |
| { |
| const char *p; |
| va_list args; |
| |
| va_start (args, fmt); |
| p = vfmtconcat (fmt, args); |
| va_end (args); |
| |
| return p; |
| } |
| |
| /* Print a message on stdout. */ |
| |
| void |
| message (int prefix, const char *fmt, ...) |
| { |
| va_list args; |
| |
| assert (fmt != NULL); |
| |
| fmtbuf.len = 0; |
| |
| if (prefix) |
| { |
| if (makelevel == 0) |
| fmtconcat ("%s: ", program); |
| else |
| fmtconcat ("%s[%u]: ", program, makelevel); |
| } |
| |
| va_start (args, fmt); |
| vfmtconcat (fmt, args); |
| va_end (args); |
| |
| fmtconcat ("\n"); |
| |
| outputs (0, fmtbuf.buffer); |
| } |
| |
| /* Print an error message. */ |
| |
| void |
| error (const gmk_floc *flocp, const char *fmt, ...) |
| { |
| va_list args; |
| |
| assert (fmt != NULL); |
| |
| fmtbuf.len = 0; |
| |
| if (flocp && flocp->filenm) |
| fmtconcat ("%s:%lu: ", flocp->filenm, flocp->lineno); |
| else if (makelevel == 0) |
| fmtconcat ("%s: ", program); |
| else |
| fmtconcat ("%s[%u]: ", program, makelevel); |
| |
| va_start (args, fmt); |
| vfmtconcat (fmt, args); |
| va_end (args); |
| |
| fmtconcat ("\n"); |
| |
| outputs (1, fmtbuf.buffer); |
| } |
| |
| /* Print an error message and exit. */ |
| |
| void |
| fatal (const gmk_floc *flocp, const char *fmt, ...) |
| { |
| va_list args; |
| |
| assert (fmt != NULL); |
| |
| fmtbuf.len = 0; |
| |
| if (flocp && flocp->filenm) |
| fmtconcat ("%s:%lu: *** ", flocp->filenm, flocp->lineno); |
| else if (makelevel == 0) |
| fmtconcat ("%s: *** ", program); |
| else |
| fmtconcat ("%s[%u]: *** ", program, makelevel); |
| |
| va_start (args, fmt); |
| vfmtconcat (fmt, args); |
| va_end (args); |
| |
| fmtconcat (_(". Stop.\n")); |
| outputs (1, fmtbuf.buffer); |
| |
| die (2); |
| } |
| |
| /* Print an error message from errno. */ |
| |
| void |
| perror_with_name (const char *str, const char *name) |
| { |
| error (NILF, _("%s%s: %s"), str, name, strerror (errno)); |
| } |
| |
| /* Print an error message from errno and exit. */ |
| |
| void |
| pfatal_with_name (const char *name) |
| { |
| fatal (NILF, _("%s: %s"), name, strerror (errno)); |
| |
| /* NOTREACHED */ |
| } |