/*	$NetBSD: supcmisc.c,v 1.20 2009/10/16 12:41:37 christos Exp $	*/

/*
 * Copyright (c) 1992 Carnegie Mellon University
 * All Rights Reserved.
 *
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 *
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 *
 * Carnegie Mellon requests users of this software to return to
 *
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 *
 * any improvements or extensions that they make and grant Carnegie Mellon
 * the rights to redistribute these changes.
 */
/*
 * sup misc. routines, include list processing.
 **********************************************************************
 * HISTORY
 * Revision 1.5  92/08/11  12:07:22  mrt
 * 	Added release to FILEWHEN name.
 * 	Brad's changes: delinted and updated variable argument usage.
 * 	[92/07/26            mrt]
 *
 * Revision 1.3  89/08/15  15:31:28  bww
 * 	Updated to use v*printf() in place of _doprnt().
 * 	From "[89/04/19            mja]" at CMU.
 * 	[89/08/15            bww]
 *
 * 27-Dec-87  Glenn Marcy (gm0w) at Carnegie-Mellon University
 *	Fixed bug in ugconvert() which left pw uninitialized.
 *
 * 25-May-87  Doug Philips (dwp) at Carnegie-Mellon University
 *	Split off from sup.c and changed goaway to use printf
 *	instead of notify if thisC is NULL.
 *
 **********************************************************************
 */

#include "supcdefs.h"
#include "supextern.h"

struct liststruct {		/* uid and gid lists */
	char *Lname;		/* name */
	int Lnumber;		/* uid or gid */
	struct liststruct *Lnext;
};
typedef struct liststruct LIST;

#define HASHBITS	4
#define HASHSIZE	(1<<HASHBITS)
#define HASHMASK	(HASHSIZE-1)
#define LISTSIZE	(HASHSIZE*HASHSIZE)

static LIST *uidL[LISTSIZE];	/* uid and gid lists */
static LIST *gidL[LISTSIZE];

extern COLLECTION *thisC;	/* collection list pointer */
extern int silent;

static int Lhash(char *);
static void Linsert(LIST **, char *, int);
static LIST *Llookup(LIST **, char *);

/*************************************************
 ***    P R I N T   U P D A T E   T I M E S    ***
 *************************************************/

void
prtime(void)
{
	char buf[STRINGLENGTH];
	char relsufix[STRINGLENGTH];
	time_t twhen;

	if ((thisC->Cflags & CFURELSUF) && thisC->Crelease)
		(void) sprintf(relsufix, ".%s", thisC->Crelease);
	else
		relsufix[0] = '\0';
	if (chdir(thisC->Cbase) < 0)
		logerr("Can't change to base directory %s for collection %s",
		    thisC->Cbase, thisC->Cname);
	twhen = getwhen(thisC->Cname, relsufix);
	(void) strcpy(buf, ctime(&twhen));
	buf[strlen(buf) - 1] = '\0';
	loginfo("Last update occurred at %s for collection %s%s",
	    buf, thisC->Cname, relsufix);
}

int 
establishdir(char *fname)
{
	char dpart[STRINGLENGTH], fpart[STRINGLENGTH];
	path(fname, dpart, fpart);
	return (estabd(fname, dpart));
}

int 
makedir(char *fname, unsigned int mode, struct stat * statp)
{
	int en, rv;

	if (lstat(fname, statp) != -1 && !S_ISDIR(statp->st_mode)) {
		if (unlink(fname) == -1) {
			notify("SUP: Can't delete %s (%s)\n", fname,
			    strerror(errno));
			return -1;
		}
	}
	if (mkdir(fname, mode) == -1)
		en = errno;
	else
		en = -1;

	rv = stat(fname, statp);

	if (en != -1)
		errno = en;

	return rv;
}

int 
estabd(char *fname, char *dname)
{
	char dpart[STRINGLENGTH], fpart[STRINGLENGTH];
	struct stat sbuf;
	int x;

	if (stat(dname, &sbuf) >= 0)
		return (FALSE);	/* exists */
	path(dname, dpart, fpart);
	if (strcmp(fpart, ".") == 0) {	/* dname is / or . */
		notify("SUP: Can't create directory %s for %s (Invalid name)\n",
		    dname, fname);
		return (TRUE);
	}
	x = estabd(fname, dpart);
	if (x)
		return (TRUE);
	if (makedir(dname, 0755, &sbuf) < 0) {
		notify("SUP: Can't create directory %s for %s (%s)\n", dname,
		    fname, strerror(errno));
		return TRUE;
	}
	vnotify("SUP Created directory %s for %s\n", dname, fname);
	return (FALSE);
}
/***************************************
 ***    L I S T   R O U T I N E S    ***
 ***************************************/

static int 
Lhash(char *name)
{
	/* Hash function is:  HASHSIZE * (strlen mod HASHSIZE) +
	 * (char   mod HASHSIZE) where "char" is last character of name (if
	 * name is non-null). */

	int len;
	char c;

	len = strlen(name);
	if (len > 0)
		c = name[len - 1];
	else
		c = 0;
	return (((len & HASHMASK) << HASHBITS) | (((int) c) & HASHMASK));
}

static void
Linsert(LIST ** table, char *name, int number)
{
	LIST *l;
	int lno;
	lno = Lhash(name);
	l = (LIST *) malloc(sizeof(LIST));
	if (l == NULL)
		goaway("Cannot allocate memory");
	l->Lname = name;
	l->Lnumber = number;
	l->Lnext = table[lno];
	table[lno] = l;
}

static LIST *
Llookup(LIST ** table, char *name)
{
	int lno;
	LIST *l;
	lno = Lhash(name);
	for (l = table[lno]; l && strcmp(l->Lname, name) != 0; l = l->Lnext);
	return (l);
}

void 
ugconvert(char *uname, char *gname, int *uid, int *gid, int *mode)
{
	LIST *u, *g;
	struct passwd *pw;
	struct group *gr;
	struct stat sbuf;
	static int defuid = -1;
	static int defgid;
	static int first = TRUE;

	if (first) {
		bzero(uidL, sizeof(uidL));
		bzero(gidL, sizeof(gidL));
		first = FALSE;
	}
	pw = NULL;
	if ((u = Llookup(uidL, uname)) != NULL)
		*uid = u->Lnumber;
	else if ((pw = getpwnam(uname)) != NULL) {
		Linsert(uidL, estrdup(uname), pw->pw_uid);
		*uid = pw->pw_uid;
	}
	if (u || pw) {
		if ((g = Llookup(gidL, gname)) != NULL) {
			*gid = g->Lnumber;
			return;
		}
		if ((gr = getgrnam(gname)) != NULL) {
			Linsert(gidL, estrdup(gname), gr->gr_gid);
			*gid = gr->gr_gid;
			return;
		}
		if (pw == NULL)
			pw = getpwnam(uname);
		*mode &= ~S_ISGID;
		*gid = pw->pw_gid;
		return;
	}
	*mode &= ~(S_ISUID | S_ISGID);
	if (defuid >= 0) {
		*uid = defuid;
		*gid = defgid;
		return;
	}
	if (stat(".", &sbuf) < 0) {
		*uid = defuid = getuid();
		*gid = defgid = getgid();
		return;
	}
	*uid = defuid = sbuf.st_uid;
	*gid = defgid = sbuf.st_gid;
}


/*********************************************
 ***    U T I L I T Y   R O U T I N E S    ***
 *********************************************/

void
notify(const char *fmt, ...)
{				/* record error message */
	char buf[STRINGLENGTH];
	char collrelname[STRINGLENGTH];
	char hostname[STRINGLENGTH];
	time_t tloc;
	static FILE *noteF = NULL;	/* mail program on pipe */
	va_list ap;
	int shouldMail = (thisC->Cflags & CFMAIL) && thisC->Cnotify;
	int needFile = shouldMail || silent;

	va_start(ap, fmt);

	if ((thisC->Cflags & CFURELSUF) && thisC->Crelease)
		(void) sprintf(collrelname, "%s-%s", collname, thisC->Crelease);
	else
		(void) strcpy(collrelname, collname);

	if (fmt == NULL) {
		if (noteF && noteF != stdout && (!silent || thisC->Cnogood)) {
			int nr;
			FILE *outF;

			if (shouldMail) {
				(void)gethostname(hostname, sizeof(hostname));
				(void) snprintf(buf, sizeof(buf),
				    "mail -s \"SUP Upgrade of %s on %s\" %s >"
				    " /dev/null", collrelname, hostname,
				    thisC->Cnotify);
				outF = popen(buf, "w");
				if (outF == NULL) {
					logerr("Can't send mail to %s for %s",
					    thisC->Cnotify, collrelname);
					outF = stdout;
				}
			} else
				outF = stdout;

			(void)rewind(noteF);
			while ((nr = fread(buf, 1, sizeof(buf), noteF)) > 0)
				(void)fwrite(buf, 1, nr, outF);
			(void)fflush(outF);
			if (outF != stdout)
				(void)pclose(outF);
			(void)fclose(noteF);
		}
		noteF = NULL;
		va_end(ap);
		return;
	}

	if (noteF == NULL) {
		if (needFile) {
			char template[] = "/tmp/sup.XXXXXX";
			int fd = mkstemp(template);
			if (fd == -1 || (noteF = fdopen(fd, "r+")) == NULL) {
				logerr("Can't open temporary file for %s",
				    collrelname);
				silent = 0;
				noteF = stdout;
			}
			(void)unlink(template);
		} else
			noteF = stdout;
		tloc = time((time_t *) NULL);
		fprintf(noteF, "SUP Upgrade of %s at %s",
		    collrelname, ctime(&tloc));
		(void) fflush(noteF);
	}
	vfprintf(noteF, fmt, ap);
	va_end(ap);
	(void) fflush(noteF);
}

void
lockout(int on)
{				/* lock out interrupts */
	static sigset_t oset;
	sigset_t nset;

	if (on) {
		sigemptyset(&nset);
		sigaddset(&nset, SIGHUP);
		sigaddset(&nset, SIGINT);
		sigaddset(&nset, SIGTERM);
		sigaddset(&nset, SIGQUIT);
		(void) sigprocmask(SIG_BLOCK, &nset, &oset);
	} else {
		(void) sigprocmask(SIG_SETMASK, &oset, NULL);
	}
}

char *
fmttime(time_t time)
{
	static char buf[STRINGLENGTH];
	unsigned int len;

	(void) strcpy(buf, ctime(&time));
	len = strlen(buf + 4) - 6;
	buf[len] = '\0';
	return buf + 4;
}
