diff --git a/native/fsNotifier/linux/fsnotifier b/native/fsNotifier/linux/fsnotifier new file mode 100755 index 000000000000..3651a1326a35 Binary files /dev/null and b/native/fsNotifier/linux/fsnotifier differ 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; +}