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