diff options
Diffstat (limited to 'contrib/cvs/src/lock.c')
-rw-r--r-- | contrib/cvs/src/lock.c | 497 |
1 files changed, 327 insertions, 170 deletions
diff --git a/contrib/cvs/src/lock.c b/contrib/cvs/src/lock.c index 7e35aed..f84eccf 100644 --- a/contrib/cvs/src/lock.c +++ b/contrib/cvs/src/lock.c @@ -10,40 +10,160 @@ * Lock file support for CVS. */ +/* 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 "rcs -i" and the 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 a means to put the cvs locks in some directory apart from + the repository (CVSROOT/locks; a -l option in modules; etc.). + + 3. Provide an option to disable locks for operations which only + read (see above for some of the consequences). + + 4. 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. + + 5. Like #4 but use shared memory or something so that the servers + merely need to all be on the same machine. This is a much smaller + change to CVS (it functions much like #2; shared memory might be an + unneeded complication although it presumably would be faster). */ + #include "cvs.h" +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; + /* Do we have a lock named CVSLCK? */ + int have_lckdir; + /* 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((char *repository, int will_wait)); -static void clear_lock PROTO((void)); +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((char *repository)); -static void lock_simple_remove PROTO((char *repository)); +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 int Check_Owner PROTO((char *lockdir)); - -static char lockers_name[20]; -static char *repository; -static char readlock[PATH_MAX], writelock[PATH_MAX], masterlock[PATH_MAX]; -static int cleanup_lckdir; +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 char *masterlock; 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; + +/* 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; + /* * Clean up all outstanding locks */ void Lock_Cleanup () { + remove_locks (); + + dellist (&lock_tree_list); + + if (locked_dir != NULL) + { + dellist (&locked_list); + free (locked_dir); + locked_dir = NULL; + locked_list = NULL; + } +} + +/* + * Remove locks without discarding the lock information + */ +static void +remove_locks () +{ /* clean up simple locks (if any) */ - if (repository != NULL) + if (global_readlock.repository != NULL) { - lock_simple_remove (repository); - repository = (char *) NULL; + lock_simple_remove (&global_readlock); + global_readlock.repository = NULL; } /* clean up multiple locks (if any) */ @@ -62,87 +182,57 @@ unlock_proc (p, closure) Node *p; void *closure; { - lock_simple_remove (p->key); + lock_simple_remove ((struct lock *)p->data); return (0); } -/* - * Remove the lock files (without complaining if they are not there), - */ +/* Remove the lock files. */ static void -lock_simple_remove (repository) - char *repository; +lock_simple_remove (lock) + struct lock *lock; { - char tmp[PATH_MAX]; + char *tmp; - if (readlock[0] != '\0') + /* 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) { - (void) sprintf (tmp, "%s/%s", repository, readlock); - if (unlink (tmp) < 0 && ! existence_error (errno)) + tmp = xmalloc (strlen (lock->repository) + strlen (readlock) + 10); + (void) sprintf (tmp, "%s/%s", lock->repository, readlock); + if ( CVS_UNLINK (tmp) < 0 && ! existence_error (errno)) error (0, errno, "failed to remove lock %s", tmp); + free (tmp); } - if (writelock[0] != '\0') + /* 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) { - (void) sprintf (tmp, "%s/%s", repository, writelock); - if (unlink (tmp) < 0 && ! existence_error (errno)) + tmp = xmalloc (strlen (lock->repository) + strlen (writelock) + 10); + (void) sprintf (tmp, "%s/%s", lock->repository, writelock); + if ( CVS_UNLINK (tmp) < 0 && ! existence_error (errno)) error (0, errno, "failed to remove lock %s", tmp); + free (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)) + if (lock->have_lckdir) { - (void) sprintf (tmp, "%s/%s", repository, CVSLCK); - if (Check_Owner(tmp)) - { -#ifdef AFSCVS - char rmuidlock[PATH_MAX]; - sprintf(rmuidlock, "rm -f %s/uidlock%d", tmp, geteuid() ); - system(rmuidlock); -#endif - (void) rmdir (tmp); - } + tmp = xmalloc (strlen (lock->repository) + sizeof (CVSLCK) + 10); + (void) sprintf (tmp, "%s/%s", lock->repository, CVSLCK); + SIG_beginCrSect (); + if (CVS_RMDIR (tmp) < 0) + error (0, errno, "failed to remove lock dir %s", tmp); + lock->have_lckdir = 0; + SIG_endCrSect (); + free (tmp); } - cleanup_lckdir = 0; } /* - * Check the owner of a lock. Returns 1 if we own it, 0 otherwise. - */ -static int -Check_Owner(lockdir) - char *lockdir; -{ - struct stat sb; - -#ifdef AFSCVS - /* In the Andrew File System (AFS), user ids from stat don't match - those from geteuid(). The AFSCVS code can deal with either AFS or - non-AFS repositories; the non-AFSCVS code is faster. */ - char uidlock[PATH_MAX]; - - /* Check if the uidlock is in the lock directory */ - sprintf(uidlock, "%s/uidlock%d", lockdir, geteuid() ); - if( stat(uidlock, &sb) != -1) - return 1; /* The file exists, therefore we own the lock */ - else - return 0; /* The file didn't exist or some other error. - * Assume that we don't own it. - */ -#else - if (stat (lockdir, &sb) != -1 && sb.st_uid == geteuid ()) - return 1; - else - return 0; -#endif -} /* end Check_Owner() */ - - -/* * Create a lock file for readers */ int @@ -151,51 +241,63 @@ Reader_Lock (xrepository) { int err = 0; FILE *fp; - char tmp[PATH_MAX]; + char *tmp; if (noexec) return (0); /* we only do one directory at a time for read locks! */ - if (repository != NULL) + if (global_readlock.repository != NULL) { error (0, 0, "Reader_Lock called while read locks set - Help!"); return (1); } - if (readlock[0] == '\0') - (void) sprintf (readlock, + if (readlock == NULL) + { + readlock = xmalloc (strlen (hostname) + sizeof (CVSRFL) + 40); + (void) sprintf (readlock, #ifdef HAVE_LONG_FILE_NAMES - "%s.%s.%ld", CVSRFL, hostname, + "%s.%s.%ld", CVSRFL, hostname, #else - "%s.%ld", CVSRFL, + "%s.%ld", CVSRFL, #endif - (long) getpid ()); + (long) getpid ()); + } - /* remember what we're locking (for lock_cleanup) */ - repository = xrepository; + /* remember what we're locking (for Lock_Cleanup) */ + global_readlock.repository = xrepository; /* get the lock dir for our own */ - if (set_lock (xrepository, 1) != L_OK) + if (set_lock (&global_readlock, 1) != L_OK) { error (0, 0, "failed to obtain dir lock in repository `%s'", xrepository); - readlock[0] = '\0'; + 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 = xmalloc (strlen (xrepository) + strlen (readlock) + 10); (void) sprintf (tmp, "%s/%s", xrepository, readlock); - if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF) + if ((fp = CVS_FOPEN (tmp, "w+")) == NULL || fclose (fp) == EOF) { error (0, errno, "cannot create read lock in repository `%s'", xrepository); - readlock[0] = '\0'; + if (readlock != NULL) + free (readlock); + readlock = NULL; err = 1; } + free (tmp); /* free the lock dir */ - clear_lock(); + clear_lock (&global_readlock); return (err); } @@ -205,10 +307,15 @@ Reader_Lock (xrepository) */ static char *lock_error_repos; static int lock_error; -int + +static int Writer_Lock PROTO ((List * list)); + +static int Writer_Lock (list) List *list; { + char *wait_repos; + if (noexec) return (0); @@ -219,32 +326,45 @@ Writer_Lock (list) 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 */ - (void) strcpy (lockers_name, "unknown"); + 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 */ - Lock_Cleanup (); /* clean up any locks we set */ + 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); @@ -266,7 +386,7 @@ set_writelock_proc (p, closure) /* apply the write lock */ lock_error_repos = p->key; - lock_error = write_lock (p->key); + lock_error = write_lock ((struct lock *)p->data); return (0); } @@ -275,33 +395,36 @@ set_writelock_proc (p, closure) * lock held by someone else or L_ERROR if an error occurred */ static int -write_lock (repository) - char *repository; +write_lock (lock) + struct lock *lock; { int status; FILE *fp; - char tmp[PATH_MAX]; + char *tmp; - if (writelock[0] == '\0') + if (writelock == NULL) + { + writelock = xmalloc (strlen (hostname) + sizeof (CVSWFL) + 40); (void) sprintf (writelock, #ifdef HAVE_LONG_FILE_NAMES - "%s.%s.%ld", CVSWFL, hostname, + "%s.%s.%ld", CVSWFL, hostname, #else - "%s.%ld", CVSWFL, + "%s.%ld", CVSWFL, #endif - (long) getpid()); + (long) getpid()); + } /* make sure the lock dir is ours (not necessarily unique to us!) */ - status = set_lock (repository, 0); + status = set_lock (lock, 0); if (status == L_OK) { /* we now own a writer - make sure there are no readers */ - if (readers_exist (repository)) + if (readers_exist (lock->repository)) { /* clean up the lock dir if we created it */ if (status == L_OK) { - clear_lock(); + clear_lock (lock); } /* indicate we failed due to read locks instead of error */ @@ -309,25 +432,28 @@ write_lock (repository) } /* write the write-lock file */ - (void) sprintf (tmp, "%s/%s", repository, writelock); - if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF) + tmp = xmalloc (strlen (lock->repository) + strlen (writelock) + 10); + (void) sprintf (tmp, "%s/%s", lock->repository, writelock); + if ((fp = CVS_FOPEN (tmp, "w+")) == NULL || fclose (fp) == EOF) { int xerrno = errno; - if (unlink (tmp) < 0 && ! existence_error (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(); + clear_lock (lock); } /* return the error */ error (0, xerrno, "cannot create write lock in repository `%s'", - repository); + lock->repository); + free (tmp); return (L_ERROR); } + free (tmp); return (L_OK); } else @@ -353,7 +479,7 @@ readers_exist (repository) again: #endif - if ((dirp = opendir (repository)) == NULL) + if ((dirp = CVS_OPENDIR (repository)) == NULL) error (1, 0, "cannot open directory %s", repository); errno = 0; @@ -368,7 +494,7 @@ again: line = xmalloc (strlen (repository) + strlen (dp->d_name) + 5); (void) sprintf (line, "%s/%s", repository, dp->d_name); - if (stat (line, &sb) != -1) + if ( CVS_STAT (line, &sb) != -1) { #ifdef CVS_FUDGELOCKS /* @@ -376,7 +502,7 @@ again: * 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) + if (now >= (sb.st_ctime + CVSLCKAGE) && CVS_UNLINK (line) != -1) { (void) closedir (dirp); free (line); @@ -418,13 +544,18 @@ set_lockers_name (statp) { struct passwd *pw; + if (lockers_name != NULL) + free (lockers_name); if ((pw = (struct passwd *) getpwuid (statp->st_uid)) != (struct passwd *) NULL) { - (void) strcpy (lockers_name, pw->pw_name); + lockers_name = xstrdup (pw->pw_name); } else + { + lockers_name = xmalloc (20); (void) sprintf (lockers_name, "uid%lu", (unsigned long) statp->st_uid); + } } /* @@ -433,24 +564,29 @@ set_lockers_name (statp) * seconds old, just try to remove the directory. */ static int -set_lock (repository, will_wait) - char *repository; +set_lock (lock, will_wait) + struct lock *lock; int will_wait; { + int waited; struct stat sb; mode_t omask; #ifdef CVS_FUDGELOCKS time_t now; #endif - (void) sprintf (masterlock, "%s/%s", repository, CVSLCK); + if (masterlock != NULL) + free (masterlock); + masterlock = xmalloc (strlen (lock->repository) + sizeof (CVSLCK) + 10); + (void) sprintf (masterlock, "%s/%s", lock->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; + waited = 0; + lock->have_lckdir = 0; for (;;) { int status = -1; @@ -458,27 +594,11 @@ set_lock (repository, will_wait) SIG_beginCrSect (); if (CVS_MKDIR (masterlock, 0777) == 0) { -#ifdef AFSCVS - char uidlock[PATH_MAX]; - FILE *fp; - - sprintf(uidlock, "%s/uidlock%d", masterlock, geteuid() ); - if ((fp = fopen(uidlock, "w+")) == NULL) - { - /* We failed to create the uidlock, - so rm masterlock and leave */ - rmdir(masterlock); - SIG_endCrSect (); - status = L_ERROR; - goto out; - } - - /* We successfully created the uid lock, so close the file */ - fclose(fp); -#endif - cleanup_lckdir = 1; + lock->have_lckdir = 1; SIG_endCrSect (); status = L_OK; + if (waited) + lock_obtained (lock->repository); goto out; } SIG_endCrSect (); @@ -491,15 +611,14 @@ set_lock (repository, will_wait) { error (0, errno, "failed to create lock directory in repository `%s'", - repository); + lock->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) + /* 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; @@ -517,13 +636,7 @@ set_lock (repository, will_wait) (void) time (&now); if (now >= (sb.st_ctime + CVSLCKAGE)) { -#ifdef AFSCVS - /* Remove the uidlock first */ - char rmuidlock[PATH_MAX]; - sprintf(rmuidlock, "rm -f %s/uidlock%d", masterlock, geteuid() ); - system(rmuidlock); -#endif - if (rmdir (masterlock) >= 0) + if (CVS_RMDIR (masterlock) >= 0) continue; } #endif @@ -534,7 +647,8 @@ set_lock (repository, will_wait) /* if he wasn't willing to wait, return an error */ if (!will_wait) return (L_LOCKED); - lock_wait (repository); + lock_wait (lock->repository); + waited = 1; } } @@ -543,17 +657,14 @@ set_lock (repository, will_wait) * clear_lock is never called except after a successful set_lock(). */ static void -clear_lock() +clear_lock (lock) + struct lock *lock; { -#ifdef AFSCVS - /* Remove the uidlock first */ - char rmuidlock[PATH_MAX]; - sprintf(rmuidlock, "rm -f %s/uidlock%d", masterlock, geteuid() ); - system(rmuidlock); -#endif - if (rmdir (masterlock) < 0) + SIG_beginCrSect (); + if (CVS_RMDIR (masterlock) < 0) error (0, errno, "failed to remove lock dir `%s'", masterlock); - cleanup_lckdir = 0; + lock->have_lckdir = 0; + SIG_endCrSect (); } /* @@ -568,30 +679,54 @@ lock_wait (repos) (void) time (&now); error (0, 0, "[%8.8s] waiting for %s's lock in %s", ctime (&now) + 11, lockers_name, repos); + /* Call cvs_flusherr to ensure that the user sees this message as + soon as possible. */ + cvs_flusherr (); (void) sleep (CVSLCKSLEEP); } + +/* + * Print out a message when we obtain a lock. + */ +static void +lock_obtained (repos) + char *repos; +{ + time_t now; + + (void) time (&now); + error (0, 0, "[%8.8s] obtained lock in %s", ctime (&now) + 11, repos); + /* Call cvs_flusherr to ensure that the user sees this message as + soon as possible. */ + cvs_flusherr (); +} -static int lock_filesdoneproc PROTO ((int err, char *repository, - char *update_dir)); +static int lock_filesdoneproc PROTO ((void *callerdat, int err, + char *repository, char *update_dir, + List *entries)); static int fsortcmp PROTO((const Node * p, const Node * q)); -static List *lock_tree_list; - /* * Create a list of repositories to lock */ /* ARGSUSED */ static int -lock_filesdoneproc (err, repository, update_dir) +lock_filesdoneproc (callerdat, err, repository, update_dir, entries) + void *callerdat; int err; char *repository; 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)->have_lckdir = 0; + /* FIXME-KRP: this error condition should not simply be passed by. */ if (p->key == NULL || addnode (lock_tree_list, p) != 0) freenode (p); @@ -623,17 +758,39 @@ lock_tree_for_write (argc, argv, local, aflag) */ lock_tree_list = getlist (); err = start_recursion ((FILEPROC) NULL, lock_filesdoneproc, - (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, argc, - argv, local, W_LOCAL, aflag, 0, (char *) NULL, 0, - 0); + (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, argc, + argv, local, W_LOCAL, aflag, 0, (char *) NULL, 0); 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_tree_cleanup () +lock_dir_for_write (repository) + char *repository; { - Lock_Cleanup (); - dellist (&lock_tree_list); + 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)->have_lckdir = 0; + + (void) addnode (locked_list, node); + Writer_Lock (locked_list); + } } |