From: Jann Horn Date: Sun, 16 Jun 2013 19:40:27 +0000 (+0200) Subject: initial commit X-Git-Url: http://git.thejh.net/?p=libjh.git;a=commitdiff_plain;h=e3a7f339e843d1424aef577e5634ac70343ec49c initial commit --- e3a7f339e843d1424aef577e5634ac70343ec49c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f62b84 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +gen diff --git a/README b/README new file mode 100644 index 0000000..b01211a --- /dev/null +++ b/README @@ -0,0 +1,4 @@ +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. diff --git a/compile.sh b/compile.sh new file mode 100755 index 0000000..0e00566 --- /dev/null +++ b/compile.sh @@ -0,0 +1,79 @@ +#!/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 diff --git a/fs.c b/fs.c new file mode 100644 index 0000000..f759719 --- /dev/null +++ b/fs.c @@ -0,0 +1,29 @@ +HEADER #include +HEADER #include +HEADER #include + +#include + +// 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; +} diff --git a/hex.c b/hex.c new file mode 100644 index 0000000..da394cc --- /dev/null +++ b/hex.c @@ -0,0 +1,46 @@ +HEADER #include +#include +#include + +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; +} diff --git a/ini.c b/ini.c new file mode 100644 index 0000000..033239a --- /dev/null +++ b/ini.c @@ -0,0 +1,96 @@ +// Supports very simple ini files. Structure: +// - data is in lines, key=value +// - # or ; starts a comment line +// - lines without = are ignored, too + +HEADER #include +#include +#include +#include +#include +#include + +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; +} diff --git a/io.c b/io.c new file mode 100644 index 0000000..d3b9a6d --- /dev/null +++ b/io.c @@ -0,0 +1,137 @@ +HEADER #include +#include +#include +#include +#include +#include +#include +#include + +// 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; +} diff --git a/string.c b/string.c new file mode 100644 index 0000000..21ff3a7 --- /dev/null +++ b/string.c @@ -0,0 +1,36 @@ +#include + +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); +}