/* * Copyright (C) 2012-2013 Andy Spencer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #define _GNU_SOURCE #define _XOPEN_SOURCE #define _XOPEN_SOURCE_EXTENDED #define _POSIX_C_SOURCE 200100 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "conf.h" #include "view.h" #include "util.h" #pragma weak view_exit #pragma weak view_message /* Static data */ static int epoll = 0; static char *log_file = NULL; static FILE *log_fd = NULL; static int running = 0; static AspellSpeller *speller = NULL; /* View debugging */ extern void view_message(const char *prefix, const char *msg); /* Helper functions */ static void message(FILE *out_fd, const char *prefix, const char *fmt, va_list ap) { va_list tmp; struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); unsigned int sec = ts.tv_sec; unsigned int msec = ts.tv_nsec / 1000000; /* Log to standard out */ if (out_fd) { va_copy(tmp, ap); fprintf(out_fd, "%u.%03u: ", sec, msec); fprintf(out_fd, "%s: ", prefix); vfprintf(out_fd, fmt, tmp); fprintf(out_fd, "\n"); fflush(out_fd); } /* Log to debug file */ if (log_fd) { va_copy(tmp, ap); fprintf(log_fd, "%u.%03u: ", sec, msec); fprintf(log_fd, "%s: ", prefix); vfprintf(log_fd, fmt, tmp); fprintf(log_fd, "\n"); fflush(log_fd); } /* Log to status bar */ if (&view_message) { static char buf[512]; va_copy(tmp, ap); vsnprintf(buf, sizeof(buf), fmt, tmp); view_message(prefix, buf); } } static void on_idle(void *_idle) { uint64_t buf; idle_t *idle = _idle; debug("on_idle"); while (read(idle->poll.fd, &buf, sizeof(buf)) > 0) idle->timer(idle->data); } /* Initialize */ void util_init(void) { /* Init epol */ epoll = epoll_create(1); /* Init log file */ if (log_file) { wordexp_t wexp; wordexp(log_file, &wexp, WRDE_NOCMD); if (wexp.we_wordc > 1) error("Found multiple log files: %s\n %s\n %s\n ...", log_file, wexp.we_wordv[0], wexp.we_wordv[1]); if (!(log_fd = fopen(wexp.we_wordv[0], "w+"))) error("Failed to create log file: %s", log_file); wordfree(&wexp); } /* Init spelling */ AspellConfig *config = new_aspell_config(); AspellCanHaveError *status = new_aspell_speller(config); if (aspell_error_number(status)) error("ASpell error: %s\n", aspell_error_message(status)); speller = to_aspell_speller(status); /* Start running */ running = 1; } /* Cleanup */ void util_exit(void) { close(epoll); if (log_fd) fclose(log_fd); free0(&log_file); aspell_speller_save_all_word_lists(speller); delete_aspell_speller(speller); } /* Config */ void util_config(const char *group, const char *name, const char *key, const char *value) { if (match(group, "general")) { if (match(key, "logfile")) strset(&log_file, get_string(value)); } } /* String functions */ void strsub(char *str, char find, char repl) { for (char *cur = str; *cur; cur++) if (*cur == find) *cur = repl; } char *strcopy(const char *str) { if (str == NULL) return NULL; return strdup(str); } void strset(char **old, const char *str) { if (match(*old, str)) return; if (*old) free(*old); if (str) *old = strdup(str); else *old = NULL; } int compare(const char *a, const char *b) { if (a == b) return 0; if (!a) return -1; if (!b) return 1; return strcmp(a, b); } int match(const char *a, const char *b) { if (a == b) return 1; if (!a || !b) return 0; return !strcmp(a, b); } int starts(const char *prefix, const char *str) { if (prefix == str) return 1; if (!prefix || !prefix[0]) return 1; if (!str || !str[0]) return 0; int len = strlen(prefix); return !strncmp(prefix, str, len); } int prefix(const char *str, const char *prefix, const char **suffix) { char last = 0; while (*str && *prefix && *str == *prefix) { prefix++; last = *str++; } while (*str && *str == ' ') last = *str++; int val = 0; const char *arg = NULL; if (*prefix == '\0' && (*str == '\0' || last == ' ')) { arg = *str ? str : NULL; val = 1; } if (suffix) *suffix = arg; return val; } int suffix(const char *str, const char *prefix, const char **suffix) { const char *end = str; while (*end) end++; while (end > str && *end != ' ') end--; while (*end == ' ') end++; while (*end && *prefix && *end == *prefix) { end++; prefix++; } int val = 0; const char *arg = NULL; if (*prefix == '\0') { arg = *end ? end : NULL; val = 1; } if (suffix) *suffix = arg; return val; } char *despace(char *str) { int chr, spaces = 1; char *src = str; char *dst = str; while ((chr = *src++)) { if (isspace(chr)) spaces++; else spaces=0; if (spaces == 0) *dst++ = chr; if (spaces == 1) *dst++ = ' '; } if (dst > str && spaces) dst--; *dst = '\0'; return str; } void escape(char *dst, const char *src, int len) { int n, di = 0; const char *repl; for (int si=0; src[si]; si++) { switch (src[si]) { case '&': n = 5; repl = "&"; break; case '<': n = 4; repl = "<"; break; case '>': n = 4; repl = ">"; break; default: n = 1; repl = &src[si]; break; } if (di+n >= len) break; for (int ri = 0; ri < n; ri++) dst[di++] = repl[ri]; } dst[di] = '\0'; } /* Memory functions */ void *alloc0(int size) { void *data = calloc(1, size); if (!data) error("memory allocation failed"); return data; } void free0(void *ptr) { void **pptr = (void**)ptr; if (*pptr) { free(*pptr); *pptr = NULL; } } void append(buf_t *buf, const char *data, int len) { if (buf->len + len + 1 > buf->max) { buf->max += (((buf->len+len)/4096)+1)*4096; buf->data = realloc(buf->data, buf->max); if (!buf->data) error("buffer reallocation allocation failed"); } if (data) memcpy(buf->data + buf->len, data, len); buf->len += len; ((char*)buf->data)[buf->len] = '\0'; } void release(buf_t *buf) { free(buf->data); buf->data = 0; buf->len = 0; buf->max = 0; } const char *reset(buf_t *buf) { if (!buf->len) return NULL; buf->len = 0; return buf->data; } /* Data functions */ int base64(const void *_in, int ilen, void *_out, int olen) { static const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; const unsigned char *in = _in; unsigned char *out = _out; int val = 0; int len = ((ilen-1)/3+1)*4; if (olen < len) return 0; for (int i=0; i>6*3)&077]; *(out++) = table[(val>>6*2)&077]; *(out++) = table[(val>>6*1)&077]; *(out++) = table[(val>>6*0)&077]; } switch (ilen%3) { case 2: val = *(in++)<<020; val |= *(in++)<<010; *out++ = table[(val>>6*3)&077]; *out++ = table[(val>>6*2)&077]; *out++ = table[(val>>6*1)&077]; *out++ = '='; break; case 1: val = *(in++)<<020; *out++ = table[(val>>6*3)&077]; *out++ = table[(val>>6*2)&077]; *out++ = '='; *out++ = '='; break; } return len; } /* File functions */ char *read_file(const char *path, int *len) { /* we could use stat, but we'll try to be portable */ FILE *fd = fopen(path, "rt+"); if (!fd) return NULL; int block = 512; // read size int size = 512; // buffer size int slen = 0; // string length char *buf = malloc(size); if (!buf) goto err; while (!feof(fd)) { if (slen + block + 1 > size) { size *= 2; buf = realloc(buf, size); if (!buf) goto err; } slen += fread(&buf[slen], 1, block, fd); buf[slen] = '\0'; } err: if (len) *len = slen; fclose(fd); return buf; } /* Polling functions */ void poll_add(poll_t *poll, int fd, cb_t cb, void *data) { struct epoll_event ctl = { .events = EPOLLET | EPOLLIN | EPOLLOUT | EPOLLERR, .data.ptr = poll, }; poll->fd = fd; poll->cb = cb; poll->data = data; if (epoll_ctl(epoll, EPOLL_CTL_ADD, fd, &ctl) < 0) error("adding poll"); } void poll_ctl(poll_t *poll, int in, int out, int err) { struct epoll_event ctl = { .events = EPOLLET | (in ? EPOLLIN : 0) | (out ? EPOLLOUT : 0) | (err ? EPOLLERR : 0), .data.ptr = poll, }; if (epoll_ctl(epoll, EPOLL_CTL_MOD, poll->fd, &ctl) < 0) error("modifying poll"); } void poll_del(poll_t *poll) { if (epoll_ctl(epoll, EPOLL_CTL_DEL, poll->fd, NULL) < 0) error("deleting poll"); } int poll_run(int timeout) { poll_t *poll; int count; struct epoll_event event; while (running) { errno = 0; count = epoll_wait(epoll, &event, 1, timeout); if (errno == EINTR) continue; if (count < 0) continue; if (count == 0) return running; if (!(poll = event.data.ptr)) continue; if (!(poll->cb)) continue; //debug("poll fd=%d count=%d,%s%s%s%s%s%s%s%s%s%s%s%s%s%s", // poll->fd, count, // event.events & EPOLLIN ? " in" : "", // event.events & EPOLLPRI ? " pri" : "", // event.events & EPOLLOUT ? " out" : "", // event.events & EPOLLRDNORM ? " rdnorm" : "", // event.events & EPOLLRDBAND ? " rdband" : "", // event.events & EPOLLWRNORM ? " wrnorm" : "", // event.events & EPOLLWRBAND ? " wrband" : "", // event.events & EPOLLMSG ? " msg" : "", // event.events & EPOLLERR ? " err" : "", // event.events & EPOLLHUP ? " hup" : "", // event.events & EPOLLRDHUP ? " rdhup" : "", // event.events & EPOLLWAKEUP ? " wakeup" : "", // event.events & EPOLLONESHOT ? " oneshot" : "", // event.events & EPOLLET ? " et" : ""); poll->cb(poll->data); return running; } return running; } void poll_quit(void) { running = 0; } /* Idle functions */ void idle_add(idle_t *idle) { int fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC); if (fd < 0) error("creating timer fd"); poll_add(&idle->poll, fd, on_idle, idle); poll_ctl(&idle->poll, 1, 0, 1); } void idle_set(idle_t *idle, int first, int next) { int first_sec = first > 0 ? first : 0; int first_nsec = first < 0 ? -first : 0; int next_sec = next > 0 ? next : 0; int next_nsec = next < 0 ? -next : 0; struct timespec first_tspec = {first_sec, first_nsec}; struct timespec next_tspec = {next_sec, next_nsec}; struct itimerspec itspec = {next_tspec, first_tspec}; if (timerfd_settime(idle->poll.fd, 0, &itspec, NULL) < 0) error("setting idle time"); } /* Regular expressions */ void reg_set(reg_t *reg, const char *pattern, int size) { if (!pattern) { reg_clr(reg); return; } if (!reg->regex) reg->regex = new0(regex_t); if (size != reg->size) { reg->size = size; reg->start = realloc(reg->start, sizeof(int)*size); reg->stop = realloc(reg->stop, sizeof(int)*size); } strset(®->pattern, pattern); regcomp(reg->regex, pattern, REG_EXTENDED | (size?0:REG_NOSUB)); } void reg_clr(reg_t *reg) { if (reg->regex) regfree(reg->regex); free0(®->regex); free0(®->pattern); free0(®->start); free0(®->stop); memset(reg, 0, sizeof(*reg)); } int reg_match(reg_t *reg, const char *text) { if (!reg->regex) return 1; if (!reg->size) return !regexec(reg->regex, text, 0, 0, 0); int si = 0, mi = 0; regmatch_t match = {}; while (mi < reg->size && text[si] && !regexec(reg->regex, &text[si], 1, &match, 0)) { reg->start[mi] = si + match.rm_so; reg->stop[mi] = si + match.rm_eo - 1; if (match.rm_eo == 0) { si += 1; } else { si += match.rm_eo; mi += 1; } } reg->count = mi; reg->index = 0; return mi; } int reg_check(reg_t *reg, int pos) { if (!reg->count) return 0; int i = reg->index; int first = 0; int last = reg->count-1; while (i < last && pos > reg->stop[i]) i++; while (i > first && pos < reg->start[i]) i--; reg->index = i; if (pos < reg->start[i] || pos > reg->stop[i]) return MATCH_NONE; if (pos == reg->start[i] && pos == reg->stop[i]) return MATCH_BOTH; if (pos == reg->start[i]) return MATCH_START; if (pos == reg->stop[i]) return MATCH_STOP; return MATCH_HERE; } /* Spelling functions */ int spell_check(const char *word, const char **fixes) { int len = 0; /* Skip special words */ if (word[0] == '/' || word[0] == '@') return 0; /* Stop at end of word */ while (word[len] > 0 && word[len] < 0x80 && isalnum(word[len])) len++; /* Skip last word */ if (word[len] == '\0') return 0; /* Skip zero-length */ if (!len) return 0; return !aspell_speller_check(speller, word, len); } /* Debugging functions */ void notice(char *fmt, ...) { va_list ap; va_start(ap, fmt); message(NULL, "notice", fmt, ap); va_end(ap); } void debug(char *fmt, ...) { va_list ap; va_start(ap, fmt); message(NULL, "debug", fmt, ap); va_end(ap); } void error(char *fmt, ...) { va_list ap; va_start(ap, fmt); fflush(stdout); fflush(stderr); fprintf(stderr, "\r\n"); message(stderr, "error", fmt, ap); va_end(ap); if (view_exit) view_exit(); exit(-1); } /* Test case */ static void test_prefix(const char *str, const char *pre, const int val0, const char *arg0) { const char *arg1 = "junk"; int val1 = prefix(str, pre, &arg1); printf("%s -- [%s]%*s [%s]%*s -> %d=%d %s=%s\n", val0==val1 && match(arg0,arg1) ? "pass" : "fail", str, (int)(8-strlen(str)), "", pre, (int)(5-strlen(pre)), "", val0, val1, arg0, arg1); } static void test_suffix(const char *str, const char *suf, const int val0, const char *arg0) { const char *arg1 = "junk"; int val1 = suffix(str, suf, &arg1); printf("%s -- [%s]%*s [%s]%*s -> %d=%d %s=%s\n", val0==val1 && match(arg0,arg1) ? "pass" : "fail", str, (int)(8-strlen(str)), "", suf, (int)(5-strlen(suf)), "", val0, val1, arg0, arg1); } static void test_regex(const char *pattern, const char *text, const int count, const char *expect) { char actual[80] = {}; reg_t reg = {}; reg_set(®, pattern, 16); reg_match(®, text); for (int i = 0; text[i]; i++) actual[i] = MATCH_STR[reg_check(®, i)]; printf("%s -- /%s/%*s [%s]%*s -> %d=%d [%s]=[%s]\n", (match(actual, expect) && count == reg.count) ? "pass" : "fail", pattern, (int)(8-strlen(pattern)), "", text, (int)(5-strlen(text)), "", count, reg.count, actual, expect); reg_clr(®); } void util_test(void) { test_prefix("/foo", "/foo", 1, NULL); test_prefix("/foo ", "/foo", 1, NULL); test_prefix("/foobar", "/foo", 0, NULL); test_prefix("/foo bar", "/foo", 1, "bar"); test_prefix("/foo", "/foo ", 0, NULL); test_prefix("/foo ", "/foo ", 1, NULL); test_prefix("/foobar", "/foo ", 0, NULL); test_prefix("/foo bar", "/foo ", 1, "bar"); test_suffix("@", "@", 1, NULL); test_suffix(" @", "@", 1, NULL); test_suffix("@foo", "@", 1, "foo"); test_suffix(" @foo", "@", 1, "foo"); test_suffix("@ ", "@", 0, NULL); test_suffix("@ foo", "@", 0, NULL); test_regex("abc", "abc", 1, "<->"); test_regex("a.c", "abc", 1, "<->"); test_regex("a.*c", "abc", 1, "<->"); test_regex("[ac]", "abc", 2, "^ ^"); test_regex("a*", "aa aa", 2, "<> <>"); test_regex("aa*", "aa aa", 2, "<> <>"); test_regex("a", "aa aa", 4, "^^ ^^"); }