/*
 * LIBflock.c --
 *
 * File opening with file locks (used by utils/path.c and database/DBio.c
 * Written by Michael D. Godfrey, Stanford University
 *
 *     ********************************************************************* 
 *     * Copyright (C) 1985, 1990 Regents of the University of California. * 
 *     * Permission to use, copy, modify, and distribute this              * 
 *     * software and its documentation for any purpose and without        * 
 *     * fee is hereby granted, provided that the above copyright          * 
 *     * notice appear in all copies.  The University of California        * 
 *     * makes no representations about the suitability of this            * 
 *     * software for any purpose.  It is provided "as is" without         * 
 *     * express or implied warranty.  Export of this software outside     * 
 *     * of the United States of America may require an export license.    * 
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] = "$Header: /ufs/repository/magic/utils/flock.c,v 1.10 2001/08/24 16:51:25 tim Exp $";
#endif  not lint

#include <stdio.h>
#include <stdlib.h>
#ifdef __STDC__
#include <unistd.h>
#endif

#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/time.h>

#ifdef FILE_LOCKS

#include "misc/magic.h"
#include "utils/hash.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "database/database.h"
#include "windows/windows.h"
#include "utils/malloc.h"

/* definitions */

typedef struct f_list {
   struct f_list	*ptr;
   struct f_list	*back_ptr;
   int			upid;
   char			*hostname;
   char			*userid;
   int			*window;
   char			*filename;
} flock_list;

#define LOCK_DIR "flock_list"   /* name of lock directory */

/* globals */

global int upid, hst;
global char hstname[100];
global char lock_buf[400], magic_buff[60];
global flock_list *f_list_ptr;
global int flock_msg = 1;
global int flock_on = 1; /* 1 = locking active, 0 = locking disabled */
global int flock_flag;  /* This is not good, but it is the only way
                           to signal that flock_open has detected a lock. */

/*-------------------------------------------------------------------------
 * Below are the service routines for file locking. The intended
 * behavior is:
 *
 *   1. Lock calls are placed at:
 *		dbReadOpen     f = flock_open(filename, ext, mode)
 *              DBCellWrite    flock_close(f, filename)
 *              Magic exit     flock_exit()
 *
 *   2. The purpose is to prevent unintended interaction of multiple users
 *      accessing the same database. The implementation should work on
 *      single machines and in an NFS environment.
 *
 *   3. The main platform is Linux (of course), but it is hoped that the
 *      code will work on other platforms. Since all the work is done in
 *      the routines below, it should be possible to reimplement the methods
 *	without the cost of figuring out Magic logic flow.
 *
 *   4. For this code to work smoothly, some additiional code that is
 *	conditional on the tag FILE_LOCKS is required. This code implements
 *	the idea of a cell which is read_only, i.e. it cannot be made the
 *	"editing" cell.  This permits users to view a cell which is being
 *	edited by another user, but not to modify it or write it back. This
 *	code resides in misc/magic.h, commands/CmdE.c, dbwind/DBWprocs.c,
 *	and in this file.
 *-------------------------------------------------------------------------
 */

/*-------------------------------------------------------------------------
 * flock_init --
 *
 * Initialization tasks:
 *
 *  1. Test/create flock directory (LOCK_DIR) in current directory.
 *     fail -> set flock_on = 0.
 *  2. Create file in $CAD_HOME/magic/flock directory:
 *     2.1 Generate unique name for link.
 *     2.2 Write this string in LOCK_DIR/link.
 *     2.3 Obtain full path to LOCK_DIR and write this string to
 *         file in $CAD_HOME/magic/flock. File name is the unique string.
 *  3. Test if environment variable MAGICID is set. If not set it to
 *     unique string. This is used for clearing stale locks, and helping
 *     users decide who has files locked.
 *-------------------------------------------------------------------------
 */

Void
flock_init()
{
    flock_list *f_list_head;
    struct stat dirstat;
    int dir, ret;
    struct timeval name_time;
    struct timezone dtime;
    char cur_path[400];
    char *cad_path;
    char cad_buf[400];
    FILE *lfile, *mfile;	/* local file and master file */
    char *normal_cad = "$CAD_HOME";

    /* First test for master flock_list directory before creating a local one	*/

    cad_path = cad_buf;
    if (PaExpand (&normal_cad, &cad_path, 400) == -1)
    {
	flock_on = 0;
	TxError ("No $CAD_HOME environment variable set, internally or externally\n");
	cad_path[0] = '\0';
	flock_on = 0;
	return;
    }

    sprintf(lock_buf, "%s/flock", cad_path);
    dir = stat(lock_buf, &dirstat);
    if((dir == -1) || !(( dirstat.st_mode) & S_IFDIR))
    {
	TxPrintf("Directory \"%s\" not available: no file locking!\n", lock_buf);
	flock_on = 0;
	return;
    }
    TxPrintf("Using directory \"%s\" for managing file locks.\n", lock_buf);

    /* Get the current time as a string to name the lock */

    gettimeofday(&name_time, &dtime);

    /* Open the master file to create a link in $CAD_HOME/flock */

    sprintf(lock_buf, "%s/flock/lock_%u%u", cad_path,
		name_time.tv_sec, name_time.tv_usec);

    if ((mfile = fopen(lock_buf, "w")) == NULL)
    {
	TxPrintf("Cannot open \"%s\" for writing: no file locking!\n", lock_buf);
	flock_on = 0;
	return;
    }

    /* Now test for local flock directory, and create one if not there. */

    dir = stat(LOCK_DIR, &dirstat);
    /* TxPrintf("dirstat.st_mode: %s %x -- %x\n", LOCK_DIR, dirstat.st_mode, S_IFDIR); */
    if((dir == -1) || !(( dirstat.st_mode) & S_IFDIR))
    {
	if (!mkdir(LOCK_DIR, 0777) < 0) {
	    TxError("Could not create local file locking directory \"" LOCK_DIR "\"\n");
	    flock_on = 0;
	    fclose(mfile);
	    mfile = NULL;
	    return;
	}
	TxPrintf("Local file locking directory \"" LOCK_DIR "\" created.\n");
    }

    /* Create local file which contains name of link in $CAD_HOME */

    if ((lfile = fopen(LOCK_DIR "/link", "w")) == NULL)
    {
	TxPrintf("Error opening \"" LOCK_DIR "/link\".\n");
	flock_on = 0;
	fclose(mfile);
	mfile = NULL;
	return;
    }

    /* Now that all files have been successfully opened, write all the	*/
    /* file locking information.					*/

    /* Contents of lock_buf (master lock file) go to lfile (local file) */

    fputs(lock_buf, lfile);
    fclose(lfile);
    lfile = NULL;

    /* Set environment variable MAGICID to hold name of master lock file */

    sprintf(magic_buff, "MAGICID=lock_%u%u", 
		name_time.tv_sec, name_time.tv_usec);
    if (getenv("MAGICID") == NULL)
	putenv(magic_buff);

    /* Now prepend current working directory to LOCK_DIR and save this	*/
    /* directory info to the master lock file.				*/

    getcwd(cur_path, 200);
    strcat(cur_path, "/" LOCK_DIR);
    fputs(cur_path, mfile);
    fclose(mfile);
    mfile = NULL;

    /* Finally, create and fill the internal f_list_head structure */

    upid = getpid();
    hst = gethostname(hstname, 100);
    MALLOC(flock_list *, f_list_ptr, sizeof(flock_list));
    f_list_head = f_list_ptr;
    f_list_head->ptr = 0;
    f_list_head->upid = upid;
    f_list_head->hostname = hstname;
    f_list_head->window = NULL;
    f_list_head->filename = "flock_list_head";
/*  TxPrintf("flock_init initialized for host %s user pid %d\n",
         f_list_head->hostname, f_list_head->upid);   */
}

/*-------------------------------------------------------------------------
 * flock_print --
 */

void
flock_print()

{
    flock_list *fptr;
    int n = 1;
    FILE *f;
    char buf[100];
    char *eof;

    fptr = f_list_ptr;
    TxPrintf("\nList of locked files: hostname: %s, pid: %d",
		fptr->hostname, fptr->upid);
    while(fptr->ptr)
    {
	fptr = fptr->ptr;
	TxPrintf("\n %d: %s\n", n, fptr->filename);
	n++;
	f = fopen(fptr->filename, "r");
	while(fgets(buf,100, f) != NULL)
	{
          TxPrintf("      %s", buf);
	}
	fclose(f);
	f = NULL;
    }
}

/*-------------------------------------------------------------------------
 * flock_close --
 */

int
flock_close(f, cellDef, filename)
    FILE *f;
    CellDef *cellDef;
    char *filename;
{
    /* At present there is nothing to be done here since closing of a file in Magic
     * really means nothing: Each .mag file is closed as soon as it is read in. When
     * it is saved, the file is opened, written, and closed. If the window which
     * displays a file is closed, and there are no other windows open with the file
     * somewhere in the hierarchy of the top cell in the window. then it might be
     * save to release the lock on window close. At present we do not attempt this
     * analysis.
     */

/*
    if (filename != NULL)
	TxPrintf("flock_close called for %s, ", filename);
    else
	TxPrintf("flock_close called with filename NULL, ");
    TxPrintf("cellDef->cd_name, cd_file %s, %s\n", cellDef->cd_name, cellDef->cd_file);
*/

}

/*-------------------------------------------------------------------------
 * flock_exit --
 */

int
flock_exit()
{
    flock_list *fptr = f_list_ptr;
    FILE *flk;
    int rms;

    if(fptr)
    {
	while(fptr->ptr)
	{
	    fptr = fptr->ptr;
	    if (unlink(fptr->filename))
	    {
		TxPrintf("Could not remove lock record %s. Please remove manually.\n",
			fptr->filename);
	    }
	    /* else
	    {
		TxPrintf("Lock record %s released.\n", fptr->filename);
	    } */
	}
    }
    unlink(lock_buf);   /* remove record in $CAD_HOME/flock	*/
}

/*-------------------------------------------------------------------------
 * flock_list_test --
 */

flock_list *
flock_list_test(realname)   /* If match return *ftpr else NULL  */
    char *realname;
{
    flock_list *fptr = f_list_ptr, *fnul = NULL;
    int dup;

    dup = 0;
    while(fptr->ptr)
    {
	fptr = fptr->ptr;
	if (!strcmp(realname, fptr->filename))
	{
	    dup = 1;
	    break;
	}
    }
    if (dup)
	return(fptr);
    else
	return(fnul);
}

/*-------------------------------------------------------------------------
 * flock_list_add --
 */

void
flock_list_add(realname)
    char *realname;
{
    flock_list *fptr = f_list_ptr;
    int dup;

    dup = 0;
    while(fptr->ptr)
    {
	fptr = fptr->ptr;
	if (!strcmp(realname, fptr->filename))
	{
	    dup = 1;
	    break;
	}
    }
    if (!dup)
    {
	MALLOC(flock_list *, fptr->ptr, sizeof(flock_list));
	fptr = fptr->ptr;
	fptr->ptr = 0;
	fptr->filename = realname;
    }
}

/*-------------------------------------------------------------------------
 * flock_test_set --
 */

FILE *
flock_test_set(filename, f)
    char *filename;
    FILE *f;
{
    FILE *flck;
    char *locfile;
    char lhost[120], ltime[40], cw_buff[200], luser[60], lpwd[120];
    int lpid;
    FILE *lrec;
    struct stat dirstat;
    int n, fl, dir;
    flock_list *fptr = f_list_ptr;
    struct tm *timeptr;
    time_t loctime;

    /* flock_test_set() may be called from dbwind/DBWprocs.c even if flock_on == 0 */

    if (flock_on == 0)
	return(f);

    /* Test for Lock set by another Magic and set lock or reopen file read-only */

    if (strpbrk (filename, "/\\") == 0)
    {
	MALLOC(char *, locfile, strlen(LOCK_DIR) + strlen(filename) +
			strlen("lock") + 4);
	sprintf(locfile, "%s/%s.%s", LOCK_DIR, filename, "lock" );
	/* TxPrintf("Lock file created: %s\n", locfile); */

	/* First test for flock directory. */

	dir = stat(LOCK_DIR, &dirstat);
	/* TxPrintf("dirstat.st_mode: %s %x -- %x\n", LOCK_DIR, dirstat.st_mode,
			S_IFDIR); */
	if ((dir == -1) || !(( dirstat.st_mode) & S_IFDIR))
	{
	    if (!mkdir(LOCK_DIR, 0777) < 0)
	    {
		TxError("Could not create flock_list directory.\n");
		flock_on = 0;
	    }
	    else
	    {
		TxPrintf("flock_list directory created.\n");
	    }
	}

        if ((fl = access(locfile, F_OK)) == 0)
        {
	    lrec = fopen(locfile, "r");
	    if (lrec != NULL)
	    {
		for (n = 1; n < 5; n++)
		{
		    fgets(cw_buff, 120, lrec);
		    /* TxPrintf("line %d: %s\n", n, cw_buff); */
		    if (n == 1) sscanf(cw_buff, "%*s%s", lhost);
		    if (n == 2) sscanf(cw_buff, "%*s%s", luser);
		    if (n == 3) sscanf(cw_buff, "%*s%s", lpwd);
		    if (n == 4) sscanf(cw_buff, "%*s%25c", ltime);
		}
		ltime[25]= 0;
		TxPrintf("File %s is locked by user %s on host: %s.\n"
			"  Directory: %s,\n  Time lock set: %s\n"
			"Read-only mode set.\n", filename, luser, lhost,
			lpwd, ltime);
		flock_flag = 1; /* causes set of cellDef->cd_flags |= CDNOEDIT;  */
		fclose(f);
	    }
	    else
	    {
		TxPrintf("File locking directory could not be opened. "
				"Fatal lock error.\n");
	    }
	    f = fopen(filename, "r");
	}
	else
	{
	    /* If file not locked by other user, write its lock file
	     * and add it to this lock list.
	     */

	    flock_list_add(locfile);
	    flck = fopen(locfile, "w");
	    if (flck == NULL)
	    {
		TxPrintf("fopen of lock_record %s failed!\n", locfile);
		exit;
	    }
	    loctime = time(NULL);
	    timeptr = localtime(&loctime);
	    getcwd(cw_buff, 200);
	    fprintf (flck, "host: %s pid: %d \nUSER: %s MAGICID: %s\nPWD: "
			"%s\nTDstamp: %s\n", fptr->hostname, fptr->upid,
			getenv("USER"), getenv("MAGICID"), cw_buff,
			asctime(timeptr));
	    fclose(flck);
	}
    }
    else
    {
	flock_flag = 2;
	TxPrintf("Non-local filename <%s> found flock_flag = 2\n", filename);
	f = fopen(filename, "r");
    }
    return(f);
}

/*-------------------------------------------------------------------------
 * flock_open --
 */

FILE *
flock_open(filename, ext, mode)
    char *filename;
    char *ext;
    char *mode;
{
    FILE *f;
    int ptr, n=0, dup=0;
    char *locfile, *recname;
    flock_list *fptr = f_list_ptr, *fnul =  NULL;

    /* TxPrintf("flock_open called for %s", filename); */
    flock_flag = 0;
    f = fopen(filename, mode);
    if ((f == NULL) || (ext == NULL) || (flock_on == 0))
    {
	return(f);
    }
    if(!strcmp(ext,".mag"))
    {
	if(flock_msg)
	{
	    TxPrintf("File locking is active.\n");
	    flock_msg = 0;
	}
	if(strpbrk(filename, "/\\") == 0)
	{
	    MALLOC (char *, recname, strlen(LOCK_DIR) + strlen(filename) +
			strlen("lock") + 4);
	    sprintf (recname, "%s/%s.%s", LOCK_DIR, filename, "lock" );
	    if (flock_list_test(recname) == fnul)
	    {
		/* File already in locked list for us or
		 * correct mode set after lock test.
		 */

		f = flock_test_set(filename, f);
	    }  
/*
	    fptr = f_list_ptr;
            TxPrintf("\nList of locked files: hostname: %s, pid: %d",
			fptr->hostname, fptr->upid);
	    while(fptr->ptr)
	    {
		fptr = fptr->ptr;
		TxPrintf("\n %d: %s", n, fptr->filename); n++;
	    }
	    TxPrintf("\n");
*/
	}
	else
	{
	    /* TxPrintf("File <%s> not local to this directory. Read-only set.\n",
			filename); */
	    if (mode != "r")
	    {
		fclose(f);
		f = fopen(filename, "r");
		flock_flag = 2;
	    }
	}
    }
    return(f);
}

/*----------------------------------------------------------------------------
 * flock_close_wlist --
 */

int
flock_close_wlist(w)
    MagWindow *w;
{
/*
    TxPrintf("flock_close_wlist called.\n");
*/
}

#endif
