Last active
February 7, 2022 07:14
-
-
Save MetroWind/04bb797488a6f8c105fcd4e6ebe5feba to your computer and use it in GitHub Desktop.
File system watcher for Linux. Currently it doesn’t watch for new directories, but it could be easily added.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <system_error> | |
#include <iostream> | |
#include <filesystem> | |
#include <unordered_map> | |
#include <array> | |
#include <string> | |
#include <unistd.h> | |
#include <sys/types.h> | |
#include <sys/inotify.h> | |
#include <signal.h> | |
constexpr size_t EVENT_SIZE = sizeof(inotify_event); | |
constexpr size_t BUF_LEN = 1024 * ( EVENT_SIZE + 16 ); | |
using Notifier = int; | |
using WatcherDirMap = std::unordered_map<int, std::string>; | |
namespace fs = std::filesystem; | |
static volatile int should_quit = false; | |
void intHandler(int _) | |
{ | |
should_quit = true; | |
std::cout << "Will quit after the next event." << std::endl; | |
} | |
enum class FileType { Dir, File, DontCare }; | |
FileType fileType(const fs::directory_entry& item) noexcept | |
{ | |
std::error_code Error; | |
auto Status = item.status(Error); | |
if(Error.value() == 0) | |
{ | |
switch(Status.type()) | |
{ | |
case fs::file_type::directory: | |
return FileType::Dir; | |
case fs::file_type::regular: | |
return FileType::File; | |
default: | |
return FileType::DontCare; | |
} | |
} | |
return FileType::DontCare; | |
} | |
class FileWatcher | |
{ | |
public: | |
static void handleEvent(const inotify_event& event, | |
std::string_view search, | |
const WatcherDirMap& watches) | |
{ | |
std::string path = watches.at(event.wd) + "/" + event.name; | |
if(path.find(search) == std::string::npos) | |
{ | |
return; | |
} | |
if(event.mask & IN_CREATE) | |
{ | |
std::cout << path << std::endl; | |
} | |
else if(event.mask & IN_DELETE) | |
{ | |
std::cout << "Removed " << path << std::endl; | |
} | |
} | |
FileWatcher() = delete; | |
explicit FileWatcher(std::string_view search_term) | |
: notify(inotify_init()), search(search_term) {} | |
~FileWatcher() | |
{ | |
for(auto& watch: watches) | |
{ | |
inotify_rm_watch(notify, watch.first); | |
} | |
close(notify); | |
} | |
void watch(const fs::path& dir) | |
{ | |
walkDir(dir); | |
startWatch([this](const auto& event){ | |
handleEvent(event, search, watches); | |
}); | |
} | |
private: | |
void walkDir(const fs::path& dir) | |
{ | |
for(const auto& item: fs::directory_iterator( | |
dir, fs::directory_options::skip_permission_denied)) | |
{ | |
std::string path = item.path().string(); | |
if(path == "/sys" || path == "/dev" || path == "/proc") | |
{ | |
continue; | |
} | |
FileType type = fileType(item); | |
int watch; | |
switch(type) | |
{ | |
case FileType::Dir: | |
watch = inotify_add_watch( | |
notify, path.c_str(), IN_CREATE | IN_DELETE); | |
watches[watch] = path; | |
walkDir(item.path()); | |
break; | |
case FileType::File: | |
if(path.find(search) != std::string::npos) | |
{ | |
std::cout << item.path().c_str() << "\n"; | |
} | |
break; | |
default: | |
break; | |
} | |
} | |
} | |
template <class Func> | |
void startWatch(Func what_to_do) | |
{ | |
std::array<char, BUF_LEN> buffer; | |
while(!should_quit) | |
{ | |
int length = read(notify, buffer.data(), BUF_LEN); | |
if(length < 0) | |
{ | |
perror("read"); | |
break; | |
} | |
// Our current event pointer | |
inotify_event* event = reinterpret_cast<inotify_event*>(buffer.data()); | |
// an end pointer so that we know when to stop looping below | |
inotify_event* end = reinterpret_cast<inotify_event*>(buffer.data() + length); | |
while(event < end) { | |
// Do your printing or whatever here | |
what_to_do(*event); | |
// Now move to the next event | |
event = reinterpret_cast<inotify_event*>( | |
reinterpret_cast<char*>(event) + sizeof(*event) + event->len); | |
} | |
} | |
} | |
private: | |
Notifier notify; | |
WatcherDirMap watches; | |
std::string search; | |
}; | |
void usage(char* prog_name) | |
{ | |
std::cout << "Usage: " << prog_name << " SUB_STR" << std::endl; | |
} | |
int main(int argc, char** argv) | |
{ | |
if(argc != 2) | |
{ | |
usage(argv[0]); | |
return -1; | |
} | |
signal(SIGINT, intHandler); | |
FileWatcher watcher(argv[1]); | |
watcher.watch("/"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment