/*
 *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
 *
 *	Global declarations and auxiliary functions.
 */

#include <stdarg.h>
#include <signal.h>
#include <pwd.h>
#include <sys/mman.h>
#include "config.h"
#include "patchlevel.h"
#include "nn_term.h"

/* global.c */

static sig_type catch_keyboard __APROTO((int n));
static sig_type catch_pipe __APROTO((int n));
static sig_type catch_suspend __APROTO((int n));
static void mail_sys_error __APROTO((char *err, int isfatal));
static void mem_error __APROTO((int t, int32 bytes));

export char *home_directory;
export char *nn_directory;		/* ~/.nn as default */
export char *news_directory;		/* /usr/spool/news */
export char *news_lib_directory;	/* /usr/lib/news */
export char *lib_directory;		/* /usr/local/lib/nn */
export char *master_directory;		/* = lib */
export char *help_directory;		/* = lib/help */
export char *bin_directory = BIN_DIRECTORY;

export char *db_directory;	/* /usr/spool/nn or NEWS_DIR/.nn */
export char *db_data_directory;	/* ..../DATA	 or undefined	 */
export int db_data_subdirs = 0;	/* set if DATA/[0-9]/ exist	 */

export char *news_active;	/* NLIB/active or DB/ACTIVE */

export char *pager;
export char *organization;

export char *log_file;			/* = lib/Log */
export char *log_entry_filter = NULL;

export char *temp_file;

#ifndef TMP_DIRECTORY
#define TMP_DIRECTORY "/var/tmp"
#endif
export char *tmp_directory = TMP_DIRECTORY;

export char version_id[100];

#ifdef NNTP
export int use_nntp = 0;	/* bool: t iff we use nntp */
#endif

uid_t	user_id;
gid_t	group_id;
export int process_id;
export int who_am_i;
export int dont_write_console = 0;
export int mail_errors_mode = 2;

extern int errno;
struct passwd *getpwuid();
extern char *getlogin(), *getenv();


#ifdef HAVE_MULTIGROUP
#ifndef NGROUPS
#include <sys/param.h>
#endif

static uid_t	user_eid;
static int	ngroups;
static gid_t	gidset[NGROUPS];

static int in_grplist(gid_t gid)
{
    int n;

    if (gid == group_id) return 1;

    for (n = 0; n < ngroups; ++n)
	if (gidset[n] == gid) return 1;

    return 0;
}

#define group_access(gpid)	in_grplist((GIDSET_TYPE)(gpid))
#else
#define group_access(gid)	((gid) == group_id)
#endif

/* signal handler interface */

export int s_hangup		= 0;	/* hangup signal */
export int s_keyboard		= 0;	/* keyboard interrupt */
export int s_pipe		= 0;	/* broken pipe */
export int s_redraw		= 0;	/* redraw signal (if job control) */
#ifdef RESIZING
export int s_resized		= 0;	/* screen resized */
#endif

#ifdef FAKE_INTERRUPT
#include <setjmp.h>

export jmp_buf fake_keyb_sig;
export int arm_fake_keyb_sig = 0;
export char fake_keyb_siglist[NSIG];
#endif

sig_type catch_hangup(n)
int n;
{
    s_hangup = 1;
    signal(n, SIG_IGN);

#ifdef FAKE_INTERRUPT
    if (fake_keyb_siglist[n] && arm_fake_keyb_sig)
	longjmp(fake_keyb_sig, 1);
#endif
}

static sig_type catch_keyboard(n)
int n;
{
#ifdef RESET_SIGNAL_WHEN_CAUGHT
    signal(n, catch_keyboard);
#endif
#ifdef FAKE_INTERRUPT
    if (fake_keyb_siglist[n] && arm_fake_keyb_sig)
	longjmp(fake_keyb_sig, 1);
#endif
    s_keyboard++;
}

static sig_type catch_pipe(n)
int n;
{
    s_pipe++;

#ifdef RESET_SIGNAL_WHEN_CAUGHT
    signal(n, catch_pipe);
#endif
#ifdef FAKE_INTERRUPT
    if (fake_keyb_siglist[n] && arm_fake_keyb_sig)
	longjmp(fake_keyb_sig, 1);
#endif
}

#ifdef HAVE_JOBCONTROL
static sig_type catch_suspend(n)
int n;
{
    s_redraw++;

#ifdef RESET_SIGNAL_WHEN_CAUGHT
    signal(n, catch_suspend);
#endif

    suspend_nn();

#ifdef FAKE_INTERRUPT
    if (fake_keyb_siglist[n] && arm_fake_keyb_sig) {
#ifdef RESIZING
	s_resized++;
#endif
	longjmp(fake_keyb_sig, 1);
    }
#endif
}
#endif


int
init_global()
{
#ifndef HAVE_UNISTD_H
    unsigned short getuid();
    int getpid();
#endif
#ifdef NOV
    char *nov_id = " (NOV)";
#else
    char *nov_id = "";
#endif

#ifdef FAKE_INTERRUPT
    for (i = 0; i < NSIG; i++) fake_keyb_siglist[i] = i == 2 ? 1 : 0;
#endif

    if (who_am_i != I_AM_NN) {
	signal(SIGINT,	SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
    }
    signal(SIGTERM, catch_hangup);
    signal(SIGHUP,  catch_hangup);
    signal(SIGPIPE, catch_pipe);
    signal(SIGALRM, SIG_IGN);

#ifdef SIGPWR
    signal(SIGPWR, catch_hangup);
#endif

#ifdef CONFIG_NUM_IN_VERSION
    sprintf(version_id, "%s.%s #%d%s", RELEASE, PATCHLEVEL,
#include "update.h"
	    ,nov_id);
#else
    sprintf(version_id, "%s.%s%s", RELEASE, PATCHLEVEL, nov_id);
#endif

    user_id = getuid();

#ifdef HAVE_MULTIGROUP
    ngroups = getgroups(NGROUPS, gidset);	/* Get users's group set */
#endif
    group_id = getegid();
    user_eid = geteuid();

    process_id = getpid();

#ifdef CLIENT_DIRECTORY
    lib_directory = CLIENT_DIRECTORY;
#else
    lib_directory = LIB_DIRECTORY;
#endif

#ifdef NEWS_DIRECTORY
    news_directory = NEWS_DIRECTORY;
#else
    news_directory = "/usr/spool/news";
#endif

#ifdef DB_DIRECTORY
    db_directory = DB_DIRECTORY;
#else
    db_directory = mk_file_name(lib_directory, ".nn");
#endif

#ifdef ACCOUNTING
    if (who_am_i == I_AM_ACCT)
	return 0;
#endif

#ifdef DB_DATA_DIRECTORY
    db_data_directory = DB_DATA_DIRECTORY;
#else
#ifdef DB_DIRECTORY
    db_data_directory = mk_file_name(db_directory, "DATA");
#else
    db_data_directory = NULL;
#endif
#endif
#ifndef DB_LONG_NAMES
    if (db_data_directory != NULL)
	db_data_subdirs = file_exist(relative(db_data_directory, "0"), "dx");
#endif

#ifdef NEWS_LIB_DIRECTORY
    news_lib_directory = NEWS_LIB_DIRECTORY;
#else
    news_lib_directory = "/usr/lib/news";
#endif

    /* this may later be changed by nntp_check */
    news_active = mk_file_name(news_lib_directory, "active");

#ifdef MASTER_DIRECTORY
    master_directory = MASTER_DIRECTORY;
#else
    master_directory = LIB_DIRECTORY;
#endif

#ifdef HELP_DIRECTORY
    help_directory = HELP_DIRECTORY;
#else
    help_directory = mk_file_name(lib_directory, "help");
#endif

#ifdef LOG_FILE
    log_file = LOG_FILE;
#else
    log_file = mk_file_name(LIB_DIRECTORY, "Log");
#endif

    if (who_am_i == I_AM_MASTER || who_am_i == I_AM_SPEW)
	return 0;

    signal(SIGINT,  catch_keyboard);
    signal(SIGQUIT, catch_keyboard);
#ifdef HAVE_JOBCONTROL
    signal(SIGTSTP, catch_suspend);
#endif

    if ((home_directory = getenv("HOME")) == NULL)
	nn_exitmsg(1, "No HOME environment variable");

    if ((pager = getenv("PAGER")) == NULL)
	pager = DEFAULT_PAGER;

    if(!nn_directory) /* if not set on command line */
	nn_directory = mk_file_name(home_directory, ".nn");

    organization = getenv("ORGANIZATION");

    return 0;
}

int init_global2()
{
    if (who_am_i != I_AM_ADMIN && !file_exist(nn_directory, "drwx")) {
	if (who_am_i != I_AM_NN) return -1;
	if (mkdir(nn_directory, 0755) < 0)
	    nn_exitmsg(1, "Cannot create %s directory", nn_directory);
	return 1;
    }
    return 0;
}

void
new_temp_file()
{
    static char buf[FILENAME];
    static char *temp_dir = NULL;

    if (temp_dir == NULL)
	if ((temp_dir = getenv("TMPDIR")) == NULL)
	    temp_dir = tmp_directory; /* just to make test above false */
	else
	    tmp_directory = temp_dir;

    sprintf(buf, "%s/nn.XXXXXX", tmp_directory);
    mktemp(buf);
    temp_file = buf;
}


FILE *open_file(name, mode)
char *name;
int mode;
{
    FILE *f = NULL;
    int fd;

    if ((mode & DONT_CREATE) && !file_exist(name, (char *)NULL))
	return NULL;

    switch (mode & 0x0f) {

     case OPEN_READ:

	f = fopen(name, "r");
	break;

     case OPEN_UPDATE:

/*	f = fopen(name, "r+");	-- not reliable on many systems (sigh) */

	if ((fd = open(name, O_WRONLY)) >= 0) {
	    if ((f = fdopen(fd, "w")) != NULL) return f;
	    close(fd);
	}

	/* FALL THRU */
     case OPEN_CREATE:

	f = fopen(name, "w");
	break;

     case OPEN_APPEND:

	f = fopen(name, "a");
	break;

     case OPEN_CREATE_RW:

	f = fopen(name, "w+");	/* not safe on all systems -- beware */
	break;

     default:

	sys_error("Illegal mode: open_file(%s, 0x%x)", name, mode);
    }

    if (f) {
	if (mode & OPEN_UNLINK) unlink(name);
	return f;
    }

    if ((mode & MUST_EXIST) == 0) return NULL;

    sys_error("Cannot open file %s, mode=0x%x, errno=%d", name, mode, errno);

    return NULL;
}

FILE *open_file_search_path(name, mode)
char *name;
int mode;
{
    FILE *f;

    if (name == NULL) return NULL;

    if (*name == '/') return open_file(name, mode);
    
#ifdef NNTP
    f = NULL;
#endif	/* NNTP */
    if (!use_nntp)
	f = open_file(relative(news_lib_directory, name), OPEN_READ);
    if (f == NULL)
	f= open_file(relative(lib_directory, name), OPEN_READ);
    if (f == NULL)
	f = open_file(relative(db_directory, name), OPEN_READ);

    return f;
}

int    
fgets_multi(buf, size, f)
char *buf;
int size;
register FILE *f;
{
    register char *s = buf;
    register int c, n = size;
    
    while (--n > 0) {
	c = getc(f);
	if (c == '\\') {
	    if ((c = getc(f)) == NL) continue;
	    *s++ = '\\';
	    if (--n < 0) break;
	}
	if (c == EOF) {
	    *s = NUL;
	    return s != buf;
	}
	if (c == NL) {
	    *s = NUL;
	    return 1;
	}
	*s++ = c;
    }
    buf[30] = NUL;
    sys_error("Line too long \"%s...\" (max %d)", buf, size);
    return 0;
}
	
/*
 *	relative -- concat directory name and file name
 */

char *relative(dir, name)
char *dir, *name;
{
    static char concat_path[FILENAME];

    sprintf(concat_path, "%s/%s", dir, name);
    return concat_path;
}


char *mk_file_name(dir, name)
char *dir, *name;
{
    char *buf;

    buf = newstr(strlen(dir) + strlen(name) + 2);
    sprintf(buf, "%s/%s", dir, name);

    return buf;
}


char *home_relative(dir)
char *dir;
{
    if (dir) {
	if (*dir == '/')
	    return copy_str(dir);
	else {
	    if (*dir == '~' && *++dir == '/')
		dir++;
	    return mk_file_name(home_directory, dir);
	}
    }
    return NULL;
}


char *substchr(str, c1, c2)
char *str, c1, c2;
{
    char *p;

    if ((p = strchr(str, c1)) != NULL) *p = c2;
    return p;
}

char *copy_str(str)
char *str;
{
    char *new;

    new = newstr(strlen(str) + 1);
    if (new) strcpy(new, str);

    return new;
}

time_t m_time(f)
FILE *f;
{
    struct stat st;

    if (fstat(fileno(f), &st) < 0) return 0;
    return st.st_mtime;
}


time_t file_exist(name, mode)
char *name;
char *mode;
{
    static struct stat statb;
    int mask;

    if (name != NULL && stat(name, &statb)) return 0;

    if (mode == NULL) return statb.st_mtime;

    if (statb.st_uid == user_eid)
	mask = 0700;
    else if (group_access(statb.st_gid))
	mask = 0070;
    else
	mask = 0007;

    while (*mode) {
	switch (*mode++) {
	case 'd':
	    if ((statb.st_mode & S_IFMT) == S_IFDIR) continue;
	    errno = ENOTDIR;
	    return 0;
	case 'f':
	    if ((statb.st_mode & S_IFMT) == S_IFREG) continue;
	    if ((statb.st_mode & S_IFMT) == 0000000) continue;
	    if ((statb.st_mode & S_IFMT) == S_IFDIR) {
		errno = EISDIR;
		return 0;
	    }
	    break;
	case 'r':
	    if (statb.st_mode & mask & 0444) continue;
	    break;
	case 'w':
	    if (statb.st_mode & mask & 0222) continue;
	    break;
	case 'x':
	    if (statb.st_mode & mask & 0111) continue;
	    break;
	}
	errno = EACCES;
	return 0;
    }

    /* all modes are ok */
    return statb.st_mtime;
}

/*
 *	cmp_file: compare two files
 *
 *	Returns		0: The files are identical.
 *			1: The files are different.
 *			2: An error occurred.
 */

int cmp_file(work, copy)
char *work, *copy;
{
    int fd1, fd2;
    struct stat sb1, sb2;
    u_char *p1, *p2;
    off_t len1, len2;

    if ((fd1 = open(work, O_RDONLY, 0)) < 0) {
	msg("%s %s", work, strerror(errno));
	return(2);
    }
    if ((fd2 = open(copy, O_RDONLY, 0)) < 0) {
	msg("%s %s", copy, strerror(errno));
	return(2);
    }

    if (fstat(fd1, &sb1)) {
	msg("%s %s", work, strerror(errno));
	return(2);
    }
    if (fstat(fd2, &sb2)) {
	msg("%s %s", copy, strerror(errno));
	return(2);
    }

    len1 = sb1.st_size;
    len2 = sb2.st_size;

    if (len1 != len2)
	return(1);

    if ((p1 = (u_char *)mmap(NULL, (size_t)len1, PROT_READ, MAP_SHARED, fd1, 0)) == (u_char *)-1) {
	msg("work=%s %s", work, strerror(errno));
	sleep(5);
	return(2);
    }
    if ((p2 = (u_char *)mmap(NULL, (size_t)len2, PROT_READ, MAP_SHARED, fd2, 0)) == (u_char *)-1) {
	msg("copy=%s %s", copy, strerror(errno));
	sleep(5);
	return(2);
    }

    for (; len1--; ++p1, ++p2) {
	if (*p1 != *p2)
	    return(1);
    }
    return(0);
}

/*
 * copy_file: copy (or append) src file to dest file.
 *
 * Returns number of characters copied or an error code:
 *  -1: source file not found
 *  -2: cannot create destination
 *  -3: write error
 */

int32 copy_file(src, dest, append)
char *src, *dest;
int append;
{
    register FILE *s, *d;
    register int32 n;
    register int c;

    s = open_file(src, OPEN_READ);
    if (s == NULL) return -1;

    d = open_file(dest, append ? OPEN_APPEND : OPEN_CREATE);
    if (d == NULL) {
	fclose(s);
	return -2;
    }

    n = 0;
    while ((c = getc(s)) != EOF) {
	putc(c, d);
	n++;
    }

    fclose(s);
    if (fclose(d) == EOF) {
	if (!append) unlink(dest);
	return -3;
    }
    return n;
}

/*
 * move_file: move old file to new file, linking if possible.
 *
 * The third arg determines what is acceptable if the old file cannot be
 * removed after copying to the new file:
 *   0: must remove old, else remove new and fail,
 *   1: must remove or truncate old, else remove new and fail,
 *   2: just leave old if it cannot be removed or truncated.
 *	
 * Returns positive value for success, negative for failure:
 *   0: file renamed (link)
 *   1: file copied, old removed
 *   2: file copied, but old file is only truncated.
 *   3: file copied, but old file still exist.
 *  -1: source file not found
 *  -2: cannot create destination
 *  -3: write error
 *  -4: cannot unlink/truncate old
 *  -5: cannot unlink new
 *  -6: cannot link old to new
 *  -9: messy situation: old and new linked on return (cannot happen?)
 */

int
move_file(old, new, may_keep_old)
char *old, *new;
int may_keep_old;
{
    int32 n;

    if (file_exist(new, (char *)NULL)) {
	if (file_exist((char *)NULL, "d"))
	    return -5;
	if (unlink(new) < 0)	/* careful - new may be directory ? */
	    switch (errno) {
	     case ENOENT:
		break;
	     case EACCES:
		if (file_exist((char *)NULL, "w")) goto do_copy;
	     default:
		return -5;
	    }
    }
    
    if (link(old, new) < 0)
	switch (errno) {
	 case EACCES:	/* can just as well try to copy */
	 case EXDEV:
	    goto do_copy;
	 default:
	    return -6;
	}
    
    if (unlink(old) == 0)
	return 0;

    /* we were able to link but not unlink old	*/
    /* remove new, and attempt a copy instead	*/
    if (unlink(new) < 0) return -9; /* cannot happen? */

 do_copy:
    if ((n = copy_file(old, new, 0)) < 0) return n;
    if (unlink(old) == 0) return 1;
    if (may_keep_old)
	if (n == 0 || nn_truncate(old, (off_t)0) == 0) return 2;
    if (may_keep_old == 2) return 3;
    unlink(new);
    return -4;
}
int
save_old_file(name, suffix)
char *name, *suffix;
{
    char buf[FILENAME];
    sprintf(buf, "%s%s", name, suffix);
    return move_file(name, buf, 0);
}

#ifdef HAVE_SYSLOG
#include <syslog.h>
#endif /* HAVE_SYSLOG */


static int
enter_log(int type, char *fmt, va_list ap)
{
    FILE *log;
    char buf[512];

    vsprintf(buf, fmt, ap);

    /* cannot use relative: one of the args may be generated by it */

    log = open_file(log_file, OPEN_APPEND);
    if (log == NULL)
	return 0;

    fprintf(log, "%c: %s (%s): %s\n", type,
	    date_time((time_t)0), user_name(), buf);

    fclose(log);
    return 1;
}

static void
mail_sys_error(err, isfatal)
char *err;
int isfatal;
{
    FILE *f;
    char cmd[FILENAME*2];

    switch (mail_errors_mode) {
     case 0:
	return;
     case 1:
	if (!isfatal) return;
     default:
	break;
    }
    
#ifdef FATAL_ERROR_MAIL_CMD
    strcpy(cmd, FATAL_ERROR_MAIL_CMD);
#else
#ifdef MAILX
    sprintf(cmd, "%s -s 'nnmaster %s' %s", MAILX,
	    isfatal ? "fatal error" : "warning", OWNER);
#else
    sprintf(cmd, "mail %s", OWNER);
#endif
#endif
    if ((f = popen(cmd, "w")) == NULL) return;
    fprintf(f, "nnmaster %s\n\n%system error:\n%s\n",
	    isfatal ? "terminated" : "warning",
	    isfatal ? "Fatal s" : "S", err);
    pclose(f);
}

void
sys_error(char *fmt,...)
{
    va_list	ap;
    int		rval;
    char buf[512];
#ifndef HAVE_SYSLOG
    FILE *f;
#endif

    va_start(ap, fmt);
    rval = enter_log('E', fmt, ap);
    va_end(ap);

    va_start(ap, fmt);
    vsprintf(buf, fmt, ap);
    va_end(ap);

    if (who_am_i == I_AM_MASTER) {
	mail_sys_error(buf, 1);
	if (dont_write_console) nn_exit(7);
#ifndef HAVE_SYSLOG
        f = open_file("/dev/console", OPEN_CREATE);
        if (f == NULL) nn_exit(8);
        fprintf(f, "\n\rNNMASTER FATAL ERROR\n\r%s\n\n\r", buf);
        fclose(f);
#else /* HAVE_SYSLOG */
	openlog("nnmaster", LOG_CONS, LOG_DAEMON);
	syslog(LOG_ALERT, "%s", buf);
	closelog();
#endif /* HAVE_SYSLOG */
	nn_exit(7);
    }
    if (rval)
	nn_exitmsg(1, "%s", buf);
    else
	nn_exitmsg(1, "Can't open log file!\n%s", buf);
}

/*
 *	sys_warning: like sys_error but MASTER will return with -1
 *	instead of exit.  Clients still terminate!
 */

int
sys_warning(char *fmt,...)
{
    va_list	ap;
    int		rval;
    char buf[512];
#ifndef HAVE_SYSLOG
    FILE *f;
#endif
    static char *last_err = NULL;

    va_start(ap, fmt);
    vsprintf(buf, fmt, ap);
    va_end(ap);

    if (last_err != NULL) {
	if (strcmp(last_err, buf) == 0)
	    return -1;
	free(last_err);
    }
    last_err = copy_str(buf);

    va_start(ap, fmt);
    rval = enter_log('R', fmt, ap);
    va_end(ap);

    if (who_am_i != I_AM_MASTER) {
	if (rval)
	    nn_exitmsg(1, "%s", buf);
	else
	    nn_exitmsg(1, "Can't open log file!\n%s", buf);
    }

    mail_sys_error(buf, 0);
    if (dont_write_console) return -1;
#ifndef HAVE_SYSLOG
    if((f = open_file("/dev/console", OPEN_CREATE)) != NULL) {
        fprintf(f, "\n\rNNMASTER WARNING\n\r%s\n\n\r", buf);
        fclose(f);
    }
#else /* HAVE_SYSLOG */
    openlog("nnmaster", LOG_CONS, LOG_DAEMON);
    syslog(LOG_ALERT, "%s", buf);
    closelog();
#endif /* HAVE_SYSLOG */
    return -1;
}

int
log_entry(int type, char *fmt,...)
{
    va_list	ap;
    int		rval;

    va_start(ap, fmt);
    rval = enter_log(type, fmt, ap);
    va_end(ap);

    return rval;
}

char *user_name()
{
    static char *user = NULL;
    struct passwd *pw;

    if (who_am_i == I_AM_MASTER) return "M";
    if (who_am_i == I_AM_EXPIRE) return "X";

    if (user == NULL) {
	user = getlogin();
	if (user != NULL && *user != NUL) goto out;

	pw = getpwuid(user_id);
	if (pw != NULL && pw->pw_name[0] != NUL) {
	    user = copy_str(pw->pw_name);
	    goto out;
	}

	user = getenv("LOGNAME");
	if (user != NULL && *user != NUL) goto out;
	user = getenv("USER");
	if (user != NULL && *user != NUL) goto out;
	user = "?";
    }

 out:
    return user;
}

time_t cur_time()
{
    time_t t;

    time(&t);
    return t;
}

char *date_time(t)
time_t t;
{
    char *str;

    if (t == (time_t)0) t = cur_time();
    str = ctime(&t);

    str[16] = 0;
    return str+4;
}

char *plural(n)
long n;
{
    return n != 1 ? "s" : "";
}

/*
 *	memory management
 */

/* #define MEM_DEBUG */		/* trace memory usage */

static void
mem_error(t, bytes)
int t;
int32 bytes;
{
    char buf[200];

    if (t == 1) {
	sprintf(buf, "Alloc failed: unsigned too short to represent %ld bytes",
		(long)bytes);
    } else {
	sprintf(buf, "Out of memory - cannot allocate %ld bytes",
		(long)bytes);
    }

    sys_error(buf);
}

char *mem_obj(size, nelt)
unsigned size;
int32 nelt;
{
    unsigned n;
    char *obj;
#ifndef HAVE_STDLIB_H
    char *calloc();
#endif

    n = nelt;
    if (n != nelt) mem_error(1, nelt);

    obj = calloc(n, size);
#ifdef MEM_DEBUG
    printf("CALLOC(%u,%u) => %lx\n", n, size, (long)obj);
#endif
    if (obj == NULL) mem_error(2, (int32)(size * nelt));
    return obj;
}

char *mem_str(nelt)
int32 nelt;
{
    unsigned n;
    char *obj;
#ifndef HAVE_STDLIB_H
    char *malloc();
#endif

    n = nelt;
    if (n != nelt) mem_error(1, nelt);

    obj = malloc(n);
#ifdef MEM_DEBUG
    printf("MALLOC(%u) => %lx\n", n, (long)obj);
#endif
    if (obj == NULL) mem_error(2, nelt);
    return obj;
}

char *mem_resize(obj, size, nelt)
char *obj;
unsigned size;
int32 nelt;
{
    unsigned n;
    char *obj1;
#ifndef HAVE_STDLIB_H
    char *realloc();
#endif

    if (obj == NULL)
	return mem_obj(size, nelt);

    nelt *= size;

    n = nelt;
    if (n != nelt) mem_error(1, nelt);

    obj1 = realloc(obj, n);
#ifdef MEM_DEBUG
    printf("REALLOC(%lx, %u) => %lx\n", (long)obj, n, (long)obj1);
#endif
    if (obj1 == NULL) mem_error(2, (int32)size);
    return obj1;
}


void
mem_clear(obj, size, nelt)
register char *obj;
unsigned size;
register int32 nelt;
{
    nelt *= size;
    while (--nelt >= 0) *obj++ = NUL;
}

void
mem_free(obj)
char *obj;
{
#ifdef MEM_DEBUG
    printf("FREE(%lx)\n", (long)obj);
#endif
    if (obj != NULL) free(obj);
}

#ifndef HAVE_MKDIR

mkdir(path, mode)
char *path;
int mode;
{
    char command[FILENAME*2 + 20];

    sprintf(command, "{ mkdir %s && chmod %o %s ; } > /dev/null 2>&1",
	    path, mode, path);
    return system(command) != 0 ? -1 : 0;
}
#endif


int
nn_truncate(path, len)
char *path;
off_t len;
{
#ifdef HAVE_TRUNCATE

    /* Easy... we already have it... */
    return truncate(path, len);

#else /* HAVE_TRUNCATE */

    int fd;
#ifndef O_TRUNC
    struct stat st;
#endif

    if (len != 0)
	sys_error("nn_truncate(%s,%ld): non-zero length", path, (long)len);

#ifdef O_TRUNC
    fd = open(path, O_WRONLY | O_TRUNC);
#else
    if (stat(path, &st) < 0) return -1;
    fd = creat(path, st.st_mode & 07777);
#endif	  
    if (fd < 0) return -1;
    close(fd);
    return 0;
#endif
}

/* Should really have some sort of configure file and use strdup().. */
char *
strsave(s)
char *s;
{
    int l;
    char *buf = NULL;
    
    if (s) {
	l = strlen(s);
	buf = malloc(l + 1);
	if (buf) {
	    if (l)
		strcpy(buf, s);
	    else
		buf[0] = '\0';
	}
    }
    return buf;
}

char *
str3save(s1, s2, s3)
char *s1, *s2, *s3;
{
    int l;
    char *buf;

    if (!s1) s1 = "";
    if (!s2) s2 = "";
    if (!s3) s3 = "";

    l = strlen(s1) + strlen(s2) + strlen(s3);

    buf = malloc(l + 1);
    if (buf) {
	strcpy(buf, s1);
	strcat(buf, s2);
	strcat(buf, s3);
    }
    return buf;
}

/*
 * This quick function is a hack of a replacement for fgets, but is
 * unlimited in line length.  It returns a pointer to a buffer which
 * just happens to be malloc()ed and is reused next time.  IE: you had
 * best strdup() it. 
 *
 * This was inspired by an article I saw in alt.sources but forgot to
 * save.
 *
 * -Peter Wemm <peter@DIALix.oz.au>
 */

char	*buf = NULL;	/* buffer for line */
int	size = 0;	/* size of buffer */

#define INITIAL_CHUNK	256	/* Initial malloc size */
#define GROW_CHUNK	256	/* extend with realloc in units if this */

/* 
 * Get a LONG line.
 * Returns a pointer to a '\0' terminated string which will be
 * overwritten on the next call.
 * Returns NULL if error or eof, or if nothing could be read at all.
 *
 * Note: It does not react very well to '\0' characters in the byte stream
 */

char *
fgetstr(f)
FILE	*f;
{	
  int	len;			/* # of chars stored into buf before '\0' */
  char	*s;

  if (size == 0 && buf == NULL) {
    size = INITIAL_CHUNK;
    buf = malloc(size);
    if (buf == NULL) {
      size = 0;
      nn_exitmsg(50, "cant allocate initial chunk\n");
      return NULL;
    }
    clearobj(buf, size, 1);
  }
    
  len = 0;

  while (fgets(buf + len, size - len, f) != NULL)
  {
    len += strlen(len + buf);
    if (len > 0 && buf[len - 1] == '\n')
      break;			/* the whole line has been read */

    size += GROW_CHUNK;
    s = realloc(buf, size);
    
    if (!s) {			/* Malloc failure */
      free(buf);		/* panic... */
      size = 0;
      buf = NULL;
      nn_exitmsg(51, "cant realloc chunk\n");
      return NULL;
    }
    buf = s;
    clearobj(buf + len, size - len, 1);
  }

  if (len == 0) {
    return NULL;		/* nothing read (eof or error) */
  }

  /* For when we are reading from a NNTP stream. */
  if (len > 1 && buf[len - 1] == '\n')
    --len;
  if (len > 1 && buf[len - 1] == '\r')
    --len;
  buf[len] = '\0';		/* unconditionally terminate string, */
				/* possibly overwriting CR and/or NL */
  return buf;
}

/*
 *	getline - gets a line from stdin
 *			returns the length of the line
 */

int getline(char *line, int max)
{
    if (fgets(line, max, stdin) == NULL)
	return 0;
    else
	return strlen(line);
}

