tarw.h

Minimalist header-only library for generating TAR files

Commit
d8bfb69668ee5cd3dcf60c0a8f023465b4040fda
Parent
8e80513468b619ca5c4593736deb50705525cbe9
Author
Pablo <pablo-pie@riseup.net>
Date

Implemented buffered IO

NOTE: substantially changed the API

Diffstats

2 files changed, 118 insertions, 24 deletions

Status Name Changes Insertions Deletions
Modified example.c 2 files changed 12 7
Modified tarw.h 2 files changed 106 17
diff --git a/example.c b/example.c
@@ -7,29 +7,34 @@
 int main(void)
 {
   const char *output_path = "./test.tar";
-  FILE *sink = fopen(output_path, "wb");
+  FILE *output = fopen(output_path, "wb");
 
-  if (sink == NULL) {
+  if (output == NULL) {
     fprintf(stderr, "ERROR: could not open \"%s\"\n", output_path);
     return EXIT_FAILURE;
   }
 
   TarWriter w = {
-    .sink     = sink,
     .owner    = "user",
     .group    = "group",
     .owner_id = 1000,
     .group_id = 1000,
   };
 
-  if (!tarw_write_directory(w, "test/", 0755)) goto error;
+  if (!tarw_add_directory(&w, "test/", 0755)) goto not_enought_memmory;
 
   const char *msg = "Hello from C!\n";
-  if (!tarw_write_file(w, "test/a", 0644, msg, strlen(msg))) goto error;
+  if (!tarw_add_file(&w, "test/a", 0644, msg, strlen(msg)))
+    goto not_enought_memmory;
+
+  if (!tarw_write(w, output)) {
+    fprintf(stderr, "ERROR: could write all bytes to \"%s\"\n", output_path);
+    return EXIT_FAILURE;
+  }
 
   return EXIT_SUCCESS;
 
-error:
-  fprintf(stderr, "ERROR: could write all bytes to \"%s\"\n", output_path);
+not_enought_memmory:
+  fprintf(stderr, "ERROR: could not allocate enough memmory\n");
   return EXIT_FAILURE;
 }
diff --git a/tarw.h b/tarw.h
@@ -12,17 +12,22 @@ extern "C" {
 #endif // __cplusplus
 
 typedef struct {
-  FILE *sink;
-
   const char *owner;
   const char *group;
   size_t owner_id;
   size_t group_id;
+
+  char   *data;
+  size_t cursor;
+  size_t capacity;
 } TarWriter;
 
-bool tarw_write_directory(TarWriter w, const char *path, uint16_t mode);
-bool tarw_write_file(TarWriter w, const char *path, uint16_t mode,
-                                  const char *buff, size_t buff_size);
+bool tarw_add_directory(TarWriter *w, const char *path, uint16_t mode);
+bool tarw_add_file(TarWriter *w, const char *path, uint16_t mode,
+                                 const char *buff, size_t buff_size);
+bool tarw_write(TarWriter w, FILE *f);
+
+void tarw_free(TarWriter *w);
 
 #define TARW_CHUNK_SIZE 512
 
@@ -68,9 +73,34 @@ enum {
   TARW_PIPE             = '6', // TODO: unsupported
 };
 
+// = Implementation ===========================================================
 #ifdef TARW_IMPLEMENTATION
+
 #define TARW_USTARW_INDICATOR "ustar"
 
+
+#if !defined(TARW_REALLOC) || !defined(TARW_FREE)
+#include <stdlib.h>
+#endif
+
+/*
+ * NOTE: should behave like realloc from libc:
+ *       - returns `NULL` on fail
+ *       - if `p` is `NULL` then behave like malloc
+ */
+#ifndef TARW_REALLOC
+#define TARW_REALLOC(p, size) realloc((p), (size))
+#endif // TARW_REALLOC
+
+#ifndef TARW_FREE
+#define TARW_FREE(p) free((p))
+#endif // TARW_FREE
+
+
+#ifdef defined(TARW_EMIT_ASSERTS) && !defined(TARW_ASSERT)
+#define TARW_ASSERT(cond, msg) assert((cond) && (msg))
+#endif
+
 void tarw_header_set_checksum(TarHeader *h)
 {
   uint8_t *data = (uint8_t*)h;
@@ -110,33 +140,92 @@ TarHeader tarw_make_header(const char *path,  uint16_t mode,
   return h;
 }
 
-bool tarw_write_directory(TarWriter w, const char *path, uint16_t mode)
+bool tarw__reserve_bytes(TarWriter *w, size_t bytes)
+{
+  if (w->capacity - w->cursor >= bytes) return true;
+
+  size_t new_capacity = 2 * w->capacity;
+  if (new_capacity - w->cursor < bytes) new_capacity = w->capacity + bytes;
+
+  void *new_data = TARW_REALLOC(w->data, new_capacity);
+  if (new_data == NULL) return false;
+  w->data     = new_data;
+  w->capacity = new_capacity;
+
+  return true;
+}
+
+/*
+ * NOTE: it is only safe to call this function when buff fits inside w->data!
+ */
+void tarw__write_bytes(TarWriter *w, const char *buff, size_t buff_size)
+{
+#ifdef TARW_EMIT_ASSERTS
+  TARW_ASSERT(w->capactiry - w->cursor >= buff_size, "buffer overflow");
+#endif // TARW_EMIT_ASSERTS
+  memcpy(w->data + w->cursor, buff, buff_size);
+  w->cursor += buff_size;
+}
+
+/*
+ * NOTE: it is only safe to call this function when buff fits inside w->data!
+ */
+void tarw__write_zeros(TarWriter *w, size_t n)
 {
+#ifdef TARW_EMIT_ASSERTS
+  TARW_ASSERT(w->capactiry - w->cursor >= buff_size, "buffer overflow");
+#endif // TARW_EMIT_ASSERTS
+  memset(w->data + w->cursor, 0, n);
+  w->cursor += n;
+}
+
+bool tarw_add_directory(TarWriter *w, const char *path, uint16_t mode)
+{
+  if (!tarw__reserve_bytes(w, TARW_CHUNK_SIZE)) return false;
+
   TarHeader h = tarw_make_header(path, mode,
-                                 w.owner, w.group, w.owner_id, w.group_id,
+                                 w->owner, w->group, w->owner_id, w->group_id,
                                  TARW_DIRECTORY, 0);
 
-  if (fwrite((char*)&h, 1, sizeof(h), w.sink) != sizeof(h)) return false;
+  tarw__write_bytes(w, (char*)&h, TARW_CHUNK_SIZE);
   return true;
 }
 
-bool tarw_write_file(TarWriter w, const char *path, uint16_t mode,
-                                  const char *buff, size_t buff_size)
+bool tarw_add_file(TarWriter *w, const char *path, uint16_t mode,
+                                 const char *buff, size_t buff_size)
 {
+  size_t chunks_ceil     = (buff_size + (TARW_CHUNK_SIZE-1))/TARW_CHUNK_SIZE;
+  size_t padding         = chunks_ceil*TARW_CHUNK_SIZE - buff_size;
+  size_t capacity_needed = TARW_CHUNK_SIZE + buff_size + padding;
+  if (!tarw__reserve_bytes(w, capacity_needed)) return false;
+
   TarHeader h = tarw_make_header(path, mode,
-                                 w.owner, w.group, w.owner_id, w.group_id,
+                                 w->owner, w->group, w->owner_id, w->group_id,
                                  TARW_NORMAL, buff_size);
 
-  if (fwrite((char*)&h, 1, sizeof(h), w.sink) != sizeof(h)) return false;
-  if (fwrite(buff, 1, buff_size, w.sink) != buff_size) return false;
+  tarw__write_bytes(w, (char*)&h, sizeof(h));
+  tarw__write_bytes(w, buff, buff_size);
+  tarw__write_zeros(w, padding);
+
+  return true;
+}
 
-  static const char zeros[TARW_CHUNK_SIZE] = {0};
-  size_t chunks_ceil = (buff_size + (TARW_CHUNK_SIZE-1))/TARW_CHUNK_SIZE;
-  size_t padding = chunks_ceil*TARW_CHUNK_SIZE - buff_size;
-  if (fwrite(zeros, 1, padding, w.sink) != padding) return false;
+bool tarw_write(TarWriter w, FILE *f)
+{
+  if (fwrite(w.data, 1, w.cursor, f) != w.cursor) return false;
   return true;
 }
 
+void tarw_free(TarWriter *w)
+{
+  if (w->data) {
+    TARW_FREE(w->data);
+    w->data = NULL;
+  }
+  w->capacity = 0;
+  w->cursor   = 0;
+}
+
 #endif // TARW_IMPLEMENTATION
 #ifdef __cplusplus
 }