Skip to content

Instantly share code, notes, and snippets.

@MetroWind
Last active February 7, 2022 07:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MetroWind/04bb797488a6f8c105fcd4e6ebe5feba to your computer and use it in GitHub Desktop.
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.
#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