/*
   PKCIPE - public key based configuration tool for CIPE

   lock.c - lock and PID file handling

   Copyright 2000 Olaf Titz <olaf@bigred.inka.de>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version
   2 of the License, or (at your option) any later version.
*/
/* $Id: lock.c,v 1.5 2000/11/30 22:12:40 olaf Exp $ */

#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/file.h>
#include "pkcipe.h"

/* Locking scheme:
   All locks are in /var/run/cipe (or OPTDIR).
   The file @@LOCKFILE ("master") is flock()d while the others are processed.
   For each PEER,
     PEER.pid holds the PID of a running ciped, managed by ip-up/ip-down.
     PEER.pkc holds the PID of pkcipe while negotiating.
   The pkc file is only removed after ciped has started, so there is no gap.
*/

#ifndef OPTDIR
#define OPTDIR "/var/run/cipe/%s"
#endif
#define PIDDIR OPTDIR ".pid"
#define PKCDIR OPTDIR ".pkc"

static int writeint(int fd, int n)
{
    char buf[16];
    int i=snprintf(buf, sizeof(buf), "%11d\n", n);
    return write(fd, buf, i);
}

static char *master=NULL;
static char masterfd=-1;

int lockMaster(void)
{
    debug((DEB_LOCK, "lockMaster"));
    if (!master) {
	if (!(master=dsprintf(OPTDIR, "@@LOCKFILE"))) {
	    Log(LOG_ERR, "lockMaster: malloc failure");
	    return -1;
	}
    }
    if (masterfd<0) {
	if ((masterfd=open(master, O_RDWR|O_CREAT, 0600))<0) {
	    Log(LOG_ERR, "lockMaster: open: %m");
	    return -1;
	}
	if (fcntl(masterfd, F_SETFD, FD_CLOEXEC)<0)
	    Log(LOG_ERR, "lockMaster: FD_CLOEXEC: %m"); /* not fatal */
    }
    if (flock(masterfd, LOCK_EX)<0) {
	Log(LOG_ERR, "lockMaster: flock: %m");
	return -1;
    }
    return writeint(masterfd, getpid());
}

int unlockMaster(void)
{
    debug((DEB_LOCK, "unlockMaster"));
    if (masterfd<0) {
	Log(LOG_ERR, "unlockMaster: internal not locked");
	return -1;
    }
    if (flock(masterfd, LOCK_UN)<0) {
	Log(LOG_ERR, "unlockMaster: flock: %m");
	return -1;
    }
    (void)close(masterfd);
    /* (void)unlink(master); no! race! */
    masterfd=-1;
    return 0;
}

/* these routines are run under master lock which should guarantee it to
   be free of races */

INLINE int handleLock(FILE *f)
{
    int i, pid, e=0;
    char buf[32];
    FILE *p;

    if (fscanf(f, "%d", &pid)<1) {
	Log(LOG_NOTICE, "handleLock: pid file garbage");
	goto out;
    }
    debug((DEB_LOCK, "handleLock: %d", pid));
    snprintf(buf, sizeof(buf), "/proc/%d/stat", pid);
    if (!(p=fopen(buf, "r"))) {
	Log(LOG_INFO, "handleLock: open %s: %m", buf);
	goto out;
    }
    if (fscanf(p, "%d %31s", &i, buf)<2 || i!=pid) {
	Log(LOG_ERR, "handleLock: proc/stat garbage?");
	fclose(p);
	goto out;
    }
    fclose(p);
    if (strstr(buf, "pkcipe")) {
	debug((DEB_LOCK, "handleLock: found pkcipe process %d", pid));
	e=pid;
	goto out;
    }
    if (strstr(buf, "ciped")) {
	Log(LOG_NOTICE, "handleLock: killing process %d %s", pid, buf);
	kill(pid, SIGTERM);
	snprintf(buf, sizeof(buf), "/proc/%d/stat", pid);
	for (i=0; i<10; ++i) {
	    if (access(buf, R_OK)<0)
		break;
	    debug((DEB_LOCK, "handleLock: waiting..."));
	    sleep(1);
	}
	if (i>=10)
	    Log(LOG_WARNING, "handleLock: %d not going away", pid);
    } else {
	Log(LOG_NOTICE, "handleLock: lock seems to be stale %s", buf);
    }
 out:
    rewind(f);
    return e;
}

static void unlockPeer(void)
{
    char *buf=dsprintf(PKCDIR, peerIdentity);
    if (!buf) {
	Log(LOG_ERR, "unlockPeer: malloc failure");
	return;
    }
    unlink(buf);
    free(buf);
}

int lockPeer(void)
{
    FILE *f;
    int i=0;
    char *pkc=dsprintf(PKCDIR, peerIdentity);
    if (!pkc) {
	Log(LOG_ERR, "lockPeer: malloc failure");
	return -1;
    }
    debug((DEB_LOCK, "lockPeer"));
    if (access(pkc, R_OK|W_OK)==0) {
	/* pkc file exists */
	f=fopen(pkc, "r+");
	if (f)
	    i=handleLock(f);
    } else {
	/* pkc file does not exist, first check pid file */
	char *pid=dsprintf(PIDDIR, peerIdentity);
	if (!pid) {
	    Log(LOG_ERR, "lockPeer: malloc failure");
	    i=-1;
	    goto out;
	}
	f=fopen(pid, "r");
	if (f) {
	    i=handleLock(f);
	    fclose(f);
	}
	f=fopen(pkc, "w");
	free(pid);
    }
    if (!f) {
	Log(LOG_ERR, "lockPeer: fopen: %m");
	i=-1;
	goto out;
    }
    fprintf(f, "%d\n", getpid());
    fclose(f);

 out:
    if (pkc)
	free(pkc);
    if (!i)
	atexit(unlockPeer);
    return i;
}
