xref: /netbsd/external/gpl2/xcvs/dist/src/find_names.c (revision 3feae4e4)
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