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