#define _GNU_SOURCE /* Needed to get O_LARGEFILE definition */ #include #include #include #include #include #include #include #include #include #include //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\": [%li, %li, %li, %li]", 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\": %lli}\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); }