/************************************************************************ * Collection of NFS resistant exclusive creat routines * * * * Copyright (c) 1990-1997, S.R. van den Berg, The Netherlands * * Copyright (c) 1999-2001, Philip Guenther, The United States * * of America * * #include "../README" * ************************************************************************/ #ifdef RCS static /*const*/char rcsid[]= "$Id: exopen.c,v 1.44 2001/08/26 21:10:11 guenther Exp $"; #endif #include "procmail.h" #include "acommon.h" #include "robust.h" #include "misc.h" #include "exopen.h" #include "lastdirsep.h" #include "sublib.h" static const char*safehost P((void)) /* return a hostname safe for filenames */ { static const char*sname=0; if(!sname) { char*to;const char*from=hostname(); int badchars=0; for(to=(char*)from;*to;to++) /* check for characters that shouldn't */ if(*to=='/'||*to==':'||*to=='\\') /* be in hostnames */ badchars++; if(!badchars) /* it's clean, pass it through */ sname=from; else if(!(sname=to=malloc(3*badchars+strlen(from)+1))) sname=""; /* no memory! */ else { int c; while(badchars) switch(c=(unsigned char)*from++) { default:*to++=c; break; case '\0':from--;to--; /* "this cannot happen" */ break; case '/':case ':':case '\\': /* we'll remap them to \ooo */ *to++='\\'; *to++='0'+(c>>6); *to++='0'+((c>>3)&7); *to++='0'+(c&7); badchars--; } strcpy(to,from); /* copy the remaining characters */ } } return sname; } int unique(full,p,len,mode,verbos,flags)char*const full;char*p; const size_t len;const mode_t mode;const int verbos,flags; { static const char s2c[]=".,+%";static int serial=STRLEN(s2c); static time_t t;char*dot,*end,*host;struct stat filebuf; int nicediff,i,didnice,retry=RETRYunique; if(flags&doCHOWN) /* semi-critical, try raising the priority */ { nicediff=nice(0);SETerrno(0);nicediff-=nice(-NICE_RANGE); didnice=!errno; /* did we succeed? */ } end=full+len; if(end-p<=UNIQnamelen-1) /* were we given enough space? */ goto ret0; /* nope, give up */ if(flags&doMAILDIR) /* 'official' maildir format */ dot=p; else /* 'traditional' format */ *p=UNIQ_PREFIX,dot=ultoan((unsigned long)thepid,p+1); if(serialSTRLEN(s2c)-1) /* roll over the count? */ { ;{ time_t t2; while(t==(t2=time((time_t*)0))) /* make sure time has passed */ ssleep(1); /* tap tap tap... */ serial=0;t=t2; } in: if(flags&doMAILDIR) { dot=ultstr(0,(unsigned long)t,p); /* time.pid_s.hostname */ *dot='.'; dot=ultstr(0,(unsigned long)thepid,dot+1); *dot++='_'; host=dot+2; } else host=1+ultoan((unsigned long)t,dot+1); /* _pid%time.hostname */ host[-1]='.'; /* add the ".hostname" part */ strlcpy(host,safehost(),end-host); } *dot=(flags&doMAILDIR)?'0'+serial:s2c[serial]; serial++; i=lstat(full,&filebuf); #ifdef ENAMETOOLONG if(i&&errno==ENAMETOOLONG) { char*op,*ldp; op=lastdirsep(full); ldp=op+1; /* keep track to avoid shortening past it */ if(end-op>MINnamelen+1) /* guess at a safe length */ op+=MINnamelen+1; /* start at MINnamelen out */ else op=end-1; /* this shouldn't happen, but... */ do *--op='\0'; /* try chopping */ while((i=lstat(full,&filebuf))&&errno==ENAMETOOLONG&&op>ldp); } /* either it stopped being a problem or we ran out of filename */ #endif } #ifndef O_CREAT #define ropen(path,type,mode) creat(path,mode) #endif while((!i||errno!=ENOENT|| /* casually check if it already exists */ (0>(i=ropen(full,O_WRONLY|O_CREAT|O_EXCL,mode))&&errno==EEXIST))&& (i= -1,retry--)); if(flags&doCHOWN&&didnice) nice(nicediff); /* put back the priority to the old level */ if(i<0) { if(verbos) /* this error message can be confusing */ writeerr(full); /* for casual users */ goto ret0; } #ifdef NOfstat if(flags&doCHOWN) { if( #else if(flags&doCHECK) { struct stat fdbuf; fstat(i,&fdbuf); /* match between the file descriptor */ #define NEQ(what) (fdbuf.what!=filebuf.what) /* and the file? */ if(lstat(full,&filebuf)||filebuf.st_nlink!=1||filebuf.st_size|| NEQ(st_dev)||NEQ(st_ino)||NEQ(st_uid)||NEQ(st_gid)|| flags&doCHOWN&& #endif chown(full,uid,sgid)) { rclose(i);unlink(full); /* forget it, no permission */ ret0: return flags&doFD?-1:0; } } if(flags&doLOCK) rwrite(i,"0",1); /* pid 0, `works' across networks */ if(flags&doFD) return i; rclose(i); return 1; } /* rename MUST fail if already existent */ int myrename(old,newn)const char*const old,*const newn; { int fd,serrno; fd=hlink(old,newn);serrno=errno;unlink(old); if(fd>0)rclose(fd-1); SETerrno(serrno); return fd<0?-1:0; } /* NFS-resistant link() */ int rlink(old,newn,st)const char*const old,*const newn;struct stat*st; { if(link(old,newn)) { register int serrno,ret;struct stat sto,stn; serrno=errno;ret= -1; #undef NEQ /* compare files to see if the link() */ #define NEQ(what) (sto.what!=stn.what) /* actually succeeded */ if(lstat(old,&sto)||(ret=1,lstat(newn,&stn)|| NEQ(st_dev)||NEQ(st_ino)||NEQ(st_uid)||NEQ(st_gid)|| S_ISLNK(sto.st_mode))) /* symlinks are also bad */ { SETerrno(serrno); if(st&&ret>0) { *st=sto; /* save the stat data */ return ret; /* it was a real failure */ } return -1; } /*SETerrno(serrno);*/ /* we really succeeded, don't bother with errno */ } return 0; } /* hardlink with fallback for systems that don't support it */ int hlink(old,newn)const char*const old,*const newn; { int ret;struct stat stbuf; if(0<(ret=rlink(old,newn,&stbuf))) /* try a real hardlink */ { int fd; #ifdef O_CREAT /* failure due to filesystem? */ if(stbuf.st_nlink<2&&errno==EXDEV&& /* try it by conventional means */ 0<=(fd=ropen(newn,O_WRONLY|O_CREAT|O_EXCL,stbuf.st_mode))) return fd+1; #endif return -1; } return ret; /* success, or the stat failed also */ }