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
26static int find_dirs PROTO((char *dir, List * list, int checkadm,
27			    List *entries));
28static int find_rcs PROTO((char *dir, List * list));
29static int add_subdir_proc PROTO((Node *, void *));
30static int register_subdir_proc PROTO((Node *, void *));
31
32/*
33 * add the key from entry on entries list to the files list
34 */
35static int add_entries_proc PROTO((Node *, void *));
36static int
37add_entries_proc (node, closure)
38     Node *node;
39     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
61List *
62Find_Names (repository, which, aflag, optentries)
63    char *repository;
64    int which;
65    int aflag;
66    List **optentries;
67{
68    List *entries;
69    List *files;
70
71    /* make a list for the files */
72    files = getlist ();
73
74    /* look at entries (if necessary) */
75    if (which & W_LOCAL)
76    {
77	/* parse the entries file (if it exists) */
78	entries = Entries_Open (aflag, NULL);
79	if (entries != NULL)
80	{
81	    /* walk the entries file adding elements to the files list */
82	    (void) walklist (entries, add_entries_proc, files);
83
84	    /* if our caller wanted the entries list, return it; else free it */
85	    if (optentries != NULL)
86		*optentries = entries;
87	    else
88		Entries_Close (entries);
89	}
90    }
91
92    if ((which & W_REPOS) && repository && !isreadable (CVSADM_ENTSTAT))
93    {
94	/* search the repository */
95	if (find_rcs (repository, files) != 0)
96	{
97	    error (0, errno, "cannot open directory %s", repository);
98	    goto error_exit;
99	}
100
101	/* search the attic too */
102	if (which & W_ATTIC)
103	{
104	    char *dir;
105	    dir = xmalloc (strlen (repository) + sizeof (CVSATTIC) + 10);
106	    (void) sprintf (dir, "%s/%s", repository, CVSATTIC);
107	    if (find_rcs (dir, files) != 0
108		&& !existence_error (errno))
109		/* For now keep this a fatal error, seems less useful
110		   for access control than the case above.  */
111		error (1, errno, "cannot open directory %s", dir);
112	    free (dir);
113	}
114    }
115
116    /* sort the list into alphabetical order and return it */
117    sortlist (files, fsortcmp);
118    return (files);
119 error_exit:
120    dellist (&files);
121    return NULL;
122}
123
124/*
125 * Add an entry from the subdirs list to the directories list.  This
126 * is called via walklist.
127 */
128
129static int
130add_subdir_proc (p, closure)
131     Node *p;
132     void *closure;
133{
134    List *dirlist = closure;
135    Entnode *entnode = p->data;
136    Node *dnode;
137
138    if (entnode->type != ENT_SUBDIR)
139	return 0;
140
141    dnode = getnode ();
142    dnode->type = DIRS;
143    dnode->key = xstrdup (entnode->user);
144    if (addnode (dirlist, dnode) != 0)
145	freenode (dnode);
146    return 0;
147}
148
149/*
150 * Register a subdirectory.  This is called via walklist.
151 */
152
153/*ARGSUSED*/
154static int
155register_subdir_proc (p, closure)
156     Node *p;
157     void *closure;
158{
159    List *entries = (List *) closure;
160
161    Subdir_Register (entries, (char *) NULL, p->key);
162    return 0;
163}
164
165/*
166 * create a list of directories to traverse from the current directory
167 */
168List *
169Find_Directories (repository, which, entries)
170    char *repository;
171    int which;
172    List *entries;
173{
174    List *dirlist;
175
176    /* make a list for the directories */
177    dirlist = getlist ();
178
179    /* find the local ones */
180    if (which & W_LOCAL)
181    {
182	List *tmpentries;
183	struct stickydirtag *sdtp;
184
185	/* Look through the Entries file.  */
186
187	if (entries != NULL)
188	    tmpentries = entries;
189	else if (isfile (CVSADM_ENT))
190	    tmpentries = Entries_Open (0, NULL);
191	else
192	    tmpentries = NULL;
193
194	if (tmpentries != NULL)
195	    sdtp = tmpentries->list->data;
196
197	/* If we do have an entries list, then if sdtp is NULL, or if
198           sdtp->subdirs is nonzero, all subdirectory information is
199           recorded in the entries list.  */
200	if (tmpentries != NULL && (sdtp == NULL || sdtp->subdirs))
201	    walklist (tmpentries, add_subdir_proc, (void *) dirlist);
202	else
203	{
204	    /* This is an old working directory, in which subdirectory
205               information is not recorded in the Entries file.  Find
206               the subdirectories the hard way, and, if possible, add
207               it to the Entries file for next time.  */
208
209	    /* FIXME-maybe: find_dirs is bogus for this usage because
210	       it skips CVSATTIC and CVSLCK directories--those names
211	       should be special only in the repository.  However, in
212	       the interests of not perturbing this code, we probably
213	       should leave well enough alone unless we want to write
214	       a sanity.sh test case (which would operate by manually
215	       hacking on the CVS/Entries file).  */
216
217	    if (find_dirs (".", dirlist, 1, tmpentries) != 0)
218		error (1, errno, "cannot open current directory");
219	    if (tmpentries != NULL)
220	    {
221		if (! list_isempty (dirlist))
222		    walklist (dirlist, register_subdir_proc,
223			      (void *) tmpentries);
224		else
225		    Subdirs_Known (tmpentries);
226	    }
227	}
228
229	if (entries == NULL && tmpentries != NULL)
230	    Entries_Close (tmpentries);
231    }
232
233    /* look for sub-dirs in the repository */
234    if ((which & W_REPOS) && repository)
235    {
236	/* search the repository */
237	if (find_dirs (repository, dirlist, 0, entries) != 0)
238	    error (1, errno, "cannot open directory %s", repository);
239
240	/* We don't need to look in the attic because directories
241	   never go in the attic.  In the future, there hopefully will
242	   be a better mechanism for detecting whether a directory in
243	   the repository is alive or dead; it may or may not involve
244	   moving directories to the attic.  */
245    }
246
247    /* sort the list into alphabetical order and return it */
248    sortlist (dirlist, fsortcmp);
249    return (dirlist);
250}
251
252/*
253 * Finds all the ,v files in the argument directory, and adds them to the
254 * files list.  Returns 0 for success and non-zero if the argument directory
255 * cannot be opened, in which case errno is set to indicate the error.
256 * In the error case LIST is left in some reasonable state (unchanged, or
257 * containing the files which were found before the error occurred).
258 */
259static int
260find_rcs (dir, list)
261    char *dir;
262    List *list;
263{
264    Node *p;
265    struct dirent *dp;
266    DIR *dirp;
267
268    /* set up to read the dir */
269    if ((dirp = CVS_OPENDIR (dir)) == NULL)
270	return (1);
271
272    /* read the dir, grabbing the ,v files */
273    errno = 0;
274    while ((dp = CVS_READDIR (dirp)) != NULL)
275    {
276	if (CVS_FNMATCH (RCSPAT, dp->d_name, 0) == 0)
277	{
278	    char *comma;
279
280	    comma = strrchr (dp->d_name, ',');	/* strip the ,v */
281	    *comma = '\0';
282	    p = getnode ();
283	    p->type = FILES;
284	    p->key = xstrdup (dp->d_name);
285	    if (addnode (list, p) != 0)
286		freenode (p);
287	}
288	errno = 0;
289    }
290    if (errno != 0)
291    {
292	int save_errno = errno;
293	(void) CVS_CLOSEDIR (dirp);
294	errno = save_errno;
295	return 1;
296    }
297    (void) CVS_CLOSEDIR (dirp);
298    return (0);
299}
300
301/*
302 * Finds all the subdirectories of the argument dir and adds them to
303 * the specified list.  Sub-directories without a CVS administration
304 * directory are optionally ignored.  If ENTRIES is not NULL, all
305 * files on the list are ignored.  Returns 0 for success or 1 on
306 * error, in which case errno is set to indicate the error.
307 */
308static int
309find_dirs (dir, list, checkadm, entries)
310    char *dir;
311    List *list;
312    int checkadm;
313    List *entries;
314{
315    Node *p;
316    char *tmp = NULL;
317    size_t tmp_size = 0;
318    struct dirent *dp;
319    DIR *dirp;
320    int skip_emptydir = 0;
321
322    /* First figure out whether we need to skip directories named
323       Emptydir.  Except in the CVSNULLREPOS case, Emptydir is just
324       a normal directory name.  */
325    if (isabsolute (dir)
326	&& strncmp (dir, current_parsed_root->directory, strlen (current_parsed_root->directory)) == 0
327	&& ISDIRSEP (dir[strlen (current_parsed_root->directory)])
328	&& strcmp (dir + strlen (current_parsed_root->directory) + 1, CVSROOTADM) == 0)
329	skip_emptydir = 1;
330
331    /* set up to read the dir */
332    if ((dirp = CVS_OPENDIR (dir)) == NULL)
333	return (1);
334
335    /* read the dir, grabbing sub-dirs */
336    errno = 0;
337    while ((dp = CVS_READDIR (dirp)) != NULL)
338    {
339	if (strcmp (dp->d_name, ".") == 0 ||
340	    strcmp (dp->d_name, "..") == 0 ||
341	    strcmp (dp->d_name, CVSATTIC) == 0 ||
342	    strcmp (dp->d_name, CVSLCK) == 0 ||
343	    strcmp (dp->d_name, CVSREP) == 0)
344	    goto do_it_again;
345
346	/* findnode() is going to be significantly faster than stat()
347	   because it involves no system calls.  That is why we bother
348	   with the entries argument, and why we check this first.  */
349	if (entries != NULL && findnode (entries, dp->d_name) != NULL)
350	    goto do_it_again;
351
352	if (skip_emptydir
353	    && strcmp (dp->d_name, CVSNULLREPOS) == 0)
354	    goto do_it_again;
355
356#ifdef DT_DIR
357	if (dp->d_type != DT_DIR)
358	{
359	    if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_LNK)
360		goto do_it_again;
361#endif
362	    /* don't bother stating ,v files */
363	    if (CVS_FNMATCH (RCSPAT, dp->d_name, 0) == 0)
364		goto do_it_again;
365
366	    expand_string (&tmp,
367			   &tmp_size,
368			   strlen (dir) + strlen (dp->d_name) + 10);
369	    sprintf (tmp, "%s/%s", dir, dp->d_name);
370	    if (!isdir (tmp))
371		goto do_it_again;
372
373#ifdef DT_DIR
374	}
375#endif
376
377	/* check for administration directories (if needed) */
378	if (checkadm)
379	{
380	    /* blow off symbolic links to dirs in local dir */
381#ifdef DT_DIR
382	    if (dp->d_type != DT_DIR)
383	    {
384		/* we're either unknown or a symlink at this point */
385		if (dp->d_type == DT_LNK)
386		    goto do_it_again;
387#endif
388		/* Note that we only get here if we already set tmp
389		   above.  */
390		if (islink (tmp))
391		    goto do_it_again;
392#ifdef DT_DIR
393	    }
394#endif
395
396	    /* check for new style */
397	    expand_string (&tmp,
398			   &tmp_size,
399			   (strlen (dir) + strlen (dp->d_name)
400			    + sizeof (CVSADM) + 10));
401	    (void) sprintf (tmp, "%s/%s/%s", dir, dp->d_name, CVSADM);
402	    if (!isdir (tmp))
403		goto do_it_again;
404	}
405
406	/* put it in the list */
407	p = getnode ();
408	p->type = DIRS;
409	p->key = xstrdup (dp->d_name);
410	if (addnode (list, p) != 0)
411	    freenode (p);
412
413    do_it_again:
414	errno = 0;
415    }
416    if (errno != 0)
417    {
418	int save_errno = errno;
419	(void) CVS_CLOSEDIR (dirp);
420	errno = save_errno;
421	return 1;
422    }
423    (void) CVS_CLOSEDIR (dirp);
424    if (tmp != NULL)
425	free (tmp);
426    return (0);
427}
428