1/************************************************************************
2 *	Whatever is needed for (un)locking files in various ways	*
3 *									*
4 *	Copyright (c) 1990-1997, S.R. van den Berg, The Netherlands	*
5 *	Copyright (c) 1998-2001, Philip Guenther, The United States	*
6 *						of America		*
7 *	#include "../README"						*
8 ************************************************************************/
9#ifdef RCS
10static /*const*/char rcsid[]=
11 "$Id: locking.c,v 1.63 2001/08/04 07:12:17 guenther Exp $";
12#endif
13#include "procmail.h"
14#include "robust.h"
15#include "shell.h"
16#include "misc.h"
17#include "pipes.h"
18#include "foldinfo.h"
19#include "exopen.h"
20#include "locking.h"
21#include "lastdirsep.h"
22
23char*globlock;
24
25int lockit(name,lockp)char*name;char**const lockp;
26{ int permanent=nfsTRY,triedforce=0,locktype=doLOCK;struct stat stbuf;time_t t;
27  zombiecollect();
28  if(*lockp)
29   { if(!strcmp(name,*lockp))	/* compare the previous lockfile to this one */
30      { free(name);return 1;	 /* they're equal, save yourself some effort */
31      }
32     unlock(lockp);		       /* unlock any previous lockfile FIRST */
33   }				  /* to prevent deadlocks (I hate deadlocks) */
34  if(!*name)
35   { free(name);return 1;
36   }
37  if(!strcmp(name,defdeflock))	       /* is it the system mailbox lockfile? */
38   { locktype=doCHECK|doLOCK;
39     if(sgid!=gid&&setegid(sgid))      /* try and get some extra permissions */
40#ifndef fdlock
41	if(!accspooldir)
42	 { yell("Bypassed locking",name);
43	   free(name);return 0;
44	 }
45#endif
46	;
47   }
48  for(lcking|=lck_DELAYSIG;;)
49   { yell("Locking",name);	    /* in order to cater for clock skew: get */
50     if(!xcreat(name,LOCKperm,&t,locktype))    /* time t from the filesystem */
51      { *lockp=name;				   /* lock acquired, hurray! */
52	break;
53      }
54     switch(errno)
55      { case EEXIST:		   /* check if it's time for a lock override */
56	   if(!lstat(name,&stbuf)&&stbuf.st_size<=MAX_locksize&&locktimeout
57	    &&!lstat(name,&stbuf)&&locktimeout<t-stbuf.st_mtime)
58	     /*
59	      * stat() till unlink() should be atomic, but can't guarantee that
60	      */
61	    { if(triedforce)			/* already tried, not trying */
62		 goto faillock;					    /* again */
63	      if(S_ISDIR(stbuf.st_mode)||unlink(name))
64		 triedforce=1,nlog("Forced unlock denied on"),logqnl(name);
65	      else
66	       { nlog("Forcing lock on");logqnl(name);suspend();
67		 goto ce;
68	       }
69	    }
70	   else
71	      triedforce=0;		 /* legitimate iteration, clear flag */
72	   break;
73	case ENOSPC:		   /* no space left, treat it as a transient */
74#ifdef EDQUOT						      /* NFS failure */
75	case EDQUOT:		      /* maybe it was a short term shortage? */
76#endif
77	case ENOENT:case ENOTDIR:case EIO:/*case EACCES:*/
78	   if(--permanent)
79	      goto ds;
80	   goto faillock;
81#ifdef ENAMETOOLONG
82	case ENAMETOOLONG:     /* maybe filename too long, shorten and retry */
83	 { int i;
84	   if(0<(i=strlen(name)-1)&&!strchr(dirsep,name[i-1]))
85	    { nlog("Truncating");logqnl(name);elog(" and retrying lock\n");
86	      name[i]='\0';permanent=nfsTRY;
87	      goto ce;
88	    }
89	 }
90#endif
91	default:
92faillock:  nlog("Lock failure on");logqnl(name);
93	   goto term;
94      }
95     permanent=nfsTRY;
96ds:  ssleep((unsigned)locksleep);
97ce:  if(nextexit)
98term: { free(name);			     /* drop the preallocated buffer */
99	break;
100      }
101   }
102  if(!privileged)				   /* we already set our ids */
103     setegid(gid);		      /* we put back our regular permissions */
104  lcking&=~lck_DELAYSIG;
105  if(nextexit)
106     elog(whilstwfor),elog("lockfile"),logqnl(name),Terminate();
107  return !!*lockp;
108}
109
110int lcllock(noext,withext)			    /* lock a local lockfile */
111 const char*const noext,*const withext;
112{ char*lckfile;			    /* locking /dev/null or | would be silly */
113  if(noext||(strcmp(withext,devnull)&&strcmp(withext,"|")))
114   { if(noext)
115	lckfile=tstrdup(noext);
116     else
117      { size_t len=strlen(withext);
118	lckfile=malloc(len+strlen(lockext)+1);
119	strcpy(strcpy(lckfile,withext)+len,lockext);
120      }
121     if(globlock&&!strcmp(lckfile,globlock))	 /* same as global lockfile? */
122      { nlog("Deadlock attempted on");logqnl(lckfile);
123	free(lckfile);
124	return 0;
125      }
126     else
127	return lockit(lckfile,&loclock);
128   }
129  return 1;
130}
131
132void unlock(lockp)char**const lockp;
133{ onguard();
134  if(*lockp)
135   { if(!strcmp(*lockp,defdeflock))    /* is it the system mailbox lockfile? */
136	setegid(sgid);		       /* try and get some extra permissions */
137     yell("Unlocking",*lockp);
138     if(unlink(*lockp))
139	nlog("Couldn't unlock"),logqnl(*lockp);
140     if(!privileged)				   /* we already set our ids */
141	setegid(gid);		      /* we put back our regular permissions */
142     if(!nextexit)			   /* if not inside a signal handler */
143	free(*lockp);
144     *lockp=0;
145   }
146  offguard();
147}
148					/* an NFS secure exclusive file open */
149int xcreat(name,mode,tim,chownit)const char*const name;const mode_t mode;
150 time_t*const tim;const int chownit;
151{ char*p;int j= -2;size_t i;
152  i=lastdirsep(name)-name;
153  memcpy(p=malloc(i+UNIQnamelen),name,i);		     /* try & rename */
154  if(unique(p,p+i,i+UNIQnamelen,mode,verbose,chownit))	/* a unique filename */
155   { if(tim)
156      { struct stat stbuf;	 /* return the filesystem time to the caller */
157	stat(p,&stbuf);*tim=stbuf.st_mtime;
158      }
159     j=myrename(p,name);
160   }
161  free(p);
162  return j;
163}
164	/* if you've ever wondered what conditional compilation was good for */
165#ifndef fdlock						/* watch closely :-) */
166#ifdef USEflock
167#ifndef SYS_FILE_H_MISSING
168#include <sys/file.h>
169#endif
170#define REITflock	1
171#else
172#define REITflock	0
173#endif /* USEflock */
174static int oldfdlock= -1;			    /* the fd we locked last */
175#ifndef NOfcntl_lock
176static struct flock flck;		/* why can't it be a local variable? */
177#define REITfcntl	1
178#else
179#define REITfcntl	0
180#endif /* NOfcntl_lock */
181#ifdef USElockf
182static off_t oldlockoffset;
183#define REITlockf	1
184#else
185#define REITlockf	0
186#endif /* USElockf */
187
188int fdlock(fd)int fd;
189{ int ret;
190  if(verbose)
191     nlog("Acquiring kernel-lock\n");
192#if REITfcntl+REITflock+REITlockf>1
193  for(;!toutflag;verbose&&(nlog("Reiterating kernel-lock\n"),0),
194   ssleep((unsigned)locksleep))
195#endif
196   { zombiecollect();
197#ifdef USElockf
198     oldlockoffset=tell(fd);
199#endif
200#ifndef NOfcntl_lock
201     flck.l_type=F_WRLCK;flck.l_whence=SEEK_SET;flck.l_len=0;
202#ifdef USElockf
203     flck.l_start=oldlockoffset;
204#else
205     flck.l_start=tell(fd);
206#endif
207#endif
208     lcking|=lck_KERNEL;
209#ifndef NOfcntl_lock
210     ret=fcntl(fd,F_SETLKW,&flck);
211#ifdef USElockf
212     if((ret|=lockf(fd,F_TLOCK,(off_t)0))&&(errno==EAGAIN||errno==EACCES||
213      errno==EWOULDBLOCK))
214ufcntl:
215      { flck.l_type=F_UNLCK;fcntl(fd,F_SETLK,&flck);
216	continue;
217      }
218#ifdef USEflock
219     if((ret|=flock(fd,LOCK_EX|LOCK_NB))&&(errno==EAGAIN||errno==EACCES||
220      errno==EWOULDBLOCK))
221      { lockf(fd,F_ULOCK,(off_t)0);
222	goto ufcntl;
223      }
224#endif /* USEflock */
225#else /* USElockf */
226#ifdef USEflock
227     if((ret|=flock(fd,LOCK_EX|LOCK_NB))&&(errno==EAGAIN||errno==EACCES||
228      errno==EWOULDBLOCK))
229      { flck.l_type=F_UNLCK;fcntl(fd,F_SETLK,&flck);
230	continue;
231      }
232#endif /* USEflock */
233#endif /* USElockf */
234#else /* NOfcntl_lock */
235#ifdef USElockf
236     ret=lockf(fd,F_LOCK,(off_t)0);
237#ifdef USEflock
238     if((ret|=flock(fd,LOCK_EX|LOCK_NB))&&(errno==EAGAIN||errno==EACCES||
239      errno==EWOULDBLOCK))
240      { lockf(fd,F_ULOCK,(off_t)0);
241	continue;
242      }
243#endif /* USEflock */
244#else /* USElockf */
245#ifdef USEflock
246     ret=flock(fd,LOCK_EX);
247#endif /* USEflock */
248#endif /* USElockf */
249#endif /* NOfcntl_lock */
250     oldfdlock=fd;lcking&=~lck_KERNEL;
251     return ret;
252   }
253  return 1;							/* timed out */
254}
255
256int fdunlock P((void))
257{ int i;
258  if(oldfdlock<0)
259     return -1;
260  i=0;
261#ifdef USEflock
262  i|=flock(oldfdlock,LOCK_UN);
263#endif
264#ifdef USElockf
265  ;{ off_t curp=tell(oldfdlock);	       /* restore the position later */
266     lseek(oldfdlock,oldlockoffset,SEEK_SET);
267     i|=lockf(oldfdlock,F_ULOCK,(off_t)0);lseek(oldfdlock,curp,SEEK_SET);
268   }
269#endif
270#ifndef NOfcntl_lock
271  flck.l_type=F_UNLCK;i|=fcntl(oldfdlock,F_SETLK,&flck);
272#endif
273  oldfdlock= -1;
274  return i;
275}
276#endif /* fdlock */
277