/*	$NetBSD: util.c,v 1.21 2005/03/25 23:00:55 skd Exp $	*/

/*
 * Copyright (c) 1988, Larry Wall
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following condition
 * is met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this condition and the following disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: util.c,v 1.21 2005/03/25 23:00:55 skd Exp $");
#endif /* not lint */

#include <sys/param.h>

#include "EXTERN.h"
#include "common.h"
#include "INTERN.h"
#include "util.h"
#include "backupfile.h"

#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

/*
 * Rename a file, copying it if necessary.
 */
int
move_file(char *from, char *to)
{
	char bakname[MAXPATHLEN];
	char *s;
	size_t i;
	int fromfd;

	if ( check_only == TRUE )
		return(0);

	/* to stdout? */

	if (strEQ(to, "-")) {
#ifdef DEBUGGING
		if (debug & 4)
			say("Moving %s to stdout.\n", from);
#endif
		fromfd = open(from, 0);
		if (fromfd < 0)
			pfatal("internal error, can't reopen %s", from);
		while ((i = read(fromfd, buf, sizeof buf)) > 0)
			if (write(1, buf, i) != 1)
				pfatal("write failed");
		Close(fromfd);
		return 0;
	}

	if (origprae) {
		strlcpy(bakname, origprae, sizeof(bakname));
		strlcat(bakname, to, sizeof(bakname));
	} else {
#ifndef NODIR
		char *backupname = find_backup_file_name(to);
		strlcpy(bakname, backupname, sizeof(bakname));
		free(backupname);
#else /* NODIR */
		strlcpy(bakname, to, sizeof(bakname));
    		strlcat(bakname, simple_backup_suffix, sizeof(bakname));
#endif /* NODIR */
	}

	if (stat(to, &filestat) == 0) {	/* output file exists */
		dev_t to_device = filestat.st_dev;
		ino_t to_inode  = filestat.st_ino;
		char *simplename = bakname;
	
		for (s = bakname; *s; s++) {
			if (*s == '/')
				simplename = s + 1;
		}
		/*
		 * Find a backup name that is not the same file.
		 * Change the first lowercase char into uppercase;
		 * if that isn't sufficient, chop off the first char
		 * and try again.
		 */
		while (stat(bakname, &filestat) == 0 &&
		       to_device == filestat.st_dev &&
		       to_inode == filestat.st_ino) {
			/* Skip initial non-lowercase chars. */
			for (s = simplename;
			     *s && *s == toupper((unsigned char)*s);
			     s++)
				;
			if (*s)
				*s = toupper((unsigned char)*s);
			else
				strcpy(simplename, simplename + 1);
		}
		while (unlink(bakname) >= 0)
			;	/* while() is for benefit of Eunice */
#ifdef DEBUGGING
		if (debug & 4)
			say("Moving %s to %s.\n", to, bakname);
#endif
		if (link(to, bakname) < 0) {
			/*
			 * Maybe `to' is a symlink into a different file
			 * system. Copying replaces the symlink with a file;
			 * using rename would be better.
			 */
			int tofd;
			int bakfd;

			bakfd = creat(bakname, 0666);
			if (bakfd < 0) {
				say("Can't backup %s, output is in %s: %s\n",
				    to, from, strerror(errno));
				return -1;
			}
			tofd = open(to, 0);
			if (tofd < 0)
				pfatal("internal error, can't open %s", to);
			while ((i = read(tofd, buf, sizeof buf)) > 0)
				if (write(bakfd, buf, i) != i)
					pfatal("write failed");
			Close(tofd);
			Close(bakfd);
		}
		while (unlink(to) >= 0) ;
	}
#ifdef DEBUGGING
	if (debug & 4)
		say("Moving %s to %s.\n", from, to);
#endif
	if (link(from, to) < 0) {		/* different file system? */
		int tofd;

		tofd = creat(to, 0666);
		if (tofd < 0) {
			say("Can't create %s, output is in %s: %s\n",
			    to, from, strerror(errno));
			return -1;
		}
		fromfd = open(from, 0);
		if (fromfd < 0)
			pfatal("internal error, can't reopen %s", from);
		while ((i = read(fromfd, buf, sizeof buf)) > 0)
			if (write(tofd, buf, i) != i)
				pfatal("write failed");
		Close(fromfd);
		Close(tofd);
	}
	Unlink(from);
	return 0;
}

/*
 * Copy a file.
 */
void
copy_file(char *from, char *to)
{
	int tofd;
	int fromfd;
	size_t i;

	tofd = creat(to, 0666);
	if (tofd < 0)
		pfatal("can't create %s", to);
	fromfd = open(from, 0);
	if (fromfd < 0)
		pfatal("internal error, can't reopen %s", from);
	while ((i = read(fromfd, buf, sizeof buf)) > 0)
		if (write(tofd, buf, i) != i)
			pfatal("write to %s failed", to);
	Close(fromfd);
	Close(tofd);
}

/*
 * malloc with result test.
 */
void *
xmalloc(size_t size)
{
	void *p;

	if ((p = malloc(size)) == NULL)
		fatal("out of memory\n");
	return p;
}

/*
 * realloc with result test.
 */
void *
xrealloc(void *ptr, size_t size)
{
	void *p;

	if ((p = realloc(ptr, size)) == NULL)
		fatal("out of memory\n");
	return p;
}

/*
 * strdup with result test.
 */
char *
xstrdup(const char *s)
{
	char *p;

	if ((p = strdup(s)) == NULL)
		fatal("out of memory\n");
	return p;
}

/*
 * Vanilla terminal output.
 */
void
say(const char *pat, ...)
{
	va_list ap;
	va_start(ap, pat);
	
	vfprintf(stderr, pat, ap);
	va_end(ap);
	Fflush(stderr);
}

/*
 * Terminal output, pun intended.
 */
void				/* very void */
fatal(const char *pat, ...)
{
	va_list ap;
	va_start(ap, pat);
	
	fprintf(stderr, "patch: **** ");
	vfprintf(stderr, pat, ap);
	va_end(ap);
	my_exit(1);
}

/*
 * Say something from patch, something from the system, then silence...
 */
void				/* very void */
pfatal(const char *pat, ...)
{
	va_list ap;
	int errnum = errno;
	va_start(ap, pat);
	
	fprintf(stderr, "patch: **** ");
	vfprintf(stderr, pat, ap);
	fprintf(stderr, ": %s\n", strerror(errnum));
	va_end(ap);
	my_exit(1);
}

/*
 * Get a response from the user, somehow or other.
 */
void
ask(const char *pat, ...)
{
	int ttyfd;
	int r;
	bool tty2 = isatty(2);
	va_list ap;
	va_start(ap, pat);

	(void)vsprintf(buf, pat, ap);
	va_end(ap);
	Fflush(stderr);
	write(2, buf, strlen(buf));
	if (tty2) {			/* might be redirected to a file */
		r = read(2, buf, sizeof buf);
	} else if (isatty(1)) {		/* this may be new file output */
		Fflush(stdout);
		write(1, buf, strlen(buf));
		r = read(1, buf, sizeof buf);
	} else if ((ttyfd = open("/dev/tty", 2)) >= 0 && isatty(ttyfd)) {
					/* might be deleted or unwritable */
		write(ttyfd, buf, strlen(buf));
		r = read(ttyfd, buf, sizeof buf);
		Close(ttyfd);
	} else if (isatty(0)) {		/* this is probably patch input */
		Fflush(stdin);
		write(0, buf, strlen(buf));
		r = read(0, buf, sizeof buf);
	} else {			/* no terminal at all--default it */
		buf[0] = '\n';
		r = 1;
	}
	if (r <= 0)
		buf[0] = 0;
	else
		buf[r] = '\0';
	if (!tty2)
		say("%s", buf);
}

/*
 * How to handle certain events when not in a critical region.
 */
void
set_signals(int reset)
{
	static void (*hupval)(int);
	static void (*intval)(int);

	if (!reset) {
		hupval = signal(SIGHUP, SIG_IGN);
		if (hupval != SIG_IGN)
			hupval = my_exit;
		intval = signal(SIGINT, SIG_IGN);
		if (intval != SIG_IGN)
			intval = my_exit;
	}
	Signal(SIGHUP, hupval);
	Signal(SIGINT, intval);
}

/*
 * How to handle certain events when in a critical region.
 */
void
ignore_signals()
{
	Signal(SIGHUP, SIG_IGN);
	Signal(SIGINT, SIG_IGN);
}

/*
 * Make sure we'll have the directories to create a file.
 * If `striplast' is TRUE, ignore the last element of `filename'.
 */
void
makedirs(char *filename, bool striplast)
{
	char tmpbuf[MAXPATHLEN];
	char *s = tmpbuf;
	char *dirv[MAXPATHLEN];	/* Point to the NULs between elements.  */
	int i;
	int dirvp = 0;		/* Number of finished entries in dirv. */

	/*
	 * Copy `filename' into `tmpbuf' with a NUL instead of a slash
	 * between the directories.
	 */
	while (*filename) {
		if (*filename == '/') {
			filename++;
			dirv[dirvp++] = s;
			*s++ = '\0';
		} else {
			*s++ = *filename++;
		}
	}
	*s = '\0';
	dirv[dirvp] = s;
	if (striplast)
		dirvp--;
	if (dirvp < 0)
		return;

	strlcpy(buf, "mkdir", sizeof(buf));
	s = buf;
	for (i = 0; i <= dirvp; i++) {
		struct stat sbuf;

		if (stat(tmpbuf, &sbuf) && errno == ENOENT) {
			while (*s)
				s++;
			*s++ = ' ';
			strlcpy(s, tmpbuf, sizeof(buf) - (s - buf));
		}
		*dirv[i] = '/';
	}
	if (s != buf)
		system(buf);
}

/*
 * Make filenames more reasonable.
 */
char *
fetchname(char *at, int strip_leading, int assume_exists)
{
	char *fullname;
	char *name;
	char *t;
	char tmpbuf[MAXPATHLEN];
	int sleading = strip_leading;

	if (!at)
		return NULL;
	while (isspace((unsigned char)*at))
		at++;
#ifdef DEBUGGING
	if (debug & 128)
		say("fetchname %s %d %d\n", at, strip_leading, assume_exists);
#endif
	filename_is_dev_null = FALSE;
	if (strnEQ(at, "/dev/null", 9)) {
		/* So files can be created by diffing against /dev/null. */
		filename_is_dev_null = TRUE;
		return NULL;
	}
	name = fullname = t = xstrdup(at);

	/* Strip off up to `sleading' leading slashes and null terminate. */
	for (; *t && !isspace((unsigned char)*t); t++)
		if (*t == '/')
			if (--sleading >= 0)
				name = t + 1;
	*t = '\0';

	/*
	 * If no -p option was given (957 is the default value!),
	 * we were given a relative pathname,
	 * and the leading directories that we just stripped off all exist,
	 * put them back on. 
	 */
	if (strip_leading == 957 && name != fullname && *fullname != '/') {
		name[-1] = '\0';
		if (stat(fullname, &filestat) == 0 &&
		    S_ISDIR(filestat.st_mode)) {
			name[-1] = '/';
			name = fullname;
		}
	}

	name = xstrdup(name);
	free(fullname);

	if (stat(name, &filestat) && !assume_exists) {
		char *filebase = basename(name);
		size_t pathlen = filebase - name;

		/* Put any leading path into `tmpbuf'. */
		if (pathlen >= sizeof(tmpbuf))
			return NULL;
		strncpy(tmpbuf, name, pathlen);
		tmpbuf[pathlen] = '\0';

#define try(f, a1, a2) \
    (snprintf(tmpbuf + pathlen, sizeof(tmpbuf) - pathlen, f, a1, a2), \
     stat(tmpbuf, &filestat) == 0)
#define try1(f, a1) \
    (snprintf(tmpbuf + pathlen, sizeof(tmpbuf) - pathlen, f, a1), \
     stat(tmpbuf, &filestat) == 0)
		if (try("RCS/%s%s", filebase, RCSSUFFIX) ||
		    try1("RCS/%s"  , filebase) ||
		    try(    "%s%s", filebase, RCSSUFFIX) ||
		    try("SCCS/%s%s", SCCSPREFIX, filebase) ||
		    try(     "%s%s", SCCSPREFIX, filebase))
			return name;
		free(name);
		name = NULL;
	}

	return name;
}
