diff options
Diffstat (limited to 'contrib/cvs/src/lock.c')
-rw-r--r-- | contrib/cvs/src/lock.c | 1166 |
1 files changed, 1166 insertions, 0 deletions
diff --git a/contrib/cvs/src/lock.c b/contrib/cvs/src/lock.c new file mode 100644 index 0000000..d8e78fa --- /dev/null +++ b/contrib/cvs/src/lock.c @@ -0,0 +1,1166 @@ +/* + * Copyright (C) 1986-2005 The Free Software Foundation, Inc. + * + * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, + * and others. + * + * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk + * Portions 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 source distribution. + * + * Set Lock + * + * Lock file support for CVS. + * + * $FreeBSD$ + */ + +/* The node Concurrency in doc/cvs.texinfo has a brief introduction to + how CVS locks function, and some of the user-visible consequences of + their existence. Here is a summary of why they exist (and therefore, + the consequences of hacking CVS to read a repository without creating + locks): + + There are two uses. One is the ability to prevent there from being + two writers at the same time. This is necessary for any number of + reasons (fileattr code, probably others). Commit needs to lock the + whole tree so that nothing happens between the up-to-date check and + the actual checkin. + + The second use is the ability to ensure that there is not a writer + and a reader at the same time (several readers are allowed). Reasons + for this are: + + * Readlocks ensure that once CVS has found a collection of rcs + files using Find_Names, the files will still exist when it reads + them (they may have moved in or out of the attic). + + * Readlocks provide some modicum of consistency, although this is + kind of limited--see the node Concurrency in cvs.texinfo. + + * Readlocks ensure that the RCS file does not change between + RCS_parse and RCS_reparsercsfile time. This one strikes me as + important, although I haven't thought up what bad scenarios might + be. + + * Readlocks ensure that we won't find the file in the state in + which it is in between the calls to add_rcs_file and RCS_checkin in + commit.c (when a file is being added). This state is a state in + which the RCS file parsing routines in rcs.c cannot parse the file. + + * Readlocks ensure that a reader won't try to look at a + half-written fileattr file (fileattr is not updated atomically). + + (see also the description of anonymous read-only access in + "Password authentication security" node in doc/cvs.texinfo). + + While I'm here, I'll try to summarize a few random suggestions + which periodically get made about how locks might be different: + + 1. Check for EROFS. Maybe useful, although in the presence of NFS + EROFS does *not* mean that the file system is unchanging. + + 2. Provide an option to disable locks for operations which only + read (see above for some of the consequences). + + 3. Have a server internally do the locking. Probably a good + long-term solution, and many people have been working hard on code + changes which would eventually make it possible to have a server + which can handle various connections in one process, but there is + much, much work still to be done before this is feasible. */ + +#include "cvs.h" +#include <assert.h> + +#ifdef HAVE_NANOSLEEP +# include "xtime.h" +#else /* HAVE_NANOSLEEP */ +# if !defined HAVE_USLEEP && defined HAVE_SELECT + /* use select as a workaround */ +# include "xselect.h" +# endif /* !defined HAVE_USLEEP && defined HAVE_SELECT */ +#endif /* !HAVE_NANOSLEEP */ + + +struct lock { + /* This is the directory in which we may have a lock named by the + readlock variable, a lock named by the writelock variable, and/or + a lock named CVSLCK. The storage is not allocated along with the + struct lock; it is allocated by the Reader_Lock caller or in the + case of writelocks, it is just a pointer to the storage allocated + for the ->key field. */ + char *repository; + + /* The name of the master lock dir. Usually CVSLCK. */ + const char *lockdirname; + + /* The full path to the lock dir, if we are currently holding it. + * + * This will be LOCKDIRNAME catted onto REPOSITORY. We waste a little + * space by storing it, but save a later malloc/free. + */ + char *lockdir; + + /* Note there is no way of knowing whether the readlock and writelock + exist. The code which sets the locks doesn't use SIG_beginCrSect + to set a flag like we do for CVSLCK. */ +}; + +static void remove_locks PROTO((void)); +static int readers_exist PROTO((char *repository)); +static int set_lock PROTO ((struct lock *lock, int will_wait)); +static void clear_lock PROTO ((struct lock *lock)); +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 ((struct lock *lock)); +static void lock_simple_remove PROTO ((struct lock *lock)); +static void lock_wait PROTO((char *repository)); +static void lock_obtained PROTO((char *repository)); + +/* Malloc'd array containing the username of the whoever has the lock. + Will always be non-NULL in the cases where it is needed. */ +static char *lockers_name; +/* Malloc'd array specifying name of a readlock within a directory. + Or NULL if none. */ +static char *readlock; +/* Malloc'd array specifying name of a writelock within a directory. + Or NULL if none. */ +static char *writelock; +/* Malloc'd array specifying the name of a CVSLCK file (absolute pathname). + Will always be non-NULL in the cases where it is used. */ +static List *locklist; + +#define L_OK 0 /* success */ +#define L_ERROR 1 /* error condition */ +#define L_LOCKED 2 /* lock owned by someone else */ + +/* This is the (single) readlock which is set by Reader_Lock. The + repository field is NULL if there is no such lock. */ +static struct lock global_readlock = {NULL, CVSLCK, NULL}; + +static struct lock global_history_lock = {NULL, CVSHISTORYLCK, NULL}; +static struct lock global_val_tags_lock = {NULL, CVSVALTAGSLCK, NULL}; + +/* List of locks set by lock_tree_for_write. This is redundant + with locklist, sort of. */ +static List *lock_tree_list; + +/* If we set locks with lock_dir_for_write, then locked_dir contains + the malloc'd name of the repository directory which we have locked. + locked_list is the same thing packaged into a list and is redundant + with locklist the same way that lock_tree_list is. */ +static char *locked_dir; +static List *locked_list; + +/* LockDir from CVSROOT/config. */ +char *lock_dir; + +static char *lock_name PROTO ((const char *repository, const char *name)); + +/* Return a newly malloc'd string containing the name of the lock for the + repository REPOSITORY and the lock file name within that directory + NAME. Also create the directories in which to put the lock file + if needed (if we need to, could save system call(s) by doing + that only if the actual operation fails. But for now we'll keep + things simple). */ +static char * +lock_name (repository, name) + const char *repository; + const char *name; +{ + char *retval; + const char *p; + char *q; + const char *short_repos; + mode_t save_umask = 0; + int saved_umask = 0; + + if (lock_dir == NULL) + { + /* This is the easy case. Because the lock files go directly + in the repository, no need to create directories or anything. */ + retval = xmalloc (strlen (repository) + strlen (name) + 10); + (void) sprintf (retval, "%s/%s", repository, name); + } + else + { + struct stat sb; + mode_t new_mode = 0; + + /* The interesting part of the repository is the part relative + to CVSROOT. */ + assert (current_parsed_root != NULL); + assert (current_parsed_root->directory != NULL); + assert (strncmp (repository, current_parsed_root->directory, + strlen (current_parsed_root->directory)) == 0); + short_repos = repository + strlen (current_parsed_root->directory) + 1; + + if (strcmp (repository, current_parsed_root->directory) == 0) + short_repos = "."; + else + assert (short_repos[-1] == '/'); + + retval = xmalloc (strlen (lock_dir) + + strlen (short_repos) + + strlen (name) + + 10); + strcpy (retval, lock_dir); + q = retval + strlen (retval); + *q++ = '/'; + + strcpy (q, short_repos); + + /* In the common case, where the directory already exists, let's + keep it to one system call. */ + if (CVS_STAT (retval, &sb) < 0) + { + /* If we need to be creating more than one directory, we'll + get the existence_error here. */ + if (!existence_error (errno)) + error (1, errno, "cannot stat directory %s", retval); + } + else + { + if (S_ISDIR (sb.st_mode)) + goto created; + else + error (1, 0, "%s is not a directory", retval); + } + + /* Now add the directories one at a time, so we can create + them if needed. + + The idea behind the new_mode stuff is that the directory we + end up creating will inherit permissions from its parent + directory (we re-set new_mode with each EEXIST). CVSUMASK + isn't right, because typically the reason for LockDir is to + use a different set of permissions. We probably want to + inherit group ownership also (but we don't try to deal with + that, some systems do it for us either always or when g+s is on). + + We don't try to do anything about the permissions on the lock + files themselves. The permissions don't really matter so much + because the locks will generally be removed by the process + which created them. */ + + if (CVS_STAT (lock_dir, &sb) < 0) + error (1, errno, "cannot stat %s", lock_dir); + new_mode = sb.st_mode; + save_umask = umask (0000); + saved_umask = 1; + + p = short_repos; + while (1) + { + while (!ISDIRSEP (*p) && *p != '\0') + ++p; + if (ISDIRSEP (*p)) + { + strncpy (q, short_repos, p - short_repos); + q[p - short_repos] = '\0'; + if (!ISDIRSEP (q[p - short_repos - 1]) + && CVS_MKDIR (retval, new_mode) < 0) + { + int saved_errno = errno; + if (saved_errno != EEXIST) + error (1, errno, "cannot make directory %s", retval); + else + { + if (CVS_STAT (retval, &sb) < 0) + error (1, errno, "cannot stat %s", retval); + new_mode = sb.st_mode; + } + } + ++p; + } + else + { + strcpy (q, short_repos); + if (CVS_MKDIR (retval, new_mode) < 0 + && errno != EEXIST) + error (1, errno, "cannot make directory %s", retval); + goto created; + } + } + created:; + + strcat (retval, "/"); + strcat (retval, name); + + if (saved_umask) + { + assert (umask (save_umask) == 0000); + saved_umask = 0; + } + } + return retval; +} + +/* + * Clean up all outstanding locks + */ +void +Lock_Cleanup () +{ + /* FIXME: error handling here is kind of bogus; we sometimes will call + error, which in turn can call us again. For the moment work around + this by refusing to reenter this function (this is a kludge). */ + /* FIXME-reentrancy: the workaround isn't reentrant. */ + static int in_lock_cleanup = 0; + + if (trace) + (void) fprintf (stderr, "%s-> Lock_Cleanup()\n", CLIENT_SERVER_STR); + + if (in_lock_cleanup) + return; + in_lock_cleanup = 1; + + remove_locks (); + + dellist (&lock_tree_list); + + if (locked_dir != NULL) + { + dellist (&locked_list); + free (locked_dir); + locked_dir = NULL; + locked_list = NULL; + } + + if (global_history_lock.repository) clear_history_lock (); + if (global_val_tags_lock.repository) clear_val_tags_lock (); + + in_lock_cleanup = 0; +} + +/* + * Remove locks without discarding the lock information + */ +static void +remove_locks () +{ + /* clean up simple locks (if any) */ + if (global_readlock.repository != NULL) + { + lock_simple_remove (&global_readlock); + global_readlock.repository = 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; +{ + lock_simple_remove (p->data); + return (0); +} + + + +/* Remove the lock files. */ +static void +lock_simple_remove (lock) + struct lock *lock; +{ + char *tmp; + + /* If readlock is set, the lock directory *might* have been created, but + since Reader_Lock doesn't use SIG_beginCrSect the way that set_lock + does, we don't know that. That is why we need to check for + existence_error here. */ + if (readlock != NULL) + { + tmp = lock_name (lock->repository, readlock); + if (CVS_UNLINK (tmp) < 0 && ! existence_error (errno)) + error (0, errno, "failed to remove lock %s", tmp); + free (tmp); + } + + /* If writelock is set, the lock directory *might* have been created, but + since write_lock doesn't use SIG_beginCrSect the way that set_lock + does, we don't know that. That is why we need to check for + existence_error here. */ + if (writelock != NULL) + { + tmp = lock_name (lock->repository, writelock); + if (CVS_UNLINK (tmp) < 0 && ! existence_error (errno)) + error (0, errno, "failed to remove lock %s", tmp); + free (tmp); + } + + clear_lock (lock); +} + + + +/* + * Create a lock file for readers + */ +int +Reader_Lock (xrepository) + char *xrepository; +{ + int err = 0; + FILE *fp; + char *tmp; + + if (trace) + (void) fprintf (stderr, "%s-> Reader_Lock(%s)\n", CLIENT_SERVER_STR, + xrepository); + + if (noexec || readonlyfs) + return 0; + + /* we only do one directory at a time for read locks! */ + if (global_readlock.repository != NULL) + { + error (0, 0, "Reader_Lock called while read locks set - Help!"); + return 1; + } + + if (readlock == NULL) + { + readlock = xmalloc (strlen (hostname) + sizeof (CVSRFL) + 40); + (void) sprintf (readlock, +#ifdef HAVE_LONG_FILE_NAMES + "%s.%s.%ld", CVSRFL, hostname, +#else + "%s.%ld", CVSRFL, +#endif + (long) getpid ()); + } + + /* remember what we're locking (for Lock_Cleanup) */ + global_readlock.repository = xrepository; + + /* get the lock dir for our own */ + if (set_lock (&global_readlock, 1) != L_OK) + { + error (0, 0, "failed to obtain dir lock in repository `%s'", + xrepository); + if (readlock != NULL) + free (readlock); + readlock = NULL; + /* We don't set global_readlock.repository to NULL. I think this + only works because recurse.c will give a fatal error if we return + a nonzero value. */ + return 1; + } + + /* write a read-lock */ + tmp = lock_name (xrepository, readlock); + if ((fp = CVS_FOPEN (tmp, "w+")) == NULL || fclose (fp) == EOF) + { + error (0, errno, "cannot create read lock in repository `%s'", + xrepository); + if (readlock != NULL) + free (readlock); + readlock = NULL; + err = 1; + } + free (tmp); + + /* free the lock dir */ + clear_lock (&global_readlock); + + return err; +} + + + +/* + * Lock a list of directories for writing + */ +static char *lock_error_repos; +static int lock_error; + +static int Writer_Lock PROTO ((List * list)); + +static int +Writer_Lock (list) + List *list; +{ + char *wait_repos; + + if (noexec) + return 0; + + if (readonlyfs) { + error (0, 0, "write lock failed - read-only repository"); + return (1); + } + + /* 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; + } + + wait_repos = NULL; + 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 */ + if (lockers_name != NULL) + free (lockers_name); + lockers_name = xstrdup ("unknown"); + + (void) walklist (list, set_writelock_proc, NULL); + + switch (lock_error) + { + case L_ERROR: /* Real Error */ + if (wait_repos != NULL) + free (wait_repos); + 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 */ + remove_locks (); /* clean up any locks we set */ + lock_wait (lock_error_repos); /* sleep a while and try again */ + wait_repos = xstrdup (lock_error_repos); + continue; + + case L_OK: /* we got the locks set */ + if (wait_repos != NULL) + { + lock_obtained (wait_repos); + free (wait_repos); + } + return 0; + + default: + if (wait_repos != NULL) + free (wait_repos); + 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->data); + 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 (lock) + struct lock *lock; +{ + int status; + FILE *fp; + char *tmp; + + if (trace) + (void) fprintf (stderr, "%s-> write_lock(%s)\n", + CLIENT_SERVER_STR, lock->repository); + + if (writelock == NULL) + { + writelock = xmalloc (strlen (hostname) + sizeof (CVSWFL) + 40); + (void) sprintf (writelock, +#ifdef HAVE_LONG_FILE_NAMES + "%s.%s.%ld", CVSWFL, hostname, +#else + "%s.%ld", CVSWFL, +#endif + (long) getpid()); + } + + /* make sure the lock dir is ours (not necessarily unique to us!) */ + status = set_lock (lock, 0); + if (status == L_OK) + { + /* we now own a writer - make sure there are no readers */ + if (readers_exist (lock->repository)) + { + /* clean up the lock dir if we created it */ + if (status == L_OK) + { + clear_lock (lock); + } + + /* indicate we failed due to read locks instead of error */ + return L_LOCKED; + } + + /* write the write-lock file */ + tmp = lock_name (lock->repository, writelock); + if ((fp = CVS_FOPEN (tmp, "w+")) == NULL || fclose (fp) == EOF) + { + int xerrno = errno; + + if ( CVS_UNLINK (tmp) < 0 && ! existence_error (errno)) + error (0, errno, "failed to remove lock %s", tmp); + + /* free the lock dir if we created it */ + if (status == L_OK) + { + clear_lock (lock); + } + + /* return the error */ + error (0, xerrno, "cannot create write lock in repository `%s'", + lock->repository); + free (tmp); + return L_ERROR; + } + free (tmp); + 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 *lockdir; + char *line; + DIR *dirp; + struct dirent *dp; + struct stat sb; + int ret; +#ifdef CVS_FUDGELOCKS + time_t now; + (void)time (&now); +#endif + + lockdir = lock_name (repository, ""); + + assert (lockdir != NULL); + + lockdir[strlen (lockdir) - 1] = '\0'; /* remove trailing slash */ + + do { + if ((dirp = CVS_OPENDIR (lockdir)) == NULL) + error (1, 0, "cannot open directory %s", lockdir); + + ret = 0; + errno = 0; + while ((dp = CVS_READDIR (dirp)) != NULL) + { + if (CVS_FNMATCH (CVSRFLPAT, dp->d_name, 0) == 0) + { + line = xmalloc (strlen (lockdir) + 1 + strlen (dp->d_name) + 1); + (void)sprintf (line, "%s/%s", lockdir, dp->d_name); + if (CVS_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) && + CVS_UNLINK (line) != -1) + { + free (line); + ret = -1; + break; + } +#endif + set_lockers_name (&sb); + } + else + { + /* If the file doesn't exist, it just means that it + * disappeared between the time we did the readdir and the + * time we did the stat. + */ + if (!existence_error (errno)) + error (0, errno, "cannot stat %s", line); + } + errno = 0; + free (line); + ret = 1; + break; + } + errno = 0; + } + if (errno != 0) + error (0, errno, "error reading directory %s", repository); + + CVS_CLOSEDIR (dirp); + } while (ret < 0); + + if (lockdir != NULL) + free (lockdir); + 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 (lockers_name != NULL) + free (lockers_name); + if ((pw = (struct passwd *)getpwuid (statp->st_uid)) != + (struct passwd *)NULL) + { + lockers_name = xstrdup (pw->pw_name); + } + else + { + lockers_name = xmalloc (20); + (void)sprintf (lockers_name, "uid%lu", (unsigned long) statp->st_uid); + } +} + + + +/* + * Persistently tries to make the directory "lckdir", which serves as a + * lock. + * + * #ifdef CVS_FUDGELOCKS + * If the create time on the directory is greater than CVSLCKAGE + * seconds old, just try to remove the directory. + * #endif + * + */ +static int +set_lock (lock, will_wait) + struct lock *lock; + int will_wait; +{ + int waited; + long us; + struct stat sb; + mode_t omask; + char *masterlock; + int status; +#ifdef CVS_FUDGELOCKS + time_t now; +#endif + + masterlock = lock_name (lock->repository, lock->lockdirname); + + /* + * 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. + */ + waited = 0; + us = 1; + for (;;) + { + status = -1; + omask = umask (cvsumask); + SIG_beginCrSect (); + if (CVS_MKDIR (masterlock, 0777) == 0) + { + lock->lockdir = masterlock; + SIG_endCrSect (); + status = L_OK; + if (waited) + lock_obtained (lock->repository); + goto after_sig_unblock; + } + SIG_endCrSect (); + after_sig_unblock: + (void) umask (omask); + if (status != -1) + goto done; + + if (errno != EEXIST) + { + error (0, errno, + "failed to create lock directory for `%s' (%s)", + lock->repository, masterlock); + status = L_ERROR; + goto done; + } + + /* Find out who owns the lock. If the lock directory is + non-existent, re-try the loop since someone probably just + removed it (thus releasing the lock). */ + if (CVS_STAT (masterlock, &sb) < 0) + { + if (existence_error (errno)) + continue; + + error (0, errno, "couldn't stat lock directory `%s'", masterlock); + status = L_ERROR; + goto done; + } + +#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 (CVS_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) + { + status = L_LOCKED; + goto done; + } + + /* if possible, try a very short sleep without a message */ + if (!waited && us < 1000) + { + us += us; +#if defined HAVE_NANOSLEEP + { + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = us * 1000; + (void)nanosleep (&ts, NULL); + continue; + } +#elif defined HAVE_USLEEP + (void)usleep (us); + continue; +#elif defined HAVE_SELECT + { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = us; + (void)select (0, (fd_set *)NULL, (fd_set *)NULL, (fd_set *)NULL, &tv); + continue; + } +#endif + } + + lock_wait (lock->repository); + waited = 1; + } +done: + if (!lock->lockdir) free (masterlock); + return status; +} + + + +/* + * Clear master lock. + * + * INPUTS + * lock The lock information. + * + * OUTPUTS + * Sets LOCK->lockdir to NULL after removing the directory it names and + * freeing the storage. + * + * ASSUMPTIONS + * If we own the master lock directory, its name is stored in LOCK->lockdir. + * We may free LOCK->lockdir. + * + */ +static void +clear_lock (lock) + struct lock *lock; +{ + SIG_beginCrSect (); + if (lock->lockdir) + { + if (CVS_RMDIR (lock->lockdir) < 0) + error (0, errno, "failed to remove lock dir `%s'", lock->lockdir); + free (lock->lockdir); + lock->lockdir = NULL; + } + SIG_endCrSect (); +} + + + +/* + * Print out a message that the lock is still held, then sleep a while. + */ +static void +lock_wait (repos) + char *repos; +{ + time_t now; + char *msg; + struct tm *tm_p; + + (void) time (&now); + tm_p = gmtime (&now); + msg = xmalloc (100 + strlen (lockers_name) + strlen (repos)); + sprintf (msg, "[%8.8s] waiting for %s's lock in %s", + (tm_p ? asctime (tm_p) : ctime (&now)) + 11, + lockers_name, repos); + error (0, 0, "%s", msg); + /* Call cvs_flusherr to ensure that the user sees this message as + soon as possible. */ + cvs_flusherr (); + free (msg); + (void) sleep (CVSLCKSLEEP); +} + +/* + * Print out a message when we obtain a lock. + */ +static void +lock_obtained (repos) + char *repos; +{ + time_t now; + char *msg; + struct tm *tm_p; + + (void) time (&now); + tm_p = gmtime (&now); + msg = xmalloc (100 + strlen (repos)); + sprintf (msg, "[%8.8s] obtained lock in %s", + (tm_p ? asctime (tm_p) : ctime (&now)) + 11, repos); + error (0, 0, "%s", msg); + /* Call cvs_flusherr to ensure that the user sees this message as + soon as possible. */ + cvs_flusherr (); + free (msg); +} + + + +static int lock_filesdoneproc PROTO ((void *callerdat, int err, + const char *repository, + const char *update_dir, + List *entries)); + +/* + * Create a list of repositories to lock + */ +/* ARGSUSED */ +static int +lock_filesdoneproc (callerdat, err, repository, update_dir, entries) + void *callerdat; + int err; + const char *repository; + const char *update_dir; + List *entries; +{ + Node *p; + + p = getnode (); + p->type = LOCK; + p->key = xstrdup (repository); + p->data = xmalloc (sizeof (struct lock)); + ((struct lock *)p->data)->repository = p->key; + ((struct lock *)p->data)->lockdirname = CVSLCK; + ((struct lock *)p->data)->lockdir = NULL; + + /* FIXME-KRP: this error condition should not simply be passed by. */ + if (p->key == NULL || addnode (lock_tree_list, p) != 0) + freenode (p); + return (err); +} + +void +lock_tree_for_write (argc, argv, local, which, aflag) + int argc; + char **argv; + int local; + int which; + int aflag; +{ + /* + * Run the recursion processor to find all the dirs to lock and lock all + * the dirs + */ + lock_tree_list = getlist (); + start_recursion ((FILEPROC) NULL, lock_filesdoneproc, + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, argc, + argv, local, which, aflag, CVS_LOCK_NONE, + (char *) NULL, 0, (char *) NULL); + sortlist (lock_tree_list, fsortcmp); + if (Writer_Lock (lock_tree_list) != 0) + error (1, 0, "lock failed - giving up"); +} + +/* Lock a single directory in REPOSITORY. It is OK to call this if + a lock has been set with lock_dir_for_write; the new lock will replace + the old one. If REPOSITORY is NULL, don't do anything. */ +void +lock_dir_for_write (repository) + char *repository; +{ + if (repository != NULL + && (locked_dir == NULL + || strcmp (locked_dir, repository) != 0)) + { + Node *node; + + if (locked_dir != NULL) + Lock_Cleanup (); + + locked_dir = xstrdup (repository); + locked_list = getlist (); + node = getnode (); + node->type = LOCK; + node->key = xstrdup (repository); + node->data = xmalloc (sizeof (struct lock)); + ((struct lock *)node->data)->repository = node->key; + ((struct lock *)node->data)->lockdirname = CVSLCK; + ((struct lock *)node->data)->lockdir = NULL; + + (void) addnode (locked_list, node); + Writer_Lock (locked_list); + } +} + + + +/* This is the internal implementation behind history_lock & val_tags_lock. It + * gets a write lock for the history or val-tags file. + * + * RETURNS + * true, on success + * false, on error + */ +static int internal_lock PROTO ((struct lock *lock, const char *xrepository)); +static int +internal_lock (lock, xrepository) + struct lock *lock; + const char *xrepository; +{ + /* remember what we're locking (for Lock_Cleanup) */ + assert (!lock->repository); + lock->repository = xmalloc (strlen (xrepository) + sizeof (CVSROOTADM) + 2); + sprintf (lock->repository, "%s/%s", xrepository, CVSROOTADM); + + /* get the lock dir for our own */ + if (set_lock (lock, 1) != L_OK) + { + if (!really_quiet) + error (0, 0, "failed to obtain history lock in repository `%s'", + xrepository); + + return 0; + } + + return 1; +} + + + +/* This is the internal implementation behind history_lock & val_tags_lock. It + * removes the write lock for the history or val-tags file, when it exists. + */ +static void internal_clear_lock PROTO((struct lock *lock)); +static void +internal_clear_lock (lock) + struct lock *lock; +{ + SIG_beginCrSect (); + if (lock->repository) + { + free (lock->repository); + lock->repository = NULL; + } + SIG_endCrSect (); + + clear_lock (lock); +} + + + +/* Lock the CVSROOT/history file for write. + */ +int +history_lock (xrepository) + const char *xrepository; +{ + return internal_lock (&global_history_lock, xrepository); +} + + + +/* Remove the CVSROOT/history lock, if it exists. + */ +void +clear_history_lock () +{ + internal_clear_lock (&global_history_lock); +} + + + +/* Lock the CVSROOT/val-tags file for write. + */ +int +val_tags_lock (xrepository) + const char *xrepository; +{ + return internal_lock (&global_val_tags_lock, xrepository); +} + + + +/* Remove the CVSROOT/val-tags lock, if it exists. + */ +void +clear_val_tags_lock () +{ + internal_clear_lock (&global_val_tags_lock); +} |