/* * Vector handling (counted lists of char *'s). * * A vector is a table for handling a list of strings with less overhead than * linked list. The intention is for vectors, once allocated, to be reused; * this saves on memory allocations once the array of char *'s reaches a * stable size. * * There are two types of vectors. Standard vectors copy strings when they're * inserted into the vector, whereas cvectors just accept pointers to external * strings to store. There are therefore two entry points for every vector * function, one for vectors and one for cvectors. * * Vectors require list of strings, not arbitrary binary data, and cannot * handle data elements containing nul characters. * * There's a whole bunch of code duplication here. This would be a lot * cleaner with C++ features (either inheritance or templates would probably * help). One could probably in some places just cast a cvector to a vector * and perform the same operations, but I'm leery of doing that as I'm not * sure if it's a violation of the C type aliasing rules. * * The canonical version of this file is maintained in the rra-c-util package, * which can be found at . * * Written by Russ Allbery * Copyright 2001-2006, 2016, 2020 Russ Allbery * Copyright 2005-2006, 2008-2011, 2013-2014 * The Board of Trustees of the Leland Stanford Junior University * * Copying and distribution of this file, with or without modification, are * permitted in any medium without royalty provided the copyright notice and * this notice are preserved. This file is offered as-is, without any * warranty. * * SPDX-License-Identifier: FSFAP */ #include "config.h" #include "portable/system.h" #include #include "inn/vector.h" #include "inn/xmalloc.h" /* * Allocate a new, empty vector. */ struct vector * vector_new(void) { struct vector *vector; vector = xcalloc(1, sizeof(struct vector)); vector->allocated = 1; vector->strings = xcalloc(1, sizeof(char *)); return vector; } struct cvector * cvector_new(void) { struct cvector *vector; vector = xcalloc(1, sizeof(struct cvector)); vector->allocated = 1; vector->strings = xcalloc(1, sizeof(const char *)); return vector; } /* * Resize a vector (using reallocarray to resize the table). Maintain a * minimum allocated size of 1 so that the strings data element is never NULL. * This simplifies other code. */ void vector_resize(struct vector *vector, size_t size) { size_t i; assert(vector != NULL); if (vector->count > size) { for (i = size; i < vector->count; i++) free(vector->strings[i]); vector->count = size; } if (size == 0) size = 1; vector->strings = xreallocarray(vector->strings, size, sizeof(char *)); vector->allocated = size; } void cvector_resize(struct cvector *vector, size_t size) { assert(vector != NULL); if (vector->count > size) vector->count = size; if (size == 0) size = 1; vector->strings = xreallocarray(vector->strings, size, sizeof(const char *)); vector->allocated = size; } /* * Add a new string to the vector, resizing the vector as necessary. The * vector is resized an element at a time; if a lot of resizes are expected, * vector_resize should be called explicitly with a more suitable size. */ void vector_add(struct vector *vector, const char *string) { assert(vector != NULL); if (vector->count == vector->allocated) vector_resize(vector, vector->allocated + 1); vector->strings[vector->count] = xstrdup(string); vector->count++; } void cvector_add(struct cvector *vector, const char *string) { assert(vector != NULL); if (vector->count == vector->allocated) cvector_resize(vector, vector->allocated + 1); vector->strings[vector->count] = string; vector->count++; } /* * Add a new string to the vector, copying at most length characters of the * string, resizing the vector as necessary the same as with vector_add. This * function is only available for vectors, not cvectors, since it requires the * duplication of the input string to be sure it's nul-terminated. */ void vector_addn(struct vector *vector, const char *string, size_t length) { assert(vector != NULL); if (vector->count == vector->allocated) vector_resize(vector, vector->allocated + 1); vector->strings[vector->count] = xstrndup(string, length); vector->count++; } /* * Empty a vector but keep the allocated memory for the pointer table. */ void vector_clear(struct vector *vector) { size_t i; assert(vector != NULL); for (i = 0; i < vector->count; i++) free(vector->strings[i]); vector->count = 0; } void cvector_clear(struct cvector *vector) { assert(vector != NULL); vector->count = 0; } /* * Free a vector completely. */ void vector_free(struct vector *vector) { if (vector == NULL) return; vector_clear(vector); free(vector->strings); free(vector); } void cvector_free(struct cvector *vector) { if (vector == NULL) return; cvector_clear(vector); free(vector->strings); free(vector); } /* * Given a vector that we may be reusing, clear it out. If the argument is * NULL, allocate a new vector. Helper function for vector_split*. */ static struct vector * vector_reuse(struct vector *vector) { if (vector == NULL) return vector_new(); else { vector_clear(vector); return vector; } } static struct cvector * cvector_reuse(struct cvector *vector) { if (vector == NULL) return cvector_new(); else { cvector_clear(vector); return vector; } } /* * Given a string and a separator character, count the number of strings that * it will split into. */ static size_t split_count(const char *string, char separator) { const char *p; size_t count; if (*string == '\0') return 1; for (count = 1, p = string; *p != '\0'; p++) if (*p == separator) count++; return count; } /* * Given a string and a separator character, form a vector by splitting the * string at occurrences of that separator. Consecutive occurrences of the * character will result in empty strings added to the vector. Reuse the * provided vector if non-NULL. */ struct vector * vector_split(const char *string, char separator, struct vector *vector) { const char *p, *start; size_t i, count; /* If the vector argument isn't NULL, reuse it. */ vector = vector_reuse(vector); /* Do a first pass to size the vector. */ count = split_count(string, separator); if (vector->allocated < count) vector_resize(vector, count); /* Walk the string and create the new strings with xstrndup. */ for (start = string, p = string, i = 0; *p != '\0'; p++) if (*p == separator) { vector->strings[i++] = xstrndup(start, p - start); start = p + 1; } vector->strings[i++] = xstrndup(start, p - start); vector->count = i; return vector; } /* * Given a modifiable string and a separator character, form a cvector by * modifying the string in-place to add nuls at the separators and then * building a vector of pointers into the string. Reuse the provided vector * if non-NULL. */ struct cvector * cvector_split(char *string, char separator, struct cvector *vector) { char *p, *start; size_t i, count; /* If the vector argument isn't NULL, reuse it. */ vector = cvector_reuse(vector); /* Do a first pass to size the vector. */ count = split_count(string, separator); if (vector->allocated < count) cvector_resize(vector, count); /* * Walk the string and replace separators with nul, and store the pointers * to the start of each string segment. */ for (start = string, p = string, i = 0; *p; p++) if (*p == separator) { *p = '\0'; vector->strings[i++] = start; start = p + 1; } vector->strings[i++] = start; vector->count = i; return vector; } /* * Given a string and a set of separators expressed as a string, count the * number of strings that it will split into when splitting on those * separators. Unlike with split_count, multiple consecutive separator * characters will be treated the same as a single separator. */ static size_t split_multi_count(const char *string, const char *seps) { const char *p; size_t count; /* The empty string produces no substrings. */ if (*string == '\0') return 0; /* * Walk the string looking for the first separator not preceded by * another separator (and ignore a separator at the start of the string). */ for (count = 1, p = string + 1; *p != '\0'; p++) if (strchr(seps, *p) != NULL && strchr(seps, p[-1]) == NULL) count++; /* * If the string ends in separators, we've overestimated the number of * strings by one. */ if (strchr(seps, p[-1]) != NULL) count--; return count; } /* * Given a string, split it at any of the provided separators to form a * vector, copying each string segment. Any number of consecutive separators * are considered a single separator. Reuse the provided vector if non-NULL. */ struct vector * vector_split_multi(const char *string, const char *seps, struct vector *vector) { const char *p, *start; size_t i, count; /* If the vector argument isn't NULL, reuse it. */ vector = vector_reuse(vector); /* Count the number of strings we'll create and size the vector. */ count = split_multi_count(string, seps); if (vector->allocated < count) vector_resize(vector, count); /* * Walk the string and look for separators. start tracks the * non-separator that starts a new string, so as long as start == p, we're * tracking a sequence of separators. */ for (start = string, p = string, i = 0; *p != '\0'; p++) if (strchr(seps, *p) != NULL) { if (start != p) vector->strings[i++] = xstrndup(start, p - start); start = p + 1; } if (start != p) vector->strings[i++] = xstrndup(start, p - start); vector->count = i; return vector; } /* * Given a string, split it at any of the provided separators to form a * vector, destructively modifying the string to nul-terminate each segment. * Any number of consecutive separators are considered a single separator. * Reuse the provided vector if non-NULL. */ struct cvector * cvector_split_multi(char *string, const char *seps, struct cvector *vector) { char *p, *start; size_t i, count; /* If the vector argument isn't NULL, reuse it. */ vector = cvector_reuse(vector); /* Count the number of strings we'll create and size the vector. */ count = split_multi_count(string, seps); if (vector->allocated < count) cvector_resize(vector, count); /* * Walk the string and look for separators, replacing the ones that * terminate a substring with a nul. start tracks the non-separator that * starts a new string, so as long as start == p, we're tracking a * sequence of separators. */ for (start = string, p = string, i = 0; *p != '\0'; p++) if (strchr(seps, *p) != NULL) { if (start != p) { *p = '\0'; vector->strings[i++] = start; } start = p + 1; } if (start != p) vector->strings[i++] = start; vector->count = i; return vector; } /* * Given a string, split it at whitespace to form a vector, copying each * string segment. Any number of consecutive whitespace characters are * considered a single separator. Reuse the provided vector if non-NULL. * This is just a special case of vector_split_multi. */ struct vector * vector_split_space(const char *string, struct vector *vector) { return vector_split_multi(string, " \t", vector); } /* * Given a string, split it at whitespace to form a vector, destructively * modifying the string to nul-terminate each segment. Any number of * consecutive whitespace characters are considered a single separator. Reuse * the provided vector if non-NULL. This is just a special case of * cvector_split_multi. */ struct cvector * cvector_split_space(char *string, struct cvector *vector) { return cvector_split_multi(string, " \t", vector); } /* * Given a vector and a separator string, allocate and build a new string * composed of all the strings in the vector separated from each other by the * separator string. Caller is responsible for freeing. */ char * vector_join(const struct vector *vector, const char *separator) { char *string; size_t i, length, offset, size, seplen; /* If the vector is empty, this is trivial. */ assert(vector != NULL); if (vector->count == 0) return xstrdup(""); /* * Determine the total size of the resulting string. Be careful of * integer overflow while doing so. */ seplen = strlen(separator); for (size = 0, i = 0; i < vector->count; i++) { assert(SIZE_MAX - size >= strlen(vector->strings[i]) + seplen + 1); size += strlen(vector->strings[i]); } assert(SIZE_MAX - size >= (vector->count - 1) * seplen + 1); size += (vector->count - 1) * seplen + 1; /* Allocate the memory and build up the string. */ string = xmalloc(size); offset = 0; for (i = 0; i < vector->count; i++) { if (i != 0) { memcpy(string + offset, separator, seplen); offset += seplen; } length = strlen(vector->strings[i]); memcpy(string + offset, vector->strings[i], length); offset += length; assert(offset < size); } string[offset] = '\0'; return string; } char * cvector_join(const struct cvector *vector, const char *separator) { char *string; size_t i, length, offset, size, seplen; /* If the vector is empty, this is trivial. */ assert(vector != NULL); if (vector->count == 0) return xstrdup(""); /* * Determine the total size of the resulting string. Be careful of * integer overflow while doing so. */ seplen = strlen(separator); for (size = 0, i = 0; i < vector->count; i++) { assert(SIZE_MAX - size >= strlen(vector->strings[i])); size += strlen(vector->strings[i]); } assert(SIZE_MAX - size >= (vector->count - 1) * seplen + 1); size += (vector->count - 1) * seplen + 1; /* Allocate the memory and build up the string. */ string = xmalloc(size); offset = 0; for (i = 0; i < vector->count; i++) { if (i != 0) { memcpy(string + offset, separator, seplen); offset += seplen; } length = strlen(vector->strings[i]); memcpy(string + offset, vector->strings[i], length); offset += length; assert(offset < size); } string[offset] = '\0'; return string; } /* * Given a vector and a path to a program, exec that program with the vector * as its arguments. This requires adding a NULL terminator to the vector * (which we do not add to count, so it will be invisible to other users of * the vector) and casting it appropriately. */ int vector_exec(const char *path, struct vector *vector) { assert(vector != NULL); if (vector->allocated == vector->count) vector_resize(vector, vector->count + 1); vector->strings[vector->count] = NULL; return execv(path, (char *const *) vector->strings); } int cvector_exec(const char *path, struct cvector *vector) { assert(vector != NULL); if (vector->allocated == vector->count) cvector_resize(vector, vector->count + 1); vector->strings[vector->count] = NULL; return execv(path, (char *const *) vector->strings); }