xref: /dragonfly/contrib/cvs-1.12/src/find_names.c (revision 0bb9290e)
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 }
533