/* * Finds all the ,v files in the argument directory, and adds them to the * files list. Returns 0 for success and non-zero if the argument directory * cannot be opened, in which case errno is set to indicate the error. * In the error case LIST is left in some reasonable state (unchanged, or * containing the files which were found before the error occurred). */ static int find_rcs (const char *dir, List *list, const char *regex) { Node *p; struct dirent *dp; DIR *dirp; /* set up to read the dir */ if ((dirp = opendir (dir)) == NULL) return (1); /* read the dir, grabbing the ,v files */ errno = 0; while ((dp = readdir (dirp)) != NULL) { if (CVS_FNMATCH (RCSPAT, dp->d_name, CVS_CASEFOLD) == 0) { char *comma, *q; comma = strrchr (dp->d_name, ','); /* strip the ,v */ *comma = '\0'; if(!fncmp(dp->d_name,RCSREPOVERSION)) continue; q = map_fixed_rename(dir,dp->d_name); if(q && *q && (!regex || regex_filename_match(regex, q))) { p = getnode (); p->type = FILES; p->key = xstrdup (q); if(!findnode_fn(list,p->key)) { if(addnode (list, p) != 0) freenode (p); } } } errno = 0; } if (errno != 0) { int save_errno = errno; (void) closedir (dirp); errno = save_errno; return 1; } (void) closedir (dirp); return (0); }
static void addfile (List **listp, char *dir, char *file) { Node *n; List *fl; /* add this dir. */ addlist (listp, dir); n = findnode_fn (*listp, dir); if (n == NULL) { error (1, 0, "can't find recently added dir node `%s' in start_recursion.", dir); } n->type = DIRS; fl = (List *) n->data; addlist (&fl, file); n->data = (char *) fl; return; }
/* Fill in and return a Vers_TS structure for the file FINFO. * * INPUTS * finfo struct file_info data about the file to be examined. * options Keyword expansion options, I think generally from the * command line. Can be either NULL or "" to indicate * none are specified here. * tag Tag specified by user on the command line (via -r). * date Date specified by user on the command line (via -D). * force_tag_match If set and TAG is specified, will only set RET->vn_rcs * based on TAG. Otherwise, if TAG is specified and does * not exist in the file, RET->vn_rcs will be set to the * head revision. * set_time If set, set the last modification time of the user file * specified by FINFO to the checkin time of RET->vn_rcs. * * RETURNS * Vers_TS structure for FINFO. */ Vers_TS * Version_TS (struct file_info *finfo, char *options, char *tag, char *date, int force_tag_match, int set_time) { Node *p; RCSNode *rcsdata; Vers_TS *vers_ts; struct stickydirtag *sdtp; Entnode *entdata; char *rcsexpand = NULL; /* get a new Vers_TS struct */ vers_ts = xmalloc (sizeof (Vers_TS)); memset (vers_ts, 0, sizeof (*vers_ts)); /* * look up the entries file entry and fill in the version and timestamp * if entries is NULL, there is no entries file so don't bother trying to * look it up (used by checkout -P) */ if (finfo->entries == NULL) { sdtp = NULL; p = NULL; } else { p = findnode_fn (finfo->entries, finfo->file); sdtp = finfo->entries->list->data; /* list-private */ } if (p == NULL) { entdata = NULL; } else { entdata = p->data; if (entdata->type == ENT_SUBDIR) { /* According to cvs.texinfo, the various fields in the Entries file for a directory (other than the name) do not have a defined meaning. We need to pass them along without getting confused based on what is in them. Therefore we make sure not to set vn_user and the like from Entries, add.c and perhaps other code will expect these fields to be NULL for a directory. */ vers_ts->entdata = entdata; } else #ifdef SERVER_SUPPORT /* An entries line with "D" in the timestamp indicates that the client sent Is-modified without sending Entry. So we want to use the entries line for the sole purpose of telling time_stamp_server what is up; we don't want the rest of CVS to think there is an entries line. */ if (strcmp (entdata->timestamp, "D") != 0) #endif { vers_ts->vn_user = xstrdup (entdata->version); vers_ts->ts_rcs = xstrdup (entdata->timestamp); vers_ts->ts_conflict = xstrdup (entdata->conflict); if (!(tag || date) && !(sdtp && sdtp->aflag)) { vers_ts->tag = xstrdup (entdata->tag); vers_ts->date = xstrdup (entdata->date); } vers_ts->entdata = entdata; } /* Even if we don't have an "entries line" as such (vers_ts->entdata), we want to pick up options which could have been from a Kopt protocol request. */ if (!options || *options == '\0') { if (!(sdtp && sdtp->aflag)) vers_ts->options = xstrdup (entdata->options); } } /* Always look up the RCS keyword mode when we have an RCS archive. It * will either be needed as a default or to avoid allowing the -k options * specified on the command line from overriding binary mode (-kb). */ if (finfo->rcs != NULL) rcsexpand = RCS_getexpand (finfo->rcs); /* * -k options specified on the command line override (and overwrite) * options stored in the entries file and default options from the RCS * archive, except for binary mode (-kb). */ if (options && *options != '\0') { if (vers_ts->options != NULL) free (vers_ts->options); if (rcsexpand != NULL && strcmp (rcsexpand, "b") == 0) vers_ts->options = xstrdup ("-kb"); else vers_ts->options = xstrdup (options); } else if ((!vers_ts->options || *vers_ts->options == '\0') && rcsexpand != NULL) { /* If no keyword expansion was specified on command line, use whatever was in the rcs file (if there is one). This is how we, if we are the server, tell the client whether a file is binary. */ if (vers_ts->options != NULL) free (vers_ts->options); vers_ts->options = xmalloc (strlen (rcsexpand) + 3); strcpy (vers_ts->options, "-k"); strcat (vers_ts->options, rcsexpand); } if (!vers_ts->options) vers_ts->options = xstrdup (""); /* * if tags were specified on the command line, they override what is in * the Entries file */ if (tag || date) { vers_ts->tag = xstrdup (tag); vers_ts->date = xstrdup (date); } else if (!vers_ts->entdata && (sdtp && sdtp->aflag == 0)) { if (!vers_ts->tag) { vers_ts->tag = xstrdup (sdtp->tag); vers_ts->nonbranch = sdtp->nonbranch; } if (!vers_ts->date) vers_ts->date = xstrdup (sdtp->date); } /* Now look up the info on the source controlled file */ if (finfo->rcs != NULL) { rcsdata = finfo->rcs; rcsdata->refcount++; } else if (finfo->repository != NULL) rcsdata = RCS_parse (finfo->file, finfo->repository); else rcsdata = NULL; if (rcsdata != NULL) { /* squirrel away the rcsdata pointer for others */ vers_ts->srcfile = rcsdata; if (vers_ts->tag && strcmp (vers_ts->tag, TAG_BASE) == 0) { vers_ts->vn_rcs = xstrdup (vers_ts->vn_user); vers_ts->vn_tag = xstrdup (vers_ts->vn_user); } else { int simple; vers_ts->vn_rcs = RCS_getversion (rcsdata, vers_ts->tag, vers_ts->date, force_tag_match, &simple); if (vers_ts->vn_rcs == NULL) vers_ts->vn_tag = NULL; else if (simple) vers_ts->vn_tag = xstrdup (vers_ts->tag); else vers_ts->vn_tag = xstrdup (vers_ts->vn_rcs); } /* * If the source control file exists and has the requested revision, * get the Date the revision was checked in. If "user" exists, set * its mtime. */ if (set_time && vers_ts->vn_rcs != NULL) { #ifdef SERVER_SUPPORT if (server_active) server_modtime (finfo, vers_ts); else #endif { struct utimbuf t; memset (&t, 0, sizeof (t)); t.modtime = RCS_getrevtime (rcsdata, vers_ts->vn_rcs, 0, 0); if (t.modtime != (time_t) -1) { #ifdef UTIME_EXPECTS_WRITABLE int change_it_back = 0; #endif (void) time (&t.actime); #ifdef UTIME_EXPECTS_WRITABLE if (!iswritable (finfo->file)) { xchmod (finfo->file, 1); change_it_back = 1; } #endif /* UTIME_EXPECTS_WRITABLE */ /* This used to need to ignore existence_errors (for cases like where update.c now clears set_time if noexec, but didn't used to). I think maybe now it doesn't (server_modtime does not like those kinds of cases). */ (void) utime (finfo->file, &t); #ifdef UTIME_EXPECTS_WRITABLE if (change_it_back) xchmod (finfo->file, 0); #endif /* UTIME_EXPECTS_WRITABLE */ } } } } /* get user file time-stamp in ts_user */ if (finfo->entries != NULL) { #ifdef SERVER_SUPPORT if (server_active) time_stamp_server (finfo->file, vers_ts, entdata); else #endif vers_ts->ts_user = time_stamp (finfo->file); } return (vers_ts); }
/* * Process the current directory, looking for files not in ILIST and * not on the global ignore list for this directory. If we find one, * call PROC passing it the name of the file and the update dir. * ENTRIES is the entries list, which is used to identify known * directories. ENTRIES may be NULL, in which case we assume that any * directory with a CVS administration directory is known. */ void ignore_files (List *ilist, List *entries, const char *update_dir, Ignore_proc proc) { int subdirs; DIR *dirp; struct dirent *dp; struct stat sb; char *file; const char *xdir; List *files; Node *p; /* Set SUBDIRS if we have subdirectory information in ENTRIES. */ if (entries == NULL) subdirs = 0; else { struct stickydirtag *sdtp = entries->list->data; subdirs = sdtp == NULL || sdtp->subdirs; } /* we get called with update_dir set to "." sometimes... strip it */ if (strcmp (update_dir, ".") == 0) xdir = ""; else xdir = update_dir; dirp = CVS_OPENDIR ("."); if (dirp == NULL) { error (0, errno, "cannot open current directory"); return; } ign_add_file (CVSDOTIGNORE, 1); wrap_add_file (CVSDOTWRAPPER, 1); /* Make a list for the files. */ files = getlist (); while (errno = 0, (dp = CVS_READDIR (dirp)) != NULL) { file = dp->d_name; if (strcmp (file, ".") == 0 || strcmp (file, "..") == 0) continue; if (findnode_fn (ilist, file) != NULL) continue; if (subdirs) { Node *node; node = findnode_fn (entries, file); if (node != NULL && ((Entnode *) node->data)->type == ENT_SUBDIR) { char *p; int dir; /* For consistency with past behaviour, we only ignore this directory if there is a CVS subdirectory. This will normally be the case, but the user may have messed up the working directory somehow. */ p = Xasprintf ("%s/%s", file, CVSADM); dir = isdir (p); free (p); if (dir) continue; } } /* We could be ignoring FIFOs and other files which are neither regular files nor directories here. */ if (ign_name (file)) continue; if ( #ifdef DT_DIR dp->d_type != DT_UNKNOWN || #endif lstat (file, &sb) != -1) { if ( #ifdef DT_DIR dp->d_type == DT_DIR || (dp->d_type == DT_UNKNOWN && S_ISDIR (sb.st_mode)) #else S_ISDIR (sb.st_mode) #endif ) { if (!subdirs) { char *temp = Xasprintf ("%s/%s", file, CVSADM); if (isdir (temp)) { free (temp); continue; } free (temp); } } #ifdef S_ISLNK else if ( #ifdef DT_DIR dp->d_type == DT_LNK || (dp->d_type == DT_UNKNOWN && S_ISLNK (sb.st_mode)) #else S_ISLNK (sb.st_mode) #endif ) { continue; } #endif } p = getnode (); p->type = FILES; p->key = xstrdup (file); (void) addnode (files, p); } if (errno != 0) error (0, errno, "error reading current directory"); (void) CVS_CLOSEDIR (dirp); sortlist (files, fsortcmp); for (p = files->list->next; p != files->list; p = p->next) (*proc) (p->key, xdir); dellist (&files); }
int cvsrename(int argc, char **argv) { int c; int err = 0; char *repos_file1, *repos_file2; char *root1, *root2; const char *filename1, *filename2, *dir1, *dir2; int rootlen; List *ent,*ent2; Node *node; Entnode *entnode; const char *cwd; if (argc == -1) usage (rename_usage); quiet = 0; optind = 0; while ((c = getopt (argc, argv, "+q")) != -1) { switch (c) { case 'q': quiet = 1; break; case '?': default: usage (rename_usage); break; } } argc -= optind; argv += optind; if(argc!=2) { usage(rename_usage); }; error(0,0,"Warning: rename is still experimental and may not behave as you would expect"); if(current_parsed_root->isremote) { if(!supported_request("Rename")) error(1,0,"Remote server does not support rename"); if(!supported_request("Can-Rename")) error(1,0,"Renames are currently disabled"); } if(!strcmp(argv[0],argv[1])) return 0; rootlen = strlen(current_parsed_root->directory); if(!isfile(argv[0])) error(1,0,"%s does not exist",argv[0]); if(isfile(argv[1]) && fncmp(argv[0],argv[1])) /* We allow case renames (on Unix this is redundant) */ error(1,0,"%s already exists",argv[1]); if(isdir(argv[0])) error(1,0,"Directory renames are not currently supported"); validate_file(argv[0],&root1, &repos_file1, &filename1, &dir1, 1); validate_file(argv[1],&root2, &repos_file2, &filename2, &dir2, 0); if(strcmp(root1,root2) || strcmp(root1,current_parsed_root->original)) error(1,0,"%s and %s are in different repositories",argv[0],argv[1]); xfree(root1); xfree(root2); repos_file1 = (char*)xrealloc(repos_file1, strlen(filename1)+strlen(repos_file1)+rootlen+10); repos_file2 = (char*)xrealloc(repos_file2, strlen(filename2)+strlen(repos_file2)+rootlen+10); memmove(repos_file1+rootlen+1,repos_file1,strlen(repos_file1)+1); memmove(repos_file2+rootlen+1,repos_file2,strlen(repos_file2)+1); strcpy(repos_file1,current_parsed_root->directory); strcpy(repos_file2,current_parsed_root->directory); repos_file1[rootlen]='/'; repos_file2[rootlen]='/'; strcat(repos_file1,"/"); strcat(repos_file2,"/"); strcat(repos_file1,filename1); strcat(repos_file2,filename2); if(fncmp(argv[0],argv[1])) set_mapping(dir2,repos_file2+rootlen+1,""); /* Delete old file */ if(fncmp(dir1,dir2)) set_mapping(dir1,repos_file1+rootlen+1,""); set_mapping(dir2,repos_file1+rootlen+1,repos_file2+rootlen+1); /* Rename to new file */ cwd = xgetwd(); if(CVS_CHDIR(dir1)) error(1,errno,"Couldn't chdir to %s",dir1); ent = Entries_Open(0, NULL); node = findnode_fn(ent, filename1); entnode=(Entnode*)node->data; if(!node) { error(1,0,"%s is not listed in CVS/Entries",filename1); CVS_CHDIR(cwd); xfree(cwd); return 1; } if(!fncmp(dir1,dir2)) Rename_Entry(ent,filename1,filename2); else { if(CVS_CHDIR(cwd)) error(1,errno,"Couldn't chdir to %s",cwd); if(CVS_CHDIR(dir2)) error(1,errno,"Couldn't chdir to %s",dir2); ent2 = Entries_Open(0, NULL); if(entnode->type==ENT_FILE) Register(ent2,(char*)filename2,entnode->version,entnode->timestamp,entnode->options,entnode->tag,entnode->date,entnode->conflict,entnode->merge_from_tag_1,entnode->merge_from_tag_2,entnode->rcs_timestamp,entnode->edit_revision,entnode->edit_tag,entnode->edit_bugid,entnode->md5); else if(entnode->type==ENT_SUBDIR) Subdir_Register(ent2,NULL,filename2); else error(1,0,"Unknown entry type %d in entries file",node->type); Entries_Close(ent2); if(CVS_CHDIR(cwd)) error(1,errno,"Couldn't chdir to %s",cwd); if(CVS_CHDIR(dir1)) error(1,errno,"Couldn't chdir to %s",dir1); if(entnode->type==ENT_SUBDIR) Subdir_Deregister(ent,NULL,filename1); else if(entnode->type==ENT_FILE) Scratch_Entry(ent,filename1); else error(1,0,"Unknown entry type %d in entries file",node->type); } Entries_Close(ent); CVS_RENAME(argv[0],argv[1]); if(isdir(argv[1])) { char *tmp=(char*)xmalloc(strlen(argv[1])+strlen(CVSADM_VIRTREPOS)+10); FILE *fp; sprintf(tmp,"%s/%s",argv[1],CVSADM_VIRTREPOS); fp = fopen(tmp,"w"); if(!fp) error(0,errno,"Couldn't write %s",tmp); fprintf(fp,"%s\n",repos_file2+rootlen+1); fclose(fp); } xfree(repos_file1); xfree(repos_file2); xfree(dir1); xfree(dir2); CVS_CHDIR(cwd); xfree(cwd); return (err); }
/* * Finds all the subdirectories of the argument dir and adds them to * the specified list. Sub-directories without a CVS administration * directory are optionally ignored. If ENTRIES is not NULL, all * files on the list are ignored. Returns 0 for success or 1 on * error, in which case errno is set to indicate the error. */ static int find_dirs (const char *dir, List *list, int checkadm, List *entries, const char *regex) { Node *p; char *tmp = NULL; size_t tmp_size = 0; struct dirent *dp; DIR *dirp; int skip_emptydir = 0; /* First figure out whether we need to skip directories named Emptydir. Except in the CVSNULLREPOS case, Emptydir is just a normal directory name. */ if (isabsolute (dir) && fnncmp (dir, current_parsed_root->directory, strlen (current_parsed_root->directory)) == 0 && ISDIRSEP (dir[strlen (current_parsed_root->directory)]) && fncmp (dir + strlen (current_parsed_root->directory) + 1, CVSROOTADM) == 0) skip_emptydir = 1; /* set up to read the dir */ if ((dirp = opendir (dir)) == NULL) return (1); /* read the dir, grabbing sub-dirs */ errno = 0; while ((dp = readdir (dirp)) != NULL) { if (fncmp (dp->d_name, ".") == 0 || fncmp (dp->d_name, "..") == 0 || fncmp (dp->d_name, CVSATTIC) == 0 || fncmp (dp->d_name, CVSLCK) == 0 || fncmp (dp->d_name, CVSREP) == 0 || fncmp (dp->d_name, CVSDUMMY) == 0) goto do_it_again; /* findnode() is going to be significantly faster than stat() because it involves no system calls. That is why we bother with the entries argument, and why we check this first. */ if (entries != NULL && findnode_fn (entries, dp->d_name) != NULL) goto do_it_again; if (skip_emptydir && fncmp (dp->d_name, CVSNULLREPOS) == 0) goto do_it_again; /* don't bother stating ,v files */ if (CVS_FNMATCH (RCSPAT, dp->d_name, CVS_CASEFOLD) == 0) goto do_it_again; expand_string (&tmp, &tmp_size, strlen (dir) + strlen (dp->d_name) + 10); sprintf (tmp, "%s/%s", dir, dp->d_name); if (!isdir (tmp)) goto do_it_again; /* check for administration directories (if needed) */ if (checkadm) { /* blow off symbolic links to dirs in local dir */ /* Note that we only get here if we already set tmp above. */ if (islink (tmp)) goto do_it_again; /* check for new style */ expand_string (&tmp, &tmp_size, (strlen (dir) + strlen (dp->d_name) + sizeof (CVSADM) + 10)); (void) sprintf (tmp, "%s/%s/%s", dir, dp->d_name, CVSADM); if (!isdir (tmp)) goto do_it_again; } /* If there's a regex, test against the name with '/' on the end to signify a directory */ if(regex) { strcpy(tmp,dp->d_name); strcat(tmp,"/"); if(!regex_filename_match(regex,tmp)) goto do_it_again; } /* put it in the list */ p = getnode (); p->type = DIRS; p->key = xstrdup (dp->d_name); if (addnode (list, p) != 0) freenode (p); do_it_again: errno = 0; } if (errno != 0) { int save_errno = errno; (void) closedir (dirp); errno = save_errno; return 1; } (void) closedir (dirp); if (tmp != NULL) xfree (tmp); return (0); }
/* * 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, *virtrepos; List *sdirlist; char *srepository; char *smapped_repository; Dtype dir_return = R_PROCESS; Dtype hint; int stripped_dot = 0; int err = 0; struct saved_cwd cwd; char *saved_update_dir; int process_this_directory = 1; int directory_opened = 0; char *old_update_repos = NULL; int directory_not_valid = 0; if (fncmp (dir, CVSADM) == 0 || fncmp (dir, CVSDUMMY) == 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", dir, dir); printed_cvs_msg = 1; } return 0; } saved_update_dir = update_dir; update_dir = (char*)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 (""); virtrepos = xstrdup (""); } else { if(frame->which&W_LOCAL) { char *d=(char*)xmalloc(strlen(dir)+strlen(CVSADM_REP)+32); sprintf(d,"%s/%s",dir,CVSADM_REP); if(isfile(d)) virtrepos = Name_Repository(dir,update_dir); else { virtrepos = (char*)xmalloc (strlen (repository) + strlen (dir) + 5); sprintf (virtrepos, "%s/%s", repository, dir); } xfree(d); } else { virtrepos = (char*)xmalloc (strlen (repository) + strlen (dir) + 5); sprintf (virtrepos, "%s/%s", repository, dir); } if(!current_parsed_root->isremote) { newrepos = map_repository(virtrepos); if(!newrepos) error(1,0,"Internal error - couldn't map %s to anything",virtrepos); } else newrepos = xstrdup(virtrepos); } } else { if (update_dir[0] == '\0') (void) strcpy (update_dir, dir); if (repository == NULL) { newrepos = xstrdup (""); virtrepos = xstrdup (""); } else { newrepos = xstrdup (mapped_repository); virtrepos = 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 = (char*)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; } } } xfree (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 (!(frame->which&W_FAKE) && CVSroot_cmdline == NULL && !server_active) { char *this_root = Name_Root (dir, update_dir); if (this_root != NULL) { if (findnode_fn (root_directories, this_root) == NULL) { /* Add it to our list. */ Node *n = getnode (); n->type = NT_UNKNOWN; n->key = xstrdup (this_root); if (addnode (root_directories, n)) error (1, 0, "cannot add new CVSROOT %s", this_root); } process_this_directory = (fncmp (current_parsed_root->original, this_root) == 0); xfree (this_root); } } /* * Do we have access to this directory? */ if(!current_parsed_root->isremote) { const char *tag=NULL; const char *date=NULL; int nonbranch=0; const char *message; const char *v_msg; /* before we do anything else, see if we have any per-directory tags */ ParseTag (&tag, &date, &nonbranch, NULL); if (! verify_access (frame->permproc, newrepos, NULL, update_dir, frame->tag?frame->tag:tag,&message, &v_msg)) { if(frame->permproc!=verify_read) { if(tag) error (0, 0, "User '%s' cannot %s %s on tag/branch %s", CVS_Username, v_msg, fn_root(virtrepos), tag); else error (0, 0, "User '%s' cannot %s %s", CVS_Username, v_msg, fn_root(virtrepos)); if(message) error(0,0,"%s",message); } dir_return = R_SKIP_ALL; } xfree(tag); xfree(date); } if(dir_return!=R_SKIP_ALL) { /* Generic behavior. I don't see a reason to make the caller specify a direntproc just to get this. */ if ((frame->which & (W_LOCAL|W_FAKE))) { if(!isdir (dir)) hint = R_SKIP_ALL; else hint = R_PROCESS; } else if(!isdir(newrepos)) hint = R_SKIP_ALL; else hint = R_PROCESS; } if (dir_return == R_SKIP_ALL || dir_return == R_ERROR) ; else if(process_this_directory) { int directory_opened_status=-1; const char *dirversion=NULL; const char *dirtag=NULL; const char *dirdate=NULL; int dirnonbranch=0; if(frame->predirentproc != NULL) { frame->predirentproc (frame->callerdat, dir, newrepos, update_dir, frent->entries, virtrepos, hint); } /* before we do anything else, see if we have any per-directory tags */ ParseTag_Dir (dir, &dirtag, &dirdate, &dirnonbranch, &dirversion); directory_opened_status=open_directory(newrepos,dir,dirtag,dirdate,dirnonbranch,dirversion,current_parsed_root->isremote); if (directory_opened_status!=-1) directory_opened = 1; xfree(dirversion); xfree(dirtag); xfree(dirdate); if(!current_parsed_root->isremote) fileattr_startdir(newrepos); } /* call-back dir entry proc (if any) */ if (dir_return == R_SKIP_ALL || dir_return == R_ERROR) ; 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, virtrepos, hint); 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. */ dir_return = hint; } /* only process the dir if the return code was 0 */ if (dir_return != R_SKIP_ALL && dir_return !=R_ERROR) { /* save our current directory and static vars */ if (save_cwd (&cwd)) error_exit (); sdirlist = dirlist; srepository = repository; smapped_repository = mapped_repository; dirlist = NULL; /* cd to the sub-directory */ if (!(frame->which&(W_LOCAL|W_FAKE))) { if ( CVS_CHDIR (newrepos) < 0) { error (1, errno, "could not chdir to %s", fn_root(newrepos)); } } else { if ( !(frame->which&W_FAKE) && CVS_CHDIR (dir) < 0) { if(!noexec) error (1, errno, "could not chdir to %s", fn_root(update_dir)); else directory_not_valid=1; } } old_update_repos = update_repos; update_repos = xstrdup(newrepos); /* 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; if(directory_not_valid) { xframe.which &= ~W_LOCAL; xframe.which |= W_FAKE; } err += do_recursion (&xframe, 0); /* 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); if(directory_opened) close_directory(); /* get back to where we started and restore state vars */ if (restore_cwd (&cwd, NULL)) error_exit (); xfree(update_repos); update_repos = old_update_repos; free_cwd (&cwd); dirlist = sdirlist; repository = srepository; mapped_repository = smapped_repository; } else { if(directory_opened) close_directory(); fileattr_write(); fileattr_free(); } xfree (newrepos); xfree (virtrepos); xfree (update_dir); update_dir = saved_update_dir; if(dir_return == R_ERROR) err++; return (err); }
/* * Process each of the files in the list with the callback proc */ static int do_file_proc (Node *p, void *closure) { struct frame_and_file *frfile = (struct frame_and_file *)closure; struct file_info *finfo = frfile->finfo; int ret; Entnode *e; const char *mapped_name; const char *mapped_file_repository = NULL; if(!current_parsed_root->isremote) mapped_name = map_filename(finfo->virtual_repository, p->key, &mapped_file_repository); else mapped_name = xstrdup(p->key); finfo->file = p->key; finfo->mapped_file = mapped_name; char *ff = (char*)xmalloc (strlen (finfo->file) + strlen (finfo->update_dir) + 2); finfo->fullname = ff; ff[0] = '\0'; if (finfo->update_dir[0] != '\0') { strcat (ff, finfo->update_dir); strcat (ff, "/"); } strcat (ff, finfo->file); if (frfile->frame->dosrcs && mapped_file_repository) { finfo->rcs = RCS_parse (finfo->mapped_file, mapped_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", fn_root(finfo->fullname)); xfree (finfo->fullname); cvs_flushout (); return 0; } } else finfo->rcs = (RCSNode *) NULL; p = findnode_fn(finfo->entries,finfo->file); if(p) { const char *message; const char *v_type; e = (Entnode *)p->data; if (! verify_access(frfile->frame->permproc, finfo->repository, finfo->file, finfo->update_dir, e->tag, &message, &v_type)) { if(e->tag && !quiet && !(frfile->frame->which&W_QUIET)) error (0, 0, "User %s is unable to %s %s/%s on branch/tag %s",CVS_Username,v_type,finfo->update_dir,finfo->file,e->tag); else if(!quiet && !(frfile->frame->which&W_QUIET)) error (0, 0, "User %s is unable to %s %s/%s",CVS_Username,v_type,finfo->update_dir,finfo->file); if(message && !quiet && !(frfile->frame->which&W_QUIET)) error (0, 0, "%s", message); ret = 1; } else ret = frfile->frame->fileproc (frfile->frame->callerdat, finfo); } else ret = frfile->frame->fileproc (frfile->frame->callerdat, finfo); freercsnode(&finfo->rcs); xfree (finfo->fullname); xfree (mapped_name); xfree (mapped_file_repository); /* 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); }
/* * 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 top_level) { int err = 0; int dodoneproc = 1; char *srepository; List *entries = NULL; int should_readlock; int process_this_directory = 1; /* do nothing if told */ if (frame->flags == R_SKIP_ALL) return (0); should_readlock = noexec ? 0 : frame->readlock; /* 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(!(frame->which&W_FAKE) && CVSroot_cmdline == NULL && ! server_active) { char *this_root = Name_Root ((char *) NULL, update_dir); if (this_root != NULL) { if (findnode_fn (root_directories, this_root) == NULL) { /* Add it to our list. */ Node *n = getnode (); n->type = NT_UNKNOWN; n->key = xstrdup (this_root); if (addnode (root_directories, n)) error (1, 0, "cannot add new CVSROOT %s", this_root); } process_this_directory = (fncmp (current_parsed_root->original, this_root) == 0); xfree (this_root); } } /* * Fill in repository with the current repository */ if (frame->which & W_LOCAL) { if (isdir (CVSADM)) repository = Name_Repository ((char *) NULL, update_dir); else repository = NULL; } else { if(update_repos) /* May have been preloaded by checkout */ repository = xstrdup(update_repos); else { repository = (char*)xmalloc(strlen(current_parsed_root->directory)+strlen(update_dir)+10); sprintf(repository,"%s/%s",current_parsed_root->directory,update_dir); } } if(repository && ISDIRSEP(repository[strlen(repository)-1])) repository[strlen(repository)-1]='\0'; srepository = repository; /* remember what to free */ if(repository && !current_parsed_root->isremote) { mapped_repository = map_repository(repository); } else { mapped_repository=xstrdup(repository); } xfree(last_repository); last_repository = xstrdup(mapped_repository); /* * Do we have access to this directory? */ /* Note that for a recursion this is done already in do_dir_proc... */ if(top_level) { if(repository && !current_parsed_root->isremote) { const char *tag; const char *message; const char *v_msg; ParseTag(&tag, NULL, NULL, NULL); if (! verify_access(frame->permproc, mapped_repository, NULL, update_dir,frame->tag?frame->tag:tag,&message, &v_msg)) { if(tag) error (0, 0, "User '%s' cannot %s %s on tag/branch %s", CVS_Username, v_msg, fn_root(repository), tag); else error (0, 0, "User '%s' cannot %s %s", CVS_Username, v_msg, fn_root(repository)); if(message) error (0, 0, "%s", message); return (1); } xfree(tag); fileattr_startdir (mapped_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; /* 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) repository = Name_Repository ((char *) NULL, update_dir); else if(mapped_repository == NULL) mapped_repository = map_repository(repository); /* find the files and fill in entries if appropriate */ if (process_this_directory) { filelist = Find_Names (mapped_repository, lwhich, frame->aflag, &entries, repository); 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; } } } if (frame->flags == R_SKIP_DIRS && !(frame->which&W_LOCAL) && nonrecursive_module(repository)) frame->flags = R_SKIP_DIRS; /* find sub-directories if we will recurse */ if (frame->flags != R_SKIP_DIRS) dirlist = Find_Directories ( process_this_directory ? mapped_repository : NULL, frame->which, entries, repository); } 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 (should_readlock && mapped_repository && Reader_Lock (mapped_repository) != 0) error (1, 0, "read lock failed - giving up"); /* 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) client_notify_check (repository, update_dir); finfo_struct.repository = mapped_repository; finfo_struct.update_dir = update_dir; finfo_struct.entries = entries; finfo_struct.virtual_repository = repository; /* 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 (should_readlock) Lock_Cleanup_Directory(); /* clean up */ if (filelist) dellist (&filelist); } /* call-back files done proc (if any) */ if (process_this_directory && dodoneproc && frame->filesdoneproc != NULL) { err = frame->filesdoneproc (frame->callerdat, err, (char*)mapped_repository, (char*)(update_dir[0] ? update_dir : "."), entries); } skip_directory: if(repository && !current_parsed_root->isremote) { fileattr_write (); fileattr_free (); } /* process the directories (if necessary) */ if (dirlist != NULL) { // BUGME!!! /* for some reason this code path is not entered some times when it SHOULD be, eg: after a rename! This means the .direcrory_history file is not created !!!! */ struct frame_and_entries frent; frent.frame = frame; frent.entries = entries; err += walklist (dirlist, do_dir_proc, (void *) &frent); } if (dirlist) dellist (&dirlist); if (entries) { Entries_Close (entries); entries = NULL; } /* free the saved copy of the pointer if necessary */ xfree (srepository); xfree (mapped_repository); repository = NULL; return (err); }
static int unroll_files_proc (Node *p, void *closure) { int directory_opened = 0; 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; char *dir = p->key; /* if this dir was also an explicitly named argument, then skip it. We'll catch it later when we do dirs. */ n = findnode_fn (dirlist, dir); if (n != NULL) return (0); /* otherwise, call dorecusion for this list of files. */ filelist = (List *) p->data; p->data = NULL; save_dirlist = dirlist; dirlist = NULL; if (strcmp(dir, ".") != 0) { if (save_cwd (&cwd)) error_exit (); if ( CVS_CHDIR (dir) < 0) error (1, errno, "could not chdir to %s", dir); save_update_dir = update_dir; update_dir = (char*)xmalloc (strlen (save_update_dir) + strlen (dir) + 5); strcpy (update_dir, save_update_dir); if (*update_dir != '\0') (void) strcat (update_dir, "/"); (void) strcat (update_dir, dir); } if(frame->which&W_LOCAL) { int directory_opened_status=-1; const char *version; const char *tag; const char *date; int nonbranch; char *repository = Name_Repository(NULL,update_dir); ParseTag(&tag, &date, &nonbranch, &version); directory_opened_status=open_directory(repository,".",tag,date,nonbranch,version,current_parsed_root->isremote); if (directory_opened_status!=-1) directory_opened = 1; xfree(repository); xfree(version); xfree(tag); xfree(date); } else { int directory_opened_status=-1; /* we can't simply call Name_Repository() here, if we do we get: -> Name_Repository((null),cvsnt/src) cvs server: in directory cvsnt/src: cvs [server aborted]: CVS directory without administration files present. Cannot continue until this directory is deleted or renamed. */ directory_opened_status=open_directory(NULL,".",NULL,NULL,0,NULL,1); /* We want to open the directory anyway, so treat it as 'remote'. This only affects rlog, etc. */ if (directory_opened_status!=-1) directory_opened = 1; } err += do_recursion (frame, 1); if(directory_opened) close_directory(); if (save_update_dir != NULL) { xfree (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); }