1/* File tree walker functions. 2 Copyright (C) 1996-2004, 2006-2008, 2010 Free Software Foundation, Inc. 3 This file is part of the GNU C Library. 4 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996. 5 6 The GNU C Library is free software; you can redistribute it and/or 7 modify it under the terms of the GNU Lesser General Public 8 License as published by the Free Software Foundation; either 9 version 2.1 of the License, or (at your option) any later version. 10 11 The GNU C Library is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 Lesser General Public License for more details. 15 16 You should have received a copy of the GNU Lesser General Public 17 License along with the GNU C Library; if not, write to the Free 18 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 19 02111-1307 USA. */ 20 21#ifdef HAVE_CONFIG_H 22#include "config.h" 23#endif 24 25#if __GNUC__ 26# define alloca __builtin_alloca 27#else 28# include <alloca.h> 29#endif 30 31#include <dirent.h> 32#define NAMLEN(dirent) strlen ((dirent)->d_name) 33#include <errno.h> 34#include <fcntl.h> 35#include <limits.h> 36#include <search.h> 37#include <stdlib.h> 38#include <string.h> 39#include <unistd.h> 40#include <sys/stat.h> 41 42#if HAVE_SYS_PARAM_H 43# include <sys/param.h> 44#endif 45 46#include <atalk/ftw.h> 47 48#define mempcpy(D, S, N) ((void *) ((char *) memcpy (D, S, N) + (N))) 49 50#define NDEBUG 1 51#include <assert.h> 52 53#ifndef _LIBC 54# undef __chdir 55# define __chdir chdir 56# undef __closedir 57# define __closedir closedir 58# undef __fchdir 59# define __fchdir fchdir 60# undef __getcwd 61# define __getcwd(P, N) xgetcwd () 62# undef __mempcpy 63# define __mempcpy mempcpy 64# undef __opendir 65# define __opendir opendir 66# undef __readdir64 67# define __readdir64 readdir 68# undef __tdestroy 69# define __tdestroy tdestroy 70# undef __tfind 71# define __tfind tfind 72# undef __tsearch 73# define __tsearch tsearch 74# undef internal_function 75# define internal_function /* empty */ 76# undef dirent64 77# define dirent64 dirent 78# undef MAX 79# define MAX(a, b) ((a) > (b) ? (a) : (b)) 80#endif 81 82#ifndef __set_errno 83# define __set_errno(Val) errno = (Val) 84#endif 85 86/* Support for the LFS API version. */ 87#ifndef FTW_NAME 88# define FTW_NAME ftw 89# define NFTW_NAME nftw 90# define NFTW_OLD_NAME __old_nftw 91# define NFTW_NEW_NAME __new_nftw 92# define INO_T ino_t 93# define STAT stat 94# define LXSTAT(V,f,sb) lstat (f,sb) 95# define XSTAT(V,f,sb) stat (f,sb) 96# define FXSTATAT(V,d,f,sb,m) fstatat (d, f, sb, m) 97 98#endif 99 100/* We define PATH_MAX if the system does not provide a definition. 101 This does not artificially limit any operation. PATH_MAX is simply 102 used as a guesstimate for the expected maximal path length. 103 Buffers will be enlarged if necessary. */ 104#ifndef PATH_MAX 105# define PATH_MAX 1024 106#endif 107 108struct dir_data 109{ 110 DIR *stream; 111 int streamfd; 112 char *content; 113}; 114 115struct known_object 116{ 117 dev_t dev; 118 INO_T ino; 119}; 120 121struct ftw_data 122{ 123 /* Array with pointers to open directory streams. */ 124 struct dir_data **dirstreams; 125 size_t actdir; 126 size_t maxdir; 127 128 /* Buffer containing name of currently processed object. */ 129 char *dirbuf; 130 size_t dirbufsize; 131 132 /* Passed as fourth argument to `nftw' callback. The `base' member 133 tracks the content of the `dirbuf'. */ 134 struct FTW ftw; 135 136 /* Flags passed to `nftw' function. 0 for `ftw'. */ 137 int flags; 138 139 /* Conversion array for flag values. It is the identity mapping for 140 `nftw' calls, otherwise it maps the values to those known by 141 `ftw'. */ 142 const int *cvt_arr; 143 144 /* Callback function. We always use the `nftw' form. */ 145 NFTW_FUNC_T func; 146 147 /* Device of starting point. Needed for FTW_MOUNT. */ 148 dev_t dev; 149 150 /* Data structure for keeping fingerprints of already processed 151 object. This is needed when not using FTW_PHYS. */ 152 void *known_objects; 153}; 154 155 156/* Internally we use the FTW_* constants used for `nftw'. When invoked 157 as `ftw', map each flag to the subset of values used by `ftw'. */ 158static const int nftw_arr[] = 159{ 160 FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_SL, FTW_DP, FTW_SLN 161}; 162 163static const int ftw_arr[] = 164{ 165 FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_F, FTW_D, FTW_NS 166}; 167 168 169static dir_notification_func_t upfunc; 170 171/* Forward declarations of local functions. */ 172static int ftw_dir (struct ftw_data *data, struct STAT *st, 173 struct dir_data *old_dir) internal_function; 174 175typedef void (*__free_fn_t) (void *__nodep); 176typedef struct node_t { 177 const void *key; 178 struct node_t *left; 179 struct node_t *right; 180 unsigned int red:1; 181} *node; 182 183static void tdestroy_recurse (node root, __free_fn_t freefct) 184{ 185 if (root->left != NULL) 186 tdestroy_recurse (root->left, freefct); 187 if (root->right != NULL) 188 tdestroy_recurse (root->right, freefct); 189 (*freefct) ((void *) root->key); 190 /* Free the node itself. */ 191 free (root); 192} 193 194static void mytdestroy (void *vroot, __free_fn_t freefct) 195{ 196 node root = (node) vroot; 197 198 if (root != NULL) 199 tdestroy_recurse (root, freefct); 200} 201 202static char *mystpcpy(char *a, const char *b) 203{ 204 strcpy(a, b); 205 return (a + strlen(a)); 206} 207 208static char *xgetcwd(void) 209{ 210 char *cwd; 211 char *ret; 212 unsigned path_max; 213 214 errno = 0; 215 path_max = (unsigned) PATH_MAX; 216 path_max += 2; /* The getcwd docs say to do this. */ 217 218 cwd = malloc (path_max); 219 errno = 0; 220 while ((ret = getcwd (cwd, path_max)) == NULL && errno == ERANGE) { 221 path_max += 512; 222 cwd = realloc (cwd, path_max); 223 errno = 0; 224 } 225 226 if (ret == NULL) { 227 int save_errno = errno; 228 free (cwd); 229 errno = save_errno; 230 return NULL; 231 } 232 return cwd; 233} 234 235static int 236object_compare (const void *p1, const void *p2) 237{ 238 /* We don't need a sophisticated and useful comparison. We are only 239 interested in equality. However, we must be careful not to 240 accidentally compare `holes' in the structure. */ 241 const struct known_object *kp1 = p1, *kp2 = p2; 242 int cmp1; 243 cmp1 = (kp1->ino > kp2->ino) - (kp1->ino < kp2->ino); 244 if (cmp1 != 0) 245 return cmp1; 246 return (kp1->dev > kp2->dev) - (kp1->dev < kp2->dev); 247} 248 249 250static int 251add_object (struct ftw_data *data, struct STAT *st) 252{ 253 struct known_object *newp = malloc (sizeof (struct known_object)); 254 if (newp == NULL) 255 return -1; 256 newp->dev = st->st_dev; 257 newp->ino = st->st_ino; 258 return __tsearch (newp, &data->known_objects, object_compare) ? 0 : -1; 259} 260 261 262static inline int 263find_object (struct ftw_data *data, struct STAT *st) 264{ 265 struct known_object obj; 266 obj.dev = st->st_dev; 267 obj.ino = st->st_ino; 268 return __tfind (&obj, &data->known_objects, object_compare) != NULL; 269} 270 271 272static inline int 273open_dir_stream (int *dfdp, struct ftw_data *data, struct dir_data *dirp) 274{ 275 int result = 0; 276 277 if (data->dirstreams[data->actdir] != NULL) 278 { 279 /* Oh, oh. We must close this stream. Get all remaining 280 entries and store them as a list in the `content' member of 281 the `struct dir_data' variable. */ 282 size_t bufsize = 1024; 283 char *buf = malloc (bufsize); 284 285 if (buf == NULL) 286 result = -1; 287 else 288 { 289 DIR *st = data->dirstreams[data->actdir]->stream; 290 struct dirent64 *d; 291 size_t actsize = 0; 292 293 while ((d = __readdir64 (st)) != NULL) 294 { 295 size_t this_len = NAMLEN (d); 296 if (actsize + this_len + 2 >= bufsize) 297 { 298 char *newp; 299 bufsize += MAX (1024, 2 * this_len); 300 newp = (char *) realloc (buf, bufsize); 301 if (newp == NULL) 302 { 303 /* No more memory. */ 304 int save_err = errno; 305 free (buf); 306 __set_errno (save_err); 307 return -1; 308 } 309 buf = newp; 310 } 311 312 *((char *) __mempcpy (buf + actsize, d->d_name, this_len)) 313 = '\0'; 314 actsize += this_len + 1; 315 } 316 317 /* Terminate the list with an additional NUL byte. */ 318 buf[actsize++] = '\0'; 319 320 /* Shrink the buffer to what we actually need. */ 321 data->dirstreams[data->actdir]->content = realloc (buf, actsize); 322 if (data->dirstreams[data->actdir]->content == NULL) 323 { 324 int save_err = errno; 325 free (buf); 326 __set_errno (save_err); 327 result = -1; 328 } 329 else 330 { 331 __closedir (st); 332 data->dirstreams[data->actdir]->stream = NULL; 333 data->dirstreams[data->actdir]->streamfd = -1; 334 data->dirstreams[data->actdir] = NULL; 335 } 336 } 337 } 338 339 /* Open the new stream. */ 340 if (result == 0) 341 { 342 assert (data->dirstreams[data->actdir] == NULL); 343 344 if (dfdp != NULL && *dfdp != -1) 345 { 346 int fd = openat(*dfdp, data->dirbuf + data->ftw.base, O_RDONLY); 347 dirp->stream = NULL; 348 if (fd != -1 && (dirp->stream = fdopendir (fd)) == NULL) 349 close(fd); 350 } 351 else 352 { 353 const char *name; 354 355 if (data->flags & FTW_CHDIR) 356 { 357 name = data->dirbuf + data->ftw.base; 358 if (name[0] == '\0') 359 name = "."; 360 } 361 else 362 name = data->dirbuf; 363 364 dirp->stream = __opendir (name); 365 } 366 367 if (dirp->stream == NULL) 368 result = -1; 369 else 370 { 371 dirp->streamfd = dirfd (dirp->stream); 372 dirp->content = NULL; 373 data->dirstreams[data->actdir] = dirp; 374 375 if (++data->actdir == data->maxdir) 376 data->actdir = 0; 377 } 378 } 379 380 return result; 381} 382 383 384static int 385process_entry (struct ftw_data *data, struct dir_data *dir, const char *name, size_t namlen) 386{ 387 struct STAT st; 388 int result = 0; 389 int flag = 0; 390 size_t new_buflen; 391 392 if (name[0] == '.' && (name[1] == '\0' 393 || (name[1] == '.' && name[2] == '\0'))) 394 /* Don't process the "." and ".." entries. */ 395 return 0; 396 397 new_buflen = data->ftw.base + namlen + 2; 398 if (data->dirbufsize < new_buflen) 399 { 400 /* Enlarge the buffer. */ 401 char *newp; 402 403 data->dirbufsize = 2 * new_buflen; 404 newp = (char *) realloc (data->dirbuf, data->dirbufsize); 405 if (newp == NULL) 406 return -1; 407 data->dirbuf = newp; 408 } 409 410 *((char *) __mempcpy (data->dirbuf + data->ftw.base, name, namlen)) = '\0'; 411 412 int statres; 413 if (dir->streamfd != -1) 414 statres = FXSTATAT (_STAT_VER, dir->streamfd, name, &st, 415 (data->flags & FTW_PHYS) ? AT_SYMLINK_NOFOLLOW : 0); 416 else 417 { 418 if ((data->flags & FTW_CHDIR) == 0) 419 name = data->dirbuf; 420 421 statres = ((data->flags & FTW_PHYS) 422 ? LXSTAT (_STAT_VER, name, &st) 423 : XSTAT (_STAT_VER, name, &st)); 424 } 425 426 if (statres < 0) 427 { 428 if (errno != EACCES && errno != ENOENT) 429 result = -1; 430 else if (data->flags & FTW_PHYS) 431 flag = FTW_NS; 432 else 433 { 434 if (dir->streamfd != -1) 435 statres = FXSTATAT (_STAT_VER, dir->streamfd, name, &st, 436 AT_SYMLINK_NOFOLLOW); 437 else 438 statres = LXSTAT (_STAT_VER, name, &st); 439 if (statres == 0 && S_ISLNK (st.st_mode)) 440 flag = FTW_SLN; 441 else 442 flag = FTW_NS; 443 } 444 } 445 else 446 { 447 if (S_ISDIR (st.st_mode)) 448 flag = FTW_D; 449 else if (S_ISLNK (st.st_mode)) 450 flag = FTW_SL; 451 else 452 flag = FTW_F; 453 } 454 455 if (result == 0 456 && (flag == FTW_NS 457 || !(data->flags & FTW_MOUNT) || st.st_dev == data->dev)) 458 { 459 if (flag == FTW_D) 460 { 461 if ((data->flags & FTW_PHYS) 462 || (!find_object (data, &st) 463 /* Remember the object. */ 464 && (result = add_object (data, &st)) == 0)) 465 result = ftw_dir (data, &st, dir); 466 } 467 else 468 result = (*data->func) (data->dirbuf, &st, data->cvt_arr[flag], 469 &data->ftw); 470 } 471 472 if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SUBTREE) 473 result = 0; 474 475 return result; 476} 477 478 479static int 480ftw_dir (struct ftw_data *data, struct STAT *st, struct dir_data *old_dir) 481{ 482 struct dir_data dir; 483 struct dirent64 *d; 484 int previous_base = data->ftw.base; 485 int result; 486 char *startp; 487 488 /* Open the stream for this directory. This might require that 489 another stream has to be closed. */ 490 result = open_dir_stream (old_dir == NULL ? NULL : &old_dir->streamfd, 491 data, &dir); 492 if (result != 0) 493 { 494 if (errno == EACCES) 495 /* We cannot read the directory. Signal this with a special flag. */ 496 result = (*data->func) (data->dirbuf, st, FTW_DNR, &data->ftw); 497 498 return result; 499 } 500 501 /* First, report the directory (if not depth-first). */ 502 if (!(data->flags & FTW_DEPTH)) 503 { 504 result = (*data->func) (data->dirbuf, st, FTW_D, &data->ftw); 505 if (result != 0) 506 { 507 int save_err; 508 fail: 509 save_err = errno; 510 __closedir (dir.stream); 511 dir.streamfd = -1; 512 __set_errno (save_err); 513 514 if (data->actdir-- == 0) 515 data->actdir = data->maxdir - 1; 516 data->dirstreams[data->actdir] = NULL; 517 return result; 518 } 519 } 520 521 /* If necessary, change to this directory. */ 522 if (data->flags & FTW_CHDIR) 523 { 524 if (__fchdir (dirfd (dir.stream)) < 0) 525 { 526 result = -1; 527 goto fail; 528 } 529 } 530 531 /* Next, update the `struct FTW' information. */ 532 ++data->ftw.level; 533 startp = data->dirbuf + strlen(data->dirbuf); 534 /* There always must be a directory name. */ 535 assert (startp != data->dirbuf); 536 if (startp[-1] != '/') 537 *startp++ = '/'; 538 data->ftw.base = startp - data->dirbuf; 539 540 while (dir.stream != NULL && (d = __readdir64 (dir.stream)) != NULL) 541 { 542 result = process_entry (data, &dir, d->d_name, NAMLEN (d)); 543 if (result != 0) 544 break; 545 } 546 547 if (dir.stream != NULL) 548 { 549 /* The stream is still open. I.e., we did not need more 550 descriptors. Simply close the stream now. */ 551 int save_err = errno; 552 553 assert (dir.content == NULL); 554 555 __closedir (dir.stream); 556 dir.streamfd = -1; 557 __set_errno (save_err); 558 559 if (data->actdir-- == 0) 560 data->actdir = data->maxdir - 1; 561 data->dirstreams[data->actdir] = NULL; 562 } 563 else 564 { 565 int save_err; 566 char *runp = dir.content; 567 568 while (result == 0 && *runp != '\0') 569 { 570 char *endp = strchr (runp, '\0'); 571 572 // XXX Should store the d_type values as well?! 573 result = process_entry (data, &dir, runp, endp - runp); 574 575 runp = endp + 1; 576 } 577 578 save_err = errno; 579 free (dir.content); 580 __set_errno (save_err); 581 } 582 583 if ((data->flags & FTW_ACTIONRETVAL) && result == FTW_SKIP_SIBLINGS) 584 result = 0; 585 586 /* Prepare the return, revert the `struct FTW' information. */ 587 data->dirbuf[data->ftw.base - 1] = '\0'; 588 --data->ftw.level; 589 if (upfunc) 590 (*upfunc)(); 591 data->ftw.base = previous_base; 592 593 /* Finally, if we process depth-first report the directory. */ 594 if (result == 0 && (data->flags & FTW_DEPTH)) 595 result = (*data->func) (data->dirbuf, st, FTW_DP, &data->ftw); 596 597 if (old_dir 598 && (data->flags & FTW_CHDIR) 599 && (result == 0 600 || ((data->flags & FTW_ACTIONRETVAL) 601 && (result != -1 && result != FTW_STOP)))) 602 { 603 /* Change back to the parent directory. */ 604 int done = 0; 605 if (old_dir->stream != NULL) 606 if (__fchdir (dirfd (old_dir->stream)) == 0) 607 done = 1; 608 609 if (!done) 610 { 611 if (data->ftw.base == 1) 612 { 613 if (__chdir ("/") < 0) 614 result = -1; 615 } 616 else 617 if (__chdir ("..") < 0) 618 result = -1; 619 } 620 } 621 622 return result; 623} 624 625 626static int ftw_startup (const char *dir, 627 int is_nftw, 628 void *func, 629 dir_notification_func_t up, 630 int descriptors, 631 int flags) 632{ 633 struct ftw_data data; 634 struct STAT st; 635 int result = 0; 636 int save_err; 637 int cwdfd = -1; 638 char *cwd = NULL; 639 char *cp; 640 641 upfunc = up; 642 643 /* First make sure the parameters are reasonable. */ 644 if (dir[0] == '\0') 645 { 646 __set_errno (ENOENT); 647 return -1; 648 } 649 650 data.maxdir = descriptors < 1 ? 1 : descriptors; 651 data.actdir = 0; 652 data.dirstreams = (struct dir_data **) alloca (data.maxdir 653 * sizeof (struct dir_data *)); 654 memset (data.dirstreams, '\0', data.maxdir * sizeof (struct dir_data *)); 655 656 /* PATH_MAX is always defined when we get here. */ 657 data.dirbufsize = MAX (2 * strlen (dir), PATH_MAX); 658 data.dirbuf = (char *) malloc (data.dirbufsize); 659 if (data.dirbuf == NULL) 660 return -1; 661 cp = mystpcpy (data.dirbuf, dir); 662 /* Strip trailing slashes. */ 663 while (cp > data.dirbuf + 1 && cp[-1] == '/') 664 --cp; 665 *cp = '\0'; 666 667 data.ftw.level = 0; 668 669 /* Find basename. */ 670 while (cp > data.dirbuf && cp[-1] != '/') 671 --cp; 672 data.ftw.base = cp - data.dirbuf; 673 674 data.flags = flags; 675 676 /* This assignment might seem to be strange but it is what we want. 677 The trick is that the first three arguments to the `ftw' and 678 `nftw' callback functions are equal. Therefore we can call in 679 every case the callback using the format of the `nftw' version 680 and get the correct result since the stack layout for a function 681 call in C allows this. */ 682 data.func = (NFTW_FUNC_T) func; 683 684 /* Since we internally use the complete set of FTW_* values we need 685 to reduce the value range before calling a `ftw' callback. */ 686 data.cvt_arr = is_nftw ? nftw_arr : ftw_arr; 687 688 /* No object known so far. */ 689 data.known_objects = NULL; 690 691 /* Now go to the directory containing the initial file/directory. */ 692 if (flags & FTW_CHDIR) 693 { 694 /* We have to be able to go back to the current working 695 directory. The best way to do this is to use a file 696 descriptor. */ 697 cwdfd = open (".", O_RDONLY); 698 if (cwdfd == -1) 699 { 700 /* Try getting the directory name. This can be needed if 701 the current directory is executable but not readable. */ 702 if (errno == EACCES) 703 /* GNU extension ahead. */ 704 cwd = __getcwd(NULL, 0); 705 706 if (cwd == NULL) 707 goto out_fail; 708 } 709 else if (data.maxdir > 1) 710 /* Account for the file descriptor we use here. */ 711 --data.maxdir; 712 713 if (data.ftw.base > 0) 714 { 715 /* Change to the directory the file is in. In data.dirbuf 716 we have a writable copy of the file name. Just NUL 717 terminate it for now and change the directory. */ 718 if (data.ftw.base == 1) 719 /* I.e., the file is in the root directory. */ 720 result = __chdir ("/"); 721 else 722 { 723 char ch = data.dirbuf[data.ftw.base - 1]; 724 data.dirbuf[data.ftw.base - 1] = '\0'; 725 result = __chdir (data.dirbuf); 726 data.dirbuf[data.ftw.base - 1] = ch; 727 } 728 } 729 } 730 731 /* Get stat info for start directory. */ 732 if (result == 0) 733 { 734 const char *name; 735 736 if (data.flags & FTW_CHDIR) 737 { 738 name = data.dirbuf + data.ftw.base; 739 if (name[0] == '\0') 740 name = "."; 741 } 742 else 743 name = data.dirbuf; 744 745 if (((flags & FTW_PHYS) 746 ? LXSTAT (_STAT_VER, name, &st) 747 : XSTAT (_STAT_VER, name, &st)) < 0) 748 { 749 if (!(flags & FTW_PHYS) 750 && errno == ENOENT 751 && LXSTAT (_STAT_VER, name, &st) == 0 752 && S_ISLNK (st.st_mode)) 753 result = (*data.func) (data.dirbuf, &st, data.cvt_arr[FTW_SLN], 754 &data.ftw); 755 else 756 /* No need to call the callback since we cannot say anything 757 about the object. */ 758 result = -1; 759 } 760 else 761 { 762 if (S_ISDIR (st.st_mode)) 763 { 764 /* Remember the device of the initial directory in case 765 FTW_MOUNT is given. */ 766 data.dev = st.st_dev; 767 768 /* We know this directory now. */ 769 if (!(flags & FTW_PHYS)) 770 result = add_object (&data, &st); 771 772 if (result == 0) 773 result = ftw_dir (&data, &st, NULL); 774 } 775 else 776 { 777 int flag = S_ISLNK (st.st_mode) ? FTW_SL : FTW_F; 778 779 result = (*data.func) (data.dirbuf, &st, data.cvt_arr[flag], 780 &data.ftw); 781 } 782 } 783 784 if ((flags & FTW_ACTIONRETVAL) 785 && (result == FTW_SKIP_SUBTREE || result == FTW_SKIP_SIBLINGS)) 786 result = 0; 787 } 788 789 /* Return to the start directory (if necessary). */ 790 if (cwdfd != -1) 791 { 792 int save_err = errno; 793 __fchdir (cwdfd); 794 close(cwdfd); 795 __set_errno (save_err); 796 } 797 else if (cwd != NULL) 798 { 799 int save_err = errno; 800 __chdir (cwd); 801 free (cwd); 802 __set_errno (save_err); 803 } 804 805 /* Free all memory. */ 806out_fail: 807 save_err = errno; 808 mytdestroy (data.known_objects, free); 809 free (data.dirbuf); 810 __set_errno (save_err); 811 812 return result; 813} 814 815 816 817/* Entry points. */ 818int NFTW_NAME(const char *path, 819 NFTW_FUNC_T func, 820 dir_notification_func_t up, 821 int descriptors, 822 int flags) 823{ 824 return ftw_startup (path, 1, func, up, descriptors, flags); 825} 826 827