Attached is a rewrite of the method vfmtconcat() in output.c. It seems to fix 
the problem.

-----Original Message-----
From: Paul Smith [mailto:psm...@gnu.org] 
Sent: Tuesday, April 08, 2014 2:00 PM
To: Rob Juergens
Cc: Philip Guenther; bug-make@gnu.org
Subject: Re: error reporting

On Tue, 2014-04-08 at 20:15 +0000, Rob Juergens wrote:
> Note that in Unix, vsnprintf() returns the TOTAL number of chars 
> needed (add 1 for the null). If the output would overflow the buffer, 
> then you would get a return value larger than the specified buffer 
> size.
> 
> In Windoze,  vsnprintf() will return -1 if the buffer would be 
> overflowed, and there is no indication of what length the buffer must 
> be.

Yes, I'm well aware of the difference in behavior, unfortunately :-/.

> Microsoft is *not* going to change this, since that would break 
> who-knows how many existing programs that depend on that behavior.

Well, that's a shame: if true MSVC will never be a conforming compiler 
implementation for C++11 or any newer C++ standard.

The C++11 standard clearly states that the return value of the
(standard) vsnprintf() function must be the "number of characters that would 
have been written if [the buffer size] had been sufficiently large, not 
counting the terminating null character".  This is basically the exact text for 
the C99 standard, imported into the C++11 standard.

Microsoft is on the C++ standards committee and they certainly were aware of 
this, so my hope is they have a plan to allow for both "legacy"
implementations and "conforming" implementations.

> Attached are 2013 files and updated other files

I'm really not excited about the prospect of continuing to add new project 
files every year for each new version of Visual Studio.  Isn't there any sort 
of backward-compatibility that allows the older files to work in newer Visual 
Studio releases?

Also, is there any way to get these project files out of the root directory?  
I'd be a lot more sanguine about them if we could put them into the "w32" 
subdirectory, or in an "msvc" subdirectory or something and get them out of the 
way.

To my mind the only reason to ship Visual Studio project files with GNU make is 
if there are people who want to develop and enhance GNU make itself, and who 
want to use Visual Studio to do it.  For people who just want to build GNU make 
on Windows and use the result for other projects, surely it's easier to just 
run a .bat build file or maybe use an nmake file to build make.exe; that's all 
you need.  Visual Studio seems like real overkill for that.


/* 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 snprintf 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 = 0;

  va_copy (vcopy, args);

#if OLD_WAY
  {
    int unused = fmtbuf.size - fmtbuf.len;
    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);
      }
  }
#else /* NEW_WAY */
  {
    if ( fmtbuf.buffer == NULL )
      {
         fmtbuf.size   = BUFSIZ;
         fmtbuf.buffer = xmalloc(fmtbuf.size);
      }

    for (;;)
      {
         int unused = fmtbuf.size - fmtbuf.len;
         tot = vsnprintf (&fmtbuf.buffer[fmtbuf.len], unused, fmt, args);

         if ( tot > 0 && tot < unused )
            break;
         fmtbuf.size *= 2;
         fmtbuf.buffer = xrealloc(fmtbuf.buffer, fmtbuf.size);
      }
  }
#endif

  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 */
}
_______________________________________________
Bug-make mailing list
Bug-make@gnu.org
https://lists.gnu.org/mailman/listinfo/bug-make

Reply via email to