cvs-cvs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Cvs-cvs] Changes to ccvs/src/import.c [signed-commits]


From: Derek Robert Price
Subject: [Cvs-cvs] Changes to ccvs/src/import.c [signed-commits]
Date: Fri, 14 Oct 2005 21:39:33 -0400

Index: ccvs/src/import.c
diff -u /dev/null ccvs/src/import.c:1.175.2.1
--- /dev/null   Sat Oct 15 01:39:33 2005
+++ ccvs/src/import.c   Sat Oct 15 01:39:24 2005
@@ -0,0 +1,1784 @@
+/*
+ * 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 "lstat.h"
+#include "save-cwd.h"
+
+static char *get_comment (const char *user);
+static int add_rev (char *message, RCSNode *rcs, char *vfile,
+                         char *vers);
+static int add_tags (RCSNode *rcs, char *vfile, char *vtag, int targc,
+                    char *targv[]);
+static int import_descend (char *message, char *vtag, int targc, char 
*targv[]);
+static int import_descend_dir (char *message, char *dir, char *vtag,
+                              int targc, char *targv[]);
+static int process_import_file (char *message, char *vfile, char *vtag,
+                               int targc, char *targv[]);
+static int update_rcs_file (char *message, char *vfile, char *vtag, int targc,
+                           char *targv[], int inattic);
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+static int preserve_initial_permissions (FILE *fprcs, const char *userfile,
+                                        mode_t file_type, struct stat *sbp);
+#endif
+static int expand_and_copy_contents (FILE *fprcs, mode_t file_type,
+                                    const char *user, FILE *fpuser);
+static void add_log (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 bool killnew;
+
+static const char *const import_usage[] =
+{
+    "Usage: %s %s [-dX] [-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-X\tWhen importing new files, mark their trunk revisions as dead.\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 (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);
+
+    /* Force -X behaviour or not based on the CVS repository
+       CVSROOT/config setting.  */
+#ifdef CLIENT_SUPPORT
+    killnew = !current_parsed_root->isremote
+             && config->ImportNewFilesToVendorBranchOnly;
+#else /* !CLIENT_SUPPORT */
+    killnew = config->ImportNewFilesToVendorBranchOnly;
+#endif /* CLIENT_SUPPORT */
+
+
+    ign_setup ();
+    wrap_setup ();
+
+    vbranch = xstrdup (CVSBRANCH);
+    optind = 0;
+    while ((c = getopt (argc, argv, "+Qqdb:m:I:k:W:X")) != -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 'X':
+               killnew = true;
+               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.
+     */
+    if ((cp = strstr (argv[0], "CVS")) &&   /* path contains "CVS" AND ... */
+        ((cp == argv[0]) || ISSLASH (*(cp-1))) && /* /^CVS/ OR m#/CVS# AND ... 
*/
+        ((*(cp+3) == '\0') || ISSLASH (*(cp+3))) /* /CVS$/ OR m#CVS/# */
+       )
+    {
+        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.");
+    }
+
+    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]);
+    }
+
+    if (ISABSOLUTE (argv[0]) || pathname_levels (argv[0]) > 0)
+       /* 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]);
+
+    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 = Xasprintf ("%s/%s", current_parsed_root->directory, argv[0]);
+    repos_len = strlen (current_parsed_root->directory);
+
+    /*
+     * 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 (NULL, &message,
+                  current_parsed_root->isremote ? NULL : repository,
+                  NULL);
+    }
+    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);
+       if (killnew)
+           send_arg ("-X");
+       /* 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");
+    }
+
+    ulist = getlist ();
+    p = getnode ();
+    p->type = UPDATE;
+    p->delproc = update_delproc;
+    p->key = xstrdup ("- Imported sources");
+    li = 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);
+    do_verify (&message, repository, ulist);
+
+    /*
+     * 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);
+    /* 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 || killnew)
+    {
+       if (!really_quiet)
+       {
+           char buf[20];
+
+           cvs_output_tagged ("+importmergecmd", NULL);
+           cvs_output_tagged ("newline", NULL);
+           if (conflicts)
+               sprintf (buf, "%d", conflicts);
+           else
+               strcpy (buf, "No");
+           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.  */
+       if (conflicts)
+           (void) fprintf (logfp, "\n%d", conflicts);
+       else
+           (void) fprintf (logfp, "\nNo");
+       (void) fprintf (logfp, " conflicts created by this import.\n");
+       (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.
+     */
+    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 (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;
+
+           /* FIXME: .#filename.sig is where the server currently saves the
+            * signature data when available.  For now just ignore it.
+            */
+           if (server_active && !fnmatch (".#*.sig", dp->d_name, 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)
+       Simple_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 (char *message, char *vfile, char *vtag, int targc,
+                    char **targv)
+{
+    char *rcs;
+    int inattic = 0;
+
+    rcs = Xasprintf ("%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;
+
+           /* If marking newly-imported files as dead, they must be
+              created in the attic!  */
+           if (!killnew)
+               free (attic_name);
+           else 
+           {
+               free (rcs);
+               rcs = attic_name;
+
+               /* Attempt to make the Attic directory, in case it
+                  does not exist.  */
+               (void) sprintf (rcs, "%s/%s", repository, CVSATTIC);
+               if (CVS_MKDIR (rcs, 0777 ) != 0 && errno != EEXIST)
+                   error (1, errno, "cannot make directory `%s'", rcs);
+
+               /* Note that the above clobbered the path name, so we
+                  recreate it here.  */
+               (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC,
+                               vfile, RCSEXT);
+           }
+
+           /*
+            * 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, killnew);
+           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 (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, NULL, vbranch, 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, NULL,
+                                 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 (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, NULL, 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 (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 (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.
+ *
+ * INPUTS
+ *   message    Log message for the addition.  Not used if add_vhead == NULL.
+ *   rcs        Filename of the RCS file to create.  Note that if 'do_killnew'
+ *             is set, this file should be in the Attic directory, and the
+ *             Attic directory must already exist.
+ *   user       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.
+ *   add_vhead  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").
+ *   key_opt    Keyword expansion mode, e.g., "b" for binary.  NULL means the
+ *              default behavior.
+ *   add_vbranch
+ *              Vendor branch to import to, or NULL if none.  If non-NULL, then
+ *              vtag should also be non-NULL.
+ *   vtag
+ *   targc      Number of elements in TARGV.
+ *   targv      The list of tags to attached to this imported revision.
+ *   desctext   If non-NULL, description for the file.  If NULL, the
+ *              description will be empty.
+ *   desclen    The number of bytes in desctext.
+ *   add_logfp  Write errors to here as well as via error (), or NULL if we
+ *              should use only error ().
+ *   do_killnew        Mark newly-imported files as being dead on the trunk, 
i.e.,
+ *             as being imported only to the vendor branch.
+ *
+ * RETURNS
+ *   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 (const char *message, const char *rcs, const char *user,
+              const char *add_vhead, const char *key_opt,
+              const char *add_vbranch, const char *vtag, int targc,
+              char **targv, const char *desctext, size_t desclen,
+              FILE *add_logfp, bool do_killnew)
+{
+    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;
+    char *dead_revision = NULL;
+
+    if (noexec)
+       return 0;
+
+    if (do_killnew)
+    {
+       char *last_place;
+       int last_number;
+
+       /* If we are marking the newly imported file as dead, we must
+          have a head revision.  */
+       if (add_vhead == NULL)
+           error (1, 0, "killing new file attempted when no head revision is 
being added");
+
+       /* One extra byte for NUL, plus one for carry generated by adding
+          one to the last number in the add_vhead revision.  */
+       dead_revision = xmalloc (strlen (add_vhead) + 2);
+       strcpy (dead_revision, add_vhead);
+
+       /* Find the loacation of the last number, which we will increment
+          and overwrite.  Note that this handles single numbers (w/o
+          dots), which is probably unnecessary.  */
+       if ((last_place = strrchr (dead_revision, '.')) != NULL)
+           last_place++;
+       else
+           last_place = dead_revision;
+       last_number = atoi (last_place);
+       if (++last_number <= 0)
+         error (1, 0, "invalid revision number %s", add_vhead);
+       sprintf (last_place, "%d", last_number);
+    }
+
+    /* 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 (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 (
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+       !config->preserve_perms ||
+#endif /* PRESERVE_PERMISSIONS_SUPPORT */
+       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",
+                    do_killnew ? dead_revision : add_vhead) < 0)
+           goto write_error;
+    }
+    else
+    {
+       if (fprintf (fprcs, "head     ;\012") < 0)
+           goto write_error;
+    }
+
+    /* This sets the default branch.  If using the 'do_killnew' functionality,
+       where imports don't show up until merged, no default branch should
+       be set.  */
+    if (add_vbranch != NULL && ! do_killnew)
+    {
+       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 (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 (do_killnew)
+    {
+       if (fprintf (fprcs, "\012%s\012", dead_revision) < 0 ||
+       fprintf (fprcs, "date     %s;  author %s;  state %s;\012",
+                altdate1, author, RCSDEAD) < 0)
+       goto write_error;
+
+       if (fprintf (fprcs, "branches;\012") < 0)
+           goto write_error;
+       if (fprintf (fprcs, "next    %s;\012", add_vhead) < 0)
+           goto write_error;
+
+       if (fprintf (fprcs, "commitid        %s;\012", global_session_id) < 0)
+           goto write_error;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+       /* Store initial permissions if necessary. */
+       if (config->preserve_perms)
+       {
+           if (preserve_initial_permissions (fprcs, userfile,
+                                             file_type, sbp))
+               goto write_error;
+       }
+#endif
+    }
+
+    if (add_vhead != NULL)
+    {
+       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;
+
+       if (fprintf (fprcs, "commitid        %s;\012", global_session_id) < 0)
+           goto write_error;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+       /* Store initial permissions if necessary. */
+       if (config->preserve_perms)
+       {
+           if (preserve_initial_permissions (fprcs, userfile,
+                                             file_type, sbp))
+               goto write_error;
+       }
+#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 ||
+               fprintf (fprcs, "commitid        %s;\012", global_session_id) < 
0)
+               goto write_error;
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+           /* Store initial permissions if necessary. */
+           if (config->preserve_perms)
+           {
+               if (preserve_initial_permissions (fprcs, userfile,
+                                                 file_type, sbp))
+                   goto write_error;
+           }
+#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 (do_killnew)
+    {
+       if (fprintf (fprcs, "\012%s\012", dead_revision) < 0 ||
+           fprintf (fprcs, "log\012@") < 0)
+           goto write_error;
+       if (fprintf (fprcs, "Revision %s was added on the vendor branch.\012",
+                    add_vhead) < 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 (expand_and_copy_contents (fprcs, file_type, user, fpuser))
+           goto write_error;
+
+       if (fprintf (fprcs, "@\012\012") < 0)
+           goto write_error;
+    }
+
+    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 config->preserve_perms is set, do this only for regular files.
+        */
+       if (!do_killnew)
+       {
+            /* Now copy over the contents of the file, expanding at signs,
+              if not done as part of do_killnew handling above.  */
+           if (expand_and_copy_contents (fprcs, file_type, user, fpuser))
+               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;
+}
+
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+/* Write file permissions and symlink information for a file being
+ * added into its RCS file.
+ *
+ * INPUTS
+ *   fprcs     FILE pointer for the (newly-created) RCS file.  Permisisons
+ *             and symlink information should be written here.
+ *   userfile  Filename of the file being added.  (Used to read symbolic
+ *             link contents, for symlinks.)
+ *   file_type File type of userfile, extracted from sbp->st_mode.
+ *   sbp       'stat' information for userfile.
+ *
+ * RETURNS
+ *   Return value is 0 for success, or nonzero for failure (in which case
+ *   no error message has yet been printed).
+ */
+static int
+preserve_initial_permissions (fprcs, userfile, file_type, sbp)
+    FILE *fprcs;
+    const char *userfile;
+    mode_t file_type;
+    struct stat *sbp;
+{
+    if (file_type == S_IFLNK)
+    {
+       char *link = Xreadlink (userfile, sbp->st_size);
+       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", sbp->st_uid) < 0)
+           goto write_error;
+       if (fprintf (fprcs, "group\t%u;\012", sbp->st_gid) < 0)
+           goto write_error;
+       if (fprintf (fprcs, "permissions\t%o;\012",
+                    sbp->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) sbp->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);
+       }
+    }
+    return 0;
+
+write_error:
+    return 1;
+}
+#endif /* PRESERVE_PERMISSIONS_SUPPORT */
+
+/* Copy file contents into an RCS file, expanding at signs.
+ *
+ * If config->preserve_perms is set, nothing is copied if the source is not
+ * a regular file.
+ *
+ * INPUTS
+ *   fprcs     FILE pointer for the (newly-created) RCS file.  The expanded
+ *             contents should be written here.
+ *   file_type File type of the data source.  No data is copied if
+ *             preserve_permissions is set and the source is not a
+ *             regular file.
+ *   user      Filename of the data source (used to print error messages).
+ *   fpuser    FILE pointer for the data source, whose data is being
+ *             copied into the RCS file.
+ *
+ * RETURNS
+ *   Return value is 0 for success, or nonzero for failure (in which case
+ *   no error message has yet been printed).
+ */
+static int
+expand_and_copy_contents (fprcs, file_type, user, fpuser)
+    FILE *fprcs, *fpuser;
+    mode_t file_type;
+    const char *user;
+{
+    if (
+#ifdef PRESERVE_PERMISSIONS_SUPPORT
+       !config->preserve_perms ||
+#endif /* PRESERVE_PERMISSIONS_SUPPORT */
+       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;
+       }
+    }
+    return 0;
+
+write_error:
+    return 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 (const char *buf, size_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 (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 (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, errno, "Failed to save current 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 = Xasprintf ("%s/%s", repository, 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", repository);
+       error (0, ierrno, "ERROR: cannot chdir to %s", repository);
+       err = 1;
+       goto out;
+    }
+    if (!current_parsed_root->isremote && !isdir (repository))
+    {
+       rcs = Xasprintf ("%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))
+       error (1, errno, "Failed to restore current directory, `%s'.",
+              cwd.name);
+    free_cwd (&cwd);
+    return err;
+}




reply via email to

[Prev in Thread] Current Thread [Next in Thread]