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