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