1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * String utility functions with dynamic memory management. 14 */ 15 16 /* 17 * Copyright 2020 Joyent, Inc. 18 */ 19 20 #include <stdlib.h> 21 #include <err.h> 22 #include <errno.h> 23 #include <string.h> 24 #include <stdio.h> 25 #include <stdarg.h> 26 #include <sys/debug.h> 27 28 #include "libcustr.h" 29 30 /* 31 * libcustr is used by some things in usr/src/tools. If we are building 32 * on an older platform, __unused might not be defined on the build host. 33 * We define it here if needed. 34 */ 35 #ifndef __unused 36 #if __GNUC_VERSION >= 20700 37 #define __unused __attribute__((_unused__)) 38 #else 39 #define __unused 40 #endif /* __GNUC_VERSION */ 41 #endif /* __unused */ 42 43 typedef enum { 44 CUSTR_FIXEDBUF = 0x01 45 } custr_flags_t; 46 47 struct custr { 48 size_t cus_strlen; 49 size_t cus_datalen; 50 char *cus_data; 51 custr_flags_t cus_flags; 52 custr_alloc_t *cus_alloc; 53 }; 54 #define CUSTR_ALLOC(_cus, _len) \ 55 (_cus)->cus_alloc->cua_ops->custr_ao_alloc((_cus)->cus_alloc, (_len)) 56 #define CUSTR_FREE(_cus, _p, _len) \ 57 (_cus)->cus_alloc->cua_ops->custr_ao_free((_cus)->cus_alloc, \ 58 (_p), (_len)) 59 60 #define STRING_CHUNK_SIZE 64 61 62 static void *custr_def_alloc(custr_alloc_t *, size_t); 63 static void custr_def_free(custr_alloc_t *, void *, size_t); 64 65 static custr_alloc_ops_t custr_alloc_ops_default = { 66 NULL, /* custr_ao_init */ 67 NULL, /* custr_ao_fini */ 68 custr_def_alloc, /* custr_ao_alloc */ 69 custr_def_free /* custr_ao_free */ 70 }; 71 72 static custr_alloc_t custr_alloc_default = { 73 CUSTR_VERSION, /* cua_version */ 74 &custr_alloc_ops_default, /* cua_ops */ 75 NULL /* cua_arg */ 76 }; 77 78 void 79 custr_reset(custr_t *cus) 80 { 81 if (cus->cus_data == NULL) 82 return; 83 84 cus->cus_strlen = 0; 85 cus->cus_data[0] = '\0'; 86 } 87 88 int 89 custr_remove(custr_t *cus, size_t idx, size_t len) 90 { 91 size_t endidx = idx + len; 92 93 /* 94 * Once gcc4 is dropped as a shadow compiler, we can migrate to 95 * using builtins for the overflow check. 96 */ 97 if (endidx < idx || endidx < len) { 98 errno = EINVAL; 99 return (-1); 100 } 101 102 if (idx >= cus->cus_strlen || endidx > cus->cus_strlen) { 103 errno = EINVAL; 104 return (-1); 105 } 106 107 if (len == 0) 108 return (0); 109 110 /* The +1 will include the terminating NUL in the move */ 111 (void) memmove(cus->cus_data + idx, cus->cus_data + endidx, 112 cus->cus_strlen - endidx + 1); 113 cus->cus_strlen -= len; 114 115 /* The result should be NUL */ 116 VERIFY0(cus->cus_data[cus->cus_strlen]); 117 return (0); 118 } 119 120 int 121 custr_rremove(custr_t *cus, size_t ridx, size_t len) 122 { 123 size_t idx; 124 125 if (ridx >= cus->cus_strlen) { 126 errno = EINVAL; 127 return (-1); 128 } 129 130 idx = cus->cus_strlen - ridx - 1; 131 return (custr_remove(cus, idx, len)); 132 } 133 134 int 135 custr_trunc(custr_t *cus, size_t idx) 136 { 137 if (idx >= cus->cus_strlen) { 138 errno = EINVAL; 139 return (-1); 140 } 141 142 cus->cus_data[idx] = '\0'; 143 cus->cus_strlen = idx; 144 return (0); 145 } 146 147 int 148 custr_rtrunc(custr_t *cus, size_t ridx) 149 { 150 size_t idx; 151 152 if (ridx >= cus->cus_strlen) { 153 errno = EINVAL; 154 return (-1); 155 } 156 157 idx = cus->cus_strlen - ridx - 1; 158 cus->cus_data[idx] = '\0'; 159 cus->cus_strlen = idx; 160 return (0); 161 } 162 163 size_t 164 custr_len(custr_t *cus) 165 { 166 return (cus->cus_strlen); 167 } 168 169 const char * 170 custr_cstr(custr_t *cus) 171 { 172 if (cus->cus_data == NULL) { 173 VERIFY(cus->cus_strlen == 0); 174 VERIFY(cus->cus_datalen == 0); 175 176 /* 177 * This function should never return NULL. If no buffer has 178 * been allocated, return a pointer to a zero-length string. 179 */ 180 return (""); 181 } 182 return (cus->cus_data); 183 } 184 185 int 186 custr_append_vprintf(custr_t *cus, const char *fmt, va_list ap) 187 { 188 int len = vsnprintf(NULL, 0, fmt, ap); 189 size_t chunksz = STRING_CHUNK_SIZE; 190 191 if (len < 0) { 192 return (len); 193 } 194 195 while (chunksz < len) { 196 chunksz *= 2; 197 } 198 199 if (len + cus->cus_strlen + 1 >= cus->cus_datalen) { 200 char *new_data; 201 size_t new_datalen = cus->cus_datalen + chunksz; 202 203 if (cus->cus_flags & CUSTR_FIXEDBUF) { 204 errno = EOVERFLOW; 205 return (-1); 206 } 207 208 /* 209 * Allocate replacement memory: 210 */ 211 if ((new_data = CUSTR_ALLOC(cus, new_datalen)) == NULL) { 212 return (-1); 213 } 214 215 /* 216 * Copy existing data into replacement memory and free 217 * the old memory. 218 */ 219 if (cus->cus_data != NULL) { 220 (void) memcpy(new_data, cus->cus_data, 221 cus->cus_strlen + 1); 222 CUSTR_FREE(cus, cus->cus_data, cus->cus_datalen); 223 } 224 225 /* 226 * Swap in the replacement buffer: 227 */ 228 cus->cus_data = new_data; 229 cus->cus_datalen = new_datalen; 230 } 231 /* 232 * Append new string to existing string: 233 */ 234 if ((len = vsnprintf(cus->cus_data + cus->cus_strlen, 235 cus->cus_datalen - cus->cus_strlen, fmt, ap)) < 0) { 236 return (len); 237 } 238 cus->cus_strlen += len; 239 240 return (0); 241 } 242 243 int 244 custr_appendc(custr_t *cus, char newc) 245 { 246 return (custr_append_printf(cus, "%c", newc)); 247 } 248 249 int 250 custr_append_printf(custr_t *cus, const char *fmt, ...) 251 { 252 va_list ap; 253 int ret; 254 255 va_start(ap, fmt); 256 ret = custr_append_vprintf(cus, fmt, ap); 257 va_end(ap); 258 259 return (ret); 260 } 261 262 int 263 custr_append(custr_t *cus, const char *name) 264 { 265 return (custr_append_printf(cus, "%s", name)); 266 } 267 268 int 269 custr_alloc_init(custr_alloc_t *cua, const custr_alloc_ops_t *ops, ...) 270 { 271 int ret = 0; 272 273 if (cua->cua_version != CUSTR_VERSION || ops->custr_ao_alloc == NULL || 274 ops->custr_ao_free == NULL) { 275 errno = EINVAL; 276 return (-1); 277 } 278 279 cua->cua_ops = ops; 280 cua->cua_arg = NULL; 281 282 if (ops->custr_ao_init != NULL) { 283 va_list ap; 284 285 va_start(ap, ops); 286 ret = ops->custr_ao_init(cua, ap); 287 va_end(ap); 288 } 289 290 return ((ret == 0) ? 0 : -1); 291 } 292 293 void 294 custr_alloc_fini(custr_alloc_t *cua) 295 { 296 if (cua->cua_ops->custr_ao_fini != NULL) 297 cua->cua_ops->custr_ao_fini(cua); 298 } 299 300 int 301 custr_xalloc(custr_t **cus, custr_alloc_t *cao) 302 { 303 custr_t *t; 304 305 if (cao == NULL) 306 cao = &custr_alloc_default; 307 308 if ((t = cao->cua_ops->custr_ao_alloc(cao, sizeof (*t))) == NULL) { 309 *cus = NULL; 310 return (-1); 311 } 312 (void) memset(t, 0, sizeof (*t)); 313 314 t->cus_alloc = cao; 315 *cus = t; 316 return (0); 317 } 318 319 int 320 custr_alloc(custr_t **cus) 321 { 322 return (custr_xalloc(cus, NULL)); 323 } 324 325 int 326 custr_xalloc_buf(custr_t **cus, void *buf, size_t buflen, custr_alloc_t *cao) 327 { 328 int ret; 329 330 if (buflen == 0 || buf == NULL) { 331 errno = EINVAL; 332 return (-1); 333 } 334 335 if ((ret = custr_xalloc(cus, cao)) != 0) 336 return (ret); 337 338 (*cus)->cus_data = buf; 339 (*cus)->cus_datalen = buflen; 340 (*cus)->cus_strlen = 0; 341 (*cus)->cus_flags = CUSTR_FIXEDBUF; 342 (*cus)->cus_data[0] = '\0'; 343 344 return (0); 345 } 346 347 int 348 custr_alloc_buf(custr_t **cus, void *buf, size_t buflen) 349 { 350 return (custr_xalloc_buf(cus, buf, buflen, NULL)); 351 } 352 353 void 354 custr_free(custr_t *cus) 355 { 356 custr_alloc_t *cao; 357 358 if (cus == NULL) 359 return; 360 361 if ((cus->cus_flags & CUSTR_FIXEDBUF) == 0) 362 CUSTR_FREE(cus, cus->cus_data, cus->cus_datalen); 363 364 cao = cus->cus_alloc; 365 cao->cua_ops->custr_ao_free(cao, cus, sizeof (*cus)); 366 } 367 368 /*ARGSUSED*/ 369 static void * 370 custr_def_alloc(custr_alloc_t *cao __unused, size_t len) 371 { 372 return (malloc(len)); 373 } 374 375 /*ARGSUSED*/ 376 static void 377 custr_def_free(custr_alloc_t *cao __unused, void *p, size_t len __unused) 378 { 379 free(p); 380 } 381