--- /dev/null
+A library for abstractions you need in most programs.
+Relies on the OS being POSIXly (and sometimes on it being Linux).
+Everything that needs watchers or so relies on libev – you'll probably
+have to use it as eventloop.
--- /dev/null
+#!/bin/bash
+# YES, THIS NEEDS BASH, NOT /bin/sh (e.g. for <<<).
+
+# -f for files with weird names
+# -u for coding mistakes (or against, to be precise)
+# -e and -o pipefail so that we don't have to spam the code with error handling
+set -f -u -e -o pipefail
+
+# flags for the build - adjust for your needs
+# delete all the generated stuff afterwards (with `rm -r gen`)
+CC='gcc'
+CFLAGS='-g -O0 -Wall -Werror -fPIC -std=c99'
+
+# create build environment if it doesn't exist yet
+mkdir -p gen # contains all generated files
+mkdir -p gen/realc # source files, with our preprocessing applied
+mkdir -p gen/realc_preprocessed # source files, preprocessed by the C compiler
+mkdir -p gen/chash # hashes of the preprocessed C files used to generate files in gen/obj
+mkdir -p gen/obj # object files
+
+echo "welcome. your friendly compiler will be \"$CC\" today." >&2
+echo "going ahead with CFLAGS=\"$CFLAGS\"..." >&2
+
+# generate header
+for source_file in $(ls|grep '\.c$'); do
+ echo "extracting header data from $source_file..." >&2
+ source_name="$(sed 's|\.c$||' <<< "$source_file")"
+ echo "/* ----======== $source_name ========---- */"
+
+ cat "$source_file" |
+ sed 's|^PUBLIC_FN \(.*\){|KEEPLINE \1;|g' |
+ sed 's|^PUBLIC_CONST |KEEPLINE #define |g' |
+ sed 's|^HEADER |KEEPLINE |g' |
+ grep '^KEEPLINE' |
+ sed 's|^KEEPLINE ||g'
+
+ echo ''
+ echo ''
+done > gen/libjh.h
+
+# preprocess all source files
+for source_file in $(ls|grep '\.c$'); do
+ echo "handling source file $source_file..." >&2
+ source_name="$(sed 's|\.c$||' <<< "$source_file")"
+
+ # do our own preprocessing
+ echo '#include "../libjh.h"' > "gen/realc/$source_name.c"
+
+ cat "$source_file" |
+ grep -v '^PUBLIC_CONST ' |
+ sed 's|^PUBLIC_FN ||g' |
+ grep -v '^HEADER ' |
+ cat >> "gen/realc/$source_name.c"
+ if [ $? -ne 0 ]; then exit 1; fi
+
+ # do the normal C preprocessing
+ echo 'preprocessing...' >&2
+ $CC -E "gen/realc/$source_name.c" > "gen/realc_preprocessed/$source_name.i"
+
+ # compile if there have been changes since the last time
+ echo -n 'checking sha checksum... ' >&2
+ file_hash="$(shasum "gen/realc_preprocessed/$source_name.i" | cut -d' ' -f1)"
+ # drop errors: that file might well be missing
+ set +e
+ last_file_hash="$(cat "gen/chash/$source_name" 2>/dev/null)"
+ set -e
+ if [ "$file_hash" = "$last_file_hash" ]; then
+ echo 'unchanged, will not compile again' >&2
+ else
+ echo 'changed – recompiling.' >&2
+ $CC $CFLAGS -c -o "gen/obj/$source_name.o" "gen/realc_preprocessed/$source_name.i"
+ echo "$file_hash" > "gen/chash/$source_name"
+ fi
+done
+
+# ... and link!
+cd gen/obj
+$CC -shared -Wl,-soname,libjh.so -o ../libjh.so $(ls)
+cd ../..
\ No newline at end of file
--- /dev/null
+HEADER #include <stdio.h>
+HEADER #include <sys/types.h>
+HEADER #include <dirent.h>
+
+#include <errno.h>
+
+// nonzero iterator exit code means cancel
+HEADER typedef int dir_iterator(struct dirent *dent, void *data);
+PUBLIC_FN int fdir_foreach(DIR *d, dir_iterator *iter, void *data) {
+ struct dirent de;
+ struct dirent *res;
+ while (1) {
+ int rd_res = readdir_r(d, &de, &res);
+ if (rd_res) return rd_res;
+ if (res == NULL) return 0;
+ int iter_res = iter(&de, data);
+ if (iter_res) return iter_res;
+ }
+}
+
+PUBLIC_FN int dir_foreach(char *path, dir_iterator *iter, void *data) {
+ DIR *d = opendir(path);
+ if (d == NULL) return 1;
+ int feach_res = fdir_foreach(d, iter, data);
+ int errno_ = errno;
+ closedir(d);
+ errno = errno_;
+ return feach_res;
+}
--- /dev/null
+HEADER #include <sys/types.h>
+#include <assert.h>
+#include <string.h>
+
+static inline char hexchar(unsigned char c) {
+ if (c >= 10) return c+'a'-10;
+ return c+'0';
+}
+
+// If `in_len` is -1, it means that `in` is a null-terminated string.
+// `out` must have space for `in_len*2+1` bytes.
+PUBLIC_FN void hex_encode(char *in, ssize_t in_len, char *out) {
+ if (in_len == -1) in_len = strlen(in);
+ assert(in_len >= 0);
+ char *in_end = in+in_len;
+ while (in < in_end) {
+ unsigned char c = *(unsigned char *)(in++);
+ *(out++) = hexchar(c>>4);
+ *(out++) = hexchar(c&0xf);
+ }
+ *(out++) = '\0';
+}
+
+static inline unsigned char unhexchar(char c) {
+ if (c >= '0' && c <= '9') return c-'0';
+ if (c >= 'a' && c <= 'f') return c-'a'+10;
+ if (c >= 'A' && c <= 'F') return c-'A'+10;
+ return 255;
+}
+
+// Returns number of decoded bytes or -1(error).
+// Does not null-terminate output.
+PUBLIC_FN ssize_t hex_decode(char *in, ssize_t in_len, char *out) {
+ char *out_ = out;
+ if (in_len == -1) in_len = strlen(in);
+ assert(in_len >= 0);
+ if (in_len&1) return -1; /* n%2 must be 0 */
+ char *in_end = in + in_len;
+ while (in < in_end) {
+ unsigned char hi = unhexchar(*(in++));
+ unsigned char lo = unhexchar(*(in++));
+ if (hi == 255 || lo == 255) return -1;
+ *(out++) = (hi<<4) | lo;
+ }
+ return out_ - out;
+}
--- /dev/null
+// Supports very simple ini files. Structure:
+// - data is in lines, key=value
+// - # or ; starts a comment line
+// - lines without = are ignored, too
+
+HEADER #include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+HEADER struct inifile_entry {
+HEADER char *key;
+HEADER char *value;
+HEADER struct inifile_entry *next;
+HEADER };
+HEADER
+HEADER struct inifile {
+HEADER struct inifile_entry *first;
+HEADER };
+
+// Parses an inifile. When you're done, you can free the whole thing
+// (including all the entries and the strings) by freeing the pointer
+// that this function returns.
+PUBLIC_FN struct inifile *parse_inifile(char *f) {
+ int n_of_lines = count_char_occurences(f, '\n')+1;
+ size_t worst_case_size = strlen(f) +
+ n_of_lines*sizeof(struct inifile_entry) +
+ sizeof(struct inifile);
+
+ char *buf = malloc(worst_case_size);
+ if (buf == NULL) return NULL;
+
+ struct inifile *head = (void*)buf;
+ void *buf_ = (void*)(head+1);
+
+ struct inifile_entry **nextp = &head->first;
+
+ while (*f) {
+ char *nl = strchr(f, '\n');
+ if (!nl) nl=f+strlen(f);
+ if (*f == '#' || *f == ';') goto next;
+ char *eqsign = strchr(f, '=');
+ if (!eqsign) break;
+ if (eqsign > nl) goto next;
+
+ struct inifile_entry *e = buf_; buf_ = e+1;
+ *nextp = e; nextp = &e->next;
+ char *key = f;
+ int keylen = eqsign-key;
+ char *value = eqsign+1;
+ int valuelen = nl-value;
+ e->key = buf_; buf_+=keylen+1;
+ memcpyn(e->key, key, keylen);
+ trim_end(e->key, " \r");
+ e->value = buf_; buf_+=valuelen+1;
+ memcpyn(e->value, value, valuelen);
+ trim_end(e->value, " \r");
+
+next:
+ f = nl;
+ if (!*f) break;
+ f++;
+ }
+
+ *nextp = NULL;
+ return head;
+}
+
+PUBLIC_FN struct inifile *fslurp_inifile(int fd) {
+ char *f = slurp_fd(fd, NULL, 0);
+ if (f == NULL) return NULL;
+ struct inifile *r = parse_inifile(f);
+ int errno_ = errno;
+ free(f);
+ errno = errno_;
+ return r;
+}
+
+PUBLIC_FN struct inifile *slurp_inifile(char *path) {
+ int fd = open(path, O_RDONLY);
+ if (fd == -1) return NULL;
+ struct inifile *r = fslurp_inifile(fd);
+ int errno_ = errno;
+ close(fd);
+ errno = errno_;
+ return r;
+}
+
+PUBLIC_FN char *inifile_lookup(struct inifile *inf, char *key) {
+ for (struct inifile_entry *e = inf->first; e; e=e->next) {
+ if (streq(e->key, key)) return e->value;
+ }
+ return NULL;
+}
--- /dev/null
+HEADER #include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+
+// Wrapper for `read` that retries on partial reads.
+// If last_res is non-NULL, it will be filled with the
+// result of the last read() call (but all positive numbers
+// become 1). In other words:
+// - -1: error
+// - 0: stream ended
+// - 1: no problems occured
+PUBLIC_FN ssize_t read_nointr(int fd, void *buf, size_t count, int *last_res) {
+ errno = 0;
+ size_t done = 0;
+ while (done < count) {
+ ssize_t part_res = read(fd, buf+done, count-done);
+ if (part_res <= 0) {
+ if (last_res) *last_res = part_res;
+ if (done) return done;
+ return part_res;
+ }
+ done += part_res;
+ }
+ if (last_res) *last_res = 1;
+ return done;
+}
+
+// Wrapper for `write` that retries on partial writes.
+// If last_res is non-NULL, it will be filled with the
+// result of the last write() call (but all positive numbers
+// become 1). In other words:
+// - -1: error
+// - 0: stream ended
+// - 1: no problems occured
+PUBLIC_FN ssize_t write_nointr(int fd, void *buf, size_t count, int *last_res) {
+ errno = 0;
+ size_t done = 0;
+ while (done < count) {
+ ssize_t part_res = write(fd, buf+done, count-done);
+ if (part_res <= 0) {
+ if (last_res) *last_res = part_res;
+ if (done) return done;
+ return part_res;
+ }
+ done += part_res;
+ }
+ if (last_res) *last_res = 1;
+ return done;
+}
+
+// Read all data from the given file descriptor. Tries fstat()+read()
+// first, but if fstat() doesn't work, falls back to multiple read()s.
+// Specify JH_NO_STAT to prevent the fstat() call.
+// The return value is a malloc'd buffer.
+// The buffer will be null-terminated, so you can read text files with
+// this and can specify len_out as NULL.
+PUBLIC_CONST JH_SLURP_NO_STAT 1
+PUBLIC_CONST JH_SLURP_REALLOC 2 /* realloc result block if it saves RAM */
+PUBLIC_FN char *slurp_fd(int fd, size_t *len_out, int flags) {
+ int errno_;
+
+ // Let's just guess that the file is 1023 bytes. Will become 1024 with
+ // the nullbyte.
+ size_t size_guess = 1023;
+ bool trusted_guess = false; /* can we rely on the guess? */
+
+ // If we can determine the exact size, we don't have to guess. So try
+ // to determine the exact size.
+ if (!(flags&JH_SLURP_NO_STAT)) {
+ struct stat st;
+ if (fstat(fd, &st) == 0) {
+ if (st.st_size > 0) {
+ size_guess = st.st_size;
+ trusted_guess = true;
+ }
+ }
+ }
+
+ char *buf = NULL;
+ int done = 0;
+
+ while (1) {
+ buf = realloc(buf, size_guess+1);
+ if (buf == NULL) return NULL;
+ int last_res;
+ ssize_t read_res = read_nointr(fd, buf+done, size_guess-done, &last_res);
+ if (last_res == -1) { errno_=errno; free(buf); errno=errno_; return NULL; }
+ done += read_res;
+ if (last_res == 0 || trusted_guess) {
+ // out
+ buf[done] = '\0';
+ if (done != size_guess && (flags&JH_SLURP_REALLOC)) {
+ // Well, it'd be weird if shrinking could fail... but meh, I can't find
+ // an explicit statement about this being disallowed, so try staying on
+ // the safe side.
+ char *buf_ = realloc(buf, done+1);
+ if (buf_) buf = buf_;
+ }
+ if (len_out) *len_out = done;
+ return buf;
+ }
+ size_guess<<=1; // try two times the buffer size
+ }
+}
+
+PUBLIC_FN char *slurp_file(char *path, size_t *len_out, int flags) {
+ int fd = open(path, O_RDONLY);
+ if (fd == -1) return NULL;
+ char *res = slurp_fd(fd, len_out, flags);
+ int errno_ = errno;
+ close(fd);
+ errno = errno_;
+ return res;
+}
+
+// Write data into a file. len can be -1; that means that
+// the real length is strlen(buf).
+PUBLIC_FN int write_file(char *path, char *buf, ssize_t len, int open_flags) {
+ if (len == -1) len = strlen(buf);
+
+ int fd = open(path, open_flags, 0777);
+ if (fd == -1) return 1;
+ ssize_t write_res = write_nointr(fd, buf, len, NULL);
+ int write_errno = errno;
+ int close_res = close(fd);
+ if (write_res != len) {
+ errno = write_errno;
+ return 1;
+ }
+ if (close_res) return 1;
+ return 0;
+}
--- /dev/null
+#include <string.h>
+
+HEADER #define streq(a,b) (!strcmp((a),(b)))
+
+PUBLIC_FN int count_char_occurences(char *s, char c) {
+ int n=0;
+ while (*s) {
+ if (*s==c) n++;
+ s++;
+ }
+ return n;
+}
+
+// memcpy plus terminating nullbyte
+PUBLIC_FN void *memcpyn(void *d, const void *s, size_t n) {
+ memcpy(d, s, n);
+ char *d_ = d;
+ d_[n] = '\0';
+ return d;
+}
+
+// Wipe out whitespace characters at the end of str using nullbytes.
+PUBLIC_FN void trim_end(char *str, char *whitespace) {
+ for (char *p = str+strlen(str)-1; p>=str; p--) {
+ if (!strchr(whitespace, *p)) break;
+ *p = '\0';
+ p--;
+ }
+}
+
+PUBLIC_FN int ends_with(char *str, char *sub) {
+ size_t str_len = strlen(str);
+ size_t sub_len = strlen(sub);
+ if (sub_len>str_len) return 0;
+ return streq(str+str_len-sub_len, sub);
+}