
/*
 * lock.c -- universal locking routines
 *
 * $Id$
 */

#include <h/mh.h>

#ifdef MMDFONLY
# define LOCKONLY
#endif

#ifndef	LOCKONLY
# include <mts.h>
#else	/* LOCKONLY */
# ifdef MMDFONLY
#  include <mmdfonly.h>
#  include <mts.h>
# else	/* not MMDFONLY */
#  include <lockonly.h>
# endif	/* not MMDFONLY */
#endif	/* LOCKONLY */

#ifdef LOCKF
# include <sys/errno.h>
# include <sys/file.h>
#endif

#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#else
# include <sys/file.h>
#endif

#include <signal.h>

#ifdef SYS5
# define u_short ushort
# define u_long ulong
#endif

extern int errno;

#if defined(LOCKONLY) && !defined(MMDFONLY)
char *lockldir = "/usr/spool/locks";
#endif

/*
 * static prototypes
 */
static int b_lkopen (char *, int);
static int lockit (char *, char *);
static int f_lkopen (char *, int);
static void lockname (char *, char *, char *, int, int);
static void timerON (char *, int);
static void timerOFF (int);


/*
 * Lock the ndbm/db file containing the Message-Id's
 * used for duplicate suppression in slocal.
 */
int
lockfile (int fd)
{
#ifdef FCNTL
    struct flock fl;
 
    fl.l_type = F_WRLCK;
    fl.l_whence = 0;
    fl.l_start = 0;
    fl.l_len = 0;
    if (fcntl (fd, F_SETLK, &fl) == -1) 
        return -1;
#endif
 
#ifdef LOCKF
    if (lockf (fd, F_LOCK, 0L) == -1) 
        return -1;
#endif
 
#ifdef FLOCK
    if (flock (fd, LOCK_EX) == -1) 
        return -1;
#endif
 
    return 0;
}


int
lkopen (char *file, int access)
{
    mts_init ("mts");
    switch (lockstyle) {
	case LOK_UNIX:
#if defined (FLOCK) || defined(LOCKF) || defined(FCNTL)
	    return f_lkopen(file, access);
#endif

	default:
	    return b_lkopen(file, access);
	}
}


static int
b_lkopen (char *file, int access)
{
    register int i, j;
    long curtime;
    char curlock[BUFSIZ], tmplock[BUFSIZ];
    struct stat st;

    if (stat (file, &st) == NOTOK)
	return NOTOK;
    lockname (curlock, tmplock, file, (int) st.st_dev, (int) st.st_ino);

    for (i = 0;;)
	switch (lockit (tmplock, curlock)) {
	    case OK: 
		if ((i = open (file, access)) == NOTOK) {
		    j = errno;
		    unlink (curlock);
		    errno = j;
		}
		timerON (curlock, i);
		return i;

	    case NOTOK: 
		if (stat (curlock, &st) == NOTOK) {
		    if (i++ > 5)
			return NOTOK;
		    sleep (5);
		    break;
		}

		i = 0;
		time (&curtime);
		if (curtime < st.st_ctime + 60L)
		    sleep (5);
		else
		    unlink (curlock);
		break;
	}
}


static int
lockit (char *tmp, char *file)
{
    register int fd;

    if ((fd = creat(tmp, 0400)) == NOTOK)
	return NOTOK;
#if defined(hpux) || defined(ncr)
    write(fd, "MH lock\n",8);
#endif /* hpux */
    close (fd);

    fd = link(tmp, file);
    unlink(tmp);

    return (fd != NOTOK ? OK : NOTOK);
}


static void
lockname (char *curlock, char *tmplock, char *file, int dev, int ino)
{
    register char *bp, *cp;

    bp = curlock;
    if ((cp = strrchr(file, '/')) == NULL || *++cp == 0)
	cp = file;
    if (lockldir == NULL || *lockldir == 0) {
	if (cp != file) {
	    sprintf (bp, "%.*s", cp - file, file);
	    bp += strlen (bp);
	}
    }
    else {
	sprintf (bp, "%s/", lockldir);
	bp += strlen (bp);
    }

    switch (lockstyle) {
	case LOK_BELL: 
	default: 
	    sprintf (bp, "%s.lock", cp);
	    break;

	case LOK_MMDF: 
	    sprintf (bp, "LCK%05d.%05d", dev, ino);
	    break;
    }

    if (tmplock) {
	if ((cp = strrchr(curlock, '/')) == NULL || *++cp == 0)
	    strcpy (tmplock, ",LCK.XXXXXX");
	else
	    sprintf (tmplock, "%.*s,LCK.XXXXXX", cp - curlock, curlock);
	unlink (mktemp (tmplock));
    }
}

/*
 * if we have any type of kernel locking defined
 */

#if defined(FLOCK) || defined(LOCKF) || defined(FCNTL)

static int
f_lkopen (char *file, int access)
{
    register int fd, i, j;

# ifdef FCNTL
    struct flock buf;
# endif /* FCNTL */

    for (i = 0; i < 5; i++) {

# if defined(LOCKF) || defined(FCNTL)
	j = access;
	access &= ~O_APPEND;	/* make sure we open at the beginning */
	if ((access & 03) == O_RDONLY) {
	/* We MUST have write permission or lockf/fcntl() won't work */
	/* (Stupid eh?) */
	    access &= ~O_RDONLY;
	    access |= O_RDWR;
	}
# endif /* LOCKF || FCNTL */

	if ((fd = open (file, access | O_NDELAY)) == NOTOK)
	    return NOTOK;

# ifndef LOCKF
#  ifndef FLOCK
#   ifndef FCNTL

	/* should be an error? */
#   else /* FCNTL */

	buf.l_type = F_WRLCK;
	buf.l_whence = 0;
	buf.l_start = 0;
	buf.l_len = 0;
	if (fcntl (fd, F_SETLK, &buf) != NOTOK)
	    return fd;
#   endif
#  else /* FLOCK */
	if (flock (fd, LOCK_EX | LOCK_NB) != NOTOK)
	    return fd;
#  endif
# else /* LOCKF */
	if (lockf (fd, F_TLOCK, 0L) != NOTOK) {
	    /* see if we should be at the end */
	    if (j & O_APPEND)
#  ifdef SVR4
		lseek (fd, (off_t) 0, SEEK_END);
#  else
		lseek (fd, (off_t) 0, L_XTND);
#  endif
	    return fd;
	}
	/* Fix errno - lockf screws it */
	if (errno == EACCES)
	    errno = EWOULDBLOCK;
# endif
	j = errno;
	close (fd);

	sleep (5);
    }

    close (fd);
    errno = j;
    return NOTOK;
}

#endif	/* FLOCK || LOCKF || FCNTL */



int
lkclose (int fd, char *file)
{
    char curlock[BUFSIZ];
    struct stat st;
#ifdef FCNTL
    struct flock buf;
#endif

    if (fd == NOTOK)
	return OK;
    switch (lockstyle) {
	case LOK_UNIX: 

#ifndef LOCKF
# ifndef FLOCK
#  ifndef FCNTL
	/* should be an error? */
#  else	/* FCNTL */
	    buf.l_type = F_UNLCK;
	    buf.l_whence = 0;
	    buf.l_start = 0;
	    buf.l_len = 0;
	    fcntl(fd, F_SETLK, &buf);
	    break;
#  endif
# else	/* FLOCK */
	    flock (fd, LOCK_UN);
	    break;
# endif
#else	/* LOCKF */
	    lseek (fd, (off_t) 0, L_SET); /* make sure we unlock the whole thing */
	    lockf (fd, F_ULOCK, 0L);
	    break;
#endif	

	default: 
	    if (fstat (fd, &st) != NOTOK) {
		lockname (curlock, NULL, file, (int) st.st_dev, (int) st.st_ino);
		unlink (curlock);
		timerOFF (fd);
	    }
    }

    return (close (fd));
}



FILE *
lkfopen (char *file, char *mode)
{
    register int fd;
    register FILE *fp;

    if ((fd = lkopen (file, strcmp (mode, "r") ? 2 : 0)) == NOTOK)
	return NULL;

    if ((fp = fdopen (fd, mode)) == NULL) {
	close (fd);
	return NULL;
    }

    return fp;
}



int
lkfclose (FILE *fp, char *file)
{
    char curlock[BUFSIZ];
    struct stat st;
#ifdef FCNTL
    struct flock buf;
#endif

    if (fp == NULL)
	return OK;

    switch (lockstyle) {
	case LOK_UNIX: 
#ifndef LOCKF
# ifndef FLOCK
#  ifndef FCNTL
	/* should be an error? */
#  else	/* FCNTL */
	    buf.l_type = F_UNLCK;
	    buf.l_whence = 0;
	    buf.l_start = 0;
	    buf.l_len = 0;
	    fcntl(fileno(fp), F_SETLK, &buf);
	    break;
#  endif
# else /* FLOCK */
	    flock (fileno(fp), LOCK_UN);
	    break;
# endif
#else	/* LOCKF */
	    fseek (fp, 0L, 0); /* make sure we unlock the whole thing */
	    lockf (fileno(fp), F_ULOCK, 0L);
	    break;
#endif

	default: 
	    if (fstat (fileno (fp), &st) != NOTOK) {
		lockname (curlock, NULL, file, (int) st.st_dev, (int) st.st_ino);
		unlink (curlock);
	    }
    }

    return (fclose (fp));
}


#define	NSECS ((unsigned) 20)


struct lock {
    int	l_fd;
    char *l_lock;
    struct lock *l_next;
};
#define	NULLP ((struct lock *) 0)

static struct lock *l_top = NULLP;



static RETSIGTYPE
alrmser (int sig)
{
    register int j;
    register char *cp;
    register struct lock *lp;

#ifndef	RELIABLE_SIGNALS
    SIGNAL (SIGALRM, alrmser);
#endif

    for (lp = l_top; lp; lp = lp->l_next)
	if (*(cp = lp->l_lock) && (j = creat (cp, 0400)) != NOTOK)
	    close (j);

    alarm (NSECS);
}


static void
timerON (char *lock, int fd)
{
    register struct lock *lp;
    size_t len;

    if (!(lp = (struct lock *) malloc ((size_t) (sizeof *lp))))
	return;

    lp->l_fd = fd;
    if (!(lp->l_lock = malloc (len))) {
	free ((char *) lp);
	return;
    }
    memcpy (lp->l_lock, lock, len);
    lp->l_next = NULLP;

    if (l_top)
	lp->l_next = l_top->l_next;
    else {
	/*
	 * perhaps SIGT{STP,TIN,TOU} ?
	 */
	SIGNAL (SIGALRM, alrmser);
	alarm (NSECS);
    }
    l_top = lp;
}


static void
timerOFF (int fd)
{
    register struct lock *pp, *lp;

    alarm(0);

    if (l_top) {
	for (pp = lp = l_top; lp; pp = lp, lp = lp->l_next)
	    if (lp->l_fd == fd)
		break;
	if (lp) {
	    if (lp == l_top)
		l_top = lp->l_next;
	    else
		pp->l_next = lp->l_next;

	    free (lp->l_lock);
	    free ((char *) lp);
	}
    }

    if (l_top)
	alarm (NSECS);
}
