initial commit
authorJann Horn <jann@thejh.net>
Sun, 16 Jun 2013 19:40:27 +0000 (21:40 +0200)
committerJann Horn <jann@thejh.net>
Sun, 16 Jun 2013 19:40:27 +0000 (21:40 +0200)
.gitignore [new file with mode: 0644]
README [new file with mode: 0644]
compile.sh [new file with mode: 0755]
fs.c [new file with mode: 0644]
hex.c [new file with mode: 0644]
ini.c [new file with mode: 0644]
io.c [new file with mode: 0644]
string.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..4f62b84
--- /dev/null
@@ -0,0 +1 @@
+gen
diff --git a/README b/README
new file mode 100644 (file)
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 (executable)
index 0000000..0e00566
--- /dev/null
@@ -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 (file)
index 0000000..f759719
--- /dev/null
+++ b/fs.c
@@ -0,0 +1,29 @@
+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;
+}
diff --git a/hex.c b/hex.c
new file mode 100644 (file)
index 0000000..da394cc
--- /dev/null
+++ b/hex.c
@@ -0,0 +1,46 @@
+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;
+}
diff --git a/ini.c b/ini.c
new file mode 100644 (file)
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 <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;
+}
diff --git a/io.c b/io.c
new file mode 100644 (file)
index 0000000..d3b9a6d
--- /dev/null
+++ b/io.c
@@ -0,0 +1,137 @@
+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;
+}
diff --git a/string.c b/string.c
new file mode 100644 (file)
index 0000000..21ff3a7
--- /dev/null
+++ b/string.c
@@ -0,0 +1,36 @@
+#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);
+}