#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <string.h>

#ifndef IN_BSD_LAND
/* This is for flock() in Linux */
#include <sys/file.h>
#endif

void hang_out(int t)
{
  int i = t;

  /* There is a cleaner way to do this... It does not really matter. I just
     do not want it to print the number we are starting with, and do not want
     it to print 0. */
  if(t >= 1)
    sleep(1);

  while(--i)
  {
    fprintf(stderr, "%d", i);
    sleep(1);
  }
}

int main(int argc, char *argv[])
{
  int f, ff;     /* File and FlagFile */
  ssize_t rw;
  char buf[16];

  /* ======================================================================= */
  if(argc != 2)
  {
    fprintf(stderr, "Unable to determine what mode I am in.\nExiting.\n");
    return(1);
  }

  if((0 == strcmp(argv[1], "--help")) ||
     (0 == strcmp(argv[1], "-h")) ||
     (0 == strcmp(argv[1], "-H")))
    {
      printf("flock - An instructive file locking tool.\n");
      printf("        Open two windows, and in the first window run \"flock first\", then in\n");
      printf("        the second window run \"flock second\". The first will hold open the\n");
      printf("        file with a shared lock for 10 seconds. The second will open the file\n");
      printf("        with a shared lock then attempt to convert the lock to exclusive. The\n");
      printf("        second will block waiting for the first to release the lock (when it\n");
      printf("        closes the file). Remember: flock blocks!\n");
      return(0);
    } 

  /* ======================================================================= */
  if(0 == strcmp(argv[1], "first"))
  {
    fprintf(stderr, "Removing any pre-existing flag file...");
    remove("flag.file");
    fprintf(stderr, "Done.\n");

#ifdef IN_BSD_LAND
    fprintf(stderr, "Opening file with exclusive lock...");
    if(-1 == (f = open("delete.me",
		       O_CREAT | O_WRONLY | O_APPEND | O_EXLOCK,
		       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)))
      {
	fprintf(stderr, "Failed.\n");
	return(1);
      }
    fprintf(stderr, "Done.\n");
#else
    fprintf(stderr, "Opening file...");
    if(-1 == (f = open("delete.me",
		       O_CREAT | O_WRONLY | O_APPEND,
		       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)))
      {
	fprintf(stderr, "Failed.\n");
	return(1);
      }
    fprintf(stderr, "Done.\n");

    fprintf(stderr, "Setting an exclusive lock on the file...");
    if(-1 == flock(f, LOCK_EX))
      {
	fprintf(stderr, "Failed.\n");
	return(1);
      }
    fprintf(stderr, "Done.\n");
#endif



    fprintf(stderr, "Writing some characters...");
    rw = write(f, "some characters", 15);
    if(rw == 15)
      fprintf(stderr, "Done.\n");
    else
    {
      fprintf(stderr, "Failed.\n");
      return(1);
    }

    fprintf(stderr, "Waiting for \"second\" to start...");
    while(-1 == (ff = open("flag.file",
			   O_RDONLY,
			   S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)))
      {
	fprintf(stderr, ".");
	sleep(1);
      }
    rw = read(ff, buf, 16);
    close(ff);
    fprintf(stderr, "Started.");

    if(rw > 0)
    {
      buf[rw] = 0;
      fprintf(stderr, " PID=%s\n", buf);
    }
    else
      fprintf(stderr, "\n");

    fprintf(stderr, "Removing the flag file...");
    remove("flag.file");
    fprintf(stderr, "Done.\n");

    fprintf(stderr, "Holding file open (exclusively) for 10 seconds...");
    hang_out(10);
    fprintf(stderr, "...Done.\n");

    fprintf(stderr, "Closing the file...");
    close(f);
    fprintf(stderr, "Done.\n");

    fprintf(stderr, "Waiting a second to insure that we are into next operation...");
    sleep(1);
    fprintf(stderr, "Done.\n");

#ifdef IN_BSD_LAND
    fprintf(stderr, "Opening file again with shared lock...");
    if(-1 == (f = open("delete.me",
		       O_CREAT | O_WRONLY | O_APPEND | O_SHLOCK,
		       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)))
      {
	fprintf(stderr, "Failed.\n");
	return(1);
      }
    fprintf(stderr, "Done.\n");
#else
    fprintf(stderr, "Opening file again...");
    if(-1 == (f = open("delete.me",
		       O_CREAT | O_WRONLY | O_APPEND,
		       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)))
      {
	fprintf(stderr, "Failed.\n");
	return(1);
      }
    fprintf(stderr, "Done.\n");

    fprintf(stderr, "Setting a shared lock on the file...");
    if(-1 == flock(f, LOCK_SH))
      {
	fprintf(stderr, "Failed.\n");
	return(1);
      }
    fprintf(stderr, "Done.\n");
#endif

    fprintf(stderr, "Closing the file...");
    close(f);
    fprintf(stderr, "Done.\n");

  }




  /* ======================================================================= */
  if(0 == strcmp(argv[1], "second"))
  {

    fprintf(stderr, "Signaling that I have started...");
    if(-1 == (ff = open("flag.file",
			O_CREAT | O_TRUNC | O_WRONLY,
			S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)))
    {
      fprintf(stderr, "Failed.");
      return(1);
    }
    sprintf(buf, "%d", getpid());
    /*fprintf(stderr, "[%s]", buf);*/
    rw = write(ff, buf, strlen(buf));
    close(ff);
    if(rw == strlen(buf))
      fprintf(stderr, "Done.\n");
    else
      fprintf(stderr, "Completed, with errors.\n");



#ifdef IN_BSD_LAND
    fprintf(stderr, "Opening file with shared lock...");
    if(-1 == (f = open("delete.me",
		       O_RDWR | O_SHLOCK,
		       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)))
    {
      fprintf(stderr, "Failed.\n");
      return(1);
    }
    fprintf(stderr, "Done.\n");

#else
    fprintf(stderr, "Opening file...");
    if(-1 == (f = open("delete.me",
		       O_RDWR,
		       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)))
    {
      fprintf(stderr, "Failed.\n");
      return(1);
    }
    fprintf(stderr, "Done.\n");

    fprintf(stderr, "Setting a shared lock on the file...");
    if(-1 == flock(f, LOCK_SH))
      {
	fprintf(stderr, "Failed.\n");
	return(1);
      }
    fprintf(stderr, "Done.\n");
#endif

    fprintf(stderr, "Reading some characters...");
    rw = read(f, &buf, 15);
    if(rw == 15)
      fprintf(stderr, "Done.\n");
    else
      {
	fprintf(stderr, "Failed.\n");
	return(1);
      }

 
    fprintf(stderr, "Upgrading file lock to exclusive...");
    if(-1 == flock(f, LOCK_EX))
      {
	fprintf(stderr, "Failed.\n");
	return(1);
      }
    fprintf(stderr, "Done.\n");

    fprintf(stderr, "Writing a character...");
    rw = write(f, "\n", 1);
    if(rw == 1)
      fprintf(stderr, "Done.\n");
    else
      {
	fprintf(stderr, "Failed.\n");
	return(1);
      }

    fprintf(stderr, "Holding file open with exclusive lock for 10 seconds...");
    hang_out(10);
    fprintf(stderr, "...Done.\n");



    fprintf(stderr, "Downgrading file lock to shared...");
    if(-1 == flock(f, LOCK_SH))
      {
	fprintf(stderr, "Failed.\n");
	return(1);
      }
    fprintf(stderr, "Done.\n");


   fprintf(stderr, "Holding file open with shared lock for 10 seconds...");
    hang_out(10);
    fprintf(stderr, "...Done.\n");


    fprintf(stderr, "Closing the file...");
    close(f);
    fprintf(stderr, "Done.\n");
  }

  
  return(0);
}