From cce63aca270c51fb3ce9790438e3c23864de0a87 Mon Sep 17 00:00:00 2001 From: Adrian Kummerlaender Date: Wed, 23 Dec 2015 21:43:51 +0100 Subject: Implement file change tracking using `diff` The newly introduced `ChangeTracker` class is now keeping track of all tracked file in addition to spawning and managing a corresponding `diff` instance that enables printing pretty _patch-style_ change summaries to the logging target. This commit introduces `boost-process` and `diff` as dependencies of this library. --- CMakeLists.txt | 13 ++++++- src/change_log.cc | 36 +++++++------------- src/change_tracker.cc | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/change_tracker.h | 32 ++++++++++++++++++ src/utility.h | 26 +++++++++++--- 5 files changed, 171 insertions(+), 30 deletions(-) create mode 100644 src/change_tracker.cc create mode 100644 src/change_tracker.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a9596fe..2be4520 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,4 +10,15 @@ include_directories( src/ ) -add_library(ChangeLog SHARED src/change_log.cc) +add_library( + ChangeLog + SHARED + src/change_log.cc + src/change_tracker.cc +) + +target_link_libraries( + ChangeLog + boost_system + boost_filesystem +) diff --git a/src/change_log.cc b/src/change_log.cc index a9e485f..e128383 100644 --- a/src/change_log.cc +++ b/src/change_log.cc @@ -4,20 +4,18 @@ #include #include -#include #include "io.h" #include "utility.h" #include "actual_function.h" +#include "change_tracker.h" -static std::unique_ptr> tracked_files; -static std::unique_ptr fd_guard; -static std::unique_ptr logger; +static std::unique_ptr fd_guard; +static std::unique_ptr logger; +static std::unique_ptr tracker; void init() __attribute__ ((constructor)); void init() { - tracked_files = std::make_unique>(); - if ( getenv("CHANGE_LOG_TARGET") != NULL ) { fd_guard = std::make_unique( getenv("CHANGE_LOG_TARGET") @@ -26,6 +24,8 @@ void init() { } else { logger = std::make_unique(STDERR_FILENO); } + + tracker = std::make_unique(logger.get()); } void exit(int status) { @@ -39,22 +39,12 @@ void exit(int status) { actual_exit(status); } -bool is_tracked_file(const std::string& file_name) { - return tracked_files->find(file_name) != tracked_files->end(); -} - -bool track_file(const std::string& file_name) { - return tracked_files->emplace(file_name).second; -} - ssize_t write(int fd, const void* buffer, size_t count) { if ( io::is_regular_file(fd) ) { const std::string file_name{ io::get_file_name(fd) }; - if ( !is_tracked_file(file_name) ) { - track_file(file_name); - - logger->append("wrote to '" + file_name + "'"); + if ( !tracker->is_tracked(file_name) ) { + tracker->track(file_name); } } @@ -62,8 +52,8 @@ ssize_t write(int fd, const void* buffer, size_t count) { } int rename(const char* old_path, const char* new_path) { - if ( !is_tracked_file(old_path) ) { - track_file(old_path); + if ( !tracker->is_tracked(old_path) ) { + tracker->track(old_path); } logger->append("renamed '" + std::string(old_path) + "' to '" + std::string(new_path) + "'"); @@ -99,10 +89,8 @@ void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) if ( ( prot & PROT_WRITE ) && io::is_regular_file(fd) ) { const std::string file_name{ io::get_file_name(fd) }; - if ( !is_tracked_file(file_name) ) { - track_file(file_name); - - logger->append("mmap '" + file_name + "'"); + if ( !tracker->is_tracked(file_name) ) { + tracker->track(file_name); } } diff --git a/src/change_tracker.cc b/src/change_tracker.cc new file mode 100644 index 0000000..1373a8a --- /dev/null +++ b/src/change_tracker.cc @@ -0,0 +1,94 @@ +#include "change_tracker.h" + +#include + +#include +#include + +namespace { + +// constants for increasing pair access readability +constexpr unsigned int EMPLACE_SUCCESS = 1; +constexpr unsigned int FILE_NAME = 0; +constexpr unsigned int DIFF_PROCESS = 1; + +boost::process::context createContext() { + boost::process::context context; + + context.environment = boost::process::self::get_environment(); + context.stdout_behavior = boost::process::capture_stream(); + context.stdin_behavior = boost::process::capture_stream(); + + return context; +} + +} + +namespace utility { + +ChangeTracker::ChangeTracker(utility::Logger* logger): + logger_(logger), + children_() { } + +ChangeTracker::~ChangeTracker() { + for ( auto&& tracked : this->children_ ) { + std::get(tracked)->get_stdin().close(); + + this->logger_->forward(std::get(tracked)->get_stdout()); + + tracked.second->wait(); + } +} + +bool ChangeTracker::is_tracked(const std::string& file_path) const { + return this->children_.find(file_path) != this->children_.end(); +} + +// Begins tracking changes to a file reachable by a given path +// +// The actual tracking is performed by a `diff` instance that is +// spawned by this method and managed by this class. +// As `diff` is called as a new subprocess and because there is no +// straight forward way for checking / enforcing if it has already +// completed reading the initial contents of the file the command +// is structured in the following sequence: +// +// diff -p - $file_path +// +// This means that reading the final file contents is delegated to +// `diff` while the initial file contents are read by this method +// and written to `diff`'s standard input. +// +// If the `-` and `$file_path` arguments were exchanged there would +// be no way to: +// - be sure the initial contents are read before the changing +// syscall that triggered this tracking in the first place is +// performed +// - be sure that the initial file contents are read completly +// as `diff` seemingly only reads the first block of the first +// file provided if the second file argument is standard input +// +bool ChangeTracker::track(const std::string& file_path) { + auto result = this->children_.emplace( + file_path, + std::make_unique( + boost::process::launch_shell("diff -p - " + file_name, createContext()) + ) + ); + + if ( std::get(result) ) { + boost::filesystem::ifstream file( + std::get(*result.first) + ); + + if ( file.is_open() ) { + std::get(*result.first)->get_stdin() << file.rdbuf(); + } + + return true; + } else { + return false; + } +} + +} diff --git a/src/change_tracker.h b/src/change_tracker.h new file mode 100644 index 0000000..e8ffa32 --- /dev/null +++ b/src/change_tracker.h @@ -0,0 +1,32 @@ +#ifndef CHANGE_SRC_CHANGE_TRACKER_H_ +#define CHANGE_SRC_CHANGE_TRACKER_H_ + +#include "utility.h" + +#include + +#include + +namespace utility { + +class ChangeTracker { + public: + ChangeTracker(utility::Logger*); + ~ChangeTracker(); + + bool is_tracked(const std::string&) const; + + bool track(const std::string&); + + private: + utility::Logger* const logger_; + + std::unordered_map< + std::string, std::unique_ptr + > children_; + +}; + +} + +#endif // CHANGE_SRC_CHANGE_TRACKER_H_ diff --git a/src/utility.h b/src/utility.h index 96ee15c..1dc44ed 100644 --- a/src/utility.h +++ b/src/utility.h @@ -3,6 +3,8 @@ #include +#include + #include namespace utility { @@ -10,16 +12,30 @@ namespace utility { class Logger { public: Logger(const int target_fd): - buffer(target_fd, std::ios::out), - stream(&this->buffer) { } + buffer_(target_fd, std::ios::out), + stream_(&this->buffer) { } void append(const std::string& msg) { - this->stream << msg << std::endl; + this->stream_ << msg << std::endl; + } + + // Forward the contents of a given standard output stream to the log target + // + // While `this->stream_ << stream.rdbuf()` would be more effective it sadly + // does not work with `boost::process::pistream` due to a broken pipe error + // in conjunction with the required `boost::process::capture_stream` context + // flag. + // + void forward(boost::process::pistream& stream) { + this->stream << std::string( + (std::istreambuf_iterator(stream)), + (std::istreambuf_iterator()) + ); } private: - __gnu_cxx::stdio_filebuf buffer; - std::ostream stream; + __gnu_cxx::stdio_filebuf buffer_; + std::ostream stream_; }; -- cgit v1.2.3