ftp.nice.ch/pub/next/unix/developer/rcvs.0.7.9.s.tar.gz#/src/lock.c

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.3 kit.
 * 
 * Set Lock
 * 
 * Lock file support for CVS.
 */

#include "cvs.h"
/* rcvs: */
#include "rcvs.h"

#ifndef lint
static char rcsid[] = "@(#)lock.c 1.42 92/04/10";
#endif

extern char *ctime ();

#if __STDC__
static int readers_exist (char *repository);
static int set_lock (char *lockdir, int will_wait, char *repository);
static void set_lockers_name (struct stat *statp);
static int set_writelock_proc (Node * p);
static int unlock_proc (Node * p);
static int write_lock (char *repository);
static void unlock (char *repository);
static void lock_wait ();
#else
static int unlock_proc ();
static void unlock ();
static int set_writelock_proc ();
static int write_lock ();
static int readers_exist ();
static int set_lock ();
static void set_lockers_name ();
static void lock_wait ();
#endif				/* __STDC__ */

static char lockers_name[20];
static char *repository;
static char readlock[PATH_MAX], writelock[PATH_MAX];
static int cleanup_lckdir;
static List *locklist;

#define L_OK		0		/* success */
#define L_ERROR		1		/* error condition */
#define L_LOCK_OWNED	2		/* lock already owned by us */
#define L_LOCKED	3		/* 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);
	locklist = (List *) NULL;
    }

    /* rcvs: cleanup remote CVS locks */
    if (trace)
        fprintf (stderr, "-> lock_cleanup\n");
    if ((! rcvs_lock_phase3) && rcvs_native)
        rcvs_Clear_my_lock ();
}

/*
 * walklist proc for removing a list of locks
 */
static int
unlock_proc (p)
    Node *p;
{
    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);
	(void) unlink (tmp);
    }

    if (writelock[0] != '\0')
    {
	(void) sprintf (tmp, "%s/%s", repository, writelock);
	(void) unlink (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.
     */
    (void) sprintf (tmp, "%s/%s", repository, CVSLCK);
    if (stat (tmp, &sb) != -1 && sb.st_uid == geteuid () &&
	(writelock[0] != '\0' || (readlock[0] != '\0' && cleanup_lckdir)))
    {
	(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, "%s.%d", CVSRFL, getpid ());

    /* remember what we're locking (for lock_cleanup) */
    repository = xrepository;

    /* make sure we clean up on error */
    (void) SIG_register (SIGHUP, Lock_Cleanup);
    (void) SIG_register (SIGINT, Lock_Cleanup);
    (void) SIG_register (SIGQUIT, Lock_Cleanup);
    (void) SIG_register (SIGPIPE, Lock_Cleanup);
    (void) SIG_register (SIGTERM, Lock_Cleanup);

    /* make sure we can write the repository */
    (void) sprintf (tmp, "%s/%s.%d", xrepository, CVSTFL, getpid ());
    if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF)
    {
	error (0, errno, "cannot create read lock in repository `%s'",
	       xrepository);
	readlock[0] = '\0';
	(void) unlink (tmp);
	return (1);
    }
    (void) unlink (tmp);

    /* get the lock dir for our own */
    (void) sprintf (tmp, "%s/%s", xrepository, CVSLCK);
    if (set_lock (tmp, 1, xrepository) != 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 */
    (void) sprintf (tmp, "%s/%s", xrepository, CVSLCK);
    if (rmdir (tmp) < 0)
	error (0, errno, "failed to remove lock dir `%s'", tmp);

    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);

	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)
    Node *p;
{
    /* 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, "%s.%d", CVSWFL, getpid ());

    /* make sure we clean up on error */
    (void) SIG_register (SIGHUP, Lock_Cleanup);
    (void) SIG_register (SIGINT, Lock_Cleanup);
    (void) SIG_register (SIGQUIT, Lock_Cleanup);
    (void) SIG_register (SIGPIPE, Lock_Cleanup);
    (void) SIG_register (SIGTERM, Lock_Cleanup);

    /* make sure we can write the repository */
    (void) sprintf (tmp, "%s/%s.%d", repository, CVSTFL, getpid ());
    if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF)
    {
	error (0, errno, "cannot create write lock in repository `%s'",
	       repository);
	(void) unlink (tmp);
	return (L_ERROR);
    }
    (void) unlink (tmp);

    /* make sure the lock dir is ours (not necessarily unique to us!) */
    (void) sprintf (tmp, "%s/%s", repository, CVSLCK);
    status = set_lock (tmp, 0, repository);
    if (status == L_OK || status == L_LOCK_OWNED)
    {
	/* 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)
	    {
		if (rmdir (tmp) < 0)
		    error (0, errno, "failed to remove lock dir `%s'", tmp);
	    }

	    /* 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;

	    (void) unlink (tmp);
	    /* free the lock dir if we created it */
	    if (status == L_OK)
	    {
		(void) sprintf (tmp, "%s/%s", repository, CVSLCK);
		if (rmdir (tmp) < 0)
		    error (0, errno, "failed to remove lock dir `%s'", tmp);
	    }

	    /* 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 direct *dp;
    struct stat sb;
    CONST char *regex_err;
    int ret = 0;

#ifdef CVS_FUDGELOCKS
again:
#endif

    if ((dirp = opendir (repository)) == NULL)
	error (1, 0, "cannot open directory %s", repository);

    (void) sprintf (line, "^%s.*", CVSRFL);
    if ((regex_err = re_comp (line)) != NULL)
	error (1, 0, "%s", regex_err);

    while ((dp = readdir (dirp)) != NULL)
    {
	(void) sprintf (line, "%s/%s", repository, dp->d_name);
	if (re_exec (dp->d_name))
	{
#ifdef CVS_FUDGELOCKS
	    time_t now;

	    (void) time (&now);

	    /*
	     * 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 (stat (line, &sb) != -1)
	    {
		if (now >= (sb.st_ctime + CVSLCKAGE) && unlink (line) != -1)
		{
		    (void) closedir (dirp);
		    goto again;
		}
		set_lockers_name (&sb);
	    }
#else
	    if (stat (line, &sb) != -1)
		set_lockers_name (&sb);
#endif
	    ret = 1;
	    break;
	}
    }
    (void) 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%d", 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 (lockdir, will_wait, repository)
    char *lockdir;
    int will_wait;
    char *repository;
{
    struct stat sb;
#ifdef CVS_FUDGELOCKS
    time_t now;
#endif

    /*
     * 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 (mkdir (lockdir, 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 (lockdir, &sb) < 0)
	{
	    if (errno == ENOENT)
		continue;

	    error (0, errno, "couldn't stat lock directory `%s'", lockdir);
	    return (L_ERROR);
	}

	/*
	 * if we already own the lock, go ahead and return 1 which means it
	 * existed but we owned it
	 */
	if (sb.st_uid == geteuid () && !will_wait)
	    return (L_LOCK_OWNED);

#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 (lockdir) >= 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);
    }
}

/*
 * 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.