#include <stdio.h>
#include "string.h"
#include "mail.h"
#include <sys/stat.h>

/* imports */
extern char *mktemp();
extern int sleep();
extern long lseek();

#define FILNAMSIZ 14
#define LOCKPREFIX "/tmp/L."
#define TMPLNAME "/tmp/mlXXXXXX"
static char lockname[FILNAMSIZ+sizeof("/tmp/")];
static char tlockname[FILNAMSIZ+sizeof("/tmp/")];
static char tmplname[sizeof(TMPLNAME)];

static void lockbotch();

/*
 *  open a file for appending.  if the file doesn't exist, create it.
 *  return the file descriptor.
 */
appendopen(file)
	char *file;
{
	int out;
	long lseek();

	lock(file);
	out = open(file, 2);
	if (out < 0)
		out = creat(file, 0666);
	else
		chmod(file, 0666);
	lseek(out, 0L, 2);
	return out;
}

/*
 *  close an append only file
 */
appendclose(fd)
	int fd;
{
	close(fd);
	unlock();
}

/*
 *  lock and open file.  If the file doesn't exist and mode!=0, create it.
 *  return the file descriptor.
 */

FILE *
lockopen(file, omode, pmode, uid, gid)
	char *file;
	char *omode;
	int pmode;
{
	FILE *fp;
	struct stat sbuf;
	int newfile;

	lock(file);
	if(stat(file, &sbuf)<0)
		newfile = 1;
	else
		newfile = 0;
	fp = fopen(file, omode);
	if(fp==NULL){
		unlock();
		return NULL;
	}
	if(newfile){
		chown(file, uid, gid);
		chmod(file, pmode);
	}
	return fp;
}

/*
 *  close and unlock a file.
 */
lockclose(fp)
	FILE *fp;
{
	fclose(fp);
	unlock();
}

/*
 *  reopen a file without losing the lock.
 */
FILE *
lockreopen(file, omode, fp)
	char *file;
	char *omode;
	FILE *fp;
{
	return freopen(file, omode, fp);
}

/*
 *  remove ALL locks
 */
cleanlocks()
{
	unlock();
}

/* break an old lock */
static void
lockbotch(err, file)
	char *err;
	char *file;
{
	int fd;
	char msg[128];
	char log[256];

	sprintf(msg, "mail: problem with lock file\n");
	write(2, msg, strlen(msg));
	strcpy(log, MAILROOT);
	strcat(log, "mail.log");
	if ((fd=open(log, 1)) >= 0) {
		sprintf(msg, "error %s (tmp=%s lock=%s)\n", err, file, lockname);
		lseek(fd, (long)0, 2);
		write(fd, msg, strlen(msg));
		close(fd);
	}
}

/* Fill name with the lock name for file */
setlname(name, file)
	char *name, *file;
{
	char *slashp;

	(void)strcpy(name, LOCKPREFIX);
	slashp = strrchr(file, '/');
	if (slashp==NULL)
		strncat(name, file, sizeof(lockname)-strlen(name)-1);
	else
		strncat(name, slashp+1, sizeof(lockname)-strlen(name)-1);
	lockname[sizeof(lockname)-1] = '\0';
}

/* Lock the given file */
lock(file)
char *file;
{
	int fd, i;
	char pidbuf[20];

	/* return if we are already in the middle of a lock */
	if (*lockname != '\0') {
		lockbotch("double lock", tmplname);
		return;
	}

	/* create a temporary file */
	(void)strcpy(tmplname, TMPLNAME);
	(void)mktemp(tmplname);
	if ((fd=creat(tmplname, 0444))<0) {
		lockbotch("tmp exists", tmplname);
		return;
	}
	sprintf(pidbuf, "%d upas", getpid());
	write(fd, pidbuf, strlen(pidbuf));
	close(fd);

	/* Make a link to it with the lock file name.  This will fail only
	 * if it already exists.
	 */
	setlname(lockname, file);
	i = 0;
	while (link(tmplname, lockname) < 0) {
		/* File is already locked */
		sleep(2);
		(void) islocked(file); /* might break a stale lock */
		if (++i > 300) {
			lockbotch("breaking old lock", tmplname);
			unlink(lockname);
			link(tmplname, lockname);
			break;
		}
	}
	unlink(tmplname);
	*tmplname = '\0';
	return;
}

/* Return true if file has been locked by us or another program using the same
 * lock name scheme.
 * But remove the lock file if the locking process has gone away.
 */
int
islocked(file)
	char *file;
{
	struct stat stbuf;
	int pid;
	FILE *fp;

	setlname(tlockname, file);
	if (stat(tlockname, &stbuf)==0) {
		fp = fopen(tlockname, "r");
		if (fp == NULL || fscanf(fp, "%d", &pid)!=1) {
			/* either we made the lock wrong, or it just went away (race) */
			lockbotch("can't read pid: breaking lock", tlockname);
			if(fp)
				fclose(fp);
			unlink(tlockname);
			return 0;
		}
		fclose(fp);
		if (kill(pid, 0) == 0)
			return 1;
		/* locker has gone away */
		lockbotch("breaking stale lock", tlockname);
		unlink(tlockname);
	}
	return 0;
}

unlock()
{
	if (*tmplname != '\0') {
		if(unlink(tmplname)<0)
			lockbotch("unlock of tmpfile failed", tmplname);
		*tmplname = '\0';
	}
	if (*lockname != '\0') {
		if(unlink(lockname)<0)
			lockbotch("unlock of lock file failed", tmplname);
		*lockname = '\0';
	}
}
