/* * 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. * * This is based on the util/vector.c library, but that library uses xmalloc * routines to exit the program if memory allocation fails. This is a * modified version of the vector library that instead returns false on * failure to allocate memory, allowing the caller to do appropriate recovery. * * Vectors require list of strings, not arbitrary binary data, and cannot * handle data elements containing nul characters. * * Only the portions of the vector library used by PAM modules are * implemented. * * The canonical version of this file is maintained in the rra-c-util package, * which can be found at . * * Written by Russ Allbery * Copyright 2017-2018 Russ Allbery * Copyright 2010-2011, 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 #include #include /* * Allocate a new, empty vector. Returns NULL if memory allocation fails. */ struct vector * vector_new(void) { struct vector *vector; vector = calloc(1, sizeof(struct vector)); vector->allocated = 1; vector->strings = calloc(1, sizeof(char *)); return vector; } /* * Allocate a new vector that's a copy of an existing vector. Returns NULL if * memory allocation fails. */ struct vector * vector_copy(const struct vector *old) { struct vector *vector; size_t i; vector = vector_new(); if (!vector_resize(vector, old->count)) { vector_free(vector); return NULL; } vector->count = old->count; for (i = 0; i < old->count; i++) { vector->strings[i] = strdup(old->strings[i]); if (vector->strings[i] == NULL) { vector_free(vector); return NULL; } } return vector; } /* * Resize a vector (using reallocarray to resize the table). Return false if * memory allocation fails. */ bool vector_resize(struct vector *vector, size_t size) { size_t i; char **strings; if (vector->count > size) { for (i = size; i < vector->count; i++) free(vector->strings[i]); vector->count = size; } if (size == 0) size = 1; strings = reallocarray(vector->strings, size, sizeof(char *)); if (strings == NULL) return false; vector->strings = strings; vector->allocated = size; return true; } /* * 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. * Return false if memory allocation fails. */ bool vector_add(struct vector *vector, const char *string) { size_t next = vector->count; if (vector->count == vector->allocated) if (!vector_resize(vector, vector->allocated + 1)) return false; vector->strings[next] = strdup(string); if (vector->strings[next] == NULL) return false; vector->count++; return true; } /* * Empty a vector but keep the allocated memory for the pointer table. */ void vector_clear(struct vector *vector) { size_t i; for (i = 0; i < vector->count; i++) if (vector->strings[i] != NULL) free(vector->strings[i]); 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); } /* * Given a vector that we may be reusing, clear it out. If the first argument * is NULL, allocate a new vector. Used by vector_split*. Returns NULL if * memory allocation fails. */ static struct vector * vector_reuse(struct vector *vector) { if (vector == NULL) return vector_new(); else { vector_clear(vector); 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. */ static size_t split_multi_count(const char *string, const char *seps) { const char *p; size_t count; if (*string == '\0') return 0; 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. If the third argument isn't NULL, * reuse that vector; otherwise, allocate a new one. Any number of * consecutive separators are considered a single separator. Returns NULL on * memory allocation failure, after which the provided vector may only have * partial results. */ struct vector * vector_split_multi(const char *string, const char *seps, struct vector *vector) { const char *p, *start; size_t i, count; bool created = false; if (vector == NULL) created = true; vector = vector_reuse(vector); if (vector == NULL) return NULL; count = split_multi_count(string, seps); if (vector->allocated < count && !vector_resize(vector, count)) goto fail; vector->count = 0; for (start = string, p = string, i = 0; *p != '\0'; p++) if (strchr(seps, *p) != NULL) { if (start != p) { vector->strings[i] = strndup(start, (size_t)(p - start)); if (vector->strings[i] == NULL) goto fail; i++; vector->count++; } start = p + 1; } if (start != p) { vector->strings[i] = strndup(start, (size_t)(p - start)); if (vector->strings[i] == NULL) goto fail; vector->count++; } return vector; fail: if (created) vector_free(vector); return NULL; } /* * 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 and * casting it appropriately. Returns 0 on success and -1 on error, like exec * does. */ int vector_exec(const char *path, struct vector *vector) { if (vector->allocated == vector->count) if (!vector_resize(vector, vector->count + 1)) return -1; vector->strings[vector->count] = NULL; return execv(path, (char *const *) vector->strings); } /* * Given a vector, a path to a program, and the environment, exec that program * with the vector as its arguments and the given environment. This requires * adding a NULL terminator to the vector and casting it appropriately. * Returns 0 on success and -1 on error, like exec does. */ int vector_exec_env(const char *path, struct vector *vector, const char *const env[]) { if (vector->allocated == vector->count) if (!vector_resize(vector, vector->count + 1)) return -1; vector->strings[vector->count] = NULL; return execve(path, (char *const *) vector->strings, (char *const *) env); }