/*  $Revision: 1.8 $
**
**  Produce reliable locks for shell scripts, by Peter Honeyman as told
**  to Rich $alz.
*/
#include <stdio.h>
#include <sys/types.h>
#include "configdata.h"
#include "clibrary.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>


STATIC BOOL	BinaryLock;
STATIC char	CANTUNLINK[] = "Can't unlink \"%s\", %s\n";
STATIC char	CANTOPEN[] = "Can't open \"%s\", %s\n";


/*
**  See if the process named in an existing lock still exists by
**  sending it a null signal.
*/
STATIC BOOL
ValidLock(name, JustChecking)
    char		*name;
    BOOL		JustChecking;
{
    register int	fd;
    register int	i;
    PID_T		pid;
    char		buff[BUFSIZ];

    /* Open the file. */
    if ((fd = open(name, O_RDONLY)) < 0) {
	if (JustChecking)
	    return FALSE;
	(void)fprintf(stderr, CANTOPEN, name, strerror(errno));
	return TRUE;
    }

    /* Read the PID that is written there. */
    if (BinaryLock) {
	if (read(fd, (char *)&pid, sizeof pid) != sizeof pid) {
	    (void)close(fd);
	    return FALSE;
	}
    }
    else {
	if ((i = read(fd, buff, sizeof buff - 1)) <= 0) {
	    (void)close(fd);
	    return FALSE;
	}
	buff[i] = '\0';
	pid = (PID_T) atol(buff);
    }
    (void)close(fd);
    if (pid <= 0)
	return FALSE;

    /* Send the signal. */
    if (kill(pid, 0) < 0 && errno == ESRCH)
	return FALSE;

    /* Either the kill worked, or we're optimistic about the error code. */
    return TRUE;
}


/*
**  Unlink a file, print a message on error, and exit.
*/
STATIC NORETURN
UnlinkAndExit(name, x)
    char	*name;
    int		x;
{
    if (unlink(name) < 0)
	(void)fprintf(stderr, CANTUNLINK, name, strerror(errno));
    exit(x);
}


/*
**  Print a usage message and exit.
*/
STATIC NORETURN
Usage()
{
    (void)fprintf(stderr, "Usage: shlock [-u|-b] -f file -p pid\n");
    exit(1);
    /* NOTREACHED */
}


int
main(ac, av)
    int			ac;
    char		*av[];
{
    register int	i;
    register char	*p;
    register int	fd;
    char		tmp[BUFSIZ];
    char		buff[BUFSIZ];
    char		*name;
    PID_T		pid;
    BOOL		ok;
    BOOL		JustChecking;

    /* Set defaults. */
    pid = 0;
    name = NULL;
    JustChecking = FALSE;
    (void)umask(NEWSUMASK);

    /* Parse JCL. */
    while ((i = getopt(ac, av, "bcup:f:")) != EOF)
	switch (i) {
	default:
	    Usage();
	    /* NOTREACHED */
	case 'b':
	case 'u':
	    BinaryLock = TRUE;
	    break;
	case 'c':
	    JustChecking = TRUE;
	    break;
	case 'p':
	    pid = (PID_T) atol(optarg);
	    break;
	case 'f':
	    name = optarg;
	    break;
	}
    ac -= optind;
    av += optind;
    if (ac || pid == 0 || name == NULL)
	Usage();

    /* Create the temp file in the same directory as the destination. */
    if ((p = strrchr(name, '/')) != NULL) {
	*p = '\0';
	(void)sprintf(tmp, "%s/shlock%ld", name, (long)getpid());
	*p = '/';
    }
    else
	(void)sprintf(tmp, "shlock%ld", (long)getpid());

    /* Loop until we can open the file. */
    while ((fd = open(tmp, O_RDWR | O_CREAT | O_EXCL, 0644)) < 0)
	switch (errno) {
	default:
	    /* Unknown error -- give up. */
	    (void)fprintf(stderr, CANTOPEN, tmp, strerror(errno));
	    exit(1);
	case EEXIST:
	    /* If we can remove the old temporary, retry the open. */
	    if (unlink(tmp) < 0) {
		(void)fprintf(stderr, CANTUNLINK, tmp, strerror(errno));
		exit(1);
	    }
	    break;
	}

    /* Write the process ID. */
    if (BinaryLock)
	ok = write(fd, (POINTER)&pid, (SIZE_T)sizeof pid) == sizeof pid;
    else {
	(void)sprintf(buff, "%ld\n", (long) pid);
	i = strlen(buff);
	ok = write(fd, (POINTER)buff, (SIZE_T)i) == i;
    }
    if (!ok) {
	(void)fprintf(stderr, "Can't write PID to \"%s\", %s\n",
	    tmp, strerror(errno));
	(void)close(fd);
	UnlinkAndExit(tmp, 1);
    }

    (void)close(fd);

    /* Handle the "-c" flag. */
    if (JustChecking) {
	if (ValidLock(name, TRUE))
	    UnlinkAndExit(tmp, 1);
	UnlinkAndExit(tmp, 0);
    }

    /* Try to link the temporary to the lockfile. */
    while (link(tmp, name) < 0)
	switch (errno) {
	default:
	    /* Unknown error -- give up. */
	    (void)fprintf(stderr, "Can't link \"%s\" to \"%s\", %s\n",
		    tmp, name, strerror(errno));
	    UnlinkAndExit(tmp, 1);
	    /* NOTREACHED */
	case EEXIST:
	    /* File exists; if lock is valid, give up. */
	    if (ValidLock(name, FALSE))
		UnlinkAndExit(tmp, 1);
	    if (unlink(name) < 0) {
		(void)fprintf(stderr, CANTUNLINK, name, strerror(errno));
		UnlinkAndExit(tmp, 1);
	    }
	}

    UnlinkAndExit(tmp, 0);
    /* NOTREACHED */
}
