/* * 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 #include #ifdef __STDC__ #include #endif #include #include #include #include #include #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"; /* First test for master flock_list directory before creating a local one */ /* environment variable CAD_HOME overrides, otherwise do tilde expansion */ /* on "~cad". */ if ((cad_path = getenv("CAD_HOME")) == NULL) { cad_path = cad_buf; if (PaConvertTilde (&normal_cad, &cad_path, 400) == -1) { flock_on = 0; TxError ("No CAD_HOME set, and could not convert ~cad to a directory.\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