LCOV - code coverage report
Current view: top level - snapwebsites - snap_pid.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 1 81 1.2 %
Date: 2019-12-15 17:13:15 Functions: 2 10 20.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Snap Websites Server -- snap websites server
       2             : // Copyright (c) 2011-2019  Made to Order Software Corp.  All Rights Reserved
       3             : //
       4             : // https://snapwebsites.org/
       5             : // contact@m2osw.com
       6             : //
       7             : // This program is free software; you can redistribute it and/or modify
       8             : // it under the terms of the GNU General Public License as published by
       9             : // the Free Software Foundation; either version 2 of the License, or
      10             : // (at your option) any later version.
      11             : //
      12             : // This program is distributed in the hope that it will be useful,
      13             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      14             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      15             : // GNU General Public License for more details.
      16             : //
      17             : // You should have received a copy of the GNU General Public License
      18             : // along with this program; if not, write to the Free Software
      19             : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      20             : 
      21             : 
      22             : // self
      23             : //
      24             : #include "snapwebsites/snap_pid.h"
      25             : 
      26             : 
      27             : // snapwebsites lib
      28             : //
      29             : #include "snapwebsites/log.h"
      30             : #include "snapwebsites/snapwebsites.h"
      31             : 
      32             : 
      33             : // C lib
      34             : //
      35             : #include <unistd.h>
      36             : #include <fcntl.h>
      37             : #include <sys/file.h>
      38             : 
      39             : 
      40             : // last include
      41             : //
      42             : #include <snapdev/poison.h>
      43             : 
      44             : 
      45             : 
      46             : 
      47             : 
      48             : /** \file
      49             :  * \brief Implementation of the snap_pid class.
      50             :  *
      51             :  * The snap_pid class is used to create a locked PID file which we keep
      52             :  * around while a service is running. It allows us to make sure only
      53             :  * one instance of a service is running. Attempting to run a second
      54             :  * instance will fail in the constructor.
      55             :  *
      56             :  * The destructor makes sure that the PID file gets unlocked and deleted.
      57             :  * It is generally not extremely useful, but it is not a bad thing to
      58             :  * clean up behind oneself.
      59             :  */
      60             : 
      61             : 
      62             : namespace snap
      63             : {
      64             : 
      65             : 
      66             : /** \brief Create a PID file.
      67             :  *
      68             :  * This function is going to create a PID file for the currently running
      69             :  * process.
      70             :  *
      71             :  * The function attempts to lock the file before writing to it. If the lock
      72             :  * fails, then the file is already held by another process so we can't
      73             :  * move forward (i.e. if another instance of the same service is already
      74             :  * running, the second isntance has to quit.)
      75             :  *
      76             :  * \note
      77             :  * We do not 100% guarantee that the current file always gets deleted on exit.
      78             :  * The lock tells us whether a process is gone or not. We are planning to
      79             :  * have a stronger rule for the delete to happen, though.
      80             :  *
      81             :  * \param[in] service_name  The name of this server.
      82             :  */
      83           0 : snap_pid::snap_pid(std::string const & service_name)
      84           0 :     : f_service_name(service_name)
      85             : {
      86           0 :     generate_filename(service_name);
      87           0 :     if(pipe2(f_pipes, O_CLOEXEC) != 0)
      88             :     {
      89           0 :         throw snapwebsites_exception_io_error("error trying to open pipe() to inform parent that the child PID file was created.");
      90             :     }
      91           0 : }
      92             : 
      93             : 
      94             : /** \brief Clean up the PID file.
      95             :  *
      96             :  * The destructor cleans up the PID file. Mainly, it attempts to delete
      97             :  * the file which has the side effect of removing the exclusive lock.
      98             :  *
      99             :  * If you hold a shared pointer to the snap_pid object, make sure to
     100             :  * reset it before calling exit() if such you do.
     101             :  */
     102           0 : snap_pid::~snap_pid()
     103             : {
     104           0 :     unlink_pid_file();
     105           0 :     close_pipes();
     106           0 : }
     107             : 
     108             : 
     109             : /** \brief Create the PID file.
     110             :  *
     111             :  * This function creates the PID file.
     112             :  *
     113             :  * The function returns success or failure to the parent using the pipes.
     114             :  *
     115             :  * On success, it sends true (0x01).
     116             :  *
     117             :  * On failure, it sends false (0x00).
     118             :  */
     119           0 : void snap_pid::create_pid_file()
     120             : {
     121           0 :     f_safe_fd.reset(open(f_pid_filename.c_str()
     122             :                        , O_CLOEXEC | O_CREAT | O_TRUNC | O_WRONLY
     123             :                        , S_IRUSR | S_IWUSR));
     124             : 
     125             :     // make sure the open() worked
     126             :     //
     127           0 :     if(!f_safe_fd)
     128             :     {
     129           0 :         SNAP_LOG_FATAL("Server \"")
     130           0 :                       (f_service_name)
     131           0 :                       ("\" could not create PID file \"")
     132           0 :                       (f_pid_filename)
     133           0 :                       ("\".");
     134           0 :         send_signal(false);
     135             :         throw snap_pid_exception_io_error(
     136             :                       "Could not open PID file \""
     137           0 :                     + f_pid_filename
     138           0 :                     + "\".");
     139             :     }
     140             : 
     141             :     // attempt an exclusive lock
     142             :     //
     143           0 :     if(flock(f_safe_fd.get(), LOCK_EX) != 0)
     144             :     {
     145           0 :         SNAP_LOG_FATAL("Server \"")
     146           0 :                       (f_service_name)
     147           0 :                       ("\"could not lock PID file \"")
     148           0 :                       (f_pid_filename)
     149           0 :                       ("\". Another instance is already running?");
     150           0 :         send_signal(false);
     151             :         throw snap_pid_exception_io_error(
     152             :                       "Could not lock PID file \""
     153           0 :                     + f_pid_filename
     154           0 :                     + "\". Another instance is already running?");
     155             :     }
     156             : 
     157           0 :     f_child_process = true;
     158             : 
     159           0 :     std::string const pid(std::to_string(getpid()) + "\n");
     160           0 :     if(write(f_safe_fd.get(), pid.c_str(), pid.length()) != static_cast<ssize_t>(pid.length()))
     161             :     {
     162           0 :         SNAP_LOG_FATAL("Server \"")
     163           0 :                       (f_service_name)
     164           0 :                       ("\"could not lock PID file \"")
     165           0 :                       (f_pid_filename)
     166           0 :                       ("\". Another instance is already running?");
     167           0 :         send_signal(false);
     168             :         throw snap_pid_exception_io_error(
     169             :                       "Could not lock PID file \""
     170           0 :                     + f_pid_filename
     171           0 :                     + "\". Another instance is already running?");
     172             :     }
     173             : 
     174             :     // PID file is all good
     175             :     //
     176             :     // TBD: we may want to allow the caller to send this signal at a
     177             :     //      later time assuming the child's initialization may not yet
     178             :     //      be complete and we may want to return with exit(1) from
     179             :     //      the parent if the child's initialization fails.
     180             :     //
     181           0 :     send_signal(true);
     182           0 : }
     183             : 
     184             : 
     185             : /** \brief Transforms the service name in a PID filename.
     186             :  *
     187             :  * This function retrieves the "run_path" parameter from the snapserver
     188             :  * configuration file (/etc/snapwebsites/[snapwebsites.d/]snapserver.conf)
     189             :  * and append the service name. It also adds a .pid extension.
     190             :  *
     191             :  * The \p service_name parameter is not expected to include a slash or
     192             :  * an extension.
     193             :  *
     194             :  * \param[in] service_name  The name of the service getting a PID file.
     195             :  */
     196           0 : void snap_pid::generate_filename(std::string const & service_name)
     197             : {
     198           0 :     if(service_name.find('/') != std::string::npos)
     199             :     {
     200           0 :         SNAP_LOG_FATAL("Service name \"")
     201           0 :                       (service_name)
     202           0 :                       ("\"cannot include a slash (/) character.");
     203             :         throw snap_pid_exception_invalid_parameter(
     204             :                       "Service name \""
     205           0 :                     + service_name
     206           0 :                     + "\"cannot include a slash (/) character.");
     207             :     }
     208             : 
     209             :     // get the "run_path=..." parameter from the snapserver.conf file
     210             :     //
     211           0 :     snap::snap_config config("snapserver");
     212           0 :     std::string run_path("/run/snapwebsites");
     213           0 :     if(config.has_parameter("run_path"))
     214             :     {
     215           0 :         run_path = config.get_parameter("run_path");
     216             :     }
     217             : 
     218             :     // generate the filename now
     219             :     //
     220           0 :     f_pid_filename = run_path + "/" + service_name + ".pid";
     221           0 : }
     222             : 
     223             : 
     224             : /** \brief Remove the PID file.
     225             :  *
     226             :  * This function removes the PID file from disk. This has the side effect
     227             :  * of unlocking the file too.
     228             :  *
     229             :  * \attention
     230             :  * Only the child process is allowed to delete the PID file. This function
     231             :  * will do nothing if called by the parent process (which happens in the
     232             :  * destructor.)
     233             :  */
     234           0 : void snap_pid::unlink_pid_file()
     235             : {
     236           0 :     if(f_child_process)
     237             :     {
     238           0 :         unlink(f_pid_filename.c_str());
     239             :     }
     240           0 : }
     241             : 
     242             : 
     243             : /** \brief Close the communication pipes.
     244             :  *
     245             :  * This function close the two pipes created by the constructor. This
     246             :  * ensures that the communication happens only once and that the
     247             :  * pipe resources get resolved.
     248             :  */
     249           0 : void snap_pid::close_pipes()
     250             : {
     251           0 :     if(f_pipes[0] != -1)
     252             :     {
     253           0 :         close(f_pipes[0]);
     254           0 :         f_pipes[0] = -1;
     255             :     }
     256           0 :     if(f_pipes[1] != -1)
     257             :     {
     258           0 :         close(f_pipes[1]);
     259           0 :         f_pipes[1] = -1;
     260             :     }
     261           0 : }
     262             : 
     263             : 
     264             : /** \brief Send the parent process a signal about the results.
     265             :  *
     266             :  * This function is used by the child to send a signal to the parent
     267             :  * letting it know that the PID file was properly created.
     268             :  *
     269             :  * The parent must call the wait_signal() function to make sure that
     270             :  * the PID gets created before it returns. In the snapserver is it
     271             :  * done in the server::detach() function.
     272             :  *
     273             :  * \param[in] result  true if the PID file was created successfully.
     274             :  */
     275           0 : void snap_pid::send_signal(bool result)
     276             : {
     277           0 :     char c[1] = { static_cast<char>(result) };
     278           0 :     if(write(f_pipes[1], c, 1) != 1)
     279             :     {
     280           0 :         throw snapwebsites_exception_io_error("error while writing to the pipe between parent and child, letting parent know that the PID file is ready.");
     281             :     }
     282           0 :     close_pipes();
     283           0 : }
     284             : 
     285             : 
     286             : /** \brief Wait for the child's signal.
     287             :  *
     288             :  * This function waits for the child to send us one byte to let us know
     289             :  * whether the creation of the PID file succeeded or not.
     290             :  *
     291             :  * If the function returns false, we can let systemd know that the
     292             :  * initialization was not a success.
     293             :  *
     294             :  * \note
     295             :  * The function can be called multiple times. It will wait for the child
     296             :  * process only the first time. Further calls will just return the same
     297             :  * result over and over again.
     298             :  *
     299             :  * \return true if the child created the PID successfully, false otherwise.
     300             :  */
     301           0 : bool snap_pid::wait_signal()
     302             : {
     303           0 :     if(f_pipes[0] != -1)
     304             :     {
     305           0 :         if(read(f_pipes[0], &f_result, 1) != 1)
     306             :         {
     307           0 :             throw snapwebsites_exception_io_error("error while reading from the pipe between parent and child, parent will never know whether the PID file is ready.");
     308             :         }
     309             : 
     310           0 :         close_pipes();
     311             :     }
     312             : 
     313           0 :     return f_result == 1;
     314             : }
     315             : 
     316             : 
     317           6 : } // namespace snap
     318             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13