/* cfengine for GNU
 
        Copyright (C) 1995
        Free Software Foundation, Inc.
 
   This file is part of GNU cfengine - written and maintained 
   by Mark Burgess, Dept of Computing and Engineering, Oslo College,
   Dept. of Theoretical physics, University of Oslo
 
   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, or (at your option) any
   later version.
 
   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 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

*/
 

/*********************************************************************/
/*                                                                   */
/* TOOLKIT : Locks and Signals                                       */
/*                                                                   */
/*********************************************************************/

/* A log file of the run times is kept for each host separately.
   This records each atomic lock and the time at which it
   completed, for use in computing the elapsed time. The file
   format is:

   %s time:operation:operand

   Each operation (independently of operand) has a "last" inode
   which keeps the time at which is last completed, for use in
   calculating IfElapsed. The idea here is that the elapsed time
   is from the time at which the last operation of this type
   FINISHED. This is different from a lock (which is used to
   allow several sub operations to coexist). Here we are
   limiting activity in general to avoid "spamming".

   Each atomic operation (including operand) has a lock. The
   removal of this lock causes the "last" file to be updated.
   This is used to actually prevent execution of an atom
   which is already being executed. If this lock has existed
   for longer than the ExpireAfter time, the process owning
   the lock is killed and the lock is re-established. The
   lock file contains the pid. 

   This is robust to hanging locks and can be thought of as
   a garbage collection mechanism for these locks.

   Last files are just inodes (empty files) so they use no disk.
   The locks (which never exceed the no of running processes)
   contain the pid.

   */

#include "cf.defs.h"
#include "cf.extern.h"

 /* The locks need these in case of signals */

char CFLOCK[bufsize];
char CFLOG[bufsize];
char CFLAST[bufsize];

/********************************************************************/
 
void HandleSignal(signum)
 
int signum;
 
{
sprintf(OUTPUT,"Received signal %s\n",SIGNALS[signum]);
CfLog(cfinform,OUTPUT,""); 

if (signum == SIGTERM || signum == SIGINT || signum == SIGHUP)
   {
   ReleaseCurrentLock();
   closelog();
   exit(0);
   }
}

/************************************************************************/

int GetLock(operator,operand,ifelapsed,expireafter,host,now)

char *operator, *operand, *host;
int ifelapsed, expireafter;
time_t now;

{ struct stat statbuf;
  unsigned int pid;
  time_t lastcompleted = 0, elapsedtime;

if (IGNORELOCK)
   {
   return true;
   }

if (now == 0)
   {
   if ((now = time((time_t *)NULL)) == -1)
      {
      printf("Couldn't read system clock\n");
      }
   return true;
   }

Debug("GetLock(%s,%s,time=%d), ExpireAfter=%d, IfElapsed=%d\n",operator,operand,now,expireafter,ifelapsed);

sprintf(CFLOG,"%s/cfengine.%s.runlog",VLOGDIR,host);
sprintf(CFLOCK,"%s/lock.%s.%s.%s.%s",VLOCKDIR,VCANONICALFILE,host,operator,operand);
sprintf(CFLAST,"%s/last.%s.%s.%s.%s",VLOCKDIR,VCANONICALFILE,host,operator,operand);

if (strlen(CFLOCK) > MAX_FILENAME)
   {
   CFLOCK[MAX_FILENAME] = '\0';  /* most nodenames are 255 chars or less */
   }

if (strlen(CFLAST) > MAX_FILENAME)
   {
   CFLAST[MAX_FILENAME] = '\0';  /* most nodenames are 255 chars or less */
   }

/* Look for non-existent (old) processes */

lastcompleted = GetLastLock();
elapsedtime = (time_t)(now-lastcompleted) / 60;

if (elapsedtime < 0)
   {
   sprintf(OUTPUT,"Another cfengine seems to have done %s.%s since I started\n",operator, operand);
   CfLog(cfverbose,OUTPUT,"");
   return false;
   }

if (elapsedtime < ifelapsed)
   {
   sprintf(OUTPUT,"Too soon since last run with %s.%s (%u/%u minutes)\n",operator,operand,elapsedtime,ifelapsed);
   CfLog(cfverbose,OUTPUT,"");
   return false;
   }

/* Look for existing (current) processes */

lastcompleted = CheckOldLock();
elapsedtime = (time_t)(now-lastcompleted) / 60;
    
if (lastcompleted != 0)
   {
   if (elapsedtime >= expireafter)
      {
      sprintf(OUTPUT,"Lock %s expired...(after %u/%u minutes)\n",CFLOCK,elapsedtime,expireafter);
      CfLog(cfinform,OUTPUT,"");
      
      pid = GetLockPid();

      if (pid == -1)
	 {
	 sprintf(OUTPUT,"Illegal pid in corrupt lock %s - ignoring lock\n",CFLOCK);
	 CfLog(cferror,OUTPUT,"");
	 }
      else
	 {
	 Verbose("Trying to kill expired cfengine\n");
	 kill(pid,SIGCONT);
	 sleep(3);
	 kill(pid,SIGINT);
	 sleep(1);
	 kill(pid,SIGTERM);
	 sleep(5);
	 kill(pid,SIGKILL);
	 sleep(1);

	 if (kill(pid,SIGTERM) == ESRCH)
	    {
	    sprintf(OUTPUT,"Unable to kill expired process %d, exiting this time..\n",pid);
	    CfLog(cferror,OUTPUT,"");
	    FatalError("");;
	    }
	 
	 LockLog(pid,"Lock expired, process killed",operator,operand);
	 }

      unlink(CFLOCK);
      }
   else
      {
      Verbose("Couldn't obtain lock for %s (already running!)\n",CFLOCK);
      return false;
      }
   }

SetLock();
return true;
}
 
/************************************************************************/

void ReleaseCurrentLock()

{ int fd;

if (IGNORELOCK)
   {
   return;
   }

Debug("ReleaseCurrentLock(%s)\n",CFLOCK);

if (unlink(CFLOCK) == -1)
   {
   sprintf(OUTPUT,"Unable to remove lock %s\n",CFLOCK);
   CfLog(cflogonly,OUTPUT,"unlink");
   return;
   }

if ((fd = creat(CFLAST,0644)) == -1)
   {
   sprintf(OUTPUT,"Unable to create %s\n",CFLAST);
   CfLog(cferror,OUTPUT,"creat");
   FatalError("");
   }
else
   {
   close(fd);
   }

LockLog(getpid(),"Lock removed normally",CFLOCK,"");
}


/************************************************************************/

int CountActiveLocks()

 /* Count the number of active locks == number of cfengines running */

{ DIR *dirh;
  struct dirent *dirp;
  int count = 0;

if ((dirh = opendir(VLOCKDIR)) == NULL)
   {
   sprintf(OUTPUT,"Can't open directory %s\n",VLOCKDIR);
   CfLog(cferror,OUTPUT,"opendir");
   return 0;
   }

for (dirp = readdir(dirh); dirp != NULL; dirp = readdir(dirh))
   {
   if (strncmp(dirp->d_name,"lock",4) == 0)
      {
      count++;
      }
   }

closedir(dirh);

return count;
}

/************************************************************************/
/* Level 2                                                              */
/************************************************************************/

time_t GetLastLock()

{ struct stat statbuf;
  int fd;

bzero(&statbuf,sizeof(statbuf));

Debug("GetLastLock()\n");

if (stat(CFLAST,&statbuf) == -1)
   {
   
   /* Do this to prevent deadlock loops from surviving if IfElapsed > T_sched */
   
   if ((fd = creat(CFLAST,0644)) == -1)
      {
      sprintf(OUTPUT,"Unable to create %s\n",CFLAST);
      CfLog(cferror,OUTPUT,"creat");
      FatalError("");
      }
   else
      {
      close(fd);
      }
   return 0;
   }
else
   {
   return statbuf.st_mtime;
   }
}

/************************************************************************/

time_t CheckOldLock()

{ struct stat statbuf;

bzero(&statbuf,sizeof(statbuf));

Debug("CheckOldLock()\n");

if (stat(CFLOCK,&statbuf) == -1)
   {
   return 0;
   }
else
   {
   return statbuf.st_mtime;
   }
}

/************************************************************************/

int GetLockPid()

{ FILE *fp;
  int pid = -1;

if ((fp = fopen(CFLOCK,"r")) == NULL)
   {
   CfLog(cferror,"GetLock weird error, lock disappeared!\n","fopen");
   FatalError("");
   }

fscanf(fp,"%d",&pid);

fclose(fp);

return pid;
}

/************************************************************************/

void SetLock()

{ FILE *fp;

Debug("SetLock(%s)\n",CFLOCK);

if ((fp = fopen(CFLOCK,"w")) == NULL)
   {
   sprintf(OUTPUT,"GetLock: can't open new lock file %s\n",CFLOCK);
   CfLog(cferror,OUTPUT,"fopen");
   FatalError("");;
   }

fprintf(fp,"%d\n",getpid());

fclose(fp);
chmod(CFLOCK,0644); 
}

/************************************************************************/

void LockLog(pid,str,operator,operand)

int pid;
char *str, *operator, *operand;

{ FILE *fp;
  char buffer[maxvarsize];
  struct stat statbuf;
  time_t tim;

Debug("LockLog(%s)\n",str);

if ((fp = fopen(CFLOG,"a")) == NULL)
   {
   sprintf(OUTPUT,"GetLock: can't open log file %s\n",CFLOG);
   CfLog(cferror,OUTPUT,"fopen");
   FatalError("");
   }

if ((tim = time((time_t *)NULL)) == -1)
   {
   Debug("Cfengine: couldn't read system clock\n");
   }

sprintf(buffer,"%s",ctime(&tim));

Chop(buffer);

fprintf(fp,"%s:%s:pid=%d:%s:%s\n",buffer,str,pid,operator,operand);

fclose(fp);

if (stat(CFLOG,&statbuf) != -1)
   {
   if (statbuf.st_size > CFLOGSIZE)
      {
      Verbose("Rotating lock-runlog file\n");
      RotateFiles(CFLOG,2);
      }
   }
}

