diff --git a/contrib/kyua/utils/fs/directory.cpp b/contrib/kyua/utils/fs/directory.cpp index c8cd5e008490..f82fe88f86dc 100644 --- a/contrib/kyua/utils/fs/directory.cpp +++ b/contrib/kyua/utils/fs/directory.cpp @@ -1,360 +1,360 @@ // Copyright 2015 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/fs/directory.hpp" extern "C" { #include #include } #include #include #include "utils/format/macros.hpp" #include "utils/fs/exceptions.hpp" #include "utils/fs/path.hpp" #include "utils/noncopyable.hpp" #include "utils/sanity.hpp" #include "utils/text/operations.ipp" namespace detail = utils::fs::detail; namespace fs = utils::fs; namespace text = utils::text; /// Constructs a new directory entry. /// /// \param name_ Name of the directory entry. fs::directory_entry::directory_entry(const std::string& name_) : name(name_) { } /// Checks if two directory entries are equal. /// /// \param other The entry to compare to. /// /// \return True if the two entries are equal; false otherwise. bool fs::directory_entry::operator==(const directory_entry& other) const { return name == other.name; } /// Checks if two directory entries are different. /// /// \param other The entry to compare to. /// /// \return True if the two entries are different; false otherwise. bool fs::directory_entry::operator!=(const directory_entry& other) const { return !(*this == other); } /// Checks if this entry sorts before another entry. /// /// \param other The entry to compare to. /// /// \return True if this entry sorts before the other entry; false otherwise. bool fs::directory_entry::operator<(const directory_entry& other) const { return name < other.name; } /// Formats a directory entry. /// /// \param output Stream into which to inject the formatted entry. /// \param entry The entry to format. /// /// \return A reference to output. std::ostream& fs::operator<<(std::ostream& output, const directory_entry& entry) { output << F("directory_entry{name=%s}") % text::quote(entry.name, '\''); return output; } /// Internal implementation details for the directory_iterator. /// /// In order to support multiple concurrent iterators over the same directory /// object, this class is the one that performs all directory-level accesses. /// In particular, even if it may seem surprising, this is the class that /// handles the DIR object for the directory. /// /// Note that iterators implemented by this class do not rely on the container /// directory class at all. This should not be relied on for object lifecycle /// purposes. struct utils::fs::detail::directory_iterator::impl : utils::noncopyable { /// Path of the directory accessed by this iterator. const fs::path _path; /// Raw pointer to the system representation of the directory. /// /// We also use this to determine if the iterator is valid (at the end) or /// not. A null pointer means an invalid iterator. ::DIR* _dirp; /// Raw representation of the system directory entry. /// /// We need to keep this at the class level so that we can use the /// readdir_r(3) function. ::dirent _dirent; /// Custom representation of the directory entry. /// /// This is separate from _dirent because this is the type we return to the /// user. We must keep this as a pointer so that we can support the common /// operators (* and ->) over iterators. std::unique_ptr< directory_entry > _entry; /// Constructs an iterator pointing to the "end" of the directory. impl(void) : _path("invalid-directory-entry"), _dirp(NULL) { } /// Constructs a new iterator to start scanning a directory. /// /// \param path The directory that will be scanned. /// /// \throw system_error If there is a problem opening the directory. explicit impl(const path& path) : _path(path) { DIR* dirp = ::opendir(_path.c_str()); if (dirp == NULL) { const int original_errno = errno; throw fs::system_error(F("opendir(%s) failed") % _path, original_errno); } _dirp = dirp; // Initialize our first directory entry. Note that this may actually // close the directory we just opened if the directory happens to be // empty -- but directories are never empty because they at least have // '.' and '..' entries. next(); } /// Destructor. /// /// This closes the directory if still open. ~impl(void) { if (_dirp != NULL) close(); } /// Closes the directory and invalidates the iterator. void close(void) { PRE(_dirp != NULL); if (::closedir(_dirp) == -1) { UNREACHABLE_MSG("Invalid dirp provided to closedir(3)"); } _dirp = NULL; } /// Advances the directory entry to the next one. /// /// It is possible to use this function on a new directory_entry object to /// initialize the first entry. /// /// \throw system_error If the call to readdir_r fails. void next(void) { ::dirent* result; if (::readdir_r(_dirp, &_dirent, &result) == -1) { const int original_errno = errno; throw fs::system_error(F("readdir_r(%s) failed") % _path, original_errno); } if (result == NULL) { - _entry.reset(NULL); + _entry.reset(); close(); } else { _entry.reset(new directory_entry(_dirent.d_name)); } } }; /// Constructs a new directory iterator. /// /// \param pimpl The constructed internal implementation structure to use. detail::directory_iterator::directory_iterator(std::shared_ptr< impl > pimpl) : _pimpl(pimpl) { } /// Destructor. detail::directory_iterator::~directory_iterator(void) { } /// Creates a new directory iterator for a directory. /// /// \return The directory iterator. Note that the result may be invalid. /// /// \throw system_error If opening the directory or reading its first entry /// fails. detail::directory_iterator detail::directory_iterator::new_begin(const path& path) { return directory_iterator(std::shared_ptr< impl >(new impl(path))); } /// Creates a new invalid directory iterator. /// /// \return The invalid directory iterator. detail::directory_iterator detail::directory_iterator::new_end(void) { return directory_iterator(std::shared_ptr< impl >(new impl())); } /// Checks if two iterators are equal. /// /// We consider two iterators to be equal if both of them are invalid or, /// otherwise, if they have the exact same internal representation (as given by /// equality of the pimpl pointers). /// /// \param other The object to compare to. /// /// \return True if the two iterators are equal; false otherwise. bool detail::directory_iterator::operator==(const directory_iterator& other) const { return (_pimpl->_dirp == NULL && other._pimpl->_dirp == NULL) || _pimpl == other._pimpl; } /// Checks if two iterators are different. /// /// \param other The object to compare to. /// /// \return True if the two iterators are different; false otherwise. bool detail::directory_iterator::operator!=(const directory_iterator& other) const { return !(*this == other); } /// Moves the iterator one element forward. /// /// \return A reference to the iterator. /// /// \throw system_error If advancing the iterator fails. detail::directory_iterator& detail::directory_iterator::operator++(void) { _pimpl->next(); return *this; } /// Dereferences the iterator to its contents. /// /// \return A reference to the directory entry pointed to by the iterator. const fs::directory_entry& detail::directory_iterator::operator*(void) const { PRE(_pimpl->_entry.get() != NULL); return *_pimpl->_entry; } /// Dereferences the iterator to its contents. /// /// \return A pointer to the directory entry pointed to by the iterator. const fs::directory_entry* detail::directory_iterator::operator->(void) const { PRE(_pimpl->_entry.get() != NULL); return _pimpl->_entry.get(); } /// Internal implementation details for the directory. struct utils::fs::directory::impl : utils::noncopyable { /// Path to the directory to scan. fs::path _path; /// Constructs a new directory. /// /// \param path_ Path to the directory to scan. impl(const fs::path& path_) : _path(path_) { } }; /// Constructs a new directory. /// /// \param path_ Path to the directory to scan. fs::directory::directory(const path& path_) : _pimpl(new impl(path_)) { } /// Returns an iterator to start scanning the directory. /// /// \return An iterator on the directory. /// /// \throw system_error If the directory cannot be opened to obtain its first /// entry. fs::directory::const_iterator fs::directory::begin(void) const { return const_iterator::new_begin(_pimpl->_path); } /// Returns an invalid iterator to check for the end of an scan. /// /// \return An invalid iterator. fs::directory::const_iterator fs::directory::end(void) const { return const_iterator::new_end(); } diff --git a/contrib/kyua/utils/logging/operations.cpp b/contrib/kyua/utils/logging/operations.cpp index ce86183910b2..fbcf28b71169 100644 --- a/contrib/kyua/utils/logging/operations.cpp +++ b/contrib/kyua/utils/logging/operations.cpp @@ -1,303 +1,303 @@ // Copyright 2011 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/logging/operations.hpp" extern "C" { #include } #include #include #include #include #include "utils/datetime.hpp" #include "utils/format/macros.hpp" #include "utils/fs/path.hpp" #include "utils/optional.ipp" #include "utils/sanity.hpp" #include "utils/stream.hpp" namespace datetime = utils::datetime; namespace fs = utils::fs; namespace logging = utils::logging; using utils::none; using utils::optional; /// The general idea for the application-wide logging goes like this: /// /// 1. The application starts. Logging is initialized to capture _all_ log /// messages into memory regardless of their level by issuing a call to the /// set_inmemory() function. /// /// 2. The application offers the user a way to select the logging level and a /// file into which to store the log. /// /// 3. The application calls set_persistency providing a new log level and a log /// file. This must be done as early as possible, to minimize the chances of an /// early crash not capturing any logs. /// /// 4. At this point, any log messages stored into memory are flushed to disk /// respecting the provided log level. /// /// 5. The internal state of the logging module is updated to only capture /// messages that are of the provided log level (or below) and is configured to /// directly send messages to disk. /// /// 6. The user may choose to call set_inmemory() again at a later stage, which /// will cause the log to be flushed and messages to be recorded in memory /// again. This is useful in case the logs are being sent to either stdout or /// stderr and the process forks and wants to keep those child channels /// unpolluted. /// /// The call to set_inmemory() should only be performed by the user-facing /// application. Tests should skip this call so that the logging messages go to /// stderr by default, thus generating a useful log to debug the tests. namespace { /// Constant string to strftime to format timestamps. static const char* timestamp_format = "%Y%m%d-%H%M%S"; /// Mutable global state. struct global_state { /// Current log level. logging::level log_level; /// Indicates whether set_persistency() will be called automatically or not. bool auto_set_persistency; /// First time recorded by the logging module. optional< datetime::timestamp > first_timestamp; /// In-memory record of log entries before persistency is enabled. std::vector< std::pair< logging::level, std::string > > backlog; /// Stream to the currently open log file. std::unique_ptr< std::ostream > logfile; global_state() : log_level(logging::level_debug), auto_set_persistency(true) { } }; /// Single instance of the mutable global state. /// /// Note that this is a raw pointer that we intentionally leak. We must do /// this, instead of making all of the singleton's members static values, /// because we want other destructors in the program to be able to log critical /// conditions. If we use complex types in this translation unit, they may be /// destroyed before the logging methods in the destructors get a chance to run /// thus resulting in a premature crash. By using a plain pointer, we ensure /// this state never gets cleaned up. static struct global_state* globals_singleton = NULL; /// Gets the singleton instance of global_state. /// /// \return A pointer to the unique global_state instance. static struct global_state* get_globals(void) { if (globals_singleton == NULL) { globals_singleton = new global_state(); } return globals_singleton; } /// Converts a level to a printable character. /// /// \param level The level to convert. /// /// \return The printable character, to be used in log messages. static char level_to_char(const logging::level level) { switch (level) { case logging::level_error: return 'E'; case logging::level_warning: return 'W'; case logging::level_info: return 'I'; case logging::level_debug: return 'D'; default: UNREACHABLE; } } } // anonymous namespace /// Generates a standard log name. /// /// This always adds the same timestamp to the log name for a particular run. /// Also, the timestamp added to the file name corresponds to the first /// timestamp recorded by the module; it does not necessarily contain the /// current value of "now". /// /// \param logdir The path to the directory in which to place the log. /// \param progname The name of the program that is generating the log. /// /// \return A string representation of the log name based on \p logdir and /// \p progname. fs::path logging::generate_log_name(const fs::path& logdir, const std::string& progname) { struct global_state* globals = get_globals(); if (!globals->first_timestamp) globals->first_timestamp = datetime::timestamp::now(); // Update kyua(1) if you change the name format. return logdir / (F("%s.%s.log") % progname % globals->first_timestamp.get().strftime(timestamp_format)); } /// Logs an entry to the log file. /// /// If the log is not yet set to persistent mode, the entry is recorded in the /// in-memory backlog. Otherwise, it is just written to disk. /// /// \param message_level The level of the entry. /// \param file The file from which the log message is generated. /// \param line The line from which the log message is generated. /// \param user_message The raw message to store. void logging::log(const level message_level, const char* file, const int line, const std::string& user_message) { struct global_state* globals = get_globals(); const datetime::timestamp now = datetime::timestamp::now(); if (!globals->first_timestamp) globals->first_timestamp = now; if (globals->auto_set_persistency) { // These values are hardcoded here for testing purposes. The // application should call set_inmemory() by itself during // initialization to avoid this, so that it has explicit control on how // the call to set_persistency() happens. set_persistency("debug", fs::path("/dev/stderr")); globals->auto_set_persistency = false; } if (message_level > globals->log_level) return; // Update doc/troubleshooting.texi if you change the log format. const std::string message = F("%s %s %s %s:%s: %s") % now.strftime(timestamp_format) % level_to_char(message_level) % ::getpid() % file % line % user_message; if (globals->logfile.get() == NULL) globals->backlog.push_back(std::make_pair(message_level, message)); else { INV(globals->backlog.empty()); (*globals->logfile) << message << '\n'; globals->logfile->flush(); } } /// Sets the logging to record messages in memory for later flushing. /// /// Can be called after set_persistency to flush logs and set recording to be /// in-memory again. void logging::set_inmemory(void) { struct global_state* globals = get_globals(); globals->auto_set_persistency = false; if (globals->logfile.get() != NULL) { INV(globals->backlog.empty()); globals->logfile->flush(); - globals->logfile.reset(NULL); + globals->logfile.reset(); } } /// Makes the log persistent. /// /// Calling this function flushes the in-memory log, if any, to disk and sets /// the logging module to send log entries to disk from this point onwards. /// There is no way back, and the caller program should execute this function as /// early as possible to ensure that a crash at startup does not discard too /// many useful log entries. /// /// Any log entries above the provided new_level are discarded. /// /// \param new_level The new log level. /// \param path The file to write the logs to. /// /// \throw std::range_error If the given log level is invalid. /// \throw std::runtime_error If the given file cannot be created. void logging::set_persistency(const std::string& new_level, const fs::path& path) { struct global_state* globals = get_globals(); globals->auto_set_persistency = false; PRE(globals->logfile.get() == NULL); // Update doc/troubleshooting.info if you change the log levels. if (new_level == "debug") globals->log_level = level_debug; else if (new_level == "error") globals->log_level = level_error; else if (new_level == "info") globals->log_level = level_info; else if (new_level == "warning") globals->log_level = level_warning; else throw std::range_error(F("Unrecognized log level '%s'") % new_level); try { globals->logfile = utils::open_ostream(path); } catch (const std::runtime_error& unused_error) { throw std::runtime_error(F("Failed to create log file %s") % path); } for (std::vector< std::pair< logging::level, std::string > >::const_iterator iter = globals->backlog.begin(); iter != globals->backlog.end(); ++iter) { if ((*iter).first <= globals->log_level) (*globals->logfile) << (*iter).second << '\n'; } globals->logfile->flush(); globals->backlog.clear(); } diff --git a/contrib/kyua/utils/process/child.cpp b/contrib/kyua/utils/process/child.cpp index d554f1fd9fb5..36b6b6b3e51f 100644 --- a/contrib/kyua/utils/process/child.cpp +++ b/contrib/kyua/utils/process/child.cpp @@ -1,385 +1,385 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/process/child.ipp" extern "C" { #include #include #include #include #include } #include #include #include #include "utils/defs.hpp" #include "utils/format/macros.hpp" #include "utils/fs/path.hpp" #include "utils/logging/macros.hpp" #include "utils/noncopyable.hpp" #include "utils/process/exceptions.hpp" #include "utils/process/fdstream.hpp" #include "utils/process/operations.hpp" #include "utils/process/system.hpp" #include "utils/process/status.hpp" #include "utils/sanity.hpp" #include "utils/signals/interrupts.hpp" namespace utils { namespace process { /// Private implementation fields for child objects. struct child::impl : utils::noncopyable { /// The process identifier. pid_t _pid; /// The input stream for the process' stdout and stderr. May be NULL. std::unique_ptr< process::ifdstream > _output; /// Initializes private implementation data. /// /// \param pid The process identifier. /// \param output The input stream. Grabs ownership of the pointer. impl(const pid_t pid, process::ifdstream* output) : _pid(pid), _output(output) {} }; } // namespace process } // namespace utils namespace fs = utils::fs; namespace process = utils::process; namespace signals = utils::signals; namespace { /// Exception-based version of dup(2). /// /// \param old_fd The file descriptor to duplicate. /// \param new_fd The file descriptor to use as the duplicate. This is /// closed if it was open before the copy happens. /// /// \throw process::system_error If the call to dup2(2) fails. static void safe_dup(const int old_fd, const int new_fd) { if (process::detail::syscall_dup2(old_fd, new_fd) == -1) { const int original_errno = errno; throw process::system_error(F("dup2(%s, %s) failed") % old_fd % new_fd, original_errno); } } /// Exception-based version of open(2) to open (or create) a file for append. /// /// \param filename The file to open in append mode. /// /// \return The file descriptor for the opened or created file. /// /// \throw process::system_error If the call to open(2) fails. static int open_for_append(const fs::path& filename) { const int fd = process::detail::syscall_open( filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd == -1) { const int original_errno = errno; throw process::system_error(F("Failed to create %s because open(2) " "failed") % filename, original_errno); } return fd; } /// Logs the execution of another program. /// /// \param program The binary to execute. /// \param args The arguments to pass to the binary, without the program name. static void log_exec(const fs::path& program, const process::args_vector& args) { std::string plain_command = program.str(); for (process::args_vector::const_iterator iter = args.begin(); iter != args.end(); ++iter) plain_command += F(" %s") % *iter; LD(F("Executing %s") % plain_command); } } // anonymous namespace /// Prints out a fatal error and aborts. void utils::process::detail::report_error_and_abort(void) { std::cerr << "Caught unknown exception\n"; std::abort(); } /// Prints out a fatal error and aborts. /// /// \param error The error to display. void utils::process::detail::report_error_and_abort(const std::runtime_error& error) { std::cerr << "Caught runtime_error: " << error.what() << '\n'; std::abort(); } /// Creates a new child. /// /// \param implptr A dynamically-allocated impl object with the contents of the /// new child. process::child::child(impl *implptr) : _pimpl(implptr) { } /// Destructor for child. process::child::~child(void) { } /// Helper function for fork(). /// /// Please note: if you update this function to change the return type or to /// raise different errors, do not forget to update fork() accordingly. /// /// \return In the case of the parent, a new child object returned as a /// dynamically-allocated object because children classes are unique and thus /// noncopyable. In the case of the child, a NULL pointer. /// /// \throw process::system_error If the calls to pipe(2) or fork(2) fail. std::unique_ptr< process::child > process::child::fork_capture_aux(void) { std::cout.flush(); std::cerr.flush(); int fds[2]; if (detail::syscall_pipe(fds) == -1) throw process::system_error("pipe(2) failed", errno); std::unique_ptr< signals::interrupts_inhibiter > inhibiter( new signals::interrupts_inhibiter); pid_t pid = detail::syscall_fork(); if (pid == -1) { - inhibiter.reset(NULL); // Unblock signals. + inhibiter.reset(); // Unblock signals. ::close(fds[0]); ::close(fds[1]); throw process::system_error("fork(2) failed", errno); } else if (pid == 0) { - inhibiter.reset(NULL); // Unblock signals. + inhibiter.reset(); // Unblock signals. ::setsid(); try { ::close(fds[0]); safe_dup(fds[1], STDOUT_FILENO); safe_dup(fds[1], STDERR_FILENO); ::close(fds[1]); } catch (const system_error& e) { std::cerr << F("Failed to set up subprocess: %s\n") % e.what(); std::abort(); } return {}; } else { ::close(fds[1]); LD(F("Spawned process %s: stdout and stderr inherited") % pid); signals::add_pid_to_kill(pid); inhibiter.reset(NULL); // Unblock signals. return std::unique_ptr< process::child >( new process::child(new impl(pid, new process::ifdstream(fds[0])))); } } /// Helper function for fork(). /// /// Please note: if you update this function to change the return type or to /// raise different errors, do not forget to update fork() accordingly. /// /// \param stdout_file The name of the file in which to store the stdout. /// If this has the magic value /dev/stdout, then the parent's stdout is /// reused without applying any redirection. /// \param stderr_file The name of the file in which to store the stderr. /// If this has the magic value /dev/stderr, then the parent's stderr is /// reused without applying any redirection. /// /// \return In the case of the parent, a new child object returned as a /// dynamically-allocated object because children classes are unique and thus /// noncopyable. In the case of the child, a NULL pointer. /// /// \throw process::system_error If the call to fork(2) fails. std::unique_ptr< process::child > process::child::fork_files_aux(const fs::path& stdout_file, const fs::path& stderr_file) { std::cout.flush(); std::cerr.flush(); std::unique_ptr< signals::interrupts_inhibiter > inhibiter( new signals::interrupts_inhibiter); pid_t pid = detail::syscall_fork(); if (pid == -1) { - inhibiter.reset(NULL); // Unblock signals. + inhibiter.reset(); // Unblock signals. throw process::system_error("fork(2) failed", errno); } else if (pid == 0) { - inhibiter.reset(NULL); // Unblock signals. + inhibiter.reset(); // Unblock signals. ::setsid(); try { if (stdout_file != fs::path("/dev/stdout")) { const int stdout_fd = open_for_append(stdout_file); safe_dup(stdout_fd, STDOUT_FILENO); ::close(stdout_fd); } if (stderr_file != fs::path("/dev/stderr")) { const int stderr_fd = open_for_append(stderr_file); safe_dup(stderr_fd, STDERR_FILENO); ::close(stderr_fd); } } catch (const system_error& e) { std::cerr << F("Failed to set up subprocess: %s\n") % e.what(); std::abort(); } return {}; } else { LD(F("Spawned process %s: stdout=%s, stderr=%s") % pid % stdout_file % stderr_file); signals::add_pid_to_kill(pid); - inhibiter.reset(NULL); // Unblock signals. + inhibiter.reset(); // Unblock signals. return std::unique_ptr< process::child >( new process::child(new impl(pid, NULL))); } } /// Spawns a new binary and multiplexes and captures its stdout and stderr. /// /// If the subprocess cannot be completely set up for any reason, it attempts to /// dump an error message to its stderr channel and it then calls std::abort(). /// /// \param program The binary to execute. /// \param args The arguments to pass to the binary, without the program name. /// /// \return A new child object, returned as a dynamically-allocated object /// because children classes are unique and thus noncopyable. /// /// \throw process::system_error If the process cannot be spawned due to a /// system call error. std::unique_ptr< process::child > process::child::spawn_capture(const fs::path& program, const args_vector& args) { std::unique_ptr< child > child = fork_capture_aux(); if (child.get() == NULL) exec(program, args); log_exec(program, args); return child; } /// Spawns a new binary and redirects its stdout and stderr to files. /// /// If the subprocess cannot be completely set up for any reason, it attempts to /// dump an error message to its stderr channel and it then calls std::abort(). /// /// \param program The binary to execute. /// \param args The arguments to pass to the binary, without the program name. /// \param stdout_file The name of the file in which to store the stdout. /// \param stderr_file The name of the file in which to store the stderr. /// /// \return A new child object, returned as a dynamically-allocated object /// because children classes are unique and thus noncopyable. /// /// \throw process::system_error If the process cannot be spawned due to a /// system call error. std::unique_ptr< process::child > process::child::spawn_files(const fs::path& program, const args_vector& args, const fs::path& stdout_file, const fs::path& stderr_file) { std::unique_ptr< child > child = fork_files_aux(stdout_file, stderr_file); if (child.get() == NULL) exec(program, args); log_exec(program, args); return child; } /// Returns the process identifier of this child. /// /// \return A process identifier. int process::child::pid(void) const { return _pimpl->_pid; } /// Gets the input stream corresponding to the stdout and stderr of the child. /// /// \pre The child must have been started by fork_capture(). /// /// \return A reference to the input stream connected to the output of the test /// case. std::istream& process::child::output(void) { PRE(_pimpl->_output.get() != NULL); return *_pimpl->_output; } /// Blocks to wait for completion. /// /// \return The termination status of the child process. /// /// \throw process::system_error If the call to waitpid(2) fails. process::status process::child::wait(void) { return process::wait(_pimpl->_pid); } diff --git a/contrib/kyua/utils/process/executor.cpp b/contrib/kyua/utils/process/executor.cpp index 843c9d862304..bddbeee7605f 100644 --- a/contrib/kyua/utils/process/executor.cpp +++ b/contrib/kyua/utils/process/executor.cpp @@ -1,937 +1,937 @@ // Copyright 2015 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/process/executor.ipp" #if defined(HAVE_CONFIG_H) #include "config.h" #endif extern "C" { #include #include #include } #include #include #include #include #include #include #include "utils/datetime.hpp" #include "utils/format/macros.hpp" #include "utils/fs/auto_cleaners.hpp" #include "utils/fs/exceptions.hpp" #include "utils/fs/operations.hpp" #include "utils/fs/path.hpp" #include "utils/logging/macros.hpp" #include "utils/logging/operations.hpp" #include "utils/noncopyable.hpp" #include "utils/optional.ipp" #include "utils/passwd.hpp" #include "utils/process/child.ipp" #include "utils/process/deadline_killer.hpp" #include "utils/process/isolation.hpp" #include "utils/process/operations.hpp" #include "utils/process/status.hpp" #include "utils/sanity.hpp" #include "utils/signals/interrupts.hpp" #include "utils/signals/timer.hpp" namespace datetime = utils::datetime; namespace executor = utils::process::executor; namespace fs = utils::fs; namespace logging = utils::logging; namespace passwd = utils::passwd; namespace process = utils::process; namespace signals = utils::signals; using utils::none; using utils::optional; namespace { /// Template for temporary directories created by the executor. static const char* work_directory_template = PACKAGE_TARNAME ".XXXXXX"; /// Mapping of active subprocess PIDs to their execution data. typedef std::map< int, executor::exec_handle > exec_handles_map; } // anonymous namespace /// Basename of the file containing the stdout of the subprocess. const char* utils::process::executor::detail::stdout_name = "stdout.txt"; /// Basename of the file containing the stderr of the subprocess. const char* utils::process::executor::detail::stderr_name = "stderr.txt"; /// Basename of the subdirectory in which the subprocess is actually executed. /// /// This is a subdirectory of the "unique work directory" generated for the /// subprocess so that our code can create control files on disk and not /// get them clobbered by the subprocess's activity. const char* utils::process::executor::detail::work_subdir = "work"; /// Prepares a subprocess to run a user-provided hook in a controlled manner. /// /// \param unprivileged_user User to switch to if not none. /// \param control_directory Path to the subprocess-specific control directory. /// \param work_directory Path to the subprocess-specific work directory. void utils::process::executor::detail::setup_child( const optional< passwd::user > unprivileged_user, const fs::path& control_directory, const fs::path& work_directory) { logging::set_inmemory(); process::isolate_path(unprivileged_user, control_directory); process::isolate_child(unprivileged_user, work_directory); } /// Internal implementation for the exec_handle class. struct utils::process::executor::exec_handle::impl : utils::noncopyable { /// PID of the process being run. int pid; /// Path to the subprocess-specific work directory. fs::path control_directory; /// Path to the subprocess's stdout file. const fs::path stdout_file; /// Path to the subprocess's stderr file. const fs::path stderr_file; /// Start time. datetime::timestamp start_time; /// User the subprocess is running as if different than the current one. const optional< passwd::user > unprivileged_user; /// Timer to kill the subprocess on activation. process::deadline_killer timer; /// Number of owners of the on-disk state. executor::detail::refcnt_t state_owners; /// Constructor. /// /// \param pid_ PID of the forked process. /// \param control_directory_ Path to the subprocess-specific work /// directory. /// \param stdout_file_ Path to the subprocess's stdout file. /// \param stderr_file_ Path to the subprocess's stderr file. /// \param start_time_ Timestamp of when this object was constructed. /// \param timeout Maximum amount of time the subprocess can run for. /// \param unprivileged_user_ User the subprocess is running as if /// different than the current one. /// \param [in,out] state_owners_ Number of owners of the on-disk state. /// For first-time processes, this should be a new counter set to 0; /// for followup processes, this should point to the same counter used /// by the preceding process. impl(const int pid_, const fs::path& control_directory_, const fs::path& stdout_file_, const fs::path& stderr_file_, const datetime::timestamp& start_time_, const datetime::delta& timeout, const optional< passwd::user > unprivileged_user_, executor::detail::refcnt_t state_owners_) : pid(pid_), control_directory(control_directory_), stdout_file(stdout_file_), stderr_file(stderr_file_), start_time(start_time_), unprivileged_user(unprivileged_user_), timer(timeout, pid_), state_owners(state_owners_) { (*state_owners)++; POST(*state_owners > 0); } }; /// Constructor. /// /// \param pimpl Constructed internal implementation. executor::exec_handle::exec_handle(std::shared_ptr< impl > pimpl) : _pimpl(pimpl) { } /// Destructor. executor::exec_handle::~exec_handle(void) { } /// Returns the PID of the process being run. /// /// \return A PID. int executor::exec_handle::pid(void) const { return _pimpl->pid; } /// Returns the path to the subprocess-specific control directory. /// /// This is where the executor may store control files. /// /// \return The path to a directory that exists until cleanup() is called. fs::path executor::exec_handle::control_directory(void) const { return _pimpl->control_directory; } /// Returns the path to the subprocess-specific work directory. /// /// This is guaranteed to be clear of files created by the executor. /// /// \return The path to a directory that exists until cleanup() is called. fs::path executor::exec_handle::work_directory(void) const { return _pimpl->control_directory / detail::work_subdir; } /// Returns the path to the subprocess's stdout file. /// /// \return The path to a file that exists until cleanup() is called. const fs::path& executor::exec_handle::stdout_file(void) const { return _pimpl->stdout_file; } /// Returns the path to the subprocess's stderr file. /// /// \return The path to a file that exists until cleanup() is called. const fs::path& executor::exec_handle::stderr_file(void) const { return _pimpl->stderr_file; } /// Internal implementation for the exit_handle class. struct utils::process::executor::exit_handle::impl : utils::noncopyable { /// Original PID of the terminated subprocess. /// /// Note that this PID is no longer valid and cannot be used on system /// tables! const int original_pid; /// Termination status of the subprocess, or none if it timed out. const optional< process::status > status; /// The user the process ran as, if different than the current one. const optional< passwd::user > unprivileged_user; /// Timestamp of when the subprocess was spawned. const datetime::timestamp start_time; /// Timestamp of when wait() or wait_any() returned this object. const datetime::timestamp end_time; /// Path to the subprocess-specific work directory. const fs::path control_directory; /// Path to the subprocess's stdout file. const fs::path stdout_file; /// Path to the subprocess's stderr file. const fs::path stderr_file; /// Number of owners of the on-disk state. /// /// This will be 1 if this exit_handle is the last holder of the on-disk /// state, in which case cleanup() invocations will wipe the disk state. /// For all other cases, this will hold a higher value. detail::refcnt_t state_owners; /// Mutable pointer to the corresponding executor state. /// /// This object references a member of the executor_handle that yielded this /// exit_handle instance. We need this direct access to clean up after /// ourselves when the handle is destroyed. exec_handles_map& all_exec_handles; /// Whether the subprocess state has been cleaned yet or not. /// /// Used to keep track of explicit calls to the public cleanup(). bool cleaned; /// Constructor. /// /// \param original_pid_ Original PID of the terminated subprocess. /// \param status_ Termination status of the subprocess, or none if /// timed out. /// \param unprivileged_user_ The user the process ran as, if different than /// the current one. /// \param start_time_ Timestamp of when the subprocess was spawned. /// \param end_time_ Timestamp of when wait() or wait_any() returned this /// object. /// \param control_directory_ Path to the subprocess-specific work /// directory. /// \param stdout_file_ Path to the subprocess's stdout file. /// \param stderr_file_ Path to the subprocess's stderr file. /// \param [in,out] state_owners_ Number of owners of the on-disk state. /// \param [in,out] all_exec_handles_ Global object keeping track of all /// active executions for an executor. This is a pointer to a member of /// the executor_handle object. impl(const int original_pid_, const optional< process::status > status_, const optional< passwd::user > unprivileged_user_, const datetime::timestamp& start_time_, const datetime::timestamp& end_time_, const fs::path& control_directory_, const fs::path& stdout_file_, const fs::path& stderr_file_, detail::refcnt_t state_owners_, exec_handles_map& all_exec_handles_) : original_pid(original_pid_), status(status_), unprivileged_user(unprivileged_user_), start_time(start_time_), end_time(end_time_), control_directory(control_directory_), stdout_file(stdout_file_), stderr_file(stderr_file_), state_owners(state_owners_), all_exec_handles(all_exec_handles_), cleaned(false) { } /// Destructor. ~impl(void) { if (!cleaned) { LW(F("Implicitly cleaning up exit_handle for exec_handle %s; " "ignoring errors!") % original_pid); try { cleanup(); } catch (const std::runtime_error& error) { LE(F("Subprocess cleanup failed: %s") % error.what()); } } } /// Cleans up the subprocess on-disk state. /// /// \throw engine::error If the cleanup fails, especially due to the /// inability to remove the work directory. void cleanup(void) { PRE(*state_owners > 0); if (*state_owners == 1) { LI(F("Cleaning up exit_handle for exec_handle %s") % original_pid); fs::rm_r(control_directory); } else { LI(F("Not cleaning up exit_handle for exec_handle %s; " "%s owners left") % original_pid % (*state_owners - 1)); } // We must decrease our reference only after we have successfully // cleaned up the control directory. Otherwise, the rm_r call would // throw an exception, which would in turn invoke the implicit cleanup // from the destructor, which would make us crash due to an invalid // reference count. (*state_owners)--; // Marking this object as clean here, even if we did not do actually the // cleaning above, is fine (albeit a bit confusing). Note that "another // owner" refers to a handle for a different PID, so that handle will be // the one issuing the cleanup. all_exec_handles.erase(original_pid); cleaned = true; } }; /// Constructor. /// /// \param pimpl Constructed internal implementation. executor::exit_handle::exit_handle(std::shared_ptr< impl > pimpl) : _pimpl(pimpl) { } /// Destructor. executor::exit_handle::~exit_handle(void) { } /// Cleans up the subprocess status. /// /// This function should be called explicitly as it provides the means to /// control any exceptions raised during cleanup. Do not rely on the destructor /// to clean things up. /// /// \throw engine::error If the cleanup fails, especially due to the inability /// to remove the work directory. void executor::exit_handle::cleanup(void) { PRE(!_pimpl->cleaned); _pimpl->cleanup(); POST(_pimpl->cleaned); } /// Gets the current number of owners of the on-disk data. /// /// \return A shared reference counter. Even though this function is marked as /// const, the return value is intentionally mutable because we need to update /// reference counts from different but related processes. This is why this /// method is not public. std::shared_ptr< std::size_t > executor::exit_handle::state_owners(void) const { return _pimpl->state_owners; } /// Returns the original PID corresponding to the terminated subprocess. /// /// \return An exec_handle. int executor::exit_handle::original_pid(void) const { return _pimpl->original_pid; } /// Returns the process termination status of the subprocess. /// /// \return A process termination status, or none if the subprocess timed out. const optional< process::status >& executor::exit_handle::status(void) const { return _pimpl->status; } /// Returns the user the process ran as if different than the current one. /// /// \return None if the credentials of the process were the same as the current /// one, or else a user. const optional< passwd::user >& executor::exit_handle::unprivileged_user(void) const { return _pimpl->unprivileged_user; } /// Returns the timestamp of when the subprocess was spawned. /// /// \return A timestamp. const datetime::timestamp& executor::exit_handle::start_time(void) const { return _pimpl->start_time; } /// Returns the timestamp of when wait() or wait_any() returned this object. /// /// \return A timestamp. const datetime::timestamp& executor::exit_handle::end_time(void) const { return _pimpl->end_time; } /// Returns the path to the subprocess-specific control directory. /// /// This is where the executor may store control files. /// /// \return The path to a directory that exists until cleanup() is called. fs::path executor::exit_handle::control_directory(void) const { return _pimpl->control_directory; } /// Returns the path to the subprocess-specific work directory. /// /// This is guaranteed to be clear of files created by the executor. /// /// \return The path to a directory that exists until cleanup() is called. fs::path executor::exit_handle::work_directory(void) const { return _pimpl->control_directory / detail::work_subdir; } /// Returns the path to the subprocess's stdout file. /// /// \return The path to a file that exists until cleanup() is called. const fs::path& executor::exit_handle::stdout_file(void) const { return _pimpl->stdout_file; } /// Returns the path to the subprocess's stderr file. /// /// \return The path to a file that exists until cleanup() is called. const fs::path& executor::exit_handle::stderr_file(void) const { return _pimpl->stderr_file; } /// Internal implementation for the executor_handle. /// /// Because the executor is a singleton, these essentially is a container for /// global variables. struct utils::process::executor::executor_handle::impl : utils::noncopyable { /// Numeric counter of executed subprocesses. /// /// This is used to generate a unique identifier for each subprocess as an /// easy mechanism to discern their unique work directories. size_t last_subprocess; /// Interrupts handler. std::unique_ptr< signals::interrupts_handler > interrupts_handler; /// Root work directory for all executed subprocesses. std::unique_ptr< fs::auto_directory > root_work_directory; /// Mapping of PIDs to the data required at run time. exec_handles_map all_exec_handles; /// Former members of all_exec_handles removed due to PID reuse. std::forward_list stale_exec_handles; /// Whether the executor state has been cleaned yet or not. /// /// Used to keep track of explicit calls to the public cleanup(). bool cleaned; /// Constructor. impl(void) : last_subprocess(0), interrupts_handler(new signals::interrupts_handler()), root_work_directory(new fs::auto_directory( fs::auto_directory::mkdtemp_public(work_directory_template))), all_exec_handles(), stale_exec_handles(), cleaned(false) { } /// Destructor. ~impl(void) { if (!cleaned) { LW("Implicitly cleaning up executor; ignoring errors!"); try { cleanup(); cleaned = true; } catch (const std::runtime_error& error) { LE(F("Executor global cleanup failed: %s") % error.what()); } } } /// Cleans up the executor state. void cleanup(void) { PRE(!cleaned); for (exec_handles_map::const_iterator iter = all_exec_handles.begin(); iter != all_exec_handles.end(); ++iter) { const int& pid = (*iter).first; const exec_handle& data = (*iter).second; process::terminate_group(pid); int status; if (::waitpid(pid, &status, 0) == -1) { // Should not happen. LW(F("Failed to wait for PID %s") % pid); } try { fs::rm_r(data.control_directory()); } catch (const fs::error& e) { LE(F("Failed to clean up subprocess work directory %s: %s") % data.control_directory() % e.what()); } } all_exec_handles.clear(); for (auto iter : stale_exec_handles) { // The process already exited, so no need to kill and wait. try { fs::rm_r(iter.control_directory()); } catch (const fs::error& e) { LE(F("Failed to clean up stale subprocess work directory " "%s: %s") % iter.control_directory() % e.what()); } } stale_exec_handles.clear(); try { // The following only causes the work directory to be deleted, not // any of its contents, so we expect this to always succeed. This // *should* be sufficient because, in the loop above, we have // individually wiped the subdirectories of any still-unclean // subprocesses. root_work_directory->cleanup(); } catch (const fs::error& e) { LE(F("Failed to clean up executor work directory %s: %s; " "this could be an internal error or a buggy test") % root_work_directory->directory() % e.what()); } - root_work_directory.reset(NULL); + root_work_directory.reset(); interrupts_handler->unprogram(); - interrupts_handler.reset(NULL); + interrupts_handler.reset(); } /// Common code to run after any of the wait calls. /// /// \param original_pid The PID of the terminated subprocess. /// \param status The exit status of the terminated subprocess. /// /// \return A pointer to an object describing the waited-for subprocess. executor::exit_handle post_wait(const int original_pid, const process::status& status) { PRE(original_pid == status.dead_pid()); LI(F("Waited for subprocess with exec_handle %s") % original_pid); process::terminate_group(status.dead_pid()); const exec_handles_map::iterator iter = all_exec_handles.find( original_pid); exec_handle& data = (*iter).second; data._pimpl->timer.unprogram(); // It is tempting to assert here (and old code did) that, if the timer // has fired, the process has been forcibly killed by us. This is not // always the case though: for short-lived processes and with very short // timeouts (think 1ms), it is possible for scheduling decisions to // allow the subprocess to finish while at the same time cause the timer // to fire. So we do not assert this any longer and just rely on the // timer expiration to check if the process timed out or not. If the // process did finish but the timer expired... oh well, we do not detect // this correctly but we don't care because this should not really // happen. if (!fs::exists(data.stdout_file())) { std::ofstream new_stdout(data.stdout_file().c_str()); } if (!fs::exists(data.stderr_file())) { std::ofstream new_stderr(data.stderr_file().c_str()); } return exit_handle(std::shared_ptr< exit_handle::impl >( new exit_handle::impl( data.pid(), data._pimpl->timer.fired() ? none : utils::make_optional(status), data._pimpl->unprivileged_user, data._pimpl->start_time, datetime::timestamp::now(), data.control_directory(), data.stdout_file(), data.stderr_file(), data._pimpl->state_owners, all_exec_handles))); } executor::exit_handle reap(const pid_t original_pid) { const exec_handles_map::iterator iter = all_exec_handles.find( original_pid); exec_handle& data = (*iter).second; data._pimpl->timer.unprogram(); if (!fs::exists(data.stdout_file())) { std::ofstream new_stdout(data.stdout_file().c_str()); } if (!fs::exists(data.stderr_file())) { std::ofstream new_stderr(data.stderr_file().c_str()); } return exit_handle(std::shared_ptr< exit_handle::impl >( new exit_handle::impl( data.pid(), none, data._pimpl->unprivileged_user, data._pimpl->start_time, datetime::timestamp::now(), data.control_directory(), data.stdout_file(), data.stderr_file(), data._pimpl->state_owners, all_exec_handles))); } }; /// Constructor. executor::executor_handle::executor_handle(void) throw() : _pimpl(new impl()) { } /// Destructor. executor::executor_handle::~executor_handle(void) { } /// Queries the path to the root of the work directory for all subprocesses. /// /// \return A path. const fs::path& executor::executor_handle::root_work_directory(void) const { return _pimpl->root_work_directory->directory(); } /// Cleans up the executor state. /// /// This function should be called explicitly as it provides the means to /// control any exceptions raised during cleanup. Do not rely on the destructor /// to clean things up. /// /// \throw engine::error If there are problems cleaning up the executor. void executor::executor_handle::cleanup(void) { PRE(!_pimpl->cleaned); _pimpl->cleanup(); _pimpl->cleaned = true; } /// Initializes the executor. /// /// \pre This function can only be called if there is no other executor_handle /// object alive. /// /// \return A handle to the operations of the executor. executor::executor_handle executor::setup(void) { return executor_handle(); } /// Pre-helper for the spawn() method. /// /// \return The created control directory for the subprocess. fs::path executor::executor_handle::spawn_pre(void) { signals::check_interrupt(); ++_pimpl->last_subprocess; const fs::path control_directory = _pimpl->root_work_directory->directory() / (F("%s") % _pimpl->last_subprocess); fs::mkdir_p(control_directory / detail::work_subdir, 0755); return control_directory; } /// Post-helper for the spawn() method. /// /// \param control_directory Control directory as returned by spawn_pre(). /// \param stdout_file Path to the subprocess' stdout. /// \param stderr_file Path to the subprocess' stderr. /// \param timeout Maximum amount of time the subprocess can run for. /// \param unprivileged_user If not none, user to switch to before execution. /// \param child The process created by spawn(). /// /// \return The execution handle of the started subprocess. executor::exec_handle executor::executor_handle::spawn_post( const fs::path& control_directory, const fs::path& stdout_file, const fs::path& stderr_file, const datetime::delta& timeout, const optional< passwd::user > unprivileged_user, std::unique_ptr< process::child > child) { const exec_handle handle(std::shared_ptr< exec_handle::impl >( new exec_handle::impl( child->pid(), control_directory, stdout_file, stderr_file, datetime::timestamp::now(), timeout, unprivileged_user, detail::refcnt_t(new detail::refcnt_t::element_type(0))))); const auto value = exec_handles_map::value_type(handle.pid(), handle); auto insert_pair = _pimpl->all_exec_handles.insert(value); if (!insert_pair.second) { LI(F("PID %s already in all_exec_handles") % handle.pid()); _pimpl->stale_exec_handles.push_front(insert_pair.first->second); _pimpl->all_exec_handles.erase(insert_pair.first); insert_pair = _pimpl->all_exec_handles.insert(value); INV_MSG(insert_pair.second, F("PID %s still in all_exec_handles") % handle.pid()); } LI(F("Spawned subprocess with exec_handle %s") % handle.pid()); return handle; } /// Pre-helper for the spawn_followup() method. void executor::executor_handle::spawn_followup_pre(void) { signals::check_interrupt(); } /// Post-helper for the spawn_followup() method. /// /// \param base Exit handle of the subprocess to use as context. /// \param timeout Maximum amount of time the subprocess can run for. /// \param child The process created by spawn_followup(). /// /// \return The execution handle of the started subprocess. executor::exec_handle executor::executor_handle::spawn_followup_post( const exit_handle& base, const datetime::delta& timeout, std::unique_ptr< process::child > child) { INV(*base.state_owners() > 0); const exec_handle handle(std::shared_ptr< exec_handle::impl >( new exec_handle::impl( child->pid(), base.control_directory(), base.stdout_file(), base.stderr_file(), datetime::timestamp::now(), timeout, base.unprivileged_user(), base.state_owners()))); const auto value = exec_handles_map::value_type(handle.pid(), handle); auto insert_pair = _pimpl->all_exec_handles.insert(value); if (!insert_pair.second) { LI(F("PID %s already in all_exec_handles") % handle.pid()); _pimpl->stale_exec_handles.push_front(insert_pair.first->second); _pimpl->all_exec_handles.erase(insert_pair.first); insert_pair = _pimpl->all_exec_handles.insert(value); INV_MSG(insert_pair.second, F("PID %s still in all_exec_handles") % handle.pid()); } LI(F("Spawned subprocess with exec_handle %s") % handle.pid()); return handle; } /// Waits for completion of any forked process. /// /// \param exec_handle The handle of the process to wait for. /// /// \return A pointer to an object describing the waited-for subprocess. executor::exit_handle executor::executor_handle::wait(const exec_handle exec_handle) { signals::check_interrupt(); const process::status status = process::wait(exec_handle.pid()); return _pimpl->post_wait(exec_handle.pid(), status); } /// Waits for completion of any forked process. /// /// \return A pointer to an object describing the waited-for subprocess. executor::exit_handle executor::executor_handle::wait_any(void) { signals::check_interrupt(); const process::status status = process::wait_any(); return _pimpl->post_wait(status.dead_pid(), status); } /// Forms exit_handle for the given PID subprocess. /// /// Can be used in the cases when we want to do cleanup(s) of a killed test /// subprocess, but we do not have exit handle as we usually do after normal /// wait mechanism. /// /// \return A pointer to an object describing the subprocess. executor::exit_handle executor::executor_handle::reap(const int pid) { return _pimpl->reap(pid); } /// Checks if an interrupt has fired. /// /// Calls to this function should be sprinkled in strategic places through the /// code protected by an interrupts_handler object. /// /// This is just a wrapper over signals::check_interrupt() to avoid leaking this /// dependency to the caller. /// /// \throw signals::interrupted_error If there has been an interrupt. void executor::executor_handle::check_interrupt(void) const { signals::check_interrupt(); } diff --git a/contrib/kyua/utils/signals/interrupts.cpp b/contrib/kyua/utils/signals/interrupts.cpp index 829df7cfbf1e..bb0d1c319856 100644 --- a/contrib/kyua/utils/signals/interrupts.cpp +++ b/contrib/kyua/utils/signals/interrupts.cpp @@ -1,309 +1,309 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/signals/interrupts.hpp" extern "C" { #include #include #include } #include #include #include #include "utils/logging/macros.hpp" #include "utils/process/operations.hpp" #include "utils/sanity.hpp" #include "utils/signals/exceptions.hpp" #include "utils/signals/programmer.hpp" namespace signals = utils::signals; namespace process = utils::process; namespace { /// The interrupt signal that fired, or -1 if none. static volatile int fired_signal = -1; /// Collection of PIDs. typedef std::set< pid_t > pids_set; /// List of processes to kill upon reception of a signal. static pids_set pids_to_kill; /// Programmer status for the SIGHUP signal. static std::unique_ptr< signals::programmer > sighup_handler; /// Programmer status for the SIGINT signal. static std::unique_ptr< signals::programmer > sigint_handler; /// Programmer status for the SIGTERM signal. static std::unique_ptr< signals::programmer > sigterm_handler; /// Signal mask to restore after exiting a signal inhibited section. static sigset_t global_old_sigmask; /// Whether there is an interrupts_handler object in existence or not. static bool interrupts_handler_active = false; /// Whether there is an interrupts_inhibiter object in existence or not. static std::size_t interrupts_inhibiter_active = 0; /// Generic handler to capture interrupt signals. /// /// From this handler, we record that an interrupt has happened so that /// check_interrupt() can know whether there execution has to be stopped or not. /// We also terminate any of our child processes (started by the /// utils::process::children class) so that any ongoing wait(2) system calls /// terminate. /// /// \param signo The signal that caused this handler to be called. static void signal_handler(const int signo) { static const char* message = "[-- Signal caught; please wait for " "cleanup --]\n"; if (::write(STDERR_FILENO, message, std::strlen(message)) == -1) { // We are exiting: the message printed here is only for informational // purposes. If we fail to print it (which probably means something // is really bad), there is not much we can do within the signal // handler, so just ignore this. } fired_signal = signo; for (pids_set::const_iterator iter = pids_to_kill.begin(); iter != pids_to_kill.end(); ++iter) { process::terminate_group(*iter); } } /// Installs signal handlers for potential interrupts. /// /// \pre Must not have been called before. /// \post The various sig*_handler global variables are atomically updated. static void setup_handlers(void) { PRE(sighup_handler.get() == NULL); PRE(sigint_handler.get() == NULL); PRE(sigterm_handler.get() == NULL); // Create the handlers on the stack first so that, if any of them fails, the // stack unwinding cleans things up. std::unique_ptr< signals::programmer > tmp_sighup_handler( new signals::programmer(SIGHUP, signal_handler)); std::unique_ptr< signals::programmer > tmp_sigint_handler( new signals::programmer(SIGINT, signal_handler)); std::unique_ptr< signals::programmer > tmp_sigterm_handler( new signals::programmer(SIGTERM, signal_handler)); // Now, update the global pointers, which is an operation that cannot fail. sighup_handler = std::move(tmp_sighup_handler); sigint_handler = std::move(tmp_sigint_handler); sigterm_handler = std::move(tmp_sigterm_handler); } /// Uninstalls the signal handlers installed by setup_handlers(). static void cleanup_handlers(void) { - sighup_handler->unprogram(); sighup_handler.reset(NULL); - sigint_handler->unprogram(); sigint_handler.reset(NULL); - sigterm_handler->unprogram(); sigterm_handler.reset(NULL); + sighup_handler->unprogram(); sighup_handler.reset(); + sigint_handler->unprogram(); sigint_handler.reset(); + sigterm_handler->unprogram(); sigterm_handler.reset(); } /// Masks the signals installed by setup_handlers(). /// /// \param[out] old_sigmask The old signal mask to save via the /// \code oset \endcode argument with sigprocmask(2). static void mask_signals(sigset_t* old_sigmask) { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGALRM); sigaddset(&mask, SIGHUP); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); const int ret = ::sigprocmask(SIG_BLOCK, &mask, old_sigmask); INV(ret != -1); } /// Resets the signal masking put in place by mask_signals(). /// /// \param[in] old_sigmask The old signal mask to restore via the /// \code set \endcode argument with sigprocmask(2). static void unmask_signals(sigset_t* old_sigmask) { const int ret = ::sigprocmask(SIG_SETMASK, old_sigmask, NULL); INV(ret != -1); } } // anonymous namespace /// Constructor that sets up the signal handlers. signals::interrupts_handler::interrupts_handler(void) : _programmed(false) { PRE(!interrupts_handler_active); setup_handlers(); _programmed = true; interrupts_handler_active = true; } /// Destructor that removes the signal handlers. /// /// Given that this is a destructor and it can't report errors back to the /// caller, the caller must attempt to call unprogram() on its own. signals::interrupts_handler::~interrupts_handler(void) { if (_programmed) { LW("Destroying still-programmed signals::interrupts_handler object"); try { unprogram(); } catch (const error& e) { UNREACHABLE; } } } /// Unprograms all signals captured by the interrupts handler. /// /// \throw system_error If the unprogramming of any signal fails. void signals::interrupts_handler::unprogram(void) { PRE(_programmed); // Modify the control variables first before unprogramming the handlers. If // we fail to do the latter, we do not want to try again because we will not // succeed (and we'll cause a crash due to failed preconditions). _programmed = false; interrupts_handler_active = false; cleanup_handlers(); fired_signal = -1; } /// Constructor that sets up signal masking. signals::interrupts_inhibiter::interrupts_inhibiter(void) { sigset_t old_sigmask; mask_signals(&old_sigmask); if (interrupts_inhibiter_active == 0) { global_old_sigmask = old_sigmask; } ++interrupts_inhibiter_active; } /// Destructor that removes signal masking. signals::interrupts_inhibiter::~interrupts_inhibiter(void) { if (interrupts_inhibiter_active > 1) { --interrupts_inhibiter_active; } else { interrupts_inhibiter_active = false; unmask_signals(&global_old_sigmask); } } /// Checks if an interrupt has fired. /// /// Calls to this function should be sprinkled in strategic places through the /// code protected by an interrupts_handler object. /// /// Only one call to this function will raise an exception per signal received. /// This is to allow executing cleanup actions without reraising interrupt /// exceptions unless the user has fired another interrupt. /// /// \throw interrupted_error If there has been an interrupt. void signals::check_interrupt(void) { if (fired_signal != -1) { const int original_fired_signal = fired_signal; fired_signal = -1; throw interrupted_error(original_fired_signal); } } /// Registers a child process to be killed upon reception of an interrupt. /// /// \pre Must be called with interrupts being inhibited. The caller must ensure /// that the call call to fork() and the addition of the PID happen atomically. /// /// \param pid The PID of the child process. Must not have been yet regsitered. void signals::add_pid_to_kill(const pid_t pid) { PRE(interrupts_inhibiter_active); PRE(pids_to_kill.find(pid) == pids_to_kill.end()); pids_to_kill.insert(pid); } /// Unregisters a child process previously registered via add_pid_to_kill(). /// /// \pre Must be called with interrupts being inhibited. This is not necessary, /// but pushing this to the caller simplifies our logic and provides consistency /// with the add_pid_to_kill() call. /// /// \param pid The PID of the child process. Must have been registered /// previously, and the process must have already been awaited for. void signals::remove_pid_to_kill(const pid_t pid) { PRE(interrupts_inhibiter_active); PRE(pids_to_kill.find(pid) != pids_to_kill.end()); pids_to_kill.erase(pid); } diff --git a/contrib/kyua/utils/signals/timer.cpp b/contrib/kyua/utils/signals/timer.cpp index eceaa7fcd4ff..044dec87c352 100644 --- a/contrib/kyua/utils/signals/timer.cpp +++ b/contrib/kyua/utils/signals/timer.cpp @@ -1,547 +1,547 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/signals/timer.hpp" extern "C" { #include #include } #include #include #include #include #include "utils/datetime.hpp" #include "utils/format/macros.hpp" #include "utils/logging/macros.hpp" #include "utils/noncopyable.hpp" #include "utils/optional.ipp" #include "utils/sanity.hpp" #include "utils/signals/exceptions.hpp" #include "utils/signals/interrupts.hpp" #include "utils/signals/programmer.hpp" namespace datetime = utils::datetime; namespace signals = utils::signals; using utils::none; using utils::optional; namespace { static void sigalrm_handler(const int); /// Calls setitimer(2) with exception-based error reporting. /// /// This does not currently support intervals. /// /// \param delta The time to the first activation of the programmed timer. /// \param old_timeval If not NULL, pointer to a timeval into which to store the /// existing system timer. /// /// \throw system_error If the call to setitimer(2) fails. static void safe_setitimer(const datetime::delta& delta, itimerval* old_timeval) { ::itimerval timeval; timerclear(&timeval.it_interval); timeval.it_value.tv_sec = delta.seconds; timeval.it_value.tv_usec = delta.useconds; if (::setitimer(ITIMER_REAL, &timeval, old_timeval) == -1) { const int original_errno = errno; throw signals::system_error("Failed to program system's interval timer", original_errno); } } /// Deadline scheduler for all user timers on top of the unique system timer. class global_state : utils::noncopyable { /// Collection of active timers. /// /// Because this is a collection of pointers, all timers are guaranteed to /// be unique, and we want all of these pointers to be valid. typedef std::set< signals::timer* > timers_set; /// Sequence of ordered timers. typedef std::vector< signals::timer* > timers_vector; /// Collection of active timestamps by their activation timestamp. /// /// This collection is ordered intentionally so that it can be scanned /// sequentially to find either expired or expiring-now timers. typedef std::map< datetime::timestamp, timers_set > timers_by_timestamp_map; /// The original timer before any timer was programmed. ::itimerval _old_timeval; /// Programmer for the SIGALRM handler. std::unique_ptr< signals::programmer > _sigalrm_programmer; /// Time of the current activation of the timer. datetime::timestamp _timer_activation; /// Mapping of all active timers using their timestamp as the key. timers_by_timestamp_map _all_timers; /// Adds a timer to the _all_timers map. /// /// \param timer The timer to add. void add_to_all_timers(signals::timer* timer) { timers_set& timers = _all_timers[timer->when()]; INV(timers.find(timer) == timers.end()); timers.insert(timer); } /// Removes a timer from the _all_timers map. /// /// This ensures that empty vectors are removed from _all_timers if the /// removal of the timer causes its bucket to be emptied. /// /// \param timer The timer to remove. void remove_from_all_timers(signals::timer* timer) { // We may not find the timer in _all_timers if the timer has fired, // because fire() took it out from the map. timers_by_timestamp_map::iterator iter = _all_timers.find( timer->when()); if (iter != _all_timers.end()) { timers_set& timers = (*iter).second; INV(timers.find(timer) != timers.end()); timers.erase(timer); if (timers.empty()) { _all_timers.erase(iter); } } } /// Calculates all timers to execute at this timestamp. /// /// \param now The current timestamp. /// /// \post _all_timers is updated to contain only the timers that are /// strictly in the future. /// /// \return A sequence of valid timers that need to be invoked in the order /// of activation. These are all previously registered timers with /// activations in the past. timers_vector compute_timers_to_run_and_prune_old( const datetime::timestamp& now, const signals::interrupts_inhibiter& /* inhibiter */) { timers_vector to_run; timers_by_timestamp_map::iterator iter = _all_timers.begin(); while (iter != _all_timers.end() && (*iter).first <= now) { const timers_set& timers = (*iter).second; to_run.insert(to_run.end(), timers.begin(), timers.end()); // Remove expired entries here so that we can always assume that // the first entry in all_timers corresponds to the next // activation. const timers_by_timestamp_map::iterator previous_iter = iter; ++iter; _all_timers.erase(previous_iter); } return to_run; } /// Adjusts the global system timer to point to the next activation. /// /// \param now The current timestamp. /// /// \throw system_error If the programming fails. void reprogram_system_timer( const datetime::timestamp& now, const signals::interrupts_inhibiter& /* inhibiter */) { if (_all_timers.empty()) { // Nothing to do. We can reach this case if all the existing timers // are in the past and they all fired. Just ignore the request and // leave the global timer as is. return; } // While fire() prunes old entries from the list of timers, it is // possible for this routine to run with "expired" timers (i.e. timers // whose deadline lies in the past but that have not yet fired for // whatever reason that is out of our control) in the list. We have to // iterate until we find the next activation instead of assuming that // the first entry represents the desired value. timers_by_timestamp_map::const_iterator iter = _all_timers.begin(); PRE(!(*iter).second.empty()); datetime::timestamp next = (*iter).first; while (next < now) { ++iter; if (iter == _all_timers.end()) { // Nothing to do. We can reach this case if all the existing // timers are in the past but they have not yet fired. return; } PRE(!(*iter).second.empty()); next = (*iter).first; } if (next < _timer_activation || now > _timer_activation) { INV(next >= now); const datetime::delta delta = next - now; LD(F("Reprogramming timer; firing on %s; now is %s") % next % now); safe_setitimer(delta, NULL); _timer_activation = next; } } public: /// Programs the first timer. /// /// The programming of the first timer involves setting up the SIGALRM /// handler and installing a timer handler for the first time, which in turn /// involves keeping track of the old handlers so that we can restore them. /// /// \param timer The timer being programmed. /// \param now The current timestamp. /// /// \throw system_error If the programming fails. global_state(signals::timer* timer, const datetime::timestamp& now) : _timer_activation(timer->when()) { PRE(now < timer->when()); signals::interrupts_inhibiter inhibiter; const datetime::delta delta = timer->when() - now; LD(F("Installing first timer; firing on %s; now is %s") % timer->when() % now); _sigalrm_programmer.reset( new signals::programmer(SIGALRM, sigalrm_handler)); try { safe_setitimer(delta, &_old_timeval); _timer_activation = timer->when(); add_to_all_timers(timer); } catch (...) { - _sigalrm_programmer.reset(NULL); + _sigalrm_programmer.reset(); throw; } } /// Unprograms all timers. /// /// This clears the global system timer and unsets the SIGALRM handler. ~global_state(void) { signals::interrupts_inhibiter inhibiter; LD("Unprogramming all timers"); if (::setitimer(ITIMER_REAL, &_old_timeval, NULL) == -1) { UNREACHABLE_MSG("Failed to restore original timer"); } _sigalrm_programmer->unprogram(); - _sigalrm_programmer.reset(NULL); + _sigalrm_programmer.reset(); } /// Programs a new timer, possibly adjusting the global system timer. /// /// Programming any timer other than the first one only involves reloading /// the existing timer, not backing up the previous handler nor installing a /// handler for SIGALRM. /// /// \param timer The timer being programmed. /// \param now The current timestamp. /// /// \throw system_error If the programming fails. void program_new(signals::timer* timer, const datetime::timestamp& now) { signals::interrupts_inhibiter inhibiter; add_to_all_timers(timer); reprogram_system_timer(now, inhibiter); } /// Unprograms a timer. /// /// This removes the timer from the global state and reprograms the global /// system timer if necessary. /// /// \param timer The timer to unprogram. /// /// \return True if the system interval timer has been reprogrammed to /// another future timer; false if there are no more active timers. bool unprogram(signals::timer* timer) { signals::interrupts_inhibiter inhibiter; LD(F("Unprogramming timer; previously firing on %s") % timer->when()); remove_from_all_timers(timer); if (_all_timers.empty()) { return false; } else { reprogram_system_timer(datetime::timestamp::now(), inhibiter); return true; } } /// Executes active timers. /// /// Active timers are all those that fire on or before 'now'. /// /// \param now The current time. void fire(const datetime::timestamp& now) { timers_vector to_run; { signals::interrupts_inhibiter inhibiter; to_run = compute_timers_to_run_and_prune_old(now, inhibiter); reprogram_system_timer(now, inhibiter); } for (timers_vector::iterator iter = to_run.begin(); iter != to_run.end(); ++iter) { signals::detail::invoke_do_fired(*iter); } } }; /// Unique instance of the global state. static std::unique_ptr< global_state > globals; /// SIGALRM handler for the timer implementation. /// /// \param signo The signal received; must be SIGALRM. static void sigalrm_handler(const int signo) { PRE(signo == SIGALRM); globals->fire(datetime::timestamp::now()); } } // anonymous namespace /// Indirection to invoke the private do_fired() method of a timer. /// /// \param timer The timer on which to run do_fired(). void utils::signals::detail::invoke_do_fired(timer* timer) { timer->do_fired(); } /// Internal implementation for the timer. /// /// We assume that there is a 1-1 mapping between timer objects and impl /// objects. If this assumption breaks, then the rest of the code in this /// module breaks as well because we use pointers to the parent timer as the /// identifier of the timer. struct utils::signals::timer::impl : utils::noncopyable { /// Timestamp when this timer is expected to fire. /// /// Note that the timer might be processed after this timestamp, so users of /// this field need to check for timers that fire on or before the /// activation time. datetime::timestamp when; /// True until unprogram() is called. bool programmed; /// Whether this timer has fired already or not. /// /// This is updated from an interrupt context, hence why it is marked /// volatile. volatile bool fired; /// Constructor. /// /// \param when_ Timestamp when this timer is expected to fire. impl(const datetime::timestamp& when_) : when(when_), programmed(true), fired(false) { } /// Destructor. ~impl(void) { } }; /// Constructor; programs a run-once timer. /// /// This programs the global timer and signal handler if this is the first timer /// being installed. Otherwise, reprograms the global timer if this timer /// expires earlier than all other active timers. /// /// \param delta The time until the timer fires. signals::timer::timer(const datetime::delta& delta) { signals::interrupts_inhibiter inhibiter; const datetime::timestamp now = datetime::timestamp::now(); _pimpl.reset(new impl(now + delta)); if (globals.get() == NULL) { globals.reset(new global_state(this, now)); } else { globals->program_new(this, now); } } /// Destructor; unprograms the timer if still programmed. /// /// Given that this is a destructor and it can't report errors back to the /// caller, the caller must attempt to call unprogram() on its own. This is /// extremely important because, otherwise, expired timers will never run! signals::timer::~timer(void) { signals::interrupts_inhibiter inhibiter; if (_pimpl->programmed) { LW("Auto-destroying still-programmed signals::timer object"); try { unprogram(); } catch (const system_error& e) { UNREACHABLE; } } if (!_pimpl->fired) { const datetime::timestamp now = datetime::timestamp::now(); if (now > _pimpl->when) { LW("Expired timer never fired; the code never called unprogram()!"); } } } /// Returns the time of the timer activation. /// /// \return A timestamp that has no relation to the current time (i.e. can be in /// the future or in the past) nor the timer's activation status. const datetime::timestamp& signals::timer::when(void) const { return _pimpl->when; } /// Callback for the SIGALRM handler when this timer expires. /// /// \warning This is executed from a signal handler context without signals /// inhibited. See signal(7) for acceptable system calls. void signals::timer::do_fired(void) { PRE(!_pimpl->fired); _pimpl->fired = true; callback(); } /// User-provided callback to run when the timer expires. /// /// The default callback does nothing. We record the activation of the timer /// separately, which may be appropriate in the majority of the cases. /// /// \warning This is executed from a signal handler context without signals /// inhibited. See signal(7) for acceptable system calls. void signals::timer::callback(void) { // Intentionally left blank. } /// Checks whether the timer has fired already or not. /// /// \return Returns true if the timer has fired. bool signals::timer::fired(void) const { return _pimpl->fired; } /// Unprograms the timer. /// /// \pre The timer is programmed (i.e. this can only be called once). /// /// \post If the timer never fired asynchronously because the signal delivery /// did not arrive on time, make sure we invoke the timer's callback here. /// /// \throw system_error If unprogramming the timer failed. void signals::timer::unprogram(void) { signals::interrupts_inhibiter inhibiter; if (!_pimpl->programmed) { // We cannot assert that the timer is not programmed because it might // have been unprogrammed asynchronously between the time we called // unprogram() and the time we reach this. Simply return in this case. LD("Called unprogram on already-unprogrammed timer; possibly just " "a race"); return; } if (!globals->unprogram(this)) { - globals.reset(NULL); + globals.reset(); } _pimpl->programmed = false; // Handle the case where the timer has expired before we ever got its // corresponding signal. Do so by invoking its callback now. if (!_pimpl->fired) { const datetime::timestamp now = datetime::timestamp::now(); if (now > _pimpl->when) { LW(F("Firing expired timer on destruction (was to fire on %s)") % _pimpl->when); do_fired(); } } }