make/src/misc.c
Paul Smith 38b19976f5 Fix issues found by ASAN and Coverity
* tests/test_driver.pl: Preserve the LSAN_OPTIONS variable.
* tests/scripts/targets/ONESHELL: Don't set a local variable.
* tests/scripts/functions/let: Test empty let variable.
* src/posixos.c (osync_parse_mutex): Free existing osync_tmpfile.
* src/misc.c (get_tmpfd): Set umask() before invoking mkstemp().
* src/ar.c (ar_parse_name): Check invalid name (shouldn't happen).
* src/function.c (define_new_function): Free previous function entry
when replacing it with a new one.
* src/job.c (child_execute_job): Initialize pid for safety.
(construct_command_argv_internal): In oneshell mode ensure that the
returned argv has the right format (0th element is a pointer to the
entire buffer).
2022-10-31 02:23:04 -04:00

977 lines
19 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Miscellaneous generic support functions for GNU Make.
Copyright (C) 1988-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
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 <https://www.gnu.org/licenses/>. */
#include "makeint.h"
#include "filedef.h"
#include "dep.h"
#include "os.h"
#include "debug.h"
/* GNU make no longer supports pre-ANSI89 environments. */
#include <stdarg.h>
#ifdef WINDOWS32
# include <windows.h>
# include <io.h>
#endif
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#else
# include <sys/file.h>
#endif
unsigned int
make_toui (const char *str, const char **error)
{
char *end;
unsigned long val = strtoul (str, &end, 10);
if (error)
{
if (str[0] == '\0')
*error = "Missing value";
else if (*end != '\0')
*error = "Invalid value";
else
*error = NULL;
}
return val;
}
/* Convert val into a string, written to buf. buf must be large enough
to hold the largest possible value, plus a nul byte. Returns buf.
We can't use standard PRI* here: those are based on intNN_t types. */
char *
make_lltoa (long long val, char *buf)
{
sprintf (buf, "%" MK_PRI64_PREFIX "d", val);
return buf;
}
char *
make_ulltoa (unsigned long long val, char *buf)
{
sprintf (buf, "%" MK_PRI64_PREFIX "u", val);
return buf;
}
/* Simple random number generator, for use with shuffle.
This doesn't need to be truly random, just pretty random. Use our own
implementation rather than relying on the C runtime's rand() so we always
get the same results for a given seed, regardless of C runtime. */
static unsigned int mk_state = 0;
void
make_seed (unsigned int seed)
{
mk_state = seed;
}
unsigned int
make_rand ()
{
/* mk_state must never be 0. */
if (mk_state == 0)
mk_state = (unsigned int)(time (NULL) ^ make_pid ()) + 1;
/* A simple xorshift RNG. */
mk_state ^= mk_state << 13;
mk_state ^= mk_state >> 17;
mk_state ^= mk_state << 5;
return mk_state;
}
/* Compare strings *S1 and *S2.
Return negative if the first is less, positive if it is greater,
zero if they are equal. */
int
alpha_compare (const void *v1, const void *v2)
{
const char *s1 = *((char **)v1);
const char *s2 = *((char **)v2);
if (*s1 != *s2)
return *s1 - *s2;
return strcmp (s1, s2);
}
/* Discard each backslash-newline combination from LINE.
Backslash-backslash-newline combinations become backslash-newlines.
This is done by copying the text at LINE into itself. */
void
collapse_continuations (char *line)
{
char *out = line;
char *in = line;
char *q;
q = strchr(in, '\n');
if (q == 0)
return;
do
{
char *p = q;
int i;
size_t out_line_length;
if (q > line && q[-1] == '\\')
{
/* Search for more backslashes. */
i = -2;
while (&p[i] >= line && p[i] == '\\')
--i;
++i;
}
else
i = 0;
/* The number of backslashes is now -I, keep half of them. */
out_line_length = (p - in) + i - i/2;
if (out != in)
memmove (out, in, out_line_length);
out += out_line_length;
/* When advancing IN, skip the newline too. */
in = q + 1;
if (i & 1)
{
/* Backslash/newline handling:
In traditional GNU make all trailing whitespace, consecutive
backslash/newlines, and any leading non-newline whitespace on the
next line is reduced to a single space.
In POSIX, each backslash/newline and is replaced by a space. */
while (ISBLANK (*in))
++in;
if (! posix_pedantic)
while (out > line && ISBLANK (out[-1]))
--out;
*out++ = ' ';
}
else
{
/* If the newline isn't quoted, put it in the output. */
*out++ = '\n';
}
q = strchr(in, '\n');
}
while (q);
memmove(out, in, strlen(in) + 1);
}
/* Print N spaces (used in debug for target-depth). */
void
print_spaces (unsigned int n)
{
while (n-- > 0)
putchar (' ');
}
/* Return a string whose contents concatenate the NUM strings provided
This string lives in static, re-used memory. */
const char *
concat (unsigned int num, ...)
{
static size_t rlen = 0;
static char *result = NULL;
size_t ri = 0;
va_list args;
va_start (args, num);
while (num-- > 0)
{
const char *s = va_arg (args, const char *);
size_t l = xstrlen (s);
if (l == 0)
continue;
if (ri + l > rlen)
{
rlen = ((rlen ? rlen : 60) + l) * 2;
result = xrealloc (result, rlen);
}
memcpy (result + ri, s, l);
ri += l;
}
va_end (args);
/* Get some more memory if we don't have enough space for the
terminating '\0'. */
if (ri == rlen)
{
rlen = (rlen ? rlen : 60) * 2;
result = xrealloc (result, rlen);
}
result[ri] = '\0';
return result;
}
#ifndef HAVE_UNISTD_H
pid_t getpid ();
#endif
pid_t make_pid ()
{
return getpid ();
}
/* Like malloc but get fatal error if memory is exhausted. */
/* Don't bother if we're using dmalloc; it provides these for us. */
#ifndef HAVE_DMALLOC_H
#undef xmalloc
#undef xcalloc
#undef xrealloc
#undef xstrdup
void *
xmalloc (size_t size)
{
/* Make sure we don't allocate 0, for pre-ISO implementations. */
void *result = malloc (size ? size : 1);
if (result == 0)
out_of_memory ();
return result;
}
void *
xcalloc (size_t size)
{
/* Make sure we don't allocate 0, for pre-ISO implementations. */
void *result = calloc (size ? size : 1, 1);
if (result == 0)
out_of_memory ();
return result;
}
void *
xrealloc (void *ptr, size_t size)
{
void *result;
/* Some older implementations of realloc() don't conform to ISO. */
if (! size)
size = 1;
result = ptr ? realloc (ptr, size) : malloc (size);
if (result == 0)
out_of_memory ();
return result;
}
char *
xstrdup (const char *ptr)
{
char *result;
#ifdef HAVE_STRDUP
result = strdup (ptr);
#else
result = malloc (strlen (ptr) + 1);
#endif
if (result == 0)
out_of_memory ();
#ifdef HAVE_STRDUP
return result;
#else
return strcpy (result, ptr);
#endif
}
#endif /* HAVE_DMALLOC_H */
char *
xstrndup (const char *str, size_t length)
{
char *result;
#ifdef HAVE_STRNDUP
result = strndup (str, length);
if (result == 0)
out_of_memory ();
#else
result = xmalloc (length + 1);
if (length > 0)
strncpy (result, str, length);
result[length] = '\0';
#endif
return result;
}
#ifndef HAVE_MEMRCHR
void *
memrchr(const void* str, int ch, size_t len)
{
const char* sp = str;
const char* cp = sp;
if (len == 0)
return NULL;
cp += len - 1;
while (cp[0] != ch)
{
if (cp == sp)
return NULL;
--cp;
}
return (void*)cp;
}
#endif
/* Limited INDEX:
Search through the string STRING, which ends at LIMIT, for the character C.
Returns a pointer to the first occurrence, or nil if none is found.
Like INDEX except that the string searched ends where specified
instead of at the first null. */
char *
lindex (const char *s, const char *limit, int c)
{
while (s < limit)
if (*s++ == c)
return (char *)(s - 1);
return 0;
}
/* Return the address of the first whitespace or null in the string S. */
char *
end_of_token (const char *s)
{
while (! END_OF_TOKEN (*s))
++s;
return (char *)s;
}
/* Return the address of the first nonwhitespace or null in the string S. */
char *
next_token (const char *s)
{
NEXT_TOKEN (s);
return (char *)s;
}
/* Find the next token in PTR; return the address of it, and store the length
of the token into *LENGTHPTR if LENGTHPTR is not nil. Set *PTR to the end
of the token, so this function can be called repeatedly in a loop. */
char *
find_next_token (const char **ptr, size_t *lengthptr)
{
const char *p = next_token (*ptr);
if (*p == '\0')
return 0;
*ptr = end_of_token (p);
if (lengthptr != 0)
*lengthptr = *ptr - p;
return (char *)p;
}
/* Write a BUFFER of size LEN to file descriptor FD.
Retry short writes from EINTR. Return LEN, or -1 on error. */
ssize_t
writebuf (int fd, const void *buffer, size_t len)
{
const char *msg = buffer;
size_t l = len;
while (l)
{
ssize_t r;
EINTRLOOP (r, write (fd, msg, l));
if (r < 0)
return r;
l -= r;
msg += r;
}
return (ssize_t)len;
}
/* Read until we get LEN bytes from file descriptor FD, into BUFFER.
Retry short reads on EINTR. If we get an error, return it.
Return 0 at EOF. */
ssize_t
readbuf (int fd, void *buffer, size_t len)
{
char *msg = buffer;
while (len)
{
ssize_t r;
EINTRLOOP (r, read (fd, msg, len));
if (r < 0)
return r;
if (r == 0)
break;
len -= r;
msg += r;
}
return (ssize_t)(msg - (char*)buffer);
}
/* Copy a chain of 'struct dep'. For 2nd expansion deps, dup the name. */
struct dep *
copy_dep_chain (const struct dep *d)
{
struct dep *firstnew = 0;
struct dep *lastnew = 0;
while (d != 0)
{
struct dep *c = xmalloc (sizeof (struct dep));
memcpy (c, d, sizeof (struct dep));
if (c->need_2nd_expansion)
c->name = xstrdup (c->name);
c->next = 0;
if (firstnew == 0)
firstnew = lastnew = c;
else
lastnew = lastnew->next = c;
d = d->next;
}
return firstnew;
}
/* Free a chain of struct nameseq.
For struct dep chains use free_dep_chain. */
void
free_ns_chain (struct nameseq *ns)
{
while (ns != 0)
{
struct nameseq *t = ns;
ns = ns->next;
free_ns (t);
}
}
#ifdef MAKE_MAINTAINER_MODE
void
spin (const char* type)
{
char filenm[256];
struct stat dummy;
sprintf (filenm, ".make-spin-%s", type);
if (stat (filenm, &dummy) == 0)
{
fprintf (stderr, "SPIN on %s\n", filenm);
do
#ifdef WINDOWS32
Sleep (1000);
#else
sleep (1);
#endif
while (stat (filenm, &dummy) == 0);
}
}
void
dbg (const char *fmt, ...)
{
FILE *fp = fopen ("/tmp/gmkdebug.log", "a+");
va_list args;
char buf[4096];
va_start (args, fmt);
vsprintf (buf, fmt, args);
va_end (args);
fprintf(fp, "%u: %s\n", (unsigned) make_pid (), buf);
fflush (fp);
fclose (fp);
}
#endif
/* Provide support for temporary files. */
#ifndef HAVE_STDLIB_H
# ifdef HAVE_MKSTEMP
int mkstemp (char *template);
# else
char *mktemp (char *template);
# endif
#endif
#ifndef HAVE_UMASK
mode_t
umask (mode_t mask)
{
return 0;
}
#endif
#ifdef VMS
# define DEFAULT_TMPFILE "sys$scratch:gnv$make_cmdXXXXXX.com"
#else
# define DEFAULT_TMPFILE "GmXXXXXX"
#endif
const char *
get_tmpdir ()
{
static const char *tmpdir = NULL;
if (!tmpdir)
{
#if defined (__MSDOS__) || defined (WINDOWS32) || defined (__EMX__)
# define TMP_EXTRAS "TMP", "TEMP",
#else
# define TMP_EXTRAS
#endif
const char *tlist[] = { "MAKE_TMPDIR", "TMPDIR", TMP_EXTRAS NULL };
const char **tp;
unsigned int found = 0;
for (tp = tlist; *tp; ++tp)
if ((tmpdir = getenv (*tp)) && *tmpdir != '\0')
{
struct stat st;
int r;
found = 1;
EINTRLOOP(r, stat (tmpdir, &st));
if (r < 0)
OSSS (error, NILF,
_("%s value %s: %s"), *tp, tmpdir, strerror (errno));
else if (! S_ISDIR (st.st_mode))
OSS (error, NILF,
_("%s value %s: not a directory"), *tp, tmpdir);
else
return tmpdir;
}
tmpdir = DEFAULT_TMPDIR;
if (found)
OS (error, NILF, _("using default temporary directory '%s'"), tmpdir);
}
return tmpdir;
}
static char *
get_tmptemplate ()
{
const char *tmpdir = get_tmpdir ();
char *template;
char *cp;
template = xmalloc (strlen (tmpdir) + CSTRLEN (DEFAULT_TMPFILE) + 2);
cp = stpcpy (template, tmpdir);
#if !defined VMS
/* It's not possible for tmpdir to be empty. */
if (! ISDIRSEP (cp[-1]))
*(cp++) = '/';
#endif
strcpy (cp, DEFAULT_TMPFILE);
return template;
}
#if !HAVE_MKSTEMP || !HAVE_FDOPEN
/* Generate a temporary filename. This is not safe as another program could
snipe our filename after we've generated it: use this only on systems
without more secure alternatives. */
static char *
get_tmppath ()
{
char *path;
# ifdef HAVE_MKTEMP
path = get_tmptemplate ();
if (*mktemp (path) == '\0')
pfatal_with_name ("mktemp");
# else
path = xmalloc (L_tmpnam + 1);
if (tmpnam (path) == NULL)
pfatal_with_name ("tmpnam");
# endif
return path;
}
#endif
/* Generate a temporary file and return an fd for it. If name is NULL then
the temp file is anonymous and will be deleted when the process exits. */
int
get_tmpfd (char **name)
{
int fd = -1;
char *tmpnm;
mode_t mask;
/* If there's an os-specific way to get an anoymous temp file use it. */
if (!name)
{
fd = os_anontmp ();
if (fd >= 0)
return fd;
}
/* Preserve the current umask, and set a restrictive one for temp files.
Only really needed for mkstemp() but won't hurt for the open method. */
mask = umask (0077);
#if defined(HAVE_MKSTEMP)
tmpnm = get_tmptemplate ();
/* It's safest to use mkstemp(), if we can. */
EINTRLOOP (fd, mkstemp (tmpnm));
#else
tmpnm = get_tmppath ();
/* Can't use mkstemp(), but try to guard against a race condition. */
EINTRLOOP (fd, open (tmpnm, O_CREAT|O_EXCL|O_RDWR, 0600));
#endif
if (fd < 0)
OSS (fatal, NILF,
_("create temporary file %s: %s"), tmpnm, strerror (errno));
if (name)
*name = tmpnm;
else
{
int r;
EINTRLOOP (r, unlink (tmpnm));
if (r < 0)
OSS (fatal, NILF,
_("unlink temporary file %s: %s"), tmpnm, strerror (errno));
free (tmpnm);
}
umask (mask);
return fd;
}
/* Return a FILE* for a temporary file, opened in the safest way possible.
Set name to point to an allocated buffer containing the name of the file.
Note, this cannot be NULL! */
FILE *
get_tmpfile (char **name)
{
/* Be consistent with tmpfile, which opens as if by "wb+". */
const char *tmpfile_mode = "wb+";
FILE *file;
#if defined(HAVE_FDOPEN)
int fd = get_tmpfd (name);
ENULLLOOP (file, fdopen (fd, tmpfile_mode));
if (file == NULL)
OSS (fatal, NILF,
_("fdopen: temporary file %s: %s"), *name, strerror (errno));
#else
/* Preserve the current umask, and set a restrictive one for temp files. */
mode_t mask = umask (0077);
int err;
*name = get_tmppath ();
/* Although this fopen is insecure, it is executed only on non-fdopen
platforms, which should be a rarity nowadays. */
ENULLLOOP (file, fopen (*name, tmpfile_mode));
if (file == NULL)
OSS (fatal, NILF,
_("fopen: temporary file %s: %s"), *name, strerror (errno));
umask (mask);
#endif
return file;
}
#if !HAVE_STRCASECMP && !HAVE_STRICMP && !HAVE_STRCMPI
/* If we don't have strcasecmp() (from POSIX), or anything that can substitute
for it, define our own version. */
int
strcasecmp (const char *s1, const char *s2)
{
while (1)
{
int c1 = (unsigned char) *(s1++);
int c2 = (unsigned char) *(s2++);
if (isalpha (c1))
c1 = tolower (c1);
if (isalpha (c2))
c2 = tolower (c2);
if (c1 != '\0' && c1 == c2)
continue;
return (c1 - c2);
}
}
#endif
#if !HAVE_STRNCASECMP && !HAVE_STRNICMP && !HAVE_STRNCMPI
/* If we don't have strncasecmp() (from POSIX), or anything that can
substitute for it, define our own version. */
int
strncasecmp (const char *s1, const char *s2, size_t n)
{
while (n-- > 0)
{
int c1 = (unsigned char) *(s1++);
int c2 = (unsigned char) *(s2++);
if (isalpha (c1))
c1 = tolower (c1);
if (isalpha (c2))
c2 = tolower (c2);
if (c1 != '\0' && c1 == c2)
continue;
return (c1 - c2);
}
return 0;
}
#endif
#ifdef NEED_GET_PATH_MAX
unsigned int
get_path_max (void)
{
static unsigned int value;
if (value == 0)
{
long x = pathconf ("/", _PC_PATH_MAX);
if (x > 0)
value = (unsigned int) x;
else
value = PATH_MAX;
}
return value;
}
#endif
#if !HAVE_MEMPCPY
void *
mempcpy (void *dest, const void *src, size_t n)
{
return (char *) memcpy (dest, src, n) + n;
}
#endif
#if !HAVE_STPCPY
char *
stpcpy (char *dest, const char *src)
{
char *d = dest;
const char *s = src;
do
*d++ = *s;
while (*s++ != '\0');
return d - 1;
}
#endif
#if !HAVE_STRTOLL
# undef UNSIGNED
# undef USE_NUMBER_GROUPING
# undef USE_WIDE_CHAR
# define QUAD 1
# include <strtol.c>
#endif
#if !HAVE_STRERROR
char *
strerror (int errnum)
{
static char msg[256];
#define SETMSG(_e, _m) case _e: strcpy(msg, _m); break
switch (errnum)
{
#ifdef EPERM
SETMSG (EPERM , "Operation not permitted");
#endif
#ifdef ENOENT
SETMSG (ENOENT , "No such file or directory");
#endif
#ifdef ESRCH
SETMSG (ESRCH , "No such process");
#endif
#ifdef EINTR
SETMSG (EINTR , "Interrupted system call");
#endif
#ifdef EIO
SETMSG (EIO , "I/O error");
#endif
#ifdef ENXIO
SETMSG (ENXIO , "No such device or address");
#endif
#ifdef E2BIG
SETMSG (E2BIG , "Argument list too long");
#endif
#ifdef ENOEXEC
SETMSG (ENOEXEC, "Exec format error");
#endif
#ifdef EBADF
SETMSG (EBADF , "Bad file number");
#endif
#ifdef ECHILD
SETMSG (ECHILD , "No child processes");
#endif
#ifdef EAGAIN
SETMSG (EAGAIN , "Try again");
#endif
#ifdef ENOMEM
SETMSG (ENOMEM , "Out of memory");
#endif
#ifdef EACCES
SETMSG (EACCES , "Permission denied");
#endif
#ifdef EFAULT
SETMSG (EFAULT , "Bad address");
#endif
#ifdef ENOTBLK
SETMSG (ENOTBLK, "Block device required");
#endif
#ifdef EBUSY
SETMSG (EBUSY , "Device or resource busy");
#endif
#ifdef EEXIST
SETMSG (EEXIST , "File exists");
#endif
#ifdef EXDEV
SETMSG (EXDEV , "Cross-device link");
#endif
#ifdef ENODEV
SETMSG (ENODEV , "No such device");
#endif
#ifdef ENOTDIR
SETMSG (ENOTDIR, "Not a directory");
#endif
#ifdef EISDIR
SETMSG (EISDIR , "Is a directory");
#endif
#ifdef EINVAL
SETMSG (EINVAL , "Invalid argument");
#endif
#ifdef ENFILE
SETMSG (ENFILE , "File table overflow");
#endif
#ifdef EMFILE
SETMSG (EMFILE , "Too many open files");
#endif
#ifdef ENOTTY
SETMSG (ENOTTY , "Not a typewriter");
#endif
#ifdef ETXTBSY
SETMSG (ETXTBSY, "Text file busy");
#endif
#ifdef EFBIG
SETMSG (EFBIG , "File too large");
#endif
#ifdef ENOSPC
SETMSG (ENOSPC , "No space left on device");
#endif
#ifdef ESPIPE
SETMSG (ESPIPE , "Illegal seek");
#endif
#ifdef EROFS
SETMSG (EROFS , "Read-only file system");
#endif
#ifdef EMLINK
SETMSG (EMLINK , "Too many links");
#endif
#ifdef EPIPE
SETMSG (EPIPE , "Broken pipe");
#endif
#ifdef EDOM
SETMSG (EDOM , "Math argument out of domain of func");
#endif
#ifdef ERANGE
SETMSG (ERANGE , "Math result not representable");
#endif
default: sprintf (msg, "Unknown error %d", errnum); break;
}
return msg;
}
#endif