/*
 * Copyright 2002 Sun Microsystems, Inc.  All rights reserved.
 *
 * Use is subject to the GNU Lesser General Public License, Version 2.1,
 * February 1999, which is contained in the read-me file named
 * "README_GNU_LGPL." This library is free software; you can
 * redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your
 * option) any later version. This library is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA  02111-1307  USA.
 *
 * lock functions for the panel utils 
 *
 * NOTE: as we want to make sure that all of the lcd utils know
 *       about each other, this is almost intentionally 
 *       single-threaded.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#define __USE_GNU
#include <fcntl.h>
#include "lcdutils.h"

#define LOCKFILE "/var/run/.lcdlock"
static int lockfd = -1;

extern int errno;

/* lcd locking */
int lcd_lock(void) {
	char buf[16];
	struct flock lock;
	struct stat st1, st2;

	if (lockfd > -1) /* already locked */
		return -2;

	/*
	 * verify file before opening it
	 */

	if (lstat(LOCKFILE, &st1) < 0) {
		/*
		 * does not exist - create
		 */
		lockfd = open(LOCKFILE, O_RDWR | O_TRUNC | O_EXCL | O_CREAT, 0600);
		if (lockfd < 0) {
			fprintf(stderr, "ERROR: unable to open file '%s' for write: %s\n",LOCKFILE, strerror(errno));
			return lockfd = -1;
		}
	}
	else {
		/*
		 * exists - allow only regular file, not links
		 */
		if (!S_ISREG(st1.st_mode)) {
			fprintf(stderr, "ERROR: file '%s' has invalid mode: %d\n",
				LOCKFILE, st1.st_mode);
			return lockfd = -1;
		}

		/*
		 * allow only files with 1 link (itself)
		 */
		if (st1.st_nlink != 1) {
			fprintf(stderr, "ERROR: file '%s' has invalid link count: %d != 1\n",LOCKFILE, st1.st_nlink);
			return lockfd = -1;
		}

		/*
		 * open it for write, overwrite existing file
		 */
		lockfd = open(LOCKFILE, O_RDWR | O_NOFOLLOW | O_TRUNC, 0600);
		if (lockfd < 0) {
			fprintf(stderr, "ERROR: unable to overwrite file '%s': %s\n",
				LOCKFILE, strerror(errno));
			return lockfd = -1;
		}

		/*
		 * stat again and verify inode/owner/link count
		 */
		fstat(lockfd, &st2);
		if (st2.st_ino != st1.st_ino || st2.st_uid != st1.st_uid || st2.st_nlink != 1) {
			fprintf(stderr, "ERROR: unable to verify file '%s'\n", LOCKFILE);
			close(lockfd);
			return lockfd = -1;
		}
	}	  

	/*
	 * establish a fcntl lock on it
	 * so it dies when the process dies
	 */
	memset(&lock, 0, sizeof(lock));
	lock.l_type = F_WRLCK;
	if (fcntl(lockfd, F_SETLK, &lock) < 0) {
		fprintf(stderr, "ERROR: unable to establish fcntl lock on file '%s'\n",LOCKFILE);
		close(lockfd);
		return lockfd = -1;
	}

	memset(&buf[0], 0, sizeof(buf));
	sprintf(buf, "%d", getpid());
	if (write(lockfd, buf, strlen(buf)) < 0) {
		fprintf(stderr, "ERROR: unable to write PID '%s' to file '%s'\n",buf, LOCKFILE);
		close(lockfd);
		return lockfd = -1;
	}
	return 0;
}

/* this assumes that every call to lcd_unlock really means it. */
void lcd_unlock(void) {
	if (lockfd > -1) {
		close(lockfd);
		lockfd = -1;
	}
	unlink(LOCKFILE);
}
