From e3a7f339e843d1424aef577e5634ac70343ec49c Mon Sep 17 00:00:00 2001 From: Jann Horn Date: Sun, 16 Jun 2013 21:40:27 +0200 Subject: [PATCH 1/1] initial commit --- .gitignore | 1 + README | 4 ++ compile.sh | 79 ++++++++++++++++++++++++++++++ fs.c | 29 ++++++++++++ hex.c | 46 ++++++++++++++++++ ini.c | 96 +++++++++++++++++++++++++++++++++++++ io.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++ string.c | 36 ++++++++++++++ 8 files changed, 428 insertions(+) create mode 100644 .gitignore create mode 100644 README create mode 100755 compile.sh create mode 100644 fs.c create mode 100644 hex.c create mode 100644 ini.c create mode 100644 io.c create mode 100644 string.c 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); +} -- 2.20.1