1/************************************************************************ 2 * Collection of NFS resistant exclusive creat routines * 3 * * 4 * Copyright (c) 1990-1997, S.R. van den Berg, The Netherlands * 5 * Copyright (c) 1999-2001, Philip Guenther, The United States * 6 * of America * 7 * #include "../README" * 8 ************************************************************************/ 9#ifdef RCS 10static /*const*/char rcsid[]= 11 "$Id: exopen.c,v 1.44 2001/08/26 21:10:11 guenther Exp $"; 12#endif 13#include "procmail.h" 14#include "acommon.h" 15#include "robust.h" 16#include "misc.h" 17#include "exopen.h" 18#include "lastdirsep.h" 19#include "sublib.h" 20 21static const char*safehost P((void)) /* return a hostname safe for filenames */ 22{ static const char*sname=0; 23 if(!sname) 24 { char*to;const char*from=hostname(); 25 int badchars=0; 26 for(to=(char*)from;*to;to++) /* check for characters that shouldn't */ 27 if(*to=='/'||*to==':'||*to=='\\') /* be in hostnames */ 28 badchars++; 29 if(!badchars) /* it's clean, pass it through */ 30 sname=from; 31 else if(!(sname=to=malloc(3*badchars+strlen(from)+1))) 32 sname=""; /* no memory! */ 33 else 34 { int c; 35 while(badchars) 36 switch(c=(unsigned char)*from++) 37 { default:*to++=c; 38 break; 39 case '\0':from--;to--; /* "this cannot happen" */ 40 break; 41 case '/':case ':':case '\\': /* we'll remap them to \ooo */ 42 *to++='\\'; 43 *to++='0'+(c>>6); 44 *to++='0'+((c>>3)&7); 45 *to++='0'+(c&7); 46 badchars--; 47 } 48 strcpy(to,from); /* copy the remaining characters */ 49 } 50 } 51 return sname; 52} 53 54int unique(full,p,len,mode,verbos,flags)char*const full;char*p; 55 const size_t len;const mode_t mode;const int verbos,flags; 56{ static const char s2c[]=".,+%";static int serial=STRLEN(s2c); 57 static time_t t;char*dot,*end,*host;struct stat filebuf; 58 int nicediff,i,didnice,retry=RETRYunique; 59 if(flags&doCHOWN) /* semi-critical, try raising the priority */ 60 { nicediff=nice(0);SETerrno(0);nicediff-=nice(-NICE_RANGE); 61 didnice=!errno; /* did we succeed? */ 62 } 63 end=full+len; 64 if(end-p<=UNIQnamelen-1) /* were we given enough space? */ 65 goto ret0; /* nope, give up */ 66 if(flags&doMAILDIR) /* 'official' maildir format */ 67 dot=p; 68 else /* 'traditional' format */ 69 *p=UNIQ_PREFIX,dot=ultoan((unsigned long)thepid,p+1); 70 if(serial<STRLEN(s2c)) 71 goto in; 72 do 73 { if(serial>STRLEN(s2c)-1) /* roll over the count? */ 74 { ;{ time_t t2; 75 while(t==(t2=time((time_t*)0))) /* make sure time has passed */ 76 ssleep(1); /* tap tap tap... */ 77 serial=0;t=t2; 78 } 79in: if(flags&doMAILDIR) 80 { dot=ultstr(0,(unsigned long)t,p); /* time.pid_s.hostname */ 81 *dot='.'; 82 dot=ultstr(0,(unsigned long)thepid,dot+1); 83 *dot++='_'; 84 host=dot+2; 85 } 86 else 87 host=1+ultoan((unsigned long)t,dot+1); /* _pid%time.hostname */ 88 host[-1]='.'; /* add the ".hostname" part */ 89 strlcpy(host,safehost(),end-host); 90 } 91 *dot=(flags&doMAILDIR)?'0'+serial:s2c[serial]; 92 serial++; 93 i=lstat(full,&filebuf); 94#ifdef ENAMETOOLONG 95 if(i&&errno==ENAMETOOLONG) 96 { char*op,*ldp; 97 op=lastdirsep(full); 98 ldp=op+1; /* keep track to avoid shortening past it */ 99 if(end-op>MINnamelen+1) /* guess at a safe length */ 100 op+=MINnamelen+1; /* start at MINnamelen out */ 101 else 102 op=end-1; /* this shouldn't happen, but... */ 103 do 104 *--op='\0'; /* try chopping */ 105 while((i=lstat(full,&filebuf))&&errno==ENAMETOOLONG&&op>ldp); 106 } /* either it stopped being a problem or we ran out of filename */ 107#endif 108 } 109#ifndef O_CREAT 110#define ropen(path,type,mode) creat(path,mode) 111#endif 112 while((!i||errno!=ENOENT|| /* casually check if it already exists */ 113 (0>(i=ropen(full,O_WRONLY|O_CREAT|O_EXCL,mode))&&errno==EEXIST))&& 114 (i= -1,retry--)); 115 if(flags&doCHOWN&&didnice) 116 nice(nicediff); /* put back the priority to the old level */ 117 if(i<0) 118 { if(verbos) /* this error message can be confusing */ 119 writeerr(full); /* for casual users */ 120 goto ret0; 121 } 122#ifdef NOfstat 123 if(flags&doCHOWN) 124 { if( 125#else 126 if(flags&doCHECK) 127 { struct stat fdbuf; 128 fstat(i,&fdbuf); /* match between the file descriptor */ 129#define NEQ(what) (fdbuf.what!=filebuf.what) /* and the file? */ 130 if(lstat(full,&filebuf)||filebuf.st_nlink!=1||filebuf.st_size|| 131 NEQ(st_dev)||NEQ(st_ino)||NEQ(st_uid)||NEQ(st_gid)|| 132 flags&doCHOWN&& 133#endif 134 chown(full,uid,sgid)) 135 { rclose(i);unlink(full); /* forget it, no permission */ 136ret0: return flags&doFD?-1:0; 137 } 138 } 139 if(flags&doLOCK) 140 rwrite(i,"0",1); /* pid 0, `works' across networks */ 141 if(flags&doFD) 142 return i; 143 rclose(i); 144 return 1; 145} 146 /* rename MUST fail if already existent */ 147int myrename(old,newn)const char*const old,*const newn; 148{ int fd,serrno; 149 fd=hlink(old,newn);serrno=errno;unlink(old); 150 if(fd>0)rclose(fd-1); 151 SETerrno(serrno); 152 return fd<0?-1:0; 153} 154 155 /* NFS-resistant link() */ 156int rlink(old,newn,st)const char*const old,*const newn;struct stat*st; 157{ if(link(old,newn)) 158 { register int serrno,ret;struct stat sto,stn; 159 serrno=errno;ret= -1; 160#undef NEQ /* compare files to see if the link() */ 161#define NEQ(what) (sto.what!=stn.what) /* actually succeeded */ 162 if(lstat(old,&sto)||(ret=1,lstat(newn,&stn)|| 163 NEQ(st_dev)||NEQ(st_ino)||NEQ(st_uid)||NEQ(st_gid)|| 164 S_ISLNK(sto.st_mode))) /* symlinks are also bad */ 165 { SETerrno(serrno); 166 if(st&&ret>0) 167 { *st=sto; /* save the stat data */ 168 return ret; /* it was a real failure */ 169 } 170 return -1; 171 } 172 /*SETerrno(serrno);*/ /* we really succeeded, don't bother with errno */ 173 } 174 return 0; 175} 176 /* hardlink with fallback for systems that don't support it */ 177int hlink(old,newn)const char*const old,*const newn; 178{ int ret;struct stat stbuf; 179 if(0<(ret=rlink(old,newn,&stbuf))) /* try a real hardlink */ 180 { int fd; 181#ifdef O_CREAT /* failure due to filesystem? */ 182 if(stbuf.st_nlink<2&&errno==EXDEV&& /* try it by conventional means */ 183 0<=(fd=ropen(newn,O_WRONLY|O_CREAT|O_EXCL,stbuf.st_mode))) 184 return fd+1; 185#endif 186 return -1; 187 } 188 return ret; /* success, or the stat failed also */ 189} 190