// gcc -O2 -Wall -Wextra -o main main.c #define _GNU_SOURCE #include // <-- прототипы fanotify_* #include #include #include #include #include #include #include #include #include // PATH_MAX #ifndef AT_FDCWD # include #endif /* Настройки из твоего D-кода */ #define INIT_FLAGS (FAN_CLASS_PRE_CONTENT | FAN_CLOEXEC | FAN_NONBLOCK) #define EVENT_FLAGS (O_RDONLY | O_LARGEFILE | O_CLOEXEC) #define BASE_FLAGS (FAN_MARK_ADD) #define PERM_MASK (FAN_OPEN_PERM | FAN_ACCESS_PERM | FAN_OPEN_EXEC_PERM | FAN_OPEN | FAN_CLOSE_WRITE) #define BUF_SZ (64 * 1024) /* Простой лог */ static void log_line(const char *s) { struct iovec v[2]; static const char nl = '\n'; v[0].iov_base = (void*)s; v[0].iov_len = strlen(s); v[1].iov_base = (void*)&nl; v[1].iov_len = 1; writev(STDERR_FILENO, v, 2); } static ssize_t fd_path(int fd, char *buf, size_t cap) { char linkpath[64]; int m = snprintf(linkpath, sizeof(linkpath), "/proc/self/fd/%d", fd); if (m <= 0 || (size_t)m >= sizeof(linkpath)) return -1; ssize_t r = readlink(linkpath, buf, cap - 1); if (r >= 0) buf[r] = '\0'; return r; } int main(void) { int fan = fanotify_init(INIT_FLAGS, EVENT_FLAGS); if (fan == -1) { perror("fanotify_init"); return 1; } /* метим /tmp и /home как файловые системы */ if (fanotify_mark(fan, BASE_FLAGS | FAN_MARK_FILESYSTEM, PERM_MASK, AT_FDCWD, "/tmp") == -1) { perror("fanotify_mark(/tmp)"); return 1; } if (fanotify_mark(fan, BASE_FLAGS | FAN_MARK_FILESYSTEM, PERM_MASK, AT_FDCWD, "/home") == -1) { perror("fanotify_mark(/home)"); return 1; } int ep = epoll_create1(EPOLL_CLOEXEC); if (ep == -1) { perror("epoll_create1"); return 1; } struct epoll_event ev = { .events = EPOLLIN | EPOLLONESHOT }; ev.data.u64 = 1; /* тег */ if (epoll_ctl(ep, EPOLL_CTL_ADD, fan, &ev) == -1) { perror("epoll_ctl ADD"); return 1; } char *buf = malloc(BUF_SZ); if (!buf) { perror("malloc"); return 1; } log_line("fanomon: started (watching /tmp and /home)"); for (;;) { struct epoll_event out; int n = epoll_wait(ep, &out, 1, -1); if (n == -1) { if (errno == EINTR) continue; perror("epoll_wait"); break; } if ((out.events & EPOLLIN) && out.data.u64 == 1) { for (;;) { ssize_t len = read(fan, buf, BUF_SZ); if (len == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) break; if (len <= 0) break; struct fanotify_event_metadata *meta; for (meta = (struct fanotify_event_metadata*)buf; FAN_EVENT_OK(meta, len); meta = FAN_EVENT_NEXT(meta, len)) { if (meta->vers != FANOTIFY_METADATA_VERSION) { log_line("metadata version mismatch"); continue; } if (meta->mask & FAN_Q_OVERFLOW) { log_line("queue overflow"); continue; } /* Быстро отвечаем на PERM-события */ if (meta->mask & (FAN_OPEN_PERM | FAN_ACCESS_PERM | FAN_OPEN_EXEC_PERM)) { struct fanotify_response resp = { .fd = meta->fd, .response = FAN_ALLOW }; if (write(fan, &resp, sizeof(resp)) == -1) perror("fanotify_response"); } /* Лёгкий лог пути (после ответа) */ if (meta->fd >= 0) { char path[PATH_MAX]; if (fd_path(meta->fd, path, sizeof(path)) >= 0) { char line[PATH_MAX + 16]; const char *tag = (meta->mask & FAN_OPEN) ? "OPEN " : (meta->mask & FAN_CLOSE_WRITE) ? "CLOSEW" : (meta->mask & FAN_ACCESS_PERM) ? "APERM " : (meta->mask & FAN_OPEN_PERM) ? "OPERM " : (meta->mask & FAN_OPEN_EXEC_PERM) ? "XPERM " : "EVENT "; snprintf(line, sizeof(line), "%s %s", tag, path); log_line(line); } close(meta->fd); } } } /* Реарм ONE_SHOT */ ev.events = EPOLLIN | EPOLLONESHOT; ev.data.u64 = 1; if (epoll_ctl(ep, EPOLL_CTL_MOD, fan, &ev) == -1) { perror("epoll_ctl MOD"); break; } } } free(buf); close(ep); close(fan); return 0; }