This is lock.c in view mode; [Download] [Up]
/*
* Copyright (c) 1992, Brian Berliner and Jeff Polk
* Copyright (c) 1989-1992, Brian Berliner
*
* You may distribute under the terms of the GNU General Public License as
* specified in the README file that comes with the CVS 1.4 kit.
*
* Set Lock
*
* Lock file support for CVS.
*/
#include "cvs.h"
#ifndef lint
static const char rcsid[] = "$CVSid: @(#)lock.c 1.50 94/09/30 $";
USE(rcsid)
#endif
extern char *ctime ();
static int readers_exist PROTO((char *repository));
static int set_lock PROTO((char *repository, int will_wait));
static void clear_lock PROTO((void));
static void set_lockers_name PROTO((struct stat *statp));
static int set_writelock_proc PROTO((Node * p, void *closure));
static int unlock_proc PROTO((Node * p, void *closure));
static int write_lock PROTO((char *repository));
static void unlock PROTO((char *repository));
static void lock_wait PROTO((char *repository));
static char lockers_name[20];
static char *repository;
static char readlock[PATH_MAX], writelock[PATH_MAX], masterlock[PATH_MAX];
static int cleanup_lckdir;
static List *locklist;
#define L_OK 0 /* success */
#define L_ERROR 1 /* error condition */
#define L_LOCKED 2 /* lock owned by someone else */
/*
* Clean up all outstanding locks
*/
void
Lock_Cleanup ()
{
/* clean up simple locks (if any) */
if (repository != NULL)
{
unlock (repository);
repository = (char *) NULL;
}
/* clean up multiple locks (if any) */
if (locklist != (List *) NULL)
{
(void) walklist (locklist, unlock_proc, NULL);
locklist = (List *) NULL;
}
}
/*
* walklist proc for removing a list of locks
*/
static int
unlock_proc (p, closure)
Node *p;
void *closure;
{
unlock (p->key);
return (0);
}
/*
* Remove the lock files (without complaining if they are not there),
*/
static void
unlock (repository)
char *repository;
{
char tmp[PATH_MAX];
struct stat sb;
if (readlock[0] != '\0')
{
(void) sprintf (tmp, "%s/%s", repository, readlock);
if (unlink (tmp) < 0 && errno != ENOENT)
error (0, errno, "failed to remove lock %s", tmp);
}
if (writelock[0] != '\0')
{
(void) sprintf (tmp, "%s/%s", repository, writelock);
if (unlink (tmp) < 0 && errno != ENOENT)
error (0, errno, "failed to remove lock %s", tmp);
}
/*
* Only remove the lock directory if it is ours, note that this does
* lead to the limitation that one user ID should not be committing
* files into the same Repository directory at the same time. Oh well.
*/
if (writelock[0] != '\0' || (readlock[0] != '\0' && cleanup_lckdir))
{
(void) sprintf (tmp, "%s/%s", repository, CVSLCK);
if (stat (tmp, &sb) != -1 && sb.st_uid == geteuid ())
{
(void) rmdir (tmp);
}
}
cleanup_lckdir = 0;
}
/*
* Create a lock file for readers
*/
int
Reader_Lock (xrepository)
char *xrepository;
{
int err = 0;
FILE *fp;
char tmp[PATH_MAX];
if (noexec)
return (0);
/* we only do one directory at a time for read locks! */
if (repository != NULL)
{
error (0, 0, "Reader_Lock called while read locks set - Help!");
return (1);
}
if (readlock[0] == '\0')
(void) sprintf (readlock,
#ifdef HAVE_LONG_FILE_NAMES
"%s.%s.%d", CVSRFL, hostname,
#else
"%s.%d", CVSRFL,
#endif
getpid ());
/* remember what we're locking (for lock_cleanup) */
repository = xrepository;
#ifdef BOGUS_UNLESS_PROVEN_OTHERWISE
/* make sure we can write the repository */
(void) sprintf (tmp,
#ifdef HAVE_LONG_FILE_NAMES
"%s/%s.%s.%d", xrepository, CVSTFL, hostname,
#else
"%s/%s.%d", xrepository, CVSTFL,
#endif
getpid());
if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF)
{
error (0, errno, "cannot create read lock in repository `%s'",
xrepository);
readlock[0] = '\0';
if (unlink (tmp) < 0 && errno != ENOENT)
error (0, errno, "failed to remove lock %s", tmp);
return (1);
}
if (unlink (tmp) < 0)
error (0, errno, "failed to remove lock %s", tmp);
#endif
/* get the lock dir for our own */
if (set_lock (xrepository, 1) != L_OK)
{
error (0, 0, "failed to obtain dir lock in repository `%s'",
xrepository);
readlock[0] = '\0';
return (1);
}
/* write a read-lock */
(void) sprintf (tmp, "%s/%s", xrepository, readlock);
if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF)
{
error (0, errno, "cannot create read lock in repository `%s'",
xrepository);
readlock[0] = '\0';
err = 1;
}
/* free the lock dir */
clear_lock();
return (err);
}
/*
* Lock a list of directories for writing
*/
static char *lock_error_repos;
static int lock_error;
int
Writer_Lock (list)
List *list;
{
if (noexec)
return (0);
/* We only know how to do one list at a time */
if (locklist != (List *) NULL)
{
error (0, 0, "Writer_Lock called while write locks set - Help!");
return (1);
}
for (;;)
{
/* try to lock everything on the list */
lock_error = L_OK; /* init for set_writelock_proc */
lock_error_repos = (char *) NULL; /* init for set_writelock_proc */
locklist = list; /* init for Lock_Cleanup */
(void) strcpy (lockers_name, "unknown");
(void) walklist (list, set_writelock_proc, NULL);
switch (lock_error)
{
case L_ERROR: /* Real Error */
Lock_Cleanup (); /* clean up any locks we set */
error (0, 0, "lock failed - giving up");
return (1);
case L_LOCKED: /* Someone already had a lock */
Lock_Cleanup (); /* clean up any locks we set */
lock_wait (lock_error_repos); /* sleep a while and try again */
continue;
case L_OK: /* we got the locks set */
return (0);
default:
error (0, 0, "unknown lock status %d in Writer_Lock",
lock_error);
return (1);
}
}
}
/*
* walklist proc for setting write locks
*/
static int
set_writelock_proc (p, closure)
Node *p;
void *closure;
{
/* if some lock was not OK, just skip this one */
if (lock_error != L_OK)
return (0);
/* apply the write lock */
lock_error_repos = p->key;
lock_error = write_lock (p->key);
return (0);
}
/*
* Create a lock file for writers returns L_OK if lock set ok, L_LOCKED if
* lock held by someone else or L_ERROR if an error occurred
*/
static int
write_lock (repository)
char *repository;
{
int status;
FILE *fp;
char tmp[PATH_MAX];
if (writelock[0] == '\0')
(void) sprintf (writelock,
#ifdef HAVE_LONG_FILE_NAMES
"%s.%s.%d", CVSWFL, hostname,
#else
"%s.%d", CVSWFL,
#endif
getpid());
#ifdef BOGUS_UNLESS_PROVEN_OTHERWISE
/* make sure we can write the repository */
(void) sprintf (tmp,
#ifdef HAVE_LONG_FILE_NAMES
"%s/%s.%s.%d", repository, CVSTFL, hostname,
#else
"%s/%s.%d", repository, CVSTFL,
#endif
getpid ());
if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF)
{
error (0, errno, "cannot create write lock in repository `%s'",
repository);
if (unlink (tmp) < 0 && errno != ENOENT)
error (0, errno, "failed to remove lock %s", tmp);
return (L_ERROR);
}
if (unlink (tmp) < 0)
error (0, errno, "failed to remove lock %s", tmp);
#endif
/* make sure the lock dir is ours (not necessarily unique to us!) */
status = set_lock (repository, 0);
if (status == L_OK)
{
/* we now own a writer - make sure there are no readers */
if (readers_exist (repository))
{
/* clean up the lock dir if we created it */
if (status == L_OK)
{
clear_lock();
}
/* indicate we failed due to read locks instead of error */
return (L_LOCKED);
}
/* write the write-lock file */
(void) sprintf (tmp, "%s/%s", repository, writelock);
if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF)
{
int xerrno = errno;
if (unlink (tmp) < 0 && errno != ENOENT)
error (0, errno, "failed to remove lock %s", tmp);
/* free the lock dir if we created it */
if (status == L_OK)
{
clear_lock();
}
/* return the error */
error (0, xerrno, "cannot create write lock in repository `%s'",
repository);
return (L_ERROR);
}
return (L_OK);
}
else
return (status);
}
/*
* readers_exist() returns 0 if there are no reader lock files remaining in
* the repository; else 1 is returned, to indicate that the caller should
* sleep a while and try again.
*/
static int
readers_exist (repository)
char *repository;
{
char line[MAXLINELEN];
DIR *dirp;
struct dirent *dp;
struct stat sb;
int ret = 0;
#ifdef CVS_FUDGELOCKS
again:
#endif
if ((dirp = opendir (repository)) == NULL)
error (1, 0, "cannot open directory %s", repository);
errno = 0;
while ((dp = readdir (dirp)) != NULL)
{
if (fnmatch (CVSRFLPAT, dp->d_name, 0) == 0)
{
#ifdef CVS_FUDGELOCKS
time_t now;
(void) time (&now);
#endif
(void) sprintf (line, "%s/%s", repository, dp->d_name);
if (stat (line, &sb) != -1)
{
#ifdef CVS_FUDGELOCKS
/*
* If the create time of the file is more than CVSLCKAGE
* seconds ago, try to clean-up the lock file, and if
* successful, re-open the directory and try again.
*/
if (now >= (sb.st_ctime + CVSLCKAGE) && unlink (line) != -1)
{
(void) closedir (dirp);
goto again;
}
#endif
set_lockers_name (&sb);
}
ret = 1;
break;
}
errno = 0;
}
if (errno != 0)
error (0, errno, "error reading directory %s", repository);
closedir (dirp);
return (ret);
}
/*
* Set the static variable lockers_name appropriately, based on the stat
* structure passed in.
*/
static void
set_lockers_name (statp)
struct stat *statp;
{
struct passwd *pw;
if ((pw = (struct passwd *) getpwuid (statp->st_uid)) !=
(struct passwd *) NULL)
{
(void) strcpy (lockers_name, pw->pw_name);
}
else
(void) sprintf (lockers_name, "uid%lu", (unsigned long) statp->st_uid);
}
/*
* Persistently tries to make the directory "lckdir",, which serves as a
* lock. If the create time on the directory is greater than CVSLCKAGE
* seconds old, just try to remove the directory.
*/
static int
set_lock (repository, will_wait)
char *repository;
int will_wait;
{
struct stat sb;
#ifdef CVS_FUDGELOCKS
time_t now;
#endif
(void) sprintf (masterlock, "%s/%s", repository, CVSLCK);
/*
* Note that it is up to the callers of set_lock() to arrange for signal
* handlers that do the appropriate things, like remove the lock
* directory before they exit.
*/
cleanup_lckdir = 0;
for (;;)
{
SIG_beginCrSect ();
if (CVS_MKDIR (masterlock, 0777) == 0)
{
cleanup_lckdir = 1;
SIG_endCrSect ();
return (L_OK);
}
SIG_endCrSect ();
if (errno != EEXIST)
{
error (0, errno,
"failed to create lock directory in repository `%s'",
repository);
return (L_ERROR);
}
/*
* stat the dir - if it is non-existent, re-try the loop since
* someone probably just removed it (thus releasing the lock)
*/
if (stat (masterlock, &sb) < 0)
{
if (errno == ENOENT)
continue;
error (0, errno, "couldn't stat lock directory `%s'", masterlock);
return (L_ERROR);
}
#ifdef CVS_FUDGELOCKS
/*
* If the create time of the directory is more than CVSLCKAGE seconds
* ago, try to clean-up the lock directory, and if successful, just
* quietly retry to make it.
*/
(void) time (&now);
if (now >= (sb.st_ctime + CVSLCKAGE))
{
if (rmdir (masterlock) >= 0)
continue;
}
#endif
/* set the lockers name */
set_lockers_name (&sb);
/* if he wasn't willing to wait, return an error */
if (!will_wait)
return (L_LOCKED);
lock_wait (repository);
}
}
/*
* Clear master lock. We don't have to recompute the lock name since
* clear_lock is never called except after a successful set_lock().
*/
static void
clear_lock()
{
if (rmdir (masterlock) < 0)
error (0, errno, "failed to remove lock dir `%s'", masterlock);
cleanup_lckdir = 0;
}
/*
* Print out a message that the lock is still held, then sleep a while.
*/
static void
lock_wait (repos)
char *repos;
{
time_t now;
(void) time (&now);
error (0, 0, "[%8.8s] waiting for %s's lock in %s", ctime (&now) + 11,
lockers_name, repos);
(void) sleep (CVSLCKSLEEP);
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.