1263367Semaste/*
2254721Semaste * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3254721Semaste *
4254721Semaste * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5254721Semaste *                                  and others.
6254721Semaste *
7254721Semaste * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8254721Semaste * Portions Copyright (C) 1989-1992, Brian Berliner
9254721Semaste *
10254721Semaste * You may distribute under the terms of the GNU General Public License as
11254721Semaste * specified in the README file that comes with the CVS source distribution.
12254721Semaste *
13254721Semaste * General recursion handler
14254721Semaste *
15254721Semaste */
16269024Semaste#include <sys/cdefs.h>
17254721Semaste__RCSID("$NetBSD: recurse.c,v 1.4 2024/02/04 20:47:25 christos Exp $");
18254721Semaste
19263363Semaste#include "cvs.h"
20263363Semaste#include "save-cwd.h"
21269024Semaste#include "fileattr.h"
22269024Semaste#include "edit.h"
23263363Semaste
24263363Semastestatic int do_dir_proc (Node * p, void *closure);
25254721Semastestatic int do_file_proc (Node * p, void *closure);
26254721Semastestatic void addlist (List ** listp, char *key);
27254721Semastestatic int unroll_files_proc (Node *p, void *closure);
28254721Semastestatic void addfile (List **listp, char *dir, char *file);
29254721Semaste
30254721Semastestatic char *update_dir;
31254721Semastestatic char *repository = NULL;
32254721Semastestatic List *filelist = NULL; /* holds list of files on which to operate */
33254721Semastestatic List *dirlist = NULL; /* holds list of directories on which to operate */
34254721Semaste
35254721Semastestruct recursion_frame {
36254721Semaste    FILEPROC fileproc;
37254721Semaste    FILESDONEPROC filesdoneproc;
38254721Semaste    DIRENTPROC direntproc;
39254721Semaste    DIRLEAVEPROC dirleaveproc;
40254721Semaste    void *callerdat;
41254721Semaste    Dtype flags;
42254721Semaste    int which;
43254721Semaste    int aflag;
44254721Semaste    int locktype;
45254721Semaste    int dosrcs;
46254721Semaste    char *repository;			/* Keep track of repository for rtag */
47254721Semaste};
48254721Semaste
49254721Semastestatic int do_recursion (struct recursion_frame *frame);
50254721Semaste
51254721Semaste/* I am half tempted to shove a struct file_info * into the struct
52254721Semaste   recursion_frame (but then we would need to modify or create a
53254721Semaste   recursion_frame for each file), or shove a struct recursion_frame *
54254721Semaste   into the struct file_info (more tempting, although it isn't completely
55254721Semaste   clear that the struct file_info should contain info about recursion
56254721Semaste   processor internals).  So instead use this struct.  */
57254721Semaste
58254721Semastestruct frame_and_file {
59254721Semaste    struct recursion_frame *frame;
60254721Semaste    struct file_info *finfo;
61254721Semaste};
62254721Semaste
63254721Semaste/* Similarly, we need to pass the entries list to do_dir_proc.  */
64254721Semaste
65254721Semastestruct frame_and_entries {
66254721Semaste    struct recursion_frame *frame;
67254721Semaste    List *entries;
68254721Semaste};
69254721Semaste
70254721Semaste
71254721Semaste/* Start a recursive command.
72254721Semaste *
73254721Semaste * INPUT
74254721Semaste *
75254721Semaste *   fileproc
76254721Semaste *     Function called with each file as an argument.
77254721Semaste *
78254721Semaste *   filesdoneproc
79254721Semaste *     Function called after all the files in a directory have been processed,
80254721Semaste *     before subdirectories have been processed.
81254721Semaste *
82269024Semaste *   direntproc
83269024Semaste *     Function called immediately upon entering a directory, before processing
84269024Semaste *     any files or subdirectories.
85269024Semaste *
86269024Semaste *   dirleaveproc
87254721Semaste *     Function called upon finishing a directory, immediately before leaving
88254721Semaste *     it and returning control to the function processing the parent
89254721Semaste *     directory.
90254721Semaste *
91263363Semaste *   callerdat
92263363Semaste *   	This void * is passed to the functions above.
93263363Semaste *
94263363Semaste *   argc, argv
95263363Semaste *     The files on which to operate, interpreted as command line arguments.
96263363Semaste *     In the special case of no arguments, defaults to operating on the
97269024Semaste *     current directory (`.').
98269024Semaste *
99269024Semaste *   local
100269024Semaste *
101269024Semaste *   which
102263363Semaste *     specifies the kind of recursion.  There are several cases:
103263363Semaste *
104263363Semaste *       1.  W_LOCAL is not set but W_REPOS or W_ATTIC is.  The current
105263363Semaste *       directory when we are called must be the repository and
106263363Semaste *       recursion proceeds according to what exists in the repository.
107263363Semaste *
108263363Semaste *       2a.  W_LOCAL is set but W_REPOS and W_ATTIC are not.  The
109254721Semaste *       current directory when we are called must be the working
110254721Semaste *       directory.  Recursion proceeds according to what exists in the
111254721Semaste *       working directory, never (I think) consulting any part of the
112254721Semaste *       repository which does not correspond to the working directory
113269024Semaste *       ("correspond" == Name_Repository).
114269024Semaste *
115269024Semaste *       2b.  W_LOCAL is set and so is W_REPOS or W_ATTIC.  This is the
116269024Semaste *       weird one.  The current directory when we are called must be
117254721Semaste *       the working directory.  We recurse through working directories,
118254721Semaste *       but we recurse into a directory if it is exists in the working
119254721Semaste *       directory *or* it exists in the repository.  If a directory
120254721Semaste *       does not exist in the working directory, the direntproc must
121254721Semaste *       either tell us to skip it (R_SKIP_ALL), or must create it (I
122254721Semaste *       think those are the only two cases).
123254721Semaste *
124254721Semaste *   aflag
125254721Semaste *   locktype
126254721Semaste *   update_preload
127254721Semaste *   dosrcs
128254721Semaste *
129254721Semaste *   repository_in
130254721Semaste *     keeps track of the repository string.  This is only for the remote mode,
131254721Semaste *     specifically, r* commands (rtag, rdiff, co, ...) where xgetcwd() was used
132254721Semaste *     to locate the repository.  Things would break when xgetcwd() was used
133254721Semaste *     with a symlinked repository because xgetcwd() would return the true path
134254721Semaste *     and in some cases this would cause the path to be printed as other than
135254721Semaste *     the user specified in error messages and in other cases some of CVS's
136254721Semaste *     security assertions would fail.
137254721Semaste *
138254721Semaste * GLOBALS
139254721Semaste *   ???
140254721Semaste *
141254721Semaste * OUTPUT
142254721Semaste *
143254721Semaste *  callerdat can be modified by the FILEPROC, FILESDONEPROC, DIRENTPROC, and
144254721Semaste *  DIRLEAVEPROC.
145254721Semaste *
146254721Semaste * RETURNS
147254721Semaste *   A count of errors counted by walking the argument list with
148254721Semaste *   unroll_files_proc() and do_recursion().
149254721Semaste *
150254721Semaste * ERRORS
151254721Semaste *   Fatal errors occur:
152254721Semaste *     1.  when there were no arguments and the current directory
153254721Semaste *         does not contain CVS metadata.
154254721Semaste *     2.  when all but the last path element from an argument from ARGV cannot
155254721Semaste *         be found to be a local directory.
156254721Semaste */
157269024Semasteint
158254721Semastestart_recursion (FILEPROC fileproc, FILESDONEPROC filesdoneproc,
159254721Semaste                 DIRENTPROC direntproc, DIRLEAVEPROC dirleaveproc,
160254721Semaste                 void *callerdat, int argc, char **argv, int local,
161254721Semaste                 int which, int aflag, int locktype,
162254721Semaste                 char *update_preload, int dosrcs, char *repository_in)
163254721Semaste{
164254721Semaste    int i, err = 0;
165254721Semaste#ifdef CLIENT_SUPPORT
166254721Semaste    List *args_to_send_when_finished = NULL;
167254721Semaste#endif
168254721Semaste    List *files_by_dir = NULL;
169254721Semaste    struct recursion_frame frame;
170254721Semaste
171269024Semaste#ifdef HAVE_PRINTF_PTR
172269024Semaste    TRACE ( TRACE_FLOW,
173269024Semaste	    "start_recursion ( fileproc=%p, filesdoneproc=%p,\n"
174269024Semaste       "                       direntproc=%p, dirleavproc=%p,\n"
175269024Semaste       "                       callerdat=%p, argc=%d, argv=%p,\n"
176269024Semaste       "                       local=%d, which=%d, aflag=%d,\n"
177269024Semaste       "                       locktype=%d, update_preload=%s\n"
178269024Semaste       "                       dosrcs=%d, repository_in=%s )",
179269024Semaste	       (void *) fileproc, (void *) filesdoneproc,
180269024Semaste	       (void *) direntproc, (void *) dirleaveproc,
181269024Semaste	       (void *) callerdat, argc, (void *) argv,
182269024Semaste	       local, which, aflag, locktype,
183254721Semaste	       update_preload ? update_preload : "(null)", dosrcs,
184254721Semaste	       repository_in ? repository_in : "(null)");
185254721Semaste#else
186254721Semaste    TRACE ( TRACE_FLOW,
187269024Semaste	    "start_recursion ( fileproc=%lx, filesdoneproc=%lx,\n"
188269024Semaste       "                       direntproc=%lx, dirleavproc=%lx,\n"
189269024Semaste       "                       callerdat=%lx, argc=%d, argv=%lx,\n"
190269024Semaste       "                       local=%d, which=%d, aflag=%d,\n"
191269024Semaste       "                       locktype=%d, update_preload=%s\n"
192269024Semaste       "                       dosrcs=%d, repository_in=%s )",
193269024Semaste	       (unsigned long) fileproc, (unsigned long) filesdoneproc,
194269024Semaste	       (unsigned long) direntproc, (unsigned long) dirleaveproc,
195269024Semaste	       (unsigned long) callerdat, argc, (unsigned long) argv,
196254721Semaste	       local, which, aflag, locktype,
197254721Semaste	       update_preload ? update_preload : "(null)", dosrcs,
198254721Semaste	       repository_in ? repository_in : "(null)");
199254721Semaste#endif
200254721Semaste
201254721Semaste    frame.fileproc = fileproc;
202254721Semaste    frame.filesdoneproc = filesdoneproc;
203254721Semaste    frame.direntproc = direntproc;
204254721Semaste    frame.dirleaveproc = dirleaveproc;
205254721Semaste    frame.callerdat = callerdat;
206254721Semaste    frame.flags = local ? R_SKIP_DIRS : R_PROCESS;
207254721Semaste    frame.which = which;
208254721Semaste    frame.aflag = aflag;
209269024Semaste    frame.locktype = locktype;
210254721Semaste    frame.dosrcs = dosrcs;
211254721Semaste
212254721Semaste    /* If our repository_in has a trailing "/.", remove it before storing it
213254721Semaste     * for do_recursion().
214254721Semaste     *
215254721Semaste     * FIXME: This is somewhat of a hack in the sense that many of our callers
216254721Semaste     * painstakingly compute and add the trailing '.' we now remove.
217254721Semaste     */
218254721Semaste    while (repository_in && strlen (repository_in) >= 2
219254721Semaste           && repository_in[strlen (repository_in) - 2] == '/'
220254721Semaste           && repository_in[strlen (repository_in) - 1] == '.')
221263363Semaste    {
222263363Semaste	/* Beware the case where the string is exactly "/." or "//.".
223263363Semaste	 * Paths with a leading "//" are special on some early UNIXes.
224254721Semaste	 */
225263363Semaste	if (strlen (repository_in) == 2 || strlen (repository_in) == 3)
226254721Semaste	    repository_in[strlen (repository_in) - 1] = '\0';
227254721Semaste	else
228254721Semaste	    repository_in[strlen (repository_in) - 2] = '\0';
229254721Semaste    }
230254721Semaste    frame.repository = repository_in;
231269024Semaste
232254721Semaste    expand_wild (argc, argv, &argc, &argv);
233254721Semaste
234254721Semaste    if (update_preload == NULL)
235254721Semaste	update_dir = xstrdup ("");
236254721Semaste    else
237254721Semaste	update_dir = xstrdup (update_preload);
238254721Semaste
239254721Semaste    /* clean up from any previous calls to start_recursion */
240254721Semaste    if (repository)
241254721Semaste    {
242254721Semaste	free (repository);
243254721Semaste	repository = NULL;
244254721Semaste    }
245254721Semaste    if (filelist)
246254721Semaste	dellist (&filelist); /* FIXME-krp: no longer correct. */
247254721Semaste    if (dirlist)
248254721Semaste	dellist (&dirlist);
249254721Semaste
250254721Semaste#ifdef SERVER_SUPPORT
251254721Semaste    if (server_active)
252254721Semaste    {
253254721Semaste	for (i = 0; i < argc; ++i)
254254721Semaste	    server_pathname_check (argv[i]);
255254721Semaste    }
256254721Semaste#endif
257254721Semaste
258254721Semaste    if (argc == 0)
259254721Semaste    {
260254721Semaste	int just_subdirs = (which & W_LOCAL) && !isdir (CVSADM);
261254721Semaste
262254721Semaste#ifdef CLIENT_SUPPORT
263254721Semaste	if (!just_subdirs
264254721Semaste	    && CVSroot_cmdline == NULL
265254721Semaste	    && current_parsed_root->isremote)
266254721Semaste	{
267254721Semaste	    cvsroot_t *root = Name_Root (NULL, update_dir);
268254721Semaste	    if (root)
269254721Semaste	    {
270254721Semaste		if (strcmp (root->original, original_parsed_root->original))
271254721Semaste		    /* We're skipping this directory because it is for
272254721Semaste		     * a different root.  Therefore, we just want to
273263367Semaste		     * do the subdirectories only.  Processing files would
274263367Semaste		     * cause a working directory from one repository to be
275263367Semaste		     * processed against a different repository, which could
276263367Semaste		     * cause all kinds of spurious conflicts and such.
277263367Semaste		     *
278254721Semaste		     * Question: what about the case of "cvs update foo"
279254721Semaste		     * where we process foo/bar and not foo itself?  That
280263363Semaste		     * seems to be handled somewhere (else) but why should
281254721Semaste		     * it be a separate case?  Needs investigation...  */
282254721Semaste		    just_subdirs = 1;
283263363Semaste	    }
284263363Semaste	}
285263363Semaste#endif
286254721Semaste
287254721Semaste	/*
288254721Semaste	 * There were no arguments, so we'll probably just recurse. The
289254721Semaste	 * exception to the rule is when we are called from a directory
290263367Semaste	 * without any CVS administration files.  That has always meant to
291263367Semaste	 * process each of the sub-directories, so we pretend like we were
292263367Semaste	 * called with the list of sub-dirs of the current dir as args
293263367Semaste	 */
294263367Semaste	if (just_subdirs)
295263367Semaste	{
296263367Semaste	    dirlist = Find_Directories (NULL, W_LOCAL, NULL);
297263367Semaste	    /* If there are no sub-directories, there is a certain logic in
298263367Semaste	       favor of doing nothing, but in fact probably the user is just
299254721Semaste	       confused about what directory they are in, or whether they
300254721Semaste	       cvs add'd a new directory.  In the case of at least one
301254721Semaste	       sub-directory, at least when we recurse into them we
302254721Semaste	       notice (hopefully) whether they are under CVS control.  */
303254721Semaste	    if (list_isempty (dirlist))
304254721Semaste	    {
305254721Semaste		if (update_dir[0] == '\0')
306254721Semaste		    error (0, 0, "in directory .:");
307254721Semaste		else
308254721Semaste		    error (0, 0, "in directory %s:", update_dir);
309269024Semaste		error (1, 0,
310269024Semaste		       "there is no version here; run '%s checkout' first",
311269024Semaste		       program_name);
312269024Semaste	    }
313254721Semaste#ifdef CLIENT_SUPPORT
314254721Semaste	    else if (current_parsed_root->isremote && server_started)
315254721Semaste	    {
316254721Semaste		/* In the the case "cvs update foo bar baz", a call to
317263363Semaste		   send_file_names in update.c will have sent the
318263363Semaste		   appropriate "Argument" commands to the server.  In
319263363Semaste		   this case, that won't have happened, so we need to
320263363Semaste		   do it here.  While this example uses "update", this
321263363Semaste		   generalizes to other commands.  */
322263363Semaste
323263363Semaste		/* This is the same call to Find_Directories as above.
324263363Semaste                   FIXME: perhaps it would be better to write a
325263363Semaste                   function that duplicates a list. */
326263363Semaste		args_to_send_when_finished = Find_Directories (NULL,
327263363Semaste							       W_LOCAL,
328263367Semaste							       NULL);
329263363Semaste	    }
330263363Semaste#endif
331263363Semaste	}
332263363Semaste	else
333263363Semaste	    addlist (&dirlist, ".");
334263363Semaste
335263363Semaste	goto do_the_work;
336263363Semaste    }
337263363Semaste
338263363Semaste
339263363Semaste    /*
340263363Semaste     * There were arguments, so we have to handle them by hand. To do
341263363Semaste     * that, we set up the filelist and dirlist with the arguments and
342263363Semaste     * call do_recursion.  do_recursion recognizes the fact that the
343263363Semaste     * lists are non-null when it starts and doesn't update them.
344263363Semaste     *
345263363Semaste     * explicitly named directories are stored in dirlist.
346263363Semaste     * explicitly named files are stored in filelist.
347263363Semaste     * other possibility is named entities whicha are not currently in
348263363Semaste     * the working directory.
349263363Semaste     */
350263363Semaste
351263363Semaste    for (i = 0; i < argc; i++)
352263363Semaste    {
353263367Semaste	/* if this argument is a directory, then add it to the list of
354263363Semaste	   directories. */
355263363Semaste
356263363Semaste	if (!wrap_name_has (argv[i], WRAP_TOCVS) && isdir (argv[i]))
357263363Semaste	{
358263363Semaste	    strip_trailing_slashes (argv[i]);
359263363Semaste	    addlist (&dirlist, argv[i]);
360263363Semaste	}
361263363Semaste	else
362263363Semaste	{
363263363Semaste	    /* otherwise, split argument into directory and component names. */
364254721Semaste	    char *dir;
365254721Semaste	    char *comp;
366254721Semaste	    char *file_to_try;
367254721Semaste
368269024Semaste	    /* Now break out argv[i] into directory part (DIR) and file part
369254721Semaste	     * (COMP).  DIR and COMP will each point to a newly malloc'd
370269024Semaste	     * string.
371269024Semaste	     */
372254721Semaste	    dir = xstrdup (argv[i]);
373269024Semaste	    /* It's okay to cast out last_component's const below since we know
374269024Semaste	     * we just allocated dir here and have control of it.
375269024Semaste	     */
376269024Semaste	    comp = (char *)last_component (dir);
377269024Semaste	    if (comp == dir)
378269024Semaste	    {
379269024Semaste		/* no dir component.  What we have is an implied "./" */
380269024Semaste		dir = xstrdup(".");
381269024Semaste	    }
382269024Semaste	    else
383269024Semaste	    {
384269024Semaste		comp[-1] = '\0';
385269024Semaste		comp = xstrdup (comp);
386254721Semaste	    }
387254721Semaste
388254721Semaste	    /* if this argument exists as a file in the current
389254721Semaste	       working directory tree, then add it to the files list.  */
390254721Semaste
391254721Semaste	    if (!(which & W_LOCAL))
392254721Semaste	    {
393254721Semaste		/* If doing rtag, we've done a chdir to the repository. */
394254721Semaste		file_to_try = Xasprintf ("%s%s", argv[i], RCSEXT);
395254721Semaste	    }
396254721Semaste	    else
397254721Semaste		file_to_try = xstrdup (argv[i]);
398254721Semaste
399254721Semaste	    if (isfile (file_to_try))
400254721Semaste		addfile (&files_by_dir, dir, comp);
401254721Semaste	    else if (isdir (dir))
402254721Semaste	    {
403254721Semaste		if ((which & W_LOCAL) && isdir (CVSADM) &&
404254721Semaste		    !current_parsed_root->isremote)
405254721Semaste		{
406254721Semaste		    /* otherwise, look for it in the repository. */
407254721Semaste		    char *tmp_update_dir;
408254721Semaste		    char *repos;
409254721Semaste		    char *reposfile;
410254721Semaste
411254721Semaste		    tmp_update_dir = xmalloc (strlen (update_dir)
412254721Semaste					      + strlen (dir)
413254721Semaste					      + 5);
414254721Semaste		    strcpy (tmp_update_dir, update_dir);
415254721Semaste
416254721Semaste		    if (*tmp_update_dir != '\0')
417254721Semaste			strcat (tmp_update_dir, "/");
418254721Semaste
419254721Semaste		    strcat (tmp_update_dir, dir);
420254721Semaste
421254721Semaste		    /* look for it in the repository. */
422254721Semaste		    repos = Name_Repository (dir, tmp_update_dir);
423254721Semaste		    reposfile = Xasprintf ("%s/%s", repos, comp);
424254721Semaste		    free (repos);
425254721Semaste
426254721Semaste		    if (!wrap_name_has (comp, WRAP_TOCVS) && isdir (reposfile))
427254721Semaste			addlist (&dirlist, argv[i]);
428254721Semaste		    else
429254721Semaste			addfile (&files_by_dir, dir, comp);
430254721Semaste
431254721Semaste		    free (tmp_update_dir);
432254721Semaste		    free (reposfile);
433254721Semaste		}
434254721Semaste		else
435254721Semaste		    addfile (&files_by_dir, dir, comp);
436254721Semaste	    }
437254721Semaste	    else
438254721Semaste		error (1, 0, "no such directory `%s'", dir);
439254721Semaste
440254721Semaste	    free (file_to_try);
441254721Semaste	    free (dir);
442254721Semaste	    free (comp);
443254721Semaste	}
444254721Semaste    }
445254721Semaste
446254721Semaste    /* At this point we have looped over all named arguments and built
447254721Semaste       a coupla lists.  Now we unroll the lists, setting up and
448254721Semaste       calling do_recursion. */
449254721Semaste
450254721Semaste    err += walklist (files_by_dir, unroll_files_proc, (void *) &frame);
451254721Semaste    dellist(&files_by_dir);
452254721Semaste
453254721Semaste    /* then do_recursion on the dirlist. */
454254721Semaste    if (dirlist != NULL)
455254721Semaste    {
456254721Semaste    do_the_work:
457254721Semaste	err += do_recursion (&frame);
458254721Semaste    }
459254721Semaste
460254721Semaste    /* Free the data which expand_wild allocated.  */
461254721Semaste    free_names (&argc, argv);
462254721Semaste
463254721Semaste    free (update_dir);
464254721Semaste    update_dir = NULL;
465254721Semaste
466254721Semaste#ifdef CLIENT_SUPPORT
467254721Semaste    if (args_to_send_when_finished != NULL)
468254721Semaste    {
469254721Semaste	/* FIXME (njc): in the multiroot case, we don't want to send
470254721Semaste	   argument commands for those top-level directories which do
471254721Semaste	   not contain any subdirectories which have files checked out
472254721Semaste	   from current_parsed_root.  If we do, and two repositories
473254721Semaste	   have a module with the same name, nasty things could happen.
474254721Semaste
475254721Semaste	   This is hard.  Perhaps we should send the Argument commands
476254721Semaste	   later in this procedure, after we've had a chance to notice
477254721Semaste	   which directores we're using (after do_recursion has been
478254721Semaste	   called once).  This means a _lot_ of rewriting, however.
479254721Semaste
480254721Semaste	   What we need to do for that to happen is descend the tree
481254721Semaste	   and construct a list of directories which are checked out
482254721Semaste	   from current_cvsroot.  Now, we eliminate from the list all
483254721Semaste	   of those directories which are immediate subdirectories of
484254721Semaste	   another directory in the list.  To say that the opposite
485254721Semaste	   way, we keep the directories which are not immediate
486254721Semaste	   subdirectories of any other in the list.  Here's a picture:
487254721Semaste
488254721Semaste			      a
489254721Semaste			     / \
490254721Semaste			    B   C
491254721Semaste			   / \
492254721Semaste			  D   e
493254721Semaste			     / \
494254721Semaste			    F   G
495254721Semaste			       / \
496254721Semaste			      H   I
497254721Semaste
498254721Semaste	   The node in capitals are those directories which are
499254721Semaste	   checked out from current_cvsroot.  We want the list to
500254721Semaste	   contain B, C, F, and G.  D, H, and I are not included,
501254721Semaste	   because their parents are also checked out from
502254721Semaste	   current_cvsroot.
503254721Semaste
504254721Semaste	   The algorithm should be:
505254721Semaste
506254721Semaste	   1) construct a tree of all directory names where each
507254721Semaste	   element contains a directory name and a flag which notes if
508254721Semaste	   that directory is checked out from current_cvsroot
509254721Semaste
510254721Semaste			      a0
511254721Semaste			     / \
512254721Semaste			    B1  C1
513254721Semaste			   / \
514254721Semaste			  D1  e0
515254721Semaste			     / \
516254721Semaste			    F1  G1
517254721Semaste			       / \
518254721Semaste			      H1  I1
519254721Semaste
520254721Semaste	   2) Recursively descend the tree.  For each node, recurse
521254721Semaste	   before processing the node.  If the flag is zero, do
522254721Semaste	   nothing.  If the flag is 1, check the node's parent.  If
523254721Semaste	   the parent's flag is one, change the current entry's flag
524254721Semaste	   to zero.
525254721Semaste
526254721Semaste			      a0
527254721Semaste			     / \
528254721Semaste			    B1  C1
529254721Semaste			   / \
530254721Semaste			  D0  e0
531254721Semaste			     / \
532254721Semaste			    F1  G1
533254721Semaste			       / \
534254721Semaste			      H0  I0
535254721Semaste
536254721Semaste	   3) Walk the tree and spit out "Argument" commands to tell
537254721Semaste	   the server which directories to munge.
538254721Semaste
539254721Semaste	   Yuck.  It's not clear this is worth spending time on, since
540254721Semaste	   we might want to disable cvs commands entirely from
541254721Semaste	   directories that do not have CVSADM files...
542254721Semaste
543254721Semaste	   Anyways, the solution as it stands has modified server.c
544254721Semaste	   (dirswitch) to create admin files [via server.c
545254721Semaste	   (create_adm_p)] in all path elements for a client's
546254721Semaste	   "Directory xxx" command, which forces the server to descend
547254721Semaste	   and serve the files there.  client.c (send_file_names) has
548254721Semaste	   also been modified to send only those arguments which are
549254721Semaste	   appropriate to current_parsed_root.
550254721Semaste
551254721Semaste	*/
552254721Semaste
553254721Semaste	/* Construct a fake argc/argv pair. */
554254721Semaste
555254721Semaste	int our_argc = 0, i;
556254721Semaste	char **our_argv = NULL;
557254721Semaste
558254721Semaste	if (! list_isempty (args_to_send_when_finished))
559254721Semaste	{
560254721Semaste	    Node *head, *p;
561254721Semaste
562254721Semaste	    head = args_to_send_when_finished->list;
563254721Semaste
564254721Semaste	    /* count the number of nodes */
565254721Semaste	    i = 0;
566263363Semaste	    for (p = head->next; p != head; p = p->next)
567263363Semaste		i++;
568263363Semaste	    our_argc = i;
569263363Semaste
570263363Semaste	    /* create the argument vector */
571254721Semaste	    our_argv = xmalloc (sizeof (char *) * our_argc);
572254721Semaste
573254721Semaste	    /* populate it */
574254721Semaste	    i = 0;
575254721Semaste	    for (p = head->next; p != head; p = p->next)
576254721Semaste		our_argv[i++] = xstrdup (p->key);
577254721Semaste	}
578254721Semaste
579263363Semaste	/* We don't want to expand widcards, since we've just created
580254721Semaste	   a list of directories directly from the filesystem. */
581254721Semaste	send_file_names (our_argc, our_argv, 0);
582254721Semaste
583254721Semaste	/* Free our argc/argv. */
584254721Semaste	if (our_argv != NULL)
585254721Semaste	{
586254721Semaste	    for (i = 0; i < our_argc; i++)
587254721Semaste		free (our_argv[i]);
588254721Semaste	    free (our_argv);
589254721Semaste	}
590254721Semaste
591254721Semaste	dellist (&args_to_send_when_finished);
592254721Semaste    }
593254721Semaste#endif
594254721Semaste
595254721Semaste    return err;
596254721Semaste}
597254721Semaste
598254721Semaste
599254721Semaste
600254721Semaste/*
601254721Semaste * Implement the recursive policies on the local directory.  This may be
602254721Semaste * called directly, or may be called by start_recursion.
603254721Semaste */
604254721Semastestatic int
605254721Semastedo_recursion (struct recursion_frame *frame)
606254721Semaste{
607254721Semaste    int err = 0;
608254721Semaste    int dodoneproc = 1;
609254721Semaste    char *srepository = NULL;
610254721Semaste    List *entries = NULL;
611254721Semaste    int locktype;
612254721Semaste    bool process_this_directory = true;
613254721Semaste
614254721Semaste#ifdef HAVE_PRINT_PTR
615254721Semaste    TRACE (TRACE_FLOW, "do_recursion ( frame=%p )", (void *) frame);
616254721Semaste#else
617254721Semaste    TRACE (TRACE_FLOW, "do_recursion ( frame=%lx )", (unsigned long) frame);
618254721Semaste#endif
619254721Semaste
620254721Semaste    /* do nothing if told */
621254721Semaste    if (frame->flags == R_SKIP_ALL)
622254721Semaste	return 0;
623254721Semaste
624254721Semaste    locktype = (nolock || noexec) ? CVS_LOCK_NONE : frame->locktype;
625254721Semaste
626254721Semaste    /* The fact that locks are not active here is what makes us fail to have
627254721Semaste       the
628254721Semaste
629254721Semaste           If someone commits some changes in one cvs command,
630254721Semaste	   then an update by someone else will either get all the
631254721Semaste	   changes, or none of them.
632254721Semaste
633254721Semaste       property (see node Concurrency in cvs.texinfo).
634254721Semaste
635254721Semaste       The most straightforward fix would just to readlock the whole
636254721Semaste       tree before starting an update, but that means that if a commit
637254721Semaste       gets blocked on a big update, it might need to wait a *long*
638254721Semaste       time.
639254721Semaste
640254721Semaste       A more adequate fix would be a two-pass design for update,
641254721Semaste       checkout, etc.  The first pass would go through the repository,
642254721Semaste       with the whole tree readlocked, noting what versions of each
643254721Semaste       file we want to get.  The second pass would release all locks
644254721Semaste       (except perhaps short-term locks on one file at a
645254721Semaste       time--although I think RCS already deals with this) and
646254721Semaste       actually get the files, specifying the particular versions it wants.
647254721Semaste
648254721Semaste       This could be sped up by separating out the data needed for the
649254721Semaste       first pass into a separate file(s)--for example a file
650254721Semaste       attribute for each file whose value contains the head revision
651254721Semaste       for each branch.  The structure should be designed so that
652254721Semaste       commit can relatively quickly update the information for a
653254721Semaste       single file or a handful of files (file attributes, as
654254721Semaste       implemented in Jan 96, are probably acceptable; improvements
655254721Semaste       would be possible such as branch attributes which are in
656254721Semaste       separate files for each branch).  */
657254721Semaste
658254721Semaste#if defined(SERVER_SUPPORT) && defined(SERVER_FLOWCONTROL)
659254721Semaste    /*
660254721Semaste     * Now would be a good time to check to see if we need to stop
661254721Semaste     * generating data, to give the buffers a chance to drain to the
662254721Semaste     * remote client.  We should not have locks active at this point,
663254721Semaste     * but if there are writelocks around, we cannot pause here.  */
664254721Semaste    if (server_active && locktype != CVS_LOCK_WRITE)
665254721Semaste	server_pause_check();
666254721Semaste#endif
667254721Semaste
668254721Semaste    /* Check the value in CVSADM_ROOT and see if it's in the list.  If
669254721Semaste       not, add it to our lists of CVS/Root directories and do not
670254721Semaste       process the files in this directory.  Otherwise, continue as
671254721Semaste       usual.  THIS_ROOT might be NULL if we're doing an initial
672254721Semaste       checkout -- check before using it.  The default should be that
673254721Semaste       we process a directory's contents and only skip those contents
674254721Semaste       if a CVS/Root file exists.
675254721Semaste
676254721Semaste       If we're running the server, we want to process all
677254721Semaste       directories, since we're guaranteed to have only one CVSROOT --
678254721Semaste       our own.  */
679263363Semaste
680254721Semaste    /* If -d was specified, it should override CVS/Root.
681254721Semaste
682254721Semaste       In the single-repository case, it is long-standing CVS behavior
683254721Semaste       and makes sense - the user might want another access method,
684254721Semaste       another server (which mounts the same repository), &c.
685254721Semaste
686254721Semaste       In the multiple-repository case, -d overrides all CVS/Root
687254721Semaste       files.  That is the only plausible generalization I can
688254721Semaste       think of.  */
689254721Semaste    if (CVSroot_cmdline == NULL && !server_active)
690254721Semaste    {
691254721Semaste	cvsroot_t *this_root = Name_Root (NULL, update_dir);
692254721Semaste	if (this_root != NULL)
693254721Semaste	{
694254721Semaste	    if (findnode (root_directories, this_root->original))
695254721Semaste	    {
696254721Semaste		process_this_directory =
697254721Semaste		    !strcmp (original_parsed_root->original,
698254721Semaste			     this_root->original);
699254721Semaste	    }
700254721Semaste	    else
701254721Semaste	    {
702254721Semaste		/* Add it to our list. */
703254721Semaste
704254721Semaste		Node *n = getnode ();
705254721Semaste		n->type = NT_UNKNOWN;
706254721Semaste		n->key = xstrdup (this_root->original);
707263363Semaste		n->data = this_root;
708263363Semaste
709263363Semaste		if (addnode (root_directories, n))
710263363Semaste		    error (1, 0, "cannot add new CVSROOT %s",
711263363Semaste			   this_root->original);
712263363Semaste
713263363Semaste		process_this_directory = false;
714263363Semaste	    }
715254721Semaste	}
716254721Semaste    }
717254721Semaste
718254721Semaste    /*
719254721Semaste     * Fill in repository with the current repository
720254721Semaste     */
721254721Semaste    if (frame->which & W_LOCAL)
722254721Semaste    {
723254721Semaste	if (isdir (CVSADM))
724254721Semaste	{
725254721Semaste	    repository = Name_Repository (NULL, update_dir);
726254721Semaste	    srepository = repository;		/* remember what to free */
727254721Semaste	}
728254721Semaste	else
729254721Semaste	    repository = NULL;
730254721Semaste    }
731254721Semaste    else
732254721Semaste    {
733254721Semaste	repository = frame->repository;
734254721Semaste	assert (repository != NULL);
735254721Semaste	assert (strstr (repository, "/./") == NULL);
736254721Semaste    }
737254721Semaste
738254721Semaste    fileattr_startdir (repository);
739254721Semaste
740254721Semaste    /*
741254721Semaste     * The filesdoneproc needs to be called for each directory where files
742254721Semaste     * processed, or each directory that is processed by a call where no
743254721Semaste     * directories were passed in.  In fact, the only time we don't want to
744254721Semaste     * call back the filesdoneproc is when we are processing directories that
745254721Semaste     * were passed in on the command line (or in the special case of `.' when
746254721Semaste     * we were called with no args
747254721Semaste     */
748254721Semaste    if (dirlist != NULL && filelist == NULL)
749254721Semaste	dodoneproc = 0;
750254721Semaste
751254721Semaste    processing = "scan";
752254721Semaste    /*
753254721Semaste     * If filelist or dirlist is already set, we don't look again. Otherwise,
754254721Semaste     * find the files and directories
755254721Semaste     */
756254721Semaste    if (filelist == NULL && dirlist == NULL)
757254721Semaste    {
758254721Semaste	/* both lists were NULL, so start from scratch */
759254721Semaste	if (frame->fileproc != NULL && frame->flags != R_SKIP_FILES)
760254721Semaste	{
761254721Semaste	    int lwhich = frame->which;
762254721Semaste
763254721Semaste	    /* be sure to look in the attic if we have sticky tags/date */
764254721Semaste	    if ((lwhich & W_ATTIC) == 0)
765254721Semaste		if (isreadable (CVSADM_TAG))
766254721Semaste		    lwhich |= W_ATTIC;
767254721Semaste
768254721Semaste	    /* In the !(which & W_LOCAL) case, we filled in repository
769254721Semaste	       earlier in the function.  In the (which & W_LOCAL) case,
770254721Semaste	       the Find_Names function is going to look through the
771254721Semaste	       Entries file.  If we do not have a repository, that
772254721Semaste	       does not make sense, so we insist upon having a
773254721Semaste	       repository at this point.  Name_Repository will give a
774254721Semaste	       reasonable error message.  */
775254721Semaste	    if (repository == NULL)
776254721Semaste	    {
777263363Semaste		Name_Repository (NULL, update_dir);
778254721Semaste	        assert (!"Not reached.  Please report this problem to <"
779254721Semaste			PACKAGE_BUGREPORT ">");
780254721Semaste	    }
781254721Semaste	    /* find the files and fill in entries if appropriate */
782254721Semaste	    if (process_this_directory)
783254721Semaste	    {
784254721Semaste		filelist = Find_Names (repository, lwhich, frame->aflag,
785254721Semaste				       &entries);
786254721Semaste		if (filelist == NULL)
787254721Semaste		{
788254721Semaste		    error (0, 0, "skipping directory %s", update_dir);
789254721Semaste		    /* Note that Find_Directories and the filesdoneproc
790254721Semaste		       in particular would do bad things ("? foo.c" in
791254721Semaste		       the case of some filesdoneproc's).  */
792254721Semaste		    goto skip_directory;
793254721Semaste		}
794263363Semaste	    }
795263363Semaste	}
796263363Semaste
797263363Semaste	/* find sub-directories if we will recurse */
798263363Semaste	if (frame->flags != R_SKIP_DIRS)
799263363Semaste	    dirlist = Find_Directories (
800263363Semaste		process_this_directory ? repository : NULL,
801263363Semaste		frame->which, entries);
802263363Semaste    }
803263363Semaste    else
804263363Semaste    {
805254721Semaste	/* something was passed on the command line */
806254721Semaste	if (filelist != NULL && frame->fileproc != NULL)
807254721Semaste	{
808254721Semaste	    /* we will process files, so pre-parse entries */
809254721Semaste	    if (frame->which & W_LOCAL)
810254721Semaste		entries = Entries_Open (frame->aflag, NULL);
811254721Semaste	}
812254721Semaste    }
813254721Semaste
814254721Semaste    processing = "process";
815254721Semaste
816254721Semaste    /* process the files (if any) */
817254721Semaste    if (process_this_directory && filelist != NULL && frame->fileproc)
818254721Semaste    {
819254721Semaste	struct file_info finfo_struct;
820254721Semaste	struct frame_and_file frfile;
821254721Semaste
822254721Semaste	/* Lock the repository, if necessary. */
823254721Semaste	if (repository)
824254721Semaste	{
825254721Semaste	    if (locktype == CVS_LOCK_READ)
826254721Semaste	    {
827254721Semaste		if (Reader_Lock (repository) != 0)
828254721Semaste		    error (1, 0, "read lock failed - giving up");
829254721Semaste	    }
830254721Semaste	    else if (locktype == CVS_LOCK_WRITE)
831254721Semaste		lock_dir_for_write (repository);
832254721Semaste	}
833254721Semaste
834254721Semaste#ifdef CLIENT_SUPPORT
835254721Semaste	/* For the server, we handle notifications in a completely different
836254721Semaste	   place (server_notify).  For local, we can't do them here--we don't
837254721Semaste	   have writelocks in place, and there is no way to get writelocks
838254721Semaste	   here.  */
839254721Semaste	if (current_parsed_root->isremote)
840254721Semaste	    notify_check (repository, update_dir);
841254721Semaste#endif /* CLIENT_SUPPORT */
842254721Semaste
843254721Semaste	finfo_struct.repository = repository;
844254721Semaste	finfo_struct.update_dir = update_dir;
845254721Semaste	finfo_struct.entries = entries;
846254721Semaste	/* do_file_proc will fill in finfo_struct.file.  */
847254721Semaste
848254721Semaste	frfile.finfo = &finfo_struct;
849254721Semaste	frfile.frame = frame;
850254721Semaste
851254721Semaste	/* process the files */
852254721Semaste	err += walklist (filelist, do_file_proc, &frfile);
853254721Semaste
854254721Semaste	/* unlock it */
855263363Semaste	if (/* We only lock the repository above when repository is set */
856263363Semaste	    repository
857263363Semaste	    /* and when asked for a read or write lock. */
858263363Semaste	    && locktype != CVS_LOCK_NONE)
859263363Semaste	    Simple_Lock_Cleanup ();
860263363Semaste
861263363Semaste	/* clean up */
862263363Semaste	dellist (&filelist);
863263363Semaste    }
864263363Semaste
865263363Semaste    processing = "cleanup";
866263363Semaste
867263363Semaste    /* call-back files done proc (if any) */
868263363Semaste    if (process_this_directory && dodoneproc && frame->filesdoneproc != NULL)
869263363Semaste	err = frame->filesdoneproc (frame->callerdat, err, repository,
870263363Semaste				    update_dir[0] ? update_dir : ".",
871263363Semaste				    entries);
872263363Semaste
873263363Semaste skip_directory:
874263363Semaste    fileattr_write ();
875263363Semaste    fileattr_free ();
876263363Semaste
877263363Semaste    /* process the directories (if necessary) */
878263363Semaste    if (dirlist != NULL)
879263363Semaste    {
880263363Semaste	struct frame_and_entries frent;
881269024Semaste
882269024Semaste	frent.frame = frame;
883269024Semaste	frent.entries = entries;
884269024Semaste	err += walklist (dirlist, do_dir_proc, &frent);
885269024Semaste    }
886269024Semaste#if 0
887269024Semaste    else if (frame->dirleaveproc != NULL)
888269024Semaste	err += frame->dirleaveproc (frame->callerdat, ".", err, ".");
889269024Semaste#endif
890269024Semaste    dellist (&dirlist);
891269024Semaste
892269024Semaste    if (entries)
893269024Semaste    {
894269024Semaste	Entries_Close (entries);
895269024Semaste	entries = NULL;
896269024Semaste    }
897269024Semaste
898269024Semaste    /* free the saved copy of the pointer if necessary */
899269024Semaste    if (srepository)
900269024Semaste	free (srepository);
901269024Semaste    repository = NULL;
902269024Semaste
903269024Semaste#ifdef HAVE_PRINT_PTR
904269024Semaste    TRACE (TRACE_FLOW, "Leaving do_recursion (frame=%p)", (void *)frame);
905269024Semaste#else
906269024Semaste    TRACE (TRACE_FLOW, "Leaving do_recursion (frame=%lx)",
907269024Semaste	   (unsigned long)frame);
908269024Semaste#endif
909269024Semaste
910269024Semaste    return err;
911269024Semaste}
912269024Semaste
913269024Semaste
914269024Semaste
915269024Semaste/*
916269024Semaste * Process each of the files in the list with the callback proc
917269024Semaste *
918269024Semaste * NOTES
919269024Semaste *    Fills in FINFO->fullname, and sometimes FINFO->rcs before
920269024Semaste *    calling the callback proc (FRFILE->frame->fileproc), but frees them
921 *    before return.
922 *
923 * OUTPUTS
924 *    Fills in FINFO->file.
925 *
926 * RETURNS
927 *    0 if we were supposed to find an RCS file but couldn't.
928 *    Otherwise, returns the error code returned by the callback function.
929 */
930static int
931do_file_proc (Node *p, void *closure)
932{
933    struct frame_and_file *frfile = closure;
934    struct file_info *finfo = frfile->finfo;
935    int ret;
936    char *tmp;
937
938    finfo->file = p->key;
939    if (finfo->update_dir[0] != '\0')
940	tmp = Xasprintf ("%s/%s", finfo->update_dir, finfo->file);
941    else
942	tmp = xstrdup (finfo->file);
943
944    if (frfile->frame->dosrcs && repository)
945    {
946	finfo->rcs = RCS_parse (finfo->file, repository);
947
948	/* OK, without W_LOCAL the error handling becomes relatively
949	   simple.  The file names came from readdir() on the
950	   repository and so we know any ENOENT is an error
951	   (e.g. symlink pointing to nothing).  Now, the logic could
952	   be simpler - since we got the name from readdir, we could
953	   just be calling RCS_parsercsfile.  */
954	if (finfo->rcs == NULL
955	    && !(frfile->frame->which & W_LOCAL))
956	{
957	    error (0, 0, "could not read RCS file for %s", tmp);
958	    free (tmp);
959	    cvs_flushout ();
960	    return 0;
961	}
962    }
963    else
964        finfo->rcs = NULL;
965    finfo->fullname = tmp;
966    ret = frfile->frame->fileproc (frfile->frame->callerdat, finfo);
967
968    freercsnode (&finfo->rcs);
969    free (tmp);
970
971    /* Allow the user to monitor progress with tail -f.  Doing this once
972       per file should be no big deal, but we don't want the performance
973       hit of flushing on every line like previous versions of CVS.  */
974    cvs_flushout ();
975
976    return ret;
977}
978
979
980
981/*
982 * Process each of the directories in the list (recursing as we go)
983 */
984static int
985do_dir_proc (Node *p, void *closure)
986{
987    struct frame_and_entries *frent = (struct frame_and_entries *) closure;
988    struct recursion_frame *frame = frent->frame;
989    struct recursion_frame xframe;
990    char *dir = p->key;
991    char *newrepos;
992    List *sdirlist;
993    char *srepository;
994    Dtype dir_return = R_PROCESS;
995    int stripped_dot = 0;
996    int err = 0;
997    struct saved_cwd cwd;
998    char *saved_update_dir;
999    bool process_this_directory = true;
1000
1001    if (fncmp (dir, CVSADM) == 0)
1002    {
1003	/* This seems to most often happen when users (beginning users,
1004	   generally), try "cvs ci *" or something similar.  On that
1005	   theory, it is possible that we should just silently skip the
1006	   CVSADM directories, but on the other hand, using a wildcard
1007	   like this isn't necessarily a practice to encourage (it operates
1008	   only on files which exist in the working directory, unlike
1009	   regular CVS recursion).  */
1010
1011	/* FIXME-reentrancy: printed_cvs_msg should be in a "command
1012	   struct" or some such, so that it gets cleared for each new
1013	   command (this is possible using the remote protocol and a
1014	   custom-written client).  The struct recursion_frame is not
1015	   far back enough though, some commands (commit at least)
1016	   will call start_recursion several times.  An alternate solution
1017	   would be to take this whole check and move it to a new function
1018	   validate_arguments or some such that all the commands call
1019	   and which snips the offending directory from the argc,argv
1020	   vector.  */
1021	static int printed_cvs_msg = 0;
1022	if (!printed_cvs_msg)
1023	{
1024	    error (0, 0, "warning: directory %s specified in argument",
1025		   dir);
1026	    error (0, 0, "\
1027but CVS uses %s for its own purposes; skipping %s directory",
1028		   CVSADM, dir);
1029	    printed_cvs_msg = 1;
1030	}
1031	return 0;
1032    }
1033
1034    saved_update_dir = update_dir;
1035    update_dir = xmalloc (strlen (saved_update_dir)
1036			  + strlen (dir)
1037			  + 5);
1038    strcpy (update_dir, saved_update_dir);
1039
1040    /* set up update_dir - skip dots if not at start */
1041    if (strcmp (dir, ".") != 0)
1042    {
1043	if (update_dir[0] != '\0')
1044	{
1045	    (void) strcat (update_dir, "/");
1046	    (void) strcat (update_dir, dir);
1047	}
1048	else
1049	    (void) strcpy (update_dir, dir);
1050
1051	/*
1052	 * Here we need a plausible repository name for the sub-directory. We
1053	 * create one by concatenating the new directory name onto the
1054	 * previous repository name.  The only case where the name should be
1055	 * used is in the case where we are creating a new sub-directory for
1056	 * update -d and in that case the generated name will be correct.
1057	 */
1058	if (repository == NULL)
1059	    newrepos = xstrdup ("");
1060	else
1061	    newrepos = Xasprintf ("%s/%s", repository, dir);
1062    }
1063    else
1064    {
1065	if (update_dir[0] == '\0')
1066	    (void) strcpy (update_dir, dir);
1067
1068	if (repository == NULL)
1069	    newrepos = xstrdup ("");
1070	else
1071	    newrepos = xstrdup (repository);
1072    }
1073
1074    /* Check to see that the CVSADM directory, if it exists, seems to be
1075       well-formed.  It can be missing files if the user hit ^C in the
1076       middle of a previous run.  We want to (a) make this a nonfatal
1077       error, and (b) make sure we print which directory has the
1078       problem.
1079
1080       Do this before the direntproc, so that (1) the direntproc
1081       doesn't have to guess/deduce whether we will skip the directory
1082       (e.g. send_dirent_proc and whether to send the directory), and
1083       (2) so that the warm fuzzy doesn't get printed if we skip the
1084       directory.  */
1085    if (frame->which & W_LOCAL)
1086    {
1087	char *cvsadmdir;
1088
1089	cvsadmdir = xmalloc (strlen (dir)
1090			     + sizeof (CVSADM_REP)
1091			     + sizeof (CVSADM_ENT)
1092			     + 80);
1093
1094	strcpy (cvsadmdir, dir);
1095	strcat (cvsadmdir, "/");
1096	strcat (cvsadmdir, CVSADM);
1097	if (isdir (cvsadmdir))
1098	{
1099	    strcpy (cvsadmdir, dir);
1100	    strcat (cvsadmdir, "/");
1101	    strcat (cvsadmdir, CVSADM_REP);
1102	    if (!isfile (cvsadmdir))
1103	    {
1104		/* Some commands like update may have printed "? foo" but
1105		   if we were planning to recurse, and don't on account of
1106		   CVS/Repository, we want to say why.  */
1107		error (0, 0, "ignoring %s (%s missing)", update_dir,
1108		       CVSADM_REP);
1109		dir_return = R_SKIP_ALL;
1110	    }
1111
1112	    /* Likewise for CVS/Entries.  */
1113	    if (dir_return != R_SKIP_ALL)
1114	    {
1115		strcpy (cvsadmdir, dir);
1116		strcat (cvsadmdir, "/");
1117		strcat (cvsadmdir, CVSADM_ENT);
1118		if (!isfile (cvsadmdir))
1119		{
1120		    /* Some commands like update may have printed "? foo" but
1121		       if we were planning to recurse, and don't on account of
1122		       CVS/Repository, we want to say why.  */
1123		    error (0, 0, "ignoring %s (%s missing)", update_dir,
1124			   CVSADM_ENT);
1125		    dir_return = R_SKIP_ALL;
1126		}
1127	    }
1128	}
1129	free (cvsadmdir);
1130    }
1131
1132    /* Only process this directory if the root matches.  This nearly
1133       duplicates code in do_recursion. */
1134
1135    /* If -d was specified, it should override CVS/Root.
1136
1137       In the single-repository case, it is long-standing CVS behavior
1138       and makes sense - the user might want another access method,
1139       another server (which mounts the same repository), &c.
1140
1141       In the multiple-repository case, -d overrides all CVS/Root
1142       files.  That is the only plausible generalization I can
1143       think of.  */
1144    if (CVSroot_cmdline == NULL && !server_active)
1145    {
1146	cvsroot_t *this_root = Name_Root (dir, update_dir);
1147	if (this_root != NULL)
1148	{
1149	    if (findnode (root_directories, this_root->original))
1150	    {
1151		process_this_directory =
1152		    !strcmp (original_parsed_root->original,
1153			     this_root->original);
1154	    }
1155	    else
1156	    {
1157		/* Add it to our list. */
1158
1159		Node *n = getnode ();
1160		n->type = NT_UNKNOWN;
1161		n->key = xstrdup (this_root->original);
1162		n->data = this_root;
1163
1164		if (addnode (root_directories, n))
1165		    error (1, 0, "cannot add new CVSROOT %s",
1166			   this_root->original);
1167
1168		process_this_directory = false;
1169	    }
1170	}
1171    }
1172
1173    /* call-back dir entry proc (if any) */
1174    if (dir_return == R_SKIP_ALL)
1175	;
1176    else if (frame->direntproc != NULL)
1177    {
1178	/* If we're doing the actual processing, call direntproc.
1179           Otherwise, assume that we need to process this directory
1180           and recurse. FIXME. */
1181
1182	if (process_this_directory)
1183	    dir_return = frame->direntproc (frame->callerdat, dir, newrepos,
1184					    update_dir, frent->entries);
1185	else
1186	    dir_return = R_PROCESS;
1187    }
1188    else
1189    {
1190	/* Generic behavior.  I don't see a reason to make the caller specify
1191	   a direntproc just to get this.  */
1192	if ((frame->which & W_LOCAL) && !isdir (dir))
1193	    dir_return = R_SKIP_ALL;
1194    }
1195
1196    free (newrepos);
1197
1198    /* only process the dir if the return code was 0 */
1199    if (dir_return != R_SKIP_ALL)
1200    {
1201	/* save our current directory and static vars */
1202        if (save_cwd (&cwd))
1203	    error (1, errno, "Failed to save current directory.");
1204	sdirlist = dirlist;
1205	srepository = repository;
1206	dirlist = NULL;
1207
1208	/* cd to the sub-directory */
1209	if (CVS_CHDIR (dir) < 0)
1210	    error (1, errno, "could not chdir to %s", dir);
1211
1212	/* honor the global SKIP_DIRS (a.k.a. local) */
1213	if (frame->flags == R_SKIP_DIRS)
1214	    dir_return = R_SKIP_DIRS;
1215
1216	/* remember if the `.' will be stripped for subsequent dirs */
1217	if (strcmp (update_dir, ".") == 0)
1218	{
1219	    update_dir[0] = '\0';
1220	    stripped_dot = 1;
1221	}
1222
1223	/* make the recursive call */
1224	xframe = *frame;
1225	xframe.flags = dir_return;
1226	/* Keep track of repository, really just for r* commands (rtag, rdiff,
1227	 * co, ...) to tag_check_valid, since all the other commands use
1228	 * CVS/Repository to figure it out per directory.
1229	 */
1230	if (repository)
1231	{
1232	    if (strcmp (dir, ".") == 0)
1233		xframe.repository = xstrdup (repository);
1234	    else
1235		xframe.repository = Xasprintf ("%s/%s", repository, dir);
1236	}
1237	else
1238	    xframe.repository = NULL;
1239	err += do_recursion (&xframe);
1240	if (xframe.repository)
1241	{
1242	    free (xframe.repository);
1243	    xframe.repository = NULL;
1244	}
1245
1246	/* put the `.' back if necessary */
1247	if (stripped_dot)
1248	    (void) strcpy (update_dir, ".");
1249
1250	/* call-back dir leave proc (if any) */
1251	if (process_this_directory && frame->dirleaveproc != NULL)
1252	    err = frame->dirleaveproc (frame->callerdat, dir, err, update_dir,
1253				       frent->entries);
1254
1255	/* get back to where we started and restore state vars */
1256	if (restore_cwd (&cwd))
1257	    error (1, errno, "Failed to restore current directory, `%s'.",
1258	           cwd.name);
1259	free_cwd (&cwd);
1260	dirlist = sdirlist;
1261	repository = srepository;
1262    }
1263
1264    free (update_dir);
1265    update_dir = saved_update_dir;
1266
1267    return err;
1268}
1269
1270/*
1271 * Add a node to a list allocating the list if necessary.
1272 */
1273static void
1274addlist (List **listp, char *key)
1275{
1276    Node *p;
1277
1278    if (*listp == NULL)
1279	*listp = getlist ();
1280    p = getnode ();
1281    p->type = FILES;
1282    p->key = xstrdup (key);
1283    if (addnode (*listp, p) != 0)
1284	freenode (p);
1285}
1286
1287static void
1288addfile (List **listp, char *dir, char *file)
1289{
1290    Node *n;
1291    List *fl;
1292
1293    /* add this dir. */
1294    addlist (listp, dir);
1295
1296    n = findnode (*listp, dir);
1297    if (n == NULL)
1298    {
1299	error (1, 0, "can't find recently added dir node `%s' in start_recursion.",
1300	       dir);
1301    }
1302
1303    n->type = DIRS;
1304    fl = n->data;
1305    addlist (&fl, file);
1306    n->data = fl;
1307    return;
1308}
1309
1310static int
1311unroll_files_proc (Node *p, void *closure)
1312{
1313    Node *n;
1314    struct recursion_frame *frame = (struct recursion_frame *) closure;
1315    int err = 0;
1316    List *save_dirlist;
1317    char *save_update_dir = NULL;
1318    struct saved_cwd cwd;
1319
1320    /* if this dir was also an explicitly named argument, then skip
1321       it.  We'll catch it later when we do dirs. */
1322    n = findnode (dirlist, p->key);
1323    if (n != NULL)
1324	return (0);
1325
1326    /* otherwise, call dorecusion for this list of files. */
1327    filelist = p->data;
1328    p->data = NULL;
1329    save_dirlist = dirlist;
1330    dirlist = NULL;
1331
1332    if (strcmp(p->key, ".") != 0)
1333    {
1334        if (save_cwd (&cwd))
1335	    error (1, errno, "Failed to save current directory.");
1336	if ( CVS_CHDIR (p->key) < 0)
1337	    error (1, errno, "could not chdir to %s", p->key);
1338
1339	save_update_dir = update_dir;
1340	update_dir = xmalloc (strlen (save_update_dir)
1341				  + strlen (p->key)
1342				  + 5);
1343	strcpy (update_dir, save_update_dir);
1344
1345	if (*update_dir != '\0')
1346	    (void) strcat (update_dir, "/");
1347
1348	(void) strcat (update_dir, p->key);
1349    }
1350
1351    err += do_recursion (frame);
1352
1353    if (save_update_dir != NULL)
1354    {
1355	free (update_dir);
1356	update_dir = save_update_dir;
1357
1358	if (restore_cwd (&cwd))
1359	    error (1, errno, "Failed to restore current directory, `%s'.",
1360	           cwd.name);
1361	free_cwd (&cwd);
1362    }
1363
1364    dirlist = save_dirlist;
1365    if (filelist)
1366	dellist (&filelist);
1367    return(err);
1368}
1369/* vim:tabstop=8:shiftwidth=4
1370 */
1371