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