process_spawn.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. // Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <exceptions/exceptions.h>
  15. #include <util/process_spawn.h>
  16. #include <util/signal_set.h>
  17. #include <boost/bind.hpp>
  18. #include <map>
  19. #include <signal.h>
  20. #include <stdlib.h>
  21. #include <errno.h>
  22. #include <unistd.h>
  23. #include <sys/wait.h>
  24. namespace isc {
  25. namespace util {
  26. /// @brief Type for process state
  27. struct ProcessState {
  28. /// @brief Constructor
  29. ProcessState() : running_(true), status_(0) {
  30. }
  31. /// @brief true until the exit status is collected
  32. bool running_;
  33. /// @brief 0 or the exit status
  34. int status_;
  35. };
  36. typedef std::map<pid_t, ProcessState> ProcessStates;
  37. /// @brief Implementation of the @c ProcessSpawn class.
  38. ///
  39. /// This pimpl idiom is used by the @c ProcessSpawn in this case to
  40. /// avoid exposing the internals of the implementation, such as
  41. /// custom handling of a SIGCHLD signal, and the conversion of the
  42. /// arguments of the executable from the STL container to the array.
  43. ///
  44. /// This class is made noncopyable so that we don't have attempts
  45. /// to make multiple copies of an object. This avoid problems
  46. /// with multiple copies of objects for a single global resource
  47. /// such as the SIGCHLD signal handler. In addition making it
  48. /// noncopyable keeps the static check code from flagging the
  49. /// lack of a copy constructor as an issue.
  50. class ProcessSpawnImpl : boost::noncopyable {
  51. public:
  52. /// @brief Constructor.
  53. ///
  54. /// @param executable A path to the program to be executed.
  55. /// @param args Arguments for the program to be executed.
  56. ProcessSpawnImpl(const std::string& executable,
  57. const ProcessArgs& args);
  58. /// @brief Destructor.
  59. ~ProcessSpawnImpl();
  60. /// @brief Returns full command line, including arguments, for the process.
  61. std::string getCommandLine() const;
  62. /// @brief Spawn the new process.
  63. ///
  64. /// This method forks the current process and executes the specified
  65. /// binary with arguments within the child process.
  66. ///
  67. /// The child process will return EXIT_FAILURE if the method was unable
  68. /// to start the executable, e.g. as a result of insufficient permissions
  69. /// or when the executable does not exist. If the process ends successfully
  70. /// the EXIT_SUCCESS is returned.
  71. ///
  72. /// @return PID of the spawned process.
  73. /// @throw ProcessSpawnError if forking a current process failed.
  74. pid_t spawn();
  75. /// @brief Checks if the process is still running.
  76. ///
  77. /// @param pid ID of the child processes for which state should be checked.
  78. /// @return true if the child process is running, false otherwise.
  79. bool isRunning(const pid_t pid) const;
  80. /// @brief Checks if any of the spawned processes is still running.
  81. ///
  82. /// @return true if at least one child process is still running.
  83. bool isAnyRunning() const;
  84. /// @brief Returns exit status of the process.
  85. ///
  86. /// If the process is still running, the previous status is returned
  87. /// or 0, if the process is being ran for the first time.
  88. ///
  89. /// @param pid ID of the child process for which exit status should be
  90. /// returned.
  91. /// @return Exit code of the process.
  92. int getExitStatus(const pid_t pid) const;
  93. /// @brief Removes the status of the process with a specified PID.
  94. ///
  95. /// This method removes the status of the process with a specified PID.
  96. /// If the process is still running, the status is not removed and the
  97. /// exception is thrown.
  98. ///
  99. /// @param pid A process pid.
  100. void clearState(const pid_t pid);
  101. private:
  102. /// @brief Copies the argument specified as a C++ string to the new
  103. /// C string.
  104. ///
  105. /// This method is used to convert arguments specified as an STL container
  106. /// holding @c std::string objects to an array of C strings, used by the
  107. /// @c execvp function in the @c ProcessSpawnImpl::spawn. It allocates a
  108. /// new C string and copies the contents of the @c src to it.
  109. ///
  110. /// @param src A source string.
  111. ///
  112. /// @return Allocated C string holding the data from @c src.
  113. char* allocateArg(const std::string& src) const;
  114. /// @brief Signal handler for SIGCHLD.
  115. ///
  116. /// This handler waits for the child process to finish and retrieves
  117. /// its exit code into the @c status_ member.
  118. ///
  119. /// @return true if the processed signal was SIGCHLD or false if it
  120. /// was a different signal.
  121. bool waitForProcess(int signum);
  122. /// @brief A signal set installing a handler for SIGCHLD.
  123. SignalSetPtr signals_;
  124. /// @brief A map holding the status codes of executed processes.
  125. ProcessStates process_state_;
  126. /// @brief Path to an executable.
  127. std::string executable_;
  128. /// @brief An array holding arguments for the executable.
  129. char** args_;
  130. };
  131. ProcessSpawnImpl::ProcessSpawnImpl(const std::string& executable,
  132. const ProcessArgs& args)
  133. : signals_(new SignalSet(SIGCHLD)), process_state_(),
  134. executable_(executable), args_(new char*[args.size() + 2]) {
  135. // Set the handler which is invoked immediately when the signal
  136. // is received.
  137. signals_->setOnReceiptHandler(boost::bind(&ProcessSpawnImpl::waitForProcess,
  138. this, _1));
  139. // Conversion of the arguments to the C-style array we start by setting
  140. // all pointers within an array to NULL to indicate that they haven't
  141. // been allocated yet.
  142. memset(args_, 0, (args.size() + 2) * sizeof(char*));
  143. // By convention, the first argument points to an executable name.
  144. args_[0] = allocateArg(executable_);
  145. // Copy arguments to the array.
  146. for (int i = 1; i <= args.size(); ++i) {
  147. args_[i] = allocateArg(args[i-1]);
  148. }
  149. }
  150. ProcessSpawnImpl::~ProcessSpawnImpl() {
  151. int i = 0;
  152. // Deallocate strings in the array of arguments.
  153. while (args_[i] != NULL) {
  154. delete[] args_[i];
  155. ++i;
  156. }
  157. // Deallocate the array.
  158. delete[] args_;
  159. }
  160. std::string
  161. ProcessSpawnImpl::getCommandLine() const {
  162. std::ostringstream s;
  163. s << executable_;
  164. // Start with index 1, because the first argument duplicates the
  165. // path to the executable. Note, that even if there are no parameters
  166. // the minimum size of the table is 2.
  167. int i = 1;
  168. while (args_[i] != NULL) {
  169. s << " " << args_[i];
  170. ++i;
  171. }
  172. return (s.str());
  173. }
  174. pid_t
  175. ProcessSpawnImpl::spawn() {
  176. // Protect us against SIGCHLD signals
  177. sigset_t sset;
  178. sigemptyset(&sset);
  179. sigaddset(&sset, SIGCHLD);
  180. sigprocmask(SIG_BLOCK, &sset, 0);
  181. // Create the child
  182. pid_t pid = fork();
  183. if (pid < 0) {
  184. isc_throw(ProcessSpawnError, "unable to fork current process");
  185. } else if (pid == 0) {
  186. // We're in the child process.
  187. sigprocmask(SIG_UNBLOCK, &sset, 0);
  188. // Run the executable.
  189. if (execvp(executable_.c_str(), args_) != 0) {
  190. // We may end up here if the execvp failed, e.g. as a result
  191. // of issue with permissions or invalid executable name.
  192. exit(EXIT_FAILURE);
  193. }
  194. // Process finished, exit the child process.
  195. exit(EXIT_SUCCESS);
  196. }
  197. // We're in the parent process.
  198. process_state_.insert(std::pair<pid_t, ProcessState>(pid, ProcessState()));
  199. sigprocmask(SIG_UNBLOCK, &sset, 0);
  200. return (pid);
  201. }
  202. bool
  203. ProcessSpawnImpl::isRunning(const pid_t pid) const {
  204. ProcessStates::const_iterator proc = process_state_.find(pid);
  205. if (proc == process_state_.end()) {
  206. isc_throw(BadValue, "the process with the pid '" << pid
  207. << "' hasn't been spawned and it status cannot be"
  208. " returned");
  209. }
  210. return (proc->second.running_);
  211. }
  212. bool
  213. ProcessSpawnImpl::isAnyRunning() const {
  214. for (ProcessStates::const_iterator proc = process_state_.begin();
  215. proc != process_state_.end(); ++proc) {
  216. if (proc->second.running_) {
  217. return (true);
  218. }
  219. }
  220. return (false);
  221. }
  222. int
  223. ProcessSpawnImpl::getExitStatus(const pid_t pid) const {
  224. ProcessStates::const_iterator proc = process_state_.find(pid);
  225. if (proc == process_state_.end()) {
  226. isc_throw(InvalidOperation, "the process with the pid '" << pid
  227. << "' hasn't been spawned and it status cannot be"
  228. " returned");
  229. }
  230. return (WEXITSTATUS(proc->second.status_));
  231. }
  232. char*
  233. ProcessSpawnImpl::allocateArg(const std::string& src) const {
  234. const size_t src_len = src.length();
  235. // Allocate the C-string with one byte more for the null termination.
  236. char* dest = new char[src_len + 1];
  237. // copy doesn't append the null at the end.
  238. src.copy(dest, src_len);
  239. // Append null on our own.
  240. dest[src_len] = '\0';
  241. return (dest);
  242. }
  243. bool
  244. ProcessSpawnImpl::waitForProcess(int signum) {
  245. // We're only interested in SIGCHLD.
  246. if (signum != SIGCHLD) {
  247. return (false);
  248. }
  249. // Need to store current value of errno, so we could restore it
  250. // after this signal handler does his work.
  251. int errno_value = errno;
  252. for (;;) {
  253. int status = 0;
  254. pid_t pid = waitpid(-1, &status, WNOHANG);
  255. if (pid <= 0) {
  256. break;
  257. }
  258. ProcessStates::iterator proc = process_state_.find(pid);
  259. /// Check that the terminating process was started
  260. /// by our instance of ProcessSpawn
  261. if (proc != process_state_.end()) {
  262. // In this order please
  263. proc->second.status_ = status;
  264. proc->second.running_ = false;
  265. }
  266. }
  267. // Need to restore previous value of errno. We called waitpid(),
  268. // which likely indicated its result by setting errno to ECHILD.
  269. // This is a signal handler, which can be called while virtually
  270. // any other code being run. If we're unlucky, we could receive a
  271. // signal when running a code that is about to check errno. As a
  272. // result the code would detect errno=ECHILD in places which are
  273. // completely unrelated to child or processes in general.
  274. errno = errno_value;
  275. return (true);
  276. }
  277. void
  278. ProcessSpawnImpl::clearState(const pid_t pid) {
  279. if (isRunning(pid)) {
  280. isc_throw(InvalidOperation, "unable to remove the status for the"
  281. "process (pid: " << pid << ") which is still running");
  282. }
  283. process_state_.erase(pid);
  284. }
  285. ProcessSpawn::ProcessSpawn(const std::string& executable,
  286. const ProcessArgs& args)
  287. : impl_(new ProcessSpawnImpl(executable, args)) {
  288. }
  289. ProcessSpawn::~ProcessSpawn() {
  290. delete impl_;
  291. }
  292. std::string
  293. ProcessSpawn::getCommandLine() const {
  294. return (impl_->getCommandLine());
  295. }
  296. pid_t
  297. ProcessSpawn::spawn() {
  298. return (impl_->spawn());
  299. }
  300. bool
  301. ProcessSpawn::isRunning(const pid_t pid) const {
  302. return (impl_->isRunning(pid));
  303. }
  304. bool
  305. ProcessSpawn::isAnyRunning() const {
  306. return (impl_->isAnyRunning());
  307. }
  308. int
  309. ProcessSpawn::getExitStatus(const pid_t pid) const {
  310. return (impl_->getExitStatus(pid));
  311. }
  312. void
  313. ProcessSpawn::clearState(const pid_t pid) {
  314. return (impl_->clearState(pid));
  315. }
  316. }
  317. }