xref: /dragonfly/contrib/cvs-1.12/src/find_names.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
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
add_entries_proc(Node * node,void * closure)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 *
Find_Names(char * repository,int which,int aflag,List ** optentries)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
add_subdir_proc(Node * p,void * closure)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
register_subdir_proc(Node * p,void * closure)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 *
Find_Directories(char * repository,int which,List * entries)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 *
find_files(const char * dir,const char * pat)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
strip_rcsext(Node * p,void * closure)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
find_rcs(dir,list)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
find_dirs(char * dir,List * list,int checkadm,List * entries)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 }
533