/*
 * utils.c - Miscellaneous general-purpose utilities.
 *
 * Author: John DiMarco, University of Toronto, CSLab
 *         jdd@cs.toronto.edu
 */

#ifndef lint
static char rcsid[] = "$Id: utils.c,v 1.2 1998/03/27 16:52:07 jdd Exp $";
#endif

#include <stdio.h>
#include <varargs.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>

#ifndef SYSV
/* trick for figuring out whether or not we're on SYSV or not */
#ifdef FILENAME_MAX
#define SYSV
#endif /* FILENAME_MAX */
#endif /* !SYSV */

#include <string.h>
#include <errno.h>
#include <time.h>
#include "utils.h"

#ifdef lint
#undef va_arg
#define va_arg(x,y) (y)NULL
#endif

extern char *progname;
extern int d;

static int log_level;   /* log cutoff priority level, 0 is high, */
static int log_options; /* logging options, currently just LOG_PID */
static FILE *log_file;  /* FILE * to log file, NULL if syslog */
static char *log_name;  /* name of program logging. */

/*
 * Error(): behaves like fprintf(stderr, ...) followed by exit(2), except
 *          that the 'programname: ' preceeds the print, and a newline
 *	    follows it.
 */
/*VARARGS*/
void Error(va_alist)
va_dcl
{
	va_list args;
	char *format;

	va_start(args);
	format = va_arg(args, char *);
	(void)fprintf(stderr, "%s: ", progname);
	(void)vfprintf(stderr, format, args);
	(void)fprintf(stderr, "\n");
	va_end(args);
	(void)exit(2);
}

/*
 * dfprintf(): behaves like fprintf, except the first argument must be a
 *            debugging level. The message will only be printed if "d"
 *            is equal to or greater than the debugging level.
 */
/*VARARGS*/
void dfprintf(va_alist)
va_dcl
{
	va_list args;
	int debugLevel;
	FILE *stream;
	char *format;
	
	va_start(args);
	debugLevel = va_arg(args, int);
	stream = va_arg(args, FILE *);
	format = va_arg(args, char *);
	if(d >= debugLevel){
		(void)vfprintf(stream, format, args);
	}
	va_end(args);
}
	
/*
 * Warning(): behaves like Error, except returns rather than exits.
 */
/*VARARGS*/
void Warning(va_alist)
va_dcl
{
	va_list args;
	char *format;

	va_start(args);
	format = va_arg(args, char *);
	(void)fprintf(stderr, "%s: ", progname);
	(void)vfprintf(stderr, format, args);
	(void)fprintf(stderr, "\n");
	va_end(args);
}

FILE *efopen(file, mode)
char *file, *mode;
{
	FILE *fp;
	if (NULL!=(fp=fopen(file,mode)))
		return(fp);
	Error("can't open file \"%s\" mode \"%s\"", file, mode);
	return(NUL(FILE *));
}

void efclose(fp)
FILE *fp;
{
	if(EOF==fclose(fp))
		Error("can't close file");
	/*NOTREACHED*/
}

/*
 * mylib_malloc(): Checks if it gets a NULL pointer, calls Error if so.
 */
char *mylib_malloc(size, file, line)
unsigned size;
char *file;
int line;
{
	char *result;

	result = malloc(size);
	if(NULL==result){
		Error("Out of memory at line %d in \"%s\".", line, file);
	}
	return(result);
}

/*
 * mylib_realloc(): Checks if it gets a NULL pointer, calls Error if so.
 */
char *mylib_realloc(ptr, size, file, line)
char *ptr, *file;
unsigned size;
int line;
{
	char *result;

	result = realloc(ptr, size);
	if(NULL==result){
		Error("Out of memory at line %d in \"%s\".", line, file);
	}
	return(result);
}

/*
 * mylib_scopy(): Takes a string and creates a new physical copy of it.
 */
char *mylib_scopy(string, file, line)
char *string, *file;
int line;
{
	char *result;

	result = malloc((unsigned)strlen(string)+1);

	if(NULL==result){
		Error("Out of memory at line %d in \"%s\".", line, file);
	}
	(void)strcpy(result, string);
	return(result);
}

/*
 * mylib_srcopy(): Reallocs first string to make room for second, copies it.
 */
char *mylib_srcopy(s1, s2, file, line)
char *s1, *s2, *file;
int line;
{
	s1=mylib_realloc(s1, (unsigned)strlen(s2)+1, file, line);
	(void)strcpy(s1, s2);
	return(s1);
}

/*
 * cat(): Take a list of strings, followed by NULL, return their concatenation
 *        in malloc'ed space.
 */
/*VARARGS*/
char *cat(va_alist)
va_dcl
{
	va_list args;
	unsigned length=1;
	char *str, *newstr;

	/* get length */
	va_start(args);
	loop{
		str = va_arg(args, char *);
		if(NULL!=str){
			 length+=strlen(str);
		} else {
			break;
		}
	}
	va_end(args);

	newstr=malloc(length);
	if(NULL==newstr) Error("Out of memory in cat()");
	
	newstr[0]=(char)0;

	/* create string */
	va_start(args);
	loop{
		str = va_arg(args, char *);
		if(NULL!=str) {
			(void)strcat(newstr, str);
		} else {
			break;
		}
	}
 va_end(args);
#ifdef lint
	args=args; /* make lint shut up about "args set but not used" */
#endif
	return(newstr);
}

/*
 * getstr(): read a string of arbitrary length from the given file 
 *           descriptor into malloc'ed memory, up to (but not including)
 *           the next newline. Return a pointer to the string; NULL if EOF.
 */
char *getstr(fp)
FILE *fp;
{
	int buffsize = 128; /* buffer size */
	char *result, *tmp;
	unsigned int length = 0; /* length of string read */
	int last;
	
	result = mem(buffsize+1);

	loop{
		tmp = result+length;
		if(NULL==fgets(tmp, buffsize+1, fp)){
			/* no more characters to read; EOF reached */
			if(tmp==result){
				/* we never read anything! */
				free(result);
				return(NULL);
			}
			break;
		} else {
			last = strlen(tmp);
			if('\n'==*(tmp+last-1)){
				/* found a newline */
				*(tmp+last-1)='\0';
				break;
			}
			/* still more to read */
			length += buffsize;
			result = rmem(result, length+buffsize);
		}
	}
	/* trim off excess buffer */
	result = rmem(result, strlen(result)+1);
	return(result);
}

/* 
 * writeline(): write a line to specified file descriptor. Keep writing until
 *              error or all bytes written.
 */
int writeline(fd, line)
int fd;
char *line;
{
        int l, w=0;

        l=strlen(line);

        while(l>0){
                if(0>(w=write(fd, line, l))) return(-1);
                line+=w;
                l-=w;
        }
        return(0);
}

/* 
 * readline(): read a line from specified file descriptor.
 */
char *readline(fd)
int fd;
{
        static char buf[256];
        int i=0;

        /* inefficient. */
        loop{
                if(1!=read(fd, buf+i, 1)) return(NULL);
                if('\n'==buf[i]){
                        buf[i]='\0';
                        break;
                }
                i++;
        }
        return(buf);
}

/*
 * strsubst(): replace all instances of origstr in buff with replstr. return
 *             result (malloc'ed storage) to stdout.
 */
char *strsubst(str, origstr, replstr)
char *str, *origstr, *replstr;
{
	char *cp, *i, *j, *buff;
	int ol, rl, l, ct=0;

	ol=strlen(origstr);
	rl=strlen(replstr);
	l=strlen(str)+1;

	/* 
	 * count the number of occurrences of origstr in str, to compute
	 * how much storage is needed. 
	 */
	for(i=strstr(str, origstr);NULL!=i;i=strstr(i+ol, origstr)) ct++;
	buff=mem((ct*(rl-ol))+l);
	
	
	/* 
	 * now do the replacements.
	 */
	i=str;  /* pointer into source */
	j=buff; /* pointer into destination */
	loop{
		cp=strstr(i, origstr);
		if(NULL==cp){
			/* finish up */
			for(;*i;i++,j++) *j=*i;
			*j='\0'; 
			break;
		} else {
			for(;i<cp;i++,j++) *j=*i;
			(void)strcpy(j, replstr);
			j+=rl;
			i+=ol;
		}
	}
	return(buff);
}

/*
 * setlog(): indicates what sort of logging, and to where. Also
 *               sets global variables used by log. If facility is -1, 
 *               assume file or stdout/stderr, otherwise syslog.
 */
void setlog(log_entity, level, facility, options, filename)
int level;        /* logging level */
int options;      /* logging options */
int facility;     /* logging facility, either a syslog fac or stdin/err/file */
char *log_entity; /* "entity" doing the logging, eg. progname */
char *filename;  /* filename of log file, if facility is LOG_FILE */
{
	log_level = level;
	log_options = options;
	log_name = log_entity;
	if(NULL==log_name) log_name="UNKNOWN";

	switch(facility){
	case LOG_FILE:
		if(NULL==filename) {
			log_level=-1;
			log_file=NULL;
			break;
		}
		log_file = fopen(filename, "a");
		if(NULL==log_file) log_level=-1; /* log nothing */
		break;
	case LOG_STDERR:
		log_file = stderr;
		break;
	case LOG_STDOUT:
		log_file = stdout;
		break;
	default:
		/* assume it's a facility known to syslog */
		openlog(log_entity, options, facility);
		log_file = NUL(FILE *);
		break;
	}
}

#ifndef SYSV
char *sys_errlist[];
int sys_nerr;

char *strerror(eno)
int eno;
{
	if(eno<sys_nerr) {
		return(sys_errlist[eno]);
	} 
	return("Unknown error");
}
#endif /* !SYSV */

/*
 * logmsg(): behaves like printf, except the first argument must be a
 *        log priority. Log only if level is <= log_level. Log to log_file
 * 	  if it's non-NULL, otherwise to syslog. %m is replaced with
 *        whatever the message corresponding to the current value of errno is.
 */
/*VARARGS*/
void logmsg(va_alist)
va_dcl
{
	va_list args;
	int level;
	char *format, *nformat, *errormsg, buff[8000];
	
	if(log_level<0) return; /* do no logging */

	errormsg = strerror(errno);

	va_start(args);
	level = va_arg(args, int);
	format = va_arg(args, char *);

	if(!*format) return;

	nformat = strsubst(format, "%m", errormsg);
	
	if(log_level >= level){
		if(log_file) {
			char *tmp, *newline, pid[30], timestamp[80];
			time_t t;
			struct tm *tm;

			(void)time(&t);
			tm=localtime(&t);
			(void)strftime(timestamp, sizeof(timestamp), 
					"%b %m %T", tm);

			if(nformat[strlen(nformat)-1]=='\n'){
				newline="";
			} else {
				newline="\n";
			}
			if(LOG_PID&log_options){
				(void)sprintf(pid, "[%lu]", (u_long)getpid());
			} else {
				pid[0]='\0';
			}
			tmp = cat(timestamp, " ", log_name, pid, ": ",
					nformat,newline,NULL);

			(void)vsprintf(buff, tmp, args);
			free(tmp);

			(void)fprintf(log_file, buff);
		} else {
			(void)vsprintf(buff, nformat, args);
			syslog(level, buff);
		}
	}
	free(nformat);
	va_end(args);
}
