xref: /openbsd/gnu/usr.bin/cvs/src/recurse.c (revision 133306f0)
1 /*
2  * Copyright (c) 1992, Brian Berliner and Jeff Polk
3  *
4  * You may distribute under the terms of the GNU General Public License as
5  * specified in the README file that comes with the CVS source distribution.
6  *
7  * General recursion handler
8  *
9  */
10 
11 #include "cvs.h"
12 #include "savecwd.h"
13 #include "fileattr.h"
14 #include "edit.h"
15 
16 static int do_dir_proc PROTO((Node * p, void *closure));
17 static int do_file_proc PROTO((Node * p, void *closure));
18 static void addlist PROTO((List ** listp, char *key));
19 static int unroll_files_proc PROTO((Node *p, void *closure));
20 static void addfile PROTO((List **listp, char *dir, char *file));
21 
22 static char *update_dir;
23 static char *repository = NULL;
24 static List *filelist = NULL; /* holds list of files on which to operate */
25 static List *dirlist = NULL; /* holds list of directories on which to operate */
26 
27 struct recursion_frame {
28     FILEPROC fileproc;
29     FILESDONEPROC filesdoneproc;
30     DIRENTPROC direntproc;
31     DIRLEAVEPROC dirleaveproc;
32     void *callerdat;
33     Dtype flags;
34     int which;
35     int aflag;
36     int readlock;
37     int dosrcs;
38 };
39 
40 static int do_recursion PROTO ((struct recursion_frame *frame));
41 
42 /* I am half tempted to shove a struct file_info * into the struct
43    recursion_frame (but then we would need to modify or create a
44    recursion_frame for each file), or shove a struct recursion_frame *
45    into the struct file_info (more tempting, although it isn't completely
46    clear that the struct file_info should contain info about recursion
47    processor internals).  So instead use this struct.  */
48 
49 struct frame_and_file {
50     struct recursion_frame *frame;
51     struct file_info *finfo;
52 };
53 
54 /* Similarly, we need to pass the entries list to do_dir_proc.  */
55 
56 struct frame_and_entries {
57     struct recursion_frame *frame;
58     List *entries;
59 };
60 
61 
62 /* Start a recursive command.
63 
64    Command line arguments (ARGC, ARGV) dictate the directories and
65    files on which we operate.  In the special case of no arguments, we
66    default to ".".  */
67 int
68 start_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc, callerdat,
69 		 argc, argv, local, which, aflag, readlock,
70 		 update_preload, dosrcs)
71     FILEPROC fileproc;
72     FILESDONEPROC filesdoneproc;
73     DIRENTPROC 	direntproc;
74     DIRLEAVEPROC dirleaveproc;
75     void *callerdat;
76 
77     int argc;
78     char **argv;
79     int local;
80 
81     /* This specifies the kind of recursion.  There are several cases:
82 
83        1.  W_LOCAL is not set but W_REPOS or W_ATTIC is.  The current
84        directory when we are called must be the repository and
85        recursion proceeds according to what exists in the repository.
86 
87        2a.  W_LOCAL is set but W_REPOS and W_ATTIC are not.  The
88        current directory when we are called must be the working
89        directory.  Recursion proceeds according to what exists in the
90        working directory, never (I think) consulting any part of the
91        repository which does not correspond to the working directory
92        ("correspond" == Name_Repository).
93 
94        2b.  W_LOCAL is set and so is W_REPOS or W_ATTIC.  This is the
95        weird one.  The current directory when we are called must be
96        the working directory.  We recurse through working directories,
97        but we recurse into a directory if it is exists in the working
98        directory *or* it exists in the repository.  If a directory
99        does not exist in the working directory, the direntproc must
100        either tell us to skip it (R_SKIP_ALL), or must create it (I
101        think those are the only two cases).  */
102     int which;
103 
104     int aflag;
105     int readlock;
106     char *update_preload;
107     int dosrcs;
108 {
109     int i, err = 0;
110 #ifdef CLIENT_SUPPORT
111     List *args_to_send_when_finished = NULL;
112 #endif
113     List *files_by_dir = NULL;
114     struct recursion_frame frame;
115 
116     frame.fileproc = fileproc;
117     frame.filesdoneproc = filesdoneproc;
118     frame.direntproc = direntproc;
119     frame.dirleaveproc = dirleaveproc;
120     frame.callerdat = callerdat;
121     frame.flags = local ? R_SKIP_DIRS : R_PROCESS;
122     frame.which = which;
123     frame.aflag = aflag;
124     frame.readlock = readlock;
125     frame.dosrcs = dosrcs;
126 
127     expand_wild (argc, argv, &argc, &argv);
128 
129     if (update_preload == NULL)
130 	update_dir = xstrdup ("");
131     else
132 	update_dir = xstrdup (update_preload);
133 
134     /* clean up from any previous calls to start_recursion */
135     if (repository)
136     {
137 	free (repository);
138 	repository = (char *) NULL;
139     }
140     if (filelist)
141 	dellist (&filelist); /* FIXME-krp: no longer correct. */
142     if (dirlist)
143 	dellist (&dirlist);
144 
145 #ifdef SERVER_SUPPORT
146     if (server_active)
147     {
148 	for (i = 0; i < argc; ++i)
149 	    server_pathname_check (argv[i]);
150     }
151 #endif
152 
153     if (argc == 0)
154     {
155 	int just_subdirs = (which & W_LOCAL) && !isdir (CVSADM);
156 
157 #ifdef CLIENT_SUPPORT
158 	if (!just_subdirs
159 	    && CVSroot_cmdline == NULL
160 	    && client_active)
161 	{
162 	    char *root = Name_Root (NULL, update_dir);
163 	    if (root && strcmp (root, current_root) != 0)
164 		/* We're skipping this directory because it is for
165 		   a different root.  Therefore, we just want to
166 		   do the subdirectories only.  Processing files would
167 		   cause a working directory from one repository to be
168 		   processed against a different repository, which could
169 		   cause all kinds of spurious conflicts and such.
170 
171 		   Question: what about the case of "cvs update foo"
172 		   where we process foo/bar and not foo itself?  That
173 		   seems to be handled somewhere (else) but why should
174 		   it be a separate case?  Needs investigation...  */
175 		just_subdirs = 1;
176 	}
177 #endif
178 
179 	/*
180 	 * There were no arguments, so we'll probably just recurse. The
181 	 * exception to the rule is when we are called from a directory
182 	 * without any CVS administration files.  That has always meant to
183 	 * process each of the sub-directories, so we pretend like we were
184 	 * called with the list of sub-dirs of the current dir as args
185 	 */
186 	if (just_subdirs)
187 	{
188 	    dirlist = Find_Directories ((char *) NULL, W_LOCAL, (List *) NULL);
189 	    /* If there are no sub-directories, there is a certain logic in
190 	       favor of doing nothing, but in fact probably the user is just
191 	       confused about what directory they are in, or whether they
192 	       cvs add'd a new directory.  In the case of at least one
193 	       sub-directory, at least when we recurse into them we
194 	       notice (hopefully) whether they are under CVS control.  */
195 	    if (list_isempty (dirlist))
196 	    {
197 		if (update_dir[0] == '\0')
198 		    error (0, 0, "in directory .:");
199 		else
200 		    error (0, 0, "in directory %s:", update_dir);
201 		error (1, 0,
202 		       "there is no version here; run '%s checkout' first",
203 		       program_name);
204 	    }
205 #ifdef CLIENT_SUPPORT
206 	    else if (client_active && server_started)
207 	    {
208 		/* In the the case "cvs update foo bar baz", a call to
209 		   send_file_names in update.c will have sent the
210 		   appropriate "Argument" commands to the server.  In
211 		   this case, that won't have happened, so we need to
212 		   do it here.  While this example uses "update", this
213 		   generalizes to other commands.  */
214 
215 		/* This is the same call to Find_Directories as above.
216                    FIXME: perhaps it would be better to write a
217                    function that duplicates a list. */
218 		args_to_send_when_finished = Find_Directories ((char *) NULL,
219 							       W_LOCAL,
220 							       (List *) NULL);
221 	    }
222 #endif
223 	}
224 	else
225 	    addlist (&dirlist, ".");
226 
227 	goto do_the_work;
228     }
229 
230 
231     /*
232      * There were arguments, so we have to handle them by hand. To do
233      * that, we set up the filelist and dirlist with the arguments and
234      * call do_recursion.  do_recursion recognizes the fact that the
235      * lists are non-null when it starts and doesn't update them.
236      *
237      * explicitly named directories are stored in dirlist.
238      * explicitly named files are stored in filelist.
239      * other possibility is named entities whicha are not currently in
240      * the working directory.
241      */
242 
243     for (i = 0; i < argc; i++)
244     {
245 	/* if this argument is a directory, then add it to the list of
246 	   directories. */
247 
248 	if (!wrap_name_has (argv[i], WRAP_TOCVS) && isdir (argv[i]))
249 	    addlist (&dirlist, argv[i]);
250 	else
251 	{
252 	    /* otherwise, split argument into directory and component names. */
253 	    char *dir;
254 	    char *comp;
255 	    char *file_to_try;
256 
257 	    /* Now break out argv[i] into directory part (DIR) and file part (COMP).
258 		   DIR and COMP will each point to a newly malloc'd string.  */
259 	    dir = xstrdup (argv[i]);
260 	    comp = last_component (dir);
261 	    if (comp == dir)
262 	    {
263 		/* no dir component.  What we have is an implied "./" */
264 		dir = xstrdup(".");
265 	    }
266 	    else
267 	    {
268 		char *p = comp;
269 
270 		p[-1] = '\0';
271 		comp = xstrdup (p);
272 	    }
273 
274 	    /* if this argument exists as a file in the current
275 	       working directory tree, then add it to the files list.  */
276 
277 	    if (!(which & W_LOCAL))
278 	    {
279 		/* If doing rtag, we've done a chdir to the repository. */
280 		file_to_try = xmalloc (strlen (argv[i]) + sizeof (RCSEXT) + 5);
281 		sprintf (file_to_try, "%s%s", argv[i], RCSEXT);
282 	    }
283 	    else
284 		file_to_try = xstrdup (argv[i]);
285 
286 	    if (isfile (file_to_try))
287 		addfile (&files_by_dir, dir, comp);
288 	    else if (isdir (dir))
289 	    {
290 		if ((which & W_LOCAL) && isdir (CVSADM)
291 #ifdef CLIENT_SUPPORT
292 		    && !client_active
293 #endif
294 		    )
295 		{
296 		    /* otherwise, look for it in the repository. */
297 		    char *tmp_update_dir;
298 		    char *repos;
299 		    char *reposfile;
300 
301 		    tmp_update_dir = xmalloc (strlen (update_dir)
302 					      + strlen (dir)
303 					      + 5);
304 		    strcpy (tmp_update_dir, update_dir);
305 
306 		    if (*tmp_update_dir != '\0')
307 			(void) strcat (tmp_update_dir, "/");
308 
309 		    (void) strcat (tmp_update_dir, dir);
310 
311 		    /* look for it in the repository. */
312 		    repos = Name_Repository (dir, tmp_update_dir);
313 		    reposfile = xmalloc (strlen (repos)
314 					 + strlen (comp)
315 					 + 5);
316 		    (void) sprintf (reposfile, "%s/%s", repos, comp);
317 		    free (repos);
318 
319 		    if (!wrap_name_has (comp, WRAP_TOCVS) && isdir (reposfile))
320 			addlist (&dirlist, argv[i]);
321 		    else
322 			addfile (&files_by_dir, dir, comp);
323 
324 		    free (tmp_update_dir);
325 		    free (reposfile);
326 		}
327 		else
328 		    addfile (&files_by_dir, dir, comp);
329 	    }
330 	    else
331 		error (1, 0, "no such directory `%s'", dir);
332 
333 	    free (file_to_try);
334 	    free (dir);
335 	    free (comp);
336 	}
337     }
338 
339     /* At this point we have looped over all named arguments and built
340        a coupla lists.  Now we unroll the lists, setting up and
341        calling do_recursion. */
342 
343     err += walklist (files_by_dir, unroll_files_proc, (void *) &frame);
344     dellist(&files_by_dir);
345 
346     /* then do_recursion on the dirlist. */
347     if (dirlist != NULL)
348     {
349     do_the_work:
350 	err += do_recursion (&frame);
351     }
352 
353     /* Free the data which expand_wild allocated.  */
354     free_names (&argc, argv);
355 
356     free (update_dir);
357     update_dir = NULL;
358 
359 #ifdef CLIENT_SUPPORT
360     if (args_to_send_when_finished != NULL)
361     {
362 	/* FIXME (njc): in the multiroot case, we don't want to send
363 	   argument commands for those top-level directories which do
364 	   not contain any subdirectories which have files checked out
365 	   from current_root.  If we do, and two repositories have a
366 	   module with the same name, nasty things could happen.
367 
368 	   This is hard.  Perhaps we should send the Argument commands
369 	   later in this procedure, after we've had a chance to notice
370 	   which directores we're using (after do_recursion has been
371 	   called once).  This means a _lot_ of rewriting, however.
372 
373 	   What we need to do for that to happen is descend the tree
374 	   and construct a list of directories which are checked out
375 	   from current_cvsroot.  Now, we eliminate from the list all
376 	   of those directories which are immediate subdirectories of
377 	   another directory in the list.  To say that the opposite
378 	   way, we keep the directories which are not immediate
379 	   subdirectories of any other in the list.  Here's a picture:
380 
381 			      a
382 			     / \
383 			    B   C
384 			   / \
385 			  D   e
386 			     / \
387 			    F   G
388 			       / \
389 			      H   I
390 
391 	   The node in capitals are those directories which are
392 	   checked out from current_cvsroot.  We want the list to
393 	   contain B, C, F, and G.  D, H, and I are not included,
394 	   because their parents are also checked out from
395 	   current_cvsroot.
396 
397 	   The algorithm should be:
398 
399 	   1) construct a tree of all directory names where each
400 	   element contains a directory name and a flag which notes if
401 	   that directory is checked out from current_cvsroot
402 
403 			      a0
404 			     / \
405 			    B1  C1
406 			   / \
407 			  D1  e0
408 			     / \
409 			    F1  G1
410 			       / \
411 			      H1  I1
412 
413 	   2) Recursively descend the tree.  For each node, recurse
414 	   before processing the node.  If the flag is zero, do
415 	   nothing.  If the flag is 1, check the node's parent.  If
416 	   the parent's flag is one, change the current entry's flag
417 	   to zero.
418 
419 			      a0
420 			     / \
421 			    B1  C1
422 			   / \
423 			  D0  e0
424 			     / \
425 			    F1  G1
426 			       / \
427 			      H0  I0
428 
429 	   3) Walk the tree and spit out "Argument" commands to tell
430 	   the server which directories to munge.
431 
432 	   Yuck.  It's not clear this is worth spending time on, since
433 	   we might want to disable cvs commands entirely from
434 	   directories that do not have CVSADM files...
435 
436 	   Anyways, the solution as it stands has modified server.c
437 	   (dirswitch) to create admin files [via server.c
438 	   (create_adm_p)] in all path elements for a client's
439 	   "Directory xxx" command, which forces the server to descend
440 	   and serve the files there.  client.c (send_file_names) has
441 	   also been modified to send only those arguments which are
442 	   appropriate to current_root.
443 
444 	*/
445 
446 	/* Construct a fake argc/argv pair. */
447 
448 	int our_argc = 0, i;
449 	char **our_argv = NULL;
450 
451 	if (! list_isempty (args_to_send_when_finished))
452 	{
453 	    Node *head, *p;
454 
455 	    head = args_to_send_when_finished->list;
456 
457 	    /* count the number of nodes */
458 	    i = 0;
459 	    for (p = head->next; p != head; p = p->next)
460 		i++;
461 	    our_argc = i;
462 
463 	    /* create the argument vector */
464 	    our_argv = (char **) xmalloc (sizeof (char *) * our_argc);
465 
466 	    /* populate it */
467 	    i = 0;
468 	    for (p = head->next; p != head; p = p->next)
469 		our_argv[i++] = xstrdup (p->key);
470 	}
471 
472 	/* We don't want to expand widcards, since we've just created
473 	   a list of directories directly from the filesystem. */
474 	send_file_names (our_argc, our_argv, 0);
475 
476 	/* Free our argc/argv. */
477 	if (our_argv != NULL)
478 	{
479 	    for (i = 0; i < our_argc; i++)
480 		free (our_argv[i]);
481 	    free (our_argv);
482 	}
483 
484 	dellist (&args_to_send_when_finished);
485     }
486 #endif
487 
488     return (err);
489 }
490 
491 /*
492  * Implement the recursive policies on the local directory.  This may be
493  * called directly, or may be called by start_recursion
494  */
495 static int
496 do_recursion (frame)
497     struct recursion_frame *frame;
498 {
499     int err = 0;
500     int dodoneproc = 1;
501     char *srepository;
502     List *entries = NULL;
503     int should_readlock;
504     int process_this_directory = 1;
505 
506     /* do nothing if told */
507     if (frame->flags == R_SKIP_ALL)
508 	return (0);
509 
510     should_readlock = noexec ? 0 : frame->readlock;
511 
512     /* The fact that locks are not active here is what makes us fail to have
513        the
514 
515            If someone commits some changes in one cvs command,
516 	   then an update by someone else will either get all the
517 	   changes, or none of them.
518 
519        property (see node Concurrency in cvs.texinfo).
520 
521        The most straightforward fix would just to readlock the whole
522        tree before starting an update, but that means that if a commit
523        gets blocked on a big update, it might need to wait a *long*
524        time.
525 
526        A more adequate fix would be a two-pass design for update,
527        checkout, etc.  The first pass would go through the repository,
528        with the whole tree readlocked, noting what versions of each
529        file we want to get.  The second pass would release all locks
530        (except perhaps short-term locks on one file at a
531        time--although I think RCS already deals with this) and
532        actually get the files, specifying the particular versions it wants.
533 
534        This could be sped up by separating out the data needed for the
535        first pass into a separate file(s)--for example a file
536        attribute for each file whose value contains the head revision
537        for each branch.  The structure should be designed so that
538        commit can relatively quickly update the information for a
539        single file or a handful of files (file attributes, as
540        implemented in Jan 96, are probably acceptable; improvements
541        would be possible such as branch attributes which are in
542        separate files for each branch).  */
543 
544 #if defined(SERVER_SUPPORT) && defined(SERVER_FLOWCONTROL)
545     /*
546      * Now would be a good time to check to see if we need to stop
547      * generating data, to give the buffers a chance to drain to the
548      * remote client.  We should not have locks active at this point.
549      */
550     if (server_active
551 	/* If there are writelocks around, we cannot pause here.  */
552 	&& (should_readlock || noexec))
553 	server_pause_check();
554 #endif
555 
556     /* Check the value in CVSADM_ROOT and see if it's in the list.  If
557        not, add it to our lists of CVS/Root directories and do not
558        process the files in this directory.  Otherwise, continue as
559        usual.  THIS_ROOT might be NULL if we're doing an initial
560        checkout -- check before using it.  The default should be that
561        we process a directory's contents and only skip those contents
562        if a CVS/Root file exists.
563 
564        If we're running the server, we want to process all
565        directories, since we're guaranteed to have only one CVSROOT --
566        our own.  */
567 
568     if (
569 	/* If -d was specified, it should override CVS/Root.
570 
571 	   In the single-repository case, it is long-standing CVS behavior
572 	   and makes sense - the user might want another access method,
573 	   another server (which mounts the same repository), &c.
574 
575 	   In the multiple-repository case, -d overrides all CVS/Root
576 	   files.  That is the only plausible generalization I can
577 	   think of.  */
578 	CVSroot_cmdline == NULL
579 
580 #ifdef SERVER_SUPPORT
581 	&& ! server_active
582 #endif
583 	)
584     {
585 	char *this_root = Name_Root ((char *) NULL, update_dir);
586 	if (this_root != NULL)
587 	{
588 	    if (findnode (root_directories, this_root) == NULL)
589 	    {
590 		/* Add it to our list. */
591 
592 		Node *n = getnode ();
593 		n->type = UNKNOWN;
594 		n->key = xstrdup (this_root);
595 
596 		if (addnode (root_directories, n))
597 		    error (1, 0, "cannot add new CVSROOT %s", this_root);
598 
599 	    }
600 
601 	    process_this_directory = (strcmp (current_root, this_root) == 0);
602 
603 	    free (this_root);
604 	}
605     }
606 
607     /*
608      * Fill in repository with the current repository
609      */
610     if (frame->which & W_LOCAL)
611     {
612 	if (isdir (CVSADM))
613 	    repository = Name_Repository ((char *) NULL, update_dir);
614 	else
615 	    repository = NULL;
616     }
617     else
618     {
619 	repository = xgetwd ();
620 	if (repository == NULL)
621 	    error (1, errno, "could not get working directory");
622     }
623     srepository = repository;		/* remember what to free */
624 
625     fileattr_startdir (repository);
626 
627     /*
628      * The filesdoneproc needs to be called for each directory where files
629      * processed, or each directory that is processed by a call where no
630      * directories were passed in.  In fact, the only time we don't want to
631      * call back the filesdoneproc is when we are processing directories that
632      * were passed in on the command line (or in the special case of `.' when
633      * we were called with no args
634      */
635     if (dirlist != NULL && filelist == NULL)
636 	dodoneproc = 0;
637 
638     /*
639      * If filelist or dirlist is already set, we don't look again. Otherwise,
640      * find the files and directories
641      */
642     if (filelist == NULL && dirlist == NULL)
643     {
644 	/* both lists were NULL, so start from scratch */
645 	if (frame->fileproc != NULL && frame->flags != R_SKIP_FILES)
646 	{
647 	    int lwhich = frame->which;
648 
649 	    /* be sure to look in the attic if we have sticky tags/date */
650 	    if ((lwhich & W_ATTIC) == 0)
651 		if (isreadable (CVSADM_TAG))
652 		    lwhich |= W_ATTIC;
653 
654 	    /* In the !(which & W_LOCAL) case, we filled in repository
655 	       earlier in the function.  In the (which & W_LOCAL) case,
656 	       the Find_Names function is going to look through the
657 	       Entries file.  If we do not have a repository, that
658 	       does not make sense, so we insist upon having a
659 	       repository at this point.  Name_Repository will give a
660 	       reasonable error message.  */
661 	    if (repository == NULL)
662 		repository = Name_Repository ((char *) NULL, update_dir);
663 
664 	    /* find the files and fill in entries if appropriate */
665 	    if (process_this_directory)
666 	    {
667 		filelist = Find_Names (repository, lwhich, frame->aflag,
668 				       &entries);
669 		if (filelist == NULL)
670 		{
671 		    error (0, 0, "skipping directory %s", update_dir);
672 		    /* Note that Find_Directories and the filesdoneproc
673 		       in particular would do bad things ("? foo.c" in
674 		       the case of some filesdoneproc's).  */
675 		    goto skip_directory;
676 		}
677 	    }
678 	}
679 
680 	/* find sub-directories if we will recurse */
681 	if (frame->flags != R_SKIP_DIRS)
682 	    dirlist = Find_Directories (
683 		process_this_directory ? repository : NULL,
684 		frame->which, entries);
685     }
686     else
687     {
688 	/* something was passed on the command line */
689 	if (filelist != NULL && frame->fileproc != NULL)
690 	{
691 	    /* we will process files, so pre-parse entries */
692 	    if (frame->which & W_LOCAL)
693 		entries = Entries_Open (frame->aflag, NULL);
694 	}
695     }
696 
697     /* process the files (if any) */
698     if (process_this_directory && filelist != NULL && frame->fileproc)
699     {
700 	struct file_info finfo_struct;
701 	struct frame_and_file frfile;
702 
703 	/* read lock it if necessary */
704 	if (should_readlock && repository && Reader_Lock (repository) != 0)
705 	    error (1, 0, "read lock failed - giving up");
706 
707 #ifdef CLIENT_SUPPORT
708 	/* For the server, we handle notifications in a completely different
709 	   place (server_notify).  For local, we can't do them here--we don't
710 	   have writelocks in place, and there is no way to get writelocks
711 	   here.  */
712 	if (client_active)
713 	    notify_check (repository, update_dir);
714 #endif /* CLIENT_SUPPORT */
715 
716 	finfo_struct.repository = repository;
717 	finfo_struct.update_dir = update_dir;
718 	finfo_struct.entries = entries;
719 	/* do_file_proc will fill in finfo_struct.file.  */
720 
721 	frfile.finfo = &finfo_struct;
722 	frfile.frame = frame;
723 
724 	/* process the files */
725 	err += walklist (filelist, do_file_proc, &frfile);
726 
727 	/* unlock it */
728 	if (should_readlock)
729 	    Lock_Cleanup ();
730 
731 	/* clean up */
732 	dellist (&filelist);
733     }
734 
735     /* call-back files done proc (if any) */
736     if (process_this_directory && dodoneproc && frame->filesdoneproc != NULL)
737 	err = frame->filesdoneproc (frame->callerdat, err, repository,
738 				    update_dir[0] ? update_dir : ".",
739 				    entries);
740 
741  skip_directory:
742     fileattr_write ();
743     fileattr_free ();
744 
745     /* process the directories (if necessary) */
746     if (dirlist != NULL)
747     {
748 	struct frame_and_entries frent;
749 
750 	frent.frame = frame;
751 	frent.entries = entries;
752 	err += walklist (dirlist, do_dir_proc, (void *) &frent);
753     }
754 #if 0
755     else if (frame->dirleaveproc != NULL)
756 	err += frame->dirleaveproc (frame->callerdat, ".", err, ".");
757 #endif
758     dellist (&dirlist);
759 
760     if (entries)
761     {
762 	Entries_Close (entries);
763 	entries = NULL;
764     }
765 
766     /* free the saved copy of the pointer if necessary */
767     if (srepository)
768     {
769 	free (srepository);
770 	repository = (char *) NULL;
771     }
772 
773     return (err);
774 }
775 
776 /*
777  * Process each of the files in the list with the callback proc
778  */
779 static int
780 do_file_proc (p, closure)
781     Node *p;
782     void *closure;
783 {
784     struct frame_and_file *frfile = (struct frame_and_file *)closure;
785     struct file_info *finfo = frfile->finfo;
786     int ret;
787 
788     finfo->file = p->key;
789     finfo->fullname = xmalloc (strlen (finfo->file)
790 			       + strlen (finfo->update_dir)
791 			       + 2);
792     finfo->fullname[0] = '\0';
793     if (finfo->update_dir[0] != '\0')
794     {
795 	strcat (finfo->fullname, finfo->update_dir);
796 	strcat (finfo->fullname, "/");
797     }
798     strcat (finfo->fullname, finfo->file);
799 
800     if (frfile->frame->dosrcs && repository)
801     {
802 	finfo->rcs = RCS_parse (finfo->file, repository);
803 
804 	/* OK, without W_LOCAL the error handling becomes relatively
805 	   simple.  The file names came from readdir() on the
806 	   repository and so we know any ENOENT is an error
807 	   (e.g. symlink pointing to nothing).  Now, the logic could
808 	   be simpler - since we got the name from readdir, we could
809 	   just be calling RCS_parsercsfile.  */
810 	if (finfo->rcs == NULL
811 	    && !(frfile->frame->which & W_LOCAL))
812 	{
813 	    error (0, 0, "could not read RCS file for %s", finfo->fullname);
814 	    free (finfo->fullname);
815 	    cvs_flushout ();
816 	    return 0;
817 	}
818     }
819     else
820         finfo->rcs = (RCSNode *) NULL;
821     ret = frfile->frame->fileproc (frfile->frame->callerdat, finfo);
822 
823     freercsnode(&finfo->rcs);
824     free (finfo->fullname);
825 
826     /* Allow the user to monitor progress with tail -f.  Doing this once
827        per file should be no big deal, but we don't want the performance
828        hit of flushing on every line like previous versions of CVS.  */
829     cvs_flushout ();
830 
831     return (ret);
832 }
833 
834 /*
835  * Process each of the directories in the list (recursing as we go)
836  */
837 static int
838 do_dir_proc (p, closure)
839     Node *p;
840     void *closure;
841 {
842     struct frame_and_entries *frent = (struct frame_and_entries *) closure;
843     struct recursion_frame *frame = frent->frame;
844     struct recursion_frame xframe;
845     char *dir = p->key;
846     char *newrepos;
847     List *sdirlist;
848     char *srepository;
849     Dtype dir_return = R_PROCESS;
850     int stripped_dot = 0;
851     int err = 0;
852     struct saved_cwd cwd;
853     char *saved_update_dir;
854     int process_this_directory = 1;
855 
856     if (fncmp (dir, CVSADM) == 0)
857     {
858 	/* This seems to most often happen when users (beginning users,
859 	   generally), try "cvs ci *" or something similar.  On that
860 	   theory, it is possible that we should just silently skip the
861 	   CVSADM directories, but on the other hand, using a wildcard
862 	   like this isn't necessarily a practice to encourage (it operates
863 	   only on files which exist in the working directory, unlike
864 	   regular CVS recursion).  */
865 
866 	/* FIXME-reentrancy: printed_cvs_msg should be in a "command
867 	   struct" or some such, so that it gets cleared for each new
868 	   command (this is possible using the remote protocol and a
869 	   custom-written client).  The struct recursion_frame is not
870 	   far back enough though, some commands (commit at least)
871 	   will call start_recursion several times.  An alternate solution
872 	   would be to take this whole check and move it to a new function
873 	   validate_arguments or some such that all the commands call
874 	   and which snips the offending directory from the argc,argv
875 	   vector.  */
876 	static int printed_cvs_msg = 0;
877 	if (!printed_cvs_msg)
878 	{
879 	    error (0, 0, "warning: directory %s specified in argument",
880 		   dir);
881 	    error (0, 0, "\
882 but CVS uses %s for its own purposes; skipping %s directory",
883 		   CVSADM, dir);
884 	    printed_cvs_msg = 1;
885 	}
886 	return 0;
887     }
888 
889     saved_update_dir = update_dir;
890     update_dir = xmalloc (strlen (saved_update_dir)
891 			  + strlen (dir)
892 			  + 5);
893     strcpy (update_dir, saved_update_dir);
894 
895     /* set up update_dir - skip dots if not at start */
896     if (strcmp (dir, ".") != 0)
897     {
898 	if (update_dir[0] != '\0')
899 	{
900 	    (void) strcat (update_dir, "/");
901 	    (void) strcat (update_dir, dir);
902 	}
903 	else
904 	    (void) strcpy (update_dir, dir);
905 
906 	/*
907 	 * Here we need a plausible repository name for the sub-directory. We
908 	 * create one by concatenating the new directory name onto the
909 	 * previous repository name.  The only case where the name should be
910 	 * used is in the case where we are creating a new sub-directory for
911 	 * update -d and in that case the generated name will be correct.
912 	 */
913 	if (repository == NULL)
914 	    newrepos = xstrdup ("");
915 	else
916 	{
917 	    newrepos = xmalloc (strlen (repository) + strlen (dir) + 5);
918 	    sprintf (newrepos, "%s/%s", repository, dir);
919 	}
920     }
921     else
922     {
923 	if (update_dir[0] == '\0')
924 	    (void) strcpy (update_dir, dir);
925 
926 	if (repository == NULL)
927 	    newrepos = xstrdup ("");
928 	else
929 	    newrepos = xstrdup (repository);
930     }
931 
932     /* Check to see that the CVSADM directory, if it exists, seems to be
933        well-formed.  It can be missing files if the user hit ^C in the
934        middle of a previous run.  We want to (a) make this a nonfatal
935        error, and (b) make sure we print which directory has the
936        problem.
937 
938        Do this before the direntproc, so that (1) the direntproc
939        doesn't have to guess/deduce whether we will skip the directory
940        (e.g. send_dirent_proc and whether to send the directory), and
941        (2) so that the warm fuzzy doesn't get printed if we skip the
942        directory.  */
943     if (frame->which & W_LOCAL)
944     {
945 	char *cvsadmdir;
946 
947 	cvsadmdir = xmalloc (strlen (dir)
948 			     + sizeof (CVSADM_REP)
949 			     + sizeof (CVSADM_ENT)
950 			     + 80);
951 
952 	strcpy (cvsadmdir, dir);
953 	strcat (cvsadmdir, "/");
954 	strcat (cvsadmdir, CVSADM);
955 	if (isdir (cvsadmdir))
956 	{
957 	    strcpy (cvsadmdir, dir);
958 	    strcat (cvsadmdir, "/");
959 	    strcat (cvsadmdir, CVSADM_REP);
960 	    if (!isfile (cvsadmdir))
961 	    {
962 		/* Some commands like update may have printed "? foo" but
963 		   if we were planning to recurse, and don't on account of
964 		   CVS/Repository, we want to say why.  */
965 		error (0, 0, "ignoring %s (%s missing)", update_dir,
966 		       CVSADM_REP);
967 		dir_return = R_SKIP_ALL;
968 	    }
969 
970 	    /* Likewise for CVS/Entries.  */
971 	    if (dir_return != R_SKIP_ALL)
972 	    {
973 		strcpy (cvsadmdir, dir);
974 		strcat (cvsadmdir, "/");
975 		strcat (cvsadmdir, CVSADM_ENT);
976 		if (!isfile (cvsadmdir))
977 		{
978 		    /* Some commands like update may have printed "? foo" but
979 		       if we were planning to recurse, and don't on account of
980 		       CVS/Repository, we want to say why.  */
981 		    error (0, 0, "ignoring %s (%s missing)", update_dir,
982 			   CVSADM_ENT);
983 		    dir_return = R_SKIP_ALL;
984 		}
985 	    }
986 	}
987 	free (cvsadmdir);
988     }
989 
990     /* Only process this directory if the root matches.  This nearly
991        duplicates code in do_recursion. */
992 
993     if (
994 	/* If -d was specified, it should override CVS/Root.
995 
996 	   In the single-repository case, it is long-standing CVS behavior
997 	   and makes sense - the user might want another access method,
998 	   another server (which mounts the same repository), &c.
999 
1000 	   In the multiple-repository case, -d overrides all CVS/Root
1001 	   files.  That is the only plausible generalization I can
1002 	   think of.  */
1003 	CVSroot_cmdline == NULL
1004 
1005 #ifdef SERVER_SUPPORT
1006 	&& ! server_active
1007 #endif
1008 	)
1009     {
1010 	char *this_root = Name_Root (dir, update_dir);
1011 	if (this_root != NULL)
1012 	{
1013 	    if (findnode (root_directories, this_root) == NULL)
1014 	    {
1015 		/* Add it to our list. */
1016 
1017 		Node *n = getnode ();
1018 		n->type = UNKNOWN;
1019 		n->key = xstrdup (this_root);
1020 
1021 		if (addnode (root_directories, n))
1022 		    error (1, 0, "cannot add new CVSROOT %s", this_root);
1023 
1024 	    }
1025 
1026 	    process_this_directory = (strcmp (current_root, this_root) == 0);
1027 	    free (this_root);
1028 	}
1029     }
1030 
1031     /* call-back dir entry proc (if any) */
1032     if (dir_return == R_SKIP_ALL)
1033 	;
1034     else if (frame->direntproc != NULL)
1035     {
1036 	/* If we're doing the actual processing, call direntproc.
1037            Otherwise, assume that we need to process this directory
1038            and recurse. FIXME. */
1039 
1040 	if (process_this_directory)
1041 	    dir_return = frame->direntproc (frame->callerdat, dir, newrepos,
1042 					    update_dir, frent->entries);
1043 	else
1044 	    dir_return = R_PROCESS;
1045     }
1046     else
1047     {
1048 	/* Generic behavior.  I don't see a reason to make the caller specify
1049 	   a direntproc just to get this.  */
1050 	if ((frame->which & W_LOCAL) && !isdir (dir))
1051 	    dir_return = R_SKIP_ALL;
1052     }
1053 
1054     free (newrepos);
1055 
1056     /* only process the dir if the return code was 0 */
1057     if (dir_return != R_SKIP_ALL)
1058     {
1059 	/* save our current directory and static vars */
1060         if (save_cwd (&cwd))
1061 	    error_exit ();
1062 	sdirlist = dirlist;
1063 	srepository = repository;
1064 	dirlist = NULL;
1065 
1066 	/* cd to the sub-directory */
1067 	if ( CVS_CHDIR (dir) < 0)
1068 	    error (1, errno, "could not chdir to %s", dir);
1069 
1070 	/* honor the global SKIP_DIRS (a.k.a. local) */
1071 	if (frame->flags == R_SKIP_DIRS)
1072 	    dir_return = R_SKIP_DIRS;
1073 
1074 	/* remember if the `.' will be stripped for subsequent dirs */
1075 	if (strcmp (update_dir, ".") == 0)
1076 	{
1077 	    update_dir[0] = '\0';
1078 	    stripped_dot = 1;
1079 	}
1080 
1081 	/* make the recursive call */
1082 	xframe = *frame;
1083 	xframe.flags = dir_return;
1084 	err += do_recursion (&xframe);
1085 
1086 	/* put the `.' back if necessary */
1087 	if (stripped_dot)
1088 	    (void) strcpy (update_dir, ".");
1089 
1090 	/* call-back dir leave proc (if any) */
1091 	if (process_this_directory && frame->dirleaveproc != NULL)
1092 	    err = frame->dirleaveproc (frame->callerdat, dir, err, update_dir,
1093 				       frent->entries);
1094 
1095 	/* get back to where we started and restore state vars */
1096 	if (restore_cwd (&cwd, NULL))
1097 	    error_exit ();
1098 	free_cwd (&cwd);
1099 	dirlist = sdirlist;
1100 	repository = srepository;
1101     }
1102 
1103     free (update_dir);
1104     update_dir = saved_update_dir;
1105 
1106     return (err);
1107 }
1108 
1109 /*
1110  * Add a node to a list allocating the list if necessary.
1111  */
1112 static void
1113 addlist (listp, key)
1114     List **listp;
1115     char *key;
1116 {
1117     Node *p;
1118 
1119     if (*listp == NULL)
1120 	*listp = getlist ();
1121     p = getnode ();
1122     p->type = FILES;
1123     p->key = xstrdup (key);
1124     if (addnode (*listp, p) != 0)
1125 	freenode (p);
1126 }
1127 
1128 static void
1129 addfile (listp, dir, file)
1130     List **listp;
1131     char *dir;
1132     char *file;
1133 {
1134     Node *n;
1135 
1136     /* add this dir. */
1137     addlist (listp, dir);
1138 
1139     n = findnode (*listp, dir);
1140     if (n == NULL)
1141     {
1142 	error (1, 0, "can't find recently added dir node `%s' in start_recursion.",
1143 	       dir);
1144     }
1145 
1146     n->type = DIRS;
1147     addlist ((List **) &n->data, file);
1148     return;
1149 }
1150 
1151 static int
1152 unroll_files_proc (p, closure)
1153     Node *p;
1154     void *closure;
1155 {
1156     Node *n;
1157     struct recursion_frame *frame = (struct recursion_frame *) closure;
1158     int err = 0;
1159     List *save_dirlist;
1160     char *save_update_dir = NULL;
1161     struct saved_cwd cwd;
1162 
1163     /* if this dir was also an explicitly named argument, then skip
1164        it.  We'll catch it later when we do dirs. */
1165     n = findnode (dirlist, p->key);
1166     if (n != NULL)
1167 	return (0);
1168 
1169     /* otherwise, call dorecusion for this list of files. */
1170     filelist = (List *) p->data;
1171     p->data = NULL;
1172     save_dirlist = dirlist;
1173     dirlist = NULL;
1174 
1175     if (strcmp(p->key, ".") != 0)
1176     {
1177         if (save_cwd (&cwd))
1178 	    error_exit ();
1179 	if ( CVS_CHDIR (p->key) < 0)
1180 	    error (1, errno, "could not chdir to %s", p->key);
1181 
1182 	save_update_dir = update_dir;
1183 	update_dir = xmalloc (strlen (save_update_dir)
1184 				  + strlen (p->key)
1185 				  + 5);
1186 	strcpy (update_dir, save_update_dir);
1187 
1188 	if (*update_dir != '\0')
1189 	    (void) strcat (update_dir, "/");
1190 
1191 	(void) strcat (update_dir, p->key);
1192     }
1193 
1194     err += do_recursion (frame);
1195 
1196     if (save_update_dir != NULL)
1197     {
1198 	free (update_dir);
1199 	update_dir = save_update_dir;
1200 
1201 	if (restore_cwd (&cwd, NULL))
1202 	    error_exit ();
1203 	free_cwd (&cwd);
1204     }
1205 
1206     dirlist = save_dirlist;
1207     filelist = NULL;
1208     return(err);
1209 }
1210