/************************************************************************
*      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           *
*                      [email protected]             *
*                      [email protected]                      *
*                                                                      *
*      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;
}