1 /* 2 * Vector handling (counted lists of char *'s). 3 * 4 * A vector is a table for handling a list of strings with less overhead than 5 * linked list. The intention is for vectors, once allocated, to be reused; 6 * this saves on memory allocations once the array of char *'s reaches a 7 * stable size. 8 * 9 * This is based on the util/vector.c library, but that library uses xmalloc 10 * routines to exit the program if memory allocation fails. This is a 11 * modified version of the vector library that instead returns false on 12 * failure to allocate memory, allowing the caller to do appropriate recovery. 13 * 14 * Vectors require list of strings, not arbitrary binary data, and cannot 15 * handle data elements containing nul characters. 16 * 17 * Only the portions of the vector library used by PAM modules are 18 * implemented. 19 * 20 * The canonical version of this file is maintained in the rra-c-util package, 21 * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>. 22 * 23 * Written by Russ Allbery <eagle@eyrie.org> 24 * Copyright 2017-2018 Russ Allbery <eagle@eyrie.org> 25 * Copyright 2010-2011, 2014 26 * The Board of Trustees of the Leland Stanford Junior University 27 * 28 * Copying and distribution of this file, with or without modification, are 29 * permitted in any medium without royalty provided the copyright notice and 30 * this notice are preserved. This file is offered as-is, without any 31 * warranty. 32 * 33 * SPDX-License-Identifier: FSFAP 34 */ 35 36 #include <config.h> 37 #include <portable/system.h> 38 39 #include <pam-util/vector.h> 40 41 42 /* 43 * Allocate a new, empty vector. Returns NULL if memory allocation fails. 44 */ 45 struct vector * 46 vector_new(void) 47 { 48 struct vector *vector; 49 50 vector = calloc(1, sizeof(struct vector)); 51 vector->allocated = 1; 52 vector->strings = calloc(1, sizeof(char *)); 53 return vector; 54 } 55 56 57 /* 58 * Allocate a new vector that's a copy of an existing vector. Returns NULL if 59 * memory allocation fails. 60 */ 61 struct vector * 62 vector_copy(const struct vector *old) 63 { 64 struct vector *vector; 65 size_t i; 66 67 vector = vector_new(); 68 if (!vector_resize(vector, old->count)) { 69 vector_free(vector); 70 return NULL; 71 } 72 vector->count = old->count; 73 for (i = 0; i < old->count; i++) { 74 vector->strings[i] = strdup(old->strings[i]); 75 if (vector->strings[i] == NULL) { 76 vector_free(vector); 77 return NULL; 78 } 79 } 80 return vector; 81 } 82 83 84 /* 85 * Resize a vector (using reallocarray to resize the table). Return false if 86 * memory allocation fails. 87 */ 88 bool 89 vector_resize(struct vector *vector, size_t size) 90 { 91 size_t i; 92 char **strings; 93 94 if (vector->count > size) { 95 for (i = size; i < vector->count; i++) 96 free(vector->strings[i]); 97 vector->count = size; 98 } 99 if (size == 0) 100 size = 1; 101 strings = reallocarray(vector->strings, size, sizeof(char *)); 102 if (strings == NULL) 103 return false; 104 vector->strings = strings; 105 vector->allocated = size; 106 return true; 107 } 108 109 110 /* 111 * Add a new string to the vector, resizing the vector as necessary. The 112 * vector is resized an element at a time; if a lot of resizes are expected, 113 * vector_resize should be called explicitly with a more suitable size. 114 * Return false if memory allocation fails. 115 */ 116 bool 117 vector_add(struct vector *vector, const char *string) 118 { 119 size_t next = vector->count; 120 121 if (vector->count == vector->allocated) 122 if (!vector_resize(vector, vector->allocated + 1)) 123 return false; 124 vector->strings[next] = strdup(string); 125 if (vector->strings[next] == NULL) 126 return false; 127 vector->count++; 128 return true; 129 } 130 131 132 /* 133 * Empty a vector but keep the allocated memory for the pointer table. 134 */ 135 void 136 vector_clear(struct vector *vector) 137 { 138 size_t i; 139 140 for (i = 0; i < vector->count; i++) 141 if (vector->strings[i] != NULL) 142 free(vector->strings[i]); 143 vector->count = 0; 144 } 145 146 147 /* 148 * Free a vector completely. 149 */ 150 void 151 vector_free(struct vector *vector) 152 { 153 if (vector == NULL) 154 return; 155 vector_clear(vector); 156 free(vector->strings); 157 free(vector); 158 } 159 160 161 /* 162 * Given a vector that we may be reusing, clear it out. If the first argument 163 * is NULL, allocate a new vector. Used by vector_split*. Returns NULL if 164 * memory allocation fails. 165 */ 166 static struct vector * 167 vector_reuse(struct vector *vector) 168 { 169 if (vector == NULL) 170 return vector_new(); 171 else { 172 vector_clear(vector); 173 return vector; 174 } 175 } 176 177 178 /* 179 * Given a string and a set of separators expressed as a string, count the 180 * number of strings that it will split into when splitting on those 181 * separators. 182 */ 183 static size_t 184 split_multi_count(const char *string, const char *seps) 185 { 186 const char *p; 187 size_t count; 188 189 if (*string == '\0') 190 return 0; 191 for (count = 1, p = string + 1; *p != '\0'; p++) 192 if (strchr(seps, *p) != NULL && strchr(seps, p[-1]) == NULL) 193 count++; 194 195 /* 196 * If the string ends in separators, we've overestimated the number of 197 * strings by one. 198 */ 199 if (strchr(seps, p[-1]) != NULL) 200 count--; 201 return count; 202 } 203 204 205 /* 206 * Given a string, split it at any of the provided separators to form a 207 * vector, copying each string segment. If the third argument isn't NULL, 208 * reuse that vector; otherwise, allocate a new one. Any number of 209 * consecutive separators are considered a single separator. Returns NULL on 210 * memory allocation failure, after which the provided vector may only have 211 * partial results. 212 */ 213 struct vector * 214 vector_split_multi(const char *string, const char *seps, struct vector *vector) 215 { 216 const char *p, *start; 217 size_t i, count; 218 bool created = false; 219 220 if (vector == NULL) 221 created = true; 222 vector = vector_reuse(vector); 223 if (vector == NULL) 224 return NULL; 225 226 count = split_multi_count(string, seps); 227 if (vector->allocated < count && !vector_resize(vector, count)) 228 goto fail; 229 230 vector->count = 0; 231 for (start = string, p = string, i = 0; *p != '\0'; p++) 232 if (strchr(seps, *p) != NULL) { 233 if (start != p) { 234 vector->strings[i] = strndup(start, (size_t)(p - start)); 235 if (vector->strings[i] == NULL) 236 goto fail; 237 i++; 238 vector->count++; 239 } 240 start = p + 1; 241 } 242 if (start != p) { 243 vector->strings[i] = strndup(start, (size_t)(p - start)); 244 if (vector->strings[i] == NULL) 245 goto fail; 246 vector->count++; 247 } 248 return vector; 249 250 fail: 251 if (created) 252 vector_free(vector); 253 return NULL; 254 } 255 256 257 /* 258 * Given a vector and a path to a program, exec that program with the vector 259 * as its arguments. This requires adding a NULL terminator to the vector and 260 * casting it appropriately. Returns 0 on success and -1 on error, like exec 261 * does. 262 */ 263 int 264 vector_exec(const char *path, struct vector *vector) 265 { 266 if (vector->allocated == vector->count) 267 if (!vector_resize(vector, vector->count + 1)) 268 return -1; 269 vector->strings[vector->count] = NULL; 270 return execv(path, (char *const *) vector->strings); 271 } 272 273 274 /* 275 * Given a vector, a path to a program, and the environment, exec that program 276 * with the vector as its arguments and the given environment. This requires 277 * adding a NULL terminator to the vector and casting it appropriately. 278 * Returns 0 on success and -1 on error, like exec does. 279 */ 280 int 281 vector_exec_env(const char *path, struct vector *vector, 282 const char *const env[]) 283 { 284 if (vector->allocated == vector->count) 285 if (!vector_resize(vector, vector->count + 1)) 286 return -1; 287 vector->strings[vector->count] = NULL; 288 return execve(path, (char *const *) vector->strings, (char *const *) env); 289 } 290