--- /dev/null
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __FSNOTIFIER_H
+#define __FSNOTIFIER_H
+
+#include <stdbool.h>
+#include <stdio.h>
+
+
+// variable-length array
+typedef struct __array array;
+
+array* array_create(int initial_capacity);
+int array_size(array* a);
+void* array_push(array* a, void* element);
+void* array_pop(array* a);
+void array_put(array* a, int index, void* element);
+void* array_get(array* a, int index);
+void array_delete(array* a);
+void array_delete_vs_data(array* a);
+
+
+// key/value pairs table
+typedef struct __table table;
+
+table* table_create(int capacity);
+void* table_put(table* t, int key, void* value);
+void* table_get(table* t, int key);
+void table_delete(table* t);
+
+
+// inotify subsystem
+enum {
+ ERR_IGNORE = -1,
+ ERR_CONTINUE = -2,
+ ERR_ABORT = -3
+};
+
+bool init_inotify();
+void set_inotify_callback(void (* callback)(char*, int));
+int get_inotify_fd();
+int get_watch_count();
+bool watch_limit_reached();
+int watch(const char* root, array* ignores);
+void unwatch(int id);
+bool process_inotify_input();
+void close_inotify();
+
+
+// reads one line from stream, trims trailing carriage return if any
+// returns pointer to the internal buffer (will be overwriten on next call)
+char* read_line(FILE* stream);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fsnotifier.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <linux/limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <syslog.h>
+#include <unistd.h>
+
+
+#define WATCH_COUNT_NAME "/proc/sys/fs/inotify/max_user_watches"
+
+#define DEFAULT_SUBDIR_COUNT 5
+
+#define CHECK_NULL(p) if (p == NULL) { syslog(LOG_ERR, "out of memory"); return ERR_ABORT; }
+
+typedef struct __watch_node {
+ char* name;
+ int wd;
+ struct __watch_node* parent;
+ array* kids;
+} watch_node;
+
+static int inotify_fd = -1;
+static int watch_count = 0;
+static table* watches;
+static bool limit_reached = false;
+static void (* callback)(char*, int) = NULL;
+
+#define EVENT_SIZE (sizeof(struct inotify_event))
+#define EVENT_BUF_LEN (2048 * (EVENT_SIZE + 16))
+static char event_buf[EVENT_BUF_LEN];
+
+
+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));
+ return;
+ }
+
+ char* str = read_line(f);
+ if (str == NULL) {
+ syslog(LOG_ERR, "can't read from %s", WATCH_COUNT_NAME);
+ }
+ else {
+ watch_count = atoi(str);
+ }
+
+ fclose(f);
+}
+
+
+bool init_inotify() {
+ inotify_fd = inotify_init();
+ if (inotify_fd < 0) {
+ syslog(LOG_ERR, "inotify_init: %s", strerror(errno));
+ return false;
+ }
+ syslog(LOG_DEBUG, "inotify fd: %d", get_inotify_fd());
+
+ read_watch_descriptors_count();
+ if (watch_count <= 0) {
+ close(inotify_fd);
+ inotify_fd = -1;
+ return false;
+ }
+ syslog(LOG_INFO, "inotify watch descriptors: %d", watch_count);
+
+ watches = table_create(watch_count);
+ if (watches == NULL) {
+ syslog(LOG_ERR, "out of memory");
+ close(inotify_fd);
+ inotify_fd = -1;
+ return false;
+ }
+
+ return true;
+}
+
+
+inline void set_inotify_callback(void (* _callback)(char*, int)) {
+ callback = _callback;
+}
+
+
+inline int get_inotify_fd() {
+ return inotify_fd;
+}
+
+
+inline int get_watch_count() {
+ return watch_count;
+}
+
+
+inline bool watch_limit_reached() {
+ return limit_reached;
+}
+
+
+static int add_watch(const char* path, watch_node* parent) {
+ int wd = inotify_add_watch(inotify_fd, path, IN_MODIFY | IN_ATTRIB | IN_CREATE | IN_DELETE | IN_MOVE | IN_DELETE_SELF);
+ if (wd < 0) {
+ if (errno == ENOSPC) {
+ limit_reached = true;
+ }
+ syslog(LOG_ERR, "inotify_add_watch(%s): %s", path, strerror(errno));
+ return ERR_CONTINUE;
+ }
+ else {
+ syslog(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);
+ return ERR_ABORT;
+ }
+
+ return wd;
+ }
+
+ node = malloc(sizeof(watch_node));
+
+ CHECK_NULL(node);
+ node->name = strdup(path);
+ CHECK_NULL(node->name);
+ node->wd = wd;
+ node->parent = parent;
+ node->kids = NULL;
+
+ if (parent != NULL) {
+ if (parent->kids == NULL) {
+ parent->kids = array_create(DEFAULT_SUBDIR_COUNT);
+ CHECK_NULL(parent->kids);
+ }
+ CHECK_NULL(array_push(parent->kids, node));
+ }
+
+ if (table_put(watches, wd, node) == NULL) {
+ syslog(LOG_ERR, "table error: unable to put (%d:%s)", wd, path);
+ return ERR_ABORT;
+ }
+
+ return wd;
+}
+
+
+static void rm_watch(int wd, bool update_parent) {
+ watch_node* node = table_get(watches, wd);
+ if (node == NULL) {
+ return;
+ }
+
+ syslog(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));
+ }
+
+ for (int i=0; i<array_size(node->kids); i++) {
+ watch_node* kid = array_get(node->kids, i);
+ if (kid != NULL) {
+ rm_watch(kid->wd, false);
+ }
+ }
+
+ if (update_parent && node->parent != NULL) {
+ for (int i=0; i<array_size(node->parent->kids); i++) {
+ if (array_get(node->parent->kids, i) == node) {
+ array_put(node->parent->kids, i, NULL);
+ break;
+ }
+ }
+ }
+
+ free(node->name);
+ array_delete(node->kids);
+ free(node);
+ table_put(watches, wd, NULL);
+}
+
+
+static bool is_directory(struct dirent* entry, const char* path) {
+ if (entry->d_type == DT_DIR) {
+ return true;
+ }
+ else if (entry->d_type == DT_UNKNOWN) { // filesystem doesn't support d_type
+ struct stat st;
+ return (stat(path, &st) == 0 && S_ISDIR(st.st_mode));
+ }
+ return false;
+}
+
+static bool is_ignored(const char* path, array* ignores) {
+ if (ignores != NULL) {
+ int pl = strlen(path);
+ for (int i=0; i<array_size(ignores); 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);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static int walk_tree(const char* path, watch_node* parent, array* ignores) {
+ if (is_ignored(path, ignores)) {
+ return ERR_IGNORE;
+ }
+
+ DIR* dir = opendir(path);
+ if (dir == NULL) {
+ if (errno == EACCES) {
+ return ERR_IGNORE;
+ }
+ else if (errno == ENOTDIR) { // flat root
+ return add_watch(path, parent);
+ }
+ syslog(LOG_ERR, "opendir(%s): %s", path, strerror(errno));
+ return ERR_CONTINUE;
+ }
+
+ int id = add_watch(path, parent);
+ if (id < 0) {
+ closedir(dir);
+ return id;
+ }
+
+ struct dirent* entry;
+ char subdir[PATH_MAX];
+ strcpy(subdir, path);
+ if (subdir[strlen(subdir) - 1] != '/') {
+ strcat(subdir, "/");
+ }
+ char* p = subdir + strlen(subdir);
+
+ while ((entry = readdir(dir)) != NULL) {
+ if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
+ continue;
+ }
+
+ strcpy(p, entry->d_name);
+ if (!is_directory(entry, subdir)) {
+ continue;
+ }
+
+ int subdir_id = walk_tree(subdir, table_get(watches, id), ignores);
+ if (subdir_id < 0 && subdir_id != ERR_IGNORE) {
+ rm_watch(id, true);
+ id = subdir_id;
+ break;
+ }
+ }
+
+ closedir(dir);
+ return id;
+}
+
+
+int watch(const char* root, array* ignores) {
+ return walk_tree(root, NULL, ignores);
+}
+
+
+void unwatch(int id) {
+ rm_watch(id, true);
+}
+
+
+static bool process_inotify_event(struct inotify_event* event) {
+ watch_node* node = table_get(watches, event->wd);
+ if (node == NULL) {
+ return true;
+ }
+
+ syslog(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];
+ strcpy(path, node->name);
+ if (event->len > 0) {
+ if (path[strlen(path) - 1] != '/') {
+ strcat(path, "/");
+ }
+ strcat(path, event->name);
+ }
+
+ if ((event->mask & IN_CREATE || event->mask & IN_MOVED_TO) && event->mask & IN_ISDIR) {
+ int result = walk_tree(path, node, NULL);
+ if (result < 0 && result != ERR_IGNORE) {
+ return false;
+ }
+ }
+
+ if ((event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) && event->mask & IN_ISDIR) {
+ for (int i=0; i<array_size(node->kids); i++) {
+ watch_node* kid = array_get(node->kids, i);
+ if (kid != NULL && strcmp(kid->name, path) == 0) {
+ rm_watch(kid->wd, false);
+ array_put(node->kids, i, NULL);
+ break;
+ }
+ }
+ }
+
+ if (callback != NULL) {
+ (*callback)(path, event->mask);
+ }
+ return true;
+}
+
+
+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));
+ return false;
+ }
+
+ int i = 0;
+ while (i < len) {
+ struct inotify_event* event = (struct inotify_event*) &event_buf[i];
+ i += EVENT_SIZE + event->len;
+
+ if (event->mask & IN_IGNORED) {
+ continue;
+ }
+ if (event->mask & IN_Q_OVERFLOW) {
+ syslog(LOG_ERR, "event queue overflow");
+ continue;
+ }
+
+ if (!process_inotify_event(event)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+void close_inotify() {
+ if (watches != NULL) {
+ table_delete(watches);
+ }
+
+ if (inotify_fd >= 0) {
+ close(inotify_fd);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fsnotifier.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/select.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#define LOG_ENV "FSNOTIFIER_LOG_LEVEL"
+#define LOG_ENV_DEBUG "debug"
+#define LOG_ENV_INFO "info"
+#define LOG_ENV_WARNING "warning"
+#define LOG_ENV_ERROR "error"
+#define LOG_ENV_OFF "off"
+
+#define USAGE_MSG \
+ "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"
+
+#define INOTIFY_LIMIT_MSG \
+ "The current <b>inotify</b>(7) watch limit of %d is too low. " \
+ "<a href=\"http://confluence.jetbrains.net/display/IDEADEV/Inotify+Watches+Limit\">More details.</a>\n"
+
+typedef struct {
+ char* name;
+ int id;
+} watch_root;
+
+static array* roots = NULL;
+
+static bool show_warning = true;
+
+#define CHECK_NULL(p) if (p == NULL) { syslog(LOG_ERR, "out of memory"); return false; }
+
+static void init_log();
+static void main_loop();
+static bool read_input();
+static bool update_roots(array* new_roots);
+static void unregister_roots();
+static bool register_roots(array* new_roots, array* unwatchable);
+static bool unwatchable_mounts(array* mounts);
+static void inotify_callback(char* path, int event);
+
+
+int main(int argc, char** argv) {
+ if (argc == 2 && strcmp(argv[1], "--help") == 0) {
+ printf(USAGE_MSG);
+ return 0;
+ }
+
+ init_log();
+ syslog(LOG_INFO, "started");
+
+ 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();
+ unregister_roots();
+ }
+ else {
+ printf("GIVEUP\n");
+ }
+ close_inotify();
+ array_delete(roots);
+
+ syslog(LOG_INFO, "finished");
+ closelog();
+
+ return 0;
+}
+
+
+static void init_log() {
+ char* env_level = getenv(LOG_ENV);
+ int level = LOG_EMERG;
+ if (env_level != NULL) {
+ if (strcmp(env_level, LOG_ENV_DEBUG) == 0) level = LOG_DEBUG;
+ else if (strcmp(env_level, LOG_ENV_INFO) == 0) level = LOG_INFO;
+ else if (strcmp(env_level, LOG_ENV_WARNING) == 0) level = LOG_WARNING;
+ else if (strcmp(env_level, LOG_ENV_ERROR) == 0) level = LOG_ERR;
+ }
+
+ char ident[32];
+ snprintf(ident, sizeof(ident), "fsnotifier[%d]", getpid());
+ openlog(ident, 0, LOG_USER);
+ setlogmask(LOG_UPTO(level));
+}
+
+
+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 rfds;
+ bool go_on = true;
+
+ while (go_on) {
+ FD_ZERO(&rfds);
+ 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));
+ go_on = false;
+ }
+ else if (FD_ISSET(input_fd, &rfds)) {
+ go_on = read_input();
+ }
+ else if (FD_ISSET(inotify_fd, &rfds)) {
+ go_on = process_inotify_input();
+ }
+ }
+}
+
+
+static bool read_input() {
+ char* line = read_line(stdin);
+ syslog(LOG_DEBUG, "input: %s", (line ? line : "<null>"));
+
+ if (line == NULL || strcmp(line, "EXIT") == 0) {
+ return false;
+ }
+
+ if (strcmp(line, "ROOTS") == 0) {
+ array* new_roots = array_create(20);
+ CHECK_NULL(new_roots);
+
+ while (1) {
+ line = read_line(stdin);
+ syslog(LOG_DEBUG, "input: %s", (line ? line : "<null>"));
+ if (line == NULL || strlen(line) == 0) {
+ return false;
+ }
+ else if (strcmp(line, "#") == 0) {
+ break;
+ }
+ else {
+ if (line[0] == '|') line++; // flat roots will be differentiated later
+
+ int l = strlen(line);
+ if (l > 1 && line[l-1] == '/') line[l-1] = '\0';
+
+ CHECK_NULL(array_push(new_roots, strdup(line)));
+ }
+ }
+
+ return update_roots(new_roots);
+ }
+
+ return true;
+}
+
+
+static bool update_roots(array* new_roots) {
+ syslog(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: /");
+ array_delete_vs_data(new_roots);
+ return true;
+ }
+
+ array* unwatchable = array_create(20);
+ CHECK_NULL(unwatchable);
+ if (!unwatchable_mounts(unwatchable)) {
+ return false;
+ }
+
+ if (!register_roots(new_roots, unwatchable)) {
+ return false;
+ }
+
+ // todo: sort/optimize list
+ printf("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);
+ }
+ printf("#\n");
+
+ array_delete_vs_data(unwatchable);
+ array_delete(new_roots);
+
+ return true;
+}
+
+
+static void unregister_roots() {
+ watch_root* root;
+ while ((root = array_pop(roots)) != NULL) {
+ syslog(LOG_INFO, "unregistering root: %s\n", 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);
+ int id = watch(new_root, unwatchable);
+ if (id == ERR_ABORT) {
+ return false;
+ }
+ else if (id >= 0) {
+ watch_root* root = malloc(sizeof(watch_root));
+ CHECK_NULL(root);
+ root->id = id;
+ root->name = new_root;
+ CHECK_NULL(array_push(roots, root));
+ }
+ 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);
+ show_warning = false; // warn only once
+ }
+ CHECK_NULL(array_push(unwatchable, new_root));
+ }
+ }
+
+ return true;
+}
+
+static bool is_watchable(const char* dev, const char* mnt, const char* fs) {
+ // don't watch special and network filesystems
+ return !(strncmp(mnt, "/dev", 4) == 0 || strncmp(mnt, "/proc", 5) == 0 || strncmp(mnt, "/sys", 4) == 0 ||
+ strcmp(fs, "fuse.gvfs-fuse-daemon") == 0 || strcmp(fs, "cifs") == 0 || strcmp(fs, "nfs") == 0);
+}
+
+#define MTAB_DELIMS " \t"
+
+static bool unwatchable_mounts(array* mounts) {
+ FILE* mtab = fopen("/etc/mtab", "r");
+ if (mtab == NULL) {
+ mtab = fopen("/proc/mounts", "r");
+ }
+ if (mtab == NULL) {
+ syslog(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);
+ 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");
+ return false;
+ }
+
+ if (!is_watchable(dev, point, fs)) {
+ CHECK_NULL(array_push(mounts, strdup(point)));
+ }
+ }
+
+ fclose(mtab);
+ return true;
+}
+
+
+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);
+ return;
+ }
+
+ if (event & IN_MODIFY) {
+ printf("CHANGE\n%s\n", path);
+ syslog(LOG_DEBUG, "CHANGE: %s", path);
+ return;
+ }
+
+ if (event & IN_ATTRIB) {
+ printf("STATS\n%s\n", path);
+ syslog(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);
+ return;
+ }
+
+ if (event & IN_UNMOUNT) {
+ printf("RESET\n");
+ syslog(LOG_DEBUG, "RESET");
+ return;
+ }
+}
--- /dev/null
+gcc -O2 -m32 -Wall -std=c99 -D_BSD_SOURCE -D_XOPEN_SOURCE=500 -o fsnotifier main.c inotify.c util.c
--- /dev/null
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fsnotifier.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#define REALLOC_FACTOR 2
+
+struct __array {
+ void** data;
+ int size;
+ int capacity;
+};
+
+static bool array_realloc(array* a) {
+ if (a->size == a->capacity) {
+ int new_cap = a->capacity * REALLOC_FACTOR;
+ void* new_ptr = realloc(a->data, sizeof(void*) * new_cap);
+ if (new_ptr == NULL) {
+ return false;
+ }
+ a->capacity = new_cap;
+ a->data = new_ptr;
+ }
+ return true;
+}
+
+array* array_create(int initial_capacity) {
+ array* a = (array*) malloc(sizeof(array));
+ if (a == NULL) {
+ return NULL;
+ }
+
+ a->data = calloc(sizeof(void*), initial_capacity);
+ if (a->data == NULL) {
+ free(a);
+ return NULL;
+ }
+
+ a->capacity = initial_capacity;
+ a->size = 0;
+
+ return a;
+}
+
+inline int array_size(array* a) {
+ return (a != NULL ? a->size : 0);
+}
+
+void* array_push(array* a, void* element) {
+ if (a == NULL || !array_realloc(a)) {
+ return NULL;
+ }
+ a->data[a->size++] = element;
+ return element;
+}
+
+void* array_pop(array* a) {
+ if (a != NULL && a->size > 0) {
+ return a->data[--a->size];
+ }
+ else {
+ return NULL;
+ }
+}
+
+void array_put(array* a, int index, void* element) {
+ if (a != NULL && index >=0 && index < a->capacity) {
+ a->data[index] = element;
+ if (a->size <= index) {
+ a->size = index + 1;
+ }
+ }
+}
+
+void* array_get(array* a, int index) {
+ if (a != NULL && index >= 0 && index < a->size) {
+ return a->data[index];
+ }
+ else {
+ return NULL;
+ }
+}
+
+void array_delete(array* a) {
+ if (a != NULL) {
+ free(a->data);
+ free(a);
+ }
+}
+
+void array_delete_vs_data(array* a) {
+ if (a != NULL) {
+ for (int i=0; i<a->size; i++) {
+ if (a->data[i] != NULL) {
+ free(a->data[i]);
+ }
+ }
+ array_delete(a);
+ }
+}
+
+
+struct __table {
+ void** data;
+ int capacity;
+};
+
+table* table_create(int capacity) {
+ table* t = malloc(sizeof(table));
+ if (t == NULL) {
+ return NULL;
+ }
+
+ t->data = calloc(sizeof(void*), capacity);
+ if (t->data == NULL) {
+ free(t);
+ return NULL;
+ }
+ memset(t->data, 0, sizeof(void*) * capacity);
+
+ t->capacity = capacity;
+
+ return t;
+}
+
+static inline int wrap(int key, table* t) {
+ return (t != NULL ? key % t->capacity : -1);
+}
+
+// todo: resolve collisions (?)
+void* table_put(table* t, int key, void* value) {
+ int k = wrap(key, t);
+ if (k < 0 || (value != NULL && t->data[k] != NULL)) {
+ return NULL;
+ }
+ else {
+ return t->data[k] = value;
+ }
+}
+
+void* table_get(table* t, int key) {
+ int k = wrap(key, t);
+ if (k < 0) {
+ return NULL;
+ }
+ else {
+ return t->data[k];
+ }
+}
+
+void table_delete(table* t) {
+ if (t != NULL) {
+ free(t->data);
+ free(t);
+ }
+}
+
+
+#define INPUT_BUF_LEN 2048
+static char input_buf[INPUT_BUF_LEN];
+
+char* read_line(FILE* stream) {
+ char* retval = fgets(input_buf, INPUT_BUF_LEN, stream);
+ if (retval == NULL || feof(stream)) {
+ return NULL;
+ }
+ int pos = strlen(input_buf) - 1;
+ if (input_buf[pos] == '\n') {
+ input_buf[pos] = '\0';
+ }
+ return input_buf;
+}