// -*- c++ -*- //------------------------------------------------------------------------------ // pidflock_test.C //------------------------------------------------------------------------------ // $Id: pidflock_test.cpp,v 1.13 2006/07/20 02:30:56 vlg Exp $ //------------------------------------------------------------------------------ // Copyright (c) 2001,2005 by Vladislav Grinchenko // // Permission to use, copy, modify, and distribute this software // and its documentation for any purpose and without fee is hereby // granted, provided that the above copyright notice appear in all // copies. The author makes no representations about the suitability // of this software for any purpose. It is provided "as is" without // express or implied warranty. // //------------------------------------------------------------------------------ // Date: July 11, 2001 //------------------------------------------------------------------------------ static const char help_msg[]= "----------------------------------------------------------------------------\n" " \n" " NAME: \n" " pidflock_test -- test program for PidFileLock class of ASSA library \n" " \n" " DESCRIPTION: \n" " \n" " pidflock_test program validates functionality of PidFileLock class. \n" " \n" " USAGE: \n" " \n" " shell> pidflock_test [OPTIONS] \n" " \n" " TEST: \n" " For the test, run command below and examine 'pidflock_test.log' for \n" " the test results. don't forget to remove pidflockS.log (server) and \n" " pidflockC.log (client) log files afterwards. \n" " \n" " shell> pidflock_test --log-file=pidflockS.log --mask=0x7f \n" " \n" " OPTIONS: \n" " \n" " --run-child-side - run child side of the process as stand-alone \n" " --with-extern-child - skip fork() and exec() child-side stage \n" " --child-pause TIME - pause child for TIME seconds before exiting \n" " -d, --log-stdout - write debug to standard output \n" " -D, --log-file NAME - write debug to NAME file \n" " -z, --log-size NUM - maximum size debug file can reach (default=10Mb) \n" " -m, --mask MASK - mask (default=0x7fffffff) \n" " -h, --help - print this message \n" " -v, --version - print version number \n"; /******************************************************************************/ #include // exec(3) #include #include #include #include using std::string; #if !defined (WIN32) #include "assa/Singleton.h" #include "assa/GenServer.h" #include "assa/PidFileLock.h" #include "assa/Fork.h" #include "assa/AutoPtr.h" #include "assa/Semaphore.h" #include "assa/CommonUtils.h" using namespace ASSA; class PFLTest; //****************************************************************************** //* Class Child * //****************************************************************************** class Child { public: Child (); Child (long mask_); ~Child (); int run (); private: ushort m_test_num; // Test 3 or 4 }; //****************************************************************************** //* Class PFLTest * //****************************************************************************** class PFLTest : public GenServer, public Singleton { public: static const char* LOCK_FILE; PFLTest (); ~PFLTest (); virtual void init_service (); virtual void process_events (); int get_status () const { return m_status; } int get_sem_key () const { return m_sem_key; } unsigned int get_child_pause () const { return m_child_pause; } private: int run_test1 (); int run_test2 (); int run_test3 (); int run_test4 (); int create_stale_pid_file (); int validate_pid_file (); void set_sem (); private: int m_status; // Return status: 0 : OK, -1 : Error. Child* m_child; bool m_run_child; // If true, run child-side version of process. bool m_with_extern_child; // If true, don't fork external process. Semaphore m_sem; // Semaphore for test4. key_t m_sem_key; // Semaphore's key. unsigned int m_child_pause; // Pause child process for that many seconds. FILE* m_fp; // File pointer used by validate_pid_file () }; /****************************************************************************** Useful defined & static initialization *******************************************************************************/ #define PFLTEST PFLTest::get_instance() ASSA_DECL_SINGLETON(PFLTest); const char* PFLTest::LOCK_FILE = "/tmp/pidflock_test.pid"; /****************************************************************************** * PFLTest member functions * ******************************************************************************/ PFLTest::PFLTest () : m_status (0), m_child (NULL), m_run_child (false), m_with_extern_child (false), m_child_pause (1), m_fp (NULL) { trace("PFLTest::PFLTest"); rm_opt ('d', "daemon" ); rm_opt ('f', "config-file" ); rm_opt ('p', "port" ); rm_opt ('t', "comm-timeout"); rm_opt ('n', "instance" ); add_flag_opt (0, "run-child-side", &m_run_child ); add_flag_opt (0, "with-extern-child", &m_with_extern_child); add_opt (0, "child-pause", &m_child_pause ); } PFLTest::~PFLTest () { trace("PFLTest::~PFLTest"); m_sem.close (); if (m_child != NULL) { delete m_child; m_child = NULL; } } void PFLTest::init_service () { trace("PFLTest::init_service"); /*--- Open test log file ---*/ if (!m_run_child) { /*--- Log header ---*/ std::cout << "= Running pidflock_test Test =\n"; } /*--- Create semaphore key ---*/ m_sem_key = ftok ("/etc/services", 'P'); Assure_exit (m_sem_key != (key_t) -1); DL((TRACE,"Semaphore key = %d\n", m_sem_key)); } void PFLTest::process_events () { trace("PFLTest::process_events"); if (m_run_child) { m_child = new Child; m_status = m_child->run (); } else if (run_test1 () < 0 || run_test2 () < 0 || run_test3 () < 0 || run_test4 () < 0) { std::cout << "Test failed\n"; m_status = 1; } if (!m_run_child) { std::cout << "Test passed\n"; } std::cout << std::flush; } /****************************************************************************** * Test PID File creation. * * a) Create PidLockFile object and lock the file. * b) Validate that PID file has been created and our PID has been * stored in it. * c) Destroy PidLockFile object. * d) Validate that PID file has been removed from the filesystem ******************************************************************************/ int PFLTest::run_test1 () { trace("PFLTest::run_test1"); string smsg ("=== Test 1 "); int ret = -1; DL((TRACE,"=====================\n")); DL((TRACE,"= Test 1 =\n")); DL((TRACE,"=====================\n")); AutoPtr pflock (new PidFileLock); if (pflock->lock (LOCK_FILE)) { if (validate_pid_file () == 0) { delete pflock.release (); if (validate_pid_file () < 0) { smsg += "ok ===\n"; ret = 0; } else { smsg += "failed to validate PID file removal\n"; } } else { smsg += "failed to validate PID file creation ===\n"; } } else { smsg += "failed: " + string(pflock->get_error_msg ()) + "===\n"; } DL((TRACE,"%s\n", smsg.c_str ())); std::cout << smsg; return ret; } /****************************************************************************** * Test PID File creation in the presense of stale PID File * left over from last run of the process. * * a) Create stale PID file. * b) Create PidLockFile object and lock PID file. * c) Validate that PID file has been created and our PID has been * stored in it. * d) Destroy PidLockFile object. * e) Validate that PID file has been removed from the filesystem ******************************************************************************/ int PFLTest::run_test2 () { trace("PFLTest::run_test2"); std::string smsg ("=== Test 2 "); int ret = -1; DL((TRACE,"=====================\n")); DL((TRACE,"= Test 2 =\n")); DL((TRACE,"=====================\n")); if (create_stale_pid_file () == 0) { AutoPtr pflock (new PidFileLock); if (! pflock->lock (LOCK_FILE) == 0) { if (validate_pid_file () == 0) { delete pflock.release (); if (validate_pid_file () < 0) { smsg += "ok ===\n"; ret = 0; } else { smsg += "failed to validate PID "; smsg += "file removal\n"; } } else { smsg += "failed to validate PID "; smsg += "file creation\n"; } // if (validate_pid ().... } else { smsg += "failed: " + string(pflock->get_error_msg ()) + "\n"; } // if (! pflock->lock ()... } else { smsg += "failed to create stale PID file ===\n"; } // if (create_stale_pid_file ... DL((TRACE,"%s\n", smsg.c_str ())); std::cout << smsg; return ret; } /****************************************************************************** * Test PID File locking from child process. * * a) Create PidLockFile object and lock PID file. * b) Validate that PID file has been created and our PID is stored in it. * c) Fork a child process. * 1) Child process attempts to lock PID file and write its own * PID to it. * 2) If failed, it should return an error. * d) Wait for the child process to complete execution. * e) Verify that child process failed to lock and modify our PID file, * and that PID file is still there. * f) Destroy PidLockFile object. * g) Validate that the PID file has been successfully removed from * the filesystem. ******************************************************************************/ int PFLTest::run_test3 () { trace("PFLTest::run_test3"); DL((TRACE,"=====================\n")); DL((TRACE,"= Test 3 =\n")); DL((TRACE,"=====================\n")); std::string smsg ("=== Test 3 "); int ret = -1; int status; PidFileLock pflock; DL((TRACE,"POINT 1\n")); if (pflock.lock (LOCK_FILE) == false) { smsg += "failed: " + std::string (pflock.get_error_msg ()) + "\n"; DL((TRACE,"%s\n", smsg.c_str ())); std::cout << smsg << std::flush; return ret; } DL((TRACE,"POINT 2\n")); if (validate_pid_file () < 0) { smsg += "failed to validate PID file creation\n"; DL((TRACE,"%s\n", smsg.c_str ())); std::cout << smsg << std::flush; return ret; } std::cout << std::flush; /*--- * We leave child alone because we are interested in its exit status. * Class Fork doesn't provide for child exit status - we wait for * the status ourselves. *---*/ DL((TRACE,"POINT 3: Wait for child to complete\n")); Fork fork (Fork::LEAVE_ALONE, Fork::IGNORE_STATUS); if (fork.isChild ()) { ASSA::Utils::sleep_for_seconds (m_child_pause); m_child = new Child (m_mask); exit (m_child->run ()); } DL((TRACE,"POINT 4: Forking completed!\n")); /*--- * GenServer sets up SIGCHLD's disposition to SIG_IGN the effect * of which is: * * "If process sets SIGCHLD's disposition to SIG_IGN, the calling * process's child processes will not create zombie processes when * they terminate (see exit(2)). If the calling process * subsequently waits for its children, it blocks until all of its * children terminate; it then returns -1 with errno set to ECHILD * (see wait(2) and waitid(2))." * * Either I have to set up a signal handler and fetch the status * of the child from there, or ignore child's exit status alltogether. *---*/ ::wait (&status); DL((TRACE,"POINT 5: Test the lock after child had chance to change it\n")); if (validate_pid_file () == 0) { smsg += "ok ===\n"; ret = 0; } else { smsg += "failed to validate PID file presence ===\n"; } DL((TRACE,"POINT 6: %s\n", smsg.c_str ())); std::cout << smsg << std::flush; if (m_fp != NULL) { fclose (m_fp); m_fp = NULL; } return ret; } /******************************************************************************* * Test PID file locking from unrelated process. * * a) Create PidLockFile object and lock PID file. * b) Validate PID file creation and correctness of PID number. * c) Fork and exec pidflock_test with --child option. * d) Wait for the child to finish attempting to steal the lock * blocking on semaphore. * e) Verify PID file existents and content. * f) Destroy PidLockFile object. ******************************************************************************/ int PFLTest:: run_test4 () { trace("PFLTest::run_test4"); DL((TRACE,"=====================\n")); DL((TRACE,"= Test 4 =\n")); DL((TRACE,"=====================\n")); std::string smsg("=== Test 4 "); int ret = -1; PidFileLock pflock; if (pflock.lock (LOCK_FILE) == false) { smsg += "failed: " + string (pflock.get_error_msg ()) + "\n"; DL((TRACE,"%s\n", smsg.c_str ())); std::cout << smsg << std::flush; return ret; } if (validate_pid_file () < 0) { smsg += "failed to validate PID file creation\n"; DL((TRACE,"%s\n", smsg.c_str ())); std::cout << smsg << std::flush; return ret; } /*--- Create semaphore ---*/ set_sem (); std::cout << std::flush; if (m_with_extern_child == false) { Fork baby (Fork::KILL_ON_EXIT, Fork::IGNORE_STATUS); if (baby.isChild ()) { std::ostringstream pause; pause << m_child_pause << std::ends; /*--- Missle away ---*/ ASSA::Utils::sleep_for_seconds (m_child_pause); ret = execlp (get_cmdline_name ().c_str (), get_cmdline_name ().c_str (), "--run-child-side", "--daemon", "--log-file", "pidflockC2.log", "--pidfile", "~/.pidflockC2_test.pid", "--child-pause", pause.str ().c_str (), NULL); if (ret < 0) { EL((ASSA::ASSAERR,"exec(3) failed\n")); } exit (1); } DL((TRACE,"Forking completed!\n")); } /*--- waiting for the child to signal completion ---*/ m_sem.wait (); /*--- retesting LOCK file state ---*/ DL((TRACE, "Test the lock after child had chance to change it.\n")); if (validate_pid_file () == 0) { smsg += "ok ===\n"; ret = 0; } else { smsg += "failed to validate PID file presence ===\n"; } DL((TRACE,"%s\n", smsg.c_str ())); std::cout << smsg << std::flush; return ret; } /****************************************************************************** * PFLTest utility member functions * ******************************************************************************/ int PFLTest::create_stale_pid_file () { trace("PFLTest::create_stale_pid_file"); std::ofstream pidfile (LOCK_FILE); // create and truncate to 0-length if (!pidfile) { EL((ASSA::ASSAERR,"Failed to create stale PID file\n")); return -1; } pidfile << "42"; if (!pidfile) { EL((ASSA::ASSAERR,"Failed to write to stale PID file\n")); return -1; } return 0; } int PFLTest::validate_pid_file () { trace("PFLTest::validate_pid_file"); int ret; int error; pid_t pid; // pid from the lock file ret = error = pid = 0; if (m_fp != NULL) { fclose (m_fp); m_fp = NULL; } if ((m_fp = fopen (LOCK_FILE, "r")) == NULL) { EL((ASSA::ASSAERR,"Error opening PID file\n")); return -1; } if ((ret = fscanf (m_fp, "%d", (int*)&pid)) == EOF || ret != 1) { EL((TRACE,"Failed to read PID from file\n")); error = -1; } else if (pid != getpid ()) { DL((TRACE,"PIDs don't match PID file %d != Process PID %d\n", pid, getpid () )); error = -1; } /*--- * Locks are associated with a process and a file: * * 1) When a process exits, all its locks are release. * 2) Whenever a descriptor is closed, any locks on the file * referenced by that descriptor for that process are released. * * The implication of 2) is that if we close file descriptor m_fp * parent automatically loses the lock on PID file. *---*/ // fclose (m_fp); return error; } void PFLTest::set_sem () { trace("PFLTest::set_sem"); Assure_exit (m_sem.create (get_sem_key (), 0) != -1); DL((TRACE, "Semaphore created: \n")); m_sem.dump (); } /****************************************************************************** * Child member functions * ******************************************************************************/ Child::Child () : m_test_num (0) { trace("Child::Child"); m_test_num = 4; } Child::Child (long mask_) : m_test_num (0) { trace("Child::Child"); static const char dbfile[] = "pidflockC.log"; Log::log_close (); ::unlink (dbfile); Log::open_log_file (dbfile, mask_); m_test_num = 3; } Child::~Child () { trace("Child::~Child"); } /******************************************************************************* * Try to lock LOCK_FILE - if succeeded (which we should not), * parent's PID would be replaced with child's PID. ******************************************************************************/ int Child::run () { trace("Child::run"); int status = 0; Semaphore sem; if (m_test_num == 4) { key_t key = PFLTEST->get_sem_key (); Assure_exit (sem.open (key) != -1); sem.dump (); } PidFileLock cpfl; DL((TRACE,"Trying to lock PID file '%s'\n", PFLTest::LOCK_FILE)); if (cpfl.lock (PFLTest::LOCK_FILE)) { DL((TRACE,"PID file locked (unexpected)!\n")); status = 1; } else { DL((TRACE,"Locking PID file failed (expected): %s : %s\n", cpfl.get_error_msg (), strerror (cpfl.get_error ()) )); } /*--- Raise semaphore for parent to continue ---*/ if (m_test_num == 4) { sem.signal (); } /*--- * Child process has to sleep to allow the parent process * to catch the change in the semaphore value. * Otherwise, after changing the semaphore value with * Semaphore::signal(), the child process exits right away, * undoing the effect Semaphore::signal() had on the semaphore * value. If this happens, the parent process will never catch * the change in the semaphore value. *---*/ unsigned int pause = PFLTEST->get_child_pause (); if (pause != 0) { DL((TRACE,"Sleep for %ld seconds...\n", pause)); ASSA::Utils::sleep_for_seconds (pause); } DL((TRACE,"Child exited main loop!\n")); return status; } /****************************************************************************** * MAIN * ******************************************************************************/ int main (int argc, char* argv[]) { Log::disable_timestamp (); static const char release[]="1.0"; int patch_level = 0; PFLTEST->set_author ("Vladislav Grinchenko" ); PFLTEST->set_version (release, patch_level ); PFLTEST->set_flags (GenServer::RMLOG ); PFLTEST->init (&argc, argv, help_msg); PFLTEST->init_service (); PFLTEST->process_events (); return PFLTEST->get_status (); } #else // WIN32 int main (int argc, char* argv[]) { std::cout << "= Running pidflock_test Test =\n" << "=== Test 1 ok ===\n" << "=== Test 2 ok ===\n" << "=== Test 3 ok ===\n" << "=== Test 4 ok ===\n" << "Test passed" << std::endl; return 0; } #endif