1080 lines
24 KiB
C
1080 lines
24 KiB
C
/* utility functions for `patch' */
|
|
|
|
/* $Id: util.c,v 1.24 1997/07/10 08:16:12 eggert Exp $ */
|
|
|
|
/*
|
|
Copyright 1986 Larry Wall
|
|
Copyright 1992, 1993, 1997 Free Software Foundation, Inc.
|
|
|
|
This program 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 2, or (at your option)
|
|
any later version.
|
|
|
|
This program 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; see the file COPYING.
|
|
If not, write to the Free Software Foundation,
|
|
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#define XTERN extern
|
|
#include <common.h>
|
|
#include <backupfile.h>
|
|
#include <quotearg.h>
|
|
#include <version.h>
|
|
#undef XTERN
|
|
#define XTERN
|
|
#include <util.h>
|
|
|
|
#include <maketime.h>
|
|
#include <partime.h>
|
|
|
|
#include <signal.h>
|
|
#if !defined SIGCHLD && defined SIGCLD
|
|
#define SIGCHLD SIGCLD
|
|
#endif
|
|
#if ! HAVE_RAISE
|
|
# define raise(sig) kill (getpid (), sig)
|
|
#endif
|
|
|
|
#ifdef __STDC__
|
|
# include <stdarg.h>
|
|
# define vararg_start va_start
|
|
#else
|
|
# define vararg_start(ap,p) va_start (ap)
|
|
# if HAVE_VARARGS_H
|
|
# include <varargs.h>
|
|
# else
|
|
typedef char *va_list;
|
|
# define va_dcl int va_alist;
|
|
# define va_start(ap) ((ap) = (va_list) &va_alist)
|
|
# define va_arg(ap, t) (((t *) ((ap) += sizeof (t))) [-1])
|
|
# define va_end(ap)
|
|
# endif
|
|
#endif
|
|
|
|
static void makedirs PARAMS ((char *));
|
|
|
|
/* Move a file FROM to TO, renaming it if possible and copying it if necessary.
|
|
If we must create TO, use MODE to create it.
|
|
If FROM is null, remove TO (ignoring FROMSTAT).
|
|
Back up TO if BACKUP is nonzero. */
|
|
|
|
#ifdef __STDC__
|
|
/* If mode_t doesn't promote to itself, we can't use old-style definition. */
|
|
void
|
|
move_file (char const *from, char *to, mode_t mode, int backup)
|
|
#else
|
|
void
|
|
move_file (from, to, mode, backup)
|
|
char const *from;
|
|
char *to;
|
|
mode_t mode;
|
|
int backup;
|
|
#endif
|
|
{
|
|
struct stat to_st;
|
|
int to_errno = ! backup ? -1 : stat (to, &to_st) == 0 ? 0 : errno;
|
|
|
|
if (backup)
|
|
{
|
|
int try_makedirs_errno = 0;
|
|
char *bakname;
|
|
|
|
if (origprae || origbase)
|
|
{
|
|
char const *p = origprae ? origprae : "";
|
|
char const *b = origbase ? origbase : "";
|
|
char const *o = base_name (to);
|
|
size_t plen = strlen (p);
|
|
size_t tlen = o - to;
|
|
size_t blen = strlen (b);
|
|
size_t osize = strlen (o) + 1;
|
|
bakname = xmalloc (plen + tlen + blen + osize);
|
|
memcpy (bakname, p, plen);
|
|
memcpy (bakname + plen, to, tlen);
|
|
memcpy (bakname + plen + tlen, b, blen);
|
|
memcpy (bakname + plen + tlen + blen, o, osize);
|
|
for (p += FILESYSTEM_PREFIX_LEN (p); *p; p++)
|
|
if (ISSLASH (*p))
|
|
{
|
|
try_makedirs_errno = ENOENT;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bakname = find_backup_file_name (to);
|
|
if (!bakname)
|
|
memory_fatal ();
|
|
}
|
|
|
|
if (to_errno)
|
|
{
|
|
int fd;
|
|
if (debug & 4)
|
|
say ("creating empty unreadable file `%s'\n", bakname);
|
|
try_makedirs_errno = ENOENT;
|
|
unlink (bakname);
|
|
while ((fd = creat (bakname, 0)) < 0)
|
|
{
|
|
if (errno != try_makedirs_errno)
|
|
pfatal ("can't create file `%s'", bakname);
|
|
makedirs (bakname);
|
|
try_makedirs_errno = 0;
|
|
}
|
|
if (close (fd) != 0)
|
|
pfatal ("can't close `%s'", bakname);
|
|
}
|
|
else
|
|
{
|
|
if (debug & 4)
|
|
say ("renaming `%s' to `%s'\n", to, bakname);
|
|
while (rename (to, bakname) != 0)
|
|
{
|
|
if (errno != try_makedirs_errno)
|
|
pfatal ("can't rename `%s' to `%s'", to, bakname);
|
|
makedirs (bakname);
|
|
try_makedirs_errno = 0;
|
|
}
|
|
}
|
|
|
|
free (bakname);
|
|
}
|
|
|
|
if (from)
|
|
{
|
|
if (debug & 4)
|
|
say ("renaming `%s' to `%s'\n", from, to);
|
|
|
|
if (rename (from, to) != 0)
|
|
{
|
|
int to_dir_known_to_exist = 0;
|
|
|
|
if (errno == ENOENT
|
|
&& (to_errno == -1 || to_errno == ENOENT))
|
|
{
|
|
makedirs (to);
|
|
to_dir_known_to_exist = 1;
|
|
if (rename (from, to) == 0)
|
|
return;
|
|
}
|
|
|
|
if (errno == EXDEV)
|
|
{
|
|
if (! backup)
|
|
{
|
|
if (unlink (to) == 0)
|
|
to_dir_known_to_exist = 1;
|
|
else if (errno != ENOENT)
|
|
pfatal ("can't remove `%s'", to);
|
|
}
|
|
if (! to_dir_known_to_exist)
|
|
makedirs (to);
|
|
copy_file (from, to, mode);
|
|
return;
|
|
}
|
|
|
|
pfatal ("can't rename `%s' to `%s'", from, to);
|
|
}
|
|
}
|
|
else if (! backup)
|
|
{
|
|
if (debug & 4)
|
|
say ("removing `%s'\n", to);
|
|
if (unlink (to) != 0)
|
|
pfatal ("can't remove `%s'", to);
|
|
}
|
|
}
|
|
|
|
/* Create FILE with OPEN_FLAGS, and with MODE adjusted so that
|
|
we can read and write the file and that the file is not executable.
|
|
Return the file descriptor. */
|
|
#ifdef __STDC__
|
|
/* If mode_t doesn't promote to itself, we can't use old-style definition. */
|
|
int
|
|
create_file (char const *file, int open_flags, mode_t mode)
|
|
#else
|
|
int
|
|
create_file (file, open_flags, mode)
|
|
char const *file;
|
|
int open_flags;
|
|
mode_t mode;
|
|
#endif
|
|
{
|
|
int fd;
|
|
mode |= S_IRUSR | S_IWUSR;
|
|
mode &= ~ (S_IXUSR | S_IXGRP | S_IXOTH);
|
|
if (! (O_CREAT && O_TRUNC))
|
|
close (creat (file, mode));
|
|
fd = open (file, O_CREAT | O_TRUNC | open_flags, mode);
|
|
if (fd < 0)
|
|
pfatal ("can't create `%s'", file);
|
|
return fd;
|
|
}
|
|
|
|
/* Copy a file. */
|
|
|
|
#ifdef __STDC__
|
|
/* If mode_t doesn't promote to itself, we can't use old-style definition. */
|
|
void
|
|
copy_file (char const *from, char const *to, mode_t mode)
|
|
#else
|
|
void
|
|
copy_file (from, to, mode)
|
|
char const *from;
|
|
char const *to;
|
|
mode_t mode;
|
|
#endif
|
|
{
|
|
int tofd;
|
|
int fromfd;
|
|
size_t i;
|
|
|
|
if ((fromfd = open (from, O_RDONLY | O_BINARY)) < 0)
|
|
pfatal ("can't reopen `%s'", from);
|
|
tofd = create_file (to, O_WRONLY | O_BINARY, mode);
|
|
while ((i = read (fromfd, buf, bufsize)) != 0)
|
|
{
|
|
if (i == -1)
|
|
read_fatal ();
|
|
if (write (tofd, buf, i) != i)
|
|
write_fatal ();
|
|
}
|
|
if (close (fromfd) != 0)
|
|
read_fatal ();
|
|
if (close (tofd) != 0)
|
|
write_fatal ();
|
|
}
|
|
|
|
static char const DEV_NULL[] = NULL_DEVICE;
|
|
|
|
static char const SCCSPREFIX[] = "s.";
|
|
static char const GET[] = "get ";
|
|
static char const GET_LOCKED[] = "get -e ";
|
|
static char const SCCSDIFF1[] = "get -p ";
|
|
static char const SCCSDIFF2[] = "|diff - %s";
|
|
|
|
static char const RCSSUFFIX[] = ",v";
|
|
static char const CHECKOUT[] = "co %s";
|
|
static char const CHECKOUT_LOCKED[] = "co -l %s";
|
|
static char const RCSDIFF1[] = "rcsdiff %s";
|
|
|
|
/* Return "RCS" if FILENAME is controlled by RCS,
|
|
"SCCS" if it is controlled by SCCS, and 0 otherwise.
|
|
READONLY is nonzero if we desire only readonly access to FILENAME.
|
|
FILESTAT describes FILENAME's status or is 0 if FILENAME does not exist.
|
|
If successful and if GETBUF is nonzero, set *GETBUF to a command
|
|
that gets the file; similarly for DIFFBUF and a command to diff the file.
|
|
*GETBUF and *DIFFBUF must be freed by the caller. */
|
|
char const *
|
|
version_controller (filename, readonly, filestat, getbuf, diffbuf)
|
|
char const *filename;
|
|
int readonly;
|
|
struct stat const *filestat;
|
|
char **getbuf;
|
|
char **diffbuf;
|
|
{
|
|
struct stat cstat;
|
|
char const *filebase = base_name (filename);
|
|
char const *dotslash = *filename == '-' ? "./" : "";
|
|
size_t dir_len = filebase - filename;
|
|
size_t filenamelen = strlen (filename);
|
|
size_t maxfixlen = sizeof "SCCS/" - 1 + sizeof SCCSPREFIX - 1;
|
|
size_t maxtrysize = filenamelen + maxfixlen + 1;
|
|
size_t quotelen = quote_system_arg (0, filename);
|
|
size_t maxgetsize = sizeof GET_LOCKED + quotelen + maxfixlen;
|
|
size_t maxdiffsize =
|
|
(sizeof SCCSDIFF1 + sizeof SCCSDIFF2 + sizeof DEV_NULL - 1
|
|
+ 2 * quotelen + maxfixlen);
|
|
char *trybuf = xmalloc (maxtrysize);
|
|
char const *r = 0;
|
|
|
|
strcpy (trybuf, filename);
|
|
|
|
#define try1(f,a1) (sprintf (trybuf + dir_len, f, a1), stat (trybuf, &cstat) == 0)
|
|
#define try2(f,a1,a2) (sprintf (trybuf + dir_len, f, a1,a2), stat (trybuf, &cstat) == 0)
|
|
|
|
/* Check that RCS file is not working file.
|
|
Some hosts don't report file name length errors. */
|
|
|
|
if ((try2 ("RCS/%s%s", filebase, RCSSUFFIX)
|
|
|| try1 ("RCS/%s", filebase)
|
|
|| try2 ("%s%s", filebase, RCSSUFFIX))
|
|
&& ! (filestat
|
|
&& filestat->st_dev == cstat.st_dev
|
|
&& filestat->st_ino == cstat.st_ino))
|
|
{
|
|
if (getbuf)
|
|
{
|
|
char *p = *getbuf = xmalloc (maxgetsize);
|
|
sprintf (p, readonly ? CHECKOUT : CHECKOUT_LOCKED, dotslash);
|
|
p += strlen (p);
|
|
p += quote_system_arg (p, filename);
|
|
*p = '\0';
|
|
}
|
|
|
|
if (diffbuf)
|
|
{
|
|
char *p = *diffbuf = xmalloc (maxdiffsize);
|
|
sprintf (p, RCSDIFF1, dotslash);
|
|
p += strlen (p);
|
|
p += quote_system_arg (p, filename);
|
|
*p++ = '>';
|
|
strcpy (p, DEV_NULL);
|
|
}
|
|
|
|
r = "RCS";
|
|
}
|
|
else if (try2 ("SCCS/%s%s", SCCSPREFIX, filebase)
|
|
|| try2 ("%s%s", SCCSPREFIX, filebase))
|
|
{
|
|
if (getbuf)
|
|
{
|
|
char *p = *getbuf = xmalloc (maxgetsize);
|
|
sprintf (p, readonly ? GET : GET_LOCKED);
|
|
p += strlen (p);
|
|
p += quote_system_arg (p, trybuf);
|
|
*p = '\0';
|
|
}
|
|
|
|
if (diffbuf)
|
|
{
|
|
char *p = *diffbuf = xmalloc (maxdiffsize);
|
|
strcpy (p, SCCSDIFF1);
|
|
p += sizeof SCCSDIFF1 - 1;
|
|
p += quote_system_arg (p, trybuf);
|
|
sprintf (p, SCCSDIFF2, dotslash);
|
|
p += strlen (p);
|
|
p += quote_system_arg (p, filename);
|
|
*p++ = '>';
|
|
strcpy (p, DEV_NULL);
|
|
}
|
|
|
|
r = "SCCS";
|
|
}
|
|
|
|
free (trybuf);
|
|
return r;
|
|
}
|
|
|
|
/* Get FILENAME from version control system CS. The file already exists if
|
|
EXISTS is nonzero. Only readonly access is needed if READONLY is nonzero.
|
|
Use the command GETBUF to actually get the named file.
|
|
Store the resulting file status into *FILESTAT.
|
|
Return nonzero if successful. */
|
|
int
|
|
version_get (filename, cs, exists, readonly, getbuf, filestat)
|
|
char const *filename;
|
|
char const *cs;
|
|
int exists;
|
|
int readonly;
|
|
char const *getbuf;
|
|
struct stat *filestat;
|
|
{
|
|
if (patch_get < 0)
|
|
{
|
|
ask ("Get file `%s' from %s%s? [y] ", filename,
|
|
cs, readonly ? "" : " with lock");
|
|
if (*buf == 'n')
|
|
return 0;
|
|
}
|
|
|
|
if (dry_run)
|
|
{
|
|
if (! exists)
|
|
fatal ("can't do dry run on nonexistent version-controlled file `%s'; invoke `%s' and try again",
|
|
filename, getbuf);
|
|
}
|
|
else
|
|
{
|
|
if (verbosity == VERBOSE)
|
|
say ("Getting file `%s' from %s%s...\n", filename,
|
|
cs, readonly ? "" : " with lock");
|
|
if (systemic (getbuf) != 0)
|
|
fatal ("can't get file `%s' from %s", filename, cs);
|
|
if (stat (filename, filestat) != 0)
|
|
pfatal ("%s", filename);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Allocate a unique area for a string. */
|
|
|
|
char *
|
|
savebuf (s, size)
|
|
register char const *s;
|
|
register size_t size;
|
|
{
|
|
register char *rv;
|
|
|
|
assert (s && size);
|
|
rv = malloc (size);
|
|
|
|
if (! rv)
|
|
{
|
|
if (! using_plan_a)
|
|
memory_fatal ();
|
|
}
|
|
else
|
|
memcpy (rv, s, size);
|
|
|
|
return rv;
|
|
}
|
|
|
|
char *
|
|
savestr(s)
|
|
char const *s;
|
|
{
|
|
return savebuf (s, strlen (s) + 1);
|
|
}
|
|
|
|
void
|
|
remove_prefix (p, prefixlen)
|
|
char *p;
|
|
size_t prefixlen;
|
|
{
|
|
char const *s = p + prefixlen;
|
|
while ((*p++ = *s++))
|
|
continue;
|
|
}
|
|
|
|
#if !HAVE_VPRINTF
|
|
#define vfprintf my_vfprintf
|
|
static int vfprintf PARAMS ((FILE *, char const *, va_list));
|
|
static int
|
|
vfprintf (stream, format, args)
|
|
FILE *stream;
|
|
char const *format;
|
|
va_list args;
|
|
{
|
|
#if !HAVE_DOPRNT && HAVE__DOPRINTF
|
|
# define _doprnt _doprintf
|
|
#endif
|
|
#if HAVE_DOPRNT || HAVE__DOPRINTF
|
|
_doprnt (format, args, stream);
|
|
return ferror (stream) ? -1 : 0;
|
|
#else
|
|
int *a = (int *) args;
|
|
return fprintf (stream, format,
|
|
a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]);
|
|
#endif
|
|
}
|
|
#endif /* !HAVE_VPRINTF */
|
|
|
|
/* Terminal output, pun intended. */
|
|
|
|
#ifdef __STDC__
|
|
void
|
|
fatal (char const *format, ...)
|
|
#else
|
|
/*VARARGS1*/ void
|
|
fatal (format, va_alist)
|
|
char const *format;
|
|
va_dcl
|
|
#endif
|
|
{
|
|
va_list args;
|
|
fprintf (stderr, "%s: **** ", program_name);
|
|
vararg_start (args, format);
|
|
vfprintf (stderr, format, args);
|
|
va_end (args);
|
|
putc ('\n', stderr);
|
|
fflush (stderr);
|
|
fatal_exit (0);
|
|
}
|
|
|
|
void
|
|
memory_fatal ()
|
|
{
|
|
fatal ("out of memory");
|
|
}
|
|
|
|
void
|
|
read_fatal ()
|
|
{
|
|
pfatal ("read error");
|
|
}
|
|
|
|
void
|
|
write_fatal ()
|
|
{
|
|
pfatal ("write error");
|
|
}
|
|
|
|
/* Say something from patch, something from the system, then silence . . . */
|
|
|
|
#ifdef __STDC__
|
|
void
|
|
pfatal (char const *format, ...)
|
|
#else
|
|
/*VARARGS1*/ void
|
|
pfatal (format, va_alist)
|
|
char const *format;
|
|
va_dcl
|
|
#endif
|
|
{
|
|
int errnum = errno;
|
|
va_list args;
|
|
fprintf (stderr, "%s: **** ", program_name);
|
|
vararg_start (args, format);
|
|
vfprintf (stderr, format, args);
|
|
va_end (args);
|
|
fflush (stderr); /* perror bypasses stdio on some hosts. */
|
|
errno = errnum;
|
|
perror (" ");
|
|
fflush (stderr);
|
|
fatal_exit (0);
|
|
}
|
|
|
|
/* Tell the user something. */
|
|
|
|
#ifdef __STDC__
|
|
void
|
|
say (char const *format, ...)
|
|
#else
|
|
/*VARARGS1*/ void
|
|
say (format, va_alist)
|
|
char const *format;
|
|
va_dcl
|
|
#endif
|
|
{
|
|
va_list args;
|
|
vararg_start (args, format);
|
|
vfprintf (stdout, format, args);
|
|
va_end (args);
|
|
fflush (stdout);
|
|
}
|
|
|
|
/* Get a response from the user, somehow or other. */
|
|
|
|
#ifdef __STDC__
|
|
void
|
|
ask (char const *format, ...)
|
|
#else
|
|
/*VARARGS1*/ void
|
|
ask (format, va_alist)
|
|
char const *format;
|
|
va_dcl
|
|
#endif
|
|
{
|
|
static int ttyfd = -2;
|
|
int r;
|
|
va_list args;
|
|
|
|
vararg_start (args, format);
|
|
vfprintf (stdout, format, args);
|
|
va_end (args);
|
|
fflush (stdout);
|
|
|
|
if (ttyfd == -2)
|
|
{
|
|
/* If standard output is not a tty, don't bother opening /dev/tty,
|
|
since it's unlikely that stdout will be seen by the tty user.
|
|
The isatty test also works around a bug in GNU Emacs 19.34 under Linux
|
|
which makes a call-process `patch' hang when it reads from /dev/tty.
|
|
POSIX.2 requires that we read /dev/tty, though. */
|
|
ttyfd = (posixly_correct || isatty (STDOUT_FILENO)
|
|
? open (TTY_DEVICE, O_RDONLY)
|
|
: -1);
|
|
}
|
|
|
|
if (ttyfd < 0)
|
|
{
|
|
/* No terminal at all -- default it. */
|
|
printf ("\n");
|
|
buf[0] = '\n';
|
|
buf[1] = '\0';
|
|
}
|
|
else
|
|
{
|
|
size_t s = 0;
|
|
while ((r = read (ttyfd, buf + s, bufsize - 1 - s)) == bufsize - 1 - s
|
|
&& buf[bufsize - 2] != '\n')
|
|
{
|
|
s = bufsize - 1;
|
|
bufsize *= 2;
|
|
buf = realloc (buf, bufsize);
|
|
if (!buf)
|
|
memory_fatal ();
|
|
}
|
|
if (r == 0)
|
|
printf ("EOF\n");
|
|
else if (r < 0)
|
|
{
|
|
perror ("tty read");
|
|
fflush (stderr);
|
|
close (ttyfd);
|
|
ttyfd = -1;
|
|
r = 0;
|
|
}
|
|
buf[s + r] = '\0';
|
|
}
|
|
}
|
|
|
|
/* Return nonzero if it OK to reverse a patch. */
|
|
|
|
#ifdef __STDC__
|
|
int
|
|
ok_to_reverse (char const *format, ...)
|
|
#else
|
|
ok_to_reverse (format, va_alist)
|
|
char const *format;
|
|
va_dcl
|
|
#endif
|
|
{
|
|
int r = 0;
|
|
|
|
if (noreverse || ! (force && verbosity == SILENT))
|
|
{
|
|
va_list args;
|
|
vararg_start (args, format);
|
|
vfprintf (stdout, format, args);
|
|
va_end (args);
|
|
}
|
|
|
|
if (noreverse)
|
|
{
|
|
printf (" Skipping patch.\n");
|
|
skip_rest_of_patch = TRUE;
|
|
r = 0;
|
|
}
|
|
else if (force)
|
|
{
|
|
if (verbosity != SILENT)
|
|
printf (" Applying it anyway.\n");
|
|
r = 0;
|
|
}
|
|
else if (batch)
|
|
{
|
|
say (reverse ? " Ignoring -R.\n" : " Assuming -R.\n");
|
|
r = 1;
|
|
}
|
|
else
|
|
{
|
|
ask (reverse ? " Ignore -R? [n] " : " Assume -R? [n] ");
|
|
r = *buf == 'y';
|
|
if (! r)
|
|
{
|
|
ask ("Apply anyway? [n] ");
|
|
if (*buf != 'y')
|
|
{
|
|
if (verbosity != SILENT)
|
|
say ("Skipping patch.\n");
|
|
skip_rest_of_patch = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/* How to handle certain events when not in a critical region. */
|
|
|
|
#define NUM_SIGS (sizeof (sigs) / sizeof (*sigs))
|
|
static int const sigs[] = {
|
|
#ifdef SIGHUP
|
|
SIGHUP,
|
|
#endif
|
|
#ifdef SIGPIPE
|
|
SIGPIPE,
|
|
#endif
|
|
#ifdef SIGTERM
|
|
SIGTERM,
|
|
#endif
|
|
#ifdef SIGXCPU
|
|
SIGXCPU,
|
|
#endif
|
|
#ifdef SIGXFSZ
|
|
SIGXFSZ,
|
|
#endif
|
|
SIGINT
|
|
};
|
|
|
|
#if !HAVE_SIGPROCMASK
|
|
#define sigset_t int
|
|
#define sigemptyset(s) (*(s) = 0)
|
|
#ifndef sigmask
|
|
#define sigmask(sig) (1 << ((sig) - 1))
|
|
#endif
|
|
#define sigaddset(s, sig) (*(s) |= sigmask (sig))
|
|
#define sigismember(s, sig) ((*(s) & sigmask (sig)) != 0)
|
|
#ifndef SIG_BLOCK
|
|
#define SIG_BLOCK 0
|
|
#endif
|
|
#ifndef SIG_UNBLOCK
|
|
#define SIG_UNBLOCK (SIG_BLOCK + 1)
|
|
#endif
|
|
#ifndef SIG_SETMASK
|
|
#define SIG_SETMASK (SIG_BLOCK + 2)
|
|
#endif
|
|
#define sigprocmask(how, n, o) \
|
|
((how) == SIG_BLOCK \
|
|
? ((o) ? *(o) = sigblock (*(n)) : sigblock (*(n))) \
|
|
: (how) == SIG_UNBLOCK \
|
|
? sigsetmask (((o) ? *(o) = sigblock (0) : sigblock (0)) & ~*(n)) \
|
|
: (o ? *(o) = sigsetmask (*(n)) : sigsetmask (*(n))))
|
|
#if !HAVE_SIGSETMASK
|
|
#define sigblock(mask) 0
|
|
#define sigsetmask(mask) 0
|
|
#endif
|
|
#endif
|
|
|
|
static sigset_t initial_signal_mask;
|
|
static sigset_t signals_to_block;
|
|
|
|
#if ! HAVE_SIGACTION
|
|
static RETSIGTYPE fatal_exit_handler PARAMS ((int)) __attribute__ ((noreturn));
|
|
static RETSIGTYPE
|
|
fatal_exit_handler (sig)
|
|
int sig;
|
|
{
|
|
signal (sig, SIG_IGN);
|
|
fatal_exit (sig);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
set_signals(reset)
|
|
int reset;
|
|
{
|
|
int i;
|
|
#if HAVE_SIGACTION
|
|
struct sigaction initial_act, fatal_act;
|
|
fatal_act.sa_handler = fatal_exit;
|
|
sigemptyset (&fatal_act.sa_mask);
|
|
fatal_act.sa_flags = 0;
|
|
#define setup_handler(sig) sigaction (sig, &fatal_act, (struct sigaction *) 0)
|
|
#else
|
|
#define setup_handler(sig) signal (sig, fatal_exit_handler)
|
|
#endif
|
|
|
|
if (!reset)
|
|
{
|
|
#ifdef SIGCHLD
|
|
/* System V fork+wait does not work if SIGCHLD is ignored. */
|
|
signal (SIGCHLD, SIG_DFL);
|
|
#endif
|
|
sigemptyset (&signals_to_block);
|
|
for (i = 0; i < NUM_SIGS; i++)
|
|
{
|
|
int ignoring_signal;
|
|
#if HAVE_SIGACTION
|
|
if (sigaction (sigs[i], (struct sigaction *) 0, &initial_act) != 0)
|
|
continue;
|
|
ignoring_signal = initial_act.sa_handler == SIG_IGN;
|
|
#else
|
|
ignoring_signal = signal (sigs[i], SIG_IGN) == SIG_IGN;
|
|
#endif
|
|
if (! ignoring_signal)
|
|
{
|
|
sigaddset (&signals_to_block, sigs[i]);
|
|
setup_handler (sigs[i]);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Undo the effect of ignore_signals. */
|
|
#if HAVE_SIGPROCMASK || HAVE_SIGSETMASK
|
|
sigprocmask (SIG_SETMASK, &initial_signal_mask, (sigset_t *) 0);
|
|
#else
|
|
for (i = 0; i < NUM_SIGS; i++)
|
|
if (sigismember (&signals_to_block, sigs[i]))
|
|
setup_handler (sigs[i]);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* How to handle certain events when in a critical region. */
|
|
|
|
void
|
|
ignore_signals()
|
|
{
|
|
#if HAVE_SIGPROCMASK || HAVE_SIGSETMASK
|
|
sigprocmask (SIG_BLOCK, &signals_to_block, &initial_signal_mask);
|
|
#else
|
|
int i;
|
|
for (i = 0; i < NUM_SIGS; i++)
|
|
if (sigismember (&signals_to_block, sigs[i]))
|
|
signal (sigs[i], SIG_IGN);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
exit_with_signal (sig)
|
|
int sig;
|
|
{
|
|
sigset_t s;
|
|
signal (sig, SIG_DFL);
|
|
sigemptyset (&s);
|
|
sigaddset (&s, sig);
|
|
sigprocmask (SIG_UNBLOCK, &s, (sigset_t *) 0);
|
|
raise (sig);
|
|
exit (2);
|
|
}
|
|
|
|
int
|
|
systemic (command)
|
|
char const *command;
|
|
{
|
|
if (debug & 8)
|
|
say ("+ %s\n", command);
|
|
fflush (stdout);
|
|
return system (command);
|
|
}
|
|
|
|
#if !HAVE_MKDIR
|
|
/* These mkdir and rmdir substitutes are good enough for `patch';
|
|
they are not general emulators. */
|
|
|
|
static int doprogram PARAMS ((char const *, char const *));
|
|
static int mkdir PARAMS ((char const *, mode_t));
|
|
static int rmdir PARAMS ((char const *));
|
|
|
|
static int
|
|
doprogram (program, arg)
|
|
char const *program;
|
|
char const *arg;
|
|
{
|
|
int result;
|
|
static char const DISCARD_OUTPUT[] = " 2>/dev/null";
|
|
size_t program_len = strlen (program);
|
|
char *cmd = xmalloc (program_len + 1 + quote_system_arg (0, arg)
|
|
+ sizeof DISCARD_OUTPUT);
|
|
char *p = cmd;
|
|
strcpy (p, program);
|
|
p += program_len;
|
|
*p++ = ' ';
|
|
p += quote_system_arg (p, arg);
|
|
strcpy (p, DISCARD_OUTPUT);
|
|
result = systemic (cmd);
|
|
free (cmd);
|
|
return result;
|
|
}
|
|
|
|
#ifdef __STDC__
|
|
/* If mode_t doesn't promote to itself, we can't use old-style definition. */
|
|
static int
|
|
mkdir (char const *path, mode_t mode)
|
|
#else
|
|
static int
|
|
mkdir (path, mode)
|
|
char const *path;
|
|
mode_t mode; /* ignored */
|
|
#endif
|
|
{
|
|
return doprogram ("mkdir", path);
|
|
}
|
|
|
|
static int
|
|
rmdir (path)
|
|
char const *path;
|
|
{
|
|
int result = doprogram ("rmdir", path);
|
|
errno = EEXIST;
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
/* Replace '/' with '\0' in FILENAME if it marks a place that
|
|
needs testing for the existence of directory. Return the address
|
|
of the last location replaced, or 0 if none were replaced. */
|
|
static char *replace_slashes PARAMS ((char *));
|
|
static char *
|
|
replace_slashes (filename)
|
|
char *filename;
|
|
{
|
|
char *f;
|
|
char *last_location_replaced = 0;
|
|
char const *component_start;
|
|
|
|
for (f = filename + FILESYSTEM_PREFIX_LEN (filename); ISSLASH (*f); f++)
|
|
continue;
|
|
|
|
component_start = f;
|
|
|
|
for (; *f; f++)
|
|
if (ISSLASH (*f))
|
|
{
|
|
char *slash = f;
|
|
|
|
/* Treat multiple slashes as if they were one slash. */
|
|
while (ISSLASH (f[1]))
|
|
f++;
|
|
|
|
/* Ignore slashes at the end of the path. */
|
|
if (! f[1])
|
|
break;
|
|
|
|
/* "." and ".." need not be tested. */
|
|
if (! (slash - component_start <= 2
|
|
&& component_start[0] == '.' && slash[-1] == '.'))
|
|
{
|
|
*slash = '\0';
|
|
last_location_replaced = slash;
|
|
}
|
|
|
|
component_start = f + 1;
|
|
}
|
|
|
|
return last_location_replaced;
|
|
}
|
|
|
|
/* Make sure we'll have the directories to create a file.
|
|
Ignore the last element of `filename'. */
|
|
|
|
static void
|
|
makedirs (filename)
|
|
register char *filename;
|
|
{
|
|
register char *f;
|
|
register char *flim = replace_slashes (filename);
|
|
|
|
if (flim)
|
|
{
|
|
/* Create any missing directories, replacing NULs by '/'s.
|
|
Ignore errors. We may have to keep going even after an EEXIST,
|
|
since the path may contain ".."s; and when there is an EEXIST
|
|
failure the system may return some other error number.
|
|
Any problems will eventually be reported when we create the file. */
|
|
for (f = filename; f <= flim; f++)
|
|
if (!*f)
|
|
{
|
|
mkdir (filename,
|
|
S_IRUSR|S_IWUSR|S_IXUSR
|
|
|S_IRGRP|S_IWGRP|S_IXGRP
|
|
|S_IROTH|S_IWOTH|S_IXOTH);
|
|
*f = '/';
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Remove empty ancestor directories of FILENAME.
|
|
Ignore errors, since the path may contain ".."s, and when there
|
|
is an EEXIST failure the system may return some other error number. */
|
|
void
|
|
removedirs (filename)
|
|
char *filename;
|
|
{
|
|
size_t i;
|
|
|
|
for (i = strlen (filename); i != 0; i--)
|
|
if (ISSLASH (filename[i])
|
|
&& ! (ISSLASH (filename[i - 1])
|
|
|| (filename[i - 1] == '.'
|
|
&& (i == 1
|
|
|| ISSLASH (filename[i - 2])
|
|
|| (filename[i - 2] == '.'
|
|
&& (i == 2
|
|
|| ISSLASH (filename[i - 3])))))))
|
|
{
|
|
filename[i] = '\0';
|
|
if (rmdir (filename) == 0 && verbosity == VERBOSE)
|
|
say ("Removed empty directory `%s'.\n", filename);
|
|
filename[i] = '/';
|
|
}
|
|
}
|
|
|
|
static time_t initial_time;
|
|
|
|
void
|
|
init_time ()
|
|
{
|
|
time (&initial_time);
|
|
}
|
|
|
|
/* Make filenames more reasonable. */
|
|
|
|
char *
|
|
fetchname (at, strip_leading, pstamp)
|
|
char *at;
|
|
int strip_leading;
|
|
time_t *pstamp;
|
|
{
|
|
char *name;
|
|
register char *t;
|
|
int sleading = strip_leading;
|
|
time_t stamp = (time_t) -1;
|
|
|
|
while (ISSPACE ((unsigned char) *at))
|
|
at++;
|
|
if (debug & 128)
|
|
say ("fetchname %s %d\n", at, strip_leading);
|
|
|
|
name = at;
|
|
/* Strip off up to `sleading' leading slashes and null terminate. */
|
|
for (t = at; *t; t++)
|
|
{
|
|
if (ISSLASH (*t))
|
|
{
|
|
while (ISSLASH (t[1]))
|
|
t++;
|
|
if (--sleading >= 0)
|
|
name = t+1;
|
|
}
|
|
else if (ISSPACE ((unsigned char) *t))
|
|
{
|
|
if (set_time | set_utc)
|
|
stamp = str2time (t, initial_time, set_utc ? 0L : TM_LOCAL_ZONE);
|
|
else
|
|
{
|
|
/* The head says the file is nonexistent if the timestamp
|
|
is the epoch; but the listed time is local time, not UTC,
|
|
and POSIX.1 allows local time to be 24 hours away from UTC.
|
|
So match any time within 24 hours of the epoch.
|
|
Use a default time zone 24 hours behind UTC so that any
|
|
non-zoned time within 24 hours of the epoch is valid. */
|
|
stamp = str2time (t, initial_time, -24L * 60 * 60);
|
|
if (0 <= stamp && stamp <= 2 * 24L * 60 * 60)
|
|
stamp = 0;
|
|
}
|
|
|
|
*t = '\0';
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!*name)
|
|
return 0;
|
|
|
|
/* Allow files to be created by diffing against /dev/null. */
|
|
if (strcmp (at, "/dev/null") == 0)
|
|
{
|
|
if (pstamp)
|
|
*pstamp = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (pstamp)
|
|
*pstamp = stamp;
|
|
|
|
return savestr (name);
|
|
}
|
|
|
|
GENERIC_OBJECT *
|
|
xmalloc (size)
|
|
size_t size;
|
|
{
|
|
register GENERIC_OBJECT *p = malloc (size);
|
|
if (!p)
|
|
memory_fatal ();
|
|
return p;
|
|
}
|
|
|
|
void
|
|
Fseek (stream, offset, ptrname)
|
|
FILE *stream;
|
|
file_offset offset;
|
|
int ptrname;
|
|
{
|
|
if (file_seek (stream, offset, ptrname) != 0)
|
|
pfatal ("fseek");
|
|
}
|