recurse.c revision 32788
117721Speter/*
217721Speter * Copyright (c) 1992, Brian Berliner and Jeff Polk
317721Speter *
417721Speter * You may distribute under the terms of the GNU General Public License as
532788Speter * specified in the README file that comes with the CVS source distribution.
617721Speter *
717721Speter * General recursion handler
817721Speter *
917721Speter */
1017721Speter
1117721Speter#include "cvs.h"
1217721Speter#include "savecwd.h"
1317721Speter#include "fileattr.h"
1417721Speter#include "edit.h"
1517721Speter
1617721Speterstatic int do_dir_proc PROTO((Node * p, void *closure));
1717721Speterstatic int do_file_proc PROTO((Node * p, void *closure));
1817721Speterstatic void addlist PROTO((List ** listp, char *key));
1917721Speterstatic int unroll_files_proc PROTO((Node *p, void *closure));
2017721Speterstatic void addfile PROTO((List **listp, char *dir, char *file));
2117721Speter
2225839Speterstatic char *update_dir;
2317721Speterstatic char *repository = NULL;
2417721Speterstatic List *filelist = NULL; /* holds list of files on which to operate */
2517721Speterstatic List *dirlist = NULL; /* holds list of directories on which to operate */
2617721Speter
2717721Speterstruct recursion_frame {
2825839Speter    FILEPROC fileproc;
2925839Speter    FILESDONEPROC filesdoneproc;
3025839Speter    DIRENTPROC direntproc;
3125839Speter    DIRLEAVEPROC dirleaveproc;
3225839Speter    void *callerdat;
3325839Speter    Dtype flags;
3425839Speter    int which;
3525839Speter    int aflag;
3625839Speter    int readlock;
3725839Speter    int dosrcs;
3817721Speter};
3917721Speter
4025839Speterstatic int do_recursion PROTO ((struct recursion_frame *frame));
4125839Speter
4225839Speter/* I am half tempted to shove a struct file_info * into the struct
4325839Speter   recursion_frame (but then we would need to modify or create a
4425839Speter   recursion_frame for each file), or shove a struct recursion_frame *
4525839Speter   into the struct file_info (more tempting, although it isn't completely
4625839Speter   clear that the struct file_info should contain info about recursion
4725839Speter   processor internals).  So instead use this struct.  */
4825839Speter
4925839Speterstruct frame_and_file {
5025839Speter    struct recursion_frame *frame;
5125839Speter    struct file_info *finfo;
5225839Speter};
5325839Speter
5425839Speter/* Similarly, we need to pass the entries list to do_dir_proc.  */
5525839Speter
5625839Speterstruct frame_and_entries {
5725839Speter    struct recursion_frame *frame;
5825839Speter    List *entries;
5925839Speter};
6025839Speter
6125839Speter/* Start a recursive command.
6225839Speter
6325839Speter   Command line arguments (ARGC, ARGV) dictate the directories and
6425839Speter   files on which we operate.  In the special case of no arguments, we
6525839Speter   default to ".".  */
6617721Speterint
6725839Speterstart_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc, callerdat,
6817721Speter		 argc, argv, local, which, aflag, readlock,
6925839Speter		 update_preload, dosrcs)
7017721Speter    FILEPROC fileproc;
7117721Speter    FILESDONEPROC filesdoneproc;
7217721Speter    DIRENTPROC 	direntproc;
7317721Speter    DIRLEAVEPROC dirleaveproc;
7425839Speter    void *callerdat;
7525839Speter
7617721Speter    int argc;
7717721Speter    char **argv;
7817721Speter    int local;
7925839Speter
8025839Speter    /* This specifies the kind of recursion.  There are several cases:
8125839Speter
8225839Speter       1.  W_LOCAL is not set but W_REPOS or W_ATTIC is.  The current
8325839Speter       directory when we are called must be the repository and
8425839Speter       recursion proceeds according to what exists in the repository.
8525839Speter
8625839Speter       2a.  W_LOCAL is set but W_REPOS and W_ATTIC are not.  The
8725839Speter       current directory when we are called must be the working
8825839Speter       directory.  Recursion proceeds according to what exists in the
8925839Speter       working directory, never (I think) consulting any part of the
9025839Speter       repository which does not correspond to the working directory
9125839Speter       ("correspond" == Name_Repository).
9225839Speter
9325839Speter       2b.  W_LOCAL is set and so is W_REPOS or W_ATTIC.  This is the
9425839Speter       weird one.  The current directory when we are called must be
9525839Speter       the working directory.  We recurse through working directories,
9625839Speter       but we recurse into a directory if it is exists in the working
9725839Speter       directory *or* it exists in the repository.  If a directory
9825839Speter       does not exist in the working directory, the direntproc must
9925839Speter       either tell us to skip it (R_SKIP_ALL), or must create it (I
10025839Speter       think those are the only two cases).  */
10117721Speter    int which;
10225839Speter
10317721Speter    int aflag;
10417721Speter    int readlock;
10517721Speter    char *update_preload;
10617721Speter    int dosrcs;
10717721Speter{
10817721Speter    int i, err = 0;
10917721Speter    List *files_by_dir = NULL;
11017721Speter    struct recursion_frame frame;
11117721Speter
11225839Speter    frame.fileproc = fileproc;
11325839Speter    frame.filesdoneproc = filesdoneproc;
11425839Speter    frame.direntproc = direntproc;
11525839Speter    frame.dirleaveproc = dirleaveproc;
11625839Speter    frame.callerdat = callerdat;
11725839Speter    frame.flags = local ? R_SKIP_DIRS : R_PROCESS;
11825839Speter    frame.which = which;
11925839Speter    frame.aflag = aflag;
12025839Speter    frame.readlock = readlock;
12125839Speter    frame.dosrcs = dosrcs;
12225839Speter
12317721Speter    expand_wild (argc, argv, &argc, &argv);
12417721Speter
12517721Speter    if (update_preload == NULL)
12625839Speter	update_dir = xstrdup ("");
12717721Speter    else
12825839Speter	update_dir = xstrdup (update_preload);
12917721Speter
13017721Speter    /* clean up from any previous calls to start_recursion */
13117721Speter    if (repository)
13217721Speter    {
13317721Speter	free (repository);
13417721Speter	repository = (char *) NULL;
13517721Speter    }
13617721Speter    if (filelist)
13717721Speter	dellist (&filelist); /* FIXME-krp: no longer correct. */
13817721Speter    if (dirlist)
13917721Speter	dellist (&dirlist);
14017721Speter
14125839Speter#ifdef SERVER_SUPPORT
14225839Speter    if (server_active)
14325839Speter    {
14425839Speter	for (i = 0; i < argc; ++i)
14525839Speter	    server_pathname_check (argv[i]);
14625839Speter    }
14725839Speter#endif
14825839Speter
14917721Speter    if (argc == 0)
15017721Speter    {
15117721Speter
15217721Speter	/*
15317721Speter	 * There were no arguments, so we'll probably just recurse. The
15417721Speter	 * exception to the rule is when we are called from a directory
15517721Speter	 * without any CVS administration files.  That has always meant to
15617721Speter	 * process each of the sub-directories, so we pretend like we were
15717721Speter	 * called with the list of sub-dirs of the current dir as args
15817721Speter	 */
15917721Speter	if ((which & W_LOCAL) && !isdir (CVSADM))
16032788Speter	{
16125839Speter	    dirlist = Find_Directories ((char *) NULL, W_LOCAL, (List *) NULL);
16232788Speter	    /* If there are no sub-directories, there is a certain logic in
16332788Speter	       favor of doing nothing, but in fact probably the user is just
16432788Speter	       confused about what directory they are in, or whether they
16532788Speter	       cvs add'd a new directory.  In the case of at least one
16632788Speter	       sub-directory, at least when we recurse into them we
16732788Speter	       notice (hopefully) whether they are under CVS control.  */
16832788Speter	    if (list_isempty (dirlist))
16932788Speter	    {
17032788Speter		if (update_dir[0] == '\0')
17132788Speter		    error (0, 0, "in directory .:");
17232788Speter		else
17332788Speter		    error (0, 0, "in directory %s:", update_dir);
17432788Speter		error (1, 0,
17532788Speter		       "there is no version here; run '%s checkout' first",
17632788Speter		       program_name);
17732788Speter	    }
17832788Speter	}
17917721Speter	else
18017721Speter	    addlist (&dirlist, ".");
18117721Speter
18225839Speter	err += do_recursion (&frame);
18325839Speter	goto out;
18417721Speter    }
18517721Speter
18617721Speter
18717721Speter    /*
18817721Speter     * There were arguments, so we have to handle them by hand. To do
18917721Speter     * that, we set up the filelist and dirlist with the arguments and
19017721Speter     * call do_recursion.  do_recursion recognizes the fact that the
19117721Speter     * lists are non-null when it starts and doesn't update them.
19217721Speter     *
19317721Speter     * explicitly named directories are stored in dirlist.
19417721Speter     * explicitly named files are stored in filelist.
19517721Speter     * other possibility is named entities whicha are not currently in
19617721Speter     * the working directory.
19717721Speter     */
19817721Speter
19917721Speter    for (i = 0; i < argc; i++)
20017721Speter    {
20117721Speter	/* if this argument is a directory, then add it to the list of
20217721Speter	   directories. */
20317721Speter
20417721Speter	if (!wrap_name_has (argv[i], WRAP_TOCVS) && isdir (argv[i]))
20517721Speter	    addlist (&dirlist, argv[i]);
20617721Speter	else
20717721Speter	{
20817721Speter	    /* otherwise, split argument into directory and component names. */
20917721Speter	    char *dir;
21017721Speter	    char *comp;
21117721Speter	    char *file_to_try;
21217721Speter
21317721Speter	    /* Now break out argv[i] into directory part (DIR) and file part (COMP).
21417721Speter		   DIR and COMP will each point to a newly malloc'd string.  */
21517721Speter	    dir = xstrdup (argv[i]);
21617721Speter	    comp = last_component (dir);
21717721Speter	    if (comp == dir)
21817721Speter	    {
21917721Speter		/* no dir component.  What we have is an implied "./" */
22017721Speter		dir = xstrdup(".");
22117721Speter	    }
22217721Speter	    else
22317721Speter	    {
22417721Speter		char *p = comp;
22517721Speter
22617721Speter		p[-1] = '\0';
22717721Speter		comp = xstrdup (p);
22817721Speter	    }
22917721Speter
23017721Speter	    /* if this argument exists as a file in the current
23117721Speter	       working directory tree, then add it to the files list.  */
23217721Speter
23325839Speter	    if (!(which & W_LOCAL))
23417721Speter	    {
23517721Speter		/* If doing rtag, we've done a chdir to the repository. */
23625839Speter		file_to_try = xmalloc (strlen (argv[i]) + sizeof (RCSEXT) + 5);
23725839Speter		sprintf (file_to_try, "%s%s", argv[i], RCSEXT);
23817721Speter	    }
23917721Speter	    else
24025839Speter		file_to_try = xstrdup (argv[i]);
24117721Speter
24225839Speter	    if (isfile (file_to_try))
24317721Speter		addfile (&files_by_dir, dir, comp);
24417721Speter	    else if (isdir (dir))
24517721Speter	    {
24625839Speter		if ((which & W_LOCAL) && isdir (CVSADM)
24725839Speter#ifdef CLIENT_SUPPORT
24825839Speter		    && !client_active
24925839Speter#endif
25025839Speter		    )
25117721Speter		{
25217721Speter		    /* otherwise, look for it in the repository. */
25325839Speter		    char *tmp_update_dir;
25417721Speter		    char *repos;
25525839Speter		    char *reposfile;
25617721Speter
25725839Speter		    tmp_update_dir = xmalloc (strlen (update_dir)
25825839Speter					      + strlen (dir)
25925839Speter					      + 5);
26025839Speter		    strcpy (tmp_update_dir, update_dir);
26117721Speter
26225839Speter		    if (*tmp_update_dir != '\0')
26325839Speter			(void) strcat (tmp_update_dir, "/");
26425839Speter
26525839Speter		    (void) strcat (tmp_update_dir, dir);
26625839Speter
26717721Speter		    /* look for it in the repository. */
26825839Speter		    repos = Name_Repository (dir, tmp_update_dir);
26925839Speter		    reposfile = xmalloc (strlen (repos)
27025839Speter					 + strlen (comp)
27125839Speter					 + 5);
27225839Speter		    (void) sprintf (reposfile, "%s/%s", repos, comp);
27317721Speter		    free (repos);
27417721Speter
27525839Speter		    if (!wrap_name_has (comp, WRAP_TOCVS) && isdir (reposfile))
27617721Speter			addlist (&dirlist, argv[i]);
27717721Speter		    else
27817721Speter			addfile (&files_by_dir, dir, comp);
27917721Speter
28025839Speter		    free (tmp_update_dir);
28125839Speter		    free (reposfile);
28217721Speter		}
28317721Speter		else
28417721Speter		    addfile (&files_by_dir, dir, comp);
28517721Speter	    }
28617721Speter	    else
28717721Speter		error (1, 0, "no such directory `%s'", dir);
28817721Speter
28925839Speter	    free (file_to_try);
29017721Speter	    free (dir);
29117721Speter	    free (comp);
29217721Speter	}
29317721Speter    }
29417721Speter
29517721Speter    /* At this point we have looped over all named arguments and built
29617721Speter       a coupla lists.  Now we unroll the lists, setting up and
29717721Speter       calling do_recursion. */
29817721Speter
29917721Speter    err += walklist (files_by_dir, unroll_files_proc, (void *) &frame);
30025839Speter    dellist(&files_by_dir);
30117721Speter
30217721Speter    /* then do_recursion on the dirlist. */
30317721Speter    if (dirlist != NULL)
30425839Speter	err += do_recursion (&frame);
30517721Speter
30617721Speter    /* Free the data which expand_wild allocated.  */
30726065Speter    free_names (&argc, argv);
30817721Speter
30925839Speter out:
31025839Speter    free (update_dir);
31125839Speter    update_dir = NULL;
31217721Speter    return (err);
31317721Speter}
31417721Speter
31517721Speter/*
31617721Speter * Implement the recursive policies on the local directory.  This may be
31717721Speter * called directly, or may be called by start_recursion
31817721Speter */
31925839Speterstatic int
32025839Speterdo_recursion (frame)
32125839Speter    struct recursion_frame *frame;
32217721Speter{
32317721Speter    int err = 0;
32417721Speter    int dodoneproc = 1;
32517721Speter    char *srepository;
32617721Speter    List *entries = NULL;
32725839Speter    int should_readlock;
32817721Speter
32917721Speter    /* do nothing if told */
33025839Speter    if (frame->flags == R_SKIP_ALL)
33117721Speter	return (0);
33217721Speter
33325839Speter    should_readlock = noexec ? 0 : frame->readlock;
33417721Speter
33517721Speter    /* The fact that locks are not active here is what makes us fail to have
33617721Speter       the
33717721Speter
33817721Speter           If someone commits some changes in one cvs command,
33917721Speter	   then an update by someone else will either get all the
34017721Speter	   changes, or none of them.
34117721Speter
34217721Speter       property (see node Concurrency in cvs.texinfo).
34317721Speter
34417721Speter       The most straightforward fix would just to readlock the whole
34517721Speter       tree before starting an update, but that means that if a commit
34617721Speter       gets blocked on a big update, it might need to wait a *long*
34717721Speter       time.
34817721Speter
34917721Speter       A more adequate fix would be a two-pass design for update,
35017721Speter       checkout, etc.  The first pass would go through the repository,
35117721Speter       with the whole tree readlocked, noting what versions of each
35217721Speter       file we want to get.  The second pass would release all locks
35317721Speter       (except perhaps short-term locks on one file at a
35417721Speter       time--although I think RCS already deals with this) and
35517721Speter       actually get the files, specifying the particular versions it wants.
35617721Speter
35717721Speter       This could be sped up by separating out the data needed for the
35817721Speter       first pass into a separate file(s)--for example a file
35917721Speter       attribute for each file whose value contains the head revision
36017721Speter       for each branch.  The structure should be designed so that
36117721Speter       commit can relatively quickly update the information for a
36217721Speter       single file or a handful of files (file attributes, as
36317721Speter       implemented in Jan 96, are probably acceptable; improvements
36417721Speter       would be possible such as branch attributes which are in
36517721Speter       separate files for each branch).  */
36617721Speter
36717721Speter#if defined(SERVER_SUPPORT) && defined(SERVER_FLOWCONTROL)
36817721Speter    /*
36917721Speter     * Now would be a good time to check to see if we need to stop
37017721Speter     * generating data, to give the buffers a chance to drain to the
37117721Speter     * remote client.  We should not have locks active at this point.
37217721Speter     */
37317721Speter    if (server_active
37417721Speter	/* If there are writelocks around, we cannot pause here.  */
37525839Speter	&& (should_readlock || noexec))
37617721Speter	server_pause_check();
37717721Speter#endif
37817721Speter
37917721Speter    /*
38017721Speter     * Fill in repository with the current repository
38117721Speter     */
38225839Speter    if (frame->which & W_LOCAL)
38317721Speter    {
38417721Speter	if (isdir (CVSADM))
38517721Speter	    repository = Name_Repository ((char *) NULL, update_dir);
38617721Speter	else
38717721Speter	    repository = NULL;
38817721Speter    }
38917721Speter    else
39017721Speter    {
39125839Speter	repository = xgetwd ();
39225839Speter	if (repository == NULL)
39325839Speter	    error (1, errno, "could not get working directory");
39417721Speter    }
39517721Speter    srepository = repository;		/* remember what to free */
39617721Speter
39717721Speter    fileattr_startdir (repository);
39817721Speter
39917721Speter    /*
40017721Speter     * The filesdoneproc needs to be called for each directory where files
40117721Speter     * processed, or each directory that is processed by a call where no
40217721Speter     * directories were passed in.  In fact, the only time we don't want to
40317721Speter     * call back the filesdoneproc is when we are processing directories that
40417721Speter     * were passed in on the command line (or in the special case of `.' when
40517721Speter     * we were called with no args
40617721Speter     */
40717721Speter    if (dirlist != NULL && filelist == NULL)
40817721Speter	dodoneproc = 0;
40917721Speter
41017721Speter    /*
41117721Speter     * If filelist or dirlist is already set, we don't look again. Otherwise,
41217721Speter     * find the files and directories
41317721Speter     */
41417721Speter    if (filelist == NULL && dirlist == NULL)
41517721Speter    {
41617721Speter	/* both lists were NULL, so start from scratch */
41725839Speter	if (frame->fileproc != NULL && frame->flags != R_SKIP_FILES)
41817721Speter	{
41925839Speter	    int lwhich = frame->which;
42017721Speter
42117721Speter	    /* be sure to look in the attic if we have sticky tags/date */
42217721Speter	    if ((lwhich & W_ATTIC) == 0)
42317721Speter		if (isreadable (CVSADM_TAG))
42417721Speter		    lwhich |= W_ATTIC;
42517721Speter
42625839Speter	    /* In the !(which & W_LOCAL) case, we filled in repository
42725839Speter	       earlier in the function.  In the (which & W_LOCAL) case,
42825839Speter	       the Find_Names function is going to look through the
42925839Speter	       Entries file.  If we do not have a repository, that
43025839Speter	       does not make sense, so we insist upon having a
43125839Speter	       repository at this point.  Name_Repository will give a
43225839Speter	       reasonable error message.  */
43325839Speter	    if (repository == NULL)
43425839Speter		repository = Name_Repository ((char *) NULL, update_dir);
43525839Speter
43617721Speter	    /* find the files and fill in entries if appropriate */
43725839Speter	    filelist = Find_Names (repository, lwhich, frame->aflag, &entries);
43817721Speter	}
43917721Speter
44017721Speter	/* find sub-directories if we will recurse */
44125839Speter	if (frame->flags != R_SKIP_DIRS)
44225839Speter	    dirlist = Find_Directories (repository, frame->which, entries);
44317721Speter    }
44417721Speter    else
44517721Speter    {
44617721Speter	/* something was passed on the command line */
44725839Speter	if (filelist != NULL && frame->fileproc != NULL)
44817721Speter	{
44917721Speter	    /* we will process files, so pre-parse entries */
45025839Speter	    if (frame->which & W_LOCAL)
45125839Speter		entries = Entries_Open (frame->aflag);
45217721Speter	}
45317721Speter    }
45417721Speter
45517721Speter    /* process the files (if any) */
45625839Speter    if (filelist != NULL && frame->fileproc)
45717721Speter    {
45817721Speter	struct file_info finfo_struct;
45925839Speter	struct frame_and_file frfile;
46017721Speter
46117721Speter	/* read lock it if necessary */
46225839Speter	if (should_readlock && repository && Reader_Lock (repository) != 0)
46317721Speter	    error (1, 0, "read lock failed - giving up");
46417721Speter
46517721Speter#ifdef CLIENT_SUPPORT
46617721Speter	/* For the server, we handle notifications in a completely different
46717721Speter	   place (server_notify).  For local, we can't do them here--we don't
46817721Speter	   have writelocks in place, and there is no way to get writelocks
46917721Speter	   here.  */
47017721Speter	if (client_active)
47117721Speter	    notify_check (repository, update_dir);
47217721Speter#endif /* CLIENT_SUPPORT */
47317721Speter
47417721Speter	finfo_struct.repository = repository;
47517721Speter	finfo_struct.update_dir = update_dir;
47617721Speter	finfo_struct.entries = entries;
47717721Speter	/* do_file_proc will fill in finfo_struct.file.  */
47817721Speter
47925839Speter	frfile.finfo = &finfo_struct;
48025839Speter	frfile.frame = frame;
48125839Speter
48217721Speter	/* process the files */
48325839Speter	err += walklist (filelist, do_file_proc, &frfile);
48417721Speter
48517721Speter	/* unlock it */
48625839Speter	if (should_readlock)
48717721Speter	    Lock_Cleanup ();
48817721Speter
48917721Speter	/* clean up */
49017721Speter	dellist (&filelist);
49117721Speter    }
49217721Speter
49317721Speter    /* call-back files done proc (if any) */
49425839Speter    if (dodoneproc && frame->filesdoneproc != NULL)
49525839Speter	err = frame->filesdoneproc (frame->callerdat, err, repository,
49625839Speter				    update_dir[0] ? update_dir : ".",
49725839Speter				    entries);
49817721Speter
49917721Speter    fileattr_write ();
50017721Speter    fileattr_free ();
50117721Speter
50217721Speter    /* process the directories (if necessary) */
50317721Speter    if (dirlist != NULL)
50425839Speter    {
50525839Speter	struct frame_and_entries frent;
50625839Speter
50725839Speter	frent.frame = frame;
50825839Speter	frent.entries = entries;
50925839Speter	err += walklist (dirlist, do_dir_proc, (void *) &frent);
51025839Speter    }
51125839Speter#if 0
51225839Speter    else if (frame->dirleaveproc != NULL)
51325839Speter	err += frame->dirleaveproc (frame->callerdat, ".", err, ".");
51417721Speter#endif
51517721Speter    dellist (&dirlist);
51617721Speter
51725839Speter    if (entries)
51825839Speter    {
51925839Speter	Entries_Close (entries);
52025839Speter	entries = NULL;
52125839Speter    }
52225839Speter
52317721Speter    /* free the saved copy of the pointer if necessary */
52417721Speter    if (srepository)
52517721Speter    {
52617721Speter	free (srepository);
52717721Speter	repository = (char *) NULL;
52817721Speter    }
52917721Speter
53017721Speter    return (err);
53117721Speter}
53217721Speter
53317721Speter/*
53417721Speter * Process each of the files in the list with the callback proc
53517721Speter */
53617721Speterstatic int
53717721Speterdo_file_proc (p, closure)
53817721Speter    Node *p;
53917721Speter    void *closure;
54017721Speter{
54125839Speter    struct frame_and_file *frfile = (struct frame_and_file *)closure;
54225839Speter    struct file_info *finfo = frfile->finfo;
54317721Speter    int ret;
54417721Speter
54517721Speter    finfo->file = p->key;
54617721Speter    finfo->fullname = xmalloc (strlen (finfo->file)
54717721Speter			       + strlen (finfo->update_dir)
54817721Speter			       + 2);
54917721Speter    finfo->fullname[0] = '\0';
55017721Speter    if (finfo->update_dir[0] != '\0')
55117721Speter    {
55217721Speter	strcat (finfo->fullname, finfo->update_dir);
55317721Speter	strcat (finfo->fullname, "/");
55417721Speter    }
55517721Speter    strcat (finfo->fullname, finfo->file);
55617721Speter
55725839Speter    if (frfile->frame->dosrcs && repository)
55817721Speter	finfo->rcs = RCS_parse (finfo->file, repository);
55917721Speter    else
56017721Speter        finfo->rcs = (RCSNode *) NULL;
56125839Speter    ret = frfile->frame->fileproc (frfile->frame->callerdat, finfo);
56217721Speter
56317721Speter    freercsnode(&finfo->rcs);
56417721Speter    free (finfo->fullname);
56517721Speter
56625839Speter    /* Allow the user to monitor progress with tail -f.  Doing this once
56725839Speter       per file should be no big deal, but we don't want the performance
56825839Speter       hit of flushing on every line like previous versions of CVS.  */
56925839Speter    cvs_flushout ();
57025839Speter
57117721Speter    return (ret);
57217721Speter}
57317721Speter
57417721Speter/*
57517721Speter * Process each of the directories in the list (recursing as we go)
57617721Speter */
57717721Speterstatic int
57817721Speterdo_dir_proc (p, closure)
57917721Speter    Node *p;
58017721Speter    void *closure;
58117721Speter{
58225839Speter    struct frame_and_entries *frent = (struct frame_and_entries *) closure;
58325839Speter    struct recursion_frame *frame = frent->frame;
58425839Speter    struct recursion_frame xframe;
58517721Speter    char *dir = p->key;
58625839Speter    char *newrepos;
58717721Speter    List *sdirlist;
58817721Speter    char *srepository;
58917721Speter    char *cp;
59017721Speter    Dtype dir_return = R_PROCESS;
59117721Speter    int stripped_dot = 0;
59217721Speter    int err = 0;
59317721Speter    struct saved_cwd cwd;
59425839Speter    char *saved_update_dir;
59517721Speter
59625839Speter    if (fncmp (dir, CVSADM) == 0)
59725839Speter    {
59825839Speter	/* This seems to most often happen when users (beginning users,
59925839Speter	   generally), try "cvs ci *" or something similar.  On that
60025839Speter	   theory, it is possible that we should just silently skip the
60125839Speter	   CVSADM directories, but on the other hand, using a wildcard
60225839Speter	   like this isn't necessarily a practice to encourage (it operates
60325839Speter	   only on files which exist in the working directory, unlike
60425839Speter	   regular CVS recursion).  */
60525839Speter
60625839Speter	/* FIXME-reentrancy: printed_cvs_msg should be in a "command
60725839Speter	   struct" or some such, so that it gets cleared for each new
60825839Speter	   command (this is possible using the remote protocol and a
60925839Speter	   custom-written client).  The struct recursion_frame is not
61025839Speter	   far back enough though, some commands (commit at least)
61125839Speter	   will call start_recursion several times.  An alternate solution
61225839Speter	   would be to take this whole check and move it to a new function
61325839Speter	   validate_arguments or some such that all the commands call
61425839Speter	   and which snips the offending directory from the argc,argv
61525839Speter	   vector.  */
61625839Speter	static int printed_cvs_msg = 0;
61725839Speter	if (!printed_cvs_msg)
61825839Speter	{
61925839Speter	    error (0, 0, "warning: directory %s specified in argument",
62025839Speter		   dir);
62125839Speter	    error (0, 0, "\
62225839Speterbut CVS uses %s for its own purposes; skipping %s directory",
62325839Speter		   CVSADM, dir);
62425839Speter	    printed_cvs_msg = 1;
62525839Speter	}
62625839Speter	return 0;
62725839Speter    }
62825839Speter
62925839Speter    saved_update_dir = update_dir;
63025839Speter    update_dir = xmalloc (strlen (saved_update_dir)
63125839Speter			  + strlen (dir)
63225839Speter			  + 5);
63325839Speter    strcpy (update_dir, saved_update_dir);
63425839Speter
63517721Speter    /* set up update_dir - skip dots if not at start */
63617721Speter    if (strcmp (dir, ".") != 0)
63717721Speter    {
63817721Speter	if (update_dir[0] != '\0')
63917721Speter	{
64017721Speter	    (void) strcat (update_dir, "/");
64117721Speter	    (void) strcat (update_dir, dir);
64217721Speter	}
64317721Speter	else
64417721Speter	    (void) strcpy (update_dir, dir);
64517721Speter
64617721Speter	/*
64717721Speter	 * Here we need a plausible repository name for the sub-directory. We
64817721Speter	 * create one by concatenating the new directory name onto the
64917721Speter	 * previous repository name.  The only case where the name should be
65017721Speter	 * used is in the case where we are creating a new sub-directory for
65117721Speter	 * update -d and in that case the generated name will be correct.
65217721Speter	 */
65317721Speter	if (repository == NULL)
65425839Speter	    newrepos = xstrdup ("");
65517721Speter	else
65625839Speter	{
65725839Speter	    newrepos = xmalloc (strlen (repository) + strlen (dir) + 5);
65832788Speter	    sprintf (newrepos, "%s/%s", repository, dir);
65925839Speter	}
66017721Speter    }
66117721Speter    else
66217721Speter    {
66317721Speter	if (update_dir[0] == '\0')
66417721Speter	    (void) strcpy (update_dir, dir);
66517721Speter
66617721Speter	if (repository == NULL)
66725839Speter	    newrepos = xstrdup ("");
66817721Speter	else
66925839Speter	    newrepos = xstrdup (repository);
67017721Speter    }
67117721Speter
67232788Speter    /* Check to see that the CVSADM directory, if it exists, seems to be
67332788Speter       well-formed.  It can be missing files if the user hit ^C in the
67432788Speter       middle of a previous run.  We want to (a) make this a nonfatal
67532788Speter       error, and (b) make sure we print which directory has the
67632788Speter       problem.
67732788Speter
67832788Speter       Do this before the direntproc, so that (1) the direntproc
67932788Speter       doesn't have to guess/deduce whether we will skip the directory
68032788Speter       (e.g. send_dirent_proc and whether to send the directory), and
68132788Speter       (2) so that the warm fuzzy doesn't get printed if we skip the
68232788Speter       directory.  */
68332788Speter    if (frame->which & W_LOCAL)
68432788Speter    {
68532788Speter	char *cvsadmdir;
68632788Speter
68732788Speter	cvsadmdir = xmalloc (strlen (dir)
68832788Speter			     + sizeof (CVSADM_REP)
68932788Speter			     + sizeof (CVSADM_ENT)
69032788Speter			     + 80);
69132788Speter
69232788Speter	strcpy (cvsadmdir, dir);
69332788Speter	strcat (cvsadmdir, "/");
69432788Speter	strcat (cvsadmdir, CVSADM);
69532788Speter	if (isdir (cvsadmdir))
69632788Speter	{
69732788Speter	    strcpy (cvsadmdir, dir);
69832788Speter	    strcat (cvsadmdir, "/");
69932788Speter	    strcat (cvsadmdir, CVSADM_REP);
70032788Speter	    if (!isfile (cvsadmdir))
70132788Speter	    {
70232788Speter		/* Some commands like update may have printed "? foo" but
70332788Speter		   if we were planning to recurse, and don't on account of
70432788Speter		   CVS/Repository, we want to say why.  */
70532788Speter		error (0, 0, "ignoring %s (%s missing)", update_dir,
70632788Speter		       CVSADM_REP);
70732788Speter		dir_return = R_SKIP_ALL;
70832788Speter	    }
70932788Speter
71032788Speter	    /* Likewise for CVS/Entries.  */
71132788Speter	    if (dir_return != R_SKIP_ALL)
71232788Speter	    {
71332788Speter		strcpy (cvsadmdir, dir);
71432788Speter		strcat (cvsadmdir, "/");
71532788Speter		strcat (cvsadmdir, CVSADM_ENT);
71632788Speter		if (!isfile (cvsadmdir))
71732788Speter		{
71832788Speter		    /* Some commands like update may have printed "? foo" but
71932788Speter		       if we were planning to recurse, and don't on account of
72032788Speter		       CVS/Repository, we want to say why.  */
72132788Speter		    error (0, 0, "ignoring %s (%s missing)", update_dir,
72232788Speter			   CVSADM_ENT);
72332788Speter		    dir_return = R_SKIP_ALL;
72432788Speter		}
72532788Speter	    }
72632788Speter	}
72732788Speter	free (cvsadmdir);
72832788Speter    }
72932788Speter
73017721Speter    /* call-back dir entry proc (if any) */
73132788Speter    if (dir_return == R_SKIP_ALL)
73232788Speter	;
73332788Speter    else if (frame->direntproc != NULL)
73425839Speter	dir_return = frame->direntproc (frame->callerdat, dir, newrepos,
73525839Speter					update_dir, frent->entries);
73632788Speter    else
73732788Speter    {
73832788Speter	/* Generic behavior.  I don't see a reason to make the caller specify
73932788Speter	   a direntproc just to get this.  */
74032788Speter	if ((frame->which & W_LOCAL) && !isdir (dir))
74132788Speter	    dir_return = R_SKIP_ALL;
74232788Speter    }
74332788Speter
74425839Speter    free (newrepos);
74517721Speter
74617721Speter    /* only process the dir if the return code was 0 */
74717721Speter    if (dir_return != R_SKIP_ALL)
74817721Speter    {
74917721Speter	/* save our current directory and static vars */
75017721Speter        if (save_cwd (&cwd))
75125839Speter	    error_exit ();
75217721Speter	sdirlist = dirlist;
75317721Speter	srepository = repository;
75417721Speter	dirlist = NULL;
75517721Speter
75617721Speter	/* cd to the sub-directory */
75725839Speter	if ( CVS_CHDIR (dir) < 0)
75817721Speter	    error (1, errno, "could not chdir to %s", dir);
75917721Speter
76017721Speter	/* honor the global SKIP_DIRS (a.k.a. local) */
76125839Speter	if (frame->flags == R_SKIP_DIRS)
76217721Speter	    dir_return = R_SKIP_DIRS;
76317721Speter
76417721Speter	/* remember if the `.' will be stripped for subsequent dirs */
76517721Speter	if (strcmp (update_dir, ".") == 0)
76617721Speter	{
76717721Speter	    update_dir[0] = '\0';
76817721Speter	    stripped_dot = 1;
76917721Speter	}
77017721Speter
77117721Speter	/* make the recursive call */
77225839Speter	xframe = *frame;
77325839Speter	xframe.flags = dir_return;
77425839Speter	err += do_recursion (&xframe);
77517721Speter
77617721Speter	/* put the `.' back if necessary */
77717721Speter	if (stripped_dot)
77817721Speter	    (void) strcpy (update_dir, ".");
77917721Speter
78017721Speter	/* call-back dir leave proc (if any) */
78125839Speter	if (frame->dirleaveproc != NULL)
78225839Speter	    err = frame->dirleaveproc (frame->callerdat, dir, err, update_dir,
78325839Speter				       frent->entries);
78417721Speter
78517721Speter	/* get back to where we started and restore state vars */
78617721Speter	if (restore_cwd (&cwd, NULL))
78725839Speter	    error_exit ();
78817721Speter	free_cwd (&cwd);
78917721Speter	dirlist = sdirlist;
79017721Speter	repository = srepository;
79117721Speter    }
79217721Speter
79330337Speter#if 0
79425839Speter    /* Put back update_dir.  I think this is the same as just setting
79525839Speter       update_dir back to saved_update_dir, but there are a few cases I'm
79625839Speter       not sure about (in particular, if DIR is "." and update_dir is
79725839Speter       not ""), so for conservatism I'm leaving this here.  */
79817721Speter    cp = last_component (update_dir);
79917721Speter    if (cp > update_dir)
80017721Speter	cp[-1] = '\0';
80117721Speter    else
80217721Speter	update_dir[0] = '\0';
80325839Speter    free (saved_update_dir);
80430337Speter#else
80530337Speter    /* The above code is cactus!!! - it doesn't handle descending
80630337Speter       multiple directories at once!  ie: it recurses down several
80730337Speter       dirs and then back up one. This breaks 'diff', 'update',
80830337Speter       'commit', etc.  */
80930337Speter    free (update_dir);
81030337Speter    update_dir = saved_update_dir;
81130337Speter#endif
81217721Speter
81317721Speter    return (err);
81417721Speter}
81517721Speter
81617721Speter/*
81717721Speter * Add a node to a list allocating the list if necessary.
81817721Speter */
81917721Speterstatic void
82017721Speteraddlist (listp, key)
82117721Speter    List **listp;
82217721Speter    char *key;
82317721Speter{
82417721Speter    Node *p;
82517721Speter
82617721Speter    if (*listp == NULL)
82717721Speter	*listp = getlist ();
82817721Speter    p = getnode ();
82917721Speter    p->type = FILES;
83017721Speter    p->key = xstrdup (key);
83117721Speter    if (addnode (*listp, p) != 0)
83217721Speter	freenode (p);
83317721Speter}
83417721Speter
83517721Speterstatic void
83617721Speteraddfile (listp, dir, file)
83717721Speter    List **listp;
83817721Speter    char *dir;
83917721Speter    char *file;
84017721Speter{
84117721Speter    Node *n;
84217721Speter
84317721Speter    /* add this dir. */
84417721Speter    addlist (listp, dir);
84517721Speter
84617721Speter    n = findnode (*listp, dir);
84717721Speter    if (n == NULL)
84817721Speter    {
84917721Speter	error (1, 0, "can't find recently added dir node `%s' in start_recursion.",
85017721Speter	       dir);
85117721Speter    }
85217721Speter
85317721Speter    n->type = DIRS;
85417721Speter    addlist ((List **) &n->data, file);
85517721Speter    return;
85617721Speter}
85717721Speter
85817721Speterstatic int
85917721Speterunroll_files_proc (p, closure)
86017721Speter    Node *p;
86117721Speter    void *closure;
86217721Speter{
86317721Speter    Node *n;
86417721Speter    struct recursion_frame *frame = (struct recursion_frame *) closure;
86517721Speter    int err = 0;
86617721Speter    List *save_dirlist;
86717721Speter    char *save_update_dir = NULL;
86817721Speter    struct saved_cwd cwd;
86917721Speter
87017721Speter    /* if this dir was also an explicitly named argument, then skip
87117721Speter       it.  We'll catch it later when we do dirs. */
87217721Speter    n = findnode (dirlist, p->key);
87317721Speter    if (n != NULL)
87417721Speter	return (0);
87517721Speter
87617721Speter    /* otherwise, call dorecusion for this list of files. */
87717721Speter    filelist = (List *) p->data;
87825839Speter    p->data = NULL;
87917721Speter    save_dirlist = dirlist;
88017721Speter    dirlist = NULL;
88117721Speter
88217721Speter    if (strcmp(p->key, ".") != 0)
88317721Speter    {
88417721Speter        if (save_cwd (&cwd))
88525839Speter	    error_exit ();
88625839Speter	if ( CVS_CHDIR (p->key) < 0)
88717721Speter	    error (1, errno, "could not chdir to %s", p->key);
88817721Speter
88925839Speter	save_update_dir = update_dir;
89025839Speter	update_dir = xmalloc (strlen (save_update_dir)
89125839Speter				  + strlen (p->key)
89225839Speter				  + 5);
89325839Speter	strcpy (update_dir, save_update_dir);
89417721Speter
89517721Speter	if (*update_dir != '\0')
89617721Speter	    (void) strcat (update_dir, "/");
89717721Speter
89817721Speter	(void) strcat (update_dir, p->key);
89917721Speter    }
90017721Speter
90125839Speter    err += do_recursion (frame);
90217721Speter
90317721Speter    if (save_update_dir != NULL)
90417721Speter    {
90525839Speter	free (update_dir);
90625839Speter	update_dir = save_update_dir;
90717721Speter
90817721Speter	if (restore_cwd (&cwd, NULL))
90925839Speter	    error_exit ();
91017721Speter	free_cwd (&cwd);
91117721Speter    }
91217721Speter
91317721Speter    dirlist = save_dirlist;
91417721Speter    filelist = NULL;
91517721Speter    return(err);
91617721Speter}
917