117721Speter/*
2177404Sobrien * Copyright (C) 1986-2008 The Free Software Foundation, Inc.
3175282Sobrien *
4175282Sobrien * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5175282Sobrien *                                  and others.
6175282Sobrien *
7175282Sobrien * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8175282Sobrien * Portions Copyright (C) 1989-1992, Brian Berliner
917721Speter *
1017721Speter * You may distribute under the terms of the GNU General Public License as
1132788Speter * specified in the README file that comes with the CVS source distribution.
1217721Speter *
1317721Speter * General recursion handler
1417721Speter *
1517721Speter */
1617721Speter
1717721Speter#include "cvs.h"
1817721Speter#include "savecwd.h"
1917721Speter#include "fileattr.h"
2017721Speter#include "edit.h"
21128269Speter#include <assert.h>
2217721Speter
2317721Speterstatic int do_dir_proc PROTO((Node * p, void *closure));
2417721Speterstatic int do_file_proc PROTO((Node * p, void *closure));
2517721Speterstatic void addlist PROTO((List ** listp, char *key));
2617721Speterstatic int unroll_files_proc PROTO((Node *p, void *closure));
2717721Speterstatic void addfile PROTO((List **listp, char *dir, char *file));
2817721Speter
2925839Speterstatic char *update_dir;
3017721Speterstatic char *repository = NULL;
3117721Speterstatic List *filelist = NULL; /* holds list of files on which to operate */
3217721Speterstatic List *dirlist = NULL; /* holds list of directories on which to operate */
3317721Speter
3417721Speterstruct recursion_frame {
3525839Speter    FILEPROC fileproc;
3625839Speter    FILESDONEPROC filesdoneproc;
3725839Speter    DIRENTPROC direntproc;
3825839Speter    DIRLEAVEPROC dirleaveproc;
3925839Speter    void *callerdat;
4025839Speter    Dtype flags;
4125839Speter    int which;
4225839Speter    int aflag;
43107487Speter    int locktype;
4425839Speter    int dosrcs;
45128269Speter    char *repository;			/* Keep track of repository for rtag */
4617721Speter};
4717721Speter
4825839Speterstatic int do_recursion PROTO ((struct recursion_frame *frame));
4925839Speter
5025839Speter/* I am half tempted to shove a struct file_info * into the struct
5125839Speter   recursion_frame (but then we would need to modify or create a
5225839Speter   recursion_frame for each file), or shove a struct recursion_frame *
5325839Speter   into the struct file_info (more tempting, although it isn't completely
5425839Speter   clear that the struct file_info should contain info about recursion
5525839Speter   processor internals).  So instead use this struct.  */
5625839Speter
5725839Speterstruct frame_and_file {
5825839Speter    struct recursion_frame *frame;
5925839Speter    struct file_info *finfo;
6025839Speter};
6125839Speter
6225839Speter/* Similarly, we need to pass the entries list to do_dir_proc.  */
6325839Speter
6425839Speterstruct frame_and_entries {
6525839Speter    struct recursion_frame *frame;
6625839Speter    List *entries;
6725839Speter};
6825839Speter
6944856Speter
7025839Speter/* Start a recursive command.
7125839Speter
7225839Speter   Command line arguments (ARGC, ARGV) dictate the directories and
7325839Speter   files on which we operate.  In the special case of no arguments, we
7425839Speter   default to ".".  */
7517721Speterint
7625839Speterstart_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc, callerdat,
77107487Speter		 argc, argv, local, which, aflag, locktype,
78128269Speter		 update_preload, dosrcs, repository_in)
7917721Speter    FILEPROC fileproc;
8017721Speter    FILESDONEPROC filesdoneproc;
8117721Speter    DIRENTPROC 	direntproc;
8217721Speter    DIRLEAVEPROC dirleaveproc;
8325839Speter    void *callerdat;
8425839Speter
8517721Speter    int argc;
8617721Speter    char **argv;
8717721Speter    int local;
8825839Speter
8925839Speter    /* This specifies the kind of recursion.  There are several cases:
9025839Speter
9125839Speter       1.  W_LOCAL is not set but W_REPOS or W_ATTIC is.  The current
9225839Speter       directory when we are called must be the repository and
9325839Speter       recursion proceeds according to what exists in the repository.
9425839Speter
9525839Speter       2a.  W_LOCAL is set but W_REPOS and W_ATTIC are not.  The
9625839Speter       current directory when we are called must be the working
9725839Speter       directory.  Recursion proceeds according to what exists in the
9825839Speter       working directory, never (I think) consulting any part of the
9925839Speter       repository which does not correspond to the working directory
10025839Speter       ("correspond" == Name_Repository).
10125839Speter
10225839Speter       2b.  W_LOCAL is set and so is W_REPOS or W_ATTIC.  This is the
10325839Speter       weird one.  The current directory when we are called must be
10425839Speter       the working directory.  We recurse through working directories,
10525839Speter       but we recurse into a directory if it is exists in the working
10625839Speter       directory *or* it exists in the repository.  If a directory
10725839Speter       does not exist in the working directory, the direntproc must
10825839Speter       either tell us to skip it (R_SKIP_ALL), or must create it (I
10925839Speter       think those are the only two cases).  */
11017721Speter    int which;
11125839Speter
11217721Speter    int aflag;
113107487Speter    int locktype;
11417721Speter    char *update_preload;
11517721Speter    int dosrcs;
116128269Speter    /* Keep track of the repository string.  This is only for the remote mode,
117128269Speter     * specifically, r* commands (rtag, rdiff, co, ...) where xgetwd() was
118128269Speter     * used to locate the repository.  Things would break when xgetwd() was
119128269Speter     * used with a symlinked repository because xgetwd() would return the true
120128269Speter     * path and in some cases this would cause the path to be printed as other
121128269Speter     * than the user specified in error messages and in other cases some of
122128269Speter     * CVS's security assertions would fail.
123128269Speter     */
124128269Speter    char *repository_in;
12517721Speter{
12617721Speter    int i, err = 0;
12754431Speter#ifdef CLIENT_SUPPORT
12854431Speter    List *args_to_send_when_finished = NULL;
12954431Speter#endif
13017721Speter    List *files_by_dir = NULL;
13117721Speter    struct recursion_frame frame;
13217721Speter
13325839Speter    frame.fileproc = fileproc;
13425839Speter    frame.filesdoneproc = filesdoneproc;
13525839Speter    frame.direntproc = direntproc;
13625839Speter    frame.dirleaveproc = dirleaveproc;
13725839Speter    frame.callerdat = callerdat;
13825839Speter    frame.flags = local ? R_SKIP_DIRS : R_PROCESS;
13925839Speter    frame.which = which;
14025839Speter    frame.aflag = aflag;
141107487Speter    frame.locktype = locktype;
14225839Speter    frame.dosrcs = dosrcs;
143175282Sobrien
144175282Sobrien    /* If our repository_in has a trailing "/.", remove it before storing it
145175282Sobrien     * for do_recursion().
146175282Sobrien     *
147175282Sobrien     * FIXME: This is somewhat of a hack in the sense that many of our callers
148175282Sobrien     * painstakingly compute and add the trailing '.' we now remove.
149175282Sobrien     */
150175282Sobrien    while (repository_in && strlen (repository_in) >= 2
151175282Sobrien           && repository_in[strlen (repository_in) - 2] == '/'
152175282Sobrien           && repository_in[strlen (repository_in) - 1] == '.')
153175282Sobrien    {
154175282Sobrien	/* Beware the case where the string is exactly "/." or "//.".
155175282Sobrien	 * Paths with a leading "//" are special on some early UNIXes.
156175282Sobrien	 */
157175282Sobrien	if (strlen (repository_in) == 2 || strlen (repository_in) == 3)
158175282Sobrien	    repository_in[strlen (repository_in) - 1] = '\0';
159175282Sobrien	else
160175282Sobrien	    repository_in[strlen (repository_in) - 2] = '\0';
161175282Sobrien    }
162128269Speter    frame.repository = repository_in;
16325839Speter
16417721Speter    expand_wild (argc, argv, &argc, &argv);
16517721Speter
16617721Speter    if (update_preload == NULL)
16725839Speter	update_dir = xstrdup ("");
16817721Speter    else
16925839Speter	update_dir = xstrdup (update_preload);
17017721Speter
17117721Speter    /* clean up from any previous calls to start_recursion */
17217721Speter    if (repository)
17317721Speter    {
17417721Speter	free (repository);
17517721Speter	repository = (char *) NULL;
17617721Speter    }
17717721Speter    if (filelist)
17817721Speter	dellist (&filelist); /* FIXME-krp: no longer correct. */
17917721Speter    if (dirlist)
18017721Speter	dellist (&dirlist);
18117721Speter
18225839Speter#ifdef SERVER_SUPPORT
18325839Speter    if (server_active)
18425839Speter    {
18525839Speter	for (i = 0; i < argc; ++i)
18625839Speter	    server_pathname_check (argv[i]);
18725839Speter    }
18825839Speter#endif
18925839Speter
19017721Speter    if (argc == 0)
19117721Speter    {
19254431Speter	int just_subdirs = (which & W_LOCAL) && !isdir (CVSADM);
19317721Speter
19454431Speter#ifdef CLIENT_SUPPORT
19554431Speter	if (!just_subdirs
19654431Speter	    && CVSroot_cmdline == NULL
19781407Speter	    && current_parsed_root->isremote)
19854431Speter	{
199175282Sobrien	    cvsroot_t *root = Name_Root (NULL, update_dir);
200175282Sobrien	    if (root)
201175282Sobrien	    {
202175282Sobrien		if (strcmp (root->original, current_parsed_root->original))
203175282Sobrien		    /* We're skipping this directory because it is for
204175282Sobrien		     * a different root.  Therefore, we just want to
205175282Sobrien		     * do the subdirectories only.  Processing files would
206175282Sobrien		     * cause a working directory from one repository to be
207175282Sobrien		     * processed against a different repository, which could
208175282Sobrien		     * cause all kinds of spurious conflicts and such.
209175282Sobrien		     *
210175282Sobrien		     * Question: what about the case of "cvs update foo"
211175282Sobrien		     * where we process foo/bar and not foo itself?  That
212175282Sobrien		     * seems to be handled somewhere (else) but why should
213175282Sobrien		     * it be a separate case?  Needs investigation...  */
214175282Sobrien		    just_subdirs = 1;
215175282Sobrien		free_cvsroot_t (root);
216175282Sobrien	    }
21754431Speter	}
21854431Speter#endif
21954431Speter
22017721Speter	/*
22117721Speter	 * There were no arguments, so we'll probably just recurse. The
22217721Speter	 * exception to the rule is when we are called from a directory
22317721Speter	 * without any CVS administration files.  That has always meant to
22417721Speter	 * process each of the sub-directories, so we pretend like we were
22517721Speter	 * called with the list of sub-dirs of the current dir as args
22617721Speter	 */
22754431Speter	if (just_subdirs)
22832788Speter	{
22925839Speter	    dirlist = Find_Directories ((char *) NULL, W_LOCAL, (List *) NULL);
23032788Speter	    /* If there are no sub-directories, there is a certain logic in
23132788Speter	       favor of doing nothing, but in fact probably the user is just
23232788Speter	       confused about what directory they are in, or whether they
23332788Speter	       cvs add'd a new directory.  In the case of at least one
23432788Speter	       sub-directory, at least when we recurse into them we
23532788Speter	       notice (hopefully) whether they are under CVS control.  */
23632788Speter	    if (list_isempty (dirlist))
23732788Speter	    {
23832788Speter		if (update_dir[0] == '\0')
23932788Speter		    error (0, 0, "in directory .:");
24032788Speter		else
24132788Speter		    error (0, 0, "in directory %s:", update_dir);
24232788Speter		error (1, 0,
24332788Speter		       "there is no version here; run '%s checkout' first",
24432788Speter		       program_name);
24532788Speter	    }
24644856Speter#ifdef CLIENT_SUPPORT
24781407Speter	    else if (current_parsed_root->isremote && server_started)
24844856Speter	    {
24944856Speter		/* In the the case "cvs update foo bar baz", a call to
25044856Speter		   send_file_names in update.c will have sent the
25144856Speter		   appropriate "Argument" commands to the server.  In
25244856Speter		   this case, that won't have happened, so we need to
25344856Speter		   do it here.  While this example uses "update", this
25454431Speter		   generalizes to other commands.  */
25544856Speter
25654431Speter		/* This is the same call to Find_Directories as above.
25754431Speter                   FIXME: perhaps it would be better to write a
25854431Speter                   function that duplicates a list. */
25954431Speter		args_to_send_when_finished = Find_Directories ((char *) NULL,
26054431Speter							       W_LOCAL,
26154431Speter							       (List *) NULL);
26244856Speter	    }
26344856Speter#endif
26432788Speter	}
26517721Speter	else
26617721Speter	    addlist (&dirlist, ".");
26717721Speter
26854431Speter	goto do_the_work;
26917721Speter    }
27017721Speter
27117721Speter
27217721Speter    /*
27317721Speter     * There were arguments, so we have to handle them by hand. To do
27417721Speter     * that, we set up the filelist and dirlist with the arguments and
27517721Speter     * call do_recursion.  do_recursion recognizes the fact that the
27617721Speter     * lists are non-null when it starts and doesn't update them.
27717721Speter     *
27817721Speter     * explicitly named directories are stored in dirlist.
27917721Speter     * explicitly named files are stored in filelist.
28017721Speter     * other possibility is named entities whicha are not currently in
28117721Speter     * the working directory.
28217721Speter     */
28317721Speter
28417721Speter    for (i = 0; i < argc; i++)
28517721Speter    {
28617721Speter	/* if this argument is a directory, then add it to the list of
28717721Speter	   directories. */
28817721Speter
28917721Speter	if (!wrap_name_has (argv[i], WRAP_TOCVS) && isdir (argv[i]))
290102843Speter	{
291102843Speter	    strip_trailing_slashes (argv[i]);
29217721Speter	    addlist (&dirlist, argv[i]);
293102843Speter	}
29417721Speter	else
29517721Speter	{
29617721Speter	    /* otherwise, split argument into directory and component names. */
29717721Speter	    char *dir;
29817721Speter	    char *comp;
29917721Speter	    char *file_to_try;
30017721Speter
30117721Speter	    /* Now break out argv[i] into directory part (DIR) and file part (COMP).
30217721Speter		   DIR and COMP will each point to a newly malloc'd string.  */
30317721Speter	    dir = xstrdup (argv[i]);
304128269Speter	    /* Its okay to discard the const below - we know we just allocated
305128269Speter	     * dir ourselves.
306128269Speter	     */
307128269Speter	    comp = (char *)last_component (dir);
30817721Speter	    if (comp == dir)
30917721Speter	    {
31017721Speter		/* no dir component.  What we have is an implied "./" */
31117721Speter		dir = xstrdup(".");
31217721Speter	    }
31317721Speter	    else
31417721Speter	    {
31517721Speter		char *p = comp;
31617721Speter
31717721Speter		p[-1] = '\0';
31817721Speter		comp = xstrdup (p);
31917721Speter	    }
32017721Speter
32117721Speter	    /* if this argument exists as a file in the current
32217721Speter	       working directory tree, then add it to the files list.  */
32317721Speter
32425839Speter	    if (!(which & W_LOCAL))
32517721Speter	    {
32617721Speter		/* If doing rtag, we've done a chdir to the repository. */
32725839Speter		file_to_try = xmalloc (strlen (argv[i]) + sizeof (RCSEXT) + 5);
32825839Speter		sprintf (file_to_try, "%s%s", argv[i], RCSEXT);
32917721Speter	    }
33017721Speter	    else
33125839Speter		file_to_try = xstrdup (argv[i]);
33217721Speter
33325839Speter	    if (isfile (file_to_try))
33417721Speter		addfile (&files_by_dir, dir, comp);
33517721Speter	    else if (isdir (dir))
33617721Speter	    {
337175282Sobrien		if ((which & W_LOCAL) && isdir (CVSADM) &&
338175282Sobrien		    !current_parsed_root->isremote)
33917721Speter		{
34017721Speter		    /* otherwise, look for it in the repository. */
34125839Speter		    char *tmp_update_dir;
34217721Speter		    char *repos;
34325839Speter		    char *reposfile;
34417721Speter
34525839Speter		    tmp_update_dir = xmalloc (strlen (update_dir)
34625839Speter					      + strlen (dir)
34725839Speter					      + 5);
34825839Speter		    strcpy (tmp_update_dir, update_dir);
34917721Speter
35025839Speter		    if (*tmp_update_dir != '\0')
35125839Speter			(void) strcat (tmp_update_dir, "/");
35225839Speter
35325839Speter		    (void) strcat (tmp_update_dir, dir);
35425839Speter
35517721Speter		    /* look for it in the repository. */
35625839Speter		    repos = Name_Repository (dir, tmp_update_dir);
35725839Speter		    reposfile = xmalloc (strlen (repos)
35825839Speter					 + strlen (comp)
35925839Speter					 + 5);
36025839Speter		    (void) sprintf (reposfile, "%s/%s", repos, comp);
36117721Speter		    free (repos);
36217721Speter
36325839Speter		    if (!wrap_name_has (comp, WRAP_TOCVS) && isdir (reposfile))
36417721Speter			addlist (&dirlist, argv[i]);
36517721Speter		    else
36617721Speter			addfile (&files_by_dir, dir, comp);
36717721Speter
36825839Speter		    free (tmp_update_dir);
36925839Speter		    free (reposfile);
37017721Speter		}
37117721Speter		else
37217721Speter		    addfile (&files_by_dir, dir, comp);
37317721Speter	    }
37417721Speter	    else
37517721Speter		error (1, 0, "no such directory `%s'", dir);
37617721Speter
37725839Speter	    free (file_to_try);
37817721Speter	    free (dir);
37917721Speter	    free (comp);
38017721Speter	}
38117721Speter    }
38217721Speter
38317721Speter    /* At this point we have looped over all named arguments and built
38417721Speter       a coupla lists.  Now we unroll the lists, setting up and
38517721Speter       calling do_recursion. */
38617721Speter
38717721Speter    err += walklist (files_by_dir, unroll_files_proc, (void *) &frame);
38825839Speter    dellist(&files_by_dir);
38917721Speter
39017721Speter    /* then do_recursion on the dirlist. */
39117721Speter    if (dirlist != NULL)
39254431Speter    {
39354431Speter    do_the_work:
39425839Speter	err += do_recursion (&frame);
39554431Speter    }
39654431Speter
39717721Speter    /* Free the data which expand_wild allocated.  */
39826065Speter    free_names (&argc, argv);
39917721Speter
40025839Speter    free (update_dir);
40125839Speter    update_dir = NULL;
40254431Speter
40354431Speter#ifdef CLIENT_SUPPORT
40454431Speter    if (args_to_send_when_finished != NULL)
40554431Speter    {
40654431Speter	/* FIXME (njc): in the multiroot case, we don't want to send
40754431Speter	   argument commands for those top-level directories which do
40854431Speter	   not contain any subdirectories which have files checked out
40981407Speter	   from current_parsed_root->original.  If we do, and two repositories
41081407Speter	   have a module with the same name, nasty things could happen.
41154431Speter
41254431Speter	   This is hard.  Perhaps we should send the Argument commands
41354431Speter	   later in this procedure, after we've had a chance to notice
41454431Speter	   which directores we're using (after do_recursion has been
41554431Speter	   called once).  This means a _lot_ of rewriting, however.
41654431Speter
41754431Speter	   What we need to do for that to happen is descend the tree
41854431Speter	   and construct a list of directories which are checked out
41954431Speter	   from current_cvsroot.  Now, we eliminate from the list all
42054431Speter	   of those directories which are immediate subdirectories of
42154431Speter	   another directory in the list.  To say that the opposite
42254431Speter	   way, we keep the directories which are not immediate
42354431Speter	   subdirectories of any other in the list.  Here's a picture:
42454431Speter
42554431Speter			      a
42654431Speter			     / \
42754431Speter			    B   C
42854431Speter			   / \
42954431Speter			  D   e
43054431Speter			     / \
43154431Speter			    F   G
43254431Speter			       / \
43354431Speter			      H   I
43454431Speter
43554431Speter	   The node in capitals are those directories which are
43654431Speter	   checked out from current_cvsroot.  We want the list to
43754431Speter	   contain B, C, F, and G.  D, H, and I are not included,
43854431Speter	   because their parents are also checked out from
43954431Speter	   current_cvsroot.
44054431Speter
44154431Speter	   The algorithm should be:
44254431Speter
44354431Speter	   1) construct a tree of all directory names where each
44454431Speter	   element contains a directory name and a flag which notes if
44554431Speter	   that directory is checked out from current_cvsroot
44654431Speter
44754431Speter			      a0
44854431Speter			     / \
44954431Speter			    B1  C1
45054431Speter			   / \
45154431Speter			  D1  e0
45254431Speter			     / \
45354431Speter			    F1  G1
45454431Speter			       / \
45554431Speter			      H1  I1
45654431Speter
45754431Speter	   2) Recursively descend the tree.  For each node, recurse
45854431Speter	   before processing the node.  If the flag is zero, do
45954431Speter	   nothing.  If the flag is 1, check the node's parent.  If
46054431Speter	   the parent's flag is one, change the current entry's flag
46154431Speter	   to zero.
46254431Speter
46354431Speter			      a0
46454431Speter			     / \
46554431Speter			    B1  C1
46654431Speter			   / \
46754431Speter			  D0  e0
46854431Speter			     / \
46954431Speter			    F1  G1
47054431Speter			       / \
47154431Speter			      H0  I0
47254431Speter
47354431Speter	   3) Walk the tree and spit out "Argument" commands to tell
47454431Speter	   the server which directories to munge.
47554431Speter
47654431Speter	   Yuck.  It's not clear this is worth spending time on, since
47754431Speter	   we might want to disable cvs commands entirely from
47854431Speter	   directories that do not have CVSADM files...
47954431Speter
48054431Speter	   Anyways, the solution as it stands has modified server.c
48154431Speter	   (dirswitch) to create admin files [via server.c
48254431Speter	   (create_adm_p)] in all path elements for a client's
48354431Speter	   "Directory xxx" command, which forces the server to descend
48454431Speter	   and serve the files there.  client.c (send_file_names) has
48554431Speter	   also been modified to send only those arguments which are
48681407Speter	   appropriate to current_parsed_root->original.
48754431Speter
48854431Speter	*/
48954431Speter
49054431Speter	/* Construct a fake argc/argv pair. */
49154431Speter
49254431Speter	int our_argc = 0, i;
49354431Speter	char **our_argv = NULL;
49454431Speter
49554431Speter	if (! list_isempty (args_to_send_when_finished))
49654431Speter	{
49754431Speter	    Node *head, *p;
49854431Speter
49954431Speter	    head = args_to_send_when_finished->list;
50054431Speter
50154431Speter	    /* count the number of nodes */
50254431Speter	    i = 0;
50354431Speter	    for (p = head->next; p != head; p = p->next)
50454431Speter		i++;
50554431Speter	    our_argc = i;
50654431Speter
50754431Speter	    /* create the argument vector */
50854431Speter	    our_argv = (char **) xmalloc (sizeof (char *) * our_argc);
50954431Speter
51054431Speter	    /* populate it */
51154431Speter	    i = 0;
51254431Speter	    for (p = head->next; p != head; p = p->next)
51354431Speter		our_argv[i++] = xstrdup (p->key);
51454431Speter	}
51554431Speter
51654431Speter	/* We don't want to expand widcards, since we've just created
51754431Speter	   a list of directories directly from the filesystem. */
51854431Speter	send_file_names (our_argc, our_argv, 0);
51954431Speter
52054431Speter	/* Free our argc/argv. */
52154431Speter	if (our_argv != NULL)
52254431Speter	{
52354431Speter	    for (i = 0; i < our_argc; i++)
52454431Speter		free (our_argv[i]);
52554431Speter	    free (our_argv);
52654431Speter	}
52754431Speter
52854431Speter	dellist (&args_to_send_when_finished);
52954431Speter    }
53054431Speter#endif
53154431Speter
53217721Speter    return (err);
53317721Speter}
53417721Speter
53517721Speter/*
53617721Speter * Implement the recursive policies on the local directory.  This may be
53717721Speter * called directly, or may be called by start_recursion
53817721Speter */
53925839Speterstatic int
54025839Speterdo_recursion (frame)
54125839Speter    struct recursion_frame *frame;
54217721Speter{
54317721Speter    int err = 0;
54417721Speter    int dodoneproc = 1;
545128269Speter    char *srepository = NULL;
54617721Speter    List *entries = NULL;
547107487Speter    int locktype;
54854431Speter    int process_this_directory = 1;
54917721Speter
55017721Speter    /* do nothing if told */
55125839Speter    if (frame->flags == R_SKIP_ALL)
55217721Speter	return (0);
55317721Speter
554109660Speter    locktype = noexec ? CVS_LOCK_NONE : frame->locktype;
55517721Speter
55617721Speter    /* The fact that locks are not active here is what makes us fail to have
55717721Speter       the
55817721Speter
55917721Speter           If someone commits some changes in one cvs command,
56017721Speter	   then an update by someone else will either get all the
56117721Speter	   changes, or none of them.
56217721Speter
56317721Speter       property (see node Concurrency in cvs.texinfo).
56417721Speter
56517721Speter       The most straightforward fix would just to readlock the whole
56617721Speter       tree before starting an update, but that means that if a commit
56717721Speter       gets blocked on a big update, it might need to wait a *long*
56817721Speter       time.
56917721Speter
57017721Speter       A more adequate fix would be a two-pass design for update,
57117721Speter       checkout, etc.  The first pass would go through the repository,
57217721Speter       with the whole tree readlocked, noting what versions of each
57317721Speter       file we want to get.  The second pass would release all locks
57417721Speter       (except perhaps short-term locks on one file at a
57517721Speter       time--although I think RCS already deals with this) and
57617721Speter       actually get the files, specifying the particular versions it wants.
57717721Speter
57817721Speter       This could be sped up by separating out the data needed for the
57917721Speter       first pass into a separate file(s)--for example a file
58017721Speter       attribute for each file whose value contains the head revision
58117721Speter       for each branch.  The structure should be designed so that
58217721Speter       commit can relatively quickly update the information for a
58317721Speter       single file or a handful of files (file attributes, as
58417721Speter       implemented in Jan 96, are probably acceptable; improvements
58517721Speter       would be possible such as branch attributes which are in
58617721Speter       separate files for each branch).  */
58717721Speter
58817721Speter#if defined(SERVER_SUPPORT) && defined(SERVER_FLOWCONTROL)
58917721Speter    /*
59017721Speter     * Now would be a good time to check to see if we need to stop
59117721Speter     * generating data, to give the buffers a chance to drain to the
592107487Speter     * remote client.  We should not have locks active at this point,
593107487Speter     * but if there are writelocks around, we cannot pause here.  */
594175282Sobrien    if (server_active && locktype != CVS_LOCK_WRITE)
59517721Speter	server_pause_check();
59617721Speter#endif
59717721Speter
59854431Speter    /* Check the value in CVSADM_ROOT and see if it's in the list.  If
59954431Speter       not, add it to our lists of CVS/Root directories and do not
60054431Speter       process the files in this directory.  Otherwise, continue as
60154431Speter       usual.  THIS_ROOT might be NULL if we're doing an initial
60254431Speter       checkout -- check before using it.  The default should be that
60354431Speter       we process a directory's contents and only skip those contents
60454431Speter       if a CVS/Root file exists.
60554431Speter
60654431Speter       If we're running the server, we want to process all
60754431Speter       directories, since we're guaranteed to have only one CVSROOT --
60854431Speter       our own.  */
60954431Speter
610175282Sobrien    /* If -d was specified, it should override CVS/Root.
61154431Speter
612175282Sobrien       In the single-repository case, it is long-standing CVS behavior
613175282Sobrien       and makes sense - the user might want another access method,
614175282Sobrien       another server (which mounts the same repository), &c.
61554431Speter
616175282Sobrien       In the multiple-repository case, -d overrides all CVS/Root
617175282Sobrien       files.  That is the only plausible generalization I can
618175282Sobrien       think of.  */
619175282Sobrien    if (CVSroot_cmdline == NULL && !server_active)
62054431Speter    {
621175282Sobrien	cvsroot_t *this_root = Name_Root ((char *) NULL, update_dir);
62254431Speter	if (this_root != NULL)
62354431Speter	{
624175282Sobrien	    if (findnode (root_directories, this_root->original))
62554431Speter	    {
626175282Sobrien		process_this_directory = !strcmp (current_parsed_root->original,
627175282Sobrien						  this_root->original);
628175282Sobrien		free_cvsroot_t (this_root);
629175282Sobrien	    }
630175282Sobrien	    else
631175282Sobrien	    {
63254431Speter		/* Add it to our list. */
63354431Speter
63454431Speter		Node *n = getnode ();
63566528Speter		n->type = NT_UNKNOWN;
636175282Sobrien		n->key = xstrdup (this_root->original);
637175282Sobrien		n->data = this_root;
63854431Speter
63954431Speter		if (addnode (root_directories, n))
640175282Sobrien		    error (1, 0, "cannot add new CVSROOT %s",
641175282Sobrien			   this_root->original);
642175282Sobrien
643175282Sobrien		process_this_directory = 0;
64454431Speter	    }
64554431Speter	}
64654431Speter    }
64754431Speter
64817721Speter    /*
64917721Speter     * Fill in repository with the current repository
65017721Speter     */
65125839Speter    if (frame->which & W_LOCAL)
65217721Speter    {
65317721Speter	if (isdir (CVSADM))
654128269Speter	{
65517721Speter	    repository = Name_Repository ((char *) NULL, update_dir);
656128269Speter	    srepository = repository;		/* remember what to free */
657128269Speter	}
65817721Speter	else
65917721Speter	    repository = NULL;
66017721Speter    }
66117721Speter    else
66217721Speter    {
663128269Speter	repository = frame->repository;
664128269Speter	assert (repository != NULL);
66517721Speter    }
66617721Speter
66717721Speter    fileattr_startdir (repository);
66817721Speter
66917721Speter    /*
67017721Speter     * The filesdoneproc needs to be called for each directory where files
67117721Speter     * processed, or each directory that is processed by a call where no
67217721Speter     * directories were passed in.  In fact, the only time we don't want to
67317721Speter     * call back the filesdoneproc is when we are processing directories that
67417721Speter     * were passed in on the command line (or in the special case of `.' when
67517721Speter     * we were called with no args
67617721Speter     */
67717721Speter    if (dirlist != NULL && filelist == NULL)
67817721Speter	dodoneproc = 0;
67917721Speter
68017721Speter    /*
68117721Speter     * If filelist or dirlist is already set, we don't look again. Otherwise,
68217721Speter     * find the files and directories
68317721Speter     */
68417721Speter    if (filelist == NULL && dirlist == NULL)
68517721Speter    {
68617721Speter	/* both lists were NULL, so start from scratch */
68725839Speter	if (frame->fileproc != NULL && frame->flags != R_SKIP_FILES)
68817721Speter	{
68925839Speter	    int lwhich = frame->which;
69017721Speter
69117721Speter	    /* be sure to look in the attic if we have sticky tags/date */
69217721Speter	    if ((lwhich & W_ATTIC) == 0)
69317721Speter		if (isreadable (CVSADM_TAG))
69417721Speter		    lwhich |= W_ATTIC;
69517721Speter
69625839Speter	    /* In the !(which & W_LOCAL) case, we filled in repository
69725839Speter	       earlier in the function.  In the (which & W_LOCAL) case,
69825839Speter	       the Find_Names function is going to look through the
69925839Speter	       Entries file.  If we do not have a repository, that
70025839Speter	       does not make sense, so we insist upon having a
70125839Speter	       repository at this point.  Name_Repository will give a
70225839Speter	       reasonable error message.  */
70325839Speter	    if (repository == NULL)
704128269Speter	    {
705128269Speter		Name_Repository ((char *) NULL, update_dir);
706175282Sobrien		assert (!"Not reached.  Please report this problem to <"
707175282Sobrien			PACKAGE_BUGREPORT ">");
708128269Speter	    }
70925839Speter
71017721Speter	    /* find the files and fill in entries if appropriate */
71154431Speter	    if (process_this_directory)
71254431Speter	    {
71354431Speter		filelist = Find_Names (repository, lwhich, frame->aflag,
71454431Speter				       &entries);
71554431Speter		if (filelist == NULL)
71654431Speter		{
71754431Speter		    error (0, 0, "skipping directory %s", update_dir);
71854431Speter		    /* Note that Find_Directories and the filesdoneproc
71954431Speter		       in particular would do bad things ("? foo.c" in
72054431Speter		       the case of some filesdoneproc's).  */
72154431Speter		    goto skip_directory;
72254431Speter		}
72354431Speter	    }
72417721Speter	}
72517721Speter
72617721Speter	/* find sub-directories if we will recurse */
72725839Speter	if (frame->flags != R_SKIP_DIRS)
72854431Speter	    dirlist = Find_Directories (
72954431Speter		process_this_directory ? repository : NULL,
73054431Speter		frame->which, entries);
73117721Speter    }
73217721Speter    else
73317721Speter    {
73417721Speter	/* something was passed on the command line */
73525839Speter	if (filelist != NULL && frame->fileproc != NULL)
73617721Speter	{
73717721Speter	    /* we will process files, so pre-parse entries */
73825839Speter	    if (frame->which & W_LOCAL)
73934467Speter		entries = Entries_Open (frame->aflag, NULL);
74017721Speter	}
74117721Speter    }
74217721Speter
74317721Speter    /* process the files (if any) */
74454431Speter    if (process_this_directory && filelist != NULL && frame->fileproc)
74517721Speter    {
74617721Speter	struct file_info finfo_struct;
74725839Speter	struct frame_and_file frfile;
74817721Speter
74917721Speter	/* read lock it if necessary */
750107487Speter	if (repository)
751107487Speter	{
752109660Speter	    if (locktype == CVS_LOCK_READ)
753107487Speter	    {
754107487Speter		if (Reader_Lock (repository) != 0)
755107487Speter		    error (1, 0, "read lock failed - giving up");
756107487Speter	    }
757109660Speter	    else if (locktype == CVS_LOCK_WRITE)
758107487Speter		lock_dir_for_write (repository);
759107487Speter	}
76017721Speter
76117721Speter#ifdef CLIENT_SUPPORT
76217721Speter	/* For the server, we handle notifications in a completely different
76317721Speter	   place (server_notify).  For local, we can't do them here--we don't
76417721Speter	   have writelocks in place, and there is no way to get writelocks
76517721Speter	   here.  */
76681407Speter	if (current_parsed_root->isremote)
767177404Sobrien	    cvs_notify_check (repository, update_dir);
76817721Speter#endif /* CLIENT_SUPPORT */
76917721Speter
77017721Speter	finfo_struct.repository = repository;
77117721Speter	finfo_struct.update_dir = update_dir;
77217721Speter	finfo_struct.entries = entries;
77317721Speter	/* do_file_proc will fill in finfo_struct.file.  */
77417721Speter
77525839Speter	frfile.finfo = &finfo_struct;
77625839Speter	frfile.frame = frame;
77725839Speter
77817721Speter	/* process the files */
77925839Speter	err += walklist (filelist, do_file_proc, &frfile);
78017721Speter
78117721Speter	/* unlock it */
782128269Speter	if (/* We only lock the repository above when repository is set */
783128269Speter	    repository
784128269Speter	    /* and when asked for a read or write lock. */
785128269Speter	    && locktype != CVS_LOCK_NONE)
78617721Speter	    Lock_Cleanup ();
78717721Speter
78817721Speter	/* clean up */
78917721Speter	dellist (&filelist);
79017721Speter    }
79117721Speter
79217721Speter    /* call-back files done proc (if any) */
79354431Speter    if (process_this_directory && dodoneproc && frame->filesdoneproc != NULL)
79425839Speter	err = frame->filesdoneproc (frame->callerdat, err, repository,
79525839Speter				    update_dir[0] ? update_dir : ".",
79625839Speter				    entries);
79717721Speter
79854431Speter skip_directory:
79917721Speter    fileattr_write ();
80017721Speter    fileattr_free ();
80117721Speter
80217721Speter    /* process the directories (if necessary) */
80317721Speter    if (dirlist != NULL)
80425839Speter    {
80525839Speter	struct frame_and_entries frent;
80625839Speter
80725839Speter	frent.frame = frame;
80825839Speter	frent.entries = entries;
80925839Speter	err += walklist (dirlist, do_dir_proc, (void *) &frent);
81025839Speter    }
81125839Speter#if 0
81225839Speter    else if (frame->dirleaveproc != NULL)
81325839Speter	err += frame->dirleaveproc (frame->callerdat, ".", err, ".");
81417721Speter#endif
81517721Speter    dellist (&dirlist);
81617721Speter
81725839Speter    if (entries)
81825839Speter    {
81925839Speter	Entries_Close (entries);
82025839Speter	entries = NULL;
82125839Speter    }
82225839Speter
82317721Speter    /* free the saved copy of the pointer if necessary */
82417721Speter    if (srepository)
82517721Speter    {
82617721Speter	free (srepository);
82717721Speter    }
828128269Speter    repository = (char *) NULL;
82917721Speter
830128269Speter    return err;
83117721Speter}
83217721Speter
833128269Speter
834128269Speter
83517721Speter/*
83617721Speter * Process each of the files in the list with the callback proc
83717721Speter */
83817721Speterstatic int
83917721Speterdo_file_proc (p, closure)
84017721Speter    Node *p;
84117721Speter    void *closure;
84217721Speter{
84325839Speter    struct frame_and_file *frfile = (struct frame_and_file *)closure;
84425839Speter    struct file_info *finfo = frfile->finfo;
84517721Speter    int ret;
846128269Speter    char *tmp;
84717721Speter
84817721Speter    finfo->file = p->key;
849128269Speter    tmp = xmalloc (strlen (finfo->file)
85017721Speter			       + strlen (finfo->update_dir)
85117721Speter			       + 2);
852128269Speter    tmp[0] = '\0';
85317721Speter    if (finfo->update_dir[0] != '\0')
85417721Speter    {
855128269Speter	strcat (tmp, finfo->update_dir);
856128269Speter	strcat (tmp, "/");
85717721Speter    }
858128269Speter    strcat (tmp, finfo->file);
85917721Speter
86025839Speter    if (frfile->frame->dosrcs && repository)
86154431Speter    {
86217721Speter	finfo->rcs = RCS_parse (finfo->file, repository);
86354431Speter
86454431Speter	/* OK, without W_LOCAL the error handling becomes relatively
86554431Speter	   simple.  The file names came from readdir() on the
86654431Speter	   repository and so we know any ENOENT is an error
86754431Speter	   (e.g. symlink pointing to nothing).  Now, the logic could
86854431Speter	   be simpler - since we got the name from readdir, we could
86954431Speter	   just be calling RCS_parsercsfile.  */
87054431Speter	if (finfo->rcs == NULL
87154431Speter	    && !(frfile->frame->which & W_LOCAL))
87254431Speter	{
873128269Speter	    error (0, 0, "could not read RCS file for %s", tmp);
874128269Speter	    free (tmp);
87554431Speter	    cvs_flushout ();
87654431Speter	    return 0;
87754431Speter	}
87854431Speter    }
87917721Speter    else
88017721Speter        finfo->rcs = (RCSNode *) NULL;
881128269Speter    finfo->fullname = tmp;
88225839Speter    ret = frfile->frame->fileproc (frfile->frame->callerdat, finfo);
88317721Speter
88417721Speter    freercsnode(&finfo->rcs);
885128269Speter    free (tmp);
88617721Speter
88725839Speter    /* Allow the user to monitor progress with tail -f.  Doing this once
88825839Speter       per file should be no big deal, but we don't want the performance
88925839Speter       hit of flushing on every line like previous versions of CVS.  */
89025839Speter    cvs_flushout ();
89125839Speter
892128269Speter    return ret;
89317721Speter}
89417721Speter
895128269Speter
896128269Speter
89717721Speter/*
89817721Speter * Process each of the directories in the list (recursing as we go)
89917721Speter */
90017721Speterstatic int
90117721Speterdo_dir_proc (p, closure)
90217721Speter    Node *p;
90317721Speter    void *closure;
90417721Speter{
90525839Speter    struct frame_and_entries *frent = (struct frame_and_entries *) closure;
90625839Speter    struct recursion_frame *frame = frent->frame;
90725839Speter    struct recursion_frame xframe;
90817721Speter    char *dir = p->key;
90925839Speter    char *newrepos;
91017721Speter    List *sdirlist;
91117721Speter    char *srepository;
91217721Speter    Dtype dir_return = R_PROCESS;
91317721Speter    int stripped_dot = 0;
91417721Speter    int err = 0;
91517721Speter    struct saved_cwd cwd;
91625839Speter    char *saved_update_dir;
91754431Speter    int process_this_directory = 1;
91817721Speter
91925839Speter    if (fncmp (dir, CVSADM) == 0)
92025839Speter    {
92125839Speter	/* This seems to most often happen when users (beginning users,
92225839Speter	   generally), try "cvs ci *" or something similar.  On that
92325839Speter	   theory, it is possible that we should just silently skip the
92425839Speter	   CVSADM directories, but on the other hand, using a wildcard
92525839Speter	   like this isn't necessarily a practice to encourage (it operates
92625839Speter	   only on files which exist in the working directory, unlike
92725839Speter	   regular CVS recursion).  */
92825839Speter
92925839Speter	/* FIXME-reentrancy: printed_cvs_msg should be in a "command
93025839Speter	   struct" or some such, so that it gets cleared for each new
93125839Speter	   command (this is possible using the remote protocol and a
93225839Speter	   custom-written client).  The struct recursion_frame is not
93325839Speter	   far back enough though, some commands (commit at least)
93425839Speter	   will call start_recursion several times.  An alternate solution
93525839Speter	   would be to take this whole check and move it to a new function
93625839Speter	   validate_arguments or some such that all the commands call
93725839Speter	   and which snips the offending directory from the argc,argv
93825839Speter	   vector.  */
93925839Speter	static int printed_cvs_msg = 0;
94025839Speter	if (!printed_cvs_msg)
94125839Speter	{
94225839Speter	    error (0, 0, "warning: directory %s specified in argument",
94325839Speter		   dir);
94425839Speter	    error (0, 0, "\
94525839Speterbut CVS uses %s for its own purposes; skipping %s directory",
94625839Speter		   CVSADM, dir);
94725839Speter	    printed_cvs_msg = 1;
94825839Speter	}
94925839Speter	return 0;
95025839Speter    }
95125839Speter
95225839Speter    saved_update_dir = update_dir;
95325839Speter    update_dir = xmalloc (strlen (saved_update_dir)
95425839Speter			  + strlen (dir)
95525839Speter			  + 5);
95625839Speter    strcpy (update_dir, saved_update_dir);
95725839Speter
95817721Speter    /* set up update_dir - skip dots if not at start */
95917721Speter    if (strcmp (dir, ".") != 0)
96017721Speter    {
96117721Speter	if (update_dir[0] != '\0')
96217721Speter	{
96317721Speter	    (void) strcat (update_dir, "/");
96417721Speter	    (void) strcat (update_dir, dir);
96517721Speter	}
96617721Speter	else
96717721Speter	    (void) strcpy (update_dir, dir);
96817721Speter
96917721Speter	/*
97017721Speter	 * Here we need a plausible repository name for the sub-directory. We
97117721Speter	 * create one by concatenating the new directory name onto the
97217721Speter	 * previous repository name.  The only case where the name should be
97317721Speter	 * used is in the case where we are creating a new sub-directory for
97417721Speter	 * update -d and in that case the generated name will be correct.
97517721Speter	 */
97617721Speter	if (repository == NULL)
97725839Speter	    newrepos = xstrdup ("");
97817721Speter	else
97925839Speter	{
98025839Speter	    newrepos = xmalloc (strlen (repository) + strlen (dir) + 5);
98132788Speter	    sprintf (newrepos, "%s/%s", repository, dir);
98225839Speter	}
98317721Speter    }
98417721Speter    else
98517721Speter    {
98617721Speter	if (update_dir[0] == '\0')
98717721Speter	    (void) strcpy (update_dir, dir);
98817721Speter
98917721Speter	if (repository == NULL)
99025839Speter	    newrepos = xstrdup ("");
99117721Speter	else
99225839Speter	    newrepos = xstrdup (repository);
99317721Speter    }
99417721Speter
99532788Speter    /* Check to see that the CVSADM directory, if it exists, seems to be
99632788Speter       well-formed.  It can be missing files if the user hit ^C in the
99732788Speter       middle of a previous run.  We want to (a) make this a nonfatal
99832788Speter       error, and (b) make sure we print which directory has the
99932788Speter       problem.
100032788Speter
100132788Speter       Do this before the direntproc, so that (1) the direntproc
100232788Speter       doesn't have to guess/deduce whether we will skip the directory
100332788Speter       (e.g. send_dirent_proc and whether to send the directory), and
100432788Speter       (2) so that the warm fuzzy doesn't get printed if we skip the
100532788Speter       directory.  */
100632788Speter    if (frame->which & W_LOCAL)
100732788Speter    {
100832788Speter	char *cvsadmdir;
100932788Speter
101032788Speter	cvsadmdir = xmalloc (strlen (dir)
101132788Speter			     + sizeof (CVSADM_REP)
101232788Speter			     + sizeof (CVSADM_ENT)
101332788Speter			     + 80);
101432788Speter
101532788Speter	strcpy (cvsadmdir, dir);
101632788Speter	strcat (cvsadmdir, "/");
101732788Speter	strcat (cvsadmdir, CVSADM);
101832788Speter	if (isdir (cvsadmdir))
101932788Speter	{
102032788Speter	    strcpy (cvsadmdir, dir);
102132788Speter	    strcat (cvsadmdir, "/");
102232788Speter	    strcat (cvsadmdir, CVSADM_REP);
102332788Speter	    if (!isfile (cvsadmdir))
102432788Speter	    {
102532788Speter		/* Some commands like update may have printed "? foo" but
102632788Speter		   if we were planning to recurse, and don't on account of
102732788Speter		   CVS/Repository, we want to say why.  */
102832788Speter		error (0, 0, "ignoring %s (%s missing)", update_dir,
102932788Speter		       CVSADM_REP);
103032788Speter		dir_return = R_SKIP_ALL;
103132788Speter	    }
103232788Speter
103332788Speter	    /* Likewise for CVS/Entries.  */
103432788Speter	    if (dir_return != R_SKIP_ALL)
103532788Speter	    {
103632788Speter		strcpy (cvsadmdir, dir);
103732788Speter		strcat (cvsadmdir, "/");
103832788Speter		strcat (cvsadmdir, CVSADM_ENT);
103932788Speter		if (!isfile (cvsadmdir))
104032788Speter		{
104132788Speter		    /* Some commands like update may have printed "? foo" but
104232788Speter		       if we were planning to recurse, and don't on account of
104332788Speter		       CVS/Repository, we want to say why.  */
104432788Speter		    error (0, 0, "ignoring %s (%s missing)", update_dir,
104532788Speter			   CVSADM_ENT);
104632788Speter		    dir_return = R_SKIP_ALL;
104732788Speter		}
104832788Speter	    }
104932788Speter	}
105032788Speter	free (cvsadmdir);
105132788Speter    }
105232788Speter
105354431Speter    /* Only process this directory if the root matches.  This nearly
105454431Speter       duplicates code in do_recursion. */
105554431Speter
1056175282Sobrien    /* If -d was specified, it should override CVS/Root.
105754431Speter
1058175282Sobrien       In the single-repository case, it is long-standing CVS behavior
1059175282Sobrien       and makes sense - the user might want another access method,
1060175282Sobrien       another server (which mounts the same repository), &c.
106154431Speter
1062175282Sobrien       In the multiple-repository case, -d overrides all CVS/Root
1063175282Sobrien       files.  That is the only plausible generalization I can
1064175282Sobrien       think of.  */
1065175282Sobrien    if (CVSroot_cmdline == NULL && !server_active)
106654431Speter    {
1067175282Sobrien	cvsroot_t *this_root = Name_Root (dir, update_dir);
106854431Speter	if (this_root != NULL)
106954431Speter	{
1070175282Sobrien	    if (findnode (root_directories, this_root->original))
107154431Speter	    {
1072175282Sobrien		process_this_directory = !strcmp (current_parsed_root->original,
1073175282Sobrien						  this_root->original);
1074175282Sobrien		free_cvsroot_t (this_root);
1075175282Sobrien	    }
1076175282Sobrien	    else
1077175282Sobrien	    {
107854431Speter		/* Add it to our list. */
107954431Speter
108054431Speter		Node *n = getnode ();
108166528Speter		n->type = NT_UNKNOWN;
1082175282Sobrien		n->key = xstrdup (this_root->original);
1083175282Sobrien		n->data = this_root;
108454431Speter
108554431Speter		if (addnode (root_directories, n))
1086175282Sobrien		    error (1, 0, "cannot add new CVSROOT %s",
1087175282Sobrien			   this_root->original);
108854431Speter
1089175282Sobrien		process_this_directory = 0;
109054431Speter	    }
109154431Speter	}
109254431Speter    }
109354431Speter
109417721Speter    /* call-back dir entry proc (if any) */
109532788Speter    if (dir_return == R_SKIP_ALL)
109632788Speter	;
109732788Speter    else if (frame->direntproc != NULL)
109854431Speter    {
109954431Speter	/* If we're doing the actual processing, call direntproc.
110054431Speter           Otherwise, assume that we need to process this directory
110154431Speter           and recurse. FIXME. */
110254431Speter
110354431Speter	if (process_this_directory)
110454431Speter	    dir_return = frame->direntproc (frame->callerdat, dir, newrepos,
110554431Speter					    update_dir, frent->entries);
110654431Speter	else
110754431Speter	    dir_return = R_PROCESS;
110854431Speter    }
110932788Speter    else
111032788Speter    {
111132788Speter	/* Generic behavior.  I don't see a reason to make the caller specify
111232788Speter	   a direntproc just to get this.  */
111332788Speter	if ((frame->which & W_LOCAL) && !isdir (dir))
111432788Speter	    dir_return = R_SKIP_ALL;
111532788Speter    }
111632788Speter
111725839Speter    free (newrepos);
111817721Speter
111917721Speter    /* only process the dir if the return code was 0 */
112017721Speter    if (dir_return != R_SKIP_ALL)
112117721Speter    {
112217721Speter	/* save our current directory and static vars */
112317721Speter        if (save_cwd (&cwd))
112425839Speter	    error_exit ();
112517721Speter	sdirlist = dirlist;
112617721Speter	srepository = repository;
112717721Speter	dirlist = NULL;
112817721Speter
112917721Speter	/* cd to the sub-directory */
1130128269Speter	if (CVS_CHDIR (dir) < 0)
113117721Speter	    error (1, errno, "could not chdir to %s", dir);
113217721Speter
113317721Speter	/* honor the global SKIP_DIRS (a.k.a. local) */
113425839Speter	if (frame->flags == R_SKIP_DIRS)
113517721Speter	    dir_return = R_SKIP_DIRS;
113617721Speter
113717721Speter	/* remember if the `.' will be stripped for subsequent dirs */
113817721Speter	if (strcmp (update_dir, ".") == 0)
113917721Speter	{
114017721Speter	    update_dir[0] = '\0';
114117721Speter	    stripped_dot = 1;
114217721Speter	}
114317721Speter
114417721Speter	/* make the recursive call */
114525839Speter	xframe = *frame;
114625839Speter	xframe.flags = dir_return;
1147128269Speter	/* Keep track of repository, really just for r* commands (rtag, rdiff,
1148128269Speter	 * co, ...) to tag_check_valid, since all the other commands use
1149128269Speter	 * CVS/Repository to figure it out per directory.
1150128269Speter	 */
1151128269Speter	if (repository)
1152128269Speter	{
1153128269Speter	    if (strcmp (dir, ".") == 0)
1154128269Speter		xframe.repository = xstrdup (repository);
1155128269Speter	    else
1156128269Speter	    {
1157128269Speter		xframe.repository = xmalloc (strlen (repository)
1158128269Speter					     + strlen (dir)
1159128269Speter					     + 2);
1160128269Speter		sprintf (xframe.repository, "%s/%s", repository, dir);
1161128269Speter	    }
1162128269Speter	}
1163128269Speter	else
1164128269Speter	    xframe.repository = NULL;
116525839Speter	err += do_recursion (&xframe);
1166128269Speter	if (xframe.repository)
1167128269Speter	{
1168128269Speter	    free (xframe.repository);
1169128269Speter	    xframe.repository = NULL;
1170128269Speter	}
117117721Speter
117217721Speter	/* put the `.' back if necessary */
117317721Speter	if (stripped_dot)
117417721Speter	    (void) strcpy (update_dir, ".");
117517721Speter
117617721Speter	/* call-back dir leave proc (if any) */
117754431Speter	if (process_this_directory && frame->dirleaveproc != NULL)
117825839Speter	    err = frame->dirleaveproc (frame->callerdat, dir, err, update_dir,
117925839Speter				       frent->entries);
118017721Speter
118117721Speter	/* get back to where we started and restore state vars */
118217721Speter	if (restore_cwd (&cwd, NULL))
118325839Speter	    error_exit ();
118417721Speter	free_cwd (&cwd);
118517721Speter	dirlist = sdirlist;
118617721Speter	repository = srepository;
118717721Speter    }
118817721Speter
118930337Speter    free (update_dir);
119030337Speter    update_dir = saved_update_dir;
119117721Speter
1192128269Speter    return err;
119317721Speter}
119417721Speter
119517721Speter/*
119617721Speter * Add a node to a list allocating the list if necessary.
119717721Speter */
119817721Speterstatic void
119917721Speteraddlist (listp, key)
120017721Speter    List **listp;
120117721Speter    char *key;
120217721Speter{
120317721Speter    Node *p;
120417721Speter
120517721Speter    if (*listp == NULL)
120617721Speter	*listp = getlist ();
120717721Speter    p = getnode ();
120817721Speter    p->type = FILES;
120917721Speter    p->key = xstrdup (key);
121017721Speter    if (addnode (*listp, p) != 0)
121117721Speter	freenode (p);
121217721Speter}
121317721Speter
121417721Speterstatic void
121517721Speteraddfile (listp, dir, file)
121617721Speter    List **listp;
121717721Speter    char *dir;
121817721Speter    char *file;
121917721Speter{
122017721Speter    Node *n;
122166528Speter    List *fl;
122217721Speter
122317721Speter    /* add this dir. */
122417721Speter    addlist (listp, dir);
122517721Speter
122617721Speter    n = findnode (*listp, dir);
122717721Speter    if (n == NULL)
122817721Speter    {
122917721Speter	error (1, 0, "can't find recently added dir node `%s' in start_recursion.",
123017721Speter	       dir);
123117721Speter    }
123217721Speter
123317721Speter    n->type = DIRS;
1234128269Speter    fl = n->data;
123566528Speter    addlist (&fl, file);
1236128269Speter    n->data = fl;
123717721Speter    return;
123817721Speter}
123917721Speter
124017721Speterstatic int
124117721Speterunroll_files_proc (p, closure)
124217721Speter    Node *p;
124317721Speter    void *closure;
124417721Speter{
124517721Speter    Node *n;
124617721Speter    struct recursion_frame *frame = (struct recursion_frame *) closure;
124717721Speter    int err = 0;
124817721Speter    List *save_dirlist;
124917721Speter    char *save_update_dir = NULL;
125017721Speter    struct saved_cwd cwd;
125117721Speter
125217721Speter    /* if this dir was also an explicitly named argument, then skip
125317721Speter       it.  We'll catch it later when we do dirs. */
125417721Speter    n = findnode (dirlist, p->key);
125517721Speter    if (n != NULL)
125617721Speter	return (0);
125717721Speter
125817721Speter    /* otherwise, call dorecusion for this list of files. */
1259128269Speter    filelist = p->data;
126025839Speter    p->data = NULL;
126117721Speter    save_dirlist = dirlist;
126217721Speter    dirlist = NULL;
126317721Speter
126417721Speter    if (strcmp(p->key, ".") != 0)
126517721Speter    {
126617721Speter        if (save_cwd (&cwd))
126725839Speter	    error_exit ();
126825839Speter	if ( CVS_CHDIR (p->key) < 0)
126917721Speter	    error (1, errno, "could not chdir to %s", p->key);
127017721Speter
127125839Speter	save_update_dir = update_dir;
127225839Speter	update_dir = xmalloc (strlen (save_update_dir)
127325839Speter				  + strlen (p->key)
127425839Speter				  + 5);
127525839Speter	strcpy (update_dir, save_update_dir);
127617721Speter
127717721Speter	if (*update_dir != '\0')
127817721Speter	    (void) strcat (update_dir, "/");
127917721Speter
128017721Speter	(void) strcat (update_dir, p->key);
128117721Speter    }
128217721Speter
128325839Speter    err += do_recursion (frame);
128417721Speter
128517721Speter    if (save_update_dir != NULL)
128617721Speter    {
128725839Speter	free (update_dir);
128825839Speter	update_dir = save_update_dir;
128917721Speter
129017721Speter	if (restore_cwd (&cwd, NULL))
129125839Speter	    error_exit ();
129217721Speter	free_cwd (&cwd);
129317721Speter    }
129417721Speter
129517721Speter    dirlist = save_dirlist;
129666528Speter    if (filelist)
129766528Speter	dellist (&filelist);
129817721Speter    return(err);
129917721Speter}
1300