// -*- c++ -*- //------------------------------------------------------------------------------ // PidFileLock.cpp //------------------------------------------------------------------------------ // Copyright (c) 2001,2005 by Vladislav Grinchenko // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. //------------------------------------------------------------------------------ //System Includes #include // errno #include // strerror(3) #include #include #include #include //Local Includes #include "assa/CommonUtils.h" #include "assa/PidFileLock.h" using namespace ASSA; //------------------------------------------------------------------------------ // External Events //------------------------------------------------------------------------------ PidFileLock:: PidFileLock () : m_fd (-1), m_error (0), m_error_msg ("no errors") { trace_with_mask ("PidFileLock::PidFileLock", PIDFLOCK); l_whence = SEEK_SET; l_start = l_len = l_pid = 0; } PidFileLock:: ~PidFileLock () { trace_with_mask ("PidFileLock::~PidFileLock", PIDFLOCK); if (m_fd != -1) { if (unlock_region () == 0) { // if we had a lock DL((PIDFLOCK,"PID file unlocked.\n")); unlink (m_filename.c_str ()); DL((PIDFLOCK,"PID file removed.\n")); } close (m_fd); DL((PIDFLOCK,"PID lock file closed.\n")); } } bool PidFileLock:: lock (const string& fname_) { trace_with_mask ("PidFileLock::lock", PIDFLOCK); #if defined(WIN32) return true; #else int val; int len; m_filename = Utils::strenv (fname_.c_str ()); val = len = 0; DL((PIDFLOCK,"PID lock file: \"%s\"\n", m_filename.c_str ())); if (open_pid_file (m_filename) < 0) { goto done; } DL((PIDFLOCK,"PID lock file opened and locked (fd=%d).\n", m_fd)); /** Now that we have the lock, truncate file to zero length */ if (ftruncate (m_fd, 0) < 0) { log_error("ftruncate() error"); goto done; } DL((PIDFLOCK,"PID lock file truncated.\n")); /** Store our PID in the file */ if (write_pid () < 0) { log_error("write(PID) error"); goto done; } /** Set close-on-exec flag */ if ((val = ::fcntl(m_fd, F_GETFD, 0)) < 0) { log_error("fcntl(F_GETFD) error"); goto done; } val |= FD_CLOEXEC; if (::fcntl (m_fd, F_SETFD, val) < 0) { log_error("fcntl(F_SETFD) error"); goto done; } DL((PIDFLOCK,"CLOSE-ON-EXEC is set on FD.\n")); done: if (get_error () != 0) { ::close (m_fd); m_fd = -1; } return m_error == 0 ? true : false; #endif // !def WIN32 } /** Cygwin does not have POSIX semantics for locks. * We need to remove shared lock and then get an exclusive lock * to write our PID. Then remove the exclusive lock and replace it * with the shared lock. This leave two chances for another process * to steal the lock from us, but this is the best we can do. * * An exclusive lock under Cygwin prevents other processes to even * open a file for read-only operations! */ int PidFileLock:: write_pid () { trace_with_mask ("PidFileLock::write_pid", PIDFLOCK); #if defined (WIN32) return 0; #else std::ostringstream mypid; size_t len; this->l_pid = getpid (); mypid << this->l_pid << std::ends; len = strlen (mypid.str ().c_str ()); #ifdef __CYGWIN__ unlock_region (); // remove shared (weak) lock lock_region_exclusive (); if (write (m_fd, mypid.str ().c_str (), len) != len) { return -1; } DL((PIDFLOCK,"Wrote PID=%d to the lock file.\n", l_pid)); unlock_region (); // give up the exclusive lock lock_region (); // place shared (weak) lock #else // POSIX-compliant locks if (write (m_fd, mypid.str ().c_str (), len) != len) { return -1; } DL((PIDFLOCK,"Wrote PID=%d to the lock file.\n", this->l_pid)); #endif return 0; #endif // !def WIN32 } //------------------------------------------------------------------------------ // Utility functions //------------------------------------------------------------------------------ int PidFileLock:: lock_region () { trace_with_mask ("PidFileLock::lock_region", PIDFLOCK); int ret; #if defined (WIN32) return 0; #else #ifdef __CYGWIN__ this->l_type = F_RDLCK; // shared lock #else this->l_type = F_WRLCK; #endif this->l_start = 0; this->l_whence = SEEK_SET; this->l_len = 0; ret = ::fcntl (m_fd, F_SETLK, static_cast(this)); DL((PIDFLOCK,"fcntl(fd=%d, F_SETLK, %s) returned: %d\n", m_fd, (this->l_type == F_RDLCK ? "F_RDLCK" : "F_WRLCK"), ret)); return (ret); #endif // !def WIN32 } int PidFileLock:: lock_region_exclusive () { trace_with_mask ("PidFileLock::lock_region_exclusive", PIDFLOCK); int ret = 0; #if defined (WIN32) return 0; #else #ifdef __CYGWIN__ this->l_type = F_WRLCK; // exclusive lock - read would fail this->l_start = 0; this->l_whence = SEEK_SET; this->l_len = 0; ret = ::fcntl (m_fd, F_SETLK, static_cast(this)); DL((PIDFLOCK,"fcntl(fd=%d, F_SETLK, F_WRLCK) returned: %d\n", m_fd, ret)); #endif return (ret); #endif // !def WIN32 } int PidFileLock:: unlock_region () { trace_with_mask ("PidFileLock::unlock_region", PIDFLOCK); int ret; #if defined (WIN32) return 0; #else this->l_type = F_UNLCK; this->l_start = 0; this->l_whence = SEEK_SET; this->l_len = 0; ret = ::fcntl (m_fd, F_SETLK, static_cast(this)); DL((PIDFLOCK,"fcntl(fd=%d, F_SETLK, F_UNLCK) returned: %d\n", m_fd, ret)); return (ret); #endif // !def WIN32 } /** * Read the file descriptor's flags. * * On POSIX-compliant systems, on input to fcntl(F_GETLK), the l_type * describes a lock we would like to place on the file. If the lock * could be placed, fcntl() does not actually place it, but returns * F_UNLCK in the l_type and leaves the other fields of the structure * unchanged. * If, however, one or more incompatable locks would prevent this * lock being placed, then fcntl() returns details about one of these * locks in the l_type/l_whence/l_start/l_len fields and sets l_pid * to be the PID of the process holding that lock. * A lock can be removed explicitly with F_UNLCK or released automatically * when the process terminates or if it closes any file descriptor referring * to a file on which locks are held. * * CYGWIN port does not support F_GETLK command with fcntl() because: * 1) LockFileEx() is not implemented on 9x/ME * 2) Lock requests given to LockFileEx() may not overlap existing locked * regions of the file. * 3) There is not nearly a functionality similar to F_GETLK in the Win32 API. * * Instead, we try to set a lock. We might fail even if we already * hold the lock ourselves. If we fail, try to unlock the file, and * if that fails, then we know that file is locked by someone else. * This method is not reliable, of course, but that's the best we can do. * */ int PidFileLock:: get_lock_status () { trace_with_mask ("PidFileLock::get_lock_status", PIDFLOCK); int ret; #if defined (WIN32) return 0; #else #ifndef __CYGWIN__ // POSIX-compliant locking this->l_type = F_WRLCK; this->l_start = 0; this->l_whence = SEEK_SET; this->l_len = 0; ret = ::fcntl (m_fd, F_GETLK, static_cast(this)); DL((PIDFLOCK,"fcntl(fd=%d, F_GETLK, %s) returned: %d\n", m_fd, (this->l_type == F_RDLCK ? "F_RDLCK" : "F_WRLCK"), ret)); if (ret < 0) { EL ((PIDFLOCK,"fcntl() failed. l_pid = %d\n", this->l_pid)); } return (ret); #else // CYGWIN if (lock_region_exclusive () < 0) { // why exclusive? if (unlock_region () < 0) { // already locked char buf[64]; pid_t pid; // someone else got it this->l_type = F_RDLCK; if (read (m_fd, buf, 64) > 0) { if (sscanf (buf, "%d", &pid) == 1) { this->l_pid = pid; } } else { this->l_pid = 1; // no real PID information } } } else { unlock_region (); // return the lock into its prestine state } return (0); #endif // !def CYGWIN #endif // !def WIN32 } /** Test to see if file is locked by some other process. If it is locked by us, return 0. If it is locked by some other process, return lock owner's PID. */ pid_t PidFileLock:: test_region () { trace_with_mask ("PidFileLock::test_region", PIDFLOCK); int ret; #if defined (WIN32) return 0; #else ret = get_lock_status (); if (ret < 0) { DL((PIDFLOCK,"Failed to retrieve lock status.\n")); return 1; } if (this->l_type == F_UNLCK) { DL((PIDFLOCK,"Region is not locked.\n")); return(0); } DL((PIDFLOCK,"Region is already locked by PID %d\n", this->l_pid)); return (this->l_pid); #endif // !def WIN32 } void PidFileLock:: dump (void) { trace_with_mask("PidFileLock::dump", PIDFLOCK); #if !defined (WIN32) DL((PIDFLOCK,"m_filename : \"%s\"\n", m_filename.c_str())); DL((PIDFLOCK,"m_error : %d\n", get_error ())); DL((PIDFLOCK,"m_error_msg: \"%s\"\n", get_error_msg ())); DL((PIDFLOCK,"m_fd : %d\n", m_fd)); if (m_fd == -1) return; test_region (); if (this->l_type == F_RDLCK) DL((PIDFLOCK,"l_type : F_RDLCK")); if (this->l_type == F_WRLCK) DL((PIDFLOCK,"l_type : F_WRLCK")); if (this->l_type == F_UNLCK) DL((PIDFLOCK,"l_type : F_UNLCK")); DL((PIDFLOCK,"l_whence : %s\n", this->l_whence == SEEK_SET ? "SEEK_SET" : this->l_whence == SEEK_CUR ? "SEEK_CUR" : "SEEK_END")); DL((PIDFLOCK,"l_start : %d\n", this->l_start)); DL((PIDFLOCK,"l_len : %d\n", this->l_len )); DL((PIDFLOCK,"l_pid : %ld\n", this->l_pid )); #endif // !def WIN32 } void PidFileLock:: log_error (const char* msg_) { m_error = errno; EL((ASSAERR, "Error: \"Failed to get a lock on PID file - %s\".\n", msg_)); } /** * Cygwin doesn't implement file locking via fcntl() - for it * we test in one step. */ pid_t PidFileLock:: open_pid_file (const std::string& fname_) { trace_with_mask("PidFileLock::open_pid_file", PIDFLOCK); #if !defined (WIN32) m_fd = ::open (fname_.c_str (), O_WRONLY|O_CREAT, 0644); if (m_fd < 0) { log_error("open() error."); return -1; } /** If we cannot get lock status, or already have a lock, or * if PID file is already locked by another process, then terminate. * Otherwise (file is unlocked), proceed with locking. */ pid_t owner_pid; if ((owner_pid = test_region ()) > 0) { log_error ("PID file is already locked (by someone)."); m_error = EPERM; return -1; } /** Try to set a write lock on the entire file */ if (lock_region () < 0) { if (errno == EACCES || errno == EAGAIN) { log_error("PID file is locked by another process"); } else { log_error("write lock error"); } return -1; } #endif // !def WIN32 return 0; }