Files
fa2json/fs-watcher.c
2026-02-14 02:00:29 +01:00

170 lines
4.6 KiB
C

#define _GNU_SOURCE /* Needed to get O_LARGEFILE definition */
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/fanotify.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
//json-writer.c prototypes
void json_write_filename_bytes(const char *input, size_t length, FILE *out);
size_t get_parent_dir(char* path, int mount_fd, struct file_handle* fh) {
int parent_fd = open_by_handle_at(mount_fd, fh, O_PATH | O_DIRECTORY);
if (parent_fd == -1) {
if (errno == ESTALE) {
fprintf(stderr, "File handle is no longer valid. File has been deleted\n");
return 0;
} else {
perror("open_by_handle_at");
exit(EXIT_FAILURE);
}
}
char procfd_path[PATH_MAX];
snprintf(procfd_path, sizeof(procfd_path), "/proc/self/fd/%d", parent_fd);
size_t path_len = readlink(procfd_path, path, PATH_MAX);
close(parent_fd);
return path_len;
}
static void handle_events(int fafd, int mount_fd) {
struct fanotify_event_metadata buf[200];
while (true) {
ssize_t size = read(fafd, buf, sizeof(buf));
if (size == -1 && errno != EAGAIN) {
perror("read");
exit(EXIT_FAILURE);
}
if (size > 0) {
const struct fanotify_event_metadata* metadata = buf;
while (FAN_EVENT_OK(metadata, size)) {
if (metadata->vers != FANOTIFY_METADATA_VERSION) {
fprintf(stderr, "Mismatch of fanotify metadata version.\n");
exit(EXIT_FAILURE);
}
struct timespec mono, wall;
clock_gettime(CLOCK_MONOTONIC, &mono);
clock_gettime(CLOCK_REALTIME, &wall);
fprintf(stdout, "{\"ts\": [%i, %i, %i, %i]", wall.tv_sec, wall.tv_nsec, mono.tv_sec, mono.tv_nsec);
char *ptr = (char *)(metadata + 1);
char *end = (char *)metadata + metadata->event_len;
int entry_index = 0;
while (ptr < end) {
struct fanotify_event_info_header *hdr = (struct fanotify_event_info_header *)ptr;
if (hdr->info_type == FAN_EVENT_INFO_TYPE_DFID_NAME ||
hdr->info_type == FAN_EVENT_INFO_TYPE_OLD_DFID_NAME ||
hdr->info_type == FAN_EVENT_INFO_TYPE_NEW_DFID_NAME) {
struct fanotify_event_info_fid *fid = (struct fanotify_event_info_fid *)hdr;
struct file_handle *fh = (struct file_handle *)fid->handle;
char parent_path[PATH_MAX];
size_t parent_path_length = get_parent_dir(parent_path, mount_fd, fh);
char *name = (char *)fh->f_handle + fh->handle_bytes;
if (hdr->info_type == FAN_EVENT_INFO_TYPE_OLD_DFID_NAME) {
fprintf(stdout, ", \"old\": ");
} else if (hdr->info_type == FAN_EVENT_INFO_TYPE_NEW_DFID_NAME) {
fprintf(stdout, ", \"new\": ");
} else {
fprintf(stdout, ", \"name\": ");
}
fputc('"', stdout);
json_write_filename_bytes(parent_path, parent_path_length, stdout);
fputc('/', stdout);
json_write_filename_bytes(name, strlen(name), stdout);
fputc('"', stdout);
}
ptr += hdr->len;
}
if (entry_index++) { fprintf(stdout, ", "); }
fprintf(stdout, ", \"mask\": %i}\n", metadata->mask);
metadata = FAN_EVENT_NEXT(metadata, size);
}
} else {
break;
}
}
}
int main(int argc, char *argv[]) {
// Acquire arguments
if (argc != 2) {
fprintf(stderr, "Usage: %s MOUNT\n", argv[0]);
exit(EXIT_FAILURE);
}
char* mount_point = argv[1];
// Open mount point for reading
int mount_fd = open(mount_point, O_DIRECTORY | O_RDONLY);
if (mount_fd == -1) {
perror(mount_point);
exit(EXIT_FAILURE);
}
// Create file descriptor for fanotify API
int fafd = fanotify_init(FAN_CLOEXEC | FAN_CLASS_NOTIF | FAN_NONBLOCK | FAN_REPORT_FID | FAN_REPORT_DFID_NAME, O_RDONLY | O_LARGEFILE);
if (fafd == -1) {
perror("fanotify_init");
exit(EXIT_FAILURE);
}
// Mark events for logging
if (fanotify_mark(fafd, FAN_MARK_ADD | FAN_MARK_FILESYSTEM, FAN_ATTRIB | FAN_RENAME | FAN_CREATE | FAN_DELETE | FAN_CLOSE_WRITE | FAN_ONDIR, AT_FDCWD, mount_point) == -1) {
perror("fanotify_mark");
exit(EXIT_FAILURE);
}
// Setup file descriptors for epoll
#define N_POLL_FDS 1
struct pollfd poll_fds[N_POLL_FDS] = {
{.fd = fafd, .events = POLLIN},
};
fprintf(stderr, "Listening for events.\n");
fprintf(stderr, "Issue interrupt signal to terminate.\n");
while (1) {
int poll_num = poll(poll_fds, N_POLL_FDS, -1);
if (poll_num == -1) {
if (errno == EINTR) // Interrupted by signal
continue;
perror("poll"); // Unexpected error
exit(EXIT_FAILURE);
}
if (poll_num >= 0) {
if (poll_fds[0].revents & POLLIN) { // fanotify events available
handle_events(fafd, mount_fd);
fflush(stdout);
}
}
}
fprintf(stderr, "Listening for events stopped.\n");
exit(EXIT_SUCCESS);
}