/*
 *                     RCS utilities
 */
/*****************************************************************************
 *****************************************************************************
 *
 * Copyright (C) 1982 by Walter F. Tichy
 *                       Purdue University
 *                       Computer Science Department
 *                       West Lafayette, IN 47907
 *
 * All rights reserved. No part of this software may be sold or distributed
 * in any form or by any means without the prior written permission of the
 * author.
 * Report problems and direct all inquiries to Tichy@purdue (ARPA net).
 */



/* $Log:	C.rcsutil $
 * Revision 1.1  90/05/22  21:53:51  pmoore
 * Initial revision
 * 
 * Revision 4.3  87/10/18  10:40:22  narten
 * Updating version numbers. Changes relative to 1.1 actually
 * relative to 4.1
 *
 * Revision 1.3  87/09/24  14:01:01  narten
 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
 * warnings)
 *
 * Revision 1.2  87/03/27  14:22:43  jenkins
 * Port to suns
 *
 * Revision 1.1  84/01/23  14:50:43  kcs
 * Initial revision
 *
 * Revision 4.1  83/05/10  15:53:13  wft
 * Added getcaller() and findlock().
 * Changed catchints() to check SIGINT for SIG_IGN before setting up the signal
 * (needed for background jobs in older shells). Added restoreints().
 * Removed printing of full RCS path from logcommand().
 *
 * Revision 3.8  83/02/15  15:41:49  wft
 * Added routine fastcopy() to copy remainder of a file in blocks.
 *
 * Revision 3.7  82/12/24  15:25:19  wft
 * added catchints(), ignoreints() for catching and ingnoring interrupts;
 * fixed catchsig().
 *
 * Revision 3.6  82/12/08  21:52:05  wft
 * Using DATEFORM to format dates.
 *
 * Revision 3.5  82/12/04  18:20:49  wft
 * Replaced SNOOPDIR with SNOOPFILE; changed addlock() to update
 * lockedby-field.
 *
 * Revision 3.4  82/12/03  17:17:43  wft
 * Added check to addlock() ensuring only one lock per person.
 * Addlock also returns a pointer to the lock created. Deleted fancydate().
 *
 * Revision 3.3  82/11/27  12:24:37  wft
 * moved rmsema(), trysema(), trydiraccess(), getfullRCSname() to rcsfnms.c.
 * Introduced macro SNOOP so that snoop can be placed in directory other than
 * TARGETDIR. Changed %02d to %.2d for compatibility reasons.
 *
 * Revision 3.2  82/10/18  21:15:11  wft
 * added function getfullRCSname().
 *
 * Revision 3.1  82/10/13  16:17:37  wft
 * Cleanup message is now suppressed in quiet mode.
 */

#include "swis.h"
#include <signal.h>
#include "rcsbase.h"

extern FILE *finptr;
extern char *RCSfilename;
extern char *basename;

void (*oldSIGINT) (int);	/* saves the original value for SIGINT */

/* Function: gets the callers login from his uid.
 * If the uid is root, tries to get the true login with getlogin().
 * For RISC OS, tries to find an OS variable RCS$Name. If not, uses "root".
 */
char *getcaller(void)
{
	char *name = getenv("RCS$Name");

	if (name)
		return strdup(name);

	return "root";
}

/* Finds the first lock held by who and returns a pointer
 * to the locked delta; also removes the lock if delete==true.
 * Returns nil if there is no lock held by who.
 */
struct hshentry *findlock(char *who, int delete)
{
	register struct lock *next, *trail;
	struct lock dummy;

	dummy.nextlock = next = Locks;
	trail = &dummy;
	while (next != nil)
	{
		if (strcmp(who, next->login) == 0)
			break;	/* found a lock */
		trail = next;
		next = next->nextlock;
	}
	if (next != nil)
	{
		/* found one */
		if (delete)
		{
			/* delete it */
			trail->nextlock = next->nextlock;
			Locks = dummy.nextlock;
			next->delta->lockedby = nil;	/* reset locked-by */
		}
		return next->delta;
	}
	else
		return nil;
}

/* Given a delta, addlock checks whether
 * the delta is locked by somebody other than who.
 * If so, an error message is printed, and false returned.
 * If the delta is not reserved at all, a lock for it is added,
 * and a pointer for the lock returned.
 */
struct lock *addlock(struct hshentry * delta, char *who)
{
	struct lock *next;

	next = Locks;
	while (next != nil)
	{
		if (cmpnum(delta->num, next->delta->num) == 0)
		{
			if (strcmp(who, next->login) == 0)
				return next;
			/* lock exists already */
			else
			{
				error("revision %s already locked by %s",
				      delta->num, next->login);
				return false;
			}
		}
		else
		{
			if (strcmp(who, next->login) == 0)
			{
				error("you already locked %s; only one lock allowed per person.",
				      next->delta->num);
				return false;
			}
			else
			{
				next = next->nextlock;
			}
		}
	}
	/* not found; set up new lockblock */
	next = (struct lock *) malloc(sizeof(struct lock));
	delta->lockedby = next->login = who;
	next->delta = delta;
	next->nextlock = Locks;
	Locks = next;
	return next;
}

/* Function: adds a new symbolic name and associates it with node delta.
 * If name already exists and rebind is true, the name is associated
 * with the new delta; otherwise, an error message is printed and
 * false returned. Returns true it successful.
 */
int addsymbol(struct hshentry * delta, char *name, int rebind)
{
	register struct assoc *next;
	next = Symbols;
	while (next != nil)
	{
		if (strcmp(name, next->symbol) == 0)
		{
			if (rebind)
			{
				next->delta = delta;
				return true;
			}
			else
			{
				error("symbolic name %s already bound to %s",
				      name, next->delta->num);
				return false;
			}
		}
		else
			next = next->nextassoc;
	}
	/* not found; insert new pair. */
	next = (struct assoc *) malloc(sizeof(struct assoc));
	next->symbol = name;
	next->delta = delta;
	next->nextassoc = Symbols;
	Symbols = next;
	return true;
}

/* Function: Returns true if who is the superuser, the owner of the
 * file, the access list is empty, or who is on the access list.
 * Prints an error message and returns false otherwise.
 * Always returns true on RISC OS.
 */
int checkaccesslist(char *who)
{
	USE(who);
	return true;
}

/* Signal handling */

void catchsig(int sig)
{
	VOID signal(sig, SIG_IGN);
	diagnose("\nRCS: cleaning up\n");
	VOID cleanup();
	exit(1);
}


void catchints(void)
{
	cksignal(SIGINT);
	cksignal(SIGTERM);
}


void cksignal(int sig)
{
	if (signal(sig, SIG_IGN) != SIG_IGN)
		VOID signal(sig, catchsig);
}

void ignoreints(void)
{
	VOID signal(SIGINT, SIG_IGN);
	VOID signal(SIGTERM, SIG_IGN);
}


void restoreints(void)
{
	if (oldSIGINT != SIG_IGN)
		VOID signal(SIGINT, catchsig);
	VOID signal(SIGTERM, catchsig);
}

/* Function: copies the remainder of file inf to outf. First flushes the
 * output file, and then copies the remainder in blocks.
 */
void fastcopy(FILE * inf, FILE * outf)
{
	char buf[BUFSIZ];
	register int rcount, wcount;

	if (fflush(outf) == EOF)
	{
		faterror("write error");
	}

	/* now read the rest of the file in blocks */
	while (!feof(inf))
	{
		rcount = fread(buf, 1, BUFSIZ, inf);
		if (ferror(inf))
		{
			faterror("read error");
		}
		wcount = fwrite(buf, 1, rcount, outf);
		if (wcount != rcount)
		{
			faterror("write error");
		}
	}
}

/* Function: Renames a file. If a simple OS rename fails, the file is
 * copied. This allows renames across filing system boundaries.
 * If the destination filename exists, the function deletes it (even
 * if locked) first.
 * Returns 0 on success, 1 on failure.
 */
int frename(const char *old, const char *new)
{
	register int result;
	register int n;
	FILE *in, *out;
	_kernel_osfile_block blk;
	char buf[BUFSIZ];

	/* Check the new file. If it exists, and is not a directory,
	 * unlock it (if necessary) and delete it.
	 */
	result = _kernel_osfile (17, new, &blk);

	/* If the file is a directory, or an error occurred, return failure */
	if (result == 2 || result == _kernel_ERROR)
		return 1;

	/* If the file exists and is locked, unlock it */
	if (result == 1 && (blk.end & 0x0008) != 0)
	{
		blk.end &= ~0x0008;
		if (_kernel_osfile(4, new, &blk) == _kernel_ERROR)
			return 1;
	}

	/* If the file exists, delete it */
	if (result == 1 && _kernel_osfile(6, new, &blk) == _kernel_ERROR)
		return 1;

	/* Now try a simple OS rename */
	if (rename(old, new) == 0)
		return 0;

	/* No luck. Get the old file attributes (to ensure that it exists,
	 * and is not locked, and for later copying to the new file).
	 */
	result = _kernel_osfile (17, old, &blk);

	/* If the file is not a simple file, or an error occurred,
	 * or the file is locked, return failure.
	 */
	if (result != 1 || (blk.end & 0x0008) != 0)
		return 1;
	
	/* Now prepare to copy the file */
	if ((in = fopen(old, "rb")) == NULL)
		return 1;

	if ((out = fopen(new, "wb")) == NULL)
	{
		ffclose(in);
		return 1;
	}

	/* Copy the file */
	while (!feof(in))
	{
		n = fread(buf, 1, BUFSIZ, in);
		if (ferror(in) || fwrite(buf, 1, n, out) != n)
		{
			ffclose(in);
			ffclose(out);
			return 1;
		}
	}

	ffclose(in);
	ffclose(out);

	/* Now copy the file attributes across, and delete the old
	 * file. Don't worry about errors - they're not too serious,
	 * and it's too late to do much anyway.
	 */
	_kernel_osfile(1, new, &blk);
	_kernel_osfile(6, old, &blk);

	return 0;
}

#ifdef SNOOPFILE

/* Function: start a process to write the file that logs the RCS command.
 * Each line in the log file contains the following information:
 * operation, revision(r), backward deltas applied(b), forward deltas
 * applied(f), total deltas present(t), creation date of delta(d), date of
 * operation(o), login of caller, RCS file name.
 */
void logcommand(char *commandname, struct hshentry * delta,
		    struct hshentry * sequence[], char *login)
{
	char command[200];
	char curdate[datelength];
	register int i, backward, forward;
	time_t clock;
	struct tm *tm;

	clock = time((time_t *) 0);
	tm = localtime(&clock);

	VOID sprintf(curdate, DATEFORM,
		         tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
		         tm->tm_hour, tm->tm_min, tm->tm_sec);

	i = backward = forward = 0;
	while (sequence[i] != nil)
	{			/* count deltas to be applied */
		if (countnumflds(sequence[i]->num) == 2)
			backward++;	/* reverse delta */
		else
			forward++;	/* branch delta  */
		i++;
	}
	VOID sprintf(command, "%s \"%s %10sr %3db %3df %3dt %sc %so %s %s\" &\n",
		         SNOOP, commandname, delta->num, backward, forward, TotalDeltas, delta->date,
		         curdate, login, basename);
	VOID system(command);
}
#endif
