#define DEFAULT_SUBDIR_COUNT 5
-#define CHECK_NULL(p) if (p == NULL) { syslog(LOG_ERR, "out of memory"); return ERR_ABORT; }
+#define CHECK_NULL(p) if (p == NULL) { userlog(LOG_ERR, "out of memory"); return ERR_ABORT; }
typedef struct __watch_node {
char* name;
static void read_watch_descriptors_count() {
FILE* f = fopen(WATCH_COUNT_NAME, "r");
if (f == NULL) {
- syslog(LOG_ERR, "can't open %s: %s", WATCH_COUNT_NAME, strerror(errno));
+ userlog(LOG_ERR, "can't open %s: %s", WATCH_COUNT_NAME, strerror(errno));
return;
}
char* str = read_line(f);
if (str == NULL) {
- syslog(LOG_ERR, "can't read from %s", WATCH_COUNT_NAME);
+ userlog(LOG_ERR, "can't read from %s", WATCH_COUNT_NAME);
}
else {
watch_count = atoi(str);
bool init_inotify() {
inotify_fd = inotify_init();
if (inotify_fd < 0) {
- syslog(LOG_ERR, "inotify_init: %s", strerror(errno));
+ userlog(LOG_ERR, "inotify_init: %s", strerror(errno));
return false;
}
- syslog(LOG_DEBUG, "inotify fd: %d", get_inotify_fd());
+ userlog(LOG_DEBUG, "inotify fd: %d", get_inotify_fd());
read_watch_descriptors_count();
if (watch_count <= 0) {
inotify_fd = -1;
return false;
}
- syslog(LOG_INFO, "inotify watch descriptors: %d", watch_count);
+ userlog(LOG_INFO, "inotify watch descriptors: %d", watch_count);
watches = table_create(watch_count);
if (watches == NULL) {
- syslog(LOG_ERR, "out of memory");
+ userlog(LOG_ERR, "out of memory");
close(inotify_fd);
inotify_fd = -1;
return false;
if (errno == ENOSPC) {
limit_reached = true;
}
- syslog(LOG_ERR, "inotify_add_watch(%s): %s", path, strerror(errno));
+ userlog(LOG_ERR, "inotify_add_watch(%s): %s", path, strerror(errno));
return ERR_CONTINUE;
}
else {
- syslog(LOG_DEBUG, "watching %s: %d", path, wd);
+ userlog(LOG_DEBUG, "watching %s: %d", path, wd);
}
watch_node* node = table_get(watches, wd);
if (node != NULL) {
if (node->wd != wd || strcmp(node->name, path) != 0) {
- syslog(LOG_ERR, "table error: collision (new %d:%s, existing %d:%s)", wd, path, node->wd, node->name);
+ userlog(LOG_ERR, "table error: collision (new %d:%s, existing %d:%s)", wd, path, node->wd, node->name);
return ERR_ABORT;
}
}
if (table_put(watches, wd, node) == NULL) {
- syslog(LOG_ERR, "table error: unable to put (%d:%s)", wd, path);
+ userlog(LOG_ERR, "table error: unable to put (%d:%s)", wd, path);
return ERR_ABORT;
}
return;
}
- syslog(LOG_DEBUG, "unwatching %s: %d (%p)", node->name, node->wd, node);
+ userlog(LOG_DEBUG, "unwatching %s: %d (%p)", node->name, node->wd, node);
if (inotify_rm_watch(inotify_fd, node->wd) < 0) {
- syslog(LOG_DEBUG, "inotify_rm_watch(%d:%s): %s", node->wd, node->name, strerror(errno));
+ userlog(LOG_DEBUG, "inotify_rm_watch(%d:%s): %s", node->wd, node->name, strerror(errno));
}
for (int i=0; i<array_size(node->kids); i++) {
const char* ignore = array_get(ignores, i);
int il = strlen(ignore);
if (pl >= il && strncmp(path, ignore, il) == 0) {
- syslog(LOG_DEBUG, "path %s is under unwatchable %s - ignoring", path, ignore);
+ userlog(LOG_DEBUG, "path %s is under unwatchable %s - ignoring", path, ignore);
return true;
}
}
else if (errno == ENOTDIR) { // flat root
return add_watch(path, parent);
}
- syslog(LOG_ERR, "opendir(%s): %s", path, strerror(errno));
+ userlog(LOG_ERR, "opendir(%s): %s", path, strerror(errno));
return ERR_CONTINUE;
}
return true;
}
- syslog(LOG_DEBUG, "inotify: wd=%d mask=%d dir=%d name=%s",
+ userlog(LOG_DEBUG, "inotify: wd=%d mask=%d dir=%d name=%s",
event->wd, event->mask & (~IN_ISDIR), (event->mask & IN_ISDIR) != 0, node->name);
char path[PATH_MAX];
bool process_inotify_input() {
size_t len = read(inotify_fd, event_buf, EVENT_BUF_LEN);
if (len < 0) {
- syslog(LOG_ERR, "read: %s", strerror(errno));
+ userlog(LOG_ERR, "read: %s", strerror(errno));
return false;
}
continue;
}
if (event->mask & IN_Q_OVERFLOW) {
- syslog(LOG_ERR, "event queue overflow");
+ userlog(LOG_ERR, "event queue overflow");
continue;
}
#include "fsnotifier.h"
#include <errno.h>
-#include <stdio.h>
+#include <limits.h>
+#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
"fsnotifier - IntelliJ IDEA companion program for watching and reporting file and directory structure modifications.\n\n" \
"fsnotifier utilizes \"user\" facility of syslog(3) - messages usually can be found in /var/log/user.log.\n" \
"Verbosity is regulated via " LOG_ENV " environment variable, possible values are: " \
- LOG_ENV_DEBUG ", " LOG_ENV_INFO ", " LOG_ENV_WARNING ", " LOG_ENV_ERROR ", " LOG_ENV_OFF "; latter is the default.\n"
+ LOG_ENV_DEBUG ", " LOG_ENV_INFO ", " LOG_ENV_WARNING ", " LOG_ENV_ERROR ", " LOG_ENV_OFF "; latter is the default.\n\n" \
+ "Use \"fsnotifier --selftest\" to perform some self-diagnostics (output will be logged and printed to console).\n"
#define INOTIFY_LIMIT_MSG \
"The current <b>inotify</b>(7) watch limit of %d is too low. " \
static bool show_warning = true;
-#define CHECK_NULL(p) if (p == NULL) { syslog(LOG_ERR, "out of memory"); return false; }
+static bool self_test = false;
+
+#define CHECK_NULL(p) if (p == NULL) { userlog(LOG_ERR, "out of memory"); return false; }
static void init_log();
+static void run_self_test();
static void main_loop();
static bool read_input();
static bool update_roots(array* new_roots);
static bool register_roots(array* new_roots, array* unwatchable);
static bool unwatchable_mounts(array* mounts);
static void inotify_callback(char* path, int event);
+static void output(const char* format, ...);
int main(int argc, char** argv) {
- if (argc == 2 && strcmp(argv[1], "--help") == 0) {
- printf(USAGE_MSG);
- return 0;
+ if (argc == 2) {
+ if (strcmp(argv[1], "--help") == 0) {
+ printf(USAGE_MSG);
+ return 0;
+ }
+ if (strcmp(argv[1], "--selftest") == 0) {
+ self_test = true;
+ }
}
init_log();
- syslog(LOG_INFO, "started");
+ if (!self_test) {
+ userlog(LOG_INFO, "started");
+ }
+ else {
+ userlog(LOG_INFO, "started (self-test mode)");
+ }
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
roots = array_create(20);
if (init_inotify() && roots != NULL) {
set_inotify_callback(&inotify_callback);
- main_loop();
+
+ if (!self_test) {
+ main_loop();
+ }
+ else {
+ run_self_test();
+ }
+
unregister_roots();
}
else {
close_inotify();
array_delete(roots);
- syslog(LOG_INFO, "finished");
+ userlog(LOG_INFO, "finished");
closelog();
return 0;
else if (strcmp(env_level, LOG_ENV_ERROR) == 0) level = LOG_ERR;
}
+ if (self_test) {
+ level = LOG_DEBUG;
+ }
+
char ident[32];
snprintf(ident, sizeof(ident), "fsnotifier[%d]", getpid());
openlog(ident, 0, LOG_USER);
}
+void userlog(int priority, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+
+ vsyslog(priority, format, ap);
+
+ if (self_test) {
+ printf("fsnotifier[%d]: ", getpid());
+ vprintf(format, ap);
+ printf("\n");
+ }
+
+ va_end(ap);
+}
+
+
+static void run_self_test() {
+ array* test_roots = array_create(1);
+ char* cwd = malloc(PATH_MAX);
+ if (getcwd(cwd, PATH_MAX) == NULL) {
+ strncpy(cwd, ".", PATH_MAX);
+ }
+ array_push(test_roots, cwd);
+ update_roots(test_roots);
+}
+
+
static void main_loop() {
int input_fd = fileno(stdin), inotify_fd = get_inotify_fd();
int nfds = (inotify_fd > input_fd ? inotify_fd : input_fd) + 1;
FD_SET(input_fd, &rfds);
FD_SET(inotify_fd, &rfds);
if (select(nfds, &rfds, NULL, NULL, NULL) < 0) {
- syslog(LOG_ERR, "select: %s", strerror(errno));
+ userlog(LOG_ERR, "select: %s", strerror(errno));
go_on = false;
}
else if (FD_ISSET(input_fd, &rfds)) {
static bool read_input() {
char* line = read_line(stdin);
- syslog(LOG_DEBUG, "input: %s", (line ? line : "<null>"));
+ userlog(LOG_DEBUG, "input: %s", (line ? line : "<null>"));
if (line == NULL || strcmp(line, "EXIT") == 0) {
return false;
while (1) {
line = read_line(stdin);
- syslog(LOG_DEBUG, "input: %s", (line ? line : "<null>"));
+ userlog(LOG_DEBUG, "input: %s", (line ? line : "<null>"));
if (line == NULL || strlen(line) == 0) {
return false;
}
static bool update_roots(array* new_roots) {
- syslog(LOG_INFO, "updating roots (curr:%d, new:%d)", array_size(roots), array_size(new_roots));
+ userlog(LOG_INFO, "updating roots (curr:%d, new:%d)", array_size(roots), array_size(new_roots));
unregister_roots();
if (array_size(new_roots) == 0) {
return true;
}
else if (array_size(new_roots) == 1 && strcmp(array_get(new_roots, 0), "/") == 0) { // refuse to watch entire tree
- printf("UNWATCHEABLE\n/\n#\n");
- syslog(LOG_INFO, "unwatchable: /");
+ output("UNWATCHEABLE\n/\n#\n");
+ userlog(LOG_INFO, "unwatchable: /");
array_delete_vs_data(new_roots);
return true;
}
}
// todo: sort/optimize list
- printf("UNWATCHEABLE\n");
+ output("UNWATCHEABLE\n");
for (int i=0; i<array_size(unwatchable); i++) {
char* s = array_get(unwatchable, i);
- printf("%s\n", s);
- syslog(LOG_INFO, "unwatchable: %s", s);
+ output("%s\n", s);
+ userlog(LOG_INFO, "unwatchable: %s", s);
}
- printf("#\n");
+ output("#\n");
array_delete_vs_data(unwatchable);
array_delete(new_roots);
static void unregister_roots() {
watch_root* root;
while ((root = array_pop(roots)) != NULL) {
- syslog(LOG_INFO, "unregistering root: %s\n", root->name);
+ userlog(LOG_INFO, "unregistering root: %s", root->name);
unwatch(root->id);
free(root->name);
free(root);
static bool register_roots(array* new_roots, array* unwatchable) {
for (int i=0; i<array_size(new_roots); i++) {
char* new_root = array_get(new_roots, i);
- syslog(LOG_INFO, "registering root: %s\n", new_root);
+ userlog(LOG_INFO, "registering root: %s", new_root);
int id = watch(new_root, unwatchable);
if (id == ERR_ABORT) {
return false;
else {
if (show_warning && watch_limit_reached()) {
int limit = get_watch_count();
- syslog(LOG_WARNING, "watch limit (%d) reached", limit);
- printf("MESSAGE\n" INOTIFY_LIMIT_MSG, limit);
+ userlog(LOG_WARNING, "watch limit (%d) reached", limit);
+ output("MESSAGE\n" INOTIFY_LIMIT_MSG, limit);
show_warning = false; // warn only once
}
CHECK_NULL(array_push(unwatchable, new_root));
mtab = fopen("/proc/mounts", "r");
}
if (mtab == NULL) {
- syslog(LOG_ERR, "neither /etc/mtab nor /proc/mounts can be read");
+ userlog(LOG_ERR, "neither /etc/mtab nor /proc/mounts can be read");
return false;
}
char* line;
while ((line = read_line(mtab)) != NULL) {
- syslog(LOG_DEBUG, "mtab: %s", line);
+ userlog(LOG_DEBUG, "mtab: %s", line);
char* dev = strtok(line, MTAB_DELIMS);
char* point = strtok(NULL, MTAB_DELIMS);
char* fs = strtok(NULL, MTAB_DELIMS);
if (dev == NULL || point == NULL || fs == NULL) {
- syslog(LOG_ERR, "can't parse mount line");
+ userlog(LOG_ERR, "can't parse mount line");
return false;
}
static void inotify_callback(char* path, int event) {
if (event & IN_CREATE || event & IN_MOVED_TO) {
- printf("CREATE\n%s\n", path);
- syslog(LOG_DEBUG, "CREATE: %s", path);
+ output("CREATE\n%s\n", path);
+ userlog(LOG_DEBUG, "CREATE: %s", path);
return;
}
if (event & IN_MODIFY) {
- printf("CHANGE\n%s\n", path);
- syslog(LOG_DEBUG, "CHANGE: %s", path);
+ output("CHANGE\n%s\n", path);
+ userlog(LOG_DEBUG, "CHANGE: %s", path);
return;
}
if (event & IN_ATTRIB) {
- printf("STATS\n%s\n", path);
- syslog(LOG_DEBUG, "STATS: %s", path);
+ output("STATS\n%s\n", path);
+ userlog(LOG_DEBUG, "STATS: %s", path);
return;
}
if (event & IN_DELETE || event & IN_MOVED_FROM) {
- printf("DELETE\n%s\n", path);
- syslog(LOG_DEBUG, "DELETE: %s", path);
+ output("DELETE\n%s\n", path);
+ userlog(LOG_DEBUG, "DELETE: %s", path);
return;
}
if (event & IN_UNMOUNT) {
- printf("RESET\n");
- syslog(LOG_DEBUG, "RESET");
+ output("RESET\n");
+ userlog(LOG_DEBUG, "RESET");
return;
}
}
+
+
+static void output(const char* format, ...) {
+ if (self_test) {
+ return;
+ }
+
+ va_list ap;
+ va_start(ap, format);
+ vprintf(format, ap);
+ va_end(ap);
+}