/contrib/cvs/src/recurse.c
https://bitbucket.org/freebsd/freebsd-head/ · C · 1299 lines · 729 code · 148 blank · 422 comment · 207 complexity · 8df86747dec896956368a0387c3ec009 MD5 · raw file
- /*
- * Copyright (C) 1986-2008 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 "savecwd.h"
- #include "fileattr.h"
- #include "edit.h"
- #include <assert.h>
- static int do_dir_proc PROTO((Node * p, void *closure));
- static int do_file_proc PROTO((Node * p, void *closure));
- static void addlist PROTO((List ** listp, char *key));
- static int unroll_files_proc PROTO((Node *p, void *closure));
- static void addfile PROTO((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 PROTO ((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.
- Command line arguments (ARGC, ARGV) dictate the directories and
- files on which we operate. In the special case of no arguments, we
- default to ".". */
- int
- start_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc, callerdat,
- argc, argv, local, which, aflag, locktype,
- update_preload, dosrcs, repository_in)
- FILEPROC fileproc;
- FILESDONEPROC filesdoneproc;
- DIRENTPROC direntproc;
- DIRLEAVEPROC dirleaveproc;
- void *callerdat;
- int argc;
- char **argv;
- int local;
- /* This 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). */
- int which;
- int aflag;
- int locktype;
- char *update_preload;
- int dosrcs;
- /* Keep track of the repository string. This is only for the remote mode,
- * specifically, r* commands (rtag, rdiff, co, ...) where xgetwd() was
- * used to locate the repository. Things would break when xgetwd() was
- * used with a symlinked repository because xgetwd() 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.
- */
- 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;
- 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 = (char *) 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, current_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;
- free_cvsroot_t (root);
- }
- }
- #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 ((char *) NULL, W_LOCAL, (List *) 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 ((char *) NULL,
- W_LOCAL,
- (List *) 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]);
- /* Its okay to discard the const below - we know we just allocated
- * dir ourselves.
- */
- comp = (char *)last_component (dir);
- if (comp == dir)
- {
- /* no dir component. What we have is an implied "./" */
- dir = xstrdup(".");
- }
- else
- {
- char *p = comp;
- p[-1] = '\0';
- comp = xstrdup (p);
- }
- /* 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 = xmalloc (strlen (argv[i]) + sizeof (RCSEXT) + 5);
- sprintf (file_to_try, "%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')
- (void) strcat (tmp_update_dir, "/");
- (void) strcat (tmp_update_dir, dir);
- /* look for it in the repository. */
- repos = Name_Repository (dir, tmp_update_dir);
- reposfile = xmalloc (strlen (repos)
- + strlen (comp)
- + 5);
- (void) sprintf (reposfile, "%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->original. 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->original.
- */
-
- /* 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 = (char **) 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 (frame)
- struct recursion_frame *frame;
- {
- int err = 0;
- int dodoneproc = 1;
- char *srepository = NULL;
- List *entries = NULL;
- int locktype;
- int process_this_directory = 1;
- /* 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 ((char *) NULL, update_dir);
- if (this_root != NULL)
- {
- if (findnode (root_directories, this_root->original))
- {
- process_this_directory = !strcmp (current_parsed_root->original,
- this_root->original);
- free_cvsroot_t (this_root);
- }
- 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 = 0;
- }
- }
- }
- /*
- * Fill in repository with the current repository
- */
- if (frame->which & W_LOCAL)
- {
- if (isdir (CVSADM))
- {
- repository = Name_Repository ((char *) NULL, update_dir);
- srepository = repository; /* remember what to free */
- }
- else
- repository = NULL;
- }
- else
- {
- repository = frame->repository;
- assert (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 ((char *) 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;
- /* read lock it 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)
- cvs_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)
- 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, (void *) &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 = (char *) NULL;
- return err;
- }
- /*
- * Process each of the files in the list with the callback proc
- */
- static int
- do_file_proc (p, closure)
- Node *p;
- void *closure;
- {
- struct frame_and_file *frfile = (struct frame_and_file *)closure;
- struct file_info *finfo = frfile->finfo;
- int ret;
- char *tmp;
- finfo->file = p->key;
- tmp = xmalloc (strlen (finfo->file)
- + strlen (finfo->update_dir)
- + 2);
- tmp[0] = '\0';
- if (finfo->update_dir[0] != '\0')
- {
- strcat (tmp, finfo->update_dir);
- strcat (tmp, "/");
- }
- strcat (tmp, 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 = (RCSNode *) 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 (p, closure)
- 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;
- int process_this_directory = 1;
- 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 = xmalloc (strlen (repository) + strlen (dir) + 5);
- sprintf (newrepos, "%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 (current_parsed_root->original,
- this_root->original);
- free_cvsroot_t (this_root);
- }
- 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 = 0;
- }
- }
- }
- /* 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_exit ();
- 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 = xmalloc (strlen (repository)
- + strlen (dir)
- + 2);
- sprintf (xframe.repository, "%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, NULL))
- error_exit ();
- 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 (listp, key)
- 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 (listp, dir, file)
- 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 (p, closure)
- 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_exit ();
- 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, NULL))
- error_exit ();
- free_cwd (&cwd);
- }
- dirlist = save_dirlist;
- if (filelist)
- dellist (&filelist);
- return(err);
- }