/*
* dynstr.c - implement dynamic byte strings
* Copyright 2010 Lars Wirzenius
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/* We shamelessly use GNU extensions to the standard C library.
* Primarily this is the memmem(3) function. Patches are welcome to
* get rid of this, but I did not feel like re-inventing the wheel.
*/
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include "dynstr.h"
/* This is how the dynamic strings are represented. */
struct Dynstr {
unsigned char *mem;
size_t size;
};
/* Return value for readline_helper's callback functions. */
#define DYNSTR_ERROR ((size_t) -1)
/* The current malloc and malloc error handler functions. */
static dynstr_error_handler *error_handler = dynstr_malloc_error_indicate;
static void *(*current_malloc)(size_t) = malloc;
void dynstr_init(void)
{
error_handler = dynstr_malloc_error_indicate;
current_malloc = malloc;
}
void *(*dynstr_get_malloc(void))(size_t)
{
return current_malloc;
}
void dynstr_set_malloc(void *(*new_malloc)(size_t))
{
current_malloc = new_malloc;
}
dynstr_error_handler *dynstr_get_malloc_error_handler(void)
{
return error_handler;
}
void dynstr_set_malloc_error_handler(dynstr_error_handler *handler)
{
error_handler = handler;
}
void dynstr_malloc_error_indicate(int error, size_t size, void *oldptr)
{
}
void dynstr_malloc_error_abort(int error, size_t size, void *oldptr)
{
abort();
}
/* Dynamically allocate a number of bytes. Call error handler if necessary. */
static void *alloc(size_t size)
{
void *p;
p = current_malloc(size);
if (p == NULL)
error_handler(errno, size, NULL);
return p;
}
static Dynstr *new_dynstr(const void *mem, size_t size, bool dynamic)
{
Dynstr *dynstr;
if (dynamic) {
dynstr = alloc(sizeof(Dynstr) + size);
if (dynstr == NULL)
return NULL;
dynstr->mem = ((unsigned char *) dynstr) + sizeof(Dynstr);
} else {
dynstr = alloc(sizeof(Dynstr));
if (dynstr == NULL)
return NULL;
dynstr->mem = (unsigned char *) mem;
}
dynstr->size = size;
return dynstr;
}
static void init_dynstr(Dynstr *dynstr, const void *mem, bool dynamic)
{
if (dynamic)
memcpy(dynstr->mem, mem, dynstr->size);
}
/* Allocate a new dynamic string. If dynamic is true, the contents of the
* new string is copied from mem. Otherwise the new string just uses mem
* directly. */
static Dynstr *newstr(const void *mem, size_t size, bool dynamic)
{
Dynstr *dynstr;
dynstr = new_dynstr(mem, size, dynamic);
if (dynstr != NULL)
init_dynstr(dynstr, mem, dynamic);
return dynstr;
}
Dynstr *dynstr_new_empty(void)
{
return new_dynstr(NULL, 0, false);
}
Dynstr *dynstr_new_from_cstring(const char *cstring)
{
return newstr(cstring, strlen(cstring), true);
}
Dynstr *dynstr_new_from_memory(const void *mem, size_t size)
{
return newstr(mem, size, true);
}
Dynstr *dynstr_new_from_constant_cstring(const char *cstring)
{
return newstr(cstring, strlen(cstring), false);
}
Dynstr *dynstr_new_from_constant_memory(const void *mem, size_t size)
{
return newstr(mem, size, false);
}
void dynstr_free(Dynstr *dynstr)
{
free(dynstr);
}
size_t dynstr_len(Dynstr *dynstr)
{
return dynstr->size;
}
bool dynstr_is_empty(Dynstr *dynstr)
{
return dynstr_len(dynstr) == 0;
}
size_t dynstr_memcpy(void *mem, Dynstr *dynstr, size_t offset, size_t size)
{
size_t len;
len = dynstr_len(dynstr);
if (offset >= len)
return 0;
if (size > len - offset)
size = len - offset;
memcpy(mem, dynstr->mem + offset, size);
return size;
}
char *dynstr_strdup(Dynstr *dynstr)
{
char *mem;
mem = alloc(dynstr->size + 1);
if (mem == NULL)
return NULL;
memcpy(mem, dynstr->mem, dynstr->size);
mem[dynstr->size] = '\0';
return mem;
}
Dynstr *dynstr_substr(Dynstr *dynstr, size_t offset, size_t size)
{
if (offset >= dynstr->size) {
offset = 0;
size = 0;
} else if (size > dynstr->size - offset) {
size = dynstr->size - offset;
}
return newstr(dynstr->mem + offset, size, true);
}
Dynstr *dynstr_cat(Dynstr *dynstr1, Dynstr *dynstr2)
{
return dynstr_cat_many(dynstr1, dynstr2, (Dynstr *) NULL);
}
Dynstr *dynstr_cat_many(Dynstr *dynstr, ...)
{
va_list args;
Dynstr *result;
size_t size;
size_t offset;
va_start(args, dynstr);
size = 0;
for (Dynstr *p = dynstr; p != NULL; p = va_arg(args, Dynstr *))
size += p->size;
va_end(args);
result = new_dynstr(NULL, size, true);
if (result == NULL)
return NULL;
va_start(args, dynstr);
offset = 0;
for (Dynstr *p = dynstr; p != NULL; p = va_arg(args, Dynstr *)) {
memcpy(result->mem + offset, p->mem, p->size);
offset += p->size;
}
va_end(args);
return result;
}
int dynstr_byte_at(Dynstr *dynstr, size_t offset)
{
if (offset >= dynstr->size)
return -1;
return dynstr->mem[offset];
}
size_t dynstr_first_byte(Dynstr *dynstr, size_t offset, int byte)
{
unsigned char *p;
if (offset >= dynstr->size)
return DYNSTR_NOT_FOUND;
p = memchr(dynstr->mem + offset, byte, dynstr->size - offset);
if (p == NULL)
return DYNSTR_NOT_FOUND;
return (size_t) (p - dynstr->mem);
}
size_t dynstr_last_byte(Dynstr *dynstr, size_t offset, int byte)
{
unsigned char *p;
if (offset >= dynstr->size)
return DYNSTR_NOT_FOUND;
p = memrchr(dynstr->mem + offset, byte, dynstr->size - offset);
if (p == NULL)
return DYNSTR_NOT_FOUND;
return (size_t) (p - dynstr->mem);
}
size_t dynstr_first_string(Dynstr *dynstr, size_t offset, Dynstr *pattern)
{
const unsigned char *p;
if (offset >= dynstr->size)
return DYNSTR_NOT_FOUND;
if (pattern->size == 0)
return DYNSTR_NOT_FOUND;
p = memmem(dynstr->mem + offset, dynstr->size - offset,
pattern->mem, pattern->size);
if (p == NULL)
return DYNSTR_NOT_FOUND;
return (size_t) (p - dynstr->mem);
}
size_t dynstr_last_string(Dynstr *dynstr, size_t offset, Dynstr *pattern)
{
size_t result;
size_t new_result;
/* GNU libc lacks a memrmem function, so we loop to the last
* dynstr_first_string match. */
result = DYNSTR_NOT_FOUND;
for (;;) {
new_result = dynstr_first_string(dynstr, offset, pattern);
if (new_result == DYNSTR_NOT_FOUND)
break;
result = new_result;
offset = result + 1;
}
return result;
}
int dynstr_cmp(Dynstr *dynstr1, Dynstr *dynstr2)
{
int result;
size_t min_size;
if (dynstr1->size < dynstr2->size)
min_size = dynstr1->size;
else
min_size = dynstr2->size;
result = memcmp(dynstr1->mem, dynstr2->mem, min_size);
if (result == 0) {
if (dynstr1->size < dynstr2->size)
return -1;
else if (dynstr1->size == dynstr2->size)
return 0;
else
return 1;
}
return result;
}
size_t dynstr_fwrite(FILE *file, Dynstr *dynstr)
{
return fwrite(dynstr->mem, 1, dynstr->size, file);
}
ssize_t dynstr_write(int fd, Dynstr *dynstr)
{
return write(fd, dynstr->mem, dynstr->size);
}
/* A helper function for dynstr_read and dynstr_fread. The callback does
* the actual reading. */
static Dynstr *read_helper(size_t (*callback)(FILE *f, int fd, unsigned char *buf,
size_t size),
FILE *f, int fd, size_t size)
{
Dynstr *dynstr;
Dynstr *dynstr2;
size_t n;
dynstr = new_dynstr(NULL, size, true);
if (dynstr == NULL)
return NULL;
n = callback(f, fd, dynstr->mem, size);
if (n == DYNSTR_ERROR) {
int saved = errno;
dynstr_free(dynstr);
errno = saved;
return NULL;
}
if (n < size) {
dynstr2 = dynstr_substr(dynstr, 0, n);
if (dynstr2 == NULL) {
dynstr->size = n;
} else {
dynstr_free(dynstr);
dynstr = dynstr2;
}
}
return dynstr;
}
static size_t fread_callback(FILE *f, int fd, unsigned char *buf, size_t size)
{
size_t n;
n = fread(buf, 1, size, f);
if (n == 0 && ferror(f))
return DYNSTR_ERROR;
return n;
}
static size_t read_callback(FILE *f, int fd, unsigned char *buf, size_t size)
{
ssize_t n;
do {
n = read(fd, buf, size);
} while (n == -1 && errno == EINTR);
if (n == -1)
return DYNSTR_ERROR;
return n;
}
Dynstr *dynstr_fread(FILE *file, size_t size)
{
return read_helper(fread_callback, file, -1, size);
}
Dynstr *dynstr_read(int file, size_t size)
{
return read_helper(read_callback, NULL, file, size);
}
/* Helper function for dynstr_readline and dynstr_freadline. The callback
* does the actual reading. Reading happens one byte at a time, since we
* must not read past the newline that terminates a line. */
static Dynstr *readline_helper(int (*callback)(FILE *, int), FILE *f, int fd)
{
Dynstr *line;
Dynstr *temp1;
Dynstr *temp2;
int c;
unsigned char buf[1024];
size_t buflen;
int saved;
line = dynstr_new_empty();
buflen = 0;
for (;;) {
temp1 = NULL;
temp2 = NULL;
c = callback(f, fd);
if (c == -1)
goto error;
else if (c == -2)
break;
buf[buflen++] = c;
if (buflen == sizeof(buf)) {
temp1 = dynstr_new_from_constant_memory(buf, buflen);
if (temp1 == NULL)
goto error;
temp2 = dynstr_cat(line, temp1);
if (temp2 == NULL)
goto error;
dynstr_free(temp1);
dynstr_free(line);
line = temp2;
buflen = 0;
}
if (c == '\n')
break;
}
if (buflen > 0) {
temp1 = dynstr_new_from_constant_memory(buf, buflen);
if (temp1 == NULL)
goto error;
temp2 = dynstr_cat(line, temp1);
if (temp2 == NULL)
goto error;
dynstr_free(temp1);
dynstr_free(line);
line = temp2;
}
return line;
error:
saved = errno;
dynstr_free(line);
dynstr_free(temp1);
dynstr_free(temp2);
errno = saved;
return NULL;
}
static int freadline_callback(FILE *f, int fd)
{
int c;
c = getc(f);
if (c == EOF && ferror(f))
return -1;
else if (c == EOF)
return -2;
else
return c;
}
Dynstr *dynstr_freadline(FILE *file)
{
return readline_helper(freadline_callback, file, -1);
}
static int readline_callback(FILE *f, int fd)
{
unsigned char c;
int n;
do {
n = read(fd, &c, 1);
} while (n == -1 && errno == EINTR);
if (n == 0)
return -2;
else if (n == -1)
return -1;
else
return c;
}
Dynstr *dynstr_readline(int file)
{
return readline_helper(readline_callback, NULL, file);
}