/* -*-Mode: C++;-*-
* PRCS - The Project Revision Control System
* Copyright (C) 1997 Josh MacDonald
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id: lock.cc 1.10.1.1.1.11.1.6 Sun, 25 May 1997 21:10:06 -0700 jmacd $
*/
extern "C" {
#include <time.h>
#include <fcntl.h>
#include "maketime.h"
}
#include "prcs.h"
#include "misc.h"
#include "utils.h"
#include "lock.h"
#define LOCK_FILE_MODE 0666
/* This file was once implemented using fcntl(), I still have the code,
* but since NFS sucks, I don't even include it anymore. */
/*
* The number of seconds the process waits between stat calls to check
* on the status of the lock.
*/
#define TRIAL_WAIT_PERIOD 1
long get_file_age(const char* file)
{
FILE* lock_file = fopen(file, "r");
char line_buf[1024];
time_t youngest = 0;
if(!lock_file)
goto done;
if(!fgets(line_buf, 1024, lock_file))
goto done;
while(fgets(line_buf, 1024, lock_file)) {
const char* ptr = line_buf;
int spaces = 0;
char c;
if(line_buf[0] == ' ')
continue;
/* this scan depends on the format of the file, it looks for the
* 5th space and assumes that is a time. */
while((c = *ptr++) != 0) {
if(c == ' ')
spaces += 1;
if(spaces == 5) {
time_t this_age = str2time(ptr, 0, 0);
if(this_age > youngest)
youngest = this_age;
break;
}
}
}
done:
if (lock_file)
fclose (lock_file);
return time(NULL) - youngest;
}
/*
* chill until the lock is available.
*/
int chilling_out(const char* waitfile, int wait_type)
{
long age = get_file_age(waitfile);
if(age < 0) {
return LOCK_AVAILABLE;
} else if(age > STALE_LOCK_TIMEOUT) {
return LOCK_TRY_STEAL;
} else {
if(wait_type == LOCK_WAIT) {
struct stat buf;
long sleeptime = STALE_LOCK_TIMEOUT - age;
while(sleeptime > 0) {
sleeptime -= TRIAL_WAIT_PERIOD - sleep(TRIAL_WAIT_PERIOD);
if(stat(waitfile, &buf) < 0)
return LOCK_AVAILABLE;
}
return LOCK_TRY_STEAL;
} else {
return LOCK_TRY_AGAIN;
}
}
}
int AdvisoryLock::write_lock(int wait_type)
{
int chill;
struct stat buf;
if(locktype == WRITE_LOCK)
return LOCK_SUCCESS;
ASSERT(locktype != READ_LOCK, "unlock first");
while (true) {
while((wrfd = open(wrlockname, O_RDWR|O_CREAT|O_EXCL, LOCK_FILE_MODE)) < 0) {
if(errno == EEXIST) {
chill = chilling_out(wrlockname, wait_type);
if(chill != LOCK_AVAILABLE)
return chill;
} else {
prcserror << "Error opening lock file "
<< squote(wrlockname) << " for write lock" << perror;
return LOCK_FAIL;
}
}
if(locktype != TRYING_READ_LOCK && stat(rdlockname, &buf) >= 0) {
if(close(wrfd) < 0) {
prcserror << "Write failed on write lock file" << perror;
return LOCK_FAIL;
}
if(unlink(wrlockname) < 0 && errno != ENOENT) {
prcserror << "Unlink failed on write lock file" << perror;
return LOCK_FAIL;
}
chill = chilling_out(rdlockname, wait_type);
if(chill != LOCK_AVAILABLE)
return chill;
} else {
break;
}
}
if(!write_write_owner_notice())
return LOCK_FAIL;
if(close(wrfd) < 0) {
prcserror << "Write failed on read lock file " << squote(wrlockname) << perror;
return LOCK_FAIL;
}
locktype = WRITE_LOCK;
return LOCK_SUCCESS;
}
int AdvisoryLock::read_lock(int wait_type)
{
int trywrite;
if(locktype == READ_LOCK) return LOCK_SUCCESS;
ASSERT(locktype != WRITE_LOCK, "unlock first");
locktype = TRYING_READ_LOCK;
trywrite = write_lock(wait_type);
if(trywrite != LOCK_SUCCESS) {
locktype = NO_LOCK;
return trywrite;
}
if((rdfd = open(rdlockname, O_RDWR|O_CREAT|O_EXCL, LOCK_FILE_MODE)) < 0) {
if(errno != EEXIST) {
prcserror << "Error opening lock file " << squote(rdlockname)
<< " for read lock" << perror;
locktype = NO_LOCK;
return LOCK_FAIL;
} else if((rdfd = open(rdlockname, O_RDWR|O_CREAT, LOCK_FILE_MODE)) < 0) {
prcserror << "Error opening lock file " << squote(rdlockname)
<< " for read lock" << perror;
locktype = NO_LOCK;
return LOCK_FAIL;
}
} else /* file was created */ {
if(write(rdfd, LOCK_HEADER, LOCK_LENGTH) < 0) {
prcserror << "Write failed on lock file " << squote(rdlockname) << perror;
locktype = NO_LOCK;
return LOCK_FAIL;
}
}
if(!write_read_owner_notice()) {
locktype = NO_LOCK;
return LOCK_FAIL;
}
locktype = READ_LOCK;
if(unlink(wrlockname) < 0 && errno != ENOENT) {
prcserror << "Unlink failed on lock file " << squote(wrlockname) << perror;
locktype = NO_LOCK;
return LOCK_FAIL;
}
if(close(rdfd) < 0) {
prcserror << "Close failed on lock file " << squote(rdlockname) << perror;
locktype = NO_LOCK;
return LOCK_FAIL;
}
return LOCK_SUCCESS;
}
bool AdvisoryLock::unlock()
{
if(locktype == NO_LOCK) {
return true;
} else if(locktype == WRITE_LOCK) {
if(unlink(wrlockname) < 0 && errno != ENOENT) {
prcserror << "Unlink failed on lock file " << squote(wrlockname) << perror;
return false;
}
} else if(locktype == READ_LOCK) {
char buf[256];
int trywrite;
char* p;
bool lastlock = true;
int c;
memset(buf, ' ', rd_lock_banner_len);
locktype = TRYING_READ_LOCK;
trywrite = write_lock_nowait();
while(true) {
/* There are two PRCS specific warnings here, cause I'm
not sure how to handle trouble obtaining the lock
while unlocking a read lock */
if(trywrite == LOCK_SUCCESS) {
break;
} else if(trywrite == LOCK_TRY_AGAIN) {
prcsinfo << "Read lock modifier lock unavailable, waiting to unlock the repository"
<< dotendl;
} else /* if(trywrite == LOCK_FAIL || trywrite == LOCK_TRY_STEAL) */ {
prcserror << "Unrecoverable error preventing unlocking of the repository, "
"you can manually remove locks " << squote(wrlockname) << " and "
<< squote(rdlockname) << dotendl;
return false;
}
trywrite = write_lock_wait();
}
locktype = READ_LOCK;
if((rdfd = open(rdlockname, O_RDWR, LOCK_FILE_MODE)) < 0) {
prcserror << "Open failed while unlocking read lock file "
<< squote(rdlockname) << ", the lock may have been stolen" << perror;
return unlink_writelock();
} else if(lseek(rdfd, rd_lock_banner_offset, SEEK_SET) < 0) {
prcserror << "Lseek failed unlocking lock file" << perror;
return unlink_writelock();
} else if(write(rdfd, buf, rd_lock_banner_len - 1) < 0) {
prcserror << "Write failed unlocking lock file" << perror;
return unlink_writelock();
} else if(lseek(rdfd, LOCK_LENGTH, SEEK_SET) < 0) {
prcserror << "Lseek failed unlocking lock file" << perror;
return unlink_writelock();
}
while (true) {
do {
c = read(rdfd, buf, 256);
} while (c < 0 && errno == EINTR);
if (c <= 0) /* error ignored */
break;
p = buf;
while(p < buf + c){
if(!isspace(*p))
lastlock = false;
p += 1;
}
}
if(close(rdfd) < 0) {
prcserror << "Close failed unlocking lock file" << perror;
return unlink_writelock();
}
if(lastlock && unlink(rdlockname) < 0 && errno != ENOENT) {
prcserror << "Failed removing lock fail " << squote(rdlockname) << perror;
return unlink_writelock();
}
locktype = WRITE_LOCK;
if(unlink(wrlockname) < 0 && errno != ENOENT) {
prcserror << "Failed removing lock file " << squote(wrlockname) << perror;
return unlink_writelock();
}
}
locktype = NO_LOCK;
return true;
}
AdvisoryLock::AdvisoryLock(const char* wrlockname0)
:wrfd(-1), rdfd(-1), locktype(NO_LOCK)
{
wrlockname.assign(wrlockname0);
wrlockname.append(".writers");
rdlockname.assign(wrlockname0);
rdlockname.append(".readers");
}
/*
* common code to both implementations
*/
bool AdvisoryLock::unlink_writelock()
{
unlink(wrlockname);
return false;
}
bool AdvisoryLock::write_write_owner_notice()
{
Dstring pid;
if(ftruncate(wrfd, 0) < 0) {
prcserror << "Ftruncate failed while write locking lock file "
<< squote(wrlockname) << perror;
return false;
}
if(lseek(wrfd, 0, SEEK_SET) < 0) {
prcserror << "Lseek failed while write locking lock file "
<< squote(wrlockname) << perror;
return false;
}
if(write(wrfd, LOCK_HEADER, LOCK_LENGTH) < 0) {
prcserror << "Write failed while write locking lock file "
<< squote(wrlockname) << perror;
return false;
}
age = time(NULL);
pid.sprintf("write lock: %s %d %s %s\n", get_login(), getpid(),
get_host_name(), time_t_to_rfc822(age));
wr_lock_banner_offset = lseek(wrfd, 0, SEEK_CUR);
wr_lock_banner_len = pid.length();
if( write(wrfd, pid.cast(), pid.length()) != pid.length()) {
prcserror << "Error recording write lock in lock file "
<< squote(wrlockname) << perror;
return false;
}
return true;
}
bool AdvisoryLock::write_read_owner_notice()
{
Dstring pid;
if(lseek(rdfd, 0, SEEK_END) < 0) {
prcserror << "Lseek failed while read locking lock file "
<< squote(rdlockname) << perror;
return false;
}
age = time(NULL);
pid.sprintf("read lock: %s %d %s %s\n", get_login(), getpid(),
get_host_name(), time_t_to_rfc822(age));
rd_lock_banner_offset = lseek(rdfd, 0, SEEK_CUR);
rd_lock_banner_len = pid.length();
if( write(rdfd, pid.cast(), pid.length()) != pid.length()) {
prcserror << "Error recording read lock in lock file " << squote(rdlockname) << perror;
return false;
}
return true;
}
bool AdvisoryLock::steal_lock()
{
if(unlink(rdlockname) < 0 && errno != ENOENT) {
prcserror << "Unlink failed while stealing lock" << perror;
return false;
}
if(unlink(wrlockname) < 0 && errno != ENOENT) {
prcserror << "Unlink failed while stealing lock" << perror;
return false;
}
return true;
}
FILE* AdvisoryLock::who_is_locking()
{
FILE* ret;
if(fs_file_exists(rdlockname)) {
ret = fopen(rdlockname, "r");
} else {
ret = fopen(wrlockname, "r");
}
if(ret) {
if(fseek(ret, LOCK_LENGTH, 0) < 0) {
prcserror << "Seek error in lock file " << squote(wrlockname) << perror;
return NULL;
}
return ret;
} else {
prcserror << "Error opening lock file " << squote(wrlockname)
<< " to get lockers" << perror;
return NULL;
}
}
int AdvisoryLock::touch_lock()
{
if (locktype == READ_LOCK) {
if ((rdfd = open (rdlockname, O_RDWR, LOCK_FILE_MODE)) < 0)
return LOCK_FAIL;
if (lseek (rdfd, rd_lock_banner_offset, SEEK_SET) < 0)
return LOCK_FAIL;
Dstring pid;
age = time(NULL);
pid.sprintf("read lock: %s %d %s %s\n", get_login(), getpid(),
get_host_name(), time_t_to_rfc822(age));
if( write(rdfd, pid.cast(), pid.length()) != pid.length())
return LOCK_FAIL;
if (close(rdfd) < 0)
return LOCK_FAIL;
} else if (locktype == WRITE_LOCK) {
if ((wrfd = open (wrlockname, O_RDWR, LOCK_FILE_MODE)) < 0)
return LOCK_FAIL;
if (lseek (wrfd, wr_lock_banner_offset, SEEK_SET) < 0)
return LOCK_FAIL;
Dstring pid;
age = time(NULL);
pid.sprintf("write lock: %s %d %s %s\n", get_login(), getpid(),
get_host_name(), time_t_to_rfc822(age));
if( write(wrfd, pid.cast(), pid.length()) != pid.length())
return LOCK_FAIL;
if ( close(wrfd) < 0)
return LOCK_FAIL;
}
return LOCK_SUCCESS;
}
int AdvisoryLock::write_lock_wait() { return write_lock(LOCK_WAIT); }
int AdvisoryLock::read_lock_wait() { return read_lock(LOCK_WAIT); }
int AdvisoryLock::write_lock_nowait() { return write_lock(LOCK_NOWAIT); }
int AdvisoryLock::read_lock_nowait() { return read_lock(LOCK_NOWAIT); }
#if 0
int main()
{
AdvisoryLock lock1("lockfile");
int lock1val;
bool wait = true;
bool write = true;
int pid;
long foo;
setup_streams("lock");
while(true) {
wait = foo % 2;
foo += random();
write = foo % 2;
foo += random();
if((pid = fork()) == 0)
break;
}
pid = getpid();
prcsout << "pid " << pid << " requests " << (write ? "write" : "read")
<< " " << (wait ? "wait" : "nowait") << " lock" << dotendl;
if(wait) {
if(write)
lock1val = lock1.write_lock_wait();
else
lock1val = lock1.read_lock_wait();
} else {
if(write)
lock1val = lock1.write_lock_nowait();
else
lock1val = lock1.read_lock_nowait();
}
if(lock1val == LOCK_SUCCESS) {
prcserror("got %s lock pid %d\n", (write? "write": "read"), pid);
sleep(10);
} else if(lock1val == LOCK_FAIL) {
prcserror("%s lock failed pid %d\n",(write? "write": "read"), pid);
} else if(lock1val == LOCK_TRY_STEAL) {
prcserror("%s lock says try steal %d\n", (write? "write": "read"), pid);
} else {
prcserror("%s lock returns try again pid %d\n",(write? "write": "read"), pid);
}
lock1.unlock();
prcserror("exiting %d\n", pid);
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1