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