/*
 * File:        lock.c
 *
 * Author:      Ulli Horlacher (framstag@belwue.de)
 *
 * History:
 *
 *   1995-11-02	Framstag	initial version
 *   1998-09-29	Framstag	print also PID on testing
 *
 * This program sets or tests advisory locks conforming to POSIX fcntl() call.
 * You may specify optionaly a program to start it within the lock context.
 *
 * Copyright © 1997 Ulli Horlacher
 * This file is covered by the GNU General Public License
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>

/* some systems are missing the getopt declarations */
int getopt(int, char * const *, const char *);
extern int opterr;
extern int optind;
extern int optopt;
extern char *optarg;


/* write-lock a file (POSIX conform) */
int wlock_file(int,int,int);

/* test if a file is write-lock blocked (POSIX conform) */
int tlock_file(int,int,int);

/* print short usage message */
void usage();


char *prg;              /* name of the game */


int main(int argc, char *argv[]) {
  int i,		/* simple loop counter */
      status,		/* return status */
      lockf,		/* lock file descriptor */
      opt,		/* getopt return value */
      verbose,		/* flag for verbose mode */
      begin,		/* begin of locking area */
      length,		/* length of locking area */
      seconds,		/* seconds to sleep */
      pid;		/* process ID */
  char type;		/* locl-type: testing or seting */
  char cmd[32768];	/* program and arguments to start */
  char *cp,		/* simple string pointer */
       *file,		/* file to lock */
       *nprg;		/* next program to start */

  verbose=0;
  begin=0;
  length=0;
  seconds=86400;
  strcpy(cmd,"");
  type='t'; 		/* default: test lock */
  
  /* what's my name? */
  prg=argv[0];
  if ((cp=strrchr(prg,'/'))) prg=cp+1;
  
  while ((opt=getopt(argc, argv, "vtsb:l:a:")) > 0) {
    switch (opt) {
      	case ':':
	case 'h':
	case '?': usage();
	case 't': type='t'; break;
	case 's': type='s'; break;
	case 'v': verbose=1; break;
	case 'b': begin=atoi(optarg); break;
	case 'l': length=atoi(optarg); break;
	case 'a': seconds=atoi(optarg); break;
    }
  }
  if (argc-optind==0) usage();
  file=argv[optind];
  nprg=argv[optind+1];

  /* try to open file */
  if ((lockf=open(file,O_WRONLY|O_APPEND,S_IRUSR|S_IWUSR))<0) {
    fprintf(stderr,"%s: cannot open %s : %s\n",prg,file,strerror(errno));
    exit(1);
  }

  /* set lock mode? */
  if (type=='s') {
    
    /* try to lock it */
    status=wlock_file(lockf,begin,length);
    
    /* lock failed */
    if (status<0) {
      if (verbose) fprintf(stderr,"%s: lock failed for %s\n",prg,file);
      exit(2);
    }
    
    /* next program specified? */
    if (nprg) {
      
      /* build command string */
      for (i=optind+1;argv[i];i++) {
	strcat(cmd," ");
	strcat(cmd,argv[i]);
      }
      
      /* create subprocess */
      pid=fork();
      if (pid<0) {
	fprintf(stderr,"%s: cannot create subprocess : %s\n",prg,strerror(errno));
	exit(2);
      }
      
      /* in subprocess start next program */
      if (pid==0) {
	if (verbose) fprintf(stderr,"%s: executing %s\n",prg,cmd);
	execvp(nprg,&argv[optind+1]);
	fprintf(stderr,"%s: cannot execute %s : %s\n",prg,nprg,strerror(errno));
	exit(2);
      }
      
      /* that's it */
      wait(NULL);
      exit(0);
      
    }
    
    /* sleep for some time to keep lock alive */
    if (verbose) fprintf(stderr,"%s: lock ok for %s, "
			        "going into sleep for %d seconds\n",
			 prg,file,seconds);
    sleep(seconds);
    exit(0);
    
  /* only test the file for locks */
  } else {
    status=tlock_file(lockf,begin,length);
    if (status<0)  {
      if (verbose) fprintf(stderr,"%s: testing lock for %s failed!\n",prg,file);
      exit(2);
    }
    if (status==0) {
      if (verbose) fprintf(stderr,"%s: no lock for %s\n",prg,file);
      exit(1);
    }
    if (status>0)  {
      if (verbose) fprintf(stderr,"%s: %s is locked by PID %d\n",
			   prg,file,status);
      exit(0);
    }
  }

  exit(0);
}


/*
 * wlock_file - write-lock a file (POSIX conform)
 *
 * INPUT:  file descriptor
 *
 * RETURN: >= 0 if ok, -1 if error
 */
int wlock_file(int fd, int begin, int length) {
  struct flock lock;	/* file locking structure */

  /* fill out the file locking structure */
  lock.l_type=F_WRLCK;
  lock.l_start=begin;
  lock.l_whence=SEEK_SET;
  lock.l_len=length;

  /* try to lock the file and return the status */
  return(fcntl(fd,F_SETLK,&lock));
}


/*
 * tlock_file - test if a file is write-lock blocked (POSIX conform)
 *
 * INPUT:  fd  - file descriptor
 *
 * RETURN: 0 if no lock, lock-PID if locked, -1 on error
 */
int tlock_file(int fd, int begin, int length) {
  int status;
  struct flock lock;    /* file locking structure */

  /* fill out the file locking structure */
  lock.l_type=F_WRLCK;
  lock.l_start=begin;
  lock.l_whence=SEEK_SET;
  lock.l_len=length;

  /* test the lock status */
  status=fcntl(fd,F_GETLK,&lock);
  if (status>=0) status=(lock.l_type!=F_UNLCK);
  if (status>0)  status=lock.l_pid;
  return(status);
}


void usage() {
  printf("purpose: \"%s\" tests or sets a file with POSIX-fcntl() write-locks\n",prg);
  printf("usage: %s [-s [-a seconds]] [-t] [-v] [-b begin] [-l length] file\n",prg);
  printf("   or: %s -s [-v] [-b begin] [-l length] file program [arguments]\n",prg);
  printf("options:  -t   test lock (default)\n");
  printf("          -s   set lock\n");
  printf("          -v   verbose output\n");
  printf("          -b   lock starts at byte #begin\n");
  printf("          -l   lock is #length byte long\n");
  printf("          -a   lock is #seconds active (default=86400)\n");
  printf("          program ... start this program within the lock context\n");
  printf("Exit status:  test: 0 = locked, 1 = not locked, 2 = error\n");
  printf("              set:  0 = lock was successful, 2 = error\n");
  printf("Examples:  %s -s -v log_file\n",prg);
  printf("           %s -s -b 10 log_file vi log_file\n",prg);
  printf("           %s -vt log_file\n",prg);
  exit(0);
}
