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