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