From 77577bf205d24d84aa8e14318a9aaf7edee6472f Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 11 Mar 2010 13:37:37 +0300 Subject: [PATCH] File system events notifier for Linux --- native/fsNotifier/linux/fsnotifier | Bin 0 -> 23388 bytes native/fsNotifier/linux/fsnotifier.h | 68 +++++ native/fsNotifier/linux/inotify.c | 376 +++++++++++++++++++++++++++ native/fsNotifier/linux/main.c | 326 +++++++++++++++++++++++ native/fsNotifier/linux/make.sh | 1 + native/fsNotifier/linux/util.c | 190 ++++++++++++++ 6 files changed, 961 insertions(+) create mode 100755 native/fsNotifier/linux/fsnotifier create mode 100644 native/fsNotifier/linux/fsnotifier.h create mode 100644 native/fsNotifier/linux/inotify.c create mode 100644 native/fsNotifier/linux/main.c create mode 100755 native/fsNotifier/linux/make.sh create mode 100644 native/fsNotifier/linux/util.c diff --git a/native/fsNotifier/linux/fsnotifier b/native/fsNotifier/linux/fsnotifier new file mode 100755 index 0000000000000000000000000000000000000000..3651a1326a35eeff6a4c0c144ab6b70d01a67095 GIT binary patch literal 23388 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 literal 0 HcmV?d00001 diff --git a/native/fsNotifier/linux/fsnotifier.h b/native/fsNotifier/linux/fsnotifier.h new file mode 100644 index 000000000000..1023f183bf19 --- /dev/null +++ b/native/fsNotifier/linux/fsnotifier.h @@ -0,0 +1,68 @@ +/* + * 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 +#include + + +// 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 diff --git a/native/fsNotifier/linux/inotify.c b/native/fsNotifier/linux/inotify.c new file mode 100644 index 000000000000..ae799e262489 --- /dev/null +++ b/native/fsNotifier/linux/inotify.c @@ -0,0 +1,376 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#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; ikids); 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; iparent->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= 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; ikids); 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); + } +} diff --git a/native/fsNotifier/linux/main.c b/native/fsNotifier/linux/main.c new file mode 100644 index 000000000000..ba498a7913bd --- /dev/null +++ b/native/fsNotifier/linux/main.c @@ -0,0 +1,326 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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 inotify(7) watch limit of %d is too low. " \ + "More details.\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 : "")); + + 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 : "")); + 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; iname); + unwatch(root->id); + free(root->name); + free(root); + }; +} + + +static bool register_roots(array* new_roots, array* unwatchable) { + for (int i=0; i= 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; + } +} diff --git a/native/fsNotifier/linux/make.sh b/native/fsNotifier/linux/make.sh new file mode 100755 index 000000000000..74eeb734e648 --- /dev/null +++ b/native/fsNotifier/linux/make.sh @@ -0,0 +1 @@ +gcc -O2 -m32 -Wall -std=c99 -D_BSD_SOURCE -D_XOPEN_SOURCE=500 -o fsnotifier main.c inotify.c util.c diff --git a/native/fsNotifier/linux/util.c b/native/fsNotifier/linux/util.c new file mode 100644 index 000000000000..6e09a9053d80 --- /dev/null +++ b/native/fsNotifier/linux/util.c @@ -0,0 +1,190 @@ +/* + * 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 +#include +#include + + +#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; isize; 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; +}