/************************************************************************
 *	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>

/************************************************************************
 * Only edit below this line if you *think* you know what you are doing *
 ************************************************************************/

#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;

#ifdef NOstrpbrk
char*strpbrk(st,del)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 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';
}

static unique(full,p,mode)const char*const full;char*const p;const mode_t mode;
{ unsigned long retry=mrotbSERIAL;int i;	  /* create unique file name */
  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 myrename(old,newn)const char*const old,*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 */
xcreat(name,mode)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)))		    /* out of memory */
     return j;
  strncpy(p,name,i);
  if(unique(p,p+i,mode))
     j=myrename(p,name);	 /* try and rename it, fails if nonexclusive */
  free(p);return j;
}
