Complete rewrite. Add implementation of `nftw'.
[kopensolaris-gnu/glibc.git] / io / ftw.c
1 /* File tree walker functions.
2    Copyright (C) 1996, 1997 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 Library General Public License as
8    published by the Free Software Foundation; either version 2 of the
9    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    Library General Public License for more details.
15
16    You should have received a copy of the GNU Library General Public
17    License along with the GNU C Library; see the file COPYING.LIB.  If not,
18    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19    Boston, MA 02111-1307, USA.  */
20
21 #include <dirent.h>
22 #include <errno.h>
23 #include <ftw.h>
24 #include <search.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/param.h>
29 #include <sys/stat.h>
30
31 /* #define NDEBUG 1 */
32 #include <assert.h>
33
34
35 struct dir_data
36 {
37   DIR *stream;
38   char *content;
39 };
40
41 struct ftw_data
42 {
43   struct dir_data **dirstreams;
44   size_t actdir;
45   size_t maxdir;
46
47   char *dirbuf;
48   size_t dirbufsize;
49   struct FTW ftw;
50
51   int flags;
52
53   int *cvt_arr;
54   __nftw_func_t func;
55
56   struct stat st;
57
58   dev_t dev;
59 };
60
61
62 /* Internally we use the FTW_* constants used for `nftw'.  When the
63    process called `ftw' we must reduce the flag to the known flags
64    for `ftw'.  */
65 static int nftw_arr[] =
66 {
67   FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_SL, FTW_DP, FTW_SLN
68 };
69
70 static int ftw_arr[] =
71 {
72   FTW_F, FTW_D, FTW_DNR, FTW_NS, FTW_F, FTW_D, FTW_NS
73 };
74
75
76 /* Forward declarations of local functions.  */
77 static int ftw_dir (struct ftw_data *data);
78
79
80 static inline int
81 open_dir_stream (struct ftw_data *data, struct dir_data *dirp)
82 {
83   int result = 0;
84
85   if (data->dirstreams[data->actdir] != NULL)
86     {
87       /* Oh, oh.  We must close this stream.  Get all remaining
88          entries and store them as a list in the `content' member of
89          the `struct dir_data' variable.  */
90       size_t bufsize = 1024;
91       char *buf = malloc (bufsize);
92
93       if (buf == NULL)
94         result = -1;
95       else
96         {
97           DIR *st = data->dirstreams[data->actdir]->stream;
98           struct dirent *d;
99           size_t actsize = 0;
100
101           while ((d = readdir (st)) != NULL)
102             {
103               size_t this_len = _D_EXACT_NAMLEN (d);
104               if (actsize + this_len + 2 >= bufsize)
105                 {
106                   char *newp;
107                   bufsize += MAX (1024, 2 * this_len);
108                   newp = realloc (buf, bufsize);
109                   if (newp == NULL)
110                     {
111                       /* No more memory.  */
112                       int save_err = errno;
113                       free (buf);
114                       __set_errno (save_err);
115                       result = -1;
116                       break;
117                     }
118                   buf = newp;
119                 }
120
121               memcpy (buf + actsize, d->d_name, this_len);
122               actsize += this_len;
123               buf[actsize++] = '\0';
124             }
125
126           /* Terminate the list with an additional NUL byte.  */
127           buf[actsize++] = '\0';
128
129           /* Shrink the buffer to what we actually need.  */
130           data->dirstreams[data->actdir]->content = realloc (buf, actsize);
131           if (data->dirstreams[data->actdir]->content == NULL)
132             {
133               int save_err = errno;
134               free (buf);
135               __set_errno (save_err);
136               result = -1;
137             }
138           else
139             {
140               closedir (st);
141               data->dirstreams[data->actdir]->stream = NULL;
142               data->dirstreams[data->actdir] = NULL;
143             }
144         }
145     }
146
147   /* Open the new stream.  */
148   if (result == 0)
149     {
150       assert (data->dirstreams[data->actdir] == NULL);
151
152       dirp->stream = opendir (data->dirbuf);
153       if (dirp->stream == NULL)
154         result = -1;
155       else
156         {
157           dirp->content = NULL;
158           data->dirstreams[data->actdir] = dirp;
159
160           if (++data->actdir == data->maxdir)
161             data->actdir = 0;
162         }
163     }
164
165   return result;
166 }
167
168
169 static inline int
170 process_entry (struct ftw_data *data, struct dir_data *dir, const char *name,
171                size_t namlen)
172 {
173   int result = 0;
174   int flag;
175
176   if (name[0] == '.' && (name[1] == '\0'
177                          || (name[1] == '.' && name[2] == '\0')))
178     /* Don't process the "." and ".." entries.  */
179     return 0;
180
181   if (data->dirbufsize < data->ftw.base + namlen + 2)
182     {
183       /* Enlarge the buffer.  */
184       char *newp;
185
186       data->dirbufsize *= 2;
187       newp = realloc (data->dirbuf, data->dirbufsize);
188       if (newp == NULL)
189         return -1;
190       data->dirbuf = newp;
191     }
192
193   memcpy (data->dirbuf + data->ftw.base, name, namlen);
194   data->dirbuf[data->ftw.base + namlen] = '\0';
195
196   if (((data->flags & FTW_PHYS) ? lstat : stat) (data->dirbuf, &data->st) < 0)
197     {
198       if (errno != EACCES && errno != ENOENT)
199         result = -1;
200       else if (!(data->flags & FTW_PHYS)
201                && lstat (data->dirbuf, &data->st) == 0
202                && S_ISLNK (data->st.st_mode))
203         flag = FTW_SLN;
204       else
205         flag = FTW_NS;
206     }
207   else
208     {
209       if (S_ISDIR (data->st.st_mode))
210         flag = FTW_D;
211       else if (S_ISLNK (data->st.st_mode))
212         flag = FTW_SL;
213       else
214         flag = FTW_F;
215     }
216
217   if (result == 0
218       && (!(data->flags & FTW_MOUNT) || data->st.st_dev == data->dev))
219     {
220       if (flag == FTW_D)
221         {
222           result = ftw_dir (data);
223
224           if (result == 0 && (data->flags & FTW_CHDIR))
225             {
226               /* Change back to current directory.  */
227               int done = 0;
228               if (dir->stream != NULL)
229                 if (fchdir (dirfd (dir->stream)) == 0)
230                   done = 1;
231
232               if (!done)
233                 {
234                   if (data->ftw.base == 1)
235                     {
236                       if (chdir ("/") < 0)
237                         result = -1;
238                     }
239                   else
240                     {
241                       /* Please note that we overwrite a slash.  */
242                       data->dirbuf[data->ftw.base - 1] = '\0';
243
244                       if (chdir (data->dirbuf) < 0)
245                         result = -1;
246                       else
247                         data->dirbuf[data->ftw.base - 1] = '/';
248                     }
249                 }
250             }
251         }
252       else
253         result = (*data->func) (data->dirbuf, &data->st, data->cvt_arr[flag],
254                                 &data->ftw);
255     }
256
257   return result;
258 }
259
260
261 static int
262 ftw_dir (struct ftw_data *data)
263 {
264   struct dir_data dir;
265   struct dirent *d;
266   int previous_base = data->ftw.base;
267   int result = 0;
268   char *startp;
269
270   /* First, report the directory (if not depth-first).  */
271   if (!(data->flags & FTW_DEPTH))
272     {
273       result = (*data->func) (data->dirbuf, &data->st, FTW_D, &data->ftw);
274       if (result != 0)
275         return result;
276     }
277
278   /* Open the stream for this directory.  This might require that
279      another stream has to be closed.  */
280   result = open_dir_stream (data, &dir);
281   if (result != 0)
282     return result;
283
284   /* If necessary, change to this directory.  */
285   if (data->flags & FTW_CHDIR)
286     {
287       if (fchdir (dirfd (dir.stream)) < 0)
288         {
289           if (errno == ENOSYS)
290             {
291               if (chdir (data->dirbuf) < 0)
292                 result = -1;
293             }
294           else
295             result = -1;
296         }
297
298       if (result != 0)
299         {
300           int save_err = errno;
301           closedir (dir.stream);
302           __set_errno (save_err);
303
304           if (data->actdir-- == 0)
305             data->actdir = data->maxdir - 1;
306           data->dirstreams[data->actdir] = NULL;
307
308           return result;
309         }
310     }
311
312   /* Next, update the `struct FTW' information.  */
313   ++data->ftw.level;
314   startp = strchr (data->dirbuf, '\0');
315   *startp++ = '/';
316   data->ftw.base = startp - data->dirbuf;
317
318   while (dir.stream != NULL && (d = readdir (dir.stream)) != NULL)
319     {
320       result = process_entry (data, &dir, d->d_name, _D_EXACT_NAMLEN (d));
321       if (result != 0)
322         break;
323     }
324
325   if (dir.stream != NULL)
326     {
327       /* The stream is still open.  I.e., we did not need more
328          descriptors.  Simply close the stream now.  */
329       int save_err = errno;
330
331       assert (dir.content == NULL);
332
333       closedir (dir.stream);
334       __set_errno (save_err);
335
336       if (data->actdir-- == 0)
337         data->actdir = data->maxdir - 1;
338       data->dirstreams[data->actdir] = NULL;
339     }
340   else
341     {
342       int save_err;
343       char *runp = dir.content;
344
345       assert (result == 0);
346
347       while (*runp != '\0')
348         {
349           char *endp = strchr (runp, '\0');
350
351           result = process_entry (data, &dir, runp, endp - runp);
352           if (result != 0)
353             break;
354
355           runp = endp + 1;
356         }
357
358       save_err = errno;
359       free (dir.content);
360       __set_errno (save_err);
361     }
362
363   /* Prepare the return, revert the `struct FTW' information.  */
364   --data->ftw.level;
365   data->ftw.base = previous_base;
366
367   /* Finally, if we process depth-first report the directory.  */
368   if (result == 0 && (data->flags & FTW_DEPTH))
369     result = (*data->func) (data->dirbuf, &data->st, FTW_DP, &data->ftw);
370
371   return result;
372 }
373
374
375 static int
376 ftw_startup (const char *dir, int is_nftw, void *func, int descriptors,
377              int flags)
378 {
379   struct ftw_data data;
380   int result = 0;
381   int save_err;
382   char *cwd;
383   char *cp;
384
385   /* First make sure the parameters are reasonable.  */
386   if (dir[0] == '\0')
387     {
388       __set_errno (ENOTDIR);
389       return -1;
390     }
391
392   data.maxdir = descriptors < 1 ? 1 : descriptors;
393   data.actdir = 0;
394   data.dirstreams = (struct dir_data **) alloca (data.maxdir
395                                                  * sizeof (struct dir_data *));
396   memset (data.dirstreams, '\0', data.maxdir * sizeof (struct dir_data *));
397
398   data.dirbufsize = MAX (2 * strlen (dir), PATH_MAX);
399   data.dirbuf = (char *) malloc (data.dirbufsize);
400   if (data.dirbuf == NULL)
401     return -1;
402   cp = stpcpy (data.dirbuf, dir);
403   /* Strip trailing slashes.  */
404   while (cp > data.dirbuf + 1 && cp[-1] == '/')
405     --cp;
406   *cp = '\0';
407
408   data.ftw.level = 0;
409
410   /* Find basename.  */
411   while (cp > data.dirbuf && cp[-1] != '/')
412     --cp;
413   data.ftw.base = cp - data.dirbuf;
414
415   data.flags = flags;
416
417   /* This assignment might seem to be strange but it is what we want.
418      The trick is that the first three arguments to the `ftw' and
419      `nftw' callback functions are equal.  Therefore we can call in
420      every case the callback using the format of the `nftw' version
421      and get the correct result since the stack layout for a function
422      call in C allows this.  */
423   data.func = (__nftw_func_t) func;
424
425   /* Since we internally use the complete set of FTW_* values we need
426      to reduce the value range before calling a `ftw' callback.  */
427   data.cvt_arr = is_nftw ? nftw_arr : ftw_arr;
428
429   /* Now go to the directory containing the initial file/directory.  */
430   if ((flags & FTW_CHDIR) && data.ftw.base > 0)
431     {
432       /* GNU extension ahead.  */
433       cwd =  getcwd (NULL, 0);
434       if (cwd == NULL)
435         result = -1;
436       else
437         {
438           /* Change to the directory the file is in.  In data.dirbuf
439              we have a writable copy of the file name.  Just NUL
440              terminate it for now and change the directory.  */
441           if (data.ftw.base == 1)
442             /* I.e., the file is in the root directory.  */
443             result = chdir ("/");
444           else
445             {
446               char ch = data.dirbuf[data.ftw.base - 1];
447               data.dirbuf[data.ftw.base - 1] = '\0';
448               result = chdir (data.dirbuf);
449               data.dirbuf[data.ftw.base - 1] = ch;
450             }
451         }
452     }
453
454   /* Get stat info for start directory.  */
455   if (result == 0)
456     if (((flags & FTW_PHYS) ? lstat : stat) (data.dirbuf, &data.st) < 0)
457       {
458         if (errno == EACCES)
459           result = (*data.func) (data.dirbuf, &data.st, FTW_NS, &data.ftw);
460         else if (!(flags & FTW_PHYS)
461                  && errno == ENOENT
462                  && lstat (dir, &data.st) == 0 && S_ISLNK (data.st.st_mode))
463           result = (*data.func) (data.dirbuf, &data.st, data.cvt_arr[FTW_SLN],
464                                  &data.ftw);
465         else
466           /* No need to call the callback since we cannot say anything
467              about the object.  */
468           result = -1;
469       }
470     else
471       {
472         if (S_ISDIR (data.st.st_mode))
473           {
474             data.dev = data.st.st_dev;
475             result = ftw_dir (&data);
476           }
477         else
478           {
479             int flag = S_ISLNK (data.st.st_mode) ? FTW_SL : FTW_F;
480
481             result = (*data.func) (data.dirbuf, &data.st, data.cvt_arr[flag],
482                                    &data.ftw);
483           }
484       }
485
486   /* Return to the start directory (if necessary).  */
487   if (cwd != NULL)
488     {
489       int save_err = errno;
490       chdir (cwd);
491       free (cwd);
492       __set_errno (save_err);
493     }
494
495   /* Free all memory.  */
496   save_err = errno;
497   free (data.dirbuf);
498   __set_errno (save_err);
499
500   return result;
501 }
502
503
504
505 /* Entry points.  */
506
507 int
508 ftw (path, func, descriptors)
509      const char *path;
510      __ftw_func_t func;
511      int descriptors;
512 {
513   return ftw_startup (path, 0, func, descriptors, 0);
514 }
515
516 int
517 nftw (path, func, descriptors, flags)
518      const char *path;
519      __nftw_func_t func;
520      int descriptors;
521      int flags;
522 {
523   return ftw_startup (path, 1, func, descriptors, flags);
524 }