/* This file is part of the KDE libraries
    Copyright (C) 1999 Paul Campbell (paul@taniwha.com)

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.
	*/

// $Id: kuapp.cpp,v 1.122 1999/01/18 10:56:12 kulow Exp $

#include <qdir.h> // must be at the front

#include <kuapp.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>


KUniqueApplication::KUniqueApplication(int& argc, char** argv ) :
  KApplication( argc, argv )
{
	uniqueCheck("");
}


KUniqueApplication::KUniqueApplication(char *message,  int& argc, char** argv ) :
  KApplication( argc, argv, rAppName)
{
	uniqueCheck(message);
}


KUniqueApplication::KUniqueApplication(char *message, int& argc, char** argv, const QString& rAppName ) :
  KApplication( argc, argv, rAppName)
{
	uniqueCheck(message);
}

KUniqueApplication::KUniqueApplication(int& argc, char** argv, const QString& rAppName ) :
  KApplication( argc, argv )
{
	uniqueCheck("");
}

//
//	the guts of what we need to do
//	the basic idea hhere is:
//
//	1. find out if I'm the first incarnation of this 
//	   application (for this user)
//
//	2. if not send a message to the first one containing the message
//
//	
//	The gory details:
//
//		- try and open ~/.kde/share/apps/<app>/unique_fifo and write code to it
//		  if this fails or we get a signal 
//

static char signaled;
static void
catch_signal(int)
{
	signaled = 1;
}

//
//	256 is magic here - block sizes are multiples of 
//	    it - this means that writes don't have to be locked
//
#define	BUFF_SIZE	256

void KUniqueApplication::uniqueCheck(char *message)
{
	void	(*old)(int);
	int	i;
  	QDir 	dir;
  	QString d = localkdedir();
	char	host[100], buff[30];
	struct stat sbuff;
	char	mbuff[BUFF_SIZE];

	i = strlen(message);
	if (i > (int)(sizeof(mbuff)-1))
		i = (sizeof(mbuff)-1);
	mbuff[0] = i;
	bcopy(message, &mbuff[1], i);
	

	//
	//	first make a ~/.kde/share/apps/"app" directory if it doesn't exist
	//
  	d += "/share/apps";
  	dir.setPath(d.data());
  	if(!dir.exists()){
    		dir.mkdir(d.data());
    		chmod(d.data(), S_IRWXU);
  	}

  	d += "/";
  	d += appName();
  	dir.setPath(d.data());
  	if(!dir.exists()){
    		dir.mkdir(d.data());
    		chmod(d.data(), S_IRWXU);       
  	}
	d += "/";
	
	//
	//	make file names
	//

  	QString lock_link, fifo, pid;
	pid.sprintf("%d", getpid());
	lock = d + "unique_lock";
	gethostname(host, sizeof(host));
	lock_link.sprintf("%sunique_lock_%s.%d", d.data(), host, pid.data());
	fifo = d + "unique_fifo";

	
	
	for (int attempts = 0;attempts < 20;attempts++) {
		//
		// try and get the lock - use the NFS safe locking trick
		//	(remember while he/she has their own macheine their home
		//	directory might be remotely mounted)
		//
		//	first make a private file with our pid in it
		//
		(void)::unlink(lock_link.data());	// remove any old one
		fd = ::open(lock_link.data(), O_CREAT|O_RDWR, 0777);
		if (fd < 0) {
			(void)::fprintf(stderr, "%s: can't create lock %s\n", appName().data(), lock_link.data());
			(void)::exit(1);
		}
		(void)::write(fd, pid.data(), pid.length());
		(void)::close(fd);
		
		//
		//	next link to the lock (this will fail silently if there's one there already)
		//

		(void)::link(lock_link.data(), lock.data());	// atomic

		//
		//	we got the lock if the link succeeded and the nlink is 2
		//

		if (::stat(lock_link.data(), &sbuff) >= 0 && sbuff.st_nlink == 2) {	// I got the lock -- hooray!

			//
			//	remove the old fifo
			//
			(void)::unlink(fifo.data());

			//
			//	make a new one and open it
			//

			if (::mknod(fifo.data(), S_IFIFO|0755, 0) < 0 ||
			    (fd = ::open(fifo.data(), O_RDWR|O_NDELAY)) < 0) {	// must be RDWR or select screws up
				(void)::unlink(lock_link.data());
				(void)::fprintf(stderr, "%s: can't create fifo %s\n", appName().data(), fifo.data());
				(void)::exit(1);
			}
			(void)::fcntl(fd, F_SETFL, fcntl(fd, F_GETFL)&~O_NONBLOCK);
	
			//
			//	now connect QT to it so we will be told when data appears in it
			//

			mynotifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
			QObject::connect(mynotifier, SIGNAL(activated(int)),
					 this, SLOT(slotRead(int)));

			//	
			//	clean up the other link
			//
			(void)::unlink(lock_link.data());
			return;
		}
		//
		//	remove our lock attempt
		//
		(void)::unlink(lock_link.data());
		//
		//	so it's locked - see if the locker really exists
		//	open the lock and read the pid from it
		//
		fd = ::open(lock.data(), O_RDONLY);
		if (fd < 0)
			continue;
		i = ::read(fd, buff, sizeof(buff)-1);	
		buff[i] = 0;
		(void)::close(fd);
		(void)::sscanf(&buff[0], "%d", &i);
		if (::kill(i, 0) < 0) {	// it's not there - break the lock
			(void)::unlink(lock.data());
			continue;
		}
		//
		//	it's a valid lock - try to write some data to the fifo
		//
		fd = ::open(fifo.data(), O_WRONLY|O_NONBLOCK);
		if (fd >= 0) {
			signaled = 0;
			old = signal(SIGPIPE, &catch_signal);
			i = ::write(fd, (void *)&mbuff[0], sizeof(mbuff));	// we don't care about endedness because we're receiving on the same machine
			if (i == sizeof(mbuff) && !signaled) {   // we sent it safely
				::exit(0);			// we can quit now
			}
			(void)signal(SIGPIPE, old);
			close(fd);
		}
		//
		//	nope - try again
		//
		sleep(1);	// wait a bit then try again
	}
	(void)::fprintf(stderr, "%s: lock retries exceeded\n", appName().data());
	(void)::exit(1);
}

//
//	deliver signals from other apps to this one's slots
//	just read the data from the fifo and pass it on
//

void KUniqueApplication::slotRead(int)
{
	char mbuff[BUFF_SIZE+1];
	int i;

	i = ::read(fd, &mbuff[0], sizeof(mbuff)-1);
	if (i == (sizeof(mbuff)-1)) {
		i = mbuff[0]&0xff;
		mbuff[i+1] = 0;
		emit uniqueCalled(&mbuff[1]);
	}
}

KUniqueApplication::~KUniqueApplication()
{
	(void)::unlink(lock.data());
	close(fd);
}

#include "kuapp.moc"
