/* * 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" 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 lock_simple_remove PROTO((char *repository)); 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 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) { lock_simple_remove (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; { lock_simple_remove (p->key); return (0); } /* * Remove the lock files (without complaining if they are not there), */ static void lock_simple_remove (repository) char *repository; { char tmp[PATH_MAX]; if (readlock[0] != '\0') { (void) sprintf (tmp, "%s/%s", repository, readlock); if (unlink (tmp) < 0 && ! existence_error (errno)) error (0, errno, "failed to remove lock %s", tmp); } if (writelock[0] != '\0') { (void) sprintf (tmp, "%s/%s", repository, writelock); if (unlink (tmp) < 0 && ! existence_error (errno)) 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 (Check_Owner(tmp)) { #ifdef AFSCVS char rmuidlock[PATH_MAX]; sprintf(rmuidlock, "rm -f %s/uidlock%d", tmp, geteuid() ); system(rmuidlock); #endif (void) rmdir (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 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.%ld", CVSRFL, hostname, #else "%s.%ld", CVSRFL, #endif (long) getpid ()); /* remember what we're locking (for lock_cleanup) */ repository = xrepository; /* 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.%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 (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 && ! 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(); } /* 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; 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 line = xmalloc (strlen (repository) + strlen (dp->d_name) + 5); (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); free (line); goto again; } #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); 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; mode_t omask; #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 (;;) { int status = -1; omask = umask (cvsumask); 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; SIG_endCrSect (); status = L_OK; goto out; } SIG_endCrSect (); out: (void) umask (omask); if (status != -1) return status; 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 (existence_error (errno)) 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)) { #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) 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() { #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) 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); } static int lock_filesdoneproc PROTO ((int err, char *repository, char *update_dir)); 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) int err; char *repository; char *update_dir; { Node *p; p = getnode (); p->type = LOCK; p->key = xstrdup (repository); /* 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); } /* * compare two lock list nodes (for sort) */ static int fsortcmp (p, q) const Node *p; const Node *q; { return (strcmp (p->key, q->key)); } void lock_tree_for_write (argc, argv, local, aflag) int argc; char **argv; int local; int aflag; { int err; /* * Run the recursion processor to find all the dirs to lock and lock all * the dirs */ 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); sortlist (lock_tree_list, fsortcmp); if (Writer_Lock (lock_tree_list) != 0) error (1, 0, "lock failed - giving up"); } void lock_tree_cleanup () { Lock_Cleanup (); dellist (&lock_tree_list); }