update changelog
[alioth/cvs.git] / src / find_names.c
1 /*
2  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3  *
4  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5  *                                  and others.
6  *
7  * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8  * Portions Copyright (C) 1989-1992, Brian Berliner
9  * 
10  * You may distribute under the terms of the GNU General Public License as
11  * specified in the README file that comes with the CVS source distribution.
12  * 
13  * Find Names
14  * 
15  * Finds all the pertinent file names, both from the administration and from the
16  * repository
17  * 
18  * Find Dirs
19  * 
20  * Finds all pertinent sub-directories of the checked out instantiation and the
21  * repository (and optionally the attic)
22  */
23
24 #include "cvs.h"
25 #include <glob.h>
26 #include <assert.h>
27
28 static int find_dirs (char *dir, List * list, int checkadm,
29                             List *entries);
30 static int find_rcs (const char *dir, List * list);
31 static int add_subdir_proc (Node *, void *);
32 static int register_subdir_proc (Node *, void *);
33
34 /*
35  * add the key from entry on entries list to the files list
36  */
37 static int add_entries_proc (Node *, void *);
38 static int
39 add_entries_proc (Node *node, void *closure)
40 {
41     Node *fnode;
42     List *filelist = closure;
43     Entnode *entnode = node->data;
44
45     if (entnode->type != ENT_FILE)
46         return (0);
47
48     fnode = getnode ();
49     fnode->type = FILES;
50     fnode->key = xstrdup (node->key);
51     if (addnode (filelist, fnode) != 0)
52         freenode (fnode);
53     return (0);
54 }
55
56 /* Find files in the repository and/or working directory.  On error,
57    may either print a nonfatal error and return NULL, or just give
58    a fatal error.  On success, return non-NULL (even if it is an empty
59    list).  */
60
61 List *
62 Find_Names (char *repository, int which, int aflag, List **optentries)
63 {
64     List *entries;
65     List *files;
66
67     /* make a list for the files */
68     files = getlist ();
69
70     /* look at entries (if necessary) */
71     if (which & W_LOCAL)
72     {
73         /* parse the entries file (if it exists) */
74         entries = Entries_Open (aflag, NULL);
75         if (entries != NULL)
76         {
77             /* walk the entries file adding elements to the files list */
78             (void) walklist (entries, add_entries_proc, files);
79
80             /* if our caller wanted the entries list, return it; else free it */
81             if (optentries != NULL)
82                 *optentries = entries;
83             else
84                 Entries_Close (entries);
85         }
86     }
87
88     if ((which & W_REPOS) && repository && !isreadable (CVSADM_ENTSTAT))
89     {
90         /* search the repository */
91         if (find_rcs (repository, files) != 0)
92         {
93             error (0, errno, "cannot open directory %s",
94                    primary_root_inverse_translate (repository));
95             goto error_exit;
96         }
97
98         /* search the attic too */
99         if (which & W_ATTIC)
100         {
101             char *dir = Xasprintf ("%s/%s", repository, CVSATTIC);
102             if (find_rcs (dir, files) != 0
103                 && !existence_error (errno))
104                 /* For now keep this a fatal error, seems less useful
105                    for access control than the case above.  */
106                 error (1, errno, "cannot open directory %s",
107                        primary_root_inverse_translate (dir));
108             free (dir);
109         }
110     }
111
112     /* sort the list into alphabetical order and return it */
113     sortlist (files, fsortcmp);
114     return files;
115  error_exit:
116     dellist (&files);
117     return NULL;
118 }
119
120 /*
121  * Add an entry from the subdirs list to the directories list.  This
122  * is called via walklist.
123  */
124
125 static int
126 add_subdir_proc (Node *p, void *closure)
127 {
128     List *dirlist = closure;
129     Entnode *entnode = p->data;
130     Node *dnode;
131
132     if (entnode->type != ENT_SUBDIR)
133         return 0;
134
135     dnode = getnode ();
136     dnode->type = DIRS;
137     dnode->key = xstrdup (entnode->user);
138     if (addnode (dirlist, dnode) != 0)
139         freenode (dnode);
140     return 0;
141 }
142
143 /*
144  * Register a subdirectory.  This is called via walklist.
145  */
146
147 /*ARGSUSED*/
148 static int
149 register_subdir_proc (Node *p, void *closure)
150 {
151     List *entries = (List *) closure;
152
153     Subdir_Register (entries, NULL, p->key);
154     return 0;
155 }
156
157 /*
158  * create a list of directories to traverse from the current directory
159  */
160 List *
161 Find_Directories (char *repository, int which, List *entries)
162 {
163     List *dirlist;
164
165     /* make a list for the directories */
166     dirlist = getlist ();
167
168     /* find the local ones */
169     if (which & W_LOCAL)
170     {
171         List *tmpentries;
172         struct stickydirtag *sdtp;
173
174         /* Look through the Entries file.  */
175
176         if (entries != NULL)
177             tmpentries = entries;
178         else if (isfile (CVSADM_ENT))
179             tmpentries = Entries_Open (0, NULL);
180         else
181             tmpentries = NULL;
182
183         if (tmpentries != NULL)
184             sdtp = tmpentries->list->data;
185
186         /* If we do have an entries list, then if sdtp is NULL, or if
187            sdtp->subdirs is nonzero, all subdirectory information is
188            recorded in the entries list.  */
189         if (tmpentries != NULL && (sdtp == NULL || sdtp->subdirs))
190             walklist (tmpentries, add_subdir_proc, (void *) dirlist);
191         else
192         {
193             /* This is an old working directory, in which subdirectory
194                information is not recorded in the Entries file.  Find
195                the subdirectories the hard way, and, if possible, add
196                it to the Entries file for next time.  */
197
198             /* FIXME-maybe: find_dirs is bogus for this usage because
199                it skips CVSATTIC and CVSLCK directories--those names
200                should be special only in the repository.  However, in
201                the interests of not perturbing this code, we probably
202                should leave well enough alone unless we want to write
203                a sanity.sh test case (which would operate by manually
204                hacking on the CVS/Entries file).  */
205
206             if (find_dirs (".", dirlist, 1, tmpentries) != 0)
207                 error (1, errno, "cannot open current directory");
208             if (tmpentries != NULL)
209             {
210                 if (! list_isempty (dirlist))
211                     walklist (dirlist, register_subdir_proc,
212                               (void *) tmpentries);
213                 else
214                     Subdirs_Known (tmpentries);
215             }
216         }
217
218         if (entries == NULL && tmpentries != NULL)
219             Entries_Close (tmpentries);
220     }
221
222     /* look for sub-dirs in the repository */
223     if ((which & W_REPOS) && repository)
224     {
225         /* search the repository */
226         if (find_dirs (repository, dirlist, 0, entries) != 0)
227             error (1, errno, "cannot open directory %s", repository);
228
229         /* We don't need to look in the attic because directories
230            never go in the attic.  In the future, there hopefully will
231            be a better mechanism for detecting whether a directory in
232            the repository is alive or dead; it may or may not involve
233            moving directories to the attic.  */
234     }
235
236     /* sort the list into alphabetical order and return it */
237     sortlist (dirlist, fsortcmp);
238     return (dirlist);
239 }
240
241
242
243 /* Finds all the files matching PAT.  If DIR is NULL, PAT will be interpreted
244  * as either absolute or relative to the PWD and read errors, e.g. failure to
245  * open a directory, will be ignored.  If DIR is not NULL, PAT is
246  * always interpreted as relative to DIR.  Adds all matching files and
247  * directories to a new List.  Returns the new List for success and NULL in
248  * case of error, in which case ERRNO will also be set.
249  *
250  * NOTES
251  *   When DIR is NULL, this is really just a thinly veiled wrapper for glob().
252  *
253  *   Much of the cruft in this function could be avoided if DIR was eliminated.
254  *
255  * INPUTS
256  *   dir        The directory to match relative to.
257  *   pat        The pattern to match against, via glob().
258  *
259  * GLOBALS
260  *   errno              Set on error.
261  *   really_quiet       Used to decide whether to print warnings.
262  *
263  * RETURNS
264  *   A pointer to a List of matching file and directory names, on success.
265  *   NULL, on error.
266  *
267  * ERRORS
268  *   Error returns can be caused if glob() returns an error.  ERRNO will be
269  *   set.  When !REALLY_QUIET and the failure was not a read error, a warning
270  *   message will be printed via error (0, errno, ...).
271  */
272 List *
273 find_files (const char *dir, const char *pat)
274 {
275     List *retval;
276     glob_t glist;
277     int err, i;
278     char *catpat = NULL;
279     bool dirslash = false;
280
281     if (dir && *dir)
282     {
283         size_t catpatlen = 0;
284         const char *p;
285         if (glob_pattern_p (dir, false))
286         {
287             /* Escape special characters in DIR.  */
288             size_t len = 0;
289             p = dir;
290             while (*p)
291             {
292                 switch (*p)
293                 {
294                     case '\\':
295                     case '*':
296                     case '[':
297                     case ']':
298                     case '?':
299                         expand_string (&catpat, &catpatlen, len + 1);
300                         catpat[len++] = '\\';
301                     default:
302                         expand_string (&catpat, &catpatlen, len + 1);
303                         catpat[len++] = *p++;
304                         break;
305                 }
306             }
307             catpat[len] = '\0';
308         }
309         else
310         {
311             xrealloc_and_strcat (&catpat, &catpatlen, dir);
312             p = dir + strlen (dir);
313         }
314
315         dirslash = *p - 1 == '/';
316         if (!dirslash)
317             xrealloc_and_strcat (&catpat, &catpatlen, "/");
318
319         xrealloc_and_strcat (&catpat, &catpatlen, pat);
320         pat = catpat;
321     }
322
323     err = glob (pat, GLOB_PERIOD | (dir ? GLOB_ERR : 0), NULL, &glist);
324     if (err && err != GLOB_NOMATCH)
325     {
326         if (err == GLOB_ABORTED)
327             /* Let our caller handle the problem.  */
328             return NULL;
329         if (err == GLOB_NOSPACE) errno = ENOMEM;
330         if (!really_quiet)
331             error (0, errno, "glob failed");
332         if (catpat) free (catpat);
333         return NULL;
334     }
335
336     /* Copy what glob() returned into a List for our caller.  */
337     retval = getlist ();
338     for (i = 0; i < glist.gl_pathc; i++)
339     {
340         Node *p;
341         const char *tmp;
342
343         /* Ignore `.' && `..'.  */
344         tmp = last_component (glist.gl_pathv[i]);
345         if (!strcmp (tmp, ".") || !strcmp (tmp, ".."))
346             continue;
347
348         p = getnode ();
349         p->type = FILES;
350         p->key = xstrdup (glist.gl_pathv[i]
351                           + (dir ? strlen (dir) + !dirslash : 0));
352         if (addnode (retval, p)) freenode (p);
353     }
354
355     if (catpat) free (catpat);
356     globfree (&glist);
357     return retval;
358 }
359
360
361
362 /* walklist() proc which strips a trailing RCSEXT from node keys.
363  */
364 static int
365 strip_rcsext (Node *p, void *closure)
366 {
367     char *s = p->key + strlen (p->key) - strlen (RCSEXT);
368     assert (!strcmp (s, RCSEXT));
369     *s = '\0'; /* strip the ,v */
370     return 0;
371 }
372
373
374
375 /*
376  * Finds all the ,v files in the directory DIR, and adds them to the LIST.
377  * Returns 0 for success and non-zero if DIR cannot be opened, in which case
378  * ERRNO is set to indicate the error.  In the error case, LIST is left in some
379  * reasonable state (unchanged, or containing the files which were found before
380  * the error occurred).
381  *
382  * INPUTS
383  *   dir        The directory to open for read.
384  *
385  * OUTPUTS
386  *   list       Where to store matching file entries.
387  *
388  * GLOBALS
389  *   errno      Set on error.
390  *
391  * RETURNS
392  *   0, for success.
393  *   <> 0, on error.
394  */
395 static int
396 find_rcs (dir, list)
397     const char *dir;
398     List *list;
399 {
400     List *newlist;
401     if (!(newlist = find_files (dir, RCSPAT)))
402         return 1;
403     walklist (newlist, strip_rcsext, NULL);
404     mergelists (list, &newlist);
405     return 0;
406 }
407
408
409
410 /*
411  * Finds all the subdirectories of the argument dir and adds them to
412  * the specified list.  Sub-directories without a CVS administration
413  * directory are optionally ignored.  If ENTRIES is not NULL, all
414  * files on the list are ignored.  Returns 0 for success or 1 on
415  * error, in which case errno is set to indicate the error.
416  */
417 static int
418 find_dirs (char *dir, List *list, int checkadm, List *entries)
419 {
420     Node *p;
421     char *tmp = NULL;
422     size_t tmp_size = 0;
423     struct dirent *dp;
424     DIR *dirp;
425     int skip_emptydir = 0;
426
427     /* First figure out whether we need to skip directories named
428        Emptydir.  Except in the CVSNULLREPOS case, Emptydir is just
429        a normal directory name.  */
430     if (ISABSOLUTE (dir)
431         && strncmp (dir, current_parsed_root->directory, strlen (current_parsed_root->directory)) == 0
432         && ISSLASH (dir[strlen (current_parsed_root->directory)])
433         && strcmp (dir + strlen (current_parsed_root->directory) + 1, CVSROOTADM) == 0)
434         skip_emptydir = 1;
435
436     /* set up to read the dir */
437     if ((dirp = CVS_OPENDIR (dir)) == NULL)
438         return (1);
439
440     /* read the dir, grabbing sub-dirs */
441     errno = 0;
442     while ((dp = CVS_READDIR (dirp)) != NULL)
443     {
444         if (strcmp (dp->d_name, ".") == 0 ||
445             strcmp (dp->d_name, "..") == 0 ||
446             strcmp (dp->d_name, CVSATTIC) == 0 ||
447             strcmp (dp->d_name, CVSLCK) == 0 ||
448             strcmp (dp->d_name, CVSREP) == 0)
449             goto do_it_again;
450
451         /* findnode() is going to be significantly faster than stat()
452            because it involves no system calls.  That is why we bother
453            with the entries argument, and why we check this first.  */
454         if (entries != NULL && findnode (entries, dp->d_name) != NULL)
455             goto do_it_again;
456
457         if (skip_emptydir
458             && strcmp (dp->d_name, CVSNULLREPOS) == 0)
459             goto do_it_again;
460
461 #ifdef DT_DIR
462         if (dp->d_type != DT_DIR) 
463         {
464             if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_LNK)
465                 goto do_it_again;
466 #endif
467             /* don't bother stating ,v files */
468             if (CVS_FNMATCH (RCSPAT, dp->d_name, 0) == 0)
469                 goto do_it_again;
470
471             expand_string (&tmp,
472                            &tmp_size,
473                            strlen (dir) + strlen (dp->d_name) + 10);
474             sprintf (tmp, "%s/%s", dir, dp->d_name);
475             if (!isdir (tmp))
476                 goto do_it_again;
477
478 #ifdef DT_DIR
479         }
480 #endif
481
482         /* check for administration directories (if needed) */
483         if (checkadm)
484         {
485             /* blow off symbolic links to dirs in local dir */
486 #ifdef DT_DIR
487             if (dp->d_type != DT_DIR)
488             {
489                 /* we're either unknown or a symlink at this point */
490                 if (dp->d_type == DT_LNK)
491                     goto do_it_again;
492 #endif
493                 /* Note that we only get here if we already set tmp
494                    above.  */
495                 if (islink (tmp))
496                     goto do_it_again;
497 #ifdef DT_DIR
498             }
499 #endif
500
501             /* check for new style */
502             expand_string (&tmp,
503                            &tmp_size,
504                            (strlen (dir) + strlen (dp->d_name)
505                             + sizeof (CVSADM) + 10));
506             (void) sprintf (tmp, "%s/%s/%s", dir, dp->d_name, CVSADM);
507             if (!isdir (tmp))
508                 goto do_it_again;
509         }
510
511         /* put it in the list */
512         p = getnode ();
513         p->type = DIRS;
514         p->key = xstrdup (dp->d_name);
515         if (addnode (list, p) != 0)
516             freenode (p);
517
518     do_it_again:
519         errno = 0;
520     }
521     if (errno != 0)
522     {
523         int save_errno = errno;
524         (void) CVS_CLOSEDIR (dirp);
525         errno = save_errno;
526         return 1;
527     }
528     (void) CVS_CLOSEDIR (dirp);
529     if (tmp != NULL)
530         free (tmp);
531     return (0);
532 }