1 // -*- c++ -*-
2 //------------------------------------------------------------------------------
3 // PidFileLock.cpp
4 //------------------------------------------------------------------------------
5 // Copyright (c) 2001,2005 by Vladislav Grinchenko
6 //
7 // This library is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU Library General Public
9 // License as published by the Free Software Foundation; either
10 // version 2 of the License, or (at your option) any later version.
11 //------------------------------------------------------------------------------
12
13 //System Includes
14 #include <errno.h> // errno
15 #include <string.h> // strerror(3)
16 #include <unistd.h>
17 #include <fcntl.h>
18 #include <sstream>
19 #include <stdio.h>
20
21 //Local Includes
22 #include "assa/CommonUtils.h"
23 #include "assa/PidFileLock.h"
24
25 using namespace ASSA;
26
27 //------------------------------------------------------------------------------
28 // External Events
29 //------------------------------------------------------------------------------
30
31 PidFileLock::
PidFileLock()32 PidFileLock () :
33 m_fd (-1),
34 m_error (0),
35 m_error_msg ("no errors")
36 {
37 trace_with_mask ("PidFileLock::PidFileLock", PIDFLOCK);
38
39 l_whence = SEEK_SET;
40 l_start = l_len = l_pid = 0;
41 }
42
43 PidFileLock::
~PidFileLock()44 ~PidFileLock ()
45 {
46 trace_with_mask ("PidFileLock::~PidFileLock", PIDFLOCK);
47
48 if (m_fd != -1) {
49 if (unlock_region () == 0) { // if we had a lock
50 DL((PIDFLOCK,"PID file unlocked.\n"));
51
52 unlink (m_filename.c_str ());
53 DL((PIDFLOCK,"PID file removed.\n"));
54 }
55 close (m_fd);
56 DL((PIDFLOCK,"PID lock file closed.\n"));
57 }
58 }
59
60 bool
61 PidFileLock::
lock(const string & fname_)62 lock (const string& fname_)
63 {
64 trace_with_mask ("PidFileLock::lock", PIDFLOCK);
65
66 #if defined(WIN32)
67 return true;
68 #else
69 int val;
70 int len;
71 m_filename = Utils::strenv (fname_.c_str ());
72 val = len = 0;
73
74 DL((PIDFLOCK,"PID lock file: \"%s\"\n", m_filename.c_str ()));
75
76 if (open_pid_file (m_filename) < 0) {
77 goto done;
78 }
79 DL((PIDFLOCK,"PID lock file opened and locked (fd=%d).\n", m_fd));
80
81 /** Now that we have the lock, truncate file to zero length
82 */
83 if (ftruncate (m_fd, 0) < 0) {
84 log_error("ftruncate() error");
85 goto done;
86 }
87 DL((PIDFLOCK,"PID lock file truncated.\n"));
88
89 /** Store our PID in the file
90 */
91 if (write_pid () < 0) {
92 log_error("write(PID) error");
93 goto done;
94 }
95
96 /** Set close-on-exec flag
97 */
98 if ((val = ::fcntl(m_fd, F_GETFD, 0)) < 0) {
99 log_error("fcntl(F_GETFD) error");
100 goto done;
101 }
102 val |= FD_CLOEXEC;
103
104 if (::fcntl (m_fd, F_SETFD, val) < 0) {
105 log_error("fcntl(F_SETFD) error");
106 goto done;
107 }
108 DL((PIDFLOCK,"CLOSE-ON-EXEC is set on FD.\n"));
109
110 done:
111 if (get_error () != 0) {
112 ::close (m_fd);
113 m_fd = -1;
114 }
115 return m_error == 0 ? true : false;
116
117 #endif // !def WIN32
118 }
119
120
121 /** Cygwin does not have POSIX semantics for locks.
122 * We need to remove shared lock and then get an exclusive lock
123 * to write our PID. Then remove the exclusive lock and replace it
124 * with the shared lock. This leave two chances for another process
125 * to steal the lock from us, but this is the best we can do.
126 *
127 * An exclusive lock under Cygwin prevents other processes to even
128 * open a file for read-only operations!
129 */
130 int
131 PidFileLock::
write_pid()132 write_pid ()
133 {
134 trace_with_mask ("PidFileLock::write_pid", PIDFLOCK);
135
136 #if defined (WIN32)
137 return 0;
138 #else
139 std::ostringstream mypid;
140 size_t len;
141
142 this->l_pid = getpid ();
143 mypid << this->l_pid << std::ends;
144 len = strlen (mypid.str ().c_str ());
145
146 #ifdef __CYGWIN__
147
148 unlock_region (); // remove shared (weak) lock
149 lock_region_exclusive ();
150
151 if (write (m_fd, mypid.str ().c_str (), len) != len) {
152 return -1;
153 }
154 DL((PIDFLOCK,"Wrote PID=%d to the lock file.\n", l_pid));
155 unlock_region (); // give up the exclusive lock
156 lock_region (); // place shared (weak) lock
157
158 #else // POSIX-compliant locks
159
160 if (write (m_fd, mypid.str ().c_str (), len) != len) {
161 return -1;
162 }
163 DL((PIDFLOCK,"Wrote PID=%d to the lock file.\n", this->l_pid));
164
165 #endif
166 return 0;
167
168 #endif // !def WIN32
169 }
170
171
172 //------------------------------------------------------------------------------
173 // Utility functions
174 //------------------------------------------------------------------------------
175
176 int
177 PidFileLock::
lock_region()178 lock_region ()
179 {
180 trace_with_mask ("PidFileLock::lock_region", PIDFLOCK);
181 int ret;
182
183 #if defined (WIN32)
184 return 0;
185 #else
186
187 #ifdef __CYGWIN__
188 this->l_type = F_RDLCK; // shared lock
189 #else
190 this->l_type = F_WRLCK;
191 #endif
192
193 this->l_start = 0;
194 this->l_whence = SEEK_SET;
195 this->l_len = 0;
196
197 ret = ::fcntl (m_fd, F_SETLK, static_cast<struct flock*>(this));
198
199 DL((PIDFLOCK,"fcntl(fd=%d, F_SETLK, %s) returned: %d\n",
200 m_fd,
201 (this->l_type == F_RDLCK ? "F_RDLCK" : "F_WRLCK"),
202 ret));
203
204 return (ret);
205
206 #endif // !def WIN32
207 }
208
209
210 int
211 PidFileLock::
lock_region_exclusive()212 lock_region_exclusive ()
213 {
214 trace_with_mask ("PidFileLock::lock_region_exclusive", PIDFLOCK);
215 int ret = 0;
216
217 #if defined (WIN32)
218 return 0;
219 #else
220
221 #ifdef __CYGWIN__
222 this->l_type = F_WRLCK; // exclusive lock - read would fail
223 this->l_start = 0;
224 this->l_whence = SEEK_SET;
225 this->l_len = 0;
226
227 ret = ::fcntl (m_fd, F_SETLK, static_cast<struct flock*>(this));
228
229 DL((PIDFLOCK,"fcntl(fd=%d, F_SETLK, F_WRLCK) returned: %d\n", m_fd, ret));
230 #endif
231
232 return (ret);
233
234 #endif // !def WIN32
235 }
236
237 int
238 PidFileLock::
unlock_region()239 unlock_region ()
240 {
241 trace_with_mask ("PidFileLock::unlock_region", PIDFLOCK);
242 int ret;
243
244 #if defined (WIN32)
245 return 0;
246 #else
247
248 this->l_type = F_UNLCK;
249 this->l_start = 0;
250 this->l_whence = SEEK_SET;
251 this->l_len = 0;
252
253 ret = ::fcntl (m_fd, F_SETLK, static_cast<struct flock*>(this));
254
255 DL((PIDFLOCK,"fcntl(fd=%d, F_SETLK, F_UNLCK) returned: %d\n",
256 m_fd, ret));
257
258 return (ret);
259
260 #endif // !def WIN32
261 }
262
263
264 /**
265 * Read the file descriptor's flags.
266 *
267 * On POSIX-compliant systems, on input to fcntl(F_GETLK), the l_type
268 * describes a lock we would like to place on the file. If the lock
269 * could be placed, fcntl() does not actually place it, but returns
270 * F_UNLCK in the l_type and leaves the other fields of the structure
271 * unchanged.
272 * If, however, one or more incompatable locks would prevent this
273 * lock being placed, then fcntl() returns details about one of these
274 * locks in the l_type/l_whence/l_start/l_len fields and sets l_pid
275 * to be the PID of the process holding that lock.
276 * A lock can be removed explicitly with F_UNLCK or released automatically
277 * when the process terminates or if it closes any file descriptor referring
278 * to a file on which locks are held.
279 *
280 * CYGWIN port does not support F_GETLK command with fcntl() because:
281 * 1) LockFileEx() is not implemented on 9x/ME
282 * 2) Lock requests given to LockFileEx() may not overlap existing locked
283 * regions of the file.
284 * 3) There is not nearly a functionality similar to F_GETLK in the Win32 API.
285 *
286 * Instead, we try to set a lock. We might fail even if we already
287 * hold the lock ourselves. If we fail, try to unlock the file, and
288 * if that fails, then we know that file is locked by someone else.
289 * This method is not reliable, of course, but that's the best we can do.
290 *
291 */
292 int
293 PidFileLock::
get_lock_status()294 get_lock_status ()
295 {
296 trace_with_mask ("PidFileLock::get_lock_status", PIDFLOCK);
297 int ret;
298
299 #if defined (WIN32)
300 return 0;
301 #else
302
303 #ifndef __CYGWIN__ // POSIX-compliant locking
304
305 this->l_type = F_WRLCK;
306 this->l_start = 0;
307 this->l_whence = SEEK_SET;
308 this->l_len = 0;
309
310 ret = ::fcntl (m_fd, F_GETLK, static_cast<struct flock*>(this));
311
312 DL((PIDFLOCK,"fcntl(fd=%d, F_GETLK, %s) returned: %d\n",
313 m_fd,
314 (this->l_type == F_RDLCK ? "F_RDLCK" : "F_WRLCK"),
315 ret));
316 if (ret < 0) {
317 EL ((PIDFLOCK,"fcntl() failed. l_pid = %d\n", this->l_pid));
318 }
319 return (ret);
320
321 #else // CYGWIN
322
323 if (lock_region_exclusive () < 0) { // why exclusive?
324 if (unlock_region () < 0) { // already locked
325 char buf[64];
326 pid_t pid; // someone else got it
327 this->l_type = F_RDLCK;
328 if (read (m_fd, buf, 64) > 0) {
329 if (sscanf (buf, "%d", &pid) == 1) {
330 this->l_pid = pid;
331 }
332 }
333 else {
334 this->l_pid = 1; // no real PID information
335 }
336 }
337 }
338 else {
339 unlock_region (); // return the lock into its prestine state
340 }
341 return (0);
342
343 #endif // !def CYGWIN
344
345 #endif // !def WIN32
346
347 }
348
349 /** Test to see if file is locked by some other process.
350 If it is locked by us, return 0.
351 If it is locked by some other process, return lock owner's PID.
352 */
353 pid_t
354 PidFileLock::
test_region()355 test_region ()
356 {
357 trace_with_mask ("PidFileLock::test_region", PIDFLOCK);
358 int ret;
359
360 #if defined (WIN32)
361 return 0;
362 #else
363
364 ret = get_lock_status ();
365
366 if (ret < 0) {
367 DL((PIDFLOCK,"Failed to retrieve lock status.\n"));
368 return 1;
369 }
370 if (this->l_type == F_UNLCK) {
371 DL((PIDFLOCK,"Region is not locked.\n"));
372 return(0);
373 }
374
375 DL((PIDFLOCK,"Region is already locked by PID %d\n", this->l_pid));
376 return (this->l_pid);
377
378 #endif // !def WIN32
379 }
380
381
382 void
383 PidFileLock::
dump(void)384 dump (void)
385 {
386 trace_with_mask("PidFileLock::dump", PIDFLOCK);
387
388 #if !defined (WIN32)
389
390 DL((PIDFLOCK,"m_filename : \"%s\"\n", m_filename.c_str()));
391 DL((PIDFLOCK,"m_error : %d\n", get_error ()));
392 DL((PIDFLOCK,"m_error_msg: \"%s\"\n", get_error_msg ()));
393 DL((PIDFLOCK,"m_fd : %d\n", m_fd));
394
395 if (m_fd == -1) return;
396
397 test_region ();
398
399 if (this->l_type == F_RDLCK)
400 DL((PIDFLOCK,"l_type : F_RDLCK"));
401
402 if (this->l_type == F_WRLCK)
403 DL((PIDFLOCK,"l_type : F_WRLCK"));
404
405 if (this->l_type == F_UNLCK)
406 DL((PIDFLOCK,"l_type : F_UNLCK"));
407
408 DL((PIDFLOCK,"l_whence : %s\n",
409 this->l_whence == SEEK_SET ? "SEEK_SET" :
410 this->l_whence == SEEK_CUR ? "SEEK_CUR" : "SEEK_END"));
411
412 DL((PIDFLOCK,"l_start : %d\n", this->l_start));
413 DL((PIDFLOCK,"l_len : %d\n", this->l_len ));
414 DL((PIDFLOCK,"l_pid : %ld\n", this->l_pid ));
415
416 #endif // !def WIN32
417 }
418
419 void
420 PidFileLock::
log_error(const char * msg_)421 log_error (const char* msg_)
422 {
423 m_error = errno;
424 EL((ASSAERR,
425 "Error: \"Failed to get a lock on PID file - %s\".\n", msg_));
426 }
427
428
429 /**
430 * Cygwin doesn't implement file locking via fcntl() - for it
431 * we test in one step.
432 */
433 pid_t
434 PidFileLock::
open_pid_file(const std::string & fname_)435 open_pid_file (const std::string& fname_)
436 {
437 trace_with_mask("PidFileLock::open_pid_file", PIDFLOCK);
438
439 #if !defined (WIN32)
440
441 m_fd = ::open (fname_.c_str (), O_WRONLY|O_CREAT, 0644);
442 if (m_fd < 0) {
443 log_error("open() error.");
444 return -1;
445 }
446
447 /** If we cannot get lock status, or already have a lock, or
448 * if PID file is already locked by another process, then terminate.
449 * Otherwise (file is unlocked), proceed with locking.
450 */
451 pid_t owner_pid;
452 if ((owner_pid = test_region ()) > 0) {
453 log_error ("PID file is already locked (by someone).");
454 m_error = EPERM;
455 return -1;
456 }
457
458 /** Try to set a write lock on the entire file
459 */
460 if (lock_region () < 0) {
461 if (errno == EACCES || errno == EAGAIN) {
462 log_error("PID file is locked by another process");
463 }
464 else {
465 log_error("write lock error");
466 }
467 return -1;
468 }
469
470 #endif // !def WIN32
471
472 return 0;
473 }
474
475