#pragma once #if (defined(__linux__) || __ANDROID_API__) \ && ! defined(WATER_WATCHER_USE_WARTHOG) #include "wtr/watcher.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace detail::wtr::watcher::adapter { /* Must maintain that an enumerated value less than complete is either pending or a warning (which means "keep running"), that complete is a successful, terminal result (means "stop, ok"), and that a value greater than complete is an error which we should send along to the user after we (attempt to) clean up. */ enum class result : unsigned short { pending = 0, w, w_sys_not_watched, w_sys_phantom, w_sys_bad_fd, w_sys_bad_meta, w_sys_q_overflow, complete, e, e_sys_api_inotify, e_sys_api_fanotify, e_sys_api_epoll, e_sys_api_read, e_sys_api_eventfd, e_sys_ret, e_sys_lim_kernel_version, e_self_noent, e_self_ev_recv, }; struct ep { /* Number of events allowed to be given to do_event_recv. The same value is usually returned by a call to `epoll_wait`). Any number between 1 and INT_MAX should be fine... But lower is fine for us. We don't lose events if we 'miss' them, the events are still waiting in the next call to `epoll_wait`. */ static constexpr auto q_ulim = 64; /* The delay, in milliseconds, while `epoll_wait` will 'pause' us for until we are woken up. We check if we are still alive through the fd from our semaphore-like eventfd. */ static constexpr auto wake_ms = -1; int fd = -1; epoll_event interests[q_ulim]{}; }; inline constexpr auto to_str(result r) { // clang-format off switch (r) { case result::pending: return "pending@"; case result::w: return "w@"; case result::w_sys_not_watched: return "w/sys/not_watched@"; case result::w_sys_phantom: return "w/sys/phantom@"; case result::w_sys_bad_fd: return "w/sys/bad_fd@"; case result::w_sys_bad_meta: return "w/sys/bad_meta@"; case result::w_sys_q_overflow: return "w/sys/q_overflow@"; case result::complete: return "complete@"; case result::e: return "e@"; case result::e_sys_api_inotify: return "e/sys/api/inotify@"; case result::e_sys_api_fanotify: return "e/sys/api/fanotify@"; case result::e_sys_api_epoll: return "e/sys/api/epoll@"; case result::e_sys_api_read: return "e/sys/api/read@"; case result::e_sys_api_eventfd: return "e/sys/api/eventfd@"; case result::e_sys_ret: return "e/sys/ret@"; case result::e_sys_lim_kernel_version: return "e/sys/lim/kernel_version@"; case result::e_self_noent: return "e/self/noent@"; case result::e_self_ev_recv: return "e/self/ev_recv@"; default: return "e/unknown@"; } // clang-format on }; inline auto send_msg = [](result r, auto path, auto const& cb) { using et = enum ::wtr::watcher::event::effect_type; using pt = enum ::wtr::watcher::event::path_type; auto msg = std::string{to_str(r)}; cb({msg + path, et::other, pt::watcher}); }; inline auto make_ep(int ev_fs_fd, int ev_il_fd) -> ep { #if __ANDROID_API__ int fd = epoll_create(1); #else int fd = epoll_create1(EPOLL_CLOEXEC); #endif auto want_ev_fs = epoll_event{.events = EPOLLIN, .data{.fd = ev_fs_fd}}; auto want_ev_il = epoll_event{.events = EPOLLIN, .data{.fd = ev_il_fd}}; bool ctl_ok = fd >= 0 && epoll_ctl(fd, EPOLL_CTL_ADD, ev_fs_fd, &want_ev_fs) >= 0 && epoll_ctl(fd, EPOLL_CTL_ADD, ev_il_fd, &want_ev_il) >= 0; if (! ctl_ok && fd >= 0) close(fd), fd = -1; return ep{.fd = fd}; } inline auto is_dir(char const* const path) -> bool { struct stat s; return stat(path, &s) == 0 && S_ISDIR(s.st_mode); } /* $ echo time wtr.watcher / -ms 1 | sudo bash -E ... real 0m25.094s user 0m4.091s sys 0m20.856s $ sudo find / -type d | wc -l ... 784418 We could parallelize this, but it's never going to be instant. It might be worth it to start watching before we're done this hot find-and-mark path, despite not having a full picture. */ template inline auto walkdir_do(char const* const path, Fn const& f) -> void { if (DIR* d = opendir(path)) { f(path); while (dirent* de = readdir(d)) { char next[PATH_MAX]; char real[PATH_MAX]; if (de->d_type != DT_DIR) continue; if (strcmp(de->d_name, ".") == 0) continue; if (strcmp(de->d_name, "..") == 0) continue; if (snprintf(next, PATH_MAX, "%s/%s", path, de->d_name) <= 0) continue; if (! realpath(next, real)) continue; walkdir_do(real, f); } (void)closedir(d); } } } /* namespace detail::wtr::watcher::adapter */ #endif