/** * 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; }