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