#include "cvs.h"
struct lock {
const char *repository;
char *file1;
#ifdef LOCK_COMPATIBILITY
char *file2;
#endif
const char *lockdirname;
char *lockdir;
bool free_repository;
};
static void remove_locks (void);
static int set_lock (struct lock *lock, int will_wait);
static void clear_lock (struct lock *lock);
static void set_lockers_name (struct stat *statp);
static char *lockers_name;
static char *readlock;
static char *writelock;
static char *promotablelock;
static List *locklist;
#define L_OK 0
#define L_ERROR 1
#define L_LOCKED 2
#ifdef LOCK_COMPATIBILITY
static struct lock global_readlock = {NULL, NULL, NULL, CVSLCK, NULL, false};
static struct lock global_writelock = {NULL, NULL, NULL, CVSLCK, NULL, false};
static struct lock global_history_lock = {NULL, NULL, NULL, CVSHISTORYLCK,
NULL, false};
static struct lock global_val_tags_lock = {NULL, NULL, NULL, CVSVALTAGSLCK,
NULL, false};
#else
static struct lock global_readlock = {NULL, NULL, CVSLCK, NULL, false};
static struct lock global_writelock = {NULL, NULL, CVSLCK, NULL, false};
static struct lock global_history_lock = {NULL, NULL, CVSHISTORYLCK, NULL,
false};
static struct lock global_val_tags_lock = {NULL, NULL, CVSVALTAGSLCK, NULL,
false};
#endif
static List *lock_tree_list;
static char *
lock_name (const char *repository, const char *name)
{
char *retval;
const char *p;
char *q;
const char *short_repos;
mode_t save_umask = 0000;
int saved_umask = 0;
TRACE (TRACE_FLOW, "lock_name (%s, %s)",
repository ? repository : "(null)", name ? name : "(null)");
if (!config->lock_dir)
{
assert (name != NULL);
assert (repository != NULL);
retval = Xasprintf ("%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 (config->lock_dir)
+ strlen (short_repos)
+ strlen (name)
+ 10);
strcpy (retval, config->lock_dir);
q = retval + strlen (retval);
*q++ = '/';
strcpy (q, short_repos);
if (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 (stat (config->lock_dir, &sb) < 0)
error (1, errno, "cannot stat %s", config->lock_dir);
new_mode = sb.st_mode;
save_umask = umask (0000);
saved_umask = 1;
p = short_repos;
while (1)
{
while (!ISSLASH (*p) && *p != '\0')
++p;
if (ISSLASH (*p))
{
strncpy (q, short_repos, p - short_repos);
q[p - short_repos] = '\0';
if (!ISSLASH (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 (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;
}
static void
remove_lock_files (struct lock *lock, bool free_repository)
{
TRACE (TRACE_FLOW, "remove_lock_files (%s)", lock->repository);
if (lock->file1)
{
char *tmp = lock->file1;
lock->file1 = NULL;
if (CVS_UNLINK (tmp) < 0 && ! existence_error (errno))
error (0, errno, "failed to remove lock %s", tmp);
free (tmp);
}
#ifdef LOCK_COMPATIBILITY
if (lock->file2)
{
char *tmp = lock->file2;
lock->file2 = NULL;
if (CVS_UNLINK (tmp) < 0 && ! existence_error (errno))
error (0, errno, "failed to remove lock %s", tmp);
free (tmp);
}
#endif
clear_lock (lock);
if (free_repository)
{
SIG_beginCrSect ();
if (lock->free_repository)
{
free ((char *)lock->repository);
lock->free_repository = false;
}
lock->repository = NULL;
SIG_endCrSect ();
}
}
void
Simple_Lock_Cleanup (void)
{
TRACE (TRACE_FUNCTION, "Simple_Lock_Cleanup()");
SIG_beginCrSect();
if (global_readlock.repository != NULL)
remove_lock_files (&global_readlock, true);
SIG_endCrSect();
SIG_beginCrSect();
if (global_writelock.repository != NULL)
remove_lock_files (&global_writelock, true);
SIG_endCrSect();
SIG_beginCrSect();
if (global_history_lock.repository)
remove_lock_files (&global_history_lock, true);
SIG_endCrSect();
SIG_beginCrSect();
if (global_val_tags_lock.repository)
remove_lock_files (&global_val_tags_lock, true);
SIG_endCrSect();
}
void
Lock_Cleanup (void)
{
TRACE (TRACE_FUNCTION, "Lock_Cleanup()");
remove_locks ();
SIG_beginCrSect();
dellist (&lock_tree_list);
SIG_endCrSect();
}
static int
unlock_proc (Node *p, void *closure)
{
remove_lock_files (p->data, false);
return 0;
}
static void
remove_locks (void)
{
TRACE (TRACE_FLOW, "remove_locks()");
Simple_Lock_Cleanup ();
SIG_beginCrSect();
if (locklist != NULL)
{
List *tmp = locklist;
locklist = NULL;
walklist (tmp, unlock_proc, NULL);
}
SIG_endCrSect();
}
static void
set_readlock_name (void)
{
if (readlock == NULL)
{
readlock = Xasprintf (
#ifdef HAVE_LONG_FILE_NAMES
"%s.%s.%ld", CVSRFL, hostname,
#else
"%s.%ld", CVSRFL,
#endif
(long) getpid ());
}
}
int
Reader_Lock (char *xrepository)
{
int err = 0;
FILE *fp;
TRACE (TRACE_FUNCTION, "Reader_Lock(%s)", xrepository);
if (noexec || readonlyfs)
return 0;
if (global_readlock.repository != NULL)
{
error (0, 0, "Reader_Lock called while read locks set - Help!");
return 1;
}
set_readlock_name ();
global_readlock.repository = xstrdup (xrepository);
global_readlock.free_repository = true;
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;
}
global_readlock.file1 = lock_name (xrepository, readlock);
if ((fp = CVS_FOPEN (global_readlock.file1, "w+")) == NULL
|| fclose (fp) == EOF)
{
error (0, errno, "cannot create read lock in repository `%s'",
xrepository);
err = 1;
}
clear_lock (&global_readlock);
return err;
}
static int
lock_exists (const char *repository, const char *filepat, const char *ignore)
{
char *lockdir;
char *line;
DIR *dirp;
struct dirent *dp;
struct stat sb;
int ret;
#ifdef CVS_FUDGELOCKS
time_t now;
(void)time (&now);
#endif
TRACE (TRACE_FLOW, "lock_exists (%s, %s, %s)",
repository, filepat, ignore ? ignore : "(null)");
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 (filepat, dp->d_name, 0) == 0)
{
if (ignore && !fncmp (ignore, dp->d_name))
continue;
line = Xasprintf ("%s/%s", lockdir, dp->d_name);
if (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 int
readers_exist (const char *repository)
{
TRACE (TRACE_FLOW, "readers_exist (%s)", repository);
return lock_exists (repository, CVSRFLPAT,
#ifdef LOCK_COMPATIBILITY
findnode (locklist, repository) ? readlock :
#endif
NULL);
}
static int
promotable_exists (const char *repository)
{
TRACE (TRACE_FLOW, "promotable_exists (%s)", repository);
return lock_exists (repository, CVSPFLPAT, promotablelock);
}
static char *lock_error_repos;
static int lock_error;
static int
set_promotable_lock (struct lock *lock)
{
int status;
FILE *fp;
TRACE (TRACE_FUNCTION, "set_promotable_lock(%s)",
lock->repository ? lock->repository : "(null)");
if (promotablelock == NULL)
{
promotablelock = Xasprintf (
#ifdef HAVE_LONG_FILE_NAMES
"%s.%s.%ld", CVSPFL, hostname,
#else
"%s.%ld", CVSPFL,
#endif
(long) getpid());
}
status = set_lock (lock, 0);
if (status == L_OK)
{
if (promotable_exists (lock->repository))
{
clear_lock (lock);
return L_LOCKED;
}
lock->file1 = lock_name (lock->repository, promotablelock);
if ((fp = CVS_FOPEN (lock->file1, "w+")) == NULL || fclose (fp) == EOF)
{
int xerrno = errno;
if (CVS_UNLINK (lock->file1) < 0 && ! existence_error (errno))
error (0, errno, "failed to remove lock %s", lock->file1);
clear_lock (lock);
error (0, xerrno,
"cannot create promotable lock in repository `%s'",
lock->repository);
return L_ERROR;
}
#ifdef LOCK_COMPATIBILITY
set_readlock_name ();
lock->file2 = lock_name (lock->repository, readlock);
if ((fp = CVS_FOPEN (lock->file2, "w+")) == NULL || fclose (fp) == EOF)
{
int xerrno = errno;
if ( CVS_UNLINK (lock->file2) < 0 && ! existence_error (errno))
error (0, errno, "failed to remove lock %s", lock->file2);
clear_lock (lock);
lock->file2 = NULL;
remove_lock_files (lock, false);
error (0, xerrno,
"cannot create read lock in repository `%s'",
lock->repository);
return L_ERROR;
}
#endif
clear_lock (lock);
return L_OK;
}
else
return status;
}
static int
set_promotablelock_proc (Node *p, void *closure)
{
if (lock_error != L_OK)
return 0;
lock_error_repos = p->key;
lock_error = set_promotable_lock ((struct lock *)p->data);
return 0;
}
static void
lock_wait (const char *repos)
{
time_t now;
char *msg;
struct tm *tm_p;
(void) time (&now);
tm_p = gmtime (&now);
msg = Xasprintf ("[%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 (const char *repos)
{
time_t now;
char *msg;
struct tm *tm_p;
(void) time (&now);
tm_p = gmtime (&now);
msg = Xasprintf ("[%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_list_promotably (List *list)
{
char *wait_repos;
TRACE (TRACE_FLOW, "lock_list_promotably ()");
if (noexec)
return 0;
if (readonlyfs) {
error (0, 0,
"promotable lock failed.\n\
WARNING: Read-only repository access mode selected via `cvs -R'.\n\
Attempting to write to a read-only filesystem is not allowed.");
return 1;
}
if (locklist != NULL)
{
error (0, 0,
"lock_list_promotably called while promotable locks set - Help!");
return 1;
}
wait_repos = NULL;
for (;;)
{
lock_error = L_OK;
lock_error_repos = NULL;
locklist = list;
if (lockers_name != NULL)
free (lockers_name);
lockers_name = xstrdup ("unknown");
(void) walklist (list, set_promotablelock_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 lock_list_promotably",
lock_error);
return 1;
}
}
}
static void
set_lockers_name (struct stat *statp)
{
struct passwd *pw;
if (lockers_name != NULL)
free (lockers_name);
pw = (struct passwd *) getpwuid (statp->st_uid);
if (pw != NULL)
lockers_name = xstrdup (pw->pw_name);
else
lockers_name = Xasprintf ("uid%lu", (unsigned long) statp->st_uid);
}
static int
set_lock (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
TRACE (TRACE_FLOW, "set_lock (%s, %d)",
lock->repository ? lock->repository : "(null)", will_wait);
masterlock = lock_name (lock->repository, lock->lockdirname);
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;
}
if (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
(void) time (&now);
if (now >= (sb.st_ctime + CVSLCKAGE))
{
if (CVS_RMDIR (masterlock) >= 0)
continue;
}
#endif
set_lockers_name (&sb);
if (!will_wait)
{
status = L_LOCKED;
goto done;
}
if (!waited && us < 1000)
{
us += us;
{
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = us * 1000;
(void)nanosleep (&ts, NULL);
continue;
}
}
lock_wait (lock->repository);
waited = 1;
}
done:
if (!lock->lockdir)
free (masterlock);
return status;
}
static void
clear_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 ();
}
static int
lock_filesdoneproc (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)->file1 = NULL;
#ifdef LOCK_COMPATIBILITY
((struct lock *)p->data)->file2 = NULL;
#endif
((struct lock *)p->data)->lockdirname = CVSLCK;
((struct lock *)p->data)->lockdir = NULL;
((struct lock *)p->data)->free_repository = false;
if (p->key == NULL || addnode (lock_tree_list, p) != 0)
freenode (p);
return err;
}
void
lock_tree_promotably (int argc, char **argv, int local, int which, int aflag)
{
TRACE (TRACE_FUNCTION, "lock_tree_promotably (%d, argv, %d, %d, %d)",
argc, local, which, aflag);
lock_tree_list = getlist ();
start_recursion
(NULL, lock_filesdoneproc,
NULL, NULL, NULL, argc,
argv, local, which, aflag, CVS_LOCK_NONE,
NULL, 0, NULL );
sortlist (lock_tree_list, fsortcmp);
if (lock_list_promotably (lock_tree_list) != 0)
error (1, 0, "lock failed - giving up");
}
void
lock_dir_for_write (const char *repository)
{
int waiting = 0;
TRACE (TRACE_FLOW, "lock_dir_for_write (%s)", repository);
if (repository != NULL
&& (global_writelock.repository == NULL
|| !strcmp (global_writelock.repository, repository)))
{
if (writelock == NULL)
{
writelock = Xasprintf (
#ifdef HAVE_LONG_FILE_NAMES
"%s.%s.%ld", CVSWFL, hostname,
#else
"%s.%ld", CVSWFL,
#endif
(long) getpid());
}
if (global_writelock.repository != NULL)
remove_lock_files (&global_writelock, true);
global_writelock.repository = xstrdup (repository);
global_writelock.free_repository = true;
for (;;)
{
FILE *fp;
if (set_lock (&global_writelock, 1) != L_OK)
error (1, 0, "failed to obtain write lock in repository `%s'",
repository);
if (readers_exist (repository)
|| promotable_exists (repository))
{
clear_lock (&global_writelock);
lock_wait (repository);
waiting = 1;
continue;
}
if (waiting)
lock_obtained (repository);
global_writelock.file1 = lock_name (global_writelock.repository,
writelock);
if ((fp = CVS_FOPEN (global_writelock.file1, "w+")) == NULL
|| fclose (fp) == EOF)
{
int xerrno = errno;
if (CVS_UNLINK (global_writelock.file1) < 0
&& !existence_error (errno))
{
error (0, errno, "failed to remove write lock %s",
global_writelock.file1);
}
clear_lock (&global_writelock);
error (1, xerrno,
"cannot create write lock in repository `%s'",
global_writelock.repository);
}
if (locklist)
{
Node *p = findnode (locklist, repository);
if (p)
{
remove_lock_files (p->data, true);
delnode (p);
}
}
break;
}
}
}
static inline int
internal_lock (struct lock *lock, const char *xrepository)
{
assert (!lock->repository);
lock->repository = Xasprintf ("%s/%s", xrepository, CVSROOTADM);
lock->free_repository = true;
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;
}
int
history_lock (const char *xrepository)
{
return internal_lock (&global_history_lock, xrepository);
}
void
clear_history_lock ()
{
remove_lock_files (&global_history_lock, true);
}
int
val_tags_lock (const char *xrepository)
{
return internal_lock (&global_val_tags_lock, xrepository);
}
void
clear_val_tags_lock ()
{
remove_lock_files (&global_val_tags_lock, true);
}