1 /***************************************************************************
2  $RCSfile$
3                              -------------------
4     cvs         : $Id$
5     begin       : Sun Nov 23 2003
6     copyright   : (C) 2003 by Martin Preuss
7     email       : martin@libchipcard.de
8 
9  ***************************************************************************
10  *                                                                         *
11  *   This library is free software; you can redistribute it and/or         *
12  *   modify it under the terms of the GNU Lesser General Public            *
13  *   License as published by the Free Software Foundation; either          *
14  *   version 2.1 of the License, or (at your option) any later version.    *
15  *                                                                         *
16  *   This library is distributed in the hope that it will be useful,       *
17  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
18  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
19  *   Lesser General Public License for more details.                       *
20  *                                                                         *
21  *   You should have received a copy of the GNU Lesser General Public      *
22  *   License along with this library; if not, write to the Free Software   *
23  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston,                 *
24  *   MA  02111-1307  USA                                                   *
25  *                                                                         *
26  ***************************************************************************/
27 
28 
29 #ifdef HAVE_CONFIG_H
30 # include <config.h>
31 #endif
32 
33 
34 #include "fslock_p.h"
35 #include "i18n_l.h"
36 #include <gwenhywfar/debug.h>
37 #include <gwenhywfar/inetsocket.h> /* for select */
38 #include <gwenhywfar/gui.h>
39 #include <gwenhywfar/gwentime.h>
40 
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <fcntl.h>
44 
45 #ifdef HAVE_UNISTD_H
46 # include <unistd.h>
47 #endif
48 #include <errno.h>
49 #include <string.h>
50 
51 #include <windows.h>
52 
53 #undef HAVE_LINK
54 /* Win32/Mingw does not have link(2), so we have to keep this
55    undefined for now. */
56 
57 
58 
GWEN_LIST_FUNCTIONS(GWEN_FSLOCK,GWEN_FSLock)59 GWEN_LIST_FUNCTIONS(GWEN_FSLOCK, GWEN_FSLock)
60 GWEN_LIST2_FUNCTIONS(GWEN_FSLOCK, GWEN_FSLock)
61 
62 
63 
64 GWEN_FSLOCK *GWEN_FSLock_new(const char *fname, GWEN_FSLOCK_TYPE t)
65 {
66   GWEN_FSLOCK *fl;
67   GWEN_BUFFER *nbuf;
68   const char *s;
69 
70   assert(fname);
71   GWEN_NEW_OBJECT(GWEN_FSLOCK, fl);
72   GWEN_LIST_INIT(GWEN_FSLOCK, fl);
73   fl->usage=1;
74   fl->entryName=strdup(fname);
75 
76   switch (t) {
77   case GWEN_FSLock_TypeFile:
78     s=".lck";
79     break;
80   case GWEN_FSLock_TypeDir:
81     s="/.dir.lck";
82     break;
83   default:
84     DBG_ERROR(GWEN_LOGDOMAIN, "Unknown log type %d", t);
85     abort();
86   } /* switch */
87 
88   nbuf=GWEN_Buffer_new(0, 256, 0, 1);
89   GWEN_Buffer_AppendString(nbuf, fname);
90   GWEN_Buffer_AppendString(nbuf, s);
91   fl->baseLockFilename=strdup(GWEN_Buffer_GetStart(nbuf));
92 
93   if (GWEN_FSLock__UnifyLockFileName(nbuf)) {
94     DBG_ERROR(GWEN_LOGDOMAIN, "Could not create unique lockfile name");
95     GWEN_Buffer_free(nbuf);
96     abort();
97   }
98   fl->uniqueLockFilename=strdup(GWEN_Buffer_GetStart(nbuf));
99   GWEN_Buffer_free(nbuf);
100 
101   return fl;
102 }
103 
104 
105 
GWEN_FSLock_free(GWEN_FSLOCK * fl)106 void GWEN_FSLock_free(GWEN_FSLOCK *fl)
107 {
108   if (fl) {
109     assert(fl->usage);
110     if (fl->usage==1) {
111       if (fl->lockCount) {
112         DBG_WARN(GWEN_LOGDOMAIN,
113                  "File \"%s\" still locked", fl->entryName);
114       }
115       free(fl->entryName);
116       free(fl->baseLockFilename);
117       free(fl->uniqueLockFilename);
118       GWEN_LIST_FINI(GWEN_FSLOCK, fl);
119       fl->usage=0;
120       GWEN_FREE_OBJECT(fl);
121     }
122     else {
123       fl->usage--;
124     }
125   }
126 }
127 
128 
129 
GWEN_FSLock_Attach(GWEN_FSLOCK * fl)130 void GWEN_FSLock_Attach(GWEN_FSLOCK *fl)
131 {
132   assert(fl);
133   assert(fl->usage);
134   fl->usage++;
135 }
136 
137 
138 
GWEN_FSLock__Lock(GWEN_FSLOCK * fl)139 GWEN_FSLOCK_RESULT GWEN_FSLock__Lock(GWEN_FSLOCK *fl)
140 {
141   assert(fl);
142 
143   if (fl->lockCount==0) {
144     int fd;
145 #ifdef HAVE_LINK
146     int linkCount;
147 #endif
148     struct stat st;
149 
150     fd=open(fl->uniqueLockFilename, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
151     if (fd==-1) {
152       DBG_DEBUG(GWEN_LOGDOMAIN,
153                 "open(%s): %s",
154                 fl->uniqueLockFilename,
155                 strerror(errno));
156       return GWEN_FSLock_ResultError;
157     }
158     close(fd);
159 
160     /* get the link count of the new unique file for later comparison */
161     if (stat(fl->uniqueLockFilename, &st)) {
162       DBG_ERROR(GWEN_LOGDOMAIN, "stat(%s): %s",
163                 fl->uniqueLockFilename, strerror(errno));
164       remove(fl->uniqueLockFilename);
165       return GWEN_FSLock_ResultError;
166     }
167 
168 #ifdef HAVE_LINK
169     linkCount=(int)(st.st_nlink);
170 
171     /* create a hard link to the new unique file with the name of the
172      * real lock file. This is guaranteed to be atomic even on NFS */
173     if (link(fl->uniqueLockFilename, fl->baseLockFilename)) {
174       /* Nonzero returned, i.e. some error occurred */
175       int lnerr;
176 
177       lnerr=errno;
178 
179       DBG_INFO(GWEN_LOGDOMAIN, "link(%s, %s): %s",
180                fl->uniqueLockFilename,
181                fl->baseLockFilename,
182                strerror(errno));
183       if (lnerr==EPERM)
184 #endif /* HAVE_LINK */
185       {
186         int fd;
187 
188         /* link() is not supported on the destination filesystem, try it the
189          * traditional way. This should be ok, since the only FS which does
190          * not handle the O_EXCL flag properly is NFS, and NFS would not
191          * return EPERM (because it generally supports link()).
192          * So for NFS file systems we would not reach this point.
193          */
194         fd=open(fl->baseLockFilename,
195                 O_CREAT | O_EXCL | O_TRUNC | O_RDWR,
196                 S_IRUSR | S_IWUSR);
197         if (fd==-1) {
198           DBG_INFO(GWEN_LOGDOMAIN, "FS-Lock to %s already in use",
199                    fl->entryName);
200           remove(fl->uniqueLockFilename);
201           return GWEN_FSLock_ResultBusy;
202         }
203         close(fd);
204       }
205 #ifdef HAVE_LINK
206       else {
207         /* link() generally is supported on the destination file system,
208          * check whether the link count of the unique file has been
209                * incremented */
210         if (stat(fl->uniqueLockFilename, &st)) {
211           DBG_ERROR(GWEN_LOGDOMAIN, "stat(%s): %s",
212                     fl->uniqueLockFilename, strerror(errno));
213           remove(fl->uniqueLockFilename);
214           return GWEN_FSLock_ResultError;
215         }
216         if ((int)(st.st_nlink)!=linkCount+1) {
217           DBG_INFO(GWEN_LOGDOMAIN, "FS-Lock to %s already in use",
218                    fl->entryName);
219           remove(fl->uniqueLockFilename);
220           return GWEN_FSLock_ResultBusy;
221         }
222       }
223     } /* if error on link */
224 #endif /* HAVE_LINK */
225 
226     DBG_INFO(GWEN_LOGDOMAIN, "FS-Lock applied to %s", fl->entryName);
227   }
228   fl->lockCount++;
229   return GWEN_FSLock_ResultOk;
230 }
231 
232 
233 
GWEN_FSLock_Unlock(GWEN_FSLOCK * fl)234 GWEN_FSLOCK_RESULT GWEN_FSLock_Unlock(GWEN_FSLOCK *fl)
235 {
236   assert(fl);
237 
238   if (fl->lockCount<1) {
239     DBG_ERROR(GWEN_LOGDOMAIN,
240               "Entry \"%s\" not locked", fl->entryName);
241     return GWEN_FSLock_ResultNoLock;
242   }
243   fl->lockCount--;
244   if (fl->lockCount<1) {
245     remove(fl->baseLockFilename);
246     remove(fl->uniqueLockFilename);
247     DBG_INFO(GWEN_LOGDOMAIN, "FS-Lock released from %s", fl->entryName);
248   }
249   return GWEN_FSLock_ResultOk;
250 }
251 
252 
253 
GWEN_FSLock_Lock(GWEN_FSLOCK * fl,int timeout,uint32_t gid)254 GWEN_FSLOCK_RESULT GWEN_FSLock_Lock(GWEN_FSLOCK *fl, int timeout, uint32_t gid)
255 {
256   GWEN_TIME *t0;
257   int distance;
258   int count;
259   GWEN_FSLOCK_RESULT rv;
260   uint32_t progressId;
261 
262   t0=GWEN_CurrentTime();
263   assert(t0);
264 
265   progressId=GWEN_Gui_ProgressStart(GWEN_GUI_PROGRESS_DELAY |
266                                     GWEN_GUI_PROGRESS_ALLOW_EMBED |
267                                     GWEN_GUI_PROGRESS_SHOW_PROGRESS |
268                                     GWEN_GUI_PROGRESS_SHOW_ABORT,
269                                     I18N("Accquiring lock"),
270                                     NULL,
271                                     (timeout==GWEN_TIMEOUT_FOREVER)
272                                     ?0:timeout, gid);
273 
274   if (timeout==GWEN_TIMEOUT_NONE)
275     distance=GWEN_TIMEOUT_NONE;
276   else if (timeout==GWEN_TIMEOUT_FOREVER)
277     distance=GWEN_TIMEOUT_FOREVER;
278   else {
279     distance=GWEN_GUI_CHECK_PERIOD;
280     if (distance>timeout)
281       distance=timeout;
282   }
283 
284   for (count=0;; count++) {
285     int err;
286 
287     err=GWEN_Gui_ProgressAdvance(progressId, GWEN_GUI_PROGRESS_NONE);
288     if (err==GWEN_ERROR_USER_ABORTED) {
289       DBG_ERROR(GWEN_LOGDOMAIN, "User aborted.");
290       GWEN_Gui_ProgressEnd(progressId);
291       return GWEN_FSLock_ResultUserAbort;
292     }
293 
294     rv=GWEN_FSLock__Lock(fl);
295     if (rv==GWEN_FSLock_ResultError) {
296       DBG_INFO(GWEN_LOGDOMAIN, "here");
297       GWEN_Time_free(t0);
298       GWEN_Gui_ProgressEnd(progressId);
299       return rv;
300     }
301     else if (rv==GWEN_FSLock_ResultOk) {
302       GWEN_Time_free(t0);
303       GWEN_Gui_ProgressEnd(progressId);
304       return rv;
305     }
306     else {
307       /* check timeout */
308       if (timeout!=GWEN_TIMEOUT_FOREVER) {
309         GWEN_TIME *t1;
310         double d;
311 
312         if (timeout==GWEN_TIMEOUT_NONE) {
313           GWEN_Gui_ProgressEnd(progressId);
314           return GWEN_FSLock_ResultTimeout;
315         }
316         t1=GWEN_CurrentTime();
317         assert(t1);
318         d=GWEN_Time_Diff(t1, t0);
319         GWEN_Time_free(t1);
320 
321         if (d>=timeout) {
322           DBG_DEBUG(GWEN_LOGDOMAIN,
323                     "Could not lock within %d milliseconds, giving up",
324                     timeout);
325           GWEN_Time_free(t0);
326           GWEN_Gui_ProgressEnd(progressId);
327           return GWEN_FSLock_ResultTimeout;
328         }
329         err=GWEN_Gui_ProgressAdvance(progressId, (uint64_t)d);
330         if (err) {
331           DBG_ERROR(GWEN_LOGDOMAIN, "User aborted.");
332           GWEN_Gui_ProgressEnd(progressId);
333           return GWEN_FSLock_ResultUserAbort;
334         }
335       }
336       /* sleep for the distance of the WaitCallback */
337       GWEN_Socket_Select(0, 0, 0, distance);
338     }
339   } /* for */
340   GWEN_Gui_ProgressEnd(progressId);
341 
342   DBG_WARN(GWEN_LOGDOMAIN, "We should never reach this point");
343   GWEN_Time_free(t0);
344   return GWEN_FSLock_ResultError;
345 
346 
347 
348 
349 
350 }
351 
352 
353 
GWEN_FSLock__UnifyLockFileName(GWEN_BUFFER * nbuf)354 int GWEN_FSLock__UnifyLockFileName(GWEN_BUFFER *nbuf)
355 {
356   char buffer[256];
357 
358   GWEN_Buffer_AppendString(nbuf, ".");
359 
360   buffer[0]=0;
361   if (gethostname(buffer, sizeof(buffer)-1)) {
362     DBG_ERROR(GWEN_LOGDOMAIN, "gethostname: %s", strerror(errno));
363     return -1;
364   }
365   buffer[sizeof(buffer)-1]=0;
366   GWEN_Buffer_AppendString(nbuf, buffer);
367   GWEN_Buffer_AppendString(nbuf, "-");
368 
369   buffer[0]=0;
370   snprintf(buffer, sizeof(buffer)-1, "%i", getpid());
371   buffer[sizeof(buffer)-1]=0;
372   GWEN_Buffer_AppendString(nbuf, buffer);
373 
374   return 0;
375 }
376 
377 
378 
GWEN_FSLock_GetName(const GWEN_FSLOCK * fl)379 const char *GWEN_FSLock_GetName(const GWEN_FSLOCK *fl)
380 {
381   assert(fl);
382   assert(fl->usage);
383   return fl->entryName;
384 }
385 
386 
387 
388 
389 
390