#include "cvs.h"
#include <assert.h>
#ifdef HAVE_NANOSLEEP
# include "xtime.h"
#else
# if !defined HAVE_USLEEP && defined HAVE_SELECT
# include "xselect.h"
# endif
#endif
struct lock {
char *repository;
int have_lckdir;
};
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));
static char *lockers_name;
static char *readlock;
static char *writelock;
static char *masterlock;
static List *locklist;
#define L_OK 0
#define L_ERROR 1
#define L_LOCKED 2
static struct lock global_readlock;
static List *lock_tree_list;
static char *locked_dir;
static List *locked_list;
char *lock_dir;
static char *lock_name PROTO ((char *repository, char *name));
static char *
lock_name (repository, name)
char *repository;
char *name;
{
char *retval;
char *p;
char *q;
char *short_repos;
mode_t save_umask;
int saved_umask = 0;
if (lock_dir == NULL)
{
retval = xmalloc (strlen (repository) + strlen (name) + 10);
(void) sprintf (retval, "%s/%s", repository, name);
}
else
{
struct stat sb;
mode_t new_mode = 0;
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);
if (CVS_STAT (retval, &sb) < 0)
{
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);
}
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;
}
void
Lock_Cleanup ()
{
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;
}
in_lock_cleanup = 0;
}
static void
remove_locks ()
{
if (global_readlock.repository != NULL)
{
lock_simple_remove (&global_readlock);
global_readlock.repository = NULL;
}
if (locklist != (List *) NULL)
{
(void) walklist (locklist, unlock_proc, NULL);
locklist = (List *) NULL;
}
}
static int
unlock_proc (p, closure)
Node *p;
void *closure;
{
lock_simple_remove (p->data);
return (0);
}
static void
lock_simple_remove (lock)
struct lock *lock;
{
char *tmp;
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 != 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);
}
if (lock->have_lckdir)
{
tmp = lock_name (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);
}
}
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;
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 ());
}
global_readlock.repository = xrepository;
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;
return 1;
}
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);
clear_lock (&global_readlock);
return err;
}
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);
}
if (locklist != (List *) NULL)
{
error (0, 0, "Writer_Lock called while write locks set - Help!");
return 1;
}
wait_repos = NULL;
for (;;)
{
lock_error = L_OK;
lock_error_repos = (char *) NULL;
locklist = list;
if (lockers_name != NULL)
free (lockers_name);
lockers_name = xstrdup ("unknown");
(void) walklist (list, set_writelock_proc, NULL);
switch (lock_error)
{
case L_ERROR:
if (wait_repos != NULL)
free (wait_repos);
Lock_Cleanup ();
error (0, 0, "lock failed - giving up");
return 1;
case L_LOCKED:
remove_locks ();
lock_wait (lock_error_repos);
wait_repos = xstrdup (lock_error_repos);
continue;
case L_OK:
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;
}
}
}
static int
set_writelock_proc (p, closure)
Node *p;
void *closure;
{
if (lock_error != L_OK)
return 0;
lock_error_repos = p->key;
lock_error = write_lock (p->data);
return 0;
}
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());
}
status = set_lock (lock, 0);
if (status == L_OK)
{
if (readers_exist (lock->repository))
{
if (status == L_OK)
{
clear_lock (lock);
}
return L_LOCKED;
}
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);
if (status == L_OK)
{
clear_lock (lock);
}
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;
}
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, "");
lockdir[strlen (lockdir) - 1] = '\0';
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 (now >= (sb.st_ctime + CVSLCKAGE) &&
CVS_UNLINK (line) != -1)
{
free (line);
ret = -1;
break;
}
#endif
set_lockers_name (&sb);
}
else
{
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;
}
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);
}
}
static int
set_lock (lock, will_wait)
struct lock *lock;
int will_wait;
{
int waited;
long us;
struct stat sb;
mode_t omask;
#ifdef CVS_FUDGELOCKS
time_t now;
#endif
if (masterlock != NULL)
free (masterlock);
masterlock = lock_name (lock->repository, CVSLCK);
waited = 0;
us = 1;
lock->have_lckdir = 0;
for (;;)
{
int status = -1;
omask = umask (cvsumask);
SIG_beginCrSect ();
if (CVS_MKDIR (masterlock, 0777) == 0)
{
lock->have_lckdir = 1;
SIG_endCrSect ();
status = L_OK;
if (waited)
lock_obtained (lock->repository);
goto out;
}
SIG_endCrSect ();
out:
(void) umask (omask);
if (status != -1)
return status;
if (errno != EEXIST)
{
error (0, errno,
"failed to create lock directory for `%s' (%s)",
lock->repository, masterlock);
return (L_ERROR);
}
if (CVS_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
(void) time (&now);
if (now >= (sb.st_ctime + CVSLCKAGE))
{
if (CVS_RMDIR (masterlock) >= 0)
continue;
}
#endif
set_lockers_name (&sb);
if (!will_wait)
return (L_LOCKED);
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;
}
}
static void
clear_lock (lock)
struct lock *lock;
{
SIG_beginCrSect ();
if (CVS_RMDIR (masterlock) < 0)
error (0, errno, "failed to remove lock dir `%s'", masterlock);
lock->have_lckdir = 0;
SIG_endCrSect ();
}
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);
cvs_flusherr ();
free (msg);
(void) sleep (CVSLCKSLEEP);
}
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);
cvs_flusherr ();
free (msg);
}
static int lock_filesdoneproc PROTO ((void *callerdat, int err,
const char *repository,
const char *update_dir,
List *entries));
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)->have_lckdir = 0;
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;
{
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");
}
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)->have_lckdir = 0;
(void) addnode (locked_list, node);
Writer_Lock (locked_list);
}
}