/****************************************************************************
**
** Copyright (C) 2003-2006 Frank Hemer <frank@hemer.org>,
**                         Tilo Riemer <riemer@crossvc.com>
**
**
**----------------------------------------------------------------------------
**
**----------------------------------------------------------------------------
**
** CrossVC is available under two different licenses:
**
** If CrossVC is linked against the GPLed version of Qt 
** CrossVC is released under the terms of GPL also.
**
** If CrossVC is linked against a nonGPLed version of Qt 
** CrossVC is released under the terms of the 
** CrossVC License for non-Unix platforms (CLNU)
**
**
** CrossVC License for non-Unix platforms (CLNU):
**
** Redistribution and use in binary form, without modification, 
** are permitted provided that the following conditions are met:
**
** 1. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 2. It is not permitted to distribute the binary package under a name
**    different than CrossVC.
** 3. The name of the authors may not be used to endorse or promote
**    products derived from this software without specific prior written
**    permission.
** 4. The source code is the creative property of the authors.
**    Extensions and development under the terms of the Gnu Public License
**    are limited to the Unix platform. Any distribution or compilation of 
**    the source code against libraries licensed other than gpl requires 
**    the written permission of the authors.
**
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR 
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
** GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**
**
** CrossVC License for Unix platforms:
**
** 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, version 2 of the License.
** This program 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 General Public License version 2 for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*****************************************************************************/

#include "config.h"
#include "globals.h"

#include <qapplication.h>
#include <qdir.h>
#include <qprocess.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>

#include "DNotifyConnector.h"

#define errstr strerror(errno)

static int wfd = -1;
static void ** map = NULL;
static unsigned int max_fd = 0;

// static int snd = 0;
// static int rcv = 0;

static void
sigrt_handler(int, siginfo_t *si, void *)
{

  static time_t lmt = 0;
  static time_t lct = 0;
  static int lfd = -1;

//   qDebug("signal received");
  int fd = si->si_fd;
//   qDebug("fd: "+QString::number(fd));
//   qDebug("fd: "+QString::number(fd)+", sending: "+QString::number(snd++)+", errno: "+QString::number(si->si_errno)+", code: "+QString::number(si->si_code));
  if (wfd > 0) {

    struct stat buf;
    fstat(fd,&buf);
    if (fd == lfd) {
      if ( (buf.st_mtime == lmt) && (buf.st_ctime == lct) ) {
	return;
      }
      else {
	lmt = buf.st_mtime;
	lct = buf.st_ctime;
      }
    } else {
      lfd = fd;
      lmt = buf.st_mtime;
      lct = buf.st_ctime;
    }

    if ( write( wfd, &map[fd], sizeof(void*)) ) {
      
    } else {
      qDebug("signal handler: write failed: "+QString(errstr)+", err: "+QString::number(errno));
    }

  } else qDebug("signal handler: no socket fd for writing");

//   qDebug("signal handler done\n");
}

DNotifyConnector::DNotifyConnector() : DirConnector(), m_child(-1), sockfd(-1), socketNotifier(0) {

  int maxfd = 1024;

  QProcess proc;
  proc.addArgument("sh");
  proc.addArgument("-c");
  proc.addArgument("ulimit -n");
  if (proc.start()) {
    while (proc.isRunning()) {
      qApp->processEvents();
    }
    if ( (proc.normalExit()) ) {
      if ( (proc.exitStatus() == 0) && proc.canReadLineStdout() ) {
	bool ok = FALSE;
	int tmp = proc.readLineStdout().toInt(&ok);
	if (ok) {
	  maxfd = tmp;
	}
      }
    }
  }// else qDebug("couldn't start proc");
  qDebug("DNotify: maxfd set to: "+QString::number(maxfd));

  int sv[2];
  socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
  
  m_child = fork();
  if (m_child < 0) {
    qDebug("cannot fork child: "+QString(errstr));
    return;
  }

  if (m_child == 0) { /* child */
//     qDebug("fork ok");

    wfd = sv[1];

    map = new (void*)[maxfd];
    for (int i = 0; i < maxfd; ++i) {
      map[i] = NULL;
    }

    struct sigaction act;
    sigset_t signalset;
    
    sigemptyset(&signalset);
    sigaddset(&signalset, SIGRTMAX);
    
    /* Work around lpthread bug (libc/4927). */
    if (sigprocmask(SIG_UNBLOCK, &signalset, NULL) < 0) {
      qDebug("sigprocmask failed: "+QString(errstr)+", err: "+QString::number(errno));
      return;
    }

    if (sigpending(&signalset) < 0) {
      qDebug("sigpending failed: "+QString(errstr)+", err: "+QString::number(errno));
      return;
    }
    
    /* Register SIGRTMIN signal handlers. */
    act.sa_sigaction = sigrt_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO | SA_RESTART;
    if (sigaction(SIGRTMAX, &act, NULL) < 0) {
      qDebug("sigaction failed: "+QString(errstr)+", err: "+QString::number(errno));
      return;
    }
    
    do {
      char action;
      if ( read( sv[1], &action, 1) == 1) {

	void * dir;
	if (action == 'a') {
// 	  qDebug("received action: add");
		
	  int len;
	  if ( read( sv[1], &len, sizeof(int)) == sizeof(int)) {
	    char * path;
	    path = new char[len+1];
	    if ( read( sv[1], path, len) == len) {
	      path[len] = '\0';
	      if ( read( sv[1], &dir, sizeof(void*)) == sizeof(void*)) {
// 		qDebug("path is: "+QString(path));
		int fd = open(path, O_RDONLY);
		if (fd <= 0) {
		  qDebug("open returned faulty fd: "+QString::number(fd));
		  qDebug("current opened max fd: "+QString::number(max_fd));
		  qDebug("probably filedescriptors are exhausted, check with: 'ulimit -n'");
		  qDebug("DNotify exiting");
		  kill(getpid(),SIGKILL);
		} else if (fd >= maxfd) {
		  qDebug("filedescriptor id exceeds size: "+QString::number(fd));
		  qDebug("DNotify exiting");
		  kill(getpid(),SIGKILL);
		} else {
// 		qDebug("fd is: "+QString::number(fd));
		  bool failed = FALSE;
		  if (fcntl(fd, F_SETSIG, SIGRTMAX) < 0) {
		    qDebug("child: fcntl F_SETSIG on `"+QString(path)+"' failed: "+QString(errstr)+", err: "+QString::number(errno));
		    failed = TRUE;
		  }
		  if (fcntl(fd, F_NOTIFY, /*DN_ACCESS|*/DN_MODIFY|DN_CREATE|DN_DELETE|DN_RENAME|DN_ATTRIB|DN_MULTISHOT) < 0) {
		    qDebug("child: fcntl F_NOTIFY on `"+QString(path)+"' failed: "+QString(errstr)+", err: "+QString::number(errno));
		    failed = TRUE;
		  }
		  if (!failed) {
		    if (sigprocmask(SIG_BLOCK, &signalset, NULL) < 0) {
		      qDebug("sigprocmask failed: "+QString(errstr)+", err: "+QString::number(errno));
		      return;
		    }
		    map[fd] = dir;//qDebug("added");
		    if (sigprocmask(SIG_UNBLOCK, &signalset, NULL) < 0) {
		      qDebug("sigprocmask failed: "+QString(errstr)+", err: "+QString::number(errno));
		      return;
		    }
		    if (fd > (int)max_fd) max_fd = fd;
		  }
		}
	      }
	    }
	    delete path;
	  }
	} else if (action == 'r') {
// 	  qDebug("received action: remove");
		
	  if ( read( sv[1], &dir, sizeof(void*)) == sizeof(void*)) {
	    if (sigprocmask(SIG_BLOCK, &signalset, NULL) < 0) {
	      qDebug("sigprocmask failed: "+QString(errstr)+", err: "+QString::number(errno));
	      return;
	    }
	    for (unsigned int i = 0; i <= max_fd; ++i) {
	      if (map[i] == dir) {
// 		qDebug("found fd: "+QString::number(i));
		if ( close(i) < 0) {
		  qDebug("child close("+QString::number(i)+") failed: "+QString(errstr)+", err: "+QString::number(errno));
		} else {
		  map[i] = NULL;//qDebug("removing");
		}
		break;
	      }
	    }
	    if (sigprocmask(SIG_UNBLOCK, &signalset, NULL) < 0) {
	      qDebug("sigprocmask failed: "+QString(errstr)+", err: "+QString::number(errno));
	      return;
	    }
	  }
	} else {
	  qDebug("received unknown action");
	}
      } else {
	qDebug("child read failed: "+QString(errstr)+", err: "+QString::number(errno));
      }
    } while(getppid() != 1);

    delete [] map;

    qDebug("child exiting");
    exit(-1);
    
  } else { /* parent */
    sockfd = sv[0];

    int flags = fcntl(sockfd, F_GETFL);
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    
    socketNotifier = new QSocketNotifier(sockfd, QSocketNotifier::Read, 0);
    connect(socketNotifier, SIGNAL(activated(int)), SLOT(readNotify()));

    FD_ZERO(&m_wfds);
    FD_SET(sockfd, &m_wfds);

  }

  m_initiated = true;
  setRunning(true);
//   qDebug("dnotify done");
}

DNotifyConnector::~DNotifyConnector() {
  if (m_child > -1) {
    kill(m_child,SIGKILL);
//     qDebug("killed child");
  }
  setRunning(false);
}

void DNotifyConnector::readNotify() {
//   qDebug("received notification");
//   qDebug("receiving: "+QString::number(rcv++));
  DirBase * dir;
  if ( read( sockfd, &dir, sizeof(DirBase*)) == sizeof(DirBase*)) {
// 	qDebug("received notification of dirName: "+dir->fullName());
    QString fileName = QString::null;
    bool hlp = FALSE;
    addToDirQueue(dir, fileName, hlp);
  }
//   qDebug("notification done\n");
}

bool DNotifyConnector::addMonitoredDir(DirBase * dir) {
  if (!m_initiated) {
    qDebug("trying to add: "+dir->fullName()+" but dnotify not initiated");
    return FALSE;
  }

  const char * path = dir->fullName().latin1();
  int len = strlen(path);
  char action = 'a';
  if (sockfd > 0) {
    int n = -1;
    struct timeval tv;
    tv.tv_sec = 5;
    tv.tv_usec = 0;
    if ( (n = select(sockfd+1,NULL,&m_wfds,NULL,&tv)) > 0) {
      if ( ( write( sockfd, &action, 1) == 1 ) &&
	   ( write( sockfd, &len, sizeof(int)) == sizeof(int) ) &&
	   ( write( sockfd, path, len) == len ) &&
	   ( write( sockfd, &dir, sizeof(DirBase*)) == sizeof(DirBase *)) ) {
	return TRUE;
      } else {
	qDebug("addMonitoredDir: write failed: "+QString(errstr)+", err: "+QString::number(errno));
      }
    } else if ( n == 0 ) {//overload
      qDebug("addMonitoredDir: timeout on write --- disabling dirwatch, fallback to status checking (poll-mode)");
    } else qDebug("addMonitoredDir: select: "+QString(errstr)+", err: "+QString::number(errno));
  } else qDebug("addMonitoredDir: no socket fd for writing");

  return FALSE;
}

void DNotifyConnector::releaseMonitoredDir(DirBase * dir) {

  if (!m_initiated) {
    qDebug("trying to release: "+dir->fullName()+", but dnotify not initiated");
    return;
  }

  char action = 'r';
  int n = 0;
  if (sockfd > 0) {
    if ( select(sockfd+1,NULL,&m_wfds,NULL,NULL) > 0) {
      if ( ( (n = write( sockfd, &action, 1)) == 1 ) &&
	   ( (n = write( sockfd, &dir, sizeof(DirBase*))) == sizeof(DirBase *)) ) {
      } else {
	qDebug("releaseMonitoredDir: write failed: "+QString(errstr)+", err: "+QString::number(errno));
      }
    } else qDebug("releaseMonitoredDir: select: "+QString(errstr)+", err: "+QString::number(errno));
  } else qDebug("releaseMonitoredDir: no socket fd for writing");

  return;
}

