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