aboutsummaryrefslogtreecommitdiff
path: root/string_buf.c
diff options
context:
space:
mode:
Diffstat (limited to 'string_buf.c')
-rw-r--r--string_buf.c308
1 files changed, 308 insertions, 0 deletions
diff --git a/string_buf.c b/string_buf.c
new file mode 100644
index 0000000..332d023
--- /dev/null
+++ b/string_buf.c
@@ -0,0 +1,308 @@
+/**
+ * C string buffers for easy construction of complex strings
+ *
+ * Copyright (C) 2021 Wojtek Kosior
+ * Redistribution terms are gathered in the `copyright' file.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <unistd.h>
+
+#define STRING_BUF_C
+#include "string_buf.h"
+
+#define MINIMUM_EXTEND_BYTES 63
+
+void stringbuf_init(struct stringbuf *sb)
+{
+ sb->buf = NULL;
+ sb->buf_len = 0;
+ sb->buf_filled = 0;
+}
+
+void stringbuf_destroy(struct stringbuf *sb)
+{
+ free(sb->buf);
+}
+
+void stringbuf_truncate(struct stringbuf *sb, size_t len)
+{
+ if (sb->buf_len <= len)
+ return;
+
+ sb->buf_filled = len;
+ sb->buf[len] = '\0';
+}
+
+#define _UNPACKED_ARGS &sb->buf, &sb->buf_len, &sb->buf_filled
+
+int extend_buf(struct stringbuf *sb, size_t extend_len)
+{
+ return extend_buf_raw(_UNPACKED_ARGS, extend_len);
+}
+
+int extend_buf_raw(_RAW_BUF_ARGS, size_t extend_len)
+{
+ ssize_t space_left = *buf_len - *buf_filled - 1;
+ size_t new_size, size_required, size_more_space;
+ char *new_buf;
+
+ if (space_left >= 0 && space_left >= extend_len)
+ return 0;
+
+ size_required = *buf_filled + extend_len + 1;
+ size_more_space = size_required + MINIMUM_EXTEND_BYTES;
+ new_size = *buf_len * 2;
+ if (new_size < size_more_space)
+ new_size = size_more_space;
+
+ new_buf = realloc(*buf, new_size);
+ if (!new_buf) {
+ new_size = size_required;
+ new_buf = realloc(*buf, new_size);
+ }
+ if (!new_buf)
+ return -1;
+
+ *buf = new_buf;
+ *buf_len = new_size;
+ return 0;
+}
+
+_SB_HEAD(bytes, const void *bytes, size_t bytes_len)
+{
+ return sb_raw_bytes(_UNPACKED_ARGS, bytes, bytes_len);
+}
+
+_SB_RAW_HEAD(bytes, const void *bytes, size_t bytes_len)
+{
+ if (extend_buf_raw(buf, buf_len, buf_filled, bytes_len))
+ return -1;
+
+ memcpy(*buf + *buf_filled, bytes, bytes_len);
+ (*buf)[*buf_filled + bytes_len] = '\0';
+ *buf_filled += bytes_len;
+
+ return 0;
+}
+
+_SB_HEAD(string, const char *string)
+{
+ return sb_raw_string(_UNPACKED_ARGS, string);
+}
+
+_SB_RAW_HEAD(string, const char *string)
+{
+ size_t string_len = strlen(string);
+
+ return sb_raw_bytes(buf, buf_len, buf_filled, string, string_len);
+}
+
+_SB_HEAD(char, char c)
+{
+ return sb_raw_char(_UNPACKED_ARGS, c);
+}
+
+_SB_RAW_HEAD(char, char c)
+{
+ return sb_raw_bytes(buf, buf_len, buf_filled, &c, 1);
+}
+
+_SB_HEAD(long, long num)
+{
+ return sb_raw_long(_UNPACKED_ARGS, num);
+}
+
+_SB_RAW_HEAD(long, long num)
+{
+ unsigned char repr[3 * sizeof(long) + 1];
+ int i;
+ bool neg = num < 0;
+
+ for (i = sizeof(repr); num; num /= 10)
+ repr[--i] = '0' + num % 10;
+
+ if (i == sizeof(repr))
+ repr[--i] = '0';
+ else if (neg)
+ repr[--i] = '-';
+
+ sb_raw_bytes(buf, buf_len, buf_filled,
+ repr + i, sizeof(repr) - i);
+
+ return 0;
+}
+
+_SB_HEAD(file, FILE *file)
+{
+ return sb_raw_file(_UNPACKED_ARGS, file);
+}
+
+_SB_RAW_HEAD(file, FILE *file)
+{
+ long file_size;
+
+ if (fseek(file, 0, SEEK_END))
+ return -1;
+
+ file_size = ftell(file);
+ if (file_size < 0)
+ return -1;
+
+ if (extend_buf_raw(buf, buf_len, buf_filled, file_size))
+ return -1;
+
+ rewind(file);
+
+ if (fread(*buf + *buf_filled, file_size, 1, file) != 1)
+ return -1;
+
+ (*buf)[*buf_filled + file_size] = '\0';
+ *buf_filled += file_size;
+
+ return 0;
+}
+
+_SB_HEAD(filepath, const char *path)
+{
+ return sb_raw_filepath(_UNPACKED_ARGS, path);
+}
+
+_SB_RAW_HEAD(filepath, const char *path)
+{
+ FILE *file;
+ int retval;
+
+ file = fopen(path, "r");
+ if (!file)
+ return -1;
+
+ retval = sb_raw_file(buf, buf_len, buf_filled, file);
+
+ fclose(file);
+ return retval;
+}
+
+_SB_HEAD(vsprintf, const char *fmt, va_list ap)
+{
+ return sb_raw_vsprintf(_UNPACKED_ARGS, fmt, ap);
+}
+
+_SB_RAW_HEAD(vsprintf, const char *fmt, va_list ap)
+{
+ const unsigned char *in_pos = (const unsigned char*) fmt;
+ char c;
+ size_t i = 0;
+ bool percent = false;
+
+ long num_arg;
+ int (*sb_cb)(char**, size_t*, size_t*, void*);
+
+ while (in_pos[i]) {
+ c = in_pos[i++];
+
+ if (!percent) {
+ if (c == '%') {
+ percent = true;
+ if (sb_raw_bytes(buf, buf_len, buf_filled,
+ in_pos, i - 1))
+ return -1;
+ }
+
+ continue;
+ }
+
+ percent = false;
+ in_pos += i;
+ i = 0;
+
+ switch (c) {
+ case 'd':
+ case 'u':
+ num_arg = c == 'd' ?
+ va_arg(ap, int) : va_arg(ap, unsigned);
+
+ if (sb_raw_long(buf, buf_len, buf_filled, num_arg))
+ return -1;
+ break;
+ case 'f':
+ if (sb_raw_file(buf, buf_len, buf_filled,
+ va_arg(ap, FILE*)))
+ return -1;
+ break;
+ case 'p':
+ if (sb_raw_filepath(buf, buf_len, buf_filled,
+ va_arg(ap, const char*)))
+ return -1;
+ break;
+ case 's':
+ if (sb_raw_string(buf, buf_len, buf_filled,
+ va_arg(ap, const char*)))
+ return -1;
+ break;
+ case '_':
+ sb_cb = va_arg(ap, int (*)(char**, size_t*,
+ size_t*, void*));
+ if (sb_cb(buf, buf_len, buf_filled,
+ va_arg(ap, void*)))
+ return -1;
+ break;
+ case '%':
+ in_pos--;
+ i++;
+ }
+ }
+
+ if (!percent && sb_raw_bytes(buf, buf_len, buf_filled, in_pos, i))
+ return -1;
+
+ return 0;
+}
+
+_SB_HEAD(sprintf, const char *fmt, ...)
+{
+ va_list ap;
+ int res;
+
+ va_start(ap, fmt);
+ res = sb_raw_vsprintf(_UNPACKED_ARGS, fmt, ap);
+ va_end(ap);
+
+ return res;
+}
+
+_SB_RAW_HEAD(sprintf, const char *fmt, ...)
+{
+ va_list ap;
+ int res;
+
+ va_start(ap, fmt);
+ res = sb_raw_vsprintf(buf, buf_len, buf_filled, fmt, ap);
+ va_end(ap);
+
+ return res;
+}
+
+int crop_buf(struct stringbuf *sb)
+{
+ return crop_buf_raw(_UNPACKED_ARGS);
+}
+
+int crop_buf_raw(_RAW_BUF_ARGS)
+{
+ char *new_buf;
+
+ if (*buf_len <= *buf_filled + 1)
+ return 0;
+
+ new_buf = realloc(*buf, *buf_filled + 1);
+ if (!new_buf)
+ return -1;
+
+ *buf = new_buf;
+ *buf_len = *buf_filled + 1;
+ return 0;
+}