diff options
Diffstat (limited to 'contrib/cvs/src/import.c')
-rw-r--r-- | contrib/cvs/src/import.c | 1653 |
1 files changed, 1653 insertions, 0 deletions
diff --git a/contrib/cvs/src/import.c b/contrib/cvs/src/import.c new file mode 100644 index 0000000..bc918e0 --- /dev/null +++ b/contrib/cvs/src/import.c @@ -0,0 +1,1653 @@ +/* + * 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. + * + * "import" checks in the vendor release located in the current directory into + * the CVS source repository. The CVS vendor branch support is utilized. + * + * At least three arguments are expected to follow the options: + * repository Where the source belongs relative to the CVSROOT + * VendorTag Vendor's major tag + * VendorReleTag Tag for this particular release + * + * Additional arguments specify more Vendor Release Tags. + */ + +#include "cvs.h" +#include "savecwd.h" +#include <assert.h> + +static char *get_comment PROTO((const char *user)); +static int add_rev PROTO((char *message, RCSNode *rcs, char *vfile, + char *vers)); +static int add_tags PROTO((RCSNode *rcs, char *vfile, char *vtag, int targc, + char *targv[])); +static int import_descend PROTO((char *message, char *vtag, int targc, char *targv[])); +static int import_descend_dir PROTO((char *message, char *dir, char *vtag, + int targc, char *targv[])); +static int process_import_file PROTO((char *message, char *vfile, char *vtag, + int targc, char *targv[])); +static int update_rcs_file PROTO((char *message, char *vfile, char *vtag, int targc, + char *targv[], int inattic)); +static void add_log PROTO((int ch, char *fname)); + +static int repos_len; +static char *vhead; +static char *vbranch; +static FILE *logfp; +static char *repository; +static int conflicts; +static int use_file_modtime; +static char *keyword_opt = NULL; + +static const char *const import_usage[] = +{ + "Usage: %s %s [-d] [-k subst] [-I ign] [-m msg] [-b branch]\n", + " [-W spec] repository vendor-tag release-tags...\n", + "\t-d\tUse the file's modification time as the time of import.\n", + "\t-k sub\tSet default RCS keyword substitution mode.\n", + "\t-I ign\tMore files to ignore (! to reset).\n", + "\t-b bra\tVendor branch id.\n", + "\t-m msg\tLog message.\n", + "\t-W spec\tWrappers specification line.\n", + "(Specify the --help global option for a list of other help options)\n", + NULL +}; + +int +import (argc, argv) + int argc; + char **argv; +{ + char *message = NULL; + char *tmpfile; + char *cp; + int i, c, msglen, err; + List *ulist; + Node *p; + struct logfile_info *li; + + if (argc == -1) + usage (import_usage); + + ign_setup (); + wrap_setup (); + + vbranch = xstrdup (CVSBRANCH); + optind = 0; + while ((c = getopt (argc, argv, "+Qqdb:m:I:k:W:")) != -1) + { + switch (c) + { + case 'Q': + case 'q': + /* The CVS 1.5 client sends these options (in addition to + Global_option requests), so we must ignore them. */ + if (!server_active) + error (1, 0, + "-q or -Q must be specified before \"%s\"", + cvs_cmd_name); + break; + case 'd': + if (server_active) + { + /* CVS 1.10 and older clients will send this, but it + doesn't do any good. So tell the user we can't + cope, rather than silently losing. */ + error (0, 0, + "warning: not setting the time of import from the file"); + error (0, 0, "due to client limitations"); + } + use_file_modtime = 1; + break; + case 'b': + free (vbranch); + vbranch = xstrdup (optarg); + break; + case 'm': +#ifdef FORCE_USE_EDITOR + use_editor = 1; +#else + use_editor = 0; +#endif + if (message) free (message); + message = xstrdup(optarg); + break; + case 'I': + ign_add (optarg, 0); + break; + case 'k': + /* RCS_check_kflag returns strings of the form -kxx. We + only use it for validation, so we can free the value + as soon as it is returned. */ + free (RCS_check_kflag (optarg)); + keyword_opt = optarg; + break; + case 'W': + wrap_add (optarg, 0); + break; + case '?': + default: + usage (import_usage); + break; + } + } + argc -= optind; + argv += optind; + if (argc < 3) + usage (import_usage); + + /* This is for handling the Checkin-time request. It might seem a + bit odd to enable the use_file_modtime code even in the case + where Checkin-time was not sent for a particular file. The + effect is that we use the time of upload, rather than the time + when we call RCS_checkin. Since those times are both during + CVS's run, that seems OK, and it is easier to implement than + putting the "was Checkin-time sent" flag in CVS/Entries or some + such place. */ + + if (server_active) + use_file_modtime = 1; + + /* Don't allow "CVS" as any directory in module path. + * + * Could abstract this to valid_module_path, but I don't think we'll need + * to call it from anywhere else. + */ + /* for each "CVS" in path... */ + cp = argv[0]; + while ((cp = strstr(cp, "CVS")) != NULL) + { + if ( /* /^CVS/ OR m#/CVS#... */ + (cp == argv[0] || ISDIRSEP(*(cp-1))) + /* ...AND /CVS$/ OR m#CVS/# */ + && (*(cp+3) == '\0' || ISDIRSEP(*(cp+3))) + ) + { + error (0, 0, + "The word `CVS' is reserved by CVS and may not be used"); + error (1, 0, "as a directory in a path or as a file name."); + } + cp += 3; + } + + for (i = 1; i < argc; i++) /* check the tags for validity */ + { + int j; + + RCS_check_tag (argv[i]); + for (j = 1; j < i; j++) + if (strcmp (argv[j], argv[i]) == 0) + error (1, 0, "tag `%s' was specified more than once", argv[i]); + } + + /* XXX - this should be a module, not just a pathname */ + if (!isabsolute (argv[0]) && pathname_levels (argv[0]) == 0) + { + if (current_parsed_root == NULL) + { + error (0, 0, "missing CVSROOT environment variable\n"); + error (1, 0, "Set it or specify the '-d' option to %s.", + program_name); + } + repository = xmalloc (strlen (current_parsed_root->directory) + + strlen (argv[0]) + + 2); + (void) sprintf (repository, "%s/%s", current_parsed_root->directory, argv[0]); + repos_len = strlen (current_parsed_root->directory); + } + else + { + /* It is somewhere between a security hole and "unexpected" to + let the client start mucking around outside the cvsroot + (wouldn't get the right CVSROOT configuration, &c). */ + error (1, 0, "directory %s not relative within the repository", + argv[0]); + } + + /* + * Consistency checks on the specified vendor branch. It must be + * composed of only numbers and dots ('.'). Also, for now we only + * support branching to a single level, so the specified vendor branch + * must only have two dots in it (like "1.1.1"). + */ + { + regex_t pat; + int ret = regcomp (&pat, "^[1-9][0-9]*\\.[1-9][0-9]*\\.[1-9][0-9]*$", + REG_EXTENDED); + assert (!ret); + if (regexec (&pat, vbranch, 0, NULL, 0)) + { + error (1, 0, +"Only numeric branch specifications with two dots are\n" +"supported by import, not `%s'. For example: `1.1.1'.", + vbranch); + } + regfree (&pat); + } + + /* Set vhead to the branch's parent. */ + vhead = xstrdup (vbranch); + cp = strrchr (vhead, '.'); + *cp = '\0'; + +#ifdef CLIENT_SUPPORT + if (current_parsed_root->isremote) + { + /* For rationale behind calling start_server before do_editor, see + commit.c */ + start_server (); + } +#endif + + if (!server_active && use_editor) + { + do_editor ((char *) NULL, &message, + current_parsed_root->isremote ? (char *) NULL : repository, + (List *) NULL); + } + do_verify (&message, repository); + msglen = message == NULL ? 0 : strlen (message); + if (msglen == 0 || message[msglen - 1] != '\n') + { + char *nm = xmalloc (msglen + 2); + *nm = '\0'; + if (message != NULL) + { + (void) strcpy (nm, message); + free (message); + } + (void) strcat (nm + msglen, "\n"); + message = nm; + } + +#ifdef CLIENT_SUPPORT + if (current_parsed_root->isremote) + { + int err; + + if (vbranch[0] != '\0') + option_with_arg ("-b", vbranch); + option_with_arg ("-m", message ? message : ""); + if (keyword_opt != NULL) + option_with_arg ("-k", keyword_opt); + /* The only ignore processing which takes place on the server side + is the CVSROOT/cvsignore file. But if the user specified -I !, + the documented behavior is to not process said file. */ + if (ign_inhibit_server) + { + send_arg ("-I"); + send_arg ("!"); + } + wrap_send (); + + { + int i; + for (i = 0; i < argc; ++i) + send_arg (argv[i]); + } + + logfp = stdin; + client_import_setup (repository); + err = import_descend (message, argv[1], argc - 2, argv + 2); + client_import_done (); + if (message) + free (message); + free (repository); + free (vbranch); + free (vhead); + send_to_server ("import\012", 0); + err += get_responses_and_close (); + return err; + } +#endif + + if (!safe_location ( NULL )) + { + error (1, 0, "attempt to import the repository"); + } + + /* + * Make all newly created directories writable. Should really use a more + * sophisticated security mechanism here. + */ + (void) umask (cvsumask); + make_directories (repository); + + /* Create the logfile that will be logged upon completion */ + if ((logfp = cvs_temp_file (&tmpfile)) == NULL) + error (1, errno, "cannot create temporary file `%s'", + tmpfile ? tmpfile : "(null)"); + /* On systems where we can unlink an open file, do so, so it will go + away no matter how we exit. FIXME-maybe: Should be checking for + errors but I'm not sure which error(s) we get if we are on a system + where one can't unlink open files. */ + (void) CVS_UNLINK (tmpfile); + (void) fprintf (logfp, "\nVendor Tag:\t%s\n", argv[1]); + (void) fprintf (logfp, "Release Tags:\t"); + for (i = 2; i < argc; i++) + (void) fprintf (logfp, "%s\n\t\t", argv[i]); + (void) fprintf (logfp, "\n"); + + /* Just Do It. */ + err = import_descend (message, argv[1], argc - 2, argv + 2); + if (conflicts) + { + if (!really_quiet) + { + char buf[20]; + + cvs_output_tagged ("+importmergecmd", NULL); + cvs_output_tagged ("newline", NULL); + sprintf (buf, "%d", conflicts); + cvs_output_tagged ("conflicts", buf); + cvs_output_tagged ("text", " conflicts created by this import."); + cvs_output_tagged ("newline", NULL); + cvs_output_tagged ("text", + "Use the following command to help the merge:"); + cvs_output_tagged ("newline", NULL); + cvs_output_tagged ("newline", NULL); + cvs_output_tagged ("text", "\t"); + cvs_output_tagged ("text", program_name); + if (CVSroot_cmdline != NULL) + { + cvs_output_tagged ("text", " -d "); + cvs_output_tagged ("text", CVSroot_cmdline); + } + cvs_output_tagged ("text", " checkout -j"); + cvs_output_tagged ("mergetag1", "<prev_rel_tag>"); + cvs_output_tagged ("text", " -j"); + cvs_output_tagged ("mergetag2", argv[2]); + cvs_output_tagged ("text", " "); + cvs_output_tagged ("repository", argv[0]); + cvs_output_tagged ("newline", NULL); + cvs_output_tagged ("newline", NULL); + cvs_output_tagged ("-importmergecmd", NULL); + } + + /* FIXME: I'm not sure whether we need to put this information + into the loginfo. If we do, then note that it does not + report any required -d option. There is no particularly + clean way to tell the server about the -d option used by + the client. */ + (void) fprintf (logfp, "\n%d conflicts created by this import.\n", + conflicts); + (void) fprintf (logfp, + "Use the following command to help the merge:\n\n"); + (void) fprintf (logfp, "\t%s checkout ", program_name); + (void) fprintf (logfp, "-j%s:yesterday -j%s %s\n\n", + argv[1], argv[1], argv[0]); + } + else + { + if (!really_quiet) + cvs_output ("\nNo conflicts created by this import\n\n", 0); + (void) fprintf (logfp, "\nNo conflicts created by this import\n\n"); + } + + /* + * Write out the logfile and clean up. + */ + ulist = getlist (); + p = getnode (); + p->type = UPDATE; + p->delproc = update_delproc; + p->key = xstrdup ("- Imported sources"); + li = (struct logfile_info *) xmalloc (sizeof (struct logfile_info)); + li->type = T_TITLE; + li->tag = xstrdup (vbranch); + li->rev_old = li->rev_new = NULL; + p->data = li; + (void) addnode (ulist, p); + Update_Logfile (repository, message, logfp, ulist); + dellist (&ulist); + if (fclose (logfp) < 0) + error (0, errno, "error closing %s", tmpfile); + + /* Make sure the temporary file goes away, even on systems that don't let + you delete a file that's in use. */ + if (CVS_UNLINK (tmpfile) < 0 && !existence_error (errno)) + error (0, errno, "cannot remove %s", tmpfile); + free (tmpfile); + + if (message) + free (message); + free (repository); + free (vbranch); + free (vhead); + + return (err); +} + +/* Process all the files in ".", then descend into other directories. + Returns 0 for success, or >0 on error (in which case a message + will have been printed). */ +static int +import_descend (message, vtag, targc, targv) + char *message; + char *vtag; + int targc; + char *targv[]; +{ + DIR *dirp; + struct dirent *dp; + int err = 0; + List *dirlist = NULL; + + /* first, load up any per-directory ignore lists */ + ign_add_file (CVSDOTIGNORE, 1); + wrap_add_file (CVSDOTWRAPPER, 1); + + if (!current_parsed_root->isremote) + lock_dir_for_write (repository); + + if ((dirp = CVS_OPENDIR (".")) == NULL) + { + error (0, errno, "cannot open directory"); + err++; + } + else + { + errno = 0; + while ((dp = CVS_READDIR (dirp)) != NULL) + { + if (strcmp (dp->d_name, ".") == 0 || strcmp (dp->d_name, "..") == 0) + goto one_more_time_boys; + + /* CVS directories are created in the temp directory by + server.c because it doesn't special-case import. So + don't print a message about them, regardless of -I!. */ + if (server_active && strcmp (dp->d_name, CVSADM) == 0) + goto one_more_time_boys; + + if (ign_name (dp->d_name)) + { + add_log ('I', dp->d_name); + goto one_more_time_boys; + } + + if ( +#ifdef DT_DIR + (dp->d_type == DT_DIR + || (dp->d_type == DT_UNKNOWN && isdir (dp->d_name))) +#else + isdir (dp->d_name) +#endif + && !wrap_name_has (dp->d_name, WRAP_TOCVS) + ) + { + Node *n; + + if (dirlist == NULL) + dirlist = getlist(); + + n = getnode(); + n->key = xstrdup (dp->d_name); + addnode(dirlist, n); + } + else if ( +#ifdef DT_DIR + dp->d_type == DT_LNK + || (dp->d_type == DT_UNKNOWN && islink (dp->d_name)) +#else + islink (dp->d_name) +#endif + ) + { + add_log ('L', dp->d_name); + err++; + } + else + { +#ifdef CLIENT_SUPPORT + if (current_parsed_root->isremote) + err += client_process_import_file (message, dp->d_name, + vtag, targc, targv, + repository, + keyword_opt != NULL && + keyword_opt[0] == 'b', + use_file_modtime); + else +#endif + err += process_import_file (message, dp->d_name, + vtag, targc, targv); + } + one_more_time_boys: + errno = 0; + } + if (errno != 0) + { + error (0, errno, "cannot read directory"); + ++err; + } + (void) CVS_CLOSEDIR (dirp); + } + + if (!current_parsed_root->isremote) + Lock_Cleanup (); + + if (dirlist != NULL) + { + Node *head, *p; + + head = dirlist->list; + for (p = head->next; p != head; p = p->next) + { + err += import_descend_dir (message, p->key, vtag, targc, targv); + } + + dellist(&dirlist); + } + + return (err); +} + +/* + * Process the argument import file. + */ +static int +process_import_file (message, vfile, vtag, targc, targv) + char *message; + char *vfile; + char *vtag; + int targc; + char *targv[]; +{ + char *rcs; + int inattic = 0; + + rcs = xmalloc (strlen (repository) + strlen (vfile) + sizeof (RCSEXT) + + 5); + (void) sprintf (rcs, "%s/%s%s", repository, vfile, RCSEXT); + if (!isfile (rcs)) + { + char *attic_name; + + attic_name = xmalloc (strlen (repository) + strlen (vfile) + + sizeof (CVSATTIC) + sizeof (RCSEXT) + 10); + (void) sprintf (attic_name, "%s/%s/%s%s", repository, CVSATTIC, + vfile, RCSEXT); + if (!isfile (attic_name)) + { + int retval; + char *free_opt = NULL; + char *our_opt = keyword_opt; + + free (attic_name); + /* + * A new import source file; it doesn't exist as a ,v within the + * repository nor in the Attic -- create it anew. + */ + add_log ('N', vfile); + +#ifdef SERVER_SUPPORT + /* The most reliable information on whether the file is binary + is what the client told us. That is because if the client had + the wrong idea about binaryness, it corrupted the file, so + we might as well believe the client. */ + if (server_active) + { + Node *node; + List *entries; + + /* Reading all the entries for each file is fairly silly, and + probably slow. But I am too lazy at the moment to do + anything else. */ + entries = Entries_Open (0, NULL); + node = findnode_fn (entries, vfile); + if (node != NULL) + { + Entnode *entdata = node->data; + + if (entdata->type == ENT_FILE) + { + assert (entdata->options[0] == '-' + && entdata->options[1] == 'k'); + our_opt = xstrdup (entdata->options + 2); + free_opt = our_opt; + } + } + Entries_Close (entries); + } +#endif + + retval = add_rcs_file (message, rcs, vfile, vhead, our_opt, + vbranch, vtag, targc, targv, + NULL, 0, logfp); + if (free_opt != NULL) + free (free_opt); + free (rcs); + return retval; + } + free (attic_name); + inattic = 1; + } + + free (rcs); + /* + * an rcs file exists. have to do things the official, slow, way. + */ + return (update_rcs_file (message, vfile, vtag, targc, targv, inattic)); +} + +/* + * The RCS file exists; update it by adding the new import file to the + * (possibly already existing) vendor branch. + */ +static int +update_rcs_file (message, vfile, vtag, targc, targv, inattic) + char *message; + char *vfile; + char *vtag; + int targc; + char *targv[]; + int inattic; +{ + Vers_TS *vers; + int letter; + char *tocvsPath; + char *expand; + struct file_info finfo; + + memset (&finfo, 0, sizeof finfo); + finfo.file = vfile; + /* Not used, so don't worry about it. */ + finfo.update_dir = NULL; + finfo.fullname = finfo.file; + finfo.repository = repository; + finfo.entries = NULL; + finfo.rcs = NULL; + vers = Version_TS (&finfo, (char *) NULL, vbranch, (char *) NULL, + 1, 0); + if (vers->vn_rcs != NULL + && !RCS_isdead(vers->srcfile, vers->vn_rcs)) + { + int different; + + /* + * The rcs file does have a revision on the vendor branch. Compare + * this revision with the import file; if they match exactly, there + * is no need to install the new import file as a new revision to the + * branch. Just tag the revision with the new import tags. + * + * This is to try to cut down the number of "C" conflict messages for + * locally modified import source files. + */ + tocvsPath = wrap_tocvs_process_file (vfile); + /* FIXME: Why don't we pass tocvsPath to RCS_cmp_file if it is + not NULL? */ + expand = vers->srcfile->expand != NULL && + vers->srcfile->expand[0] == 'b' ? "-kb" : "-ko"; + different = RCS_cmp_file( vers->srcfile, vers->vn_rcs, (char **)NULL, + (char *)NULL, expand, vfile ); + if (tocvsPath) + if (unlink_file_dir (tocvsPath) < 0) + error (0, errno, "cannot remove %s", tocvsPath); + + if (!different) + { + int retval = 0; + + /* + * The two files are identical. Just update the tags, print the + * "U", signifying that the file has changed, but needs no + * attention, and we're done. + */ + if (add_tags (vers->srcfile, vfile, vtag, targc, targv)) + retval = 1; + add_log ('U', vfile); + freevers_ts (&vers); + return (retval); + } + } + + /* We may have failed to parse the RCS file; check just in case */ + if (vers->srcfile == NULL || + add_rev (message, vers->srcfile, vfile, vers->vn_rcs) || + add_tags (vers->srcfile, vfile, vtag, targc, targv)) + { + freevers_ts (&vers); + return (1); + } + + if (vers->srcfile->branch == NULL || inattic || + strcmp (vers->srcfile->branch, vbranch) != 0) + { + conflicts++; + letter = 'C'; + } + else + letter = 'U'; + add_log (letter, vfile); + + freevers_ts (&vers); + return (0); +} + +/* + * Add the revision to the vendor branch + */ +static int +add_rev (message, rcs, vfile, vers) + char *message; + RCSNode *rcs; + char *vfile; + char *vers; +{ + int locked, status, ierrno; + char *tocvsPath; + + if (noexec) + return (0); + + locked = 0; + if (vers != NULL) + { + /* Before RCS_lock existed, we were directing stdout, as well as + stderr, from the RCS command, to DEVNULL. I wouldn't guess that + was necessary, but I don't know for sure. */ + /* Earlier versions of this function printed a `fork failed' error + when RCS_lock returned an error code. That's not appropriate + now that RCS_lock is librarified, but should the error text be + preserved? */ + if (RCS_lock (rcs, vbranch, 1) != 0) + return 1; + locked = 1; + RCS_rewrite (rcs, NULL, NULL); + } + tocvsPath = wrap_tocvs_process_file (vfile); + + status = RCS_checkin (rcs, tocvsPath == NULL ? vfile : tocvsPath, + message, vbranch, 0, + (RCS_FLAGS_QUIET | RCS_FLAGS_KEEPFILE + | (use_file_modtime ? RCS_FLAGS_MODTIME : 0))); + ierrno = errno; + + if ((tocvsPath != NULL) && (unlink_file_dir (tocvsPath) < 0)) + error (0, errno, "cannot remove %s", tocvsPath); + + if (status) + { + if (!noexec) + { + fperrmsg (logfp, 0, status == -1 ? ierrno : 0, + "ERROR: Check-in of %s failed", rcs->path); + error (0, status == -1 ? ierrno : 0, + "ERROR: Check-in of %s failed", rcs->path); + } + if (locked) + { + (void) RCS_unlock(rcs, vbranch, 0); + RCS_rewrite (rcs, NULL, NULL); + } + return (1); + } + return (0); +} + +/* + * Add the vendor branch tag and all the specified import release tags to the + * RCS file. The vendor branch tag goes on the branch root (1.1.1) while the + * vendor release tags go on the newly added leaf of the branch (1.1.1.1, + * 1.1.1.2, ...). + */ +static int +add_tags (rcs, vfile, vtag, targc, targv) + RCSNode *rcs; + char *vfile; + char *vtag; + int targc; + char *targv[]; +{ + int i, ierrno; + Vers_TS *vers; + int retcode = 0; + struct file_info finfo; + + if (noexec) + return (0); + + if ((retcode = RCS_settag(rcs, vtag, vbranch)) != 0) + { + ierrno = errno; + fperrmsg (logfp, 0, retcode == -1 ? ierrno : 0, + "ERROR: Failed to set tag %s in %s", vtag, rcs->path); + error (0, retcode == -1 ? ierrno : 0, + "ERROR: Failed to set tag %s in %s", vtag, rcs->path); + return (1); + } + RCS_rewrite (rcs, NULL, NULL); + + memset (&finfo, 0, sizeof finfo); + finfo.file = vfile; + /* Not used, so don't worry about it. */ + finfo.update_dir = NULL; + finfo.fullname = finfo.file; + finfo.repository = repository; + finfo.entries = NULL; + finfo.rcs = NULL; + vers = Version_TS (&finfo, NULL, vtag, NULL, 1, 0); + for (i = 0; i < targc; i++) + { + if ((retcode = RCS_settag (rcs, targv[i], vers->vn_rcs)) == 0) + RCS_rewrite (rcs, NULL, NULL); + else + { + ierrno = errno; + fperrmsg (logfp, 0, retcode == -1 ? ierrno : 0, + "WARNING: Couldn't add tag %s to %s", targv[i], + rcs->path); + error (0, retcode == -1 ? ierrno : 0, + "WARNING: Couldn't add tag %s to %s", targv[i], + rcs->path); + } + } + freevers_ts (&vers); + return (0); +} + +/* + * Stolen from rcs/src/rcsfnms.c, and adapted/extended. + */ +struct compair +{ + char *suffix, *comlead; +}; + +static const struct compair comtable[] = +{ + +/* + * comtable pairs each filename suffix with a comment leader. The comment + * leader is placed before each line generated by the $Log keyword. This + * table is used to guess the proper comment leader from the working file's + * suffix during initial ci (see InitAdmin()). Comment leaders are needed for + * languages without multiline comments; for others they are optional. + * + * I believe that the comment leader is unused if you are using RCS 5.7, which + * decides what leader to use based on the text surrounding the $Log keyword + * rather than a specified comment leader. + */ + {"a", "-- "}, /* Ada */ + {"ada", "-- "}, + {"adb", "-- "}, + {"asm", ";; "}, /* assembler (MS-DOS) */ + {"ads", "-- "}, /* Ada */ + {"bas", "' "}, /* Visual Basic code */ + {"bat", ":: "}, /* batch (MS-DOS) */ + {"body", "-- "}, /* Ada */ + {"c", " * "}, /* C */ + {"c++", "// "}, /* C++ in all its infinite guises */ + {"cc", "// "}, + {"cpp", "// "}, + {"cxx", "// "}, + {"m", "// "}, /* Objective-C */ + {"cl", ";;; "}, /* Common Lisp */ + {"cmd", ":: "}, /* command (OS/2) */ + {"cmf", "c "}, /* CM Fortran */ + {"cs", " * "}, /* C* */ + {"csh", "# "}, /* shell */ + {"dlg", " * "}, /* MS Windows dialog file */ + {"e", "# "}, /* efl */ + {"epsf", "% "}, /* encapsulated postscript */ + {"epsi", "% "}, /* encapsulated postscript */ + {"el", "; "}, /* Emacs Lisp */ + {"f", "c "}, /* Fortran */ + {"for", "c "}, + {"frm", "' "}, /* Visual Basic form */ + {"h", " * "}, /* C-header */ + {"hh", "// "}, /* C++ header */ + {"hpp", "// "}, + {"hxx", "// "}, + {"in", "# "}, /* for Makefile.in */ + {"l", " * "}, /* lex (conflict between lex and + * franzlisp) */ + {"mac", ";; "}, /* macro (DEC-10, MS-DOS, PDP-11, + * VMS, etc) */ + {"mak", "# "}, /* makefile, e.g. Visual C++ */ + {"me", ".\\\" "}, /* me-macros t/nroff */ + {"ml", "; "}, /* mocklisp */ + {"mm", ".\\\" "}, /* mm-macros t/nroff */ + {"ms", ".\\\" "}, /* ms-macros t/nroff */ + {"man", ".\\\" "}, /* man-macros t/nroff */ + {"1", ".\\\" "}, /* feeble attempt at man pages... */ + {"2", ".\\\" "}, + {"3", ".\\\" "}, + {"4", ".\\\" "}, + {"5", ".\\\" "}, + {"6", ".\\\" "}, + {"7", ".\\\" "}, + {"8", ".\\\" "}, + {"9", ".\\\" "}, + {"p", " * "}, /* pascal */ + {"pas", " * "}, + {"pl", "# "}, /* perl (conflict with Prolog) */ + {"ps", "% "}, /* postscript */ + {"psw", "% "}, /* postscript wrap */ + {"pswm", "% "}, /* postscript wrap */ + {"r", "# "}, /* ratfor */ + {"rc", " * "}, /* Microsoft Windows resource file */ + {"red", "% "}, /* psl/rlisp */ +#ifdef sparc + {"s", "! "}, /* assembler */ +#endif +#ifdef mc68000 + {"s", "| "}, /* assembler */ +#endif +#ifdef pdp11 + {"s", "/ "}, /* assembler */ +#endif +#ifdef vax + {"s", "# "}, /* assembler */ +#endif +#ifdef __ksr__ + {"s", "# "}, /* assembler */ + {"S", "# "}, /* Macro assembler */ +#endif + {"sh", "# "}, /* shell */ + {"sl", "% "}, /* psl */ + {"spec", "-- "}, /* Ada */ + {"tex", "% "}, /* tex */ + {"y", " * "}, /* yacc */ + {"ye", " * "}, /* yacc-efl */ + {"yr", " * "}, /* yacc-ratfor */ + {"", "# "}, /* default for empty suffix */ + {NULL, "# "} /* default for unknown suffix; */ +/* must always be last */ +}; + +static char * +get_comment (user) + const char *user; +{ + char *cp, *suffix; + char *suffix_path; + int i; + char *retval; + + suffix_path = xmalloc (strlen (user) + 5); + cp = strrchr (user, '.'); + if (cp != NULL) + { + cp++; + + /* + * Convert to lower-case, since we are not concerned about the + * case-ness of the suffix. + */ + (void) strcpy (suffix_path, cp); + for (cp = suffix_path; *cp; cp++) + if (isupper ((unsigned char) *cp)) + *cp = tolower (*cp); + suffix = suffix_path; + } + else + suffix = ""; /* will use the default */ + for (i = 0;; i++) + { + if (comtable[i].suffix == NULL) + { + /* Default. Note we'll always hit this case before we + ever return NULL. */ + retval = comtable[i].comlead; + break; + } + if (strcmp (suffix, comtable[i].suffix) == 0) + { + retval = comtable[i].comlead; + break; + } + } + free (suffix_path); + return retval; +} + +/* Create a new RCS file from scratch. + + This probably should be moved to rcs.c now that it is called from + places outside import.c. + + Return value is 0 for success, or nonzero for failure (in which + case an error message will have already been printed). */ +int +add_rcs_file (message, rcs, user, add_vhead, key_opt, + add_vbranch, vtag, targc, targv, + desctext, desclen, add_logfp) + /* Log message for the addition. Not used if add_vhead == NULL. */ + const char *message; + /* Filename of the RCS file to create. */ + const char *rcs; + /* Filename of the file to serve as the contents of the initial + revision. Even if add_vhead is NULL, we use this to determine + the modes to give the new RCS file. */ + const char *user; + + /* Revision number of head that we are adding. Normally 1.1 but + could be another revision as long as ADD_VBRANCH is a branch + from it. If NULL, then just add an empty file without any + revisions (similar to the one created by "rcs -i"). */ + const char *add_vhead; + + /* Keyword expansion mode, e.g., "b" for binary. NULL means the + default behavior. */ + const char *key_opt; + + /* Vendor branch to import to, or NULL if none. If non-NULL, then + vtag should also be non-NULL. */ + const char *add_vbranch; + const char *vtag; + int targc; + char *targv[]; + + /* If non-NULL, description for the file. If NULL, the description + will be empty. */ + const char *desctext; + size_t desclen; + + /* Write errors to here as well as via error (), or NULL if we should + use only error (). */ + FILE *add_logfp; +{ + FILE *fprcs, *fpuser; + struct stat sb; + struct tm *ftm; + time_t now; + char altdate1[MAXDATELEN]; + char *author; + int i, ierrno, err = 0; + mode_t mode; + char *tocvsPath; + const char *userfile; + char *free_opt = NULL; + mode_t file_type; + + if (noexec) + return (0); + + /* Note that as the code stands now, the -k option overrides any + settings in wrappers (whether CVSROOT/cvswrappers, -W, or + whatever). Some have suggested this should be the other way + around. As far as I know the documentation doesn't say one way + or the other. Before making a change of this sort, should think + about what is best, document it (in cvs.texinfo and NEWS), &c. */ + + if (key_opt == NULL) + { + if (wrap_name_has (user, WRAP_RCSOPTION)) + { + key_opt = free_opt = wrap_rcsoption (user, 0); + } + } + + tocvsPath = wrap_tocvs_process_file (user); + userfile = (tocvsPath == NULL ? user : tocvsPath); + + /* Opening in text mode is probably never the right thing for the + server (because the protocol encodes text files in a fashion + which does not depend on what the client or server OS is, as + documented in cvsclient.texi), but as long as the server just + runs on unix it is a moot point. */ + + /* If PreservePermissions is set, then make sure that the file + is a plain file before trying to open it. Longstanding (although + often unpopular) CVS behavior has been to follow symlinks, so we + maintain that behavior if PreservePermissions is not on. + + NOTE: this error message used to be `cannot fstat', but is now + `cannot lstat'. I don't see a way around this, since we must + stat the file before opening it. -twp */ + + if (CVS_LSTAT (userfile, &sb) < 0) + { + /* not fatal, continue import */ + if (add_logfp != NULL) + fperrmsg (add_logfp, 0, errno, + "ERROR: cannot lstat file %s", userfile); + error (0, errno, "cannot lstat file %s", userfile); + goto read_error; + } + file_type = sb.st_mode & S_IFMT; + + fpuser = NULL; + if (!preserve_perms || file_type == S_IFREG) + { + fpuser = CVS_FOPEN (userfile, + ((key_opt != NULL && strcmp (key_opt, "b") == 0) + ? "rb" + : "r") + ); + if (fpuser == NULL) + { + /* not fatal, continue import */ + if (add_logfp != NULL) + fperrmsg (add_logfp, 0, errno, + "ERROR: cannot read file %s", userfile); + error (0, errno, "ERROR: cannot read file %s", userfile); + goto read_error; + } + } + + fprcs = CVS_FOPEN (rcs, "w+b"); + if (fprcs == NULL) + { + ierrno = errno; + goto write_error_noclose; + } + + /* + * putadmin() + */ + if (add_vhead != NULL) + { + if (fprintf (fprcs, "head %s;\012", add_vhead) < 0) + goto write_error; + } + else + { + if (fprintf (fprcs, "head ;\012") < 0) + goto write_error; + } + + if (add_vbranch != NULL) + { + if (fprintf (fprcs, "branch %s;\012", add_vbranch) < 0) + goto write_error; + } + if (fprintf (fprcs, "access ;\012") < 0 || + fprintf (fprcs, "symbols ") < 0) + { + goto write_error; + } + + for (i = targc - 1; i >= 0; i--) + { + /* RCS writes the symbols backwards */ + assert (add_vbranch != NULL); + if (fprintf (fprcs, "%s:%s.1 ", targv[i], add_vbranch) < 0) + goto write_error; + } + + if (add_vbranch != NULL) + { + if (fprintf (fprcs, "%s:%s", vtag, add_vbranch) < 0) + goto write_error; + } + if (fprintf (fprcs, ";\012") < 0) + goto write_error; + + if (fprintf (fprcs, "locks ; strict;\012") < 0 || + /* XXX - make sure @@ processing works in the RCS file */ + fprintf (fprcs, "comment @%s@;\012", get_comment (user)) < 0) + { + goto write_error; + } + + if (key_opt != NULL && strcmp (key_opt, "kv") != 0) + { + if (fprintf (fprcs, "expand @%s@;\012", key_opt) < 0) + { + goto write_error; + } + } + + if (fprintf (fprcs, "\012") < 0) + goto write_error; + + /* Write the revision(s), with the date and author and so on + (that is "delta" rather than "deltatext" from rcsfile(5)). */ + if (add_vhead != NULL) + { + if (use_file_modtime) + now = sb.st_mtime; + else + (void) time (&now); + ftm = gmtime (&now); + (void) sprintf (altdate1, DATEFORM, + ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), + ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, + ftm->tm_min, ftm->tm_sec); + author = getcaller (); + + if (fprintf (fprcs, "\012%s\012", add_vhead) < 0 || + fprintf (fprcs, "date %s; author %s; state Exp;\012", + altdate1, author) < 0) + goto write_error; + + if (fprintf (fprcs, "branches") < 0) + goto write_error; + if (add_vbranch != NULL) + { + if (fprintf (fprcs, " %s.1", add_vbranch) < 0) + goto write_error; + } + if (fprintf (fprcs, ";\012") < 0) + goto write_error; + + if (fprintf (fprcs, "next ;\012") < 0) + goto write_error; + +#ifdef PRESERVE_PERMISSIONS_SUPPORT + /* Store initial permissions if necessary. */ + if (preserve_perms) + { + if (file_type == S_IFLNK) + { + char *link = xreadlink (userfile); + if (fprintf (fprcs, "symlink\t@") < 0 || + expand_at_signs (link, strlen (link), fprcs) < 0 || + fprintf (fprcs, "@;\012") < 0) + goto write_error; + free (link); + } + else + { + if (fprintf (fprcs, "owner\t%u;\012", sb.st_uid) < 0) + goto write_error; + if (fprintf (fprcs, "group\t%u;\012", sb.st_gid) < 0) + goto write_error; + if (fprintf (fprcs, "permissions\t%o;\012", + sb.st_mode & 07777) < 0) + goto write_error; + switch (file_type) + { + case S_IFREG: break; + case S_IFCHR: + case S_IFBLK: +#ifdef HAVE_STRUCT_STAT_ST_RDEV + if (fprintf (fprcs, "special\t%s %lu;\012", + (file_type == S_IFCHR + ? "character" + : "block"), + (unsigned long) sb.st_rdev) < 0) + goto write_error; +#else + error (0, 0, +"can't import %s: unable to import device files on this system", +userfile); +#endif + break; + default: + error (0, 0, + "can't import %s: unknown kind of special file", + userfile); + } + } + } +#endif + + if (add_vbranch != NULL) + { + if (fprintf (fprcs, "\012%s.1\012", add_vbranch) < 0 || + fprintf (fprcs, "date %s; author %s; state Exp;\012", + altdate1, author) < 0 || + fprintf (fprcs, "branches ;\012") < 0 || + fprintf (fprcs, "next ;\012") < 0) + goto write_error; + +#ifdef PRESERVE_PERMISSIONS_SUPPORT + /* Store initial permissions if necessary. */ + if (preserve_perms) + { + if (file_type == S_IFLNK) + { + char *link = xreadlink (userfile); + if (fprintf (fprcs, "symlink\t@") < 0 || + expand_at_signs (link, strlen (link), fprcs) < 0 || + fprintf (fprcs, "@;\012") < 0) + goto write_error; + free (link); + } + else + { + if (fprintf (fprcs, "owner\t%u;\012", sb.st_uid) < 0 || + fprintf (fprcs, "group\t%u;\012", sb.st_gid) < 0 || + fprintf (fprcs, "permissions\t%o;\012", + sb.st_mode & 07777) < 0) + goto write_error; + + switch (file_type) + { + case S_IFREG: break; + case S_IFCHR: + case S_IFBLK: +#ifdef HAVE_STRUCT_STAT_ST_RDEV + if (fprintf (fprcs, "special\t%s %lu;\012", + (file_type == S_IFCHR + ? "character" + : "block"), + (unsigned long) sb.st_rdev) < 0) + goto write_error; +#else + error (0, 0, +"can't import %s: unable to import device files on this system", +userfile); +#endif + break; + default: + error (0, 0, + "cannot import %s: special file of unknown type", + userfile); + } + } + } +#endif + + if (fprintf (fprcs, "\012") < 0) + goto write_error; + } + } + + /* Now write the description (possibly empty). */ + if (fprintf (fprcs, "\012desc\012") < 0 || + fprintf (fprcs, "@") < 0) + goto write_error; + if (desctext != NULL) + { + /* The use of off_t not size_t for the second argument is very + strange, since we are dealing with something which definitely + fits in memory. */ + if (expand_at_signs (desctext, (off_t) desclen, fprcs) < 0) + goto write_error; + } + if (fprintf (fprcs, "@\012\012\012") < 0) + goto write_error; + + /* Now write the log messages and contents for the revision(s) (that + is, "deltatext" rather than "delta" from rcsfile(5)). */ + if (add_vhead != NULL) + { + if (fprintf (fprcs, "\012%s\012", add_vhead) < 0 || + fprintf (fprcs, "log\012@") < 0) + goto write_error; + if (add_vbranch != NULL) + { + /* We are going to put the log message in the revision on the + branch. So putting it here too seems kind of redundant, I + guess (and that is what CVS has always done, anyway). */ + if (fprintf (fprcs, "Initial revision\012") < 0) + goto write_error; + } + else + { + if (expand_at_signs (message, (off_t) strlen (message), fprcs) < 0) + goto write_error; + } + if (fprintf (fprcs, "@\012") < 0 || + fprintf (fprcs, "text\012@") < 0) + { + goto write_error; + } + + /* Now copy over the contents of the file, expanding at signs. + If preserve_perms is set, do this only for regular files. */ + if (!preserve_perms || file_type == S_IFREG) + { + char buf[8192]; + unsigned int len; + + while (1) + { + len = fread (buf, 1, sizeof buf, fpuser); + if (len == 0) + { + if (ferror (fpuser)) + error (1, errno, "cannot read file %s for copying", + user); + break; + } + if (expand_at_signs (buf, len, fprcs) < 0) + goto write_error; + } + } + if (fprintf (fprcs, "@\012\012") < 0) + goto write_error; + if (add_vbranch != NULL) + { + if (fprintf (fprcs, "\012%s.1\012", add_vbranch) < 0 || + fprintf (fprcs, "log\012@") < 0 || + expand_at_signs (message, + (off_t) strlen (message), fprcs) < 0 || + fprintf (fprcs, "@\012text\012") < 0 || + fprintf (fprcs, "@@\012") < 0) + goto write_error; + } + } + + if (fclose (fprcs) == EOF) + { + ierrno = errno; + goto write_error_noclose; + } + /* Close fpuser only if we opened it to begin with. */ + if (fpuser != NULL) + { + if (fclose (fpuser) < 0) + error (0, errno, "cannot close %s", user); + } + + /* + * Fix the modes on the RCS files. The user modes of the original + * user file are propagated to the group and other modes as allowed + * by the repository umask, except that all write permissions are + * turned off. + */ + mode = (sb.st_mode | + (sb.st_mode & S_IRWXU) >> 3 | + (sb.st_mode & S_IRWXU) >> 6) & + ~cvsumask & + ~(S_IWRITE | S_IWGRP | S_IWOTH); + if (chmod (rcs, mode) < 0) + { + ierrno = errno; + if (add_logfp != NULL) + fperrmsg (add_logfp, 0, ierrno, + "WARNING: cannot change mode of file %s", rcs); + error (0, ierrno, "WARNING: cannot change mode of file %s", rcs); + err++; + } + if (tocvsPath) + if (unlink_file_dir (tocvsPath) < 0) + error (0, errno, "cannot remove %s", tocvsPath); + if (free_opt != NULL) + free (free_opt); + return (err); + +write_error: + ierrno = errno; + if (fclose (fprcs) < 0) + error (0, errno, "cannot close %s", rcs); +write_error_noclose: + if (fclose (fpuser) < 0) + error (0, errno, "cannot close %s", user); + if (add_logfp != NULL) + fperrmsg (add_logfp, 0, ierrno, "ERROR: cannot write file %s", rcs); + error (0, ierrno, "ERROR: cannot write file %s", rcs); + if (ierrno == ENOSPC) + { + if (CVS_UNLINK (rcs) < 0) + error (0, errno, "cannot remove %s", rcs); + if (add_logfp != NULL) + fperrmsg (add_logfp, 0, 0, "ERROR: out of space - aborting"); + error (1, 0, "ERROR: out of space - aborting"); + } +read_error: + if (tocvsPath) + if (unlink_file_dir (tocvsPath) < 0) + error (0, errno, "cannot remove %s", tocvsPath); + + if (free_opt != NULL) + free (free_opt); + + return (err + 1); +} + +/* + * Write SIZE bytes at BUF to FP, expanding @ signs into double @ + * signs. If an error occurs, return a negative value and set errno + * to indicate the error. If not, return a nonnegative value. + */ +int +expand_at_signs (buf, size, fp) + const char *buf; + off_t size; + FILE *fp; +{ + register const char *cp, *next; + + cp = buf; + while ((next = memchr (cp, '@', size)) != NULL) + { + size_t len = ++next - cp; + if (fwrite (cp, 1, len, fp) != len) + return EOF; + if (putc ('@', fp) == EOF) + return EOF; + cp = next; + size -= len; + } + + if (fwrite (cp, 1, size, fp) != size) + return EOF; + + return 1; +} + +/* + * Write an update message to (potentially) the screen and the log file. + */ +static void +add_log (ch, fname) + int ch; + char *fname; +{ + if (!really_quiet) /* write to terminal */ + { + char buf[2]; + buf[0] = ch; + buf[1] = ' '; + cvs_output (buf, 2); + if (repos_len) + { + cvs_output (repository + repos_len + 1, 0); + cvs_output ("/", 1); + } + else if (repository[0] != '\0') + { + cvs_output (repository, 0); + cvs_output ("/", 1); + } + cvs_output (fname, 0); + cvs_output ("\n", 1); + } + + if (repos_len) /* write to logfile */ + (void) fprintf (logfp, "%c %s/%s\n", ch, + repository + repos_len + 1, fname); + else if (repository[0]) + (void) fprintf (logfp, "%c %s/%s\n", ch, repository, fname); + else + (void) fprintf (logfp, "%c %s\n", ch, fname); +} + +/* + * This is the recursive function that walks the argument directory looking + * for sub-directories that have CVS administration files in them and updates + * them recursively. + * + * Note that we do not follow symbolic links here, which is a feature! + */ +static int +import_descend_dir (message, dir, vtag, targc, targv) + char *message; + char *dir; + char *vtag; + int targc; + char *targv[]; +{ + struct saved_cwd cwd; + char *cp; + int ierrno, err; + char *rcs = NULL; + + if (islink (dir)) + return (0); + if (save_cwd (&cwd)) + { + fperrmsg (logfp, 0, 0, "ERROR: cannot get working directory"); + return (1); + } + + /* Concatenate DIR to the end of REPOSITORY. */ + if (repository[0] == '\0') + { + char *new = xstrdup (dir); + free (repository); + repository = new; + } + else + { + char *new = xmalloc (strlen (repository) + strlen (dir) + 10); + strcpy (new, repository); + (void) strcat (new, "/"); + (void) strcat (new, dir); + free (repository); + repository = new; + } + + if (!quiet && !current_parsed_root->isremote) + error (0, 0, "Importing %s", repository); + + if ( CVS_CHDIR (dir) < 0) + { + ierrno = errno; + fperrmsg (logfp, 0, ierrno, "ERROR: cannot chdir to %s", dir); + error (0, ierrno, "ERROR: cannot chdir to %s", dir); + err = 1; + goto out; + } + if (!current_parsed_root->isremote && !isdir (repository)) + { + rcs = xmalloc (strlen (repository) + sizeof (RCSEXT) + 5); + (void) sprintf (rcs, "%s%s", repository, RCSEXT); + if (isfile (repository) || isfile(rcs)) + { + fperrmsg (logfp, 0, 0, + "ERROR: %s is a file, should be a directory!", + repository); + error (0, 0, "ERROR: %s is a file, should be a directory!", + repository); + err = 1; + goto out; + } + if (noexec == 0 && CVS_MKDIR (repository, 0777) < 0) + { + ierrno = errno; + fperrmsg (logfp, 0, ierrno, + "ERROR: cannot mkdir %s -- not added", repository); + error (0, ierrno, + "ERROR: cannot mkdir %s -- not added", repository); + err = 1; + goto out; + } + } + err = import_descend (message, vtag, targc, targv); + out: + if (rcs != NULL) + free (rcs); + if ((cp = strrchr (repository, '/')) != NULL) + *cp = '\0'; + else + repository[0] = '\0'; + if (restore_cwd (&cwd, NULL)) + error_exit (); + free_cwd (&cwd); + return (err); +} |