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 #include <unistd.h>
46 #include <errno.h>
47 #include <string.h>
48 #include <strings.h>
49 
50 
51 #define GWEN_FSLOCK_TIMEOUT_ASK (7000.0)   /* about 7 secs */
52 
53 
54 
GWEN_LIST_FUNCTIONS(GWEN_FSLOCK,GWEN_FSLock)55 GWEN_LIST_FUNCTIONS(GWEN_FSLOCK, GWEN_FSLock)
56 GWEN_LIST2_FUNCTIONS(GWEN_FSLOCK, GWEN_FSLock)
57 
58 
59 
60 GWEN_FSLOCK *GWEN_FSLock_new(const char *fname, GWEN_FSLOCK_TYPE t)
61 {
62   GWEN_FSLOCK *fl;
63   GWEN_BUFFER *nbuf;
64   const char *s;
65 
66   assert(fname);
67   GWEN_NEW_OBJECT(GWEN_FSLOCK, fl);
68   GWEN_LIST_INIT(GWEN_FSLOCK, fl);
69   fl->usage=1;
70   fl->entryName=strdup(fname);
71 
72   switch (t) {
73   case GWEN_FSLock_TypeFile:
74     s=".lck";
75     break;
76   case GWEN_FSLock_TypeDir:
77     s="/.dir.lck";
78     break;
79   default:
80     DBG_ERROR(GWEN_LOGDOMAIN, "Unknown log type %d", t);
81     abort();
82   } /* switch */
83 
84   nbuf=GWEN_Buffer_new(0, 256, 0, 1);
85   GWEN_Buffer_AppendString(nbuf, fname);
86   GWEN_Buffer_AppendString(nbuf, s);
87   fl->baseLockFilename=strdup(GWEN_Buffer_GetStart(nbuf));
88 
89   if (GWEN_FSLock__UnifyLockFileName(nbuf)) {
90     DBG_ERROR(GWEN_LOGDOMAIN, "Could not create unique lockfile name");
91     GWEN_Buffer_free(nbuf);
92     abort();
93   }
94   fl->uniqueLockFilename=strdup(GWEN_Buffer_GetStart(nbuf));
95   GWEN_Buffer_free(nbuf);
96 
97   return fl;
98 }
99 
100 
101 
GWEN_FSLock_free(GWEN_FSLOCK * fl)102 void GWEN_FSLock_free(GWEN_FSLOCK *fl)
103 {
104   if (fl) {
105     assert(fl->usage);
106     if (fl->usage==1) {
107       if (fl->lockCount) {
108         DBG_WARN(GWEN_LOGDOMAIN,
109                  "File \"%s\" still locked", fl->entryName);
110       }
111       free(fl->entryName);
112       free(fl->baseLockFilename);
113       free(fl->uniqueLockFilename);
114       GWEN_LIST_FINI(GWEN_FSLOCK, fl);
115       fl->usage=0;
116       GWEN_FREE_OBJECT(fl);
117     }
118     else {
119       fl->usage--;
120     }
121   }
122 }
123 
124 
125 
GWEN_FSLock_Attach(GWEN_FSLOCK * fl)126 void GWEN_FSLock_Attach(GWEN_FSLOCK *fl)
127 {
128   assert(fl);
129   assert(fl->usage);
130   fl->usage++;
131 }
132 
133 
134 
GWEN_FSLock__Lock(GWEN_FSLOCK * fl)135 GWEN_FSLOCK_RESULT GWEN_FSLock__Lock(GWEN_FSLOCK *fl)
136 {
137   assert(fl);
138 
139   if (fl->lockCount==0) {
140     int fd;
141     int linkCount;
142     struct stat st;
143 
144     fd=open(fl->uniqueLockFilename, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
145     if (fd==-1) {
146       DBG_ERROR(GWEN_LOGDOMAIN,
147                 "Could not open lock file %s for file %s: %s",
148                 fl->baseLockFilename,
149                 fl->entryName,
150                 strerror(errno));
151       return GWEN_FSLock_ResultError;
152     }
153     close(fd);
154 
155     /* get the link count of the new unique file for later comparison */
156     if (stat(fl->uniqueLockFilename, &st)) {
157       DBG_ERROR(GWEN_LOGDOMAIN, "stat(%s): %s",
158                 fl->uniqueLockFilename, strerror(errno));
159       remove(fl->uniqueLockFilename);
160       return GWEN_FSLock_ResultError;
161     }
162     linkCount=(int)(st.st_nlink);
163 
164     /* create a hard link to the new unique file with the name of the
165      * real lock file. This is guaranteed to be atomic even on NFS */
166     if (link(fl->uniqueLockFilename, fl->baseLockFilename)) {
167       int lnerr;
168 
169       lnerr=errno;
170 
171       DBG_INFO(GWEN_LOGDOMAIN, "link(%s, %s): %s",
172                fl->uniqueLockFilename,
173                fl->baseLockFilename,
174                strerror(errno));
175       if (lnerr==EPERM) {
176         /* link() is not supported on the destination filesystem, try it the
177          * traditional way. This should be ok, since the only FS which does
178          * not handle the O_EXCL flag properly is NFS, and NFS would not
179          * return EPERM (because it generally supports link()).
180          * So for NFS file systems we would not reach this point.
181          */
182         fd=open(fl->baseLockFilename,
183                 O_CREAT | O_EXCL | O_TRUNC | O_RDWR,
184                 S_IRUSR | S_IWUSR);
185         if (fd==-1) {
186           DBG_INFO(GWEN_LOGDOMAIN, "FS-Lock to %s already in use",
187                    fl->entryName);
188           remove(fl->uniqueLockFilename);
189           return GWEN_FSLock_ResultBusy;
190         }
191         close(fd);
192       }
193       else {
194         /* link() generally is supported on the destination file system,
195          * check whether the link count of the unique file has been
196                * incremented */
197         if (stat(fl->uniqueLockFilename, &st)) {
198           DBG_ERROR(GWEN_LOGDOMAIN, "stat(%s): %s",
199                     fl->uniqueLockFilename, strerror(errno));
200           remove(fl->uniqueLockFilename);
201           return GWEN_FSLock_ResultError;
202         }
203         if ((int)(st.st_nlink)!=linkCount+1) {
204           DBG_INFO(GWEN_LOGDOMAIN, "FS-Lock to %s already in use",
205                    fl->entryName);
206           remove(fl->uniqueLockFilename);
207           return GWEN_FSLock_ResultBusy;
208         }
209       }
210     } /* if error on link */
211 
212     DBG_VERBOUS(GWEN_LOGDOMAIN, "FS-Lock applied to %s", fl->entryName);
213   }
214   fl->lockCount++;
215   return GWEN_FSLock_ResultOk;
216 }
217 
218 
219 
GWEN_FSLock_Unlock(GWEN_FSLOCK * fl)220 GWEN_FSLOCK_RESULT GWEN_FSLock_Unlock(GWEN_FSLOCK *fl)
221 {
222   assert(fl);
223 
224   if (fl->lockCount<1) {
225     DBG_ERROR(GWEN_LOGDOMAIN,
226               "Entry \"%s\" not locked", fl->entryName);
227     return GWEN_FSLock_ResultNoLock;
228   }
229   fl->lockCount--;
230   if (fl->lockCount<1) {
231     remove(fl->baseLockFilename);
232     remove(fl->uniqueLockFilename);
233     DBG_VERBOUS(GWEN_LOGDOMAIN, "FS-Lock released from %s", fl->entryName);
234   }
235   return GWEN_FSLock_ResultOk;
236 }
237 
238 
239 
GWEN_FSLock_Lock(GWEN_FSLOCK * fl,int timeout,uint32_t gid)240 GWEN_FSLOCK_RESULT GWEN_FSLock_Lock(GWEN_FSLOCK *fl, int timeout, uint32_t gid)
241 {
242   GWEN_TIME *t0;
243   int distance;
244   int count;
245   GWEN_FSLOCK_RESULT rv;
246   uint32_t progressId;
247   int haveAskedForUnlock=0;
248   int isInteractive=1;
249 
250   t0=GWEN_CurrentTime();
251   assert(t0);
252 
253   isInteractive=(GWEN_Gui_GetFlags(GWEN_Gui_GetGui()) & GWEN_GUI_FLAGS_NONINTERACTIVE)==0;
254   progressId=GWEN_Gui_ProgressStart(GWEN_GUI_PROGRESS_DELAY |
255                                     GWEN_GUI_PROGRESS_ALLOW_EMBED |
256                                     GWEN_GUI_PROGRESS_SHOW_PROGRESS |
257                                     GWEN_GUI_PROGRESS_SHOW_ABORT,
258                                     I18N("Accquiring lock"),
259                                     NULL,
260                                     (timeout==GWEN_TIMEOUT_FOREVER)
261                                     ?0:timeout, gid);
262 
263   if (timeout==GWEN_TIMEOUT_NONE)
264     distance=GWEN_TIMEOUT_NONE;
265   else if (timeout==GWEN_TIMEOUT_FOREVER)
266     distance=GWEN_TIMEOUT_FOREVER;
267   else {
268     distance=GWEN_GUI_CHECK_PERIOD;
269     if (distance>timeout)
270       distance=timeout;
271   }
272 
273   for (count=0;; count++) {
274     int err;
275 
276     err=GWEN_Gui_ProgressAdvance(progressId, GWEN_GUI_PROGRESS_NONE);
277     if (err==GWEN_ERROR_USER_ABORTED) {
278       DBG_ERROR(GWEN_LOGDOMAIN, "User aborted.");
279       GWEN_Time_free(t0);
280       GWEN_Gui_ProgressEnd(progressId);
281       return GWEN_FSLock_ResultUserAbort;
282     }
283 
284     rv=GWEN_FSLock__Lock(fl);
285     if (rv==GWEN_FSLock_ResultError) {
286       DBG_INFO(GWEN_LOGDOMAIN, "here");
287       GWEN_Time_free(t0);
288       GWEN_Gui_ProgressEnd(progressId);
289       return rv;
290     }
291     else if (rv==GWEN_FSLock_ResultOk) {
292       GWEN_Time_free(t0);
293       GWEN_Gui_ProgressEnd(progressId);
294       return rv;
295     }
296     else if (rv==GWEN_FSLock_ResultBusy) {
297       int doWait=1;
298 
299       /* lock is busy */
300       if (timeout==GWEN_TIMEOUT_NONE) {
301         /* no timeout, so immediately return the result */
302         GWEN_Time_free(t0);
303         GWEN_Gui_ProgressEnd(progressId);
304         return GWEN_FSLock_ResultBusy;
305       }
306 
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         /* check timeout */
322         if (d>=timeout) {
323           DBG_DEBUG(GWEN_LOGDOMAIN,
324                     "Could not lock within %d milliseconds, giving up",
325                     timeout);
326           GWEN_Time_free(t0);
327           GWEN_Gui_ProgressEnd(progressId);
328           return GWEN_FSLock_ResultTimeout;
329         }
330 
331         err=GWEN_Gui_ProgressAdvance(progressId, (uint64_t)d);
332         if (err) {
333           DBG_ERROR(GWEN_LOGDOMAIN, "User aborted.");
334           GWEN_Gui_ProgressEnd(progressId);
335           return GWEN_FSLock_ResultUserAbort;
336         }
337 
338         if (isInteractive &&
339             haveAskedForUnlock==0 &&
340             d>=GWEN_FSLOCK_TIMEOUT_ASK) {
341           char buffer[2048];
342 
343           haveAskedForUnlock=1;
344           snprintf(buffer, sizeof(buffer)-1,
345                    I18N("There already is a lock for \"%s\".\n"
346                         "Either that lock is valid (e.g. some other process is currently "
347                         "holding that lock) or it is a stale lock of a process which "
348                         "for whatever reason did not remove the lock before terminating.\n"
349                         "\n"
350                         "This dialog allows for forced removal of that lock.\n"
351                         "\n"
352                         "WARNING: Only remove the lock if you are certain that "
353                         "no other process is actually holding the lock!\n"
354                         "\n"
355                         "Do you want to remove the possibly stale lock?\n"
356                         "<html>"
357                         "<p>There already is a lock for <i>%s</i>.</p>"
358                         "<p>Either that lock is valid (e.g. some other process is currently "
359                         "holding that lock) or it is a stale lock of a process which "
360                         "for whatever reason did not remove the lock before terminating.</p>"
361                         "<p>This dialog allows for forced removal of that lock.</p>"
362                         "<p><font color=\"red\"><b>Warning</b></font>: "
363                         "Only remove the lock if you are certain that "
364                         "no other process is actually holding the lock!</p>"
365                         "<p>Do you want to remove the possibly stale lock?</p>"
366                         "</html>"),
367                    fl->entryName,
368                    fl->entryName);
369           buffer[sizeof(buffer)-1]=0;
370 
371           rv=GWEN_Gui_MessageBox(GWEN_GUI_MSG_FLAGS_TYPE_INFO |
372                                  GWEN_GUI_MSG_FLAGS_SEVERITY_NORMAL |
373                                  GWEN_GUI_MSG_FLAGS_CONFIRM_B1,
374                                  I18N("Possible Stale Lock"),
375                                  buffer,
376                                  I18N("Wait"),
377                                  I18N("Remove Lock"),
378                                  I18N("Abort"),
379                                  progressId);
380           if (rv==3) {
381             DBG_ERROR(GWEN_LOGDOMAIN, "User aborted");
382             GWEN_Time_free(t0);
383             GWEN_Gui_ProgressLog(progressId, GWEN_LoggerLevel_Notice,
384                                  I18N("Aborted by user."));
385             GWEN_Gui_ProgressEnd(progressId);
386             return GWEN_FSLock_ResultUserAbort;
387           }
388           else if (rv==2) {
389             remove(fl->baseLockFilename);
390             remove(fl->uniqueLockFilename);
391             DBG_WARN(GWEN_LOGDOMAIN, "FS-Lock forcably released from %s", fl->entryName);
392             GWEN_Gui_ProgressLog(progressId, GWEN_LoggerLevel_Notice,
393                                  I18N("Lock removed by user request."));
394             doWait=0;
395             /* reset timeout */
396             GWEN_Time_free(t0);
397             t0=GWEN_CurrentTime();
398             assert(t0);
399           }
400         }
401       }
402 
403       /* sleep for at most GWEN_GUI_CHECK_PERIOD */
404       if (doWait)
405         GWEN_Socket_Select(0, 0, 0, distance);
406     }
407     else {
408       DBG_ERROR(GWEN_LOGDOMAIN, "Unexpected return code %d", rv);
409       GWEN_Time_free(t0);
410       GWEN_Gui_ProgressEnd(progressId);
411       return rv;
412     }
413   } /* for */
414   GWEN_Gui_ProgressEnd(progressId);
415 
416   DBG_ERROR(GWEN_LOGDOMAIN, "We should never reach this point");
417   GWEN_Time_free(t0);
418   return GWEN_FSLock_ResultError;
419 }
420 
421 
422 
GWEN_FSLock__UnifyLockFileName(GWEN_BUFFER * nbuf)423 int GWEN_FSLock__UnifyLockFileName(GWEN_BUFFER *nbuf)
424 {
425   char buffer[256];
426 
427   GWEN_Buffer_AppendString(nbuf, ".");
428 
429   buffer[0]=0;
430   if (gethostname(buffer, sizeof(buffer)-1)) {
431     DBG_ERROR(GWEN_LOGDOMAIN, "gethostname: %s", strerror(errno));
432     return -1;
433   }
434   buffer[sizeof(buffer)-1]=0;
435   GWEN_Buffer_AppendString(nbuf, buffer);
436   GWEN_Buffer_AppendString(nbuf, "-");
437 
438   buffer[0]=0;
439   snprintf(buffer, sizeof(buffer)-1, "%i", (int)getpid());
440   buffer[sizeof(buffer)-1]=0;
441   GWEN_Buffer_AppendString(nbuf, buffer);
442 
443   return 0;
444 }
445 
446 
447 
GWEN_FSLock_GetName(const GWEN_FSLOCK * fl)448 const char *GWEN_FSLock_GetName(const GWEN_FSLOCK *fl)
449 {
450   assert(fl);
451   assert(fl->usage);
452   return fl->entryName;
453 }
454 
455 
456 
457 
458 
459 
460 
461 
462