1 /* 2 * Copyright (c) 2013-2026 Devin Teske <dteske@FreeBSD.org> 3 * Copyright (c) 2021-2026 Faraz Vahedi <kfv@FreeBSD.org> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8 #include <sys/stat.h> 9 10 #include <dirent.h> 11 #include <errno.h> 12 #include <libgen.h> 13 #include <limits.h> 14 #include <stdio.h> 15 #include <stdlib.h> 16 #include <string.h> 17 18 #include "bsdconf.h" 19 #include "bsdconf_formats.h" 20 21 /* 22 * The format abstraction layer registry. 23 * 24 * Every configuration file format -- built-in or bolted on at run-time -- is 25 * described by the same small descriptor (struct bsdconf_format_def in 26 * bsdconf.h): a target keyword, a default write path, an ordered list of 27 * configuration sources, and the processing/put bitmasks that drive the 28 * shared parsing and writing cores. There is exactly one engine; a format 29 * merely parameterizes it. 30 * 31 * Each built-in format is a self-contained translation unit 32 * (bsdconf_format_<keyword>.c) documenting and defining its own descriptor; 33 * this file only collects the descriptors into the table indexed by enum 34 * bsdconf_format and resolves keywords, basenames, and source lists against 35 * it (see bsdconf_formats.h for the recipe to add a format). 36 * 37 * A format that is "mostly like" an existing one need not be added at all: 38 * an application calls bsdconf_format_derive() to inherit the nearest 39 * descriptor, adjusts only the members that differ, and registers the 40 * result with bsdconf_format_register(). The handle it gets back taps into 41 * the same core engine everywhere a built-in format works, including 42 * keyword, path, and source-list resolution in sysconf(8). 43 */ 44 45 static const struct bsdconf_format_def *bsdconf_formats[] = { 46 [BSDCONF_FORMAT_GENERIC] = &bsdconf_format_generic_def, 47 [BSDCONF_FORMAT_LOADER] = &bsdconf_format_loader_def, 48 [BSDCONF_FORMAT_SYSCTL] = &bsdconf_format_sysctl_def, 49 [BSDCONF_FORMAT_MAKE] = &bsdconf_format_make_def, 50 [BSDCONF_FORMAT_SRC] = &bsdconf_format_src_def, 51 }; 52 #define BSDCONF_NFORMATS \ 53 (sizeof(bsdconf_formats) / sizeof(*bsdconf_formats)) 54 55 #define BSDCONF_CONF_SUFFIX ".conf" 56 #define BSDCONF_CONF_SUFFIX_LEN (sizeof(BSDCONF_CONF_SUFFIX) - 1) 57 58 /* 59 * Registered (bolt-on) formats. Descriptors are copied by value; the 60 * keyword and path strings they reference remain owned by the caller and 61 * must stay valid for the life of the registration. 62 */ 63 #define BSDCONF_MAXUSERFORMATS 32 64 static struct bsdconf_format_def bsdconf_user_formats[BSDCONF_MAXUSERFORMATS]; 65 static unsigned int bsdconf_nuser_formats = 0; 66 67 /* 68 * Return the descriptor for `format' or NULL if the format is neither 69 * built-in nor registered. 70 */ 71 const struct bsdconf_format_def * 72 bsdconf_format_lookup(enum bsdconf_format format) 73 { 74 unsigned int n; 75 76 if ((unsigned int)format < BSDCONF_NFORMATS) 77 return (bsdconf_formats[format]); 78 79 n = (unsigned int)format - BSDCONF_FORMAT_USER; 80 if (format >= BSDCONF_FORMAT_USER && n < bsdconf_nuser_formats) 81 return (&bsdconf_user_formats[n]); 82 83 return (NULL); 84 } 85 86 /* 87 * Copy the descriptor of `base' into `def' so that a caller can adjust only 88 * the members that differ before registering the result as a new format. 89 * On success, returns zero; otherwise returns -1 (unknown base format) and 90 * errno is set to EINVAL. 91 */ 92 int 93 bsdconf_format_derive(enum bsdconf_format base, 94 struct bsdconf_format_def *def) 95 { 96 const struct bsdconf_format_def *found; 97 98 /* Check arguments */ 99 if (def == NULL || (found = bsdconf_format_lookup(base)) == NULL) { 100 errno = EINVAL; 101 return (-1); 102 } 103 104 *def = *found; 105 return (0); 106 } 107 108 /* 109 * Register a new configuration file format described by `def', storing its 110 * newly assigned handle through `format'. The descriptor is copied; the 111 * keyword and path strings it references are not (see above). On success, 112 * returns zero; otherwise returns -1 and errno is set (EINVAL for a bad 113 * argument, ENOSPC when the registration table is full). 114 */ 115 int 116 bsdconf_format_register(const struct bsdconf_format_def *def, 117 enum bsdconf_format *format) 118 { 119 120 /* Check arguments */ 121 if (def == NULL || format == NULL) { 122 errno = EINVAL; 123 return (-1); 124 } 125 if (bsdconf_nuser_formats >= BSDCONF_MAXUSERFORMATS) { 126 errno = ENOSPC; 127 return (-1); 128 } 129 130 bsdconf_user_formats[bsdconf_nuser_formats] = *def; 131 *format = (enum bsdconf_format) 132 (BSDCONF_FORMAT_USER + bsdconf_nuser_formats); 133 bsdconf_nuser_formats++; 134 135 return (0); 136 } 137 138 /* 139 * Map a target keyword (e.g., "loader") to its format, storing the result 140 * through `format'. Registered formats are searched after the built-ins. 141 * On success, returns zero; otherwise returns -1 (unknown keyword; `format' 142 * is left untouched). 143 */ 144 int 145 bsdconf_format_find(const char *keyword, enum bsdconf_format *format) 146 { 147 unsigned int n; 148 149 /* Check arguments */ 150 if (keyword == NULL || format == NULL) 151 return (-1); 152 153 for (n = 0; n < BSDCONF_NFORMATS; n++) { 154 if (bsdconf_formats[n]->keyword != NULL && 155 strcmp(bsdconf_formats[n]->keyword, keyword) == 0) { 156 *format = (enum bsdconf_format)n; 157 return (0); 158 } 159 } 160 for (n = 0; n < bsdconf_nuser_formats; n++) { 161 if (bsdconf_user_formats[n].keyword != NULL && 162 strcmp(bsdconf_user_formats[n].keyword, keyword) == 0) { 163 *format = (enum bsdconf_format) 164 (BSDCONF_FORMAT_USER + n); 165 return (0); 166 } 167 } 168 169 return (-1); 170 } 171 172 /* 173 * Test whether the basename of `path' matches `<keyword>.conf' with an 174 * optional trailing suffix (e.g., "sysctl.conf.local" matches "sysctl"). 175 */ 176 static int 177 bsdconf_basename_matches(const char *base, const char *keyword) 178 { 179 size_t klen; 180 181 if (keyword == NULL) 182 return (0); 183 klen = strlen(keyword); 184 if (strncmp(base, keyword, klen) != 0) 185 return (0); 186 return (strncmp(base + klen, BSDCONF_CONF_SUFFIX, 187 BSDCONF_CONF_SUFFIX_LEN) == 0); 188 } 189 190 /* 191 * Guess the format of an arbitrary configuration file from the basename of 192 * its `path' (e.g., "/etc/sysctl.conf.local" is BSDCONF_FORMAT_SYSCTL). 193 * Registered formats are searched after the built-ins. Returns 194 * BSDCONF_FORMAT_GENERIC when nothing matches. 195 */ 196 enum bsdconf_format 197 bsdconf_format_guess(const char *path) 198 { 199 unsigned int n; 200 char tmp[PATH_MAX]; 201 char *base; 202 203 if (path == NULL) 204 return (BSDCONF_FORMAT_GENERIC); 205 206 if (strlen(path) >= sizeof(tmp)) 207 return (BSDCONF_FORMAT_GENERIC); 208 memcpy(tmp, path, strlen(path) + 1); 209 base = basename(tmp); 210 211 for (n = 0; n < BSDCONF_NFORMATS; n++) 212 if (bsdconf_basename_matches(base, 213 bsdconf_formats[n]->keyword)) 214 return ((enum bsdconf_format)n); 215 for (n = 0; n < bsdconf_nuser_formats; n++) 216 if (bsdconf_basename_matches(base, 217 bsdconf_user_formats[n].keyword)) 218 return ((enum bsdconf_format) 219 (BSDCONF_FORMAT_USER + n)); 220 221 return (BSDCONF_FORMAT_GENERIC); 222 } 223 224 /* 225 * Return the default file path for `format' (e.g., "/boot/loader.conf") or 226 * NULL if the format has no fixed path (e.g., BSDCONF_FORMAT_GENERIC). 227 */ 228 const char * 229 bsdconf_format_path(enum bsdconf_format format) 230 { 231 const struct bsdconf_format_def *def; 232 233 if ((def = bsdconf_format_lookup(format)) == NULL) 234 return (NULL); 235 return (def->path); 236 } 237 238 /* 239 * Return the processing_options bitmask suited to `format', for passing to 240 * bsdconf_parse(), bsdconf_fparse(), and bsdconf_put(). Unknown formats 241 * fall back to the generic descriptor. 242 */ 243 uint16_t 244 bsdconf_format_processing(enum bsdconf_format format) 245 { 246 const struct bsdconf_format_def *def; 247 248 if ((def = bsdconf_format_lookup(format)) == NULL) 249 def = bsdconf_formats[BSDCONF_FORMAT_GENERIC]; 250 return (def->processing); 251 } 252 253 /* 254 * Return the put_options bitmask suited to `format', for passing to 255 * bsdconf_put(). Unknown formats fall back to the generic descriptor. 256 */ 257 uint16_t 258 bsdconf_format_put(enum bsdconf_format format) 259 { 260 const struct bsdconf_format_def *def; 261 262 if ((def = bsdconf_format_lookup(format)) == NULL) 263 def = bsdconf_formats[BSDCONF_FORMAT_GENERIC]; 264 return (def->put); 265 } 266 267 /* 268 * Append `path' (already allocated; ownership is taken) to the growing 269 * `files' array. On success, returns zero; otherwise returns -1 (errno set 270 * by realloc(3)) and `path' is freed. 271 */ 272 static int 273 bsdconf_files_add(char ***files, size_t *nfiles, size_t *size, char *path) 274 { 275 char **tmp; 276 277 if (path == NULL) 278 return (-1); 279 if (*nfiles >= *size) { 280 *size = (*size == 0) ? 8 : *size << 1; 281 tmp = realloc(*files, *size * sizeof(**files)); 282 if (tmp == NULL) { 283 free(path); 284 return (-1); 285 } 286 *files = tmp; 287 } 288 (*files)[(*nfiles)++] = path; 289 return (0); 290 } 291 292 /* 293 * scandir(3) filter accepting non-hidden `*.conf' entries. 294 */ 295 static int 296 bsdconf_files_filter(const struct dirent *entry) 297 { 298 size_t len; 299 300 if (entry->d_name[0] == '.') 301 return (0); 302 len = strlen(entry->d_name); 303 return (len > BSDCONF_CONF_SUFFIX_LEN && 304 strcmp(entry->d_name + len - BSDCONF_CONF_SUFFIX_LEN, 305 BSDCONF_CONF_SUFFIX) == 0); 306 } 307 308 /* 309 * Compose the full path `prefix' + `path' [+ `/' + `name' [+ `.conf']] into 310 * newly allocated storage. Returns NULL on allocation failure (errno set). 311 * 312 * asprintf(3) is not in POSIX.1-2008; size with snprintf(NULL, 0) then fill 313 * so the library still compiles under _POSIX_C_SOURCE=200809L. 314 */ 315 static char * 316 bsdconf_files_path(const char *prefix, const char *path, const char *name, 317 const char *suffix) 318 { 319 char *full; 320 int len; 321 322 len = snprintf(NULL, 0, "%s%s%s%s%s", prefix, path, 323 name != NULL ? "/" : "", name != NULL ? name : "", 324 suffix != NULL ? suffix : ""); 325 if (len < 0 || (full = malloc((size_t)len + 1)) == NULL) 326 return (NULL); 327 snprintf(full, (size_t)len + 1, "%s%s%s%s%s", prefix, path, 328 name != NULL ? "/" : "", name != NULL ? name : "", 329 suffix != NULL ? suffix : ""); 330 return (full); 331 } 332 333 /* 334 * Append each existing `*.conf' entry of directory `path' (prefixed with 335 * `prefix'), sorted, to the growing `files' array. A missing or unreadable 336 * directory is not an error (a drop-in directory is optional by nature). 337 * On success, returns zero; otherwise returns -1 (errno set). 338 */ 339 static int 340 bsdconf_files_add_dir(char ***files, size_t *nfiles, size_t *size, 341 const char *prefix, const char *path) 342 { 343 int n, nentries; 344 char *dir; 345 char *full; 346 struct dirent **entries; 347 348 if ((dir = bsdconf_files_path(prefix, path, NULL, NULL)) == NULL) 349 return (-1); 350 nentries = scandir(dir, &entries, bsdconf_files_filter, alphasort); 351 free(dir); 352 if (nentries < 0) 353 return (0); 354 for (n = 0; n < nentries; n++) { 355 full = bsdconf_files_path(prefix, path, entries[n]->d_name, 356 NULL); 357 if (bsdconf_files_add(files, nfiles, size, full) != 0) { 358 while (n < nentries) 359 free(entries[n++]); 360 free(entries); 361 return (-1); 362 } 363 free(entries[n]); 364 } 365 free(entries); 366 return (0); 367 } 368 369 /* 370 * Parse call-back for the list directives chased by 371 * bsdconf_files_discover() below. The option's `value.data' member points 372 * at the (char *) slot holding the current value of the directive; each 373 * assignment encountered replaces the slot (last assignment wins, exactly 374 * as the consumer of the file behaves). 375 */ 376 static int 377 bsdconf_files_list_cb(struct bsdconf_option *option, uint32_t line, 378 char *directive, char *value) 379 { 380 char *copy; 381 char **slot = (char **)option->value.data; 382 383 (void)line; 384 (void)directive; 385 386 if ((copy = strdup(bsdconf_unquote(value))) == NULL) 387 return (-1); 388 free(*slot); 389 *slot = copy; 390 return (0); 391 } 392 393 /* 394 * Advance to the next whitespace-separated token of `list' at or beyond 395 * `cursor', storing its length through `lenp'. Returns the token or NULL 396 * when the list is exhausted. 397 */ 398 static const char * 399 bsdconf_files_token(const char *cursor, size_t *lenp) 400 { 401 402 if (cursor == NULL) 403 return (NULL); 404 cursor += strspn(cursor, " \t"); 405 if ((*lenp = strcspn(cursor, " \t")) == 0) 406 return (NULL); 407 return (cursor); 408 } 409 410 /* 411 * Ceiling on the number of configuration files discovery will chase, 412 * bounding run-away (or maliciously self-referential) file lists. 413 */ 414 #define BSDCONF_DISCOVER_MAX 64 415 416 /* 417 * Resolve the backing files of a format whose descriptor carries a 418 * `defaults' file by performing the same discovery its consumer does: read 419 * the defaults file, then chase the file-list directive (e.g., 420 * loader_conf_files) -- re-reading it after every file, as the loader does 421 * when a file queues additional names -- and finally append the drop-in 422 * directory entries and local files named by the final values of the other 423 * two list directives (likewise applied only after the file-list walk). 424 * Every file named by the file-list directive is included in 425 * the result whether or not it exists (a listed file is a legitimate 426 * write target before its first directive lands); the defaults file 427 * itself is deliberately excluded. The write candidate stored through 428 * `write_idx' is the last file-list entry, the final word among the 429 * regular configuration files. 430 * 431 * The defaults file is `defaults' verbatim when non-NULL (a caller-level 432 * override; see sysconf(8)'s LOADER_DEFAULTS); otherwise the descriptor 433 * default prefixed with `rootdir'. 434 * 435 * On success, returns zero. Returns 1 when the defaults file is missing 436 * or unreadable, directing the caller to fall back to the static source 437 * list. Otherwise returns -1 (errno set). 438 */ 439 static int 440 bsdconf_files_discover(const struct bsdconf_format_def *def, 441 const char *rootdir, const char *defaults, char ***files, 442 size_t *nfiles, size_t *size, size_t *write_idx) 443 { 444 int error, rv = -1; 445 uint16_t processing; 446 size_t len, n, nvisited = 0; 447 char *dpath = NULL; 448 char *full; 449 char *name; 450 char *conf_dirs = NULL; 451 char *conf_files = NULL; 452 char *local_files = NULL; 453 const char *token; 454 char *visited[BSDCONF_DISCOVER_MAX]; 455 struct bsdconf_option options[4]; 456 457 /* Wire each list directive to the slot holding its value */ 458 memset(options, 0, sizeof(options)); 459 n = 0; 460 if (def->files_directive != NULL) { 461 options[n].directive = def->files_directive; 462 options[n].value.data = &conf_files; 463 options[n].parse = bsdconf_files_list_cb; 464 n++; 465 } 466 if (def->dirs_directive != NULL) { 467 options[n].directive = def->dirs_directive; 468 options[n].value.data = &conf_dirs; 469 options[n].parse = bsdconf_files_list_cb; 470 n++; 471 } 472 if (def->local_directive != NULL) { 473 options[n].directive = def->local_directive; 474 options[n].value.data = &local_files; 475 options[n].parse = bsdconf_files_list_cb; 476 n++; 477 } 478 479 /* Resolve and read the defaults file (missing means fall back) */ 480 if (defaults != NULL) 481 dpath = strdup(defaults); 482 else 483 dpath = bsdconf_files_path(rootdir, def->defaults, NULL, 484 NULL); 485 if (dpath == NULL) 486 goto cleanup; 487 processing = def->processing & ~BSDCONF_REQUIRE_EQUALS; 488 if (bsdconf_parse(options, dpath, NULL, processing) != 0) { 489 rv = 1; 490 goto cleanup; 491 } 492 493 /* 494 * Chase the file-list directive. Each pass rescans the current 495 * value of the list from the start for the first file not yet 496 * visited (any file read may have revised the list), reads it, 497 * and repeats until every listed file has been visited. 498 */ 499 for (;;) { 500 token = conf_files; 501 while ((token = bsdconf_files_token(token, &len)) != NULL) { 502 for (n = 0; n < nvisited; n++) 503 if (strncmp(visited[n], token, len) == 0 && 504 visited[n][len] == '\0') 505 break; 506 if (n == nvisited) 507 break; 508 token += len; 509 } 510 if (token == NULL || nvisited >= BSDCONF_DISCOVER_MAX) 511 break; 512 if ((visited[nvisited] = strndup(token, len)) == NULL) 513 goto cleanup; 514 full = bsdconf_files_path(rootdir, visited[nvisited], NULL, 515 NULL); 516 nvisited++; 517 if (bsdconf_files_add(files, nfiles, size, full) != 0) 518 goto cleanup; 519 /* A listed file that does not (yet) exist is normal */ 520 (void)bsdconf_parse(options, full, NULL, processing); 521 } 522 *write_idx = (*nfiles > 0) ? *nfiles - 1 : 0; 523 524 /* Drop-in directories, then local files, from the final lists */ 525 token = conf_dirs; 526 while ((token = bsdconf_files_token(token, &len)) != NULL) { 527 if ((name = strndup(token, len)) == NULL) 528 goto cleanup; 529 token += len; 530 error = bsdconf_files_add_dir(files, nfiles, size, rootdir, 531 name); 532 free(name); 533 if (error != 0) 534 goto cleanup; 535 } 536 token = local_files; 537 while ((token = bsdconf_files_token(token, &len)) != NULL) { 538 if ((name = strndup(token, len)) == NULL) 539 goto cleanup; 540 token += len; 541 full = bsdconf_files_path(rootdir, name, NULL, NULL); 542 free(name); 543 if (bsdconf_files_add(files, nfiles, size, full) != 0) 544 goto cleanup; 545 } 546 547 rv = 0; 548 549 cleanup: 550 n = errno; /* preserve errno across free(3) */ 551 while (nvisited > 0) 552 free(visited[--nvisited]); 553 free(conf_files); 554 free(conf_dirs); 555 free(local_files); 556 free(dpath); 557 errno = n; 558 return (rv); 559 } 560 561 /* 562 * Resolve the ordered list of configuration files backing `format' into a 563 * newly allocated array of newly allocated paths, stored through `filesp' 564 * with the count stored through `nfilesp'. Files appear in the order the 565 * system sources them at boot; a directive in a later file overrides the 566 * same directive in an earlier one, making the last file listing a 567 * directive the authoritative source of its value. When `write_idxp' is 568 * non-NULL, the index of the file recommended for directives found in no 569 * file at all is stored through it (the format's default write path, or 570 * the last regular configuration file when discovery is in play). 571 * 572 * A format whose descriptor names a defaults file resolves its list by 573 * discovery (see bsdconf_files_discover() above); `defaults' overrides 574 * the descriptor's defaults file when non-NULL and is used verbatim (it 575 * is not prefixed with `rootdir'). For all other formats `defaults' is 576 * ignored and the static source list governs, as it also does when the 577 * defaults file is missing. 578 * 579 * Each static path is prefixed with `rootdir' unless NULL or empty. 580 * Sources of type BSDCONF_SOURCE_FILE are always listed, whether or not 581 * the file exists; sources of type BSDCONF_SOURCE_DIR contribute each 582 * existing `*.conf' entry (sorted); sources of type BSDCONF_SOURCE_MODDIR 583 * contribute `<module>.conf' only when `module' is non-NULL. A format 584 * with no source list resolves to its default path alone. 585 * 586 * On success, returns zero and the caller frees the result with 587 * bsdconf_format_files_free(). Otherwise returns -1 and errno is set 588 * (EINVAL for an unknown format or a format with no backing files). 589 */ 590 int 591 bsdconf_format_files(enum bsdconf_format format, const char *rootdir, 592 const char *module, const char *defaults, char ***filesp, 593 size_t *nfilesp, size_t *write_idxp) 594 { 595 int error; 596 size_t nfiles = 0; 597 size_t size = 0; 598 size_t write_idx = 0; 599 char **files = NULL; 600 char *path; 601 const struct bsdconf_format_def *def; 602 const struct bsdconf_source *source; 603 604 /* Check arguments */ 605 if (filesp == NULL || nfilesp == NULL || 606 (def = bsdconf_format_lookup(format)) == NULL) { 607 errno = EINVAL; 608 return (-1); 609 } 610 if (rootdir == NULL) 611 rootdir = ""; 612 613 /* Discover the list when the descriptor names a defaults file */ 614 if (def->defaults != NULL) { 615 error = bsdconf_files_discover(def, rootdir, defaults, 616 &files, &nfiles, &size, &write_idx); 617 if (error < 0) 618 goto cleanup; 619 if (error == 0 && nfiles > 0) 620 goto done; 621 /* Missing defaults file; fall back to static sources */ 622 bsdconf_format_files_free(files, nfiles); 623 files = NULL; 624 nfiles = size = write_idx = 0; 625 } 626 627 /* A format with no source list is backed by its path alone */ 628 if (def->sources == NULL) { 629 if (def->path == NULL) { 630 errno = EINVAL; 631 return (-1); 632 } 633 path = bsdconf_files_path(rootdir, def->path, NULL, NULL); 634 if (bsdconf_files_add(&files, &nfiles, &size, path) != 0) 635 goto cleanup; 636 goto done; 637 } 638 639 for (source = def->sources; source->path != NULL; source++) { 640 switch (source->type) { 641 case BSDCONF_SOURCE_FILE: 642 path = bsdconf_files_path(rootdir, source->path, 643 NULL, NULL); 644 if (def->path != NULL && 645 strcmp(source->path, def->path) == 0) 646 write_idx = nfiles; 647 if (bsdconf_files_add(&files, &nfiles, &size, 648 path) != 0) 649 goto cleanup; 650 break; 651 case BSDCONF_SOURCE_DIR: 652 if (bsdconf_files_add_dir(&files, &nfiles, &size, 653 rootdir, source->path) != 0) 654 goto cleanup; 655 break; 656 case BSDCONF_SOURCE_MODDIR: 657 if (module == NULL) 658 break; 659 path = bsdconf_files_path(rootdir, source->path, 660 module, BSDCONF_CONF_SUFFIX); 661 if (bsdconf_files_add(&files, &nfiles, &size, 662 path) != 0) 663 goto cleanup; 664 break; 665 } 666 } 667 668 done: 669 *filesp = files; 670 *nfilesp = nfiles; 671 if (write_idxp != NULL) 672 *write_idxp = write_idx; 673 return (0); 674 675 cleanup: 676 bsdconf_format_files_free(files, nfiles); 677 return (-1); 678 } 679 680 /* 681 * Release an array of paths obtained from bsdconf_format_files(). 682 */ 683 void 684 bsdconf_format_files_free(char **files, size_t nfiles) 685 { 686 size_t n; 687 688 if (files == NULL) 689 return; 690 for (n = 0; n < nfiles; n++) 691 free(files[n]); 692 free(files); 693 } 694