recurse.c revision 44856
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
1644856Speter#ifdef CLIENT_SUPPORT
1744856Speterstatic int do_argument_proc PROTO((Node * p, void *closure));
1844856Speter#endif
1917721Speterstatic int do_dir_proc PROTO((Node * p, void *closure));
2017721Speterstatic int do_file_proc PROTO((Node * p, void *closure));
2117721Speterstatic void addlist PROTO((List ** listp, char *key));
2217721Speterstatic int unroll_files_proc PROTO((Node *p, void *closure));
2317721Speterstatic void addfile PROTO((List **listp, char *dir, char *file));
2417721Speter
2525839Speterstatic char *update_dir;
2617721Speterstatic char *repository = NULL;
2717721Speterstatic List *filelist = NULL; /* holds list of files on which to operate */
2817721Speterstatic List *dirlist = NULL; /* holds list of directories on which to operate */
2917721Speter
3017721Speterstruct recursion_frame {
3125839Speter    FILEPROC fileproc;
3225839Speter    FILESDONEPROC filesdoneproc;
3325839Speter    DIRENTPROC direntproc;
3425839Speter    DIRLEAVEPROC dirleaveproc;
3525839Speter    void *callerdat;
3625839Speter    Dtype flags;
3725839Speter    int which;
3825839Speter    int aflag;
3925839Speter    int readlock;
4025839Speter    int dosrcs;
4117721Speter};
4217721Speter
4325839Speterstatic int do_recursion PROTO ((struct recursion_frame *frame));
4425839Speter
4525839Speter/* I am half tempted to shove a struct file_info * into the struct
4625839Speter   recursion_frame (but then we would need to modify or create a
4725839Speter   recursion_frame for each file), or shove a struct recursion_frame *
4825839Speter   into the struct file_info (more tempting, although it isn't completely
4925839Speter   clear that the struct file_info should contain info about recursion
5025839Speter   processor internals).  So instead use this struct.  */
5125839Speter
5225839Speterstruct frame_and_file {
5325839Speter    struct recursion_frame *frame;
5425839Speter    struct file_info *finfo;
5525839Speter};
5625839Speter
5725839Speter/* Similarly, we need to pass the entries list to do_dir_proc.  */
5825839Speter
5925839Speterstruct frame_and_entries {
6025839Speter    struct recursion_frame *frame;
6125839Speter    List *entries;
6225839Speter};
6325839Speter
6444856Speter#ifdef CLIENT_SUPPORT
6544856Speter/* This is a callback to send "Argument" commands to the server in the
6644856Speter   case we've done a "cvs update" or "cvs commit" in a top-level
6744856Speter   directory where there is no CVSADM directory. */
6844856Speter
6944856Speterstatic int
7044856Speterdo_argument_proc (p, closure)
7144856Speter    Node *p;
7244856Speter    void *closure;
7344856Speter{
7444856Speter    char *dir = p->key;
7544856Speter    send_to_server ("Argument ", 0);
7644856Speter    send_to_server (dir, 0);
7744856Speter    send_to_server ("\012", 1);
7844856Speter    return 0;
7944856Speter}
8044856Speter#endif
8144856Speter
8225839Speter/* Start a recursive command.
8325839Speter
8425839Speter   Command line arguments (ARGC, ARGV) dictate the directories and
8525839Speter   files on which we operate.  In the special case of no arguments, we
8625839Speter   default to ".".  */
8717721Speterint
8825839Speterstart_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc, callerdat,
8917721Speter		 argc, argv, local, which, aflag, readlock,
9025839Speter		 update_preload, dosrcs)
9117721Speter    FILEPROC fileproc;
9217721Speter    FILESDONEPROC filesdoneproc;
9317721Speter    DIRENTPROC 	direntproc;
9417721Speter    DIRLEAVEPROC dirleaveproc;
9525839Speter    void *callerdat;
9625839Speter
9717721Speter    int argc;
9817721Speter    char **argv;
9917721Speter    int local;
10025839Speter
10125839Speter    /* This specifies the kind of recursion.  There are several cases:
10225839Speter
10325839Speter       1.  W_LOCAL is not set but W_REPOS or W_ATTIC is.  The current
10425839Speter       directory when we are called must be the repository and
10525839Speter       recursion proceeds according to what exists in the repository.
10625839Speter
10725839Speter       2a.  W_LOCAL is set but W_REPOS and W_ATTIC are not.  The
10825839Speter       current directory when we are called must be the working
10925839Speter       directory.  Recursion proceeds according to what exists in the
11025839Speter       working directory, never (I think) consulting any part of the
11125839Speter       repository which does not correspond to the working directory
11225839Speter       ("correspond" == Name_Repository).
11325839Speter
11425839Speter       2b.  W_LOCAL is set and so is W_REPOS or W_ATTIC.  This is the
11525839Speter       weird one.  The current directory when we are called must be
11625839Speter       the working directory.  We recurse through working directories,
11725839Speter       but we recurse into a directory if it is exists in the working
11825839Speter       directory *or* it exists in the repository.  If a directory
11925839Speter       does not exist in the working directory, the direntproc must
12025839Speter       either tell us to skip it (R_SKIP_ALL), or must create it (I
12125839Speter       think those are the only two cases).  */
12217721Speter    int which;
12325839Speter
12417721Speter    int aflag;
12517721Speter    int readlock;
12617721Speter    char *update_preload;
12717721Speter    int dosrcs;
12817721Speter{
12917721Speter    int i, err = 0;
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;
14125839Speter    frame.readlock = readlock;
14225839Speter    frame.dosrcs = dosrcs;
14325839Speter
14417721Speter    expand_wild (argc, argv, &argc, &argv);
14517721Speter
14617721Speter    if (update_preload == NULL)
14725839Speter	update_dir = xstrdup ("");
14817721Speter    else
14925839Speter	update_dir = xstrdup (update_preload);
15017721Speter
15117721Speter    /* clean up from any previous calls to start_recursion */
15217721Speter    if (repository)
15317721Speter    {
15417721Speter	free (repository);
15517721Speter	repository = (char *) NULL;
15617721Speter    }
15717721Speter    if (filelist)
15817721Speter	dellist (&filelist); /* FIXME-krp: no longer correct. */
15917721Speter    if (dirlist)
16017721Speter	dellist (&dirlist);
16117721Speter
16225839Speter#ifdef SERVER_SUPPORT
16325839Speter    if (server_active)
16425839Speter    {
16525839Speter	for (i = 0; i < argc; ++i)
16625839Speter	    server_pathname_check (argv[i]);
16725839Speter    }
16825839Speter#endif
16925839Speter
17017721Speter    if (argc == 0)
17117721Speter    {
17217721Speter
17317721Speter	/*
17417721Speter	 * There were no arguments, so we'll probably just recurse. The
17517721Speter	 * exception to the rule is when we are called from a directory
17617721Speter	 * without any CVS administration files.  That has always meant to
17717721Speter	 * process each of the sub-directories, so we pretend like we were
17817721Speter	 * called with the list of sub-dirs of the current dir as args
17917721Speter	 */
18017721Speter	if ((which & W_LOCAL) && !isdir (CVSADM))
18132788Speter	{
18225839Speter	    dirlist = Find_Directories ((char *) NULL, W_LOCAL, (List *) NULL);
18332788Speter	    /* If there are no sub-directories, there is a certain logic in
18432788Speter	       favor of doing nothing, but in fact probably the user is just
18532788Speter	       confused about what directory they are in, or whether they
18632788Speter	       cvs add'd a new directory.  In the case of at least one
18732788Speter	       sub-directory, at least when we recurse into them we
18832788Speter	       notice (hopefully) whether they are under CVS control.  */
18932788Speter	    if (list_isempty (dirlist))
19032788Speter	    {
19132788Speter		if (update_dir[0] == '\0')
19232788Speter		    error (0, 0, "in directory .:");
19332788Speter		else
19432788Speter		    error (0, 0, "in directory %s:", update_dir);
19532788Speter		error (1, 0,
19632788Speter		       "there is no version here; run '%s checkout' first",
19732788Speter		       program_name);
19832788Speter	    }
19944856Speter#ifdef CLIENT_SUPPORT
20044856Speter	    else if (client_active && server_started)
20144856Speter	    {
20244856Speter		/* In the the case "cvs update foo bar baz", a call to
20344856Speter		   send_file_names in update.c will have sent the
20444856Speter		   appropriate "Argument" commands to the server.  In
20544856Speter		   this case, that won't have happened, so we need to
20644856Speter		   do it here.  While this example uses "update", this
20744856Speter		   generalizes to other commands. */
20844856Speter
20944856Speter		err += walklist (dirlist, do_argument_proc, NULL);
21044856Speter	    }
21144856Speter#endif
21232788Speter	}
21317721Speter	else
21417721Speter	    addlist (&dirlist, ".");
21517721Speter
21625839Speter	err += do_recursion (&frame);
21725839Speter	goto out;
21817721Speter    }
21917721Speter
22017721Speter
22117721Speter    /*
22217721Speter     * There were arguments, so we have to handle them by hand. To do
22317721Speter     * that, we set up the filelist and dirlist with the arguments and
22417721Speter     * call do_recursion.  do_recursion recognizes the fact that the
22517721Speter     * lists are non-null when it starts and doesn't update them.
22617721Speter     *
22717721Speter     * explicitly named directories are stored in dirlist.
22817721Speter     * explicitly named files are stored in filelist.
22917721Speter     * other possibility is named entities whicha are not currently in
23017721Speter     * the working directory.
23117721Speter     */
23217721Speter
23317721Speter    for (i = 0; i < argc; i++)
23417721Speter    {
23517721Speter	/* if this argument is a directory, then add it to the list of
23617721Speter	   directories. */
23717721Speter
23817721Speter	if (!wrap_name_has (argv[i], WRAP_TOCVS) && isdir (argv[i]))
23917721Speter	    addlist (&dirlist, argv[i]);
24017721Speter	else
24117721Speter	{
24217721Speter	    /* otherwise, split argument into directory and component names. */
24317721Speter	    char *dir;
24417721Speter	    char *comp;
24517721Speter	    char *file_to_try;
24617721Speter
24717721Speter	    /* Now break out argv[i] into directory part (DIR) and file part (COMP).
24817721Speter		   DIR and COMP will each point to a newly malloc'd string.  */
24917721Speter	    dir = xstrdup (argv[i]);
25017721Speter	    comp = last_component (dir);
25117721Speter	    if (comp == dir)
25217721Speter	    {
25317721Speter		/* no dir component.  What we have is an implied "./" */
25417721Speter		dir = xstrdup(".");
25517721Speter	    }
25617721Speter	    else
25717721Speter	    {
25817721Speter		char *p = comp;
25917721Speter
26017721Speter		p[-1] = '\0';
26117721Speter		comp = xstrdup (p);
26217721Speter	    }
26317721Speter
26417721Speter	    /* if this argument exists as a file in the current
26517721Speter	       working directory tree, then add it to the files list.  */
26617721Speter
26725839Speter	    if (!(which & W_LOCAL))
26817721Speter	    {
26917721Speter		/* If doing rtag, we've done a chdir to the repository. */
27025839Speter		file_to_try = xmalloc (strlen (argv[i]) + sizeof (RCSEXT) + 5);
27125839Speter		sprintf (file_to_try, "%s%s", argv[i], RCSEXT);
27217721Speter	    }
27317721Speter	    else
27425839Speter		file_to_try = xstrdup (argv[i]);
27517721Speter
27625839Speter	    if (isfile (file_to_try))
27717721Speter		addfile (&files_by_dir, dir, comp);
27817721Speter	    else if (isdir (dir))
27917721Speter	    {
28025839Speter		if ((which & W_LOCAL) && isdir (CVSADM)
28125839Speter#ifdef CLIENT_SUPPORT
28225839Speter		    && !client_active
28325839Speter#endif
28425839Speter		    )
28517721Speter		{
28617721Speter		    /* otherwise, look for it in the repository. */
28725839Speter		    char *tmp_update_dir;
28817721Speter		    char *repos;
28925839Speter		    char *reposfile;
29017721Speter
29125839Speter		    tmp_update_dir = xmalloc (strlen (update_dir)
29225839Speter					      + strlen (dir)
29325839Speter					      + 5);
29425839Speter		    strcpy (tmp_update_dir, update_dir);
29517721Speter
29625839Speter		    if (*tmp_update_dir != '\0')
29725839Speter			(void) strcat (tmp_update_dir, "/");
29825839Speter
29925839Speter		    (void) strcat (tmp_update_dir, dir);
30025839Speter
30117721Speter		    /* look for it in the repository. */
30225839Speter		    repos = Name_Repository (dir, tmp_update_dir);
30325839Speter		    reposfile = xmalloc (strlen (repos)
30425839Speter					 + strlen (comp)
30525839Speter					 + 5);
30625839Speter		    (void) sprintf (reposfile, "%s/%s", repos, comp);
30717721Speter		    free (repos);
30817721Speter
30925839Speter		    if (!wrap_name_has (comp, WRAP_TOCVS) && isdir (reposfile))
31017721Speter			addlist (&dirlist, argv[i]);
31117721Speter		    else
31217721Speter			addfile (&files_by_dir, dir, comp);
31317721Speter
31425839Speter		    free (tmp_update_dir);
31525839Speter		    free (reposfile);
31617721Speter		}
31717721Speter		else
31817721Speter		    addfile (&files_by_dir, dir, comp);
31917721Speter	    }
32017721Speter	    else
32117721Speter		error (1, 0, "no such directory `%s'", dir);
32217721Speter
32325839Speter	    free (file_to_try);
32417721Speter	    free (dir);
32517721Speter	    free (comp);
32617721Speter	}
32717721Speter    }
32817721Speter
32917721Speter    /* At this point we have looped over all named arguments and built
33017721Speter       a coupla lists.  Now we unroll the lists, setting up and
33117721Speter       calling do_recursion. */
33217721Speter
33317721Speter    err += walklist (files_by_dir, unroll_files_proc, (void *) &frame);
33425839Speter    dellist(&files_by_dir);
33517721Speter
33617721Speter    /* then do_recursion on the dirlist. */
33717721Speter    if (dirlist != NULL)
33825839Speter	err += do_recursion (&frame);
33917721Speter
34017721Speter    /* Free the data which expand_wild allocated.  */
34126065Speter    free_names (&argc, argv);
34217721Speter
34325839Speter out:
34425839Speter    free (update_dir);
34525839Speter    update_dir = NULL;
34617721Speter    return (err);
34717721Speter}
34817721Speter
34917721Speter/*
35017721Speter * Implement the recursive policies on the local directory.  This may be
35117721Speter * called directly, or may be called by start_recursion
35217721Speter */
35325839Speterstatic int
35425839Speterdo_recursion (frame)
35525839Speter    struct recursion_frame *frame;
35617721Speter{
35717721Speter    int err = 0;
35817721Speter    int dodoneproc = 1;
35917721Speter    char *srepository;
36017721Speter    List *entries = NULL;
36125839Speter    int should_readlock;
36217721Speter
36317721Speter    /* do nothing if told */
36425839Speter    if (frame->flags == R_SKIP_ALL)
36517721Speter	return (0);
36617721Speter
36725839Speter    should_readlock = noexec ? 0 : frame->readlock;
36817721Speter
36917721Speter    /* The fact that locks are not active here is what makes us fail to have
37017721Speter       the
37117721Speter
37217721Speter           If someone commits some changes in one cvs command,
37317721Speter	   then an update by someone else will either get all the
37417721Speter	   changes, or none of them.
37517721Speter
37617721Speter       property (see node Concurrency in cvs.texinfo).
37717721Speter
37817721Speter       The most straightforward fix would just to readlock the whole
37917721Speter       tree before starting an update, but that means that if a commit
38017721Speter       gets blocked on a big update, it might need to wait a *long*
38117721Speter       time.
38217721Speter
38317721Speter       A more adequate fix would be a two-pass design for update,
38417721Speter       checkout, etc.  The first pass would go through the repository,
38517721Speter       with the whole tree readlocked, noting what versions of each
38617721Speter       file we want to get.  The second pass would release all locks
38717721Speter       (except perhaps short-term locks on one file at a
38817721Speter       time--although I think RCS already deals with this) and
38917721Speter       actually get the files, specifying the particular versions it wants.
39017721Speter
39117721Speter       This could be sped up by separating out the data needed for the
39217721Speter       first pass into a separate file(s)--for example a file
39317721Speter       attribute for each file whose value contains the head revision
39417721Speter       for each branch.  The structure should be designed so that
39517721Speter       commit can relatively quickly update the information for a
39617721Speter       single file or a handful of files (file attributes, as
39717721Speter       implemented in Jan 96, are probably acceptable; improvements
39817721Speter       would be possible such as branch attributes which are in
39917721Speter       separate files for each branch).  */
40017721Speter
40117721Speter#if defined(SERVER_SUPPORT) && defined(SERVER_FLOWCONTROL)
40217721Speter    /*
40317721Speter     * Now would be a good time to check to see if we need to stop
40417721Speter     * generating data, to give the buffers a chance to drain to the
40517721Speter     * remote client.  We should not have locks active at this point.
40617721Speter     */
40717721Speter    if (server_active
40817721Speter	/* If there are writelocks around, we cannot pause here.  */
40925839Speter	&& (should_readlock || noexec))
41017721Speter	server_pause_check();
41117721Speter#endif
41217721Speter
41317721Speter    /*
41417721Speter     * Fill in repository with the current repository
41517721Speter     */
41625839Speter    if (frame->which & W_LOCAL)
41717721Speter    {
41817721Speter	if (isdir (CVSADM))
41917721Speter	    repository = Name_Repository ((char *) NULL, update_dir);
42017721Speter	else
42117721Speter	    repository = NULL;
42217721Speter    }
42317721Speter    else
42417721Speter    {
42525839Speter	repository = xgetwd ();
42625839Speter	if (repository == NULL)
42725839Speter	    error (1, errno, "could not get working directory");
42817721Speter    }
42917721Speter    srepository = repository;		/* remember what to free */
43017721Speter
43117721Speter    fileattr_startdir (repository);
43217721Speter
43317721Speter    /*
43417721Speter     * The filesdoneproc needs to be called for each directory where files
43517721Speter     * processed, or each directory that is processed by a call where no
43617721Speter     * directories were passed in.  In fact, the only time we don't want to
43717721Speter     * call back the filesdoneproc is when we are processing directories that
43817721Speter     * were passed in on the command line (or in the special case of `.' when
43917721Speter     * we were called with no args
44017721Speter     */
44117721Speter    if (dirlist != NULL && filelist == NULL)
44217721Speter	dodoneproc = 0;
44317721Speter
44417721Speter    /*
44517721Speter     * If filelist or dirlist is already set, we don't look again. Otherwise,
44617721Speter     * find the files and directories
44717721Speter     */
44817721Speter    if (filelist == NULL && dirlist == NULL)
44917721Speter    {
45017721Speter	/* both lists were NULL, so start from scratch */
45125839Speter	if (frame->fileproc != NULL && frame->flags != R_SKIP_FILES)
45217721Speter	{
45325839Speter	    int lwhich = frame->which;
45417721Speter
45517721Speter	    /* be sure to look in the attic if we have sticky tags/date */
45617721Speter	    if ((lwhich & W_ATTIC) == 0)
45717721Speter		if (isreadable (CVSADM_TAG))
45817721Speter		    lwhich |= W_ATTIC;
45917721Speter
46025839Speter	    /* In the !(which & W_LOCAL) case, we filled in repository
46125839Speter	       earlier in the function.  In the (which & W_LOCAL) case,
46225839Speter	       the Find_Names function is going to look through the
46325839Speter	       Entries file.  If we do not have a repository, that
46425839Speter	       does not make sense, so we insist upon having a
46525839Speter	       repository at this point.  Name_Repository will give a
46625839Speter	       reasonable error message.  */
46725839Speter	    if (repository == NULL)
46825839Speter		repository = Name_Repository ((char *) NULL, update_dir);
46925839Speter
47017721Speter	    /* find the files and fill in entries if appropriate */
47125839Speter	    filelist = Find_Names (repository, lwhich, frame->aflag, &entries);
47217721Speter	}
47317721Speter
47417721Speter	/* find sub-directories if we will recurse */
47525839Speter	if (frame->flags != R_SKIP_DIRS)
47625839Speter	    dirlist = Find_Directories (repository, frame->which, entries);
47717721Speter    }
47817721Speter    else
47917721Speter    {
48017721Speter	/* something was passed on the command line */
48125839Speter	if (filelist != NULL && frame->fileproc != NULL)
48217721Speter	{
48317721Speter	    /* we will process files, so pre-parse entries */
48425839Speter	    if (frame->which & W_LOCAL)
48534467Speter		entries = Entries_Open (frame->aflag, NULL);
48617721Speter	}
48717721Speter    }
48817721Speter
48917721Speter    /* process the files (if any) */
49025839Speter    if (filelist != NULL && frame->fileproc)
49117721Speter    {
49217721Speter	struct file_info finfo_struct;
49325839Speter	struct frame_and_file frfile;
49417721Speter
49517721Speter	/* read lock it if necessary */
49625839Speter	if (should_readlock && repository && Reader_Lock (repository) != 0)
49717721Speter	    error (1, 0, "read lock failed - giving up");
49817721Speter
49917721Speter#ifdef CLIENT_SUPPORT
50017721Speter	/* For the server, we handle notifications in a completely different
50117721Speter	   place (server_notify).  For local, we can't do them here--we don't
50217721Speter	   have writelocks in place, and there is no way to get writelocks
50317721Speter	   here.  */
50417721Speter	if (client_active)
50517721Speter	    notify_check (repository, update_dir);
50617721Speter#endif /* CLIENT_SUPPORT */
50717721Speter
50817721Speter	finfo_struct.repository = repository;
50917721Speter	finfo_struct.update_dir = update_dir;
51017721Speter	finfo_struct.entries = entries;
51117721Speter	/* do_file_proc will fill in finfo_struct.file.  */
51217721Speter
51325839Speter	frfile.finfo = &finfo_struct;
51425839Speter	frfile.frame = frame;
51525839Speter
51617721Speter	/* process the files */
51725839Speter	err += walklist (filelist, do_file_proc, &frfile);
51817721Speter
51917721Speter	/* unlock it */
52025839Speter	if (should_readlock)
52117721Speter	    Lock_Cleanup ();
52217721Speter
52317721Speter	/* clean up */
52417721Speter	dellist (&filelist);
52517721Speter    }
52617721Speter
52717721Speter    /* call-back files done proc (if any) */
52825839Speter    if (dodoneproc && frame->filesdoneproc != NULL)
52925839Speter	err = frame->filesdoneproc (frame->callerdat, err, repository,
53025839Speter				    update_dir[0] ? update_dir : ".",
53125839Speter				    entries);
53217721Speter
53317721Speter    fileattr_write ();
53417721Speter    fileattr_free ();
53517721Speter
53617721Speter    /* process the directories (if necessary) */
53717721Speter    if (dirlist != NULL)
53825839Speter    {
53925839Speter	struct frame_and_entries frent;
54025839Speter
54125839Speter	frent.frame = frame;
54225839Speter	frent.entries = entries;
54325839Speter	err += walklist (dirlist, do_dir_proc, (void *) &frent);
54425839Speter    }
54525839Speter#if 0
54625839Speter    else if (frame->dirleaveproc != NULL)
54725839Speter	err += frame->dirleaveproc (frame->callerdat, ".", err, ".");
54817721Speter#endif
54917721Speter    dellist (&dirlist);
55017721Speter
55125839Speter    if (entries)
55225839Speter    {
55325839Speter	Entries_Close (entries);
55425839Speter	entries = NULL;
55525839Speter    }
55625839Speter
55717721Speter    /* free the saved copy of the pointer if necessary */
55817721Speter    if (srepository)
55917721Speter    {
56017721Speter	free (srepository);
56117721Speter	repository = (char *) NULL;
56217721Speter    }
56317721Speter
56417721Speter    return (err);
56517721Speter}
56617721Speter
56717721Speter/*
56817721Speter * Process each of the files in the list with the callback proc
56917721Speter */
57017721Speterstatic int
57117721Speterdo_file_proc (p, closure)
57217721Speter    Node *p;
57317721Speter    void *closure;
57417721Speter{
57525839Speter    struct frame_and_file *frfile = (struct frame_and_file *)closure;
57625839Speter    struct file_info *finfo = frfile->finfo;
57717721Speter    int ret;
57817721Speter
57917721Speter    finfo->file = p->key;
58017721Speter    finfo->fullname = xmalloc (strlen (finfo->file)
58117721Speter			       + strlen (finfo->update_dir)
58217721Speter			       + 2);
58317721Speter    finfo->fullname[0] = '\0';
58417721Speter    if (finfo->update_dir[0] != '\0')
58517721Speter    {
58617721Speter	strcat (finfo->fullname, finfo->update_dir);
58717721Speter	strcat (finfo->fullname, "/");
58817721Speter    }
58917721Speter    strcat (finfo->fullname, finfo->file);
59017721Speter
59125839Speter    if (frfile->frame->dosrcs && repository)
59217721Speter	finfo->rcs = RCS_parse (finfo->file, repository);
59317721Speter    else
59417721Speter        finfo->rcs = (RCSNode *) NULL;
59525839Speter    ret = frfile->frame->fileproc (frfile->frame->callerdat, finfo);
59617721Speter
59717721Speter    freercsnode(&finfo->rcs);
59817721Speter    free (finfo->fullname);
59917721Speter
60025839Speter    /* Allow the user to monitor progress with tail -f.  Doing this once
60125839Speter       per file should be no big deal, but we don't want the performance
60225839Speter       hit of flushing on every line like previous versions of CVS.  */
60325839Speter    cvs_flushout ();
60425839Speter
60517721Speter    return (ret);
60617721Speter}
60717721Speter
60817721Speter/*
60917721Speter * Process each of the directories in the list (recursing as we go)
61017721Speter */
61117721Speterstatic int
61217721Speterdo_dir_proc (p, closure)
61317721Speter    Node *p;
61417721Speter    void *closure;
61517721Speter{
61625839Speter    struct frame_and_entries *frent = (struct frame_and_entries *) closure;
61725839Speter    struct recursion_frame *frame = frent->frame;
61825839Speter    struct recursion_frame xframe;
61917721Speter    char *dir = p->key;
62025839Speter    char *newrepos;
62117721Speter    List *sdirlist;
62217721Speter    char *srepository;
62317721Speter    Dtype dir_return = R_PROCESS;
62417721Speter    int stripped_dot = 0;
62517721Speter    int err = 0;
62617721Speter    struct saved_cwd cwd;
62725839Speter    char *saved_update_dir;
62817721Speter
62925839Speter    if (fncmp (dir, CVSADM) == 0)
63025839Speter    {
63125839Speter	/* This seems to most often happen when users (beginning users,
63225839Speter	   generally), try "cvs ci *" or something similar.  On that
63325839Speter	   theory, it is possible that we should just silently skip the
63425839Speter	   CVSADM directories, but on the other hand, using a wildcard
63525839Speter	   like this isn't necessarily a practice to encourage (it operates
63625839Speter	   only on files which exist in the working directory, unlike
63725839Speter	   regular CVS recursion).  */
63825839Speter
63925839Speter	/* FIXME-reentrancy: printed_cvs_msg should be in a "command
64025839Speter	   struct" or some such, so that it gets cleared for each new
64125839Speter	   command (this is possible using the remote protocol and a
64225839Speter	   custom-written client).  The struct recursion_frame is not
64325839Speter	   far back enough though, some commands (commit at least)
64425839Speter	   will call start_recursion several times.  An alternate solution
64525839Speter	   would be to take this whole check and move it to a new function
64625839Speter	   validate_arguments or some such that all the commands call
64725839Speter	   and which snips the offending directory from the argc,argv
64825839Speter	   vector.  */
64925839Speter	static int printed_cvs_msg = 0;
65025839Speter	if (!printed_cvs_msg)
65125839Speter	{
65225839Speter	    error (0, 0, "warning: directory %s specified in argument",
65325839Speter		   dir);
65425839Speter	    error (0, 0, "\
65525839Speterbut CVS uses %s for its own purposes; skipping %s directory",
65625839Speter		   CVSADM, dir);
65725839Speter	    printed_cvs_msg = 1;
65825839Speter	}
65925839Speter	return 0;
66025839Speter    }
66125839Speter
66225839Speter    saved_update_dir = update_dir;
66325839Speter    update_dir = xmalloc (strlen (saved_update_dir)
66425839Speter			  + strlen (dir)
66525839Speter			  + 5);
66625839Speter    strcpy (update_dir, saved_update_dir);
66725839Speter
66817721Speter    /* set up update_dir - skip dots if not at start */
66917721Speter    if (strcmp (dir, ".") != 0)
67017721Speter    {
67117721Speter	if (update_dir[0] != '\0')
67217721Speter	{
67317721Speter	    (void) strcat (update_dir, "/");
67417721Speter	    (void) strcat (update_dir, dir);
67517721Speter	}
67617721Speter	else
67717721Speter	    (void) strcpy (update_dir, dir);
67817721Speter
67917721Speter	/*
68017721Speter	 * Here we need a plausible repository name for the sub-directory. We
68117721Speter	 * create one by concatenating the new directory name onto the
68217721Speter	 * previous repository name.  The only case where the name should be
68317721Speter	 * used is in the case where we are creating a new sub-directory for
68417721Speter	 * update -d and in that case the generated name will be correct.
68517721Speter	 */
68617721Speter	if (repository == NULL)
68725839Speter	    newrepos = xstrdup ("");
68817721Speter	else
68925839Speter	{
69025839Speter	    newrepos = xmalloc (strlen (repository) + strlen (dir) + 5);
69132788Speter	    sprintf (newrepos, "%s/%s", repository, dir);
69225839Speter	}
69317721Speter    }
69417721Speter    else
69517721Speter    {
69617721Speter	if (update_dir[0] == '\0')
69717721Speter	    (void) strcpy (update_dir, dir);
69817721Speter
69917721Speter	if (repository == NULL)
70025839Speter	    newrepos = xstrdup ("");
70117721Speter	else
70225839Speter	    newrepos = xstrdup (repository);
70317721Speter    }
70417721Speter
70532788Speter    /* Check to see that the CVSADM directory, if it exists, seems to be
70632788Speter       well-formed.  It can be missing files if the user hit ^C in the
70732788Speter       middle of a previous run.  We want to (a) make this a nonfatal
70832788Speter       error, and (b) make sure we print which directory has the
70932788Speter       problem.
71032788Speter
71132788Speter       Do this before the direntproc, so that (1) the direntproc
71232788Speter       doesn't have to guess/deduce whether we will skip the directory
71332788Speter       (e.g. send_dirent_proc and whether to send the directory), and
71432788Speter       (2) so that the warm fuzzy doesn't get printed if we skip the
71532788Speter       directory.  */
71632788Speter    if (frame->which & W_LOCAL)
71732788Speter    {
71832788Speter	char *cvsadmdir;
71932788Speter
72032788Speter	cvsadmdir = xmalloc (strlen (dir)
72132788Speter			     + sizeof (CVSADM_REP)
72232788Speter			     + sizeof (CVSADM_ENT)
72332788Speter			     + 80);
72432788Speter
72532788Speter	strcpy (cvsadmdir, dir);
72632788Speter	strcat (cvsadmdir, "/");
72732788Speter	strcat (cvsadmdir, CVSADM);
72832788Speter	if (isdir (cvsadmdir))
72932788Speter	{
73032788Speter	    strcpy (cvsadmdir, dir);
73132788Speter	    strcat (cvsadmdir, "/");
73232788Speter	    strcat (cvsadmdir, CVSADM_REP);
73332788Speter	    if (!isfile (cvsadmdir))
73432788Speter	    {
73532788Speter		/* Some commands like update may have printed "? foo" but
73632788Speter		   if we were planning to recurse, and don't on account of
73732788Speter		   CVS/Repository, we want to say why.  */
73832788Speter		error (0, 0, "ignoring %s (%s missing)", update_dir,
73932788Speter		       CVSADM_REP);
74032788Speter		dir_return = R_SKIP_ALL;
74132788Speter	    }
74232788Speter
74332788Speter	    /* Likewise for CVS/Entries.  */
74432788Speter	    if (dir_return != R_SKIP_ALL)
74532788Speter	    {
74632788Speter		strcpy (cvsadmdir, dir);
74732788Speter		strcat (cvsadmdir, "/");
74832788Speter		strcat (cvsadmdir, CVSADM_ENT);
74932788Speter		if (!isfile (cvsadmdir))
75032788Speter		{
75132788Speter		    /* Some commands like update may have printed "? foo" but
75232788Speter		       if we were planning to recurse, and don't on account of
75332788Speter		       CVS/Repository, we want to say why.  */
75432788Speter		    error (0, 0, "ignoring %s (%s missing)", update_dir,
75532788Speter			   CVSADM_ENT);
75632788Speter		    dir_return = R_SKIP_ALL;
75732788Speter		}
75832788Speter	    }
75932788Speter	}
76032788Speter	free (cvsadmdir);
76132788Speter    }
76232788Speter
76317721Speter    /* call-back dir entry proc (if any) */
76432788Speter    if (dir_return == R_SKIP_ALL)
76532788Speter	;
76632788Speter    else if (frame->direntproc != NULL)
76725839Speter	dir_return = frame->direntproc (frame->callerdat, dir, newrepos,
76825839Speter					update_dir, frent->entries);
76932788Speter    else
77032788Speter    {
77132788Speter	/* Generic behavior.  I don't see a reason to make the caller specify
77232788Speter	   a direntproc just to get this.  */
77332788Speter	if ((frame->which & W_LOCAL) && !isdir (dir))
77432788Speter	    dir_return = R_SKIP_ALL;
77532788Speter    }
77632788Speter
77725839Speter    free (newrepos);
77817721Speter
77917721Speter    /* only process the dir if the return code was 0 */
78017721Speter    if (dir_return != R_SKIP_ALL)
78117721Speter    {
78217721Speter	/* save our current directory and static vars */
78317721Speter        if (save_cwd (&cwd))
78425839Speter	    error_exit ();
78517721Speter	sdirlist = dirlist;
78617721Speter	srepository = repository;
78717721Speter	dirlist = NULL;
78817721Speter
78917721Speter	/* cd to the sub-directory */
79025839Speter	if ( CVS_CHDIR (dir) < 0)
79117721Speter	    error (1, errno, "could not chdir to %s", dir);
79217721Speter
79317721Speter	/* honor the global SKIP_DIRS (a.k.a. local) */
79425839Speter	if (frame->flags == R_SKIP_DIRS)
79517721Speter	    dir_return = R_SKIP_DIRS;
79617721Speter
79717721Speter	/* remember if the `.' will be stripped for subsequent dirs */
79817721Speter	if (strcmp (update_dir, ".") == 0)
79917721Speter	{
80017721Speter	    update_dir[0] = '\0';
80117721Speter	    stripped_dot = 1;
80217721Speter	}
80317721Speter
80417721Speter	/* make the recursive call */
80525839Speter	xframe = *frame;
80625839Speter	xframe.flags = dir_return;
80725839Speter	err += do_recursion (&xframe);
80817721Speter
80917721Speter	/* put the `.' back if necessary */
81017721Speter	if (stripped_dot)
81117721Speter	    (void) strcpy (update_dir, ".");
81217721Speter
81317721Speter	/* call-back dir leave proc (if any) */
81425839Speter	if (frame->dirleaveproc != NULL)
81525839Speter	    err = frame->dirleaveproc (frame->callerdat, dir, err, update_dir,
81625839Speter				       frent->entries);
81717721Speter
81817721Speter	/* get back to where we started and restore state vars */
81917721Speter	if (restore_cwd (&cwd, NULL))
82025839Speter	    error_exit ();
82117721Speter	free_cwd (&cwd);
82217721Speter	dirlist = sdirlist;
82317721Speter	repository = srepository;
82417721Speter    }
82517721Speter
82630337Speter    free (update_dir);
82730337Speter    update_dir = saved_update_dir;
82817721Speter
82917721Speter    return (err);
83017721Speter}
83117721Speter
83217721Speter/*
83317721Speter * Add a node to a list allocating the list if necessary.
83417721Speter */
83517721Speterstatic void
83617721Speteraddlist (listp, key)
83717721Speter    List **listp;
83817721Speter    char *key;
83917721Speter{
84017721Speter    Node *p;
84117721Speter
84217721Speter    if (*listp == NULL)
84317721Speter	*listp = getlist ();
84417721Speter    p = getnode ();
84517721Speter    p->type = FILES;
84617721Speter    p->key = xstrdup (key);
84717721Speter    if (addnode (*listp, p) != 0)
84817721Speter	freenode (p);
84917721Speter}
85017721Speter
85117721Speterstatic void
85217721Speteraddfile (listp, dir, file)
85317721Speter    List **listp;
85417721Speter    char *dir;
85517721Speter    char *file;
85617721Speter{
85717721Speter    Node *n;
85817721Speter
85917721Speter    /* add this dir. */
86017721Speter    addlist (listp, dir);
86117721Speter
86217721Speter    n = findnode (*listp, dir);
86317721Speter    if (n == NULL)
86417721Speter    {
86517721Speter	error (1, 0, "can't find recently added dir node `%s' in start_recursion.",
86617721Speter	       dir);
86717721Speter    }
86817721Speter
86917721Speter    n->type = DIRS;
87017721Speter    addlist ((List **) &n->data, file);
87117721Speter    return;
87217721Speter}
87317721Speter
87417721Speterstatic int
87517721Speterunroll_files_proc (p, closure)
87617721Speter    Node *p;
87717721Speter    void *closure;
87817721Speter{
87917721Speter    Node *n;
88017721Speter    struct recursion_frame *frame = (struct recursion_frame *) closure;
88117721Speter    int err = 0;
88217721Speter    List *save_dirlist;
88317721Speter    char *save_update_dir = NULL;
88417721Speter    struct saved_cwd cwd;
88517721Speter
88617721Speter    /* if this dir was also an explicitly named argument, then skip
88717721Speter       it.  We'll catch it later when we do dirs. */
88817721Speter    n = findnode (dirlist, p->key);
88917721Speter    if (n != NULL)
89017721Speter	return (0);
89117721Speter
89217721Speter    /* otherwise, call dorecusion for this list of files. */
89317721Speter    filelist = (List *) p->data;
89425839Speter    p->data = NULL;
89517721Speter    save_dirlist = dirlist;
89617721Speter    dirlist = NULL;
89717721Speter
89817721Speter    if (strcmp(p->key, ".") != 0)
89917721Speter    {
90017721Speter        if (save_cwd (&cwd))
90125839Speter	    error_exit ();
90225839Speter	if ( CVS_CHDIR (p->key) < 0)
90317721Speter	    error (1, errno, "could not chdir to %s", p->key);
90417721Speter
90525839Speter	save_update_dir = update_dir;
90625839Speter	update_dir = xmalloc (strlen (save_update_dir)
90725839Speter				  + strlen (p->key)
90825839Speter				  + 5);
90925839Speter	strcpy (update_dir, save_update_dir);
91017721Speter
91117721Speter	if (*update_dir != '\0')
91217721Speter	    (void) strcat (update_dir, "/");
91317721Speter
91417721Speter	(void) strcat (update_dir, p->key);
91517721Speter    }
91617721Speter
91725839Speter    err += do_recursion (frame);
91817721Speter
91917721Speter    if (save_update_dir != NULL)
92017721Speter    {
92125839Speter	free (update_dir);
92225839Speter	update_dir = save_update_dir;
92317721Speter
92417721Speter	if (restore_cwd (&cwd, NULL))
92525839Speter	    error_exit ();
92617721Speter	free_cwd (&cwd);
92717721Speter    }
92817721Speter
92917721Speter    dirlist = save_dirlist;
93017721Speter    filelist = NULL;
93117721Speter    return(err);
93217721Speter}
933