summaryrefslogtreecommitdiff
path: root/src/recurse.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/recurse.c')
-rw-r--r--src/recurse.c1363
1 files changed, 1363 insertions, 0 deletions
diff --git a/src/recurse.c b/src/recurse.c
new file mode 100644
index 0000000..ef80138
--- /dev/null
+++ b/src/recurse.c
@@ -0,0 +1,1363 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ * and others.
+ *
+ * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (C) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ * General recursion handler
+ *
+ */
+
+#include "cvs.h"
+#include "save-cwd.h"
+#include "fileattr.h"
+#include "edit.h"
+
+static int do_dir_proc (Node * p, void *closure);
+static int do_file_proc (Node * p, void *closure);
+static void addlist (List ** listp, char *key);
+static int unroll_files_proc (Node *p, void *closure);
+static void addfile (List **listp, char *dir, char *file);
+
+static char *update_dir;
+static char *repository = NULL;
+static List *filelist = NULL; /* holds list of files on which to operate */
+static List *dirlist = NULL; /* holds list of directories on which to operate */
+
+struct recursion_frame {
+ FILEPROC fileproc;
+ FILESDONEPROC filesdoneproc;
+ DIRENTPROC direntproc;
+ DIRLEAVEPROC dirleaveproc;
+ void *callerdat;
+ Dtype flags;
+ int which;
+ int aflag;
+ int locktype;
+ int dosrcs;
+ char *repository; /* Keep track of repository for rtag */
+};
+
+static int do_recursion (struct recursion_frame *frame);
+
+/* I am half tempted to shove a struct file_info * into the struct
+ recursion_frame (but then we would need to modify or create a
+ recursion_frame for each file), or shove a struct recursion_frame *
+ into the struct file_info (more tempting, although it isn't completely
+ clear that the struct file_info should contain info about recursion
+ processor internals). So instead use this struct. */
+
+struct frame_and_file {
+ struct recursion_frame *frame;
+ struct file_info *finfo;
+};
+
+/* Similarly, we need to pass the entries list to do_dir_proc. */
+
+struct frame_and_entries {
+ struct recursion_frame *frame;
+ List *entries;
+};
+
+
+/* Start a recursive command.
+ *
+ * INPUT
+ *
+ * fileproc
+ * Function called with each file as an argument.
+ *
+ * filesdoneproc
+ * Function called after all the files in a directory have been processed,
+ * before subdirectories have been processed.
+ *
+ * direntproc
+ * Function called immediately upon entering a directory, before processing
+ * any files or subdirectories.
+ *
+ * dirleaveproc
+ * Function called upon finishing a directory, immediately before leaving
+ * it and returning control to the function processing the parent
+ * directory.
+ *
+ * callerdat
+ * This void * is passed to the functions above.
+ *
+ * argc, argv
+ * The files on which to operate, interpreted as command line arguments.
+ * In the special case of no arguments, defaults to operating on the
+ * current directory (`.').
+ *
+ * local
+ *
+ * which
+ * specifies the kind of recursion. There are several cases:
+ *
+ * 1. W_LOCAL is not set but W_REPOS or W_ATTIC is. The current
+ * directory when we are called must be the repository and
+ * recursion proceeds according to what exists in the repository.
+ *
+ * 2a. W_LOCAL is set but W_REPOS and W_ATTIC are not. The
+ * current directory when we are called must be the working
+ * directory. Recursion proceeds according to what exists in the
+ * working directory, never (I think) consulting any part of the
+ * repository which does not correspond to the working directory
+ * ("correspond" == Name_Repository).
+ *
+ * 2b. W_LOCAL is set and so is W_REPOS or W_ATTIC. This is the
+ * weird one. The current directory when we are called must be
+ * the working directory. We recurse through working directories,
+ * but we recurse into a directory if it is exists in the working
+ * directory *or* it exists in the repository. If a directory
+ * does not exist in the working directory, the direntproc must
+ * either tell us to skip it (R_SKIP_ALL), or must create it (I
+ * think those are the only two cases).
+ *
+ * aflag
+ * locktype
+ * update_preload
+ * dosrcs
+ *
+ * repository_in
+ * keeps track of the repository string. This is only for the remote mode,
+ * specifically, r* commands (rtag, rdiff, co, ...) where xgetcwd() was used
+ * to locate the repository. Things would break when xgetcwd() was used
+ * with a symlinked repository because xgetcwd() would return the true path
+ * and in some cases this would cause the path to be printed as other than
+ * the user specified in error messages and in other cases some of CVS's
+ * security assertions would fail.
+ *
+ * GLOBALS
+ * ???
+ *
+ * OUTPUT
+ *
+ * callerdat can be modified by the FILEPROC, FILESDONEPROC, DIRENTPROC, and
+ * DIRLEAVEPROC.
+ *
+ * RETURNS
+ * A count of errors counted by walking the argument list with
+ * unroll_files_proc() and do_recursion().
+ *
+ * ERRORS
+ * Fatal errors occur:
+ * 1. when there were no arguments and the current directory
+ * does not contain CVS metadata.
+ * 2. when all but the last path element from an argument from ARGV cannot
+ * be found to be a local directory.
+ */
+int
+start_recursion (FILEPROC fileproc, FILESDONEPROC filesdoneproc,
+ DIRENTPROC direntproc, DIRLEAVEPROC dirleaveproc,
+ void *callerdat, int argc, char **argv, int local,
+ int which, int aflag, int locktype,
+ char *update_preload, int dosrcs, char *repository_in)
+{
+ int i, err = 0;
+#ifdef CLIENT_SUPPORT
+ List *args_to_send_when_finished = NULL;
+#endif
+ List *files_by_dir = NULL;
+ struct recursion_frame frame;
+
+#ifdef HAVE_PRINTF_PTR
+ TRACE ( TRACE_FLOW,
+ "start_recursion ( fileproc=%p, filesdoneproc=%p,\n"
+ " direntproc=%p, dirleavproc=%p,\n"
+ " callerdat=%p, argc=%d, argv=%p,\n"
+ " local=%d, which=%d, aflag=%d,\n"
+ " locktype=%d, update_preload=%s\n"
+ " dosrcs=%d, repository_in=%s )",
+ (void *) fileproc, (void *) filesdoneproc,
+ (void *) direntproc, (void *) dirleaveproc,
+ (void *) callerdat, argc, (void *) argv,
+ local, which, aflag, locktype,
+ update_preload ? update_preload : "(null)", dosrcs,
+ repository_in ? repository_in : "(null)");
+#else
+ TRACE ( TRACE_FLOW,
+ "start_recursion ( fileproc=%lx, filesdoneproc=%lx,\n"
+ " direntproc=%lx, dirleavproc=%lx,\n"
+ " callerdat=%lx, argc=%d, argv=%lx,\n"
+ " local=%d, which=%d, aflag=%d,\n"
+ " locktype=%d, update_preload=%s\n"
+ " dosrcs=%d, repository_in=%s )",
+ (unsigned long) fileproc, (unsigned long) filesdoneproc,
+ (unsigned long) direntproc, (unsigned long) dirleaveproc,
+ (unsigned long) callerdat, argc, (unsigned long) argv,
+ local, which, aflag, locktype,
+ update_preload ? update_preload : "(null)", dosrcs,
+ repository_in ? repository_in : "(null)");
+#endif
+
+ frame.fileproc = fileproc;
+ frame.filesdoneproc = filesdoneproc;
+ frame.direntproc = direntproc;
+ frame.dirleaveproc = dirleaveproc;
+ frame.callerdat = callerdat;
+ frame.flags = local ? R_SKIP_DIRS : R_PROCESS;
+ frame.which = which;
+ frame.aflag = aflag;
+ frame.locktype = locktype;
+ frame.dosrcs = dosrcs;
+
+ /* If our repository_in has a trailing "/.", remove it before storing it
+ * for do_recursion().
+ *
+ * FIXME: This is somewhat of a hack in the sense that many of our callers
+ * painstakingly compute and add the trailing '.' we now remove.
+ */
+ while (repository_in && strlen (repository_in) >= 2
+ && repository_in[strlen (repository_in) - 2] == '/'
+ && repository_in[strlen (repository_in) - 1] == '.')
+ {
+ /* Beware the case where the string is exactly "/." or "//.".
+ * Paths with a leading "//" are special on some early UNIXes.
+ */
+ if (strlen (repository_in) == 2 || strlen (repository_in) == 3)
+ repository_in[strlen (repository_in) - 1] = '\0';
+ else
+ repository_in[strlen (repository_in) - 2] = '\0';
+ }
+ frame.repository = repository_in;
+
+ expand_wild (argc, argv, &argc, &argv);
+
+ if (update_preload == NULL)
+ update_dir = xstrdup ("");
+ else
+ update_dir = xstrdup (update_preload);
+
+ /* clean up from any previous calls to start_recursion */
+ if (repository)
+ {
+ free (repository);
+ repository = NULL;
+ }
+ if (filelist)
+ dellist (&filelist); /* FIXME-krp: no longer correct. */
+ if (dirlist)
+ dellist (&dirlist);
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ for (i = 0; i < argc; ++i)
+ server_pathname_check (argv[i]);
+ }
+#endif
+
+ if (argc == 0)
+ {
+ int just_subdirs = (which & W_LOCAL) && !isdir (CVSADM);
+
+#ifdef CLIENT_SUPPORT
+ if (!just_subdirs
+ && CVSroot_cmdline == NULL
+ && current_parsed_root->isremote)
+ {
+ cvsroot_t *root = Name_Root (NULL, update_dir);
+ if (root)
+ {
+ if (strcmp (root->original, original_parsed_root->original))
+ /* We're skipping this directory because it is for
+ * a different root. Therefore, we just want to
+ * do the subdirectories only. Processing files would
+ * cause a working directory from one repository to be
+ * processed against a different repository, which could
+ * cause all kinds of spurious conflicts and such.
+ *
+ * Question: what about the case of "cvs update foo"
+ * where we process foo/bar and not foo itself? That
+ * seems to be handled somewhere (else) but why should
+ * it be a separate case? Needs investigation... */
+ just_subdirs = 1;
+ }
+ }
+#endif
+
+ /*
+ * There were no arguments, so we'll probably just recurse. The
+ * exception to the rule is when we are called from a directory
+ * without any CVS administration files. That has always meant to
+ * process each of the sub-directories, so we pretend like we were
+ * called with the list of sub-dirs of the current dir as args
+ */
+ if (just_subdirs)
+ {
+ dirlist = Find_Directories (NULL, W_LOCAL, NULL);
+ /* If there are no sub-directories, there is a certain logic in
+ favor of doing nothing, but in fact probably the user is just
+ confused about what directory they are in, or whether they
+ cvs add'd a new directory. In the case of at least one
+ sub-directory, at least when we recurse into them we
+ notice (hopefully) whether they are under CVS control. */
+ if (list_isempty (dirlist))
+ {
+ if (update_dir[0] == '\0')
+ error (0, 0, "in directory .:");
+ else
+ error (0, 0, "in directory %s:", update_dir);
+ error (1, 0,
+ "there is no version here; run '%s checkout' first",
+ program_name);
+ }
+#ifdef CLIENT_SUPPORT
+ else if (current_parsed_root->isremote && server_started)
+ {
+ /* In the the case "cvs update foo bar baz", a call to
+ send_file_names in update.c will have sent the
+ appropriate "Argument" commands to the server. In
+ this case, that won't have happened, so we need to
+ do it here. While this example uses "update", this
+ generalizes to other commands. */
+
+ /* This is the same call to Find_Directories as above.
+ FIXME: perhaps it would be better to write a
+ function that duplicates a list. */
+ args_to_send_when_finished = Find_Directories (NULL,
+ W_LOCAL,
+ NULL);
+ }
+#endif
+ }
+ else
+ addlist (&dirlist, ".");
+
+ goto do_the_work;
+ }
+
+
+ /*
+ * There were arguments, so we have to handle them by hand. To do
+ * that, we set up the filelist and dirlist with the arguments and
+ * call do_recursion. do_recursion recognizes the fact that the
+ * lists are non-null when it starts and doesn't update them.
+ *
+ * explicitly named directories are stored in dirlist.
+ * explicitly named files are stored in filelist.
+ * other possibility is named entities whicha are not currently in
+ * the working directory.
+ */
+
+ for (i = 0; i < argc; i++)
+ {
+ /* if this argument is a directory, then add it to the list of
+ directories. */
+
+ if (!wrap_name_has (argv[i], WRAP_TOCVS) && isdir (argv[i]))
+ {
+ strip_trailing_slashes (argv[i]);
+ addlist (&dirlist, argv[i]);
+ }
+ else
+ {
+ /* otherwise, split argument into directory and component names. */
+ char *dir;
+ char *comp;
+ char *file_to_try;
+
+ /* Now break out argv[i] into directory part (DIR) and file part
+ * (COMP). DIR and COMP will each point to a newly malloc'd
+ * string.
+ */
+ dir = xstrdup (argv[i]);
+ /* It's okay to cast out last_component's const below since we know
+ * we just allocated dir here and have control of it.
+ */
+ comp = (char *)last_component (dir);
+ if (comp == dir)
+ {
+ /* no dir component. What we have is an implied "./" */
+ dir = xstrdup(".");
+ }
+ else
+ {
+ comp[-1] = '\0';
+ comp = xstrdup (comp);
+ }
+
+ /* if this argument exists as a file in the current
+ working directory tree, then add it to the files list. */
+
+ if (!(which & W_LOCAL))
+ {
+ /* If doing rtag, we've done a chdir to the repository. */
+ file_to_try = Xasprintf ("%s%s", argv[i], RCSEXT);
+ }
+ else
+ file_to_try = xstrdup (argv[i]);
+
+ if (isfile (file_to_try))
+ addfile (&files_by_dir, dir, comp);
+ else if (isdir (dir))
+ {
+ if ((which & W_LOCAL) && isdir (CVSADM) &&
+ !current_parsed_root->isremote)
+ {
+ /* otherwise, look for it in the repository. */
+ char *tmp_update_dir;
+ char *repos;
+ char *reposfile;
+
+ tmp_update_dir = xmalloc (strlen (update_dir)
+ + strlen (dir)
+ + 5);
+ strcpy (tmp_update_dir, update_dir);
+
+ if (*tmp_update_dir != '\0')
+ strcat (tmp_update_dir, "/");
+
+ strcat (tmp_update_dir, dir);
+
+ /* look for it in the repository. */
+ repos = Name_Repository (dir, tmp_update_dir);
+ reposfile = Xasprintf ("%s/%s", repos, comp);
+ free (repos);
+
+ if (!wrap_name_has (comp, WRAP_TOCVS) && isdir (reposfile))
+ addlist (&dirlist, argv[i]);
+ else
+ addfile (&files_by_dir, dir, comp);
+
+ free (tmp_update_dir);
+ free (reposfile);
+ }
+ else
+ addfile (&files_by_dir, dir, comp);
+ }
+ else
+ error (1, 0, "no such directory `%s'", dir);
+
+ free (file_to_try);
+ free (dir);
+ free (comp);
+ }
+ }
+
+ /* At this point we have looped over all named arguments and built
+ a coupla lists. Now we unroll the lists, setting up and
+ calling do_recursion. */
+
+ err += walklist (files_by_dir, unroll_files_proc, (void *) &frame);
+ dellist(&files_by_dir);
+
+ /* then do_recursion on the dirlist. */
+ if (dirlist != NULL)
+ {
+ do_the_work:
+ err += do_recursion (&frame);
+ }
+
+ /* Free the data which expand_wild allocated. */
+ free_names (&argc, argv);
+
+ free (update_dir);
+ update_dir = NULL;
+
+#ifdef CLIENT_SUPPORT
+ if (args_to_send_when_finished != NULL)
+ {
+ /* FIXME (njc): in the multiroot case, we don't want to send
+ argument commands for those top-level directories which do
+ not contain any subdirectories which have files checked out
+ from current_parsed_root. If we do, and two repositories
+ have a module with the same name, nasty things could happen.
+
+ This is hard. Perhaps we should send the Argument commands
+ later in this procedure, after we've had a chance to notice
+ which directores we're using (after do_recursion has been
+ called once). This means a _lot_ of rewriting, however.
+
+ What we need to do for that to happen is descend the tree
+ and construct a list of directories which are checked out
+ from current_cvsroot. Now, we eliminate from the list all
+ of those directories which are immediate subdirectories of
+ another directory in the list. To say that the opposite
+ way, we keep the directories which are not immediate
+ subdirectories of any other in the list. Here's a picture:
+
+ a
+ / \
+ B C
+ / \
+ D e
+ / \
+ F G
+ / \
+ H I
+
+ The node in capitals are those directories which are
+ checked out from current_cvsroot. We want the list to
+ contain B, C, F, and G. D, H, and I are not included,
+ because their parents are also checked out from
+ current_cvsroot.
+
+ The algorithm should be:
+
+ 1) construct a tree of all directory names where each
+ element contains a directory name and a flag which notes if
+ that directory is checked out from current_cvsroot
+
+ a0
+ / \
+ B1 C1
+ / \
+ D1 e0
+ / \
+ F1 G1
+ / \
+ H1 I1
+
+ 2) Recursively descend the tree. For each node, recurse
+ before processing the node. If the flag is zero, do
+ nothing. If the flag is 1, check the node's parent. If
+ the parent's flag is one, change the current entry's flag
+ to zero.
+
+ a0
+ / \
+ B1 C1
+ / \
+ D0 e0
+ / \
+ F1 G1
+ / \
+ H0 I0
+
+ 3) Walk the tree and spit out "Argument" commands to tell
+ the server which directories to munge.
+
+ Yuck. It's not clear this is worth spending time on, since
+ we might want to disable cvs commands entirely from
+ directories that do not have CVSADM files...
+
+ Anyways, the solution as it stands has modified server.c
+ (dirswitch) to create admin files [via server.c
+ (create_adm_p)] in all path elements for a client's
+ "Directory xxx" command, which forces the server to descend
+ and serve the files there. client.c (send_file_names) has
+ also been modified to send only those arguments which are
+ appropriate to current_parsed_root.
+
+ */
+
+ /* Construct a fake argc/argv pair. */
+
+ int our_argc = 0, i;
+ char **our_argv = NULL;
+
+ if (! list_isempty (args_to_send_when_finished))
+ {
+ Node *head, *p;
+
+ head = args_to_send_when_finished->list;
+
+ /* count the number of nodes */
+ i = 0;
+ for (p = head->next; p != head; p = p->next)
+ i++;
+ our_argc = i;
+
+ /* create the argument vector */
+ our_argv = xmalloc (sizeof (char *) * our_argc);
+
+ /* populate it */
+ i = 0;
+ for (p = head->next; p != head; p = p->next)
+ our_argv[i++] = xstrdup (p->key);
+ }
+
+ /* We don't want to expand widcards, since we've just created
+ a list of directories directly from the filesystem. */
+ send_file_names (our_argc, our_argv, 0);
+
+ /* Free our argc/argv. */
+ if (our_argv != NULL)
+ {
+ for (i = 0; i < our_argc; i++)
+ free (our_argv[i]);
+ free (our_argv);
+ }
+
+ dellist (&args_to_send_when_finished);
+ }
+#endif
+
+ return err;
+}
+
+
+
+/*
+ * Implement the recursive policies on the local directory. This may be
+ * called directly, or may be called by start_recursion.
+ */
+static int
+do_recursion (struct recursion_frame *frame)
+{
+ int err = 0;
+ int dodoneproc = 1;
+ char *srepository = NULL;
+ List *entries = NULL;
+ int locktype;
+ bool process_this_directory = true;
+
+#ifdef HAVE_PRINT_PTR
+ TRACE (TRACE_FLOW, "do_recursion ( frame=%p )", (void *) frame);
+#else
+ TRACE (TRACE_FLOW, "do_recursion ( frame=%lx )", (unsigned long) frame);
+#endif
+
+ /* do nothing if told */
+ if (frame->flags == R_SKIP_ALL)
+ return 0;
+
+ locktype = noexec ? CVS_LOCK_NONE : frame->locktype;
+
+ /* The fact that locks are not active here is what makes us fail to have
+ the
+
+ If someone commits some changes in one cvs command,
+ then an update by someone else will either get all the
+ changes, or none of them.
+
+ property (see node Concurrency in cvs.texinfo).
+
+ The most straightforward fix would just to readlock the whole
+ tree before starting an update, but that means that if a commit
+ gets blocked on a big update, it might need to wait a *long*
+ time.
+
+ A more adequate fix would be a two-pass design for update,
+ checkout, etc. The first pass would go through the repository,
+ with the whole tree readlocked, noting what versions of each
+ file we want to get. The second pass would release all locks
+ (except perhaps short-term locks on one file at a
+ time--although I think RCS already deals with this) and
+ actually get the files, specifying the particular versions it wants.
+
+ This could be sped up by separating out the data needed for the
+ first pass into a separate file(s)--for example a file
+ attribute for each file whose value contains the head revision
+ for each branch. The structure should be designed so that
+ commit can relatively quickly update the information for a
+ single file or a handful of files (file attributes, as
+ implemented in Jan 96, are probably acceptable; improvements
+ would be possible such as branch attributes which are in
+ separate files for each branch). */
+
+#if defined(SERVER_SUPPORT) && defined(SERVER_FLOWCONTROL)
+ /*
+ * Now would be a good time to check to see if we need to stop
+ * generating data, to give the buffers a chance to drain to the
+ * remote client. We should not have locks active at this point,
+ * but if there are writelocks around, we cannot pause here. */
+ if (server_active && locktype != CVS_LOCK_WRITE)
+ server_pause_check();
+#endif
+
+ /* Check the value in CVSADM_ROOT and see if it's in the list. If
+ not, add it to our lists of CVS/Root directories and do not
+ process the files in this directory. Otherwise, continue as
+ usual. THIS_ROOT might be NULL if we're doing an initial
+ checkout -- check before using it. The default should be that
+ we process a directory's contents and only skip those contents
+ if a CVS/Root file exists.
+
+ If we're running the server, we want to process all
+ directories, since we're guaranteed to have only one CVSROOT --
+ our own. */
+
+ /* If -d was specified, it should override CVS/Root.
+
+ In the single-repository case, it is long-standing CVS behavior
+ and makes sense - the user might want another access method,
+ another server (which mounts the same repository), &c.
+
+ In the multiple-repository case, -d overrides all CVS/Root
+ files. That is the only plausible generalization I can
+ think of. */
+ if (CVSroot_cmdline == NULL && !server_active)
+ {
+ cvsroot_t *this_root = Name_Root (NULL, update_dir);
+ if (this_root != NULL)
+ {
+ if (findnode (root_directories, this_root->original))
+ {
+ process_this_directory =
+ !strcmp (original_parsed_root->original,
+ this_root->original);
+ }
+ else
+ {
+ /* Add it to our list. */
+
+ Node *n = getnode ();
+ n->type = NT_UNKNOWN;
+ n->key = xstrdup (this_root->original);
+ n->data = this_root;
+
+ if (addnode (root_directories, n))
+ error (1, 0, "cannot add new CVSROOT %s",
+ this_root->original);
+
+ process_this_directory = false;
+ }
+ }
+ }
+
+ /*
+ * Fill in repository with the current repository
+ */
+ if (frame->which & W_LOCAL)
+ {
+ if (isdir (CVSADM))
+ {
+ repository = Name_Repository (NULL, update_dir);
+ srepository = repository; /* remember what to free */
+ }
+ else
+ repository = NULL;
+ }
+ else
+ {
+ repository = frame->repository;
+ assert (repository != NULL);
+ assert (strstr (repository, "/./") == NULL);
+ }
+
+ fileattr_startdir (repository);
+
+ /*
+ * The filesdoneproc needs to be called for each directory where files
+ * processed, or each directory that is processed by a call where no
+ * directories were passed in. In fact, the only time we don't want to
+ * call back the filesdoneproc is when we are processing directories that
+ * were passed in on the command line (or in the special case of `.' when
+ * we were called with no args
+ */
+ if (dirlist != NULL && filelist == NULL)
+ dodoneproc = 0;
+
+ /*
+ * If filelist or dirlist is already set, we don't look again. Otherwise,
+ * find the files and directories
+ */
+ if (filelist == NULL && dirlist == NULL)
+ {
+ /* both lists were NULL, so start from scratch */
+ if (frame->fileproc != NULL && frame->flags != R_SKIP_FILES)
+ {
+ int lwhich = frame->which;
+
+ /* be sure to look in the attic if we have sticky tags/date */
+ if ((lwhich & W_ATTIC) == 0)
+ if (isreadable (CVSADM_TAG))
+ lwhich |= W_ATTIC;
+
+ /* In the !(which & W_LOCAL) case, we filled in repository
+ earlier in the function. In the (which & W_LOCAL) case,
+ the Find_Names function is going to look through the
+ Entries file. If we do not have a repository, that
+ does not make sense, so we insist upon having a
+ repository at this point. Name_Repository will give a
+ reasonable error message. */
+ if (repository == NULL)
+ {
+ Name_Repository (NULL, update_dir);
+ assert (!"Not reached. Please report this problem to <"
+ PACKAGE_BUGREPORT ">");
+ }
+ /* find the files and fill in entries if appropriate */
+ if (process_this_directory)
+ {
+ filelist = Find_Names (repository, lwhich, frame->aflag,
+ &entries);
+ if (filelist == NULL)
+ {
+ error (0, 0, "skipping directory %s", update_dir);
+ /* Note that Find_Directories and the filesdoneproc
+ in particular would do bad things ("? foo.c" in
+ the case of some filesdoneproc's). */
+ goto skip_directory;
+ }
+ }
+ }
+
+ /* find sub-directories if we will recurse */
+ if (frame->flags != R_SKIP_DIRS)
+ dirlist = Find_Directories (
+ process_this_directory ? repository : NULL,
+ frame->which, entries);
+ }
+ else
+ {
+ /* something was passed on the command line */
+ if (filelist != NULL && frame->fileproc != NULL)
+ {
+ /* we will process files, so pre-parse entries */
+ if (frame->which & W_LOCAL)
+ entries = Entries_Open (frame->aflag, NULL);
+ }
+ }
+
+ /* process the files (if any) */
+ if (process_this_directory && filelist != NULL && frame->fileproc)
+ {
+ struct file_info finfo_struct;
+ struct frame_and_file frfile;
+
+ /* Lock the repository, if necessary. */
+ if (repository)
+ {
+ if (locktype == CVS_LOCK_READ)
+ {
+ if (Reader_Lock (repository) != 0)
+ error (1, 0, "read lock failed - giving up");
+ }
+ else if (locktype == CVS_LOCK_WRITE)
+ lock_dir_for_write (repository);
+ }
+
+#ifdef CLIENT_SUPPORT
+ /* For the server, we handle notifications in a completely different
+ place (server_notify). For local, we can't do them here--we don't
+ have writelocks in place, and there is no way to get writelocks
+ here. */
+ if (current_parsed_root->isremote)
+ notify_check (repository, update_dir);
+#endif /* CLIENT_SUPPORT */
+
+ finfo_struct.repository = repository;
+ finfo_struct.update_dir = update_dir;
+ finfo_struct.entries = entries;
+ /* do_file_proc will fill in finfo_struct.file. */
+
+ frfile.finfo = &finfo_struct;
+ frfile.frame = frame;
+
+ /* process the files */
+ err += walklist (filelist, do_file_proc, &frfile);
+
+ /* unlock it */
+ if (/* We only lock the repository above when repository is set */
+ repository
+ /* and when asked for a read or write lock. */
+ && locktype != CVS_LOCK_NONE)
+ Simple_Lock_Cleanup ();
+
+ /* clean up */
+ dellist (&filelist);
+ }
+
+ /* call-back files done proc (if any) */
+ if (process_this_directory && dodoneproc && frame->filesdoneproc != NULL)
+ err = frame->filesdoneproc (frame->callerdat, err, repository,
+ update_dir[0] ? update_dir : ".",
+ entries);
+
+ skip_directory:
+ fileattr_write ();
+ fileattr_free ();
+
+ /* process the directories (if necessary) */
+ if (dirlist != NULL)
+ {
+ struct frame_and_entries frent;
+
+ frent.frame = frame;
+ frent.entries = entries;
+ err += walklist (dirlist, do_dir_proc, &frent);
+ }
+#if 0
+ else if (frame->dirleaveproc != NULL)
+ err += frame->dirleaveproc (frame->callerdat, ".", err, ".");
+#endif
+ dellist (&dirlist);
+
+ if (entries)
+ {
+ Entries_Close (entries);
+ entries = NULL;
+ }
+
+ /* free the saved copy of the pointer if necessary */
+ if (srepository)
+ free (srepository);
+ repository = NULL;
+
+#ifdef HAVE_PRINT_PTR
+ TRACE (TRACE_FLOW, "Leaving do_recursion (frame=%p)", (void *)frame);
+#else
+ TRACE (TRACE_FLOW, "Leaving do_recursion (frame=%lx)",
+ (unsigned long)frame);
+#endif
+
+ return err;
+}
+
+
+
+/*
+ * Process each of the files in the list with the callback proc
+ *
+ * NOTES
+ * Fills in FINFO->fullname, and sometimes FINFO->rcs before
+ * calling the callback proc (FRFILE->frame->fileproc), but frees them
+ * before return.
+ *
+ * OUTPUTS
+ * Fills in FINFO->file.
+ *
+ * RETURNS
+ * 0 if we were supposed to find an RCS file but couldn't.
+ * Otherwise, returns the error code returned by the callback function.
+ */
+static int
+do_file_proc (Node *p, void *closure)
+{
+ struct frame_and_file *frfile = closure;
+ struct file_info *finfo = frfile->finfo;
+ int ret;
+ char *tmp;
+
+ finfo->file = p->key;
+ if (finfo->update_dir[0] != '\0')
+ tmp = Xasprintf ("%s/%s", finfo->update_dir, finfo->file);
+ else
+ tmp = xstrdup (finfo->file);
+
+ if (frfile->frame->dosrcs && repository)
+ {
+ finfo->rcs = RCS_parse (finfo->file, repository);
+
+ /* OK, without W_LOCAL the error handling becomes relatively
+ simple. The file names came from readdir() on the
+ repository and so we know any ENOENT is an error
+ (e.g. symlink pointing to nothing). Now, the logic could
+ be simpler - since we got the name from readdir, we could
+ just be calling RCS_parsercsfile. */
+ if (finfo->rcs == NULL
+ && !(frfile->frame->which & W_LOCAL))
+ {
+ error (0, 0, "could not read RCS file for %s", tmp);
+ free (tmp);
+ cvs_flushout ();
+ return 0;
+ }
+ }
+ else
+ finfo->rcs = NULL;
+ finfo->fullname = tmp;
+ ret = frfile->frame->fileproc (frfile->frame->callerdat, finfo);
+
+ freercsnode (&finfo->rcs);
+ free (tmp);
+
+ /* Allow the user to monitor progress with tail -f. Doing this once
+ per file should be no big deal, but we don't want the performance
+ hit of flushing on every line like previous versions of CVS. */
+ cvs_flushout ();
+
+ return ret;
+}
+
+
+
+/*
+ * Process each of the directories in the list (recursing as we go)
+ */
+static int
+do_dir_proc (Node *p, void *closure)
+{
+ struct frame_and_entries *frent = (struct frame_and_entries *) closure;
+ struct recursion_frame *frame = frent->frame;
+ struct recursion_frame xframe;
+ char *dir = p->key;
+ char *newrepos;
+ List *sdirlist;
+ char *srepository;
+ Dtype dir_return = R_PROCESS;
+ int stripped_dot = 0;
+ int err = 0;
+ struct saved_cwd cwd;
+ char *saved_update_dir;
+ bool process_this_directory = true;
+
+ if (fncmp (dir, CVSADM) == 0)
+ {
+ /* This seems to most often happen when users (beginning users,
+ generally), try "cvs ci *" or something similar. On that
+ theory, it is possible that we should just silently skip the
+ CVSADM directories, but on the other hand, using a wildcard
+ like this isn't necessarily a practice to encourage (it operates
+ only on files which exist in the working directory, unlike
+ regular CVS recursion). */
+
+ /* FIXME-reentrancy: printed_cvs_msg should be in a "command
+ struct" or some such, so that it gets cleared for each new
+ command (this is possible using the remote protocol and a
+ custom-written client). The struct recursion_frame is not
+ far back enough though, some commands (commit at least)
+ will call start_recursion several times. An alternate solution
+ would be to take this whole check and move it to a new function
+ validate_arguments or some such that all the commands call
+ and which snips the offending directory from the argc,argv
+ vector. */
+ static int printed_cvs_msg = 0;
+ if (!printed_cvs_msg)
+ {
+ error (0, 0, "warning: directory %s specified in argument",
+ dir);
+ error (0, 0, "\
+but CVS uses %s for its own purposes; skipping %s directory",
+ CVSADM, dir);
+ printed_cvs_msg = 1;
+ }
+ return 0;
+ }
+
+ saved_update_dir = update_dir;
+ update_dir = xmalloc (strlen (saved_update_dir)
+ + strlen (dir)
+ + 5);
+ strcpy (update_dir, saved_update_dir);
+
+ /* set up update_dir - skip dots if not at start */
+ if (strcmp (dir, ".") != 0)
+ {
+ if (update_dir[0] != '\0')
+ {
+ (void) strcat (update_dir, "/");
+ (void) strcat (update_dir, dir);
+ }
+ else
+ (void) strcpy (update_dir, dir);
+
+ /*
+ * Here we need a plausible repository name for the sub-directory. We
+ * create one by concatenating the new directory name onto the
+ * previous repository name. The only case where the name should be
+ * used is in the case where we are creating a new sub-directory for
+ * update -d and in that case the generated name will be correct.
+ */
+ if (repository == NULL)
+ newrepos = xstrdup ("");
+ else
+ newrepos = Xasprintf ("%s/%s", repository, dir);
+ }
+ else
+ {
+ if (update_dir[0] == '\0')
+ (void) strcpy (update_dir, dir);
+
+ if (repository == NULL)
+ newrepos = xstrdup ("");
+ else
+ newrepos = xstrdup (repository);
+ }
+
+ /* Check to see that the CVSADM directory, if it exists, seems to be
+ well-formed. It can be missing files if the user hit ^C in the
+ middle of a previous run. We want to (a) make this a nonfatal
+ error, and (b) make sure we print which directory has the
+ problem.
+
+ Do this before the direntproc, so that (1) the direntproc
+ doesn't have to guess/deduce whether we will skip the directory
+ (e.g. send_dirent_proc and whether to send the directory), and
+ (2) so that the warm fuzzy doesn't get printed if we skip the
+ directory. */
+ if (frame->which & W_LOCAL)
+ {
+ char *cvsadmdir;
+
+ cvsadmdir = xmalloc (strlen (dir)
+ + sizeof (CVSADM_REP)
+ + sizeof (CVSADM_ENT)
+ + 80);
+
+ strcpy (cvsadmdir, dir);
+ strcat (cvsadmdir, "/");
+ strcat (cvsadmdir, CVSADM);
+ if (isdir (cvsadmdir))
+ {
+ strcpy (cvsadmdir, dir);
+ strcat (cvsadmdir, "/");
+ strcat (cvsadmdir, CVSADM_REP);
+ if (!isfile (cvsadmdir))
+ {
+ /* Some commands like update may have printed "? foo" but
+ if we were planning to recurse, and don't on account of
+ CVS/Repository, we want to say why. */
+ error (0, 0, "ignoring %s (%s missing)", update_dir,
+ CVSADM_REP);
+ dir_return = R_SKIP_ALL;
+ }
+
+ /* Likewise for CVS/Entries. */
+ if (dir_return != R_SKIP_ALL)
+ {
+ strcpy (cvsadmdir, dir);
+ strcat (cvsadmdir, "/");
+ strcat (cvsadmdir, CVSADM_ENT);
+ if (!isfile (cvsadmdir))
+ {
+ /* Some commands like update may have printed "? foo" but
+ if we were planning to recurse, and don't on account of
+ CVS/Repository, we want to say why. */
+ error (0, 0, "ignoring %s (%s missing)", update_dir,
+ CVSADM_ENT);
+ dir_return = R_SKIP_ALL;
+ }
+ }
+ }
+ free (cvsadmdir);
+ }
+
+ /* Only process this directory if the root matches. This nearly
+ duplicates code in do_recursion. */
+
+ /* If -d was specified, it should override CVS/Root.
+
+ In the single-repository case, it is long-standing CVS behavior
+ and makes sense - the user might want another access method,
+ another server (which mounts the same repository), &c.
+
+ In the multiple-repository case, -d overrides all CVS/Root
+ files. That is the only plausible generalization I can
+ think of. */
+ if (CVSroot_cmdline == NULL && !server_active)
+ {
+ cvsroot_t *this_root = Name_Root (dir, update_dir);
+ if (this_root != NULL)
+ {
+ if (findnode (root_directories, this_root->original))
+ {
+ process_this_directory =
+ !strcmp (original_parsed_root->original,
+ this_root->original);
+ }
+ else
+ {
+ /* Add it to our list. */
+
+ Node *n = getnode ();
+ n->type = NT_UNKNOWN;
+ n->key = xstrdup (this_root->original);
+ n->data = this_root;
+
+ if (addnode (root_directories, n))
+ error (1, 0, "cannot add new CVSROOT %s",
+ this_root->original);
+
+ process_this_directory = false;
+ }
+ }
+ }
+
+ /* call-back dir entry proc (if any) */
+ if (dir_return == R_SKIP_ALL)
+ ;
+ else if (frame->direntproc != NULL)
+ {
+ /* If we're doing the actual processing, call direntproc.
+ Otherwise, assume that we need to process this directory
+ and recurse. FIXME. */
+
+ if (process_this_directory)
+ dir_return = frame->direntproc (frame->callerdat, dir, newrepos,
+ update_dir, frent->entries);
+ else
+ dir_return = R_PROCESS;
+ }
+ else
+ {
+ /* Generic behavior. I don't see a reason to make the caller specify
+ a direntproc just to get this. */
+ if ((frame->which & W_LOCAL) && !isdir (dir))
+ dir_return = R_SKIP_ALL;
+ }
+
+ free (newrepos);
+
+ /* only process the dir if the return code was 0 */
+ if (dir_return != R_SKIP_ALL)
+ {
+ /* save our current directory and static vars */
+ if (save_cwd (&cwd))
+ error (1, errno, "Failed to save current directory.");
+ sdirlist = dirlist;
+ srepository = repository;
+ dirlist = NULL;
+
+ /* cd to the sub-directory */
+ if (CVS_CHDIR (dir) < 0)
+ error (1, errno, "could not chdir to %s", dir);
+
+ /* honor the global SKIP_DIRS (a.k.a. local) */
+ if (frame->flags == R_SKIP_DIRS)
+ dir_return = R_SKIP_DIRS;
+
+ /* remember if the `.' will be stripped for subsequent dirs */
+ if (strcmp (update_dir, ".") == 0)
+ {
+ update_dir[0] = '\0';
+ stripped_dot = 1;
+ }
+
+ /* make the recursive call */
+ xframe = *frame;
+ xframe.flags = dir_return;
+ /* Keep track of repository, really just for r* commands (rtag, rdiff,
+ * co, ...) to tag_check_valid, since all the other commands use
+ * CVS/Repository to figure it out per directory.
+ */
+ if (repository)
+ {
+ if (strcmp (dir, ".") == 0)
+ xframe.repository = xstrdup (repository);
+ else
+ xframe.repository = Xasprintf ("%s/%s", repository, dir);
+ }
+ else
+ xframe.repository = NULL;
+ err += do_recursion (&xframe);
+ if (xframe.repository)
+ {
+ free (xframe.repository);
+ xframe.repository = NULL;
+ }
+
+ /* put the `.' back if necessary */
+ if (stripped_dot)
+ (void) strcpy (update_dir, ".");
+
+ /* call-back dir leave proc (if any) */
+ if (process_this_directory && frame->dirleaveproc != NULL)
+ err = frame->dirleaveproc (frame->callerdat, dir, err, update_dir,
+ frent->entries);
+
+ /* get back to where we started and restore state vars */
+ if (restore_cwd (&cwd))
+ error (1, errno, "Failed to restore current directory, `%s'.",
+ cwd.name);
+ free_cwd (&cwd);
+ dirlist = sdirlist;
+ repository = srepository;
+ }
+
+ free (update_dir);
+ update_dir = saved_update_dir;
+
+ return err;
+}
+
+/*
+ * Add a node to a list allocating the list if necessary.
+ */
+static void
+addlist (List **listp, char *key)
+{
+ Node *p;
+
+ if (*listp == NULL)
+ *listp = getlist ();
+ p = getnode ();
+ p->type = FILES;
+ p->key = xstrdup (key);
+ if (addnode (*listp, p) != 0)
+ freenode (p);
+}
+
+static void
+addfile (List **listp, char *dir, char *file)
+{
+ Node *n;
+ List *fl;
+
+ /* add this dir. */
+ addlist (listp, dir);
+
+ n = findnode (*listp, dir);
+ if (n == NULL)
+ {
+ error (1, 0, "can't find recently added dir node `%s' in start_recursion.",
+ dir);
+ }
+
+ n->type = DIRS;
+ fl = n->data;
+ addlist (&fl, file);
+ n->data = fl;
+ return;
+}
+
+static int
+unroll_files_proc (Node *p, void *closure)
+{
+ Node *n;
+ struct recursion_frame *frame = (struct recursion_frame *) closure;
+ int err = 0;
+ List *save_dirlist;
+ char *save_update_dir = NULL;
+ struct saved_cwd cwd;
+
+ /* if this dir was also an explicitly named argument, then skip
+ it. We'll catch it later when we do dirs. */
+ n = findnode (dirlist, p->key);
+ if (n != NULL)
+ return (0);
+
+ /* otherwise, call dorecusion for this list of files. */
+ filelist = p->data;
+ p->data = NULL;
+ save_dirlist = dirlist;
+ dirlist = NULL;
+
+ if (strcmp(p->key, ".") != 0)
+ {
+ if (save_cwd (&cwd))
+ error (1, errno, "Failed to save current directory.");
+ if ( CVS_CHDIR (p->key) < 0)
+ error (1, errno, "could not chdir to %s", p->key);
+
+ save_update_dir = update_dir;
+ update_dir = xmalloc (strlen (save_update_dir)
+ + strlen (p->key)
+ + 5);
+ strcpy (update_dir, save_update_dir);
+
+ if (*update_dir != '\0')
+ (void) strcat (update_dir, "/");
+
+ (void) strcat (update_dir, p->key);
+ }
+
+ err += do_recursion (frame);
+
+ if (save_update_dir != NULL)
+ {
+ free (update_dir);
+ update_dir = save_update_dir;
+
+ if (restore_cwd (&cwd))
+ error (1, errno, "Failed to restore current directory, `%s'.",
+ cwd.name);
+ free_cwd (&cwd);
+ }
+
+ dirlist = save_dirlist;
+ if (filelist)
+ dellist (&filelist);
+ return(err);
+}
+/* vim:tabstop=8:shiftwidth=4
+ */