forked from mikael-lovqvist/fa2json
Initial commit
This commit is contained in:
18
fanotify-projekt.service
Normal file
18
fanotify-projekt.service
Normal file
@@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=Fanotify → Tag Event → Message Hub
|
||||
Requires=message-hub.service
|
||||
After=message-hub.service
|
||||
RequiresMountsFor=/srv/Projekt
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=messagehub
|
||||
Group=messagehub
|
||||
WorkingDirectory=/srv/Projekt/message-hub/system2
|
||||
ExecStart=/bin/sh -c '/srv/Projekt/fanotify-logger/fa2json /srv/Projekt | tee -a /srv/project-fs.log | node tag-event.mjs'
|
||||
AmbientCapabilities=CAP_SYS_ADMIN CAP_DAC_READ_SEARCH
|
||||
NoNewPrivileges=yes
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
169
fs-watcher.c
Normal file
169
fs-watcher.c
Normal file
@@ -0,0 +1,169 @@
|
||||
#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);
|
||||
}
|
||||
177
json-writer.c
Normal file
177
json-writer.c
Normal file
@@ -0,0 +1,177 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
#include <locale.h>
|
||||
|
||||
|
||||
//TODO - move to header
|
||||
size_t json_compute_unicode(const char* input);
|
||||
char* json_instance_unicode(const char* input);
|
||||
void json_write_unicode(const char* input, FILE *out);
|
||||
int json_write_unicode_to_buffer(const char* input, char* buffer, size_t buffer_size);
|
||||
|
||||
char* json_instance_unicode(const char* input) {
|
||||
size_t req = json_compute_unicode(input) + 1; // +1 for sentinel
|
||||
char* result = calloc(1, req);
|
||||
json_write_unicode_to_buffer(input, result, req); //Possibly return null if req != written but for now we just ignore
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
size_t json_compute_unicode(const char* input) {
|
||||
setlocale(LC_CTYPE, ""); // Enable locale-aware decoding
|
||||
size_t result = 2;
|
||||
|
||||
const char *p = input;
|
||||
mbstate_t st;
|
||||
memset(&st, 0, sizeof(st));
|
||||
|
||||
while (*p) {
|
||||
wchar_t wc;
|
||||
size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
|
||||
|
||||
if (len == (size_t)-1 || len == (size_t)-2) {
|
||||
// Invalid UTF-8 sequence — fallback to hex escape
|
||||
result += 4;
|
||||
p++;
|
||||
memset(&st, 0, sizeof(st));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wc == L'"') result += 2;
|
||||
else if (wc == L'\\') result += 2;
|
||||
else if (wc == L'\b') result += 2;
|
||||
else if (wc == L'\f') result += 2;
|
||||
else if (wc == L'\n') result += 2;
|
||||
else if (wc == L'\r') result += 2;
|
||||
else if (wc == L'\t') result += 2;
|
||||
else if (wc < 0x20) {
|
||||
// Control characters: output as \u0000..\u001F
|
||||
result += 6;
|
||||
} else {
|
||||
// Write UTF-8 bytes directly
|
||||
result += len;
|
||||
}
|
||||
|
||||
p += len;
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
int json_write_unicode_to_buffer(const char* input, char* buffer, size_t buffer_size) {
|
||||
setlocale(LC_CTYPE, ""); // Enable locale-aware decoding
|
||||
|
||||
const char *p = input;
|
||||
mbstate_t st;
|
||||
memset(&st, 0, sizeof(st));
|
||||
size_t written = 0;
|
||||
|
||||
// Open quote
|
||||
written += snprintf(buffer + written, buffer_size - written, "\"");
|
||||
|
||||
while (*p) {
|
||||
wchar_t wc;
|
||||
size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
|
||||
|
||||
if (len == (size_t)-1 || len == (size_t)-2) {
|
||||
// Invalid UTF-8 sequence — fallback to hex escape
|
||||
written += snprintf(buffer + written, buffer_size - written, "\\u00%02X", (unsigned char)*p);
|
||||
p++;
|
||||
memset(&st, 0, sizeof(st));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wc == L'"') written += snprintf(buffer + written, buffer_size - written, "\\\"");
|
||||
else if (wc == L'\\') written += snprintf(buffer + written, buffer_size - written, "\\\\");
|
||||
else if (wc == L'\b') written += snprintf(buffer + written, buffer_size - written, "\\b");
|
||||
else if (wc == L'\f') written += snprintf(buffer + written, buffer_size - written, "\\f");
|
||||
else if (wc == L'\n') written += snprintf(buffer + written, buffer_size - written, "\\n");
|
||||
else if (wc == L'\r') written += snprintf(buffer + written, buffer_size - written, "\\r");
|
||||
else if (wc == L'\t') written += snprintf(buffer + written, buffer_size - written, "\\t");
|
||||
else if (wc < 0x20) {
|
||||
// Control characters: output as \u0000..\u001F
|
||||
written += snprintf(buffer + written, buffer_size - written, "\\u00%02X", wc);
|
||||
} else {
|
||||
// Write UTF-8 bytes directly
|
||||
written += snprintf(buffer + written, buffer_size - written, "%.*s", (int)len, p);
|
||||
}
|
||||
|
||||
p += len;
|
||||
}
|
||||
|
||||
// Close quote
|
||||
written += snprintf(buffer + written, buffer_size - written, "\"");
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
void json_write_unicode(const char* input, FILE *out) {
|
||||
setlocale(LC_CTYPE, ""); // Enable locale-aware decoding
|
||||
|
||||
const char *p = input;
|
||||
mbstate_t st;
|
||||
memset(&st, 0, sizeof(st));
|
||||
|
||||
fputc('"', out); // Open quote
|
||||
|
||||
while (*p) {
|
||||
wchar_t wc;
|
||||
size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
|
||||
|
||||
if (len == (size_t)-1 || len == (size_t)-2) {
|
||||
// Invalid UTF-8 sequence — fallback to hex escape
|
||||
fprintf(out, "\\u00%02X", (unsigned char)*p);
|
||||
p++;
|
||||
memset(&st, 0, sizeof(st));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wc == L'"') fputs("\\\"", out);
|
||||
else if (wc == L'\\') fputs("\\\\", out);
|
||||
else if (wc == L'\b') fputs("\\b", out);
|
||||
else if (wc == L'\f') fputs("\\f", out);
|
||||
else if (wc == L'\n') fputs("\\n", out);
|
||||
else if (wc == L'\r') fputs("\\r", out);
|
||||
else if (wc == L'\t') fputs("\\t", out);
|
||||
else if (wc < 0x20) {
|
||||
// Control characters: output as \u0000..\u001F
|
||||
fprintf(out, "\\u00%02X", wc);
|
||||
} else {
|
||||
// Write UTF-8 bytes directly
|
||||
fwrite(p, 1, len, out);
|
||||
}
|
||||
|
||||
p += len;
|
||||
}
|
||||
|
||||
fputc('"', out); // Close quote
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void json_write_filename_bytes(const char *input, size_t length, FILE *out) {
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
unsigned char c = (unsigned char)input[i];
|
||||
|
||||
switch (c) {
|
||||
case '"': fputs("\\\"", out); break;
|
||||
case '\\': fputs("\\\\", out); break;
|
||||
case '\b': fputs("\\b", out); break;
|
||||
case '\f': fputs("\\f", out); break;
|
||||
case '\n': fputs("\\n", out); break;
|
||||
case '\r': fputs("\\r", out); break;
|
||||
case '\t': fputs("\\t", out); break;
|
||||
|
||||
default:
|
||||
if (c < 0x20 || c >= 0x80) {
|
||||
// surrogateescape encoding
|
||||
fprintf(out, "\\uDC%02X", c);
|
||||
} else {
|
||||
fputc(c, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user