home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
kermit.columbia.edu
/
kermit.columbia.edu.tar
/
kermit.columbia.edu
/
sredird
/
telnetcpcd-1.09.tar.gz
/
telnetcpcd-1.09.tar
/
uucplocks.c
< prev
next >
Wrap
C/C++ Source or Header
|
2003-08-12
|
11KB
|
394 lines
/*
uucplocks.c
Copyright (c) 2002,2003 Thomas J Pinkl <tom@pinkl.com>
UUCP locking functions.
Version 1.00 05/25/1999 Initial version
Version 1.01 12/10/2001 Modified for telnetcpcd
Version 1.02 05/15/2002 Modified uucp_lock_filename() such that
it doesn't need to call mybasename().
Version 1.03 05/16/2002 Added uulog() and changed all of the
error logging to use it.
Version 1.04 05/22/2002 Added conditional code for OSR5 to
uucp_lock_filename() to force the lock
file name to lower case.
*/
#include "telnetcpcd.h"
/*
create a uucp lock file and write the specified pid
to it.
returns a ptr to the lock file name on success, NULL on failure.
*/
char *uucp_lock(char *device,pid_t pid)
{
extern int errno;
int ret; /* gp int */
mode_t mode; /* file mode */
pid_t lockpid; /* pid which owns lock file */
char *tmpname; /* tmp file name */
char *lockfile; /* lock file name */
tmpname = uucp_tmp_filename(pid); /* ptr to tmp file name */
mode = 0644; /* must be writable by owner */
/* create tmp file and write pid to it */
ret = write_pidfile(tmpname,O_WRONLY|O_CREAT,mode,pid);
if (ret != 0) return(NULL);
lockfile = uucp_lock_filename(device); /* ptr to lock file name */
ret = link(tmpname,lockfile); /* link() is atomic */
if (ret != 0) { /* if link() failed */
if (errno == EEXIST) { /* existing lock file */
/*
07/30/2003 - There's a race condition here. If 2 (or more) processes
simultaneously try to lock the same device AND both detect a stale lock AND
one process removes the stale lock, creates a new lock, and verifies it
BEFORE the other process removes what WAS a stale lock (but isn't any more),
then you have a situation where both processes believe they've locked the
same device.
08/01/2003 - to avoid (but not completely eliminate) this race condition,
I've moved the unlink() call before any calls to uulog() and I've
added a sleep before we try to call link() again. The sleep is what
avoids the race condition. It needs to be long enough (I choose 3 seconds)
that OTHER paths through this code will execute to completion (or up to the
same sleep statement) BEFORE we call link() again. And it makes it LESS
likely that, given 2 processes executing this same code block, one will be
able to sleep AND call link() successfully, BEFORE the other calls unlink().
It should be noted that moving the unlink() call before the uulog()
call also plays a part. Since the uulog() call involves I/O, it might cause
the calling process to block, yielding the CPU to another process. By calling
unlink() first, it's more likely to be executed in the same quantum where we
detect the stale lock.
*/
if (uucp_verify_lock(lockfile,&lockpid) == 1) {
ret = unlink(lockfile); /* remove stale lock */
if (ret == 0) {
uulog(LOG_INFO,"removed stale uucp lock %s, owned by PID %d",lockfile,lockpid);
} else {
uulog(LOG_ERR,"unlink(%s) error: %s",lockfile,strerror(errno));
}
sleep(3); /* avoid race condition */
ret = link(tmpname,lockfile); /* try link() again */
}
}
}
if (ret != 0) { /* if link() failed */
if (errno != EEXIST) { /* not due to existing lock file */
uulog(LOG_ERR,"link(%s,%s) error: %s",tmpname,lockfile,strerror(errno));
uulog(LOG_ERR,"error creating uucp lock file %s",lockfile);
}
unlink(tmpname); /* remove tmp file */
return(NULL); /* fail */
}
/* else, link() was successful */
unlink(tmpname); /* remove tmp file */
/* verify that we really got the lock */
ret = uucp_verify_lock(lockfile,&lockpid);
if ((ret == 0) && (lockpid == pid)) {
uulog(LOG_INFO,"created uucp lock file %s",lockfile);
return(lockfile); /* success */
} else {
uulog(LOG_INFO,"error verifying uucp lock file %s",lockfile);
return(NULL); /* fail */
}
}
/*
return a ptr to a temporary file in the uucp lock
directory.
*/
char *uucp_tmp_filename(pid_t pid)
{
static char tmpname[PATH_MAX]; /* tmp file name */
char *template;
char *p; /* gp ptr */
template = uucp_lock_template(); /* get ptr to "/path/to/lock/dir/LCK..%s" */
sprintf(tmpname,template,"tmp.");
p = tmpname + strlen(tmpname); /* point to the null */
sprintf(p,"%d",pid);
return(tmpname);
}
/*
return a ptr to the uucp lock file name for the specified
device.
*/
char *uucp_lock_filename(char *device)
{
static char lockfile[PATH_MAX]; /* lock file name */
char *template;
char *name;
#ifdef OSR5
char *lcname;
char *p;
int c;
#endif
struct stat st;
template = uucp_lock_template(); /* get ptr to "/path/to/lock/dir/LCK..%s" */
lockfile[0] = '\0'; /* init lockfile */
if (device != NULL) {
name = strrchr(device,'/'); /* find last '/' in device name */
if (name != NULL) {
name++; /* point to char after the last '/' */
} else {
name = device;
}
#ifdef OSR5
lcname = strdup(name); /* make a copy of the string */
if (lcname != NULL) { /* force it to lower case */
p = lcname;
while (*p != '\0') {
if (isupper((int) *p)) {
c = tolower((int) *p);
*p = c;
}
p++;
}
name = lcname; /* use the lower case version */
}
#endif
if (strstr(template,"%s") != NULL) { /* template contains "%s" */
sprintf(lockfile,template,name);
} else {
/* assume SVR4 style names, "LK.%03u.%03u.%03u" */
if (stat(device,&st) == 0) {
sprintf(lockfile,template,major(st.st_dev),major(st.st_rdev),minor(st.st_rdev));
/* device, major device, minor device */
}
}
if (lockfile[0] == '\0') {
sprintf(lockfile,template,name);
}
} else {
sprintf(lockfile,template,"null");
}
#ifdef OSR5
if (lcname != NULL) {
free(lcname); /* free the string copy */
}
#endif
return(lockfile);
}
char *uucp_lock_template(void)
{
#ifdef HAVE_CONFIG_T
extern struct config_t conf;
#endif
static char template[PATH_MAX]; /* file name template */
char *p;
#ifdef HAVE_CONFIG_T
/* uucp lock directory */
if (conf.lockdir != NULL) {
strcpy(template,conf.lockdir);
} else {
strcpy(template,UUCPLOCK_DIR);
}
/* append '/' */
strcat(template,"/");
/* append the lock template, eg. "LCK..%s" */
if (conf.locktemplate != NULL) {
strcat(template,conf.locktemplate);
} else {
strcat(template,UUCPLOCK_TMPL);
}
#else
/* uucp lock prefix */
strcpy(template,UUCP_LOCK_PREFIX);
#endif /* HAVE_CONFIG_T */
/* append "%s" if the template does NOT contain a '%' */
if ((p = strchr(template,'%')) == NULL) {
strcat(template,"%s");
}
return(template);
}
/*
remove the uucp lock file for the specified device.
returns 0 on success, non-zero on failure.
*/
int uucp_unlock(char *device)
{
extern int errno;
char *lockfile; /* lock file name */
int ret; /* gp int */
lockfile = uucp_lock_filename(device); /* ptr to lock file name */
ret = unlink(lockfile);
if (ret == 0) {
uulog(LOG_INFO,"removed uucp lock file %s",lockfile);
} else {
uulog(LOG_ERR,"unlink(%s) error: %s",lockfile,strerror(errno));
}
return(ret);
}
/*
read the uucp lock file whose pathname is specified
and verify that the pid contained within, is valid.
if we can open and read the lock file: returns 0 if the
lock is valid (ie. the pid still exists), returns 1 if
the lock is invalid (ie. the pid doesn't exist).
returns -1 if we can't open or read the lock file.
*/
int uucp_verify_lock(char *path,pid_t *ret_pid)
{
extern int errno;
int fd; /* fd for file */
struct stat sbuf; /* stat buffer */
char apid[12]; /* ascii pid */
char *p;
int ret; /* gp int */
pid_t pid; /* process id */
int error; /* error flag */
if (ret_pid != NULL) {
*ret_pid = 0; /* init returned pid */
}
fd = open(path,O_RDONLY); /* open file */
if (fd < 0) {
uulog(LOG_ERR,"open(%s,O_RDONLY) error: %s",path,strerror(errno));
return(-1); /* fail */
}
if (fstat(fd,&sbuf) != 0) { /* get file status */
uulog(LOG_ERR,"fstat() error: %s",strerror(errno));
close(fd); /* close file */
return(-1); /* fail */
}
error = 0; /* clear error flag */
if (sbuf.st_size == sizeof(pid)) { /* binary pid */
ret = read(fd,&pid,sizeof(pid));/* read it */
if (ret != sizeof(pid)) {
++error;
uulog(LOG_ERR,"error reading pid from %s: %s",path,strerror(errno));
}
} else if (sbuf.st_size < sizeof(apid)) { /* ascii pid */
ret = read(fd,apid,sbuf.st_size); /* read it */
if (ret != sbuf.st_size) {
++error;
uulog(LOG_ERR,"error reading pid from %s: %s",path,strerror(errno));
} else {
apid[ret] = '\0'; /* null terminate */
/* remove CR and LF chars */
p = apid + ret - 1; /* point to last char */
while ((*p == 0x0d) || (*p == 0x0a)) {
*p-- = '\0'; /* replace CR or LF with null */
if (p == apid) break; /* back to the beginning */
}
pid = atoi(apid); /* ascii -> number */
}
} else {
uulog(LOG_ERR,"bad size (%lu) for lock file %s",sbuf.st_size,path);
++error;
}
close(fd); /* close file */
if (error) return(-1); /* fail */
if (ret_pid != NULL) {
*ret_pid = pid; /* return the pid */
}
uulog(LOG_INFO,"lock file %s contains pid %d",path,pid);
if (pid < 1) return(-1); /* fail */
ret = kill(pid,0); /* is pid valid? */
if ((ret == -1) && (errno == ESRCH))
return(1); /* pid doesn't exist */
return(0); /* pid exists */
}
/*
open (and possibly create) the file whose pathname
is specified and write the specified pid to it.
returns 0 on success, non-zero on failure.
*/
int write_pidfile(char *path,int flags,mode_t mode,pid_t pid)
{
extern int errno;
int fd; /* fd for file */
#ifdef ASCIIPID
char apid[12]; /* ascii pid */
int len; /* gp length var */
#endif
fd = open(path,flags,mode); /* open/creat file */
if (fd < 0) {
uulog(LOG_ERR,"open(%s,...) error: %s",path,strerror(errno));
return(fd); /* fail */
}
#ifdef ASCIIPID
sprintf(apid,"% 10d\n",pid);
len = strlen(apid); /* length of pid string */
if (write(fd,apid,len) != len) {
#else
if (write(fd,&pid,sizeof(pid)) != sizeof(pid)) {
#endif
uulog(LOG_ERR,"error writing pid to %s: %s",path,strerror(errno));
close(fd); /* close file */
unlink(path); /* remove it */
return(1); /* fail */
}
close(fd); /* close file */
chmod(path,mode); /* set file mode */
return(0); /* success */
}
/*
write our printf-style arguments to either:
1. syslog()
or 2. the debug log
the 'level' parameter is one of LOG_ERR, LOG_INFO, etc...
returns nothing.
*/
void uulog(int level,char *fmt, ...)
{
static char str[1024];
va_list args;
/* sanity checks */
if (fmt == NULL) return;
va_start(args,fmt);
vsnprintf(str,sizeof(str),fmt,args);
#ifdef UULOG_SYSLOG
syslog(level,"%s",str);
#else
if (level == LOG_ERR) {
debug(DBG_ERR,"%s",str);
} else if (level == LOG_INFO) {
debug(DBG_INF,"%s",str);
} else {
debug(DBG_VINF,"%s",str);
}
#endif /* UULOG_SYSLOG */
va_end(args);
}