Re: patch: NFS and O_EXCL. Also: is O_EXCL really atomic?

Phil Edwards (pedwards@valhalla.cs.wright.edu)
Sun, 15 Sep 1996 00:18:00 -0400


This has probably been seen before. Apologies for any repitition. Just
to let you know that this problem has been successfully dealt with already.
This is the secure-creation routine used by procmail for its lockfiles.

Phil
=========================================================================

/**** Made happy and readable by pedwards@valhalla.cs.wright.edu 5Jan96 */
/************************************************************************
* secure exclusive creat/lock v1.6 1995/05/05 *
* (works even across NFS, which O_EXCL does *not*) *
* *
* Created 1990-1995, S.R. van den Berg, The Netherlands *
* berg@pool.informatik.rwth-aachen.de *
* berg@physik.tu-muenchen.de *
* *
* This file is donated to the public domain. *
* *
* Usage: int xcreat(const char*filename,mode_t mode) *
* *
* returns 0:success -1:lock busy *
* -2:parameter error or out of memory *
* *
* sets errno on failure *
* *
* To remove a `lockfile', simply unlink it. *
* *
************************************************************************/
#define HOSTNAMElen 9 /* significant characters for hostname */
/*#define NOuname uncomment if uname is not available */
/*#define NOstrpbrk uncomment if strpbrk is not available */
/*#define strchr(s,c) index(s,c) uncomment if strchr is not available */
#define const /* can be undefined for ANSI compilers */
static const char dirsep[]="/"; /* directory separators */

#include <unistd.h> /* open() close() link() unlink() getpid() */
#include <fcntl.h> /* O_WRONLY O_CREAT O_EXCL */
#include <stdlib.h> /* malloc() free() */
#include <string.h> /* strncpy() strcat() strpbrk() */
#include <sys/stat.h> /* stat() struct stat */
#ifndef NOuname
#include <sys/utsname.h> /* uname() struct utsname */
#endif
#include <errno.h>

#ifndef O_SYNC
#define O_SYNC 0
#endif
#ifndef O_CREAT
#define copen(path,type,mode) creat(path,mode)
#else
#define copen(path,type,mode) open(path,type,mode)
#endif
#define log(string) /* should log string to stderr */
#define UNIQ_PREFIX '_'
#define charsSERIAL 4
#define UNIQnamelen (1+charsSERIAL+HOSTNAMElen+1)
#define bitsSERIAL (6*charsSERIAL)
#define maskSERIAL ((1L<<bitsSERIAL)-1)
#define rotbSERIAL 2
#define irotbSERIAL (1L<<bitsSERIAL-rotbSERIAL)
#define mrotbSERIAL ((maskSERIAL&irotbSERIAL-1)+irotbSERIAL)

/* extern errno; Bad Things -- already declared in errno.h */

#ifdef NOstrpbrk
char*
strpbrk(const char* const st, *del)
{
const char *f=0, *t;
for(f=0; *del; )
if((t=strchr(st,*del++))&&(!f||t<f))
f=t;
return (char*)f;
}
#endif

static const char*hostname()
{ static char name[HOSTNAMElen+1];
#ifdef NOuname
gethostname(name,HOSTNAMElen+1);
#else
struct utsname names;
uname(&names);strncpy(name,names.nodename,HOSTNAMElen);
#endif
name[HOSTNAMElen]='\0';return name;
}

static void ultoan(val,dest)unsigned long val;char*dest; /* convert to a number */
{ register i; /* within the set [0-9A-Za-z-_] */
do
{ i=val&0x3f;
*dest++=i+(i<10?'0':i<10+26?'A'-10:i<10+26+26?'a'-10-26:
i==10+26+26?'-'-10-26-26:'_'-10-26-27);
}
while(val>>=6);
*dest='\0';
}

/* create unique file name */
static
int
unique (const char* const full, char* const p, const mode_t mode)
{
unsigned long retry = mrotbSERIAL;
int i;
do {
ultoan(maskSERIAL&(retry-=irotbSERIAL)+(long)getpid(),p+1);
*p = UNIQ_PREFIX;
strcat(p,hostname());
}
while (0>(i=copen(full,O_WRONLY|O_CREAT|O_EXCL|O_SYNC,mode))&&errno==EEXIST&&
retry); /* casually check if it already exists (highly unlikely) */

if (i<0) {
log("Error while writing to \"");
log(full);
log("\"\n");
return 0;
}
close(i);
return 1;
}

/* rename MUST fail if already existent */
static
int
myrename (const char* const old, const char* const newn)
{
int i, serrno;
struct stat stbuf;
if (!link(old,newn)) {
unlink(old);
return 0;
}
serrno = errno;
i = stat(old, &stbuf);
unlink(old);
errno = serrno;
return stbuf.st_nlink==2 ? i : -1;
}

/* an NFS secure exclusive file open */
int
xcreat (char *name, const mode_t mode)
{
char *p, *q;
int j=-2, i;

for (q=name; p=strpbrk(q,dirsep); q=p+1); /* find last DIRSEP */
if (!(p=malloc((i=q-name)+UNIQnamelen))) return j; /* Out of memory */
strncpy(p,name,i);
/* try and rename it, fails if nonexclusive */
if (unique(p,p+i,mode)) j=myrename(p,name);
free(p);
return j;
}