diff options
author | peter <peter@FreeBSD.org> | 1998-01-26 03:24:11 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 1998-01-26 03:24:11 +0000 |
commit | 7a4c09bb4393297abf2afd137668362e5365befb (patch) | |
tree | f2b946de03f95cc1662893962401bf021040ddc5 /contrib/cvs | |
parent | 6389db38d7b06e7b98156d32743e434ff54d16bf (diff) | |
download | FreeBSD-src-7a4c09bb4393297abf2afd137668362e5365befb.zip FreeBSD-src-7a4c09bb4393297abf2afd137668362e5365befb.tar.gz |
Merge changes from CYCLIC branch onto mainline. rcs.[ch] still to come.
Diffstat (limited to 'contrib/cvs')
-rw-r--r-- | contrib/cvs/src/commit.c | 196 | ||||
-rw-r--r-- | contrib/cvs/src/cvs.h | 118 | ||||
-rw-r--r-- | contrib/cvs/src/diff.c | 144 | ||||
-rw-r--r-- | contrib/cvs/src/import.c | 325 | ||||
-rw-r--r-- | contrib/cvs/src/lock.c | 8 | ||||
-rw-r--r-- | contrib/cvs/src/logmsg.c | 28 | ||||
-rw-r--r-- | contrib/cvs/src/main.c | 280 | ||||
-rw-r--r-- | contrib/cvs/src/mkmodules.c | 44 | ||||
-rw-r--r-- | contrib/cvs/src/recurse.c | 92 | ||||
-rw-r--r-- | contrib/cvs/src/server.c | 1209 | ||||
-rw-r--r-- | contrib/cvs/src/update.c | 342 |
11 files changed, 1904 insertions, 882 deletions
diff --git a/contrib/cvs/src/commit.c b/contrib/cvs/src/commit.c index 2159835..71b491f 100644 --- a/contrib/cvs/src/commit.c +++ b/contrib/cvs/src/commit.c @@ -3,7 +3,7 @@ * 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 1.4 kit. + * specified in the README file that comes with the CVS source distribution. * * Commit Files * @@ -92,6 +92,7 @@ static const char *const commit_usage[] = "\t-F file\tRead the log message from file.\n", "\t-m msg\tLog message.\n", "\t-r rev\tCommit to this branch or trunk revision.\n", + "(Specify the --help global option for a list of other help options)\n", NULL }; @@ -144,8 +145,22 @@ find_dirent_proc (callerdat, dir, repository, update_dir, entries) { struct find_data *find_data = (struct find_data *)callerdat; + /* This check seems to slowly be creeping throughout CVS (update + and send_dirent_proc by CVS 1.5, diff in 31 Oct 1995. My guess + is that it (or some variant thereof) should go in all the + dirent procs. Unless someone has some better idea... */ + if (!isdir (dir)) + return (R_SKIP_ALL); + /* initialize the ignore list for this directory */ find_data->ignlist = getlist (); + + /* Print the same warm fuzzy as in check_direntproc, since that + code will never be run during client/server operation and we + want the messages to match. */ + if (!quiet) + error (0, 0, "Examining %s", update_dir); + return R_PROCESS; } @@ -232,7 +247,7 @@ find_fileproc (callerdat, finfo) xfinfo.repository = NULL; xfinfo.rcs = NULL; - vers = Version_TS (&xfinfo, NULL, NULL, NULL, 0, 0); + vers = Version_TS (&xfinfo, NULL, tag, NULL, 0, 0); if (vers->ts_user == NULL && vers->vn_user != NULL && vers->vn_user[0] == '-') @@ -246,8 +261,8 @@ find_fileproc (callerdat, finfo) if (vers->ts_user == NULL) error (0, 0, "nothing known about `%s'", finfo->fullname); else - error (0, 0, "use `cvs add' to create an entry for %s", - finfo->fullname); + error (0, 0, "use `%s add' to create an entry for %s", + program_name, finfo->fullname); return 1; } else if (vers->ts_user != NULL @@ -321,6 +336,9 @@ commit (argc, argv) * For log purposes, do not allow "root" to commit files. If you look * like root, but are really logged in as a non-root user, it's OK. */ + /* FIXME: Shouldn't this check be much more closely related to the + readonly user stuff (CVSROOT/readers, &c). That is, why should + root be able to "cvs init", "cvs import", &c, but not "cvs ci"? */ if (geteuid () == (uid_t) 0) { struct passwd *pw; @@ -342,9 +360,9 @@ commit (argc, argv) break; case 'm': #ifdef FORCE_USE_EDITOR - use_editor = TRUE; + use_editor = 1; #else - use_editor = FALSE; + use_editor = 0; #endif if (message) { @@ -371,9 +389,9 @@ commit (argc, argv) break; case 'F': #ifdef FORCE_USE_EDITOR - use_editor = TRUE; + use_editor = 1; #else - use_editor = FALSE; + use_editor = 0; #endif logfile = optarg; break; @@ -425,6 +443,7 @@ commit (argc, argv) #ifdef CLIENT_SUPPORT if (client_active) { + int err; struct find_data find_args; ign_setup (); @@ -489,6 +508,8 @@ commit (argc, argv) do_verify (&message, (char *)NULL); /* We always send some sort of message, even if empty. */ + /* FIXME: is that true? There seems to be some code in do_editor + which can leave the message NULL. */ option_with_arg ("-m", message); /* OK, now process all the questionable files we have been saving @@ -553,14 +574,39 @@ commit (argc, argv) "cvs commit -r 2" across a whole bunch of files a very slow operation (and it isn't documented in cvsclient.texi). I haven't looked at the server code carefully enough to be - _sure_ why this is needed, but if it is because RCS_CI - wants the file to exist, then it would be relatively simple - (but not trivial) to fix in the server. */ + _sure_ why this is needed, but if it is because the "ci" + program, which we used to call, wanted the file to exist, + then it would be relatively simple to fix in the server. */ send_files (find_args.argc, find_args.argv, local, 0, find_args.force ? SEND_FORCE : 0); send_to_server ("ci\012", 0); - return get_responses_and_close (); + err = get_responses_and_close (); + if (err != 0 && use_editor && message != NULL) + { + /* If there was an error, don't nuke the user's carefully + constructed prose. This is something of a kludge; a better + solution is probably more along the lines of #150 in TODO + (doing a second up-to-date check before accepting the + log message has also been suggested, but that seems kind of + iffy because the real up-to-date check could still fail, + another error could occur, &c. Also, a second check would + slow things down). */ + + char *fname; + FILE *fp; + + fname = cvs_temp_name (); + fp = CVS_FOPEN (fname, "w+"); + if (fp == NULL) + error (1, 0, "cannot create temporary file %s", fname); + if (fwrite (message, 1, strlen (message), fp) != strlen (message)) + error (1, errno, "cannot write temporary file %s", fname); + if (fclose (fp) < 0) + error (0, errno, "cannot close temporary file %s", fname); + error (0, 0, "saving log message in %s", fname); + } + return err; } #endif @@ -633,7 +679,10 @@ classify_file_internal (finfo, vers) { int save_noexec, save_quiet, save_really_quiet; Ctype status; - + + /* FIXME: Do we need to save quiet as well as really_quiet? Last + time I glanced at Classify_File I only saw it looking at really_quiet + not quiet. */ save_noexec = noexec; save_quiet = quiet; save_really_quiet = really_quiet; @@ -817,16 +866,30 @@ check_fileproc (callerdat, finfo) if (file_has_markers (finfo)) { + /* Make this a warning, not an error, because we have + no way of knowing whether the "conflict indicators" + are really from a conflict or whether they are part + of the document itself (cvs.texinfo and sanity.sh in + CVS itself, for example, tend to want to have strings + like ">>>>>>>" at the start of a line). Making people + kludge this the way they need to kludge keyword + expansion seems undesirable. And it is worse than + keyword expansion, because there is no -ko + analogue. */ error (0, 0, - "file `%s' still contains conflict indicators", + "\ +warning: file `%s' seems to still contain conflict indicators", finfo->fullname); - freevers_ts (&vers); - return (1); } } if (status == T_REMOVED && vers->tag && isdigit (*vers->tag)) { + /* Remove also tries to forbid this, but we should check + here. I'm only _sure_ about somewhat obscure cases + (hacking the Entries file, using an old version of + CVS for the remove and a new one for the commit), but + there might be other cases. */ error (0, 0, "cannot remove file `%s' which has a numeric sticky tag of `%s'", finfo->fullname, vers->tag); @@ -946,7 +1009,8 @@ check_fileproc (callerdat, finfo) } /* - * Print warm fuzzies while examining the dirs + * By default, return the code that tells do_recursion to examine all + * directories */ /* ARGSUSED */ static Dtype @@ -957,6 +1021,9 @@ check_direntproc (callerdat, dir, repos, update_dir, entries) char *update_dir; List *entries; { + if (!isdir (dir)) + return (R_SKIP_ALL); + if (!quiet) error (0, 0, "Examining %s", update_dir); @@ -1013,7 +1080,8 @@ precommit_proc (repository, filter) free (s); } - run_setup ("%s %s", filter, repository); + run_setup (filter); + run_arg (repository); (void) walklist (ulist, precommit_list_proc, NULL); return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY)); } @@ -1333,7 +1401,8 @@ commit_filesdoneproc (callerdat, err, repository, update_dir, entries) if (line[line_length - 1] == '\n') line[--line_length] = '\0'; repository = Name_Repository ((char *) NULL, update_dir); - run_setup ("%s %s", line, repository); + run_setup (line); + run_arg (repository); cvs_output (program_name, 0); cvs_output (" ", 1); cvs_output (command_name, 0); @@ -1365,7 +1434,7 @@ commit_filesdoneproc (callerdat, err, repository, update_dir, entries) } /* - * Get the log message for a dir and print a warm fuzzy + * Get the log message for a dir */ /* ARGSUSED */ static Dtype @@ -1380,6 +1449,9 @@ commit_direntproc (callerdat, dir, repos, update_dir, entries) List *ulist; char *real_repos; + if (!isdir (dir)) + return (R_SKIP_ALL); + /* find the update list for this dir */ p = findnode (mulist, update_dir); if (p != NULL) @@ -1391,10 +1463,6 @@ commit_direntproc (callerdat, dir, repos, update_dir, entries) if (ulist == NULL || ulist->list->next == ulist->list) return (R_SKIP_FILES); - /* print the warm fuzzy */ - if (!quiet) - error (0, 0, "Committing %s", update_dir); - /* get commit message */ real_repos = Name_Repository (dir, update_dir); got_message = 1; @@ -1489,10 +1557,10 @@ remove_file (finfo, tag, message) error (1, 0, "internal error: no parsed RCS file"); branch = 0; - if (tag && !(branch = RCS_isbranch (finfo->rcs, tag))) + if (tag && !(branch = RCS_nodeisbranch (finfo->rcs, tag))) { /* a symbolic tag is specified; just remove the tag from the file */ - if ((retcode = RCS_deltag (finfo->rcs, tag, 1)) != 0) + if ((retcode = RCS_deltag (finfo->rcs, tag)) != 0) { if (!quiet) error (0, retcode == -1 ? errno : 0, @@ -1500,6 +1568,7 @@ remove_file (finfo, tag, message) finfo->fullname); return (1); } + RCS_rewrite (finfo->rcs, NULL, NULL); Scratch_Entry (finfo->entries, finfo->file); return (0); } @@ -1556,6 +1625,7 @@ remove_file (finfo, tag, message) finfo->fullname); return (1); } + RCS_rewrite (finfo->rcs, NULL, NULL); } #ifdef SERVER_SUPPORT @@ -1584,12 +1654,15 @@ remove_file (finfo, tag, message) /* Except when we are creating a branch, lock the revision so that we can check in the new revision. */ if (lockflag) - RCS_lock (finfo->rcs, rev ? corev : NULL, 0); + { + if (RCS_lock (finfo->rcs, rev ? corev : NULL, 1) == 0) + RCS_rewrite (finfo->rcs, NULL, NULL); + } if (corev != NULL) free (corev); - retcode = RCS_checkin (finfo->rcs->path, finfo->file, message, rev, + retcode = RCS_checkin (finfo->rcs, finfo->file, message, rev, RCS_FLAGS_DEAD | RCS_FLAGS_QUIET); if (retcode != 0) { @@ -1690,6 +1763,8 @@ unlockrcs (rcs) if ((retcode = RCS_unlock (rcs, NULL, 0)) != 0) error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, "could not unlock %s", rcs->path); + else + RCS_rewrite (rcs, NULL, NULL); } /* @@ -1730,6 +1805,7 @@ fixbranch (rcs, branch) if ((retcode = RCS_setbranch (rcs, branch)) != 0) error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, "cannot restore branch to %s for %s", branch, rcs->path); + RCS_rewrite (rcs, NULL, NULL); } } @@ -1841,29 +1917,63 @@ internal error: `%s' didn't move out of the attic", { /* this is the first time we have ever seen this file; create an rcs file. */ - run_setup ("%s%s -x,v/ -i", Rcsbin, RCS); + char *desc; + size_t descalloc; + size_t desclen; + + char *opt; + + desc = NULL; + descalloc = 0; + desclen = 0; fname = xmalloc (strlen (file) + sizeof (CVSADM) + sizeof (CVSEXT_LOG) + 10); (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_LOG); /* If the file does not exist, no big deal. In particular, the server does not (yet at least) create CVSEXT_LOG files. */ if (isfile (fname)) - run_args ("-t%s/%s%s", CVSADM, file, CVSEXT_LOG); + /* FIXME: Should be including update_dir in the appropriate + place here. */ + get_file (fname, fname, "r", &desc, &descalloc, &desclen); free (fname); + /* From reading the RCS 5.7 source, "rcs -i" adds a newline to the + end of the log message if the message is nonempty. + Do it. RCS also deletes certain whitespace, in cleanlogmsg, + which we don't try to do here. */ + if (desclen > 0) + { + expand_string (&desc, &descalloc, desclen + 1); + desc[desclen++] = '\012'; + } + /* Set RCS keyword expansion options. */ if (options && options[0] == '-' && options[1] == 'k') - run_arg (options); - run_arg (rcs); - if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0) + opt = options + 2; + else + opt = NULL; + + /* This message is an artifact of the time when this + was implemented via "rcs -i". It should be revised at + some point (does the "initial revision" in the message from + RCS_checkin indicate that this is a new file? Or does the + "RCS file" message serve some function?). */ + cvs_output ("RCS file: ", 0); + cvs_output (rcs, 0); + cvs_output ("\ndone\n", 0); + + if (add_rcs_file (NULL, rcs, file, NULL, opt, + NULL, NULL, 0, NULL, + desc, desclen, NULL) != 0) { - error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, - "could not create %s", rcs); retval = 1; goto out; } + rcsfile = RCS_parsercsfile (rcs); newfile = 1; + if (desc != NULL) + free (desc); } /* when adding a file for the first time, and using a tag, we need @@ -1883,7 +1993,7 @@ internal error: `%s' didn't move out of the attic", /* commit a dead revision. */ (void) sprintf (tmp, "file %s was initially added on branch %s.", file, tag); - retcode = RCS_checkin (rcs, NULL, tmp, NULL, + retcode = RCS_checkin (rcsfile, NULL, tmp, NULL, RCS_FLAGS_DEAD | RCS_FLAGS_QUIET); free (tmp); if (retcode != 0) @@ -1898,7 +2008,8 @@ internal error: `%s' didn't move out of the attic", rename_file (fname, file); free (fname); - assert (rcsfile == NULL); + /* double-check that the file was written correctly */ + freercsnode (&rcsfile); rcsfile = RCS_parse (file, repository); if (rcsfile == NULL) { @@ -1954,6 +2065,7 @@ internal error: `%s' didn't move out of the attic", magicrev = RCS_magicrev (rcsfile, head); retcode = RCS_settag (rcsfile, tag, magicrev); + RCS_rewrite (rcsfile, NULL, NULL); free (head); free (magicrev); @@ -1986,7 +2098,14 @@ internal error: `%s' didn't move out of the attic", fileattr_newfile (file); + /* I don't think fix_rcs_modes is needed any more. In the + add_rcs_file case, the algorithms used by add_rcs_file and + fix_rcs_modes are the same, so there is no need to go through + it all twice. In the other cases, I think we want to just + preserve the mode that the file had before we started. That is + a behavior change, but I would think a desirable one. */ fix_rcs_modes (rcs, file); + retval = 0; out: @@ -2033,12 +2152,13 @@ lock_RCS (user, rcs, rev, repository) return (1); } } - err = RCS_lock(rcs, NULL, 0); + err = RCS_lock(rcs, NULL, 1); } else { (void) RCS_lock(rcs, rev, 1); } + RCS_rewrite (rcs, NULL, NULL); if (err == 0) { diff --git a/contrib/cvs/src/cvs.h b/contrib/cvs/src/cvs.h index 5ab21c7..7b50497 100644 --- a/contrib/cvs/src/cvs.h +++ b/contrib/cvs/src/cvs.h @@ -127,6 +127,8 @@ extern int errno; #define CVSADM_NOTIFY "CVS/Notify." #define CVSADM_NOTIFYTMP "CVS/Notify.tmp" #define CVSADM_BASE "CVS/Base" +#define CVSADM_BASEREV "CVS/Baserev." +#define CVSADM_BASEREVTMP "CVS/Baserev.tmp" #define CVSADM_TEMPLATE "CVS/Template." #else /* USE_VMS_FILENAMES */ #define CVSADM "CVS" @@ -144,6 +146,8 @@ extern int errno; /* A directory in which we store base versions of files we currently are editing with "cvs edit". */ #define CVSADM_BASE "CVS/Base" +#define CVSADM_BASEREV "CVS/Baserev" +#define CVSADM_BASEREVTMP "CVS/Baserev.tmp" /* File which contains the template for use in log messages. */ #define CVSADM_TEMPLATE "CVS/Template" #endif /* USE_VMS_FILENAMES */ @@ -182,6 +186,7 @@ extern int errno; #define CVSROOTADM_READERS "readers" #define CVSROOTADM_WRITERS "writers" #define CVSROOTADM_PASSWD "passwd" +#define CVSROOTADM_CONFIG "config" #define CVSROOTADM_OPTIONS "options" #define CVSNULLREPOS "Emptydir" /* an empty directory */ @@ -207,11 +212,10 @@ extern int errno; /* Command attributes -- see function lookup_command_attribute(). */ #define CVS_CMD_IGNORE_ADMROOT 1 -/* Set if CVS does _not_ need to create a CVS/Root file upon - completion of this command. The name is confusing, both because - the meaning is closer to "does not use working directory" than - "uses working directory" and because the flag isn't really as - general purpose as it seems (cvs release sets it). */ +/* Set if CVS needs to create a CVS/Root file upon completion of this + command. The name may be slightly confusing, because the flag + isn't really as general purpose as it seems (it is not set for cvs + release). */ #define CVS_CMD_USES_WORK_DIR 2 @@ -241,9 +245,6 @@ extern int errno; #endif #endif /* USE_VMS_FILENAMES */ -#define FALSE 0 -#define TRUE 1 - /* * Special tags. -rHEAD refers to the head of an RCS file, regardless of any * sticky tags. -rBASE refers to the current revision the user has checked @@ -254,13 +255,10 @@ extern int errno; /* Environment variable used by CVS */ #define CVSREAD_ENV "CVSREAD" /* make files read-only */ -#define CVSREAD_DFLT FALSE /* writable files by default */ +#define CVSREAD_DFLT 0 /* writable files by default */ #define CVSREADONLYFS_ENV "CVSREADONLYFS" /* repository is read-only */ -#define RCSBIN_ENV "RCSBIN" /* RCS binary directory */ -/* #define RCSBIN_DFLT Set by options.h */ - #define TMPDIR_ENV "TMPDIR" /* Temporary directory */ /* #define TMPDIR_DFLT Set by options.h */ @@ -303,8 +301,13 @@ struct entnode enum ent_type type; char *user; char *version; + + /* Timestamp, or "" if none (never NULL). */ char *timestamp; + + /* Keyword expansion options, or "" if none (never NULL). */ char *options; + char *tag; char *date; char *conflict; @@ -356,7 +359,7 @@ typedef enum direnter_type Dtype; #endif extern char *program_name, *program_path, *command_name; -extern char *Rcsbin, *Tmpdir, *Editor; +extern char *Tmpdir, *Editor; extern int cvsadmin_root; extern char *CurDir; extern int really_quiet, quiet; @@ -367,7 +370,8 @@ extern char *RCS_citag; /* Access method specified in CVSroot. */ typedef enum { - local_method, server_method, pserver_method, kserver_method, ext_method + local_method, server_method, pserver_method, kserver_method, gserver_method, + ext_method } CVSmethod; extern char *method_names[]; /* change this in root.c if you change the enum above */ @@ -379,6 +383,8 @@ extern char *CVSroot_username; /* the username or NULL if method == local */ extern char *CVSroot_hostname; /* the hostname or NULL if method == local */ extern char *CVSroot_directory; /* the directory name */ +extern char *emptydir_name PROTO ((void)); + extern int trace; /* Show all commands */ extern int noexec; /* Don't modify disk anywhere */ extern int readonlyfs; /* fail on all write locks; succeed all read locks */ @@ -394,20 +400,24 @@ extern char hostname[]; /* Externs that are included directly in the CVS sources */ -int RCS_exec_settag PROTO((const char *, const char *, const char *)); -int RCS_exec_deltag PROTO((const char *, const char *, int)); -int RCS_exec_setbranch PROTO((const char *, const char *)); -int RCS_exec_lock PROTO((const char *, const char *, int)); -int RCS_exec_unlock PROTO((const char *, const char *, int)); -int RCS_merge PROTO((const char *, const char *, const char *, const char *)); +int RCS_merge PROTO((RCSNode *, char *, char *, char *, char *, char *)); /* Flags used by RCS_* functions. See the description of the individual functions for which flags mean what for each function. */ #define RCS_FLAGS_FORCE 1 #define RCS_FLAGS_DEAD 2 #define RCS_FLAGS_QUIET 4 #define RCS_FLAGS_MODTIME 8 -int RCS_checkin PROTO ((char *rcsfile, char *workfile, char *message, - char *rev, int flags)); + +extern int RCS_exec_rcsdiff PROTO ((RCSNode *rcsfile, + char *opts, char *options, + char *rev1, char *rev2, + char *label1, char *label2, + char *workfile)); +extern int diff_exec PROTO ((char *file1, char *file2, char *options, + char *out)); +extern int diff_execv PROTO ((char *file1, char *file2, + char *label1, char *label2, + char *options, char *out)); @@ -421,9 +431,11 @@ List *Entries_Open PROTO((int aflag)); void Subdirs_Known PROTO((List *entries)); void Subdir_Register PROTO((List *, const char *, const char *)); void Subdir_Deregister PROTO((List *, const char *, const char *)); + char *Make_Date PROTO((char *rawdate)); char *Name_Repository PROTO((char *dir, char *update_dir)); - +char *Short_Repository PROTO((char *repository)); +void Sanitize_Repository_Name PROTO((char *repository)); char *Name_Root PROTO((char *dir, char *update_dir)); int parse_cvsroot PROTO((char *CVSroot)); @@ -433,9 +445,8 @@ void root_allow_add PROTO ((char *)); void root_allow_free PROTO ((void)); int root_allow_ok PROTO ((char *)); -int same_directories PROTO((char *dir1, char *dir2)); -char *Short_Repository PROTO((char *repository)); -char *gca PROTO((char *rev1, char *rev2)); +char *gca PROTO((const char *rev1, const char *rev2)); +extern void check_numeric PROTO ((const char *, int, char **)); char *getcaller PROTO((void)); char *time_stamp PROTO((char *file)); @@ -448,6 +459,8 @@ int pathname_levels PROTO ((char *path)); typedef int (*CALLPROC) PROTO((char *repository, char *value)); int Parse_Info PROTO((char *infofile, char *repository, CALLPROC callproc, int all)); +extern int parse_config PROTO ((char *)); + typedef RETSIGTYPE (*SIGCLEANUPPROC) PROTO(()); int SIG_register PROTO((int sig, SIGCLEANUPPROC sigcleanup)); int isdir PROTO((const char *file)); @@ -463,6 +476,8 @@ char *cvs_temp_name PROTO ((void)); void parseopts PROTO ((const char *root)); int numdots PROTO((const char *s)); +char *increment_revnum PROTO ((const char *)); +int compare_revnums PROTO ((const char *, const char *)); int unlink_file PROTO((const char *f)); int link_file PROTO ((const char *from, const char *to)); int unlink_file_dir PROTO((const char *f)); @@ -471,9 +486,10 @@ int xcmp PROTO((const char *file1, const char *file2)); int yesno PROTO((void)); void *valloc PROTO((size_t bytes)); time_t get_date PROTO((char *date, struct timeb *now)); -void Create_Admin PROTO((char *dir, char *update_dir, - char *repository, char *tag, char *date, - int nonbranch)); +extern int Create_Admin PROTO ((char *dir, char *update_dir, + char *repository, char *tag, char *date, + int nonbranch, int warn)); +extern int expand_at_signs PROTO ((char *, off_t, FILE *)); /* Locking subsystem (implemented in lock.c). */ @@ -511,7 +527,7 @@ extern int ign_case; #include "update.h" -void line2argv PROTO ((int *pargc, char ***argv, char *line)); +void line2argv PROTO ((int *pargc, char ***argv, char *line, char *sepchars)); void make_directories PROTO((const char *name)); void make_directory PROTO((const char *name)); extern int mkdir_if_needed PROTO ((char *name)); @@ -548,7 +564,7 @@ void do_editor PROTO((char *dir, char **messagep, void do_verify PROTO((char **messagep, char *repository)); typedef int (*CALLBACKPROC) PROTO((int *pargc, char *argv[], char *where, - char *mwhere, char *mfile, int horten, int local_specified, + char *mwhere, char *mfile, int shorten, int local_specified, char *omodule, char *msg)); /* This is the structure that the recursion processor passes to the @@ -609,7 +625,9 @@ void SIG_endCrSect PROTO((void)); void read_cvsrc PROTO((int *argc, char ***argv, char *cmdname)); char *make_message_rcslegal PROTO((char *message)); -extern int file_has_markers PROTO ((struct file_info *)); +extern int file_has_markers PROTO ((const struct file_info *)); +extern void get_file PROTO ((const char *, const char *, const char *, + char **, size_t *, size_t *)); /* flags for run_exec(), the fast system() for CVS */ #define RUN_NORMAL 0x0000 /* no special behaviour */ @@ -622,14 +640,9 @@ extern int file_has_markers PROTO ((struct file_info *)); void run_arg PROTO((const char *s)); void run_print PROTO((FILE * fp)); -#ifdef HAVE_VPRINTF -void run_setup PROTO((const char *fmt,...)); -void run_args PROTO((const char *fmt,...)); -#else -void run_setup (); -void run_args (); -#endif -int run_exec PROTO((char *stin, char *stout, char *sterr, int flags)); +void run_setup PROTO ((const char *prog)); +int run_exec PROTO((const char *stin, const char *stout, const char *sterr, + int flags)); /* other similar-minded stuff from run.c. */ FILE *run_popen PROTO((const char *, const char *)); @@ -649,10 +662,16 @@ pid_t waitpid PROTO((pid_t, int *, int)); struct vers_ts { /* rcs version user file derives from, from CVS/Entries. - * it can have the following special values: - * empty = no user file - * 0 = user file is new - * -vers = user file to be removed. */ + It can have the following special values: + + NULL = file is not mentioned in Entries (this is also used for a + directory). + "" = ILLEGAL! The comment used to say that it meant "no user file" + but as far as I know CVS didn't actually use it that way. + Note that according to cvs.texinfo, "" is not legal in the + Entries file. + 0 = user file is new + -vers = user file to be removed. */ char *vn_user; /* Numeric revision number corresponding to ->vn_tag (->vn_tag @@ -675,7 +694,8 @@ struct vers_ts and if they differ it is modified. */ char *ts_rcs; - /* Options from CVS/Entries (keyword expansion). */ + /* Options from CVS/Entries (keyword expansion), malloc'd. If none, + then it is an empty string (never NULL). */ char *options; /* If non-NULL, there was a conflict (or merely a merge? See merge_file) @@ -714,6 +734,11 @@ int Checkin PROTO ((int type, struct file_info *finfo, char *rcs, char *rev, char *tag, char *options, char *message)); int No_Difference PROTO ((struct file_info *finfo, Vers_TS *vers)); +/* CVSADM_BASEREV stuff, from entries.c. */ +extern char *base_get PROTO ((struct file_info *)); +extern void base_register PROTO ((struct file_info *, char *)); +extern void base_deregister PROTO ((struct file_info *)); + /* * defines for Classify_File() to determine the current state of a file. * These are also used as types in the data field for the list we make for @@ -824,10 +849,13 @@ extern void tag_check_valid PROTO ((char *, int, char **, int, int, char *)); extern void tag_check_valid_join PROTO ((char *, int, char **, int, int, char *)); +/* From server.c and documented there. */ extern void cvs_output PROTO ((const char *, size_t)); +extern void cvs_output_binary PROTO ((char *, size_t)); extern void cvs_outerr PROTO ((const char *, size_t)); extern void cvs_flusherr PROTO ((void)); extern void cvs_flushout PROTO ((void)); +extern void cvs_output_tagged PROTO ((char *, char *)); #if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT) #include "server.h" diff --git a/contrib/cvs/src/diff.c b/contrib/cvs/src/diff.c index 3fdf3e4..4690ed8 100644 --- a/contrib/cvs/src/diff.c +++ b/contrib/cvs/src/diff.c @@ -3,7 +3,7 @@ * 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 1.4 kit. + * specified in the README file that comes with the CVS source distribution. * * Difference * @@ -43,6 +43,7 @@ static void diff_mark_errors PROTO((int err)); static char *diff_rev1, *diff_rev2; static char *diff_date1, *diff_date2; static char *use_rev1, *use_rev2; +static int have_rev1_label, have_rev2_label; /* Revision of the user file, if it is unchanged from something in the repository and we want to use that fact. */ @@ -71,6 +72,7 @@ static const char *const diff_usage[] = "\t--ifdef=arg\tOutput diffs in ifdef format.\n", "(consult the documentation for your diff program for rcsdiff-options.\n", "The most popular is -c for context diffs but there are many more).\n", + "(Specify the --help global option for a list of other help options)\n", NULL }; @@ -103,13 +105,13 @@ static struct option const longopts[] = { {"ignore-blank-lines", 0, 0, 'B'}, {"context", 2, 0, 143}, - {"ifdef", 1, 0, 147}, + {"ifdef", 1, 0, 131}, {"show-function-line", 1, 0, 'F'}, {"speed-large-files", 0, 0, 'H'}, {"ignore-matching-lines", 1, 0, 'I'}, {"label", 1, 0, 'L'}, {"new-file", 0, 0, 'N'}, - {"initial-tab", 0, 0, 'T'}, + {"initial-tab", 0, 0, 148}, {"width", 1, 0, 'W'}, {"text", 0, 0, 'a'}, {"ignore-space-change", 0, 0, 'b'}, @@ -130,7 +132,7 @@ static struct option const longopts[] = {"report-identical-files", 0, 0, 's'}, {"expand-tabs", 0, 0, 't'}, {"ignore-all-space", 0, 0, 'w'}, - {"side-by-side", 0, 0, 'y'}, + {"side-by-side", 0, 0, 147}, {"unified", 2, 0, 146}, {"left-column", 0, 0, 129}, {"suppress-common-lines", 0, 0, 130}, @@ -147,6 +149,37 @@ static struct option const longopts[] = {0, 0, 0, 0} }; +/* CVS 1.9 and similar versions seemed to have pretty weird handling + of -y and -T. In the cases where it called rcsdiff, + they would have the meanings mentioned below. In the cases where it + called diff, they would have the meanings mentioned in "longopts". + Noone seems to have missed them, so I think the right thing to do is + just to remove the options altogether (which I have done). + + In the case of -z and -q, "cvs diff" did not accept them even back + when we called rcsdiff (at least, it hasn't accepted them + recently). + + In comparing rcsdiff to the new CVS implementation, I noticed that + the following rcsdiff flags are not handled by CVS diff: + + -y: perform diff even when the requested revisions are the + same revision number + -q: run quietly + -T: preserve modification time on the RCS file + -z: specify timezone for use in file labels + + I think these are not really relevant. -y is undocumented even in + RCS 5.7, and seems like a minor change at best. According to RCS + documentation, -T only applies when a RCS file has been modified + because of lock changes; doesn't CVS sidestep RCS's entire lock + structure? -z seems to be unsupported by CVS diff, and has a + different meaning as a global option anyway. (Adding it could be + a feature, but if it is left out for now, it should not break + anything.) For the purposes of producing output, CVS diff appears + mostly to ignore -q. Maybe this should be fixed, but I think it's + a larger issue than the changes included here. */ + static void strcat_and_allocate PROTO ((char **, size_t *, const char *)); /* *STR is a pointer to a malloc'd string. *LENP is its allocated @@ -183,6 +216,8 @@ diff (argc, argv) if (argc == -1) usage (diff_usage); + have_rev1_label = have_rev2_label = 0; + /* * Note that we catch all the valid arguments here, so that we can * intercept the -r arguments for doing revision diffs; and -l/-R for a @@ -201,33 +236,44 @@ diff (argc, argv) optind = 0; while ((c = getopt_long (argc, argv, - "+abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:V:W:k:r:", + "+abcdefhilnpstuw0123456789BHNRC:D:F:I:L:U:V:W:k:r:", longopts, &option_index)) != -1) { switch (c) { case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'h': case 'i': case 'n': case 'p': case 's': case 't': - case 'u': case 'w': case 'y': case '0': case '1': case '2': + case 'u': case 'w': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': - case '9': case 'B': case 'H': case 'T': + case '9': case 'B': case 'H': (void) sprintf (tmp, " -%c", (char) c); strcat_and_allocate (&opts, &opts_allocated, tmp); break; - case 'C': case 'F': case 'I': case 'L': case 'U': case 'V': - case 'W': + case 'L': + if (have_rev1_label++) + if (have_rev2_label++) + { + error (0, 0, "extra -L arguments ignored"); + break; + } + + strcat_and_allocate (&opts, &opts_allocated, " -L"); + strcat_and_allocate (&opts, &opts_allocated, optarg); + break; + case 'C': case 'F': case 'I': case 'U': case 'V': case 'W': (void) sprintf (tmp, " -%c", (char) c); strcat_and_allocate (&opts, &opts_allocated, tmp); strcat_and_allocate (&opts, &opts_allocated, optarg); break; - case 147: + case 131: /* --ifdef. */ strcat_and_allocate (&opts, &opts_allocated, " -D"); strcat_and_allocate (&opts, &opts_allocated, optarg); break; - case 129: case 130: case 131: case 132: case 133: case 134: + case 129: case 130: case 132: case 133: case 134: case 135: case 136: case 137: case 138: case 139: case 140: case 141: case 142: case 143: case 144: case 145: case 146: + case 147: case 148: strcat_and_allocate (&opts, &opts_allocated, " --"); strcat_and_allocate (&opts, &opts_allocated, longopts[option_index].name); @@ -359,6 +405,11 @@ diff_fileproc (callerdat, finfo) char *tocvsPath; char *fname; + /* Initialize these solely to avoid warnings from gcc -Wall about + variables that might be used uninitialized. */ + tmp = NULL; + fname = NULL; + user_file_rev = 0; vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0); @@ -536,9 +587,9 @@ diff_fileproc (callerdat, finfo) } /* Output an "Index:" line for patch to use */ - (void) fflush (stdout); - (void) printf ("Index: %s\n", finfo->fullname); - (void) fflush (stdout); + cvs_output ("Index: ", 0); + cvs_output (finfo->fullname, 0); + cvs_output ("\n", 1); tocvsPath = wrap_tocvs_process_file(finfo->file); if (tocvsPath) @@ -561,14 +612,20 @@ diff_fileproc (callerdat, finfo) { /* This is file, not fullname, because it is the "Index:" line which is supposed to contain the directory. */ - (void) printf ("===================================================================\nRCS file: %s\n", - finfo->file); - (void) printf ("diff -N %s\n", finfo->file); + cvs_output ("\ +===================================================================\n\ +RCS file: ", 0); + cvs_output (finfo->file, 0); + cvs_output ("\n", 1); + + cvs_output ("diff -N ", 0); + cvs_output (finfo->file, 0); + cvs_output ("\n", 1); if (empty_file == DIFF_ADDED) { if (use_rev2 == NULL) - run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, finfo->file); + status = diff_exec (DEVNULL, finfo->file, opts, RUN_TTY); else { int retcode; @@ -589,7 +646,7 @@ diff_fileproc (callerdat, finfo) } /* FIXME: what if retcode > 0? */ - run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, tmp); + status = diff_exec (DEVNULL, tmp, opts, RUN_TTY); } } else @@ -610,30 +667,36 @@ diff_fileproc (callerdat, finfo) } /* FIXME: what if retcode > 0? */ - run_setup ("%s %s %s %s", DIFF, opts, tmp, DEVNULL); + status = diff_exec (tmp, DEVNULL, opts, RUN_TTY); } } else { - if (use_rev2) - { - run_setup ("%s%s -x,v/ %s %s -r%s -r%s", Rcsbin, RCS_DIFF, - opts, *options ? options : vers->options, - use_rev1, use_rev2); - } - else - { - run_setup ("%s%s -x,v/ %s %s -r%s", Rcsbin, RCS_DIFF, opts, - *options ? options : vers->options, use_rev1); - } - run_arg (vers->srcfile->path); + char *label1 = NULL; + char *label2 = NULL; + + if (!have_rev1_label) + label1 = + make_file_label (finfo->fullname, use_rev1, vers->srcfile); + + if (!have_rev2_label) + label2 = + make_file_label (finfo->fullname, use_rev2, vers->srcfile); + + status = RCS_exec_rcsdiff (vers->srcfile, opts, + *options ? options : vers->options, + use_rev1, use_rev2, + label1, label2, + finfo->file); + + if (label1) free (label1); + if (label2) free (label2); } - switch ((status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, - RUN_REALLY|RUN_COMBINED))) + switch (status) { case -1: /* fork failed */ - error (1, errno, "fork failed during rcsdiff of %s", + error (1, errno, "fork failed while diffing %s", vers->srcfile->path); case 0: /* everything ok */ err = 0; @@ -662,7 +725,6 @@ diff_fileproc (callerdat, finfo) free (tmp); } - (void) fflush (stdout); freevers_ts (&vers); diff_mark_errors (err); return (err); @@ -696,9 +758,9 @@ diff_dirproc (callerdat, dir, pos_repos, update_dir, entries) /* XXX - check for dirs we don't want to process??? */ /* YES ... for instance dirs that don't exist!!! -- DW */ - if (!isdir (dir) ) - return (R_SKIP_ALL); - + if (!isdir (dir)) + return (R_SKIP_ALL); + if (!quiet) error (0, 0, "Diffing %s", update_dir); return (R_PROCESS); @@ -855,9 +917,9 @@ diff_file_nodiff (finfo, vers, empty_file) { /* drop user_file_rev into first unused use_rev */ if (!use_rev1) - use_rev1 = xstrdup (user_file_rev); + use_rev1 = xstrdup (user_file_rev); else if (!use_rev2) - use_rev2 = xstrdup (user_file_rev); + use_rev2 = xstrdup (user_file_rev); /* and if not, it wasn't needed anyhow */ user_file_rev = 0; } diff --git a/contrib/cvs/src/import.c b/contrib/cvs/src/import.c index 279bcd4..11fc99d 100644 --- a/contrib/cvs/src/import.c +++ b/contrib/cvs/src/import.c @@ -3,7 +3,7 @@ * 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 1.4 kit. + * 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. @@ -23,7 +23,6 @@ #define FILE_HOLDER ".#cvsxxx" static char *get_comment PROTO((char *user)); -static int expand_at_signs PROTO((char *buf, off_t size, FILE *fp)); 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, @@ -56,6 +55,7 @@ static const char *const import_usage[] = "\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 }; @@ -104,9 +104,9 @@ import (argc, argv) break; case 'm': #ifdef FORCE_USE_EDITOR - use_editor = TRUE; + use_editor = 1; #else - use_editor = FALSE; + use_editor = 0; #endif message = xstrdup(optarg); break; @@ -199,6 +199,7 @@ import (argc, argv) if (msglen == 0 || message[msglen - 1] != '\n') { char *nm = xmalloc (msglen + 2); + *nm = '\0'; if (message != NULL) { (void) strcpy (nm, message); @@ -467,6 +468,8 @@ process_import_file (message, vfile, vtag, targc, targv) if (!isfile (attic_name)) { int retval; + char *free_opt = NULL; + char *our_opt = keyword_opt; free (attic_name); /* @@ -474,8 +477,42 @@ process_import_file (message, vfile, vtag, targc, targv) * repository nor in the Attic -- create it anew. */ add_log ('N', vfile); - retval = add_rcs_file (message, rcs, vfile, vhead, vbranch, - vtag, targc, targv, logfp); + +#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); + node = findnode_fn (entries, vfile); + if (node != NULL) + { + Entnode *entdata = (Entnode *) 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; } @@ -602,12 +639,14 @@ add_rev (message, rcs, vfile, vers) /* 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. */ - if (RCS_lock (rcs, vbranch, 1) != 0) - { - error (0, errno, "fork failed"); - return (1); - } + /* 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); if (tocvsPath == NULL) @@ -632,7 +671,7 @@ add_rev (message, rcs, vfile, vers) } } - status = RCS_checkin (rcs->path, tocvsPath == NULL ? vfile : tocvsPath, + status = RCS_checkin (rcs, tocvsPath == NULL ? vfile : tocvsPath, message, vbranch, (RCS_FLAGS_QUIET | (use_file_modtime ? RCS_FLAGS_MODTIME : 0))); @@ -656,6 +695,7 @@ add_rev (message, rcs, vfile, vers) if (locked) { (void) RCS_unlock(rcs, vbranch, 0); + RCS_rewrite (rcs, NULL, NULL); } return (1); } @@ -693,6 +733,7 @@ add_tags (rcs, vfile, vtag, targc, targv) "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; @@ -705,7 +746,9 @@ add_tags (rcs, vfile, vtag, targc, targv) 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) + if ((retcode = RCS_settag (rcs, targv[i], vers->vn_rcs)) == 0) + RCS_rewrite (rcs, NULL, NULL); + else { ierrno = errno; fperror (logfp, 0, retcode == -1 ? ierrno : 0, @@ -879,23 +922,33 @@ get_comment (user) /* 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. */ + 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, add_vbranch, vtag, targc, targv, - add_logfp) - /* Log message for the addition. */ +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. */ char *message; /* Filename of the RCS file to create. */ char *rcs; /* Filename of the file to serve as the contents of the initial - revision. */ + revision. Even if add_vhead is NULL, we use this to determine + the modes to give the new RCS file. */ 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. */ + from it. If NULL, then just add an empty file without any + revisions (similar to the one created by "rcs -i"). */ char *add_vhead; + /* Keyword expansion mode, e.g., "b" for binary. NULL means the + default behavior. */ + char *key_opt; + /* Vendor branch to import to, or NULL if none. If non-NULL, then vtag should also be non-NULL. */ char *add_vbranch; @@ -903,6 +956,11 @@ add_rcs_file (message, rcs, user, add_vhead, add_vbranch, vtag, targc, targv, int targc; char *targv[]; + /* If non-NULL, description for the file. If NULL, the description + will be empty. */ + 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; @@ -912,20 +970,24 @@ add_rcs_file (message, rcs, user, add_vhead, add_vbranch, vtag, targc, targv, struct tm *ftm; time_t now; char altdate1[MAXDATELEN]; -#ifndef HAVE_RCS5 - char altdate2[MAXDATELEN]; -#endif char *author; int i, ierrno, err = 0; mode_t mode; char *tocvsPath; char *userfile; - char *local_opt = keyword_opt; + char *local_opt = key_opt; char *free_opt = NULL; 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 (local_opt == NULL) { if (wrap_name_has (user, WRAP_RCSOPTION)) @@ -936,7 +998,17 @@ add_rcs_file (message, rcs, user, add_vhead, add_vbranch, vtag, targc, targv, tocvsPath = wrap_tocvs_process_file (user); userfile = (tocvsPath == NULL ? user : tocvsPath); - fpuser = CVS_FOPEN (userfile, "r"); + + /* 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. */ + fpuser = CVS_FOPEN (userfile, + ((local_opt != NULL && strcmp (local_opt, "b") == 0) + ? "rb" + : "r") + ); if (fpuser == NULL) { /* not fatal, continue import */ @@ -954,8 +1026,17 @@ add_rcs_file (message, rcs, user, add_vhead, add_vbranch, vtag, targc, targv, /* * putadmin() */ - if (fprintf (fprcs, "head %s;\012", add_vhead) < 0) - goto write_error; + 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) @@ -1001,125 +1082,125 @@ add_rcs_file (message, rcs, user, add_vhead, add_vbranch, vtag, targc, targv, if (fprintf (fprcs, "\012") < 0) goto write_error; - /* - * puttree() - */ + /* Get information on modtime and mode. */ if (fstat (fileno (fpuser), &sb) < 0) error (1, errno, "cannot fstat %s", user); - if (use_file_modtime) - now = sb.st_mtime; - else - (void) time (&now); -#ifdef HAVE_RCS5 - ftm = gmtime (&now); -#else - ftm = localtime (&now); -#endif - (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); -#ifdef HAVE_RCS5 -#define altdate2 altdate1 -#else - /* - * If you don't have RCS V5 or later, you need to lie about the ci - * time, since RCS V4 and earlier insist that the times differ. - */ - now++; - ftm = localtime (&now); - (void) sprintf (altdate2, 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); -#endif - author = getcaller (); - if (fprintf (fprcs, "\012%s\012", add_vhead) < 0 || + /* 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) + 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, ";\012") < 0) - goto write_error; - if (fprintf (fprcs, "next ;\012") < 0) - goto write_error; - if (add_vbranch != NULL) - { - if (fprintf (fprcs, "\012%s.1\012", add_vbranch) < 0 || - fprintf (fprcs, "date %s; author %s; state Exp;\012", - altdate2, author) < 0 || - fprintf (fprcs, "branches ;\012") < 0 || - fprintf (fprcs, "next ;\012\012") < 0) + if (fprintf (fprcs, "next ;\012") < 0) goto write_error; + 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\012") < 0) + goto write_error; + } } - if ( - /* - * putdesc() - */ - fprintf (fprcs, "\012desc\012") < 0 || - fprintf (fprcs, "@@\012\012\012") < 0 || - /* - * putdelta() - */ - fprintf (fprcs, "\012%s\012", add_vhead) < 0 || - fprintf (fprcs, "log\012@") < 0) + + /* Now write the description (possibly empty). */ + if (fprintf (fprcs, "\012desc\012") < 0 || + fprintf (fprcs, "@") < 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 (desctext != NULL) { - if (expand_at_signs (message, (off_t) strlen (message), fprcs) < 0) + /* 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") < 0 || - fprintf (fprcs, "text\012@") < 0) - { + if (fprintf (fprcs, "@\012\012\012") < 0) goto write_error; - } - /* Now copy over the contents of the file, expanding at signs. */ + /* Now write the log messages and contents for the revision(s) (that + is, "deltatext" rather than "delta" from rcsfile(5)). */ + if (add_vhead != NULL) { - char buf[8192]; - unsigned int len; + 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; + } - while (1) + /* Now copy over the contents of the file, expanding at signs. */ { - len = fread (buf, 1, sizeof buf, fpuser); - if (len == 0) + char buf[8192]; + unsigned int len; + + while (1) { - if (ferror (fpuser)) - error (1, errno, "cannot read file %s for copying", user); - break; + 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 (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) + 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) @@ -1184,7 +1265,7 @@ read_error: * signs. If an error occurs, return a negative value and set errno * to indicate the error. If not, return a nonnegative value. */ -static int +int expand_at_signs (buf, size, fp) char *buf; off_t size; diff --git a/contrib/cvs/src/lock.c b/contrib/cvs/src/lock.c index c8d9bc6..0d5cef6 100644 --- a/contrib/cvs/src/lock.c +++ b/contrib/cvs/src/lock.c @@ -3,7 +3,7 @@ * 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 1.4 kit. + * specified in the README file that comes with the CVS source distribution. * * Set Lock * @@ -39,9 +39,9 @@ be. * Readlocks ensure that we won't find the file in the state in - which it is in between the "rcs -i" and the RCS_checkin in commit.c - (when a file is being added). This state is a state in which the - RCS file parsing routines in rcs.c cannot parse the file. + which it is in between the calls to add_rcs_file and RCS_checkin in + commit.c (when a file is being added). This state is a state in + which the RCS file parsing routines in rcs.c cannot parse the file. * Readlocks ensure that a reader won't try to look at a half-written fileattr file (fileattr is not updated atomically). diff --git a/contrib/cvs/src/logmsg.c b/contrib/cvs/src/logmsg.c index 7f83183..0e26bce 100644 --- a/contrib/cvs/src/logmsg.c +++ b/contrib/cvs/src/logmsg.c @@ -3,7 +3,7 @@ * 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 1.4 kit. + * specified in the README file that comes with the CVS source distribution. */ #include "cvs.h" @@ -282,7 +282,7 @@ do_editor (dir, messagep, repository, changes) (void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0); /* run the editor */ - run_setup ("%s", editinfo_editor ? editinfo_editor : Editor); + run_setup (editinfo_editor ? editinfo_editor : Editor); run_arg (fname); if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL | RUN_SIGIGNORE)) != 0) @@ -345,8 +345,16 @@ do_editor (dir, messagep, repository, changes) (void) printf ("Action: (continue) "); (void) fflush (stdout); line_length = getline (&line, &line_chars_allocated, stdin); - if (line_length <= 0 - || *line == '\n' || *line == 'c' || *line == 'C') + if (line_length < 0) + { + error (0, errno, "cannot read from stdin"); + if (unlink_file (fname) < 0) + error (0, errno, + "warning: cannot remove temp file %s", fname); + error (1, 0, "aborting"); + } + else if (line_length == 0 + || *line == '\n' || *line == 'c' || *line == 'C') break; if (*line == 'a' || *line == 'A') { @@ -417,10 +425,7 @@ do_verify (messagep, repository) fp = fopen (fname, "w"); if (fp == NULL) - { error (1, errno, "cannot create temporary file %s", fname); - return; - } else { fprintf (fp, "%s", *messagep); @@ -440,10 +445,15 @@ do_verify (messagep, repository) if (verifymsg_script) { - run_setup ("%s", verifymsg_script); + run_setup (verifymsg_script); run_arg (fname); if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL | RUN_SIGIGNORE)) != 0) + { + /* Since following error() exits, delete the temp file + now. */ + unlink_file (fname); + error (1, retcode == -1 ? errno : 0, "Message verification failed"); } @@ -497,7 +507,7 @@ do_verify (messagep, repository) if (fclose (fp) < 0) error (0, errno, "warning: cannot close %s", fname); - /* Close and delete the temp file */ + /* Delete the temp file */ unlink_file (fname); free (fname); diff --git a/contrib/cvs/src/main.c b/contrib/cvs/src/main.c index c11eb40..b2d1677 100644 --- a/contrib/cvs/src/main.c +++ b/contrib/cvs/src/main.c @@ -3,7 +3,7 @@ * 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 1.4 kit. + * as specified in the README file that comes with the CVS source distribution. * * This is the main C driver for the CVS system. * @@ -33,15 +33,15 @@ char *command_name; char hostname[MAXHOSTNAMELEN]; -int use_editor = TRUE; -int use_cvsrc = TRUE; +int use_editor = 1; +int use_cvsrc = 1; int cvswrite = !CVSREAD_DFLT; -int really_quiet = FALSE; -int quiet = FALSE; -int trace = FALSE; -int noexec = FALSE; -int readonlyfs = FALSE; -int logoff = FALSE; +int really_quiet = 0; +int quiet = 0; +int trace = 0; +int noexec = 0; +int readonlyfs = 0; +int logoff = 0; mode_t cvsumask = UMASK_DFLT; char *CurDir; @@ -49,7 +49,6 @@ char *CurDir; /* * Defaults, for the environment variables that are not set */ -char *Rcsbin = RCSBIN_DFLT; char *Tmpdir = TMPDIR_DFLT; char *Editor = EDITOR_DFLT; @@ -125,33 +124,47 @@ static const struct cmd static const char *const usg[] = { - "Usage: %s [cvs-options] command [command-options] [files...]\n", - " Where 'cvs-options' are:\n", - " -H Displays Usage information for command\n", - " -Q Cause CVS to be really quiet.\n", - " -q Cause CVS to be somewhat quiet.\n", - " -r Make checked-out files read-only\n", - " -w Make checked-out files read-write (default)\n", - " -l Turn History logging off\n", - " -n Do not execute anything that will change the disk\n", - " -R Assume repository is read-only, such as CDROM\n", - " -t Show trace of program execution -- Try with -n\n", - " -v CVS version and copyright\n", - " -b bindir Find RCS programs in 'bindir'\n", - " -T tmpdir Use 'tmpdir' for temporary files\n", - " -e editor Use 'editor' for editing log information\n", - " -d CVS_root Overrides $CVSROOT as the root of the CVS tree\n", - " -f Do not use the ~/.cvsrc file\n", -#ifdef CLIENT_SUPPORT - " -z # Use compression level '#' for net traffic.\n", -#ifdef ENCRYPTION - " -x Encrypt all net traffic.\n", -#endif -#endif - " -s VAR=VAL Set CVS user variable.\n", + /* CVS usage messages never have followed the GNU convention of + putting metavariables in uppercase. I don't know whether that + is a good convention or not, but if it changes it would have to + change in all the usage messages. For now, they consistently + use lowercase, as far as I know. Puncutation is pretty funky, + though. Sometimes they use none, as here. Sometimes they use + single quotes (not the TeX-ish `' stuff), as in --help-options. + Sometimes they use double quotes, as in cvs -H add. + + Most (not all) of the usage messages seem to have periods at + the end of each line. I haven't tried to duplicate this style + in --help as it is a rather different format from the rest. */ + + "Usage: %s [cvs-options] command [command-options-and-arguments]\n", + " where cvs-options are -q, -n, etc.\n", + " (specify --help-options for a list of options)\n", + " where command is add, admin, etc.\n", + " (specify --help-commands for a list of commands\n", + " or --help-synonyms for a list of command synonyms)\n", + " where command-options-and-arguments depend on the specific command\n", + " (specify -H followed by a command name for command-specific help)\n", + " Specify --help to receive this message\n", "\n", - " and where 'command' is: add, admin, etc. (use the --help-commands\n", - " option for a list of commands)\n", + + /* Some people think that a bug-reporting address should go here. IMHO, + the web sites are better because anything else is very likely to go + obsolete in the years between a release and when someone might be + reading this help. Besides, we could never adequately discuss + bug reporting in a concise enough way to put in a help message. */ + + /* I was going to put this at the top, but usage() wants the %s to + be in the first line. */ + "The Concurrent Versions System (CVS) is a tool for version control.\n", + /* I really don't think I want to try to define "version control" + in one line. I'm not sure one can get more concise than the + paragraph in ../cvs.spec without assuming the reader knows what + version control means. */ + + "For CVS updates and additional information, see\n", + " Cyclic Software at http://www.cyclic.com/ or\n", + " Pascal Molli's CVS site at http://www.loria.fr/~molli/cvs-index.html\n", NULL, }; @@ -185,17 +198,48 @@ static const char *const cmd_usage[] = " update Bring work tree in sync with repository\n", " watch Set watches\n", " watchers See who is watching a file\n", - "(Use the --help-synonyms option for a list of alternate command names)\n", + "(Specify the --help option for a list of other help options)\n", NULL, }; +static const char *const opt_usage[] = +{ + "CVS global options (specified before the command name) are:\n", + " -H Displays usage information for command.\n", + " -Q Cause CVS to be really quiet.\n", + " -q Cause CVS to be somewhat quiet.\n", + " -r Make checked-out files read-only.\n", + " -w Make checked-out files read-write (default).\n", + " -l Turn history logging off.\n", + " -n Do not execute anything that will change the disk.\n", + " -t Show trace of program execution -- try with -n.\n", + " -R Assume repository is read-only, such as CDROM\n", + " -v CVS version and copyright.\n", + " -b bindir Find RCS programs in 'bindir'.\n", + " -T tmpdir Use 'tmpdir' for temporary files.\n", + " -e editor Use 'editor' for editing log information.\n", + " -d CVS_root Overrides $CVSROOT as the root of the CVS tree.\n", + " -f Do not use the ~/.cvsrc file.\n", +#ifdef CLIENT_SUPPORT + " -z # Use compression level '#' for net traffic.\n", +#ifdef ENCRYPTION + " -x Encrypt all net traffic.\n", +#endif + " -a Authenticate all net traffic.\n", +#endif + " -s VAR=VAL Set CVS user variable.\n", + "(Specify the --help option for a list of other help options)\n", + NULL +}; + static const char * const* cmd_synonyms () { char ** synonyms; char ** line; const struct cmd *c = &cmds[0]; - int numcmds = 2; /* two more for title and end */ + /* Three more for title, "specify --help" line, and NULL. */ + int numcmds = 3; while (c->fullname != NULL) { @@ -220,6 +264,7 @@ cmd_synonyms () line++; } } + *line++ = "(Specify the --help option for a list of other help options)\n"; *line = NULL; return (const char * const*) synonyms; /* will never be freed */ @@ -330,11 +375,10 @@ main (argc, argv) char *cp, *end; const struct cmd *cm; int c, err = 0; - int rcsbin_update_env, tmpdir_update_env, cvs_update_env; + int tmpdir_update_env, cvs_update_env; int free_CVSroot = 0; int free_Editor = 0; int free_Tmpdir = 0; - int free_Rcsbin = 0; int help = 0; /* Has the user asked for help? This lets us support the `cvs -H cmd' @@ -345,6 +389,7 @@ main (argc, argv) {"version", 0, NULL, 'v'}, {"help-commands", 0, NULL, 1}, {"help-synonyms", 0, NULL, 2}, + {"help-options", 0, NULL, 4}, {"allow-root", required_argument, NULL, 3}, {0, 0, 0, 0} }; @@ -382,12 +427,6 @@ main (argc, argv) * they can be overridden by command line arguments */ cvs_update_env = 0; - rcsbin_update_env = *Rcsbin; /* RCSBIN_DFLT must be set */ - if ((cp = getenv (RCSBIN_ENV)) != NULL) - { - Rcsbin = cp; - rcsbin_update_env = 0; /* it's already there */ - } tmpdir_update_env = *Tmpdir; /* TMPDIR_DFLT must be set */ if ((cp = getenv (TMPDIR_ENV)) != NULL) { @@ -406,10 +445,10 @@ main (argc, argv) cvs_update_env = 0; /* it's already there */ } if (getenv (CVSREAD_ENV) != NULL) - cvswrite = FALSE; + cvswrite = 0; if (getenv (CVSREADONLYFS_ENV) != NULL) { - readonlyfs = TRUE; - logoff = TRUE; + readonlyfs = 1; + logoff = 1; } /* Set this to 0 to force getopt initialization. getopt() sets @@ -427,7 +466,7 @@ main (argc, argv) != EOF) { if (c == 'f') - use_cvsrc = FALSE; + use_cvsrc = 0; } /* @@ -440,7 +479,7 @@ main (argc, argv) opterr = 1; while ((c = getopt_long - (argc, argv, "+QqrwtnRlvb:T:e:d:Hfz:s:x", long_options, &option_index)) + (argc, argv, "+QqrwtnRlvb:T:e:d:Hfz:s:xa", long_options, &option_index)) != EOF) { switch (c) @@ -453,51 +492,63 @@ main (argc, argv) /* --help-synonyms */ usage (cmd_synonyms()); break; + case 4: + /* --help-options */ + usage (opt_usage); + break; case 3: /* --allow-root */ root_allow_add (optarg); break; case 'Q': - really_quiet = TRUE; + really_quiet = 1; /* FALL THROUGH */ case 'q': - quiet = TRUE; + quiet = 1; break; case 'r': - cvswrite = FALSE; + cvswrite = 0; break; case 'w': - cvswrite = TRUE; + cvswrite = 1; break; case 't': - trace = TRUE; + trace = 1; break; case 'R': - readonlyfs = TRUE; - logoff = TRUE; + readonlyfs = 1; + logoff = 1; break; case 'n': - noexec = TRUE; + noexec = 1; case 'l': /* Fall through */ - logoff = TRUE; + logoff = 1; break; case 'v': + /* Having the year here is a good idea, so people have + some idea of how long ago their version of CVS was + released. */ (void) fputs (version_string, stdout); (void) fputs (config_string, stdout); (void) fputs ("\n", stdout); - (void) fputs ("Copyright (c) 1993-1994 Brian Berliner\n", stdout); - (void) fputs ("Copyright (c) 1993-1994 david d `zoo' zuhn\n", stdout); - (void) fputs ("Copyright (c) 1992, Brian Berliner and Jeff Polk\n", stdout); - (void) fputs ("Copyright (c) 1989-1992, Brian Berliner\n", stdout); + (void) fputs ("\ +Copyright (c) 1989-1997 Brian Berliner, david d `zoo' zuhn, \n\ + Jeff Polk, and other authors\n", stdout); (void) fputs ("\n", stdout); (void) fputs ("CVS may be copied only under the terms of the GNU General Public License,\n", stdout); (void) fputs ("a copy of which can be found with the CVS distribution kit.\n", stdout); + (void) fputs ("\n", stdout); + + (void) fputs ("Specify the --help option for further information about CVS\n", stdout); + exit (0); break; case 'b': - Rcsbin = xstrdup (optarg); - free_Rcsbin = 1; - rcsbin_update_env = 1; /* need to update environment */ + /* This option used to specify the directory for RCS + executables. But since we don't run them any more, + this is a noop. Silently ignore it so that .cvsrc + and scripts and inetd.conf and such can work with + either new or old CVS. */ break; case 'T': Tmpdir = xstrdup (optarg); @@ -517,7 +568,7 @@ main (argc, argv) help = 1; break; case 'f': - use_cvsrc = FALSE; /* unnecessary, since we've done it above */ + use_cvsrc = 0; /* unnecessary, since we've done it above */ break; case 'z': #ifdef CLIENT_SUPPORT @@ -542,6 +593,15 @@ main (argc, argv) If no ENCRYPTION, we still accept -x, but issue an error if we are being run as a client. */ break; + case 'a': +#ifdef CLIENT_SUPPORT + cvsauthenticate = 1; +#endif + /* If no CLIENT_SUPPORT, ignore -a, so that users can + have it in their .cvsrc and not cause any trouble. + We will issue an error later if stream + authentication is not supported. */ + break; case '?': default: usage (usg); @@ -572,6 +632,9 @@ main (argc, argv) else command_name = cm->fullname; /* Global pointer for later use */ + /* This should probably remain a warning, rather than an error, + for quite a while. For one thing the version of VC distributed + with GNU emacs 19.34 invokes 'cvs rlog' instead of 'cvs log'. */ if (strcmp (argv[0], "rlog") == 0) { error (0, 0, "warning: the rlog command is deprecated"); @@ -615,7 +678,7 @@ main (argc, argv) #endif /* HAVE_KERBEROS */ -#if defined(AUTH_SERVER_SUPPORT) && defined(SERVER_SUPPORT) +#if (defined(AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)) && defined(SERVER_SUPPORT) if (strcmp (command_name, "pserver") == 0) { /* The reason that --allow-root is not a command option @@ -632,15 +695,16 @@ main (argc, argv) /* Pretend we were invoked as a plain server. */ command_name = "server"; } -#endif /* AUTH_SERVER_SUPPORT && SERVER_SUPPORT */ +#endif /* (AUTH_SERVER_SUPPORT || HAVE_GSSAPI) && SERVER_SUPPORT */ +#ifdef SERVER_SUPPORT + server_active = strcmp (command_name, "server") == 0; /* Fiddling with CVSROOT doesn't make sense if we're running in server mode, since the client will send the repository directory after the connection is made. */ -#ifdef SERVER_SUPPORT - if (strcmp (command_name, "server") != 0) + if (!server_active) #endif { char *CVSADM_Root; @@ -788,7 +852,7 @@ main (argc, argv) it is worth the trouble. */ #ifdef SERVER_SUPPORT - if (strcmp (command_name, "server") == 0) + if (server_active) CurDir = xstrdup ("<remote>"); else #endif @@ -802,16 +866,6 @@ main (argc, argv) Tmpdir = "/tmp"; #ifdef HAVE_PUTENV - /* Now, see if we should update the environment with the - Rcsbin value */ - if (rcsbin_update_env) - { - char *env; - env = xmalloc (strlen (RCSBIN_ENV) + strlen (Rcsbin) + 1 + 1); - (void) sprintf (env, "%s=%s", RCSBIN_ENV, Rcsbin); - (void) putenv (env); - /* do not free env, as putenv has control of it */ - } if (tmpdir_update_env) { char *env; @@ -828,24 +882,6 @@ main (argc, argv) } #endif - /* - * If Rcsbin is set to something, make sure it is terminated with - * a slash character. If not, add one. - */ - if (*Rcsbin) - { - int len = strlen (Rcsbin); - char *rcsbin; - - if (Rcsbin[len - 1] != '/') - { - rcsbin = Rcsbin; - Rcsbin = xmalloc (len + 2); /* one for '/', one for NULL */ - (void) strcpy (Rcsbin, rcsbin); - (void) strcat (Rcsbin, "/"); - } - } - #ifndef DONT_USE_SIGNALS /* make sure we clean up on error */ #ifdef SIGHUP @@ -883,6 +919,28 @@ main (argc, argv) if (use_cvsrc) read_cvsrc (&argc, &argv, command_name); + /* Parse the CVSROOT/config file, but only for local. For the + server, we parse it after we know $CVSROOT. For the + client, it doesn't get parsed at all, obviously. The + presence of the parse_config call here is not mean to + predetermine whether CVSROOT/config overrides things from + read_cvsrc and other such places or vice versa. That sort + of thing probably needs more thought. */ + if (1 +#ifdef SERVER_SUPPORT + && !server_active +#endif +#ifdef CLIENT_SUPPORT + && !client_active +#endif + ) + { + /* If there was an error parsing the config file, parse_config + already printed an error. We keep going. Why? Because + if we didn't, then there would be no way to check in a new + CVSROOT/config file to fix the broken one! */ + parse_config (CVSroot_directory); + } } /* end of stuff that gets done if the user DOESN'T ask for help */ err = (*(cm->func)) (argc, argv); @@ -891,7 +949,10 @@ main (argc, argv) { /* Update the CVS/Root file. We might want to do this in all directories that we recurse into, but currently we - don't. */ + don't. Note that if there is an error writing the file, + we give an error/warning. This is so if users try to rewrite + CVS/Root with the -d option (a documented feature), they will + either succeed, or be told why it didn't work. */ Create_Root (NULL, CVSroot); } @@ -904,8 +965,6 @@ main (argc, argv) free (Editor); if (free_Tmpdir) free (Tmpdir); - if (free_Rcsbin) - free (Rcsbin); root_allow_free (); #ifdef SYSTEM_CLEANUP @@ -917,6 +976,8 @@ main (argc, argv) /* This is exit rather than return because apparently that keeps some tools which check for memory leaks happier. */ exit (err ? EXIT_FAILURE : 0); + /* Keep picky/stupid compilers (e.g. Visual C++ 5.0) happy. */ + return 0; } char * @@ -931,11 +992,14 @@ Make_Date (rawdate) unixtime = get_date (rawdate, (struct timeb *) NULL); if (unixtime == (time_t) - 1) error (1, 0, "Can't parse date/time: %s", rawdate); -#ifdef HAVE_RCS5 + ftm = gmtime (&unixtime); -#else - ftm = localtime (&unixtime); -#endif + if (ftm == NULL) + /* This is a system, like VMS, where the system clock is in local + time. Hopefully using localtime here matches the "zero timezone" + hack I added to get_date. */ + ftm = localtime (&unixtime); + (void) sprintf (date, DATEFORM, ftm->tm_year + (ftm->tm_year < 100 ? 0 : 1900), ftm->tm_mon + 1, ftm->tm_mday, ftm->tm_hour, diff --git a/contrib/cvs/src/mkmodules.c b/contrib/cvs/src/mkmodules.c index ec5d770..3c63433 100644 --- a/contrib/cvs/src/mkmodules.c +++ b/contrib/cvs/src/mkmodules.c @@ -275,6 +275,12 @@ static const char *const modules_contents[] = { NULL }; +static const char *const config_contents[] = { + "# Set this to \"no\" if pserver shouldn't check system users/passwords\n", + "#SystemAuth=no\n", + NULL +}; + static const struct admin_file filelist[] = { {CVSROOTADM_LOGINFO, "no logging of 'cvs commit' messages is done without a %s file", @@ -316,14 +322,27 @@ static const struct admin_file filelist[] = { {CVSROOTADM_WRITERS, "a %s file specifies read/write users", NULL}, - /* Some have suggested listing CVSROOTADM_PASSWD here too. The - security implications of transmitting hashed passwords over the - net are no worse than transmitting cleartext passwords which pserver - does, so this isn't a problem. But I'm worried about the implications - of storing old passwords--if someone used a password in the past - they might be using it elsewhere, using a similar password, etc, - and so it doesn't seem to me like we should be saving old passwords, - even hashed. */ + + /* Some have suggested listing CVSROOTADM_PASSWD here too. This + would mean that CVS commands which operate on the + CVSROOTADM_PASSWD file would transmit hashed passwords over the + net. This might seem to be no big deal, as pserver normally + transmits cleartext passwords, but the difference is that + CVSROOTADM_PASSWD contains *all* passwords, not just the ones + currently being used. For example, it could be too easy to + accidentally give someone readonly access to CVSROOTADM_PASSWD + (e.g. via anonymous CVS or cvsweb), and then if there are any + guessable passwords for read/write access (usually there will be) + they get read/write access. + + Another worry is the implications of storing old passwords--if + someone used a password in the past they might be using it + elsewhere, using a similar password, etc, and so saving old + passwords, even hashed, is probably not a good idea. */ + + {CVSROOTADM_CONFIG, + "a %s file configures various behaviors", + config_contents}, {NULL, NULL} }; @@ -694,6 +713,7 @@ rename_rcsfile (temp, real) const char *const init_usage[] = { "Usage: %s %s\n", + "(Specify the --help global option for a list of other help options)\n", NULL }; @@ -785,8 +805,12 @@ init (argc, argv) "initial checkin" but I fail to see the point as we know what file it is from the name. */ retcode = add_rcs_file ("initial checkin", info_v, - fileptr->filename, "1.1", NULL, NULL, - 0, NULL, NULL); + fileptr->filename, "1.1", NULL, + + /* No vendor branch. */ + NULL, NULL, 0, NULL, + + NULL, 0, NULL); if (retcode != 0) /* add_rcs_file already printed an error message. */ err = 1; diff --git a/contrib/cvs/src/recurse.c b/contrib/cvs/src/recurse.c index a32f0da..25b5b71 100644 --- a/contrib/cvs/src/recurse.c +++ b/contrib/cvs/src/recurse.c @@ -2,7 +2,7 @@ * Copyright (c) 1992, Brian Berliner and Jeff Polk * * You may distribute under the terms of the GNU General Public License as - * specified in the README file that comes with the CVS 1.4 kit. + * specified in the README file that comes with the CVS source distribution. * * General recursion handler * @@ -157,7 +157,25 @@ start_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc, callerdat, * called with the list of sub-dirs of the current dir as args */ if ((which & W_LOCAL) && !isdir (CVSADM)) + { dirlist = Find_Directories ((char *) NULL, W_LOCAL, (List *) NULL); + /* If there are no sub-directories, there is a certain logic in + favor of doing nothing, but in fact probably the user is just + confused about what directory they are in, or whether they + cvs add'd a new directory. In the case of at least one + sub-directory, at least when we recurse into them we + notice (hopefully) whether they are under CVS control. */ + if (list_isempty (dirlist)) + { + if (update_dir[0] == '\0') + error (0, 0, "in directory .:"); + else + error (0, 0, "in directory %s:", update_dir); + error (1, 0, + "there is no version here; run '%s checkout' first", + program_name); + } + } else addlist (&dirlist, "."); @@ -637,7 +655,7 @@ but CVS uses %s for its own purposes; skipping %s directory", else { newrepos = xmalloc (strlen (repository) + strlen (dir) + 5); - (void) sprintf (newrepos, "%s/%s", repository, dir); + sprintf (newrepos, "%s/%s", repository, dir); } } else @@ -651,10 +669,78 @@ but CVS uses %s for its own purposes; skipping %s directory", newrepos = xstrdup (repository); } + /* Check to see that the CVSADM directory, if it exists, seems to be + well-formed. It can be missing files if the user hit ^C in the + middle of a previous run. We want to (a) make this a nonfatal + error, and (b) make sure we print which directory has the + problem. + + Do this before the direntproc, so that (1) the direntproc + doesn't have to guess/deduce whether we will skip the directory + (e.g. send_dirent_proc and whether to send the directory), and + (2) so that the warm fuzzy doesn't get printed if we skip the + directory. */ + if (frame->which & W_LOCAL) + { + char *cvsadmdir; + + cvsadmdir = xmalloc (strlen (dir) + + sizeof (CVSADM_REP) + + sizeof (CVSADM_ENT) + + 80); + + strcpy (cvsadmdir, dir); + strcat (cvsadmdir, "/"); + strcat (cvsadmdir, CVSADM); + if (isdir (cvsadmdir)) + { + strcpy (cvsadmdir, dir); + strcat (cvsadmdir, "/"); + strcat (cvsadmdir, CVSADM_REP); + if (!isfile (cvsadmdir)) + { + /* Some commands like update may have printed "? foo" but + if we were planning to recurse, and don't on account of + CVS/Repository, we want to say why. */ + error (0, 0, "ignoring %s (%s missing)", update_dir, + CVSADM_REP); + dir_return = R_SKIP_ALL; + } + + /* Likewise for CVS/Entries. */ + if (dir_return != R_SKIP_ALL) + { + strcpy (cvsadmdir, dir); + strcat (cvsadmdir, "/"); + strcat (cvsadmdir, CVSADM_ENT); + if (!isfile (cvsadmdir)) + { + /* Some commands like update may have printed "? foo" but + if we were planning to recurse, and don't on account of + CVS/Repository, we want to say why. */ + error (0, 0, "ignoring %s (%s missing)", update_dir, + CVSADM_ENT); + dir_return = R_SKIP_ALL; + } + } + } + free (cvsadmdir); + } + /* call-back dir entry proc (if any) */ - if (frame->direntproc != NULL) + if (dir_return == R_SKIP_ALL) + ; + else if (frame->direntproc != NULL) dir_return = frame->direntproc (frame->callerdat, dir, newrepos, update_dir, frent->entries); + else + { + /* Generic behavior. I don't see a reason to make the caller specify + a direntproc just to get this. */ + if ((frame->which & W_LOCAL) && !isdir (dir)) + dir_return = R_SKIP_ALL; + } + free (newrepos); /* only process the dir if the return code was 0 */ diff --git a/contrib/cvs/src/server.c b/contrib/cvs/src/server.c index ea40a4c..509be56 100644 --- a/contrib/cvs/src/server.c +++ b/contrib/cvs/src/server.c @@ -22,16 +22,16 @@ #include <winsock.h> #endif -#if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_KERBEROS) +#if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_KERBEROS) || defined (HAVE_GSSAPI) #include <sys/socket.h> #endif #ifdef HAVE_KERBEROS -#include <netinet/in.h> -#include <krb.h> -#ifndef HAVE_KRB_GET_ERR_TEXT -#define krb_get_err_text(status) krb_err_txt[status] -#endif +# include <netinet/in.h> +# include <krb.h> +# ifndef HAVE_KRB_GET_ERR_TEXT +# define krb_get_err_text(status) krb_err_txt[status] +# endif /* Information we need if we are going to use Kerberos encryption. */ static C_Block kblock; @@ -39,6 +39,34 @@ static Key_schedule sched; #endif +#ifdef HAVE_GSSAPI + +#include <netdb.h> +#include <gssapi/gssapi.h> +#include <gssapi/gssapi_generic.h> + +/* We use Kerberos 5 routines to map the GSSAPI credential to a user + name. */ +#include <krb5.h> + +/* We need this to wrap data. */ +static gss_ctx_id_t gcontext; + +static void gserver_authenticate_connection PROTO((void)); + +/* Whether we are already wrapping GSSAPI communication. */ +static int cvs_gssapi_wrapping; + +# ifdef ENCRYPTION +/* Whether to encrypt GSSAPI communication. We use a global variable + like this because we use the same buffer type (gssapi_wrap) to + handle both authentication and encryption, and we don't want + multiple instances of that buffer in the communication stream. */ +int cvs_gssapi_encrypt; +# endif + +#endif + /* for select */ #include <sys/types.h> #ifdef HAVE_SYS_BSDTYPES_H @@ -76,12 +104,12 @@ static Key_schedule sched; #ifdef HAVE_GETSPNAM #include <shadow.h> #endif +#endif /* AUTH_SERVER_SUPPORT */ + /* For initgroups(). */ #if HAVE_INITGROUPS #include <grp.h> #endif /* HAVE_INITGROUPS */ -#endif /* AUTH_SERVER_SUPPORT */ - #ifdef AUTH_SERVER_SUPPORT @@ -95,6 +123,10 @@ char *CVS_Username = NULL; later CVS protocol. Exported because root.c also uses. */ char *Pserver_Repos = NULL; +/* Should we check for system usernames/passwords? Can be changed by + CVSROOT/config. */ +int system_auth = 1; + #endif /* AUTH_SERVER_SUPPORT */ @@ -329,7 +361,7 @@ mkdir_p (dir) { strncpy (q, dir, p - dir); q[p - dir] = '\0'; - if (CVS_MKDIR (q, 0777) < 0) + if (q[p - dir - 1] != '/' && CVS_MKDIR (q, 0777) < 0) { int saved_errno = errno; @@ -437,6 +469,8 @@ alloc_pending (size) return 1; } +static void serve_is_modified PROTO ((char *)); + static int supported_response PROTO ((char *)); static int @@ -526,8 +560,30 @@ serve_root (arg) "E Root %s must be an absolute pathname", arg); return; } + + /* Sending "Root" twice is illegal. It would also be nice to + check for the other case, in which there is no Root request + prior to a request which requires one. + + The other way to handle a duplicate Root requests would be as a + request to clear out all state and start over as if it was a + new connection. Doing this would cause interoperability + headaches, so it should be a different request, if there is + any reason why such a feature is needed. */ + if (CVSroot_directory != NULL) + { + if (alloc_pending (80 + strlen (arg))) + sprintf (pending_error_text, + "E Protocol error: Duplicate Root request, for %s", arg); + return; + } + set_local_cvsroot (arg); + /* For pserver, this will already have happened, and the call will do + nothing. But for rsh, we need to do it now. */ + parse_config (CVSroot_directory); + path = xmalloc (strlen (CVSroot_directory) + sizeof (CVSROOTADM) + sizeof (CVSROOTADM_HISTORY) @@ -630,6 +686,7 @@ dirswitch (dir, repos) int status; FILE *f; char *b; + size_t dir_len; server_write_entries (); @@ -638,20 +695,22 @@ dirswitch (dir, repos) if (dir_name != NULL) free (dir_name); + dir_len = strlen (dir); + /* Check for a trailing '/'. This is not ISDIRSEP because \ in the protocol is an ordinary character, not a directory separator (of course, it is perhaps unwise to use it in directory names, but that is another issue). */ - if (strlen (dir) > 0 - && dir[strlen (dir) - 1] == '/') + if (dir_len > 0 + && dir[dir_len - 1] == '/') { - if (alloc_pending (80 + strlen (dir))) + if (alloc_pending (80 + dir_len)) sprintf (pending_error_text, - "E protocol error: illegal directory syntax in %s", dir); + "E protocol error: invalid directory syntax in %s", dir); return; } - dir_name = malloc (strlen (server_temp_dir) + strlen (dir) + 40); + dir_name = malloc (strlen (server_temp_dir) + dir_len + 40); if (dir_name == NULL) { pending_error = ENOMEM; @@ -672,6 +731,12 @@ dirswitch (dir, repos) return; } + /* Note that this call to Subdir_Register will be a noop if the parent + directory does not yet exist (for example, if the client sends + "Directory foo" followed by "Directory .", then the subdirectory does + not get registered, but if the client sends "Directory ." followed + by "Directory foo", then the subdirectory does get registered. + This seems pretty fishy, but maybe it is the way it needs to work. */ b = strrchr (dir_name, '/'); *b = '\0'; Subdir_Register ((List *) NULL, dir_name, b + 1); @@ -775,6 +840,24 @@ serve_directory (arg) status = buf_read_line (buf_from_net, &repos, (int *) NULL); if (status == 0) { + /* I think isabsolute (repos) should always be true, and that + any RELATIVE_REPOS stuff should only be in CVS/Repository + files, not the protocol (for compatibility), but I'm putting + in the in isabsolute check just in case. */ + if (isabsolute (repos) + && strncmp (CVSroot_directory, + repos, + strlen (CVSroot_directory)) != 0) + { + if (alloc_pending (strlen (CVSroot_directory) + + strlen (repos) + + 80)) + sprintf (pending_error_text, "\ +E protocol error: directory '%s' not within root '%s'", + repos, CVSroot_directory); + return; + } + dirswitch (arg, repos); free (repos); } @@ -1005,6 +1088,11 @@ receive_file (size, file, gzipped) } } +/* Kopt for the next file sent in Modified or Is-modified. */ +static char *kopt; + +static void serve_modified PROTO ((char *)); + static void serve_modified (arg) char *arg; @@ -1119,6 +1207,13 @@ serve_modified (arg) return; } } + + /* Make sure that the Entries indicate the right kopt. We probably + could do this even in the non-kopt case and, I think, save a stat() + call in time_stamp_server. But for conservatism I'm leaving the + non-kopt case alone. */ + if (kopt != NULL) + serve_is_modified (arg); } @@ -1175,8 +1270,6 @@ serve_unchanged (arg) } } -static void serve_is_modified PROTO ((char *)); - static void serve_is_modified (arg) char *arg; @@ -1213,6 +1306,16 @@ serve_is_modified (arg) } *timefield = 'M'; } + if (kopt != NULL) + { + if (alloc_pending (strlen (name) + 80)) + sprintf (pending_error_text, + "E protocol error: both Kopt and Entry for %s", + arg); + free (kopt); + kopt = NULL; + return; + } found = 1; break; } @@ -1221,22 +1324,36 @@ serve_is_modified (arg) { /* We got Is-modified but no Entry. Add a dummy entry. The "D" timestamp is what makes it a dummy. */ - struct an_entry *p; p = (struct an_entry *) malloc (sizeof (struct an_entry)); if (p == NULL) { pending_error = ENOMEM; return; } - p->entry = xmalloc (strlen (arg) + 80); + p->entry = malloc (strlen (arg) + 80); + if (p->entry == NULL) + { + pending_error = ENOMEM; + free (p); + return; + } strcpy (p->entry, "/"); strcat (p->entry, arg); - strcat (p->entry, "//D//"); + strcat (p->entry, "//D/"); + if (kopt != NULL) + { + strcat (p->entry, kopt); + free (kopt); + kopt = NULL; + } + strcat (p->entry, "/"); p->next = entries; entries = p; } } +static void serve_entry PROTO ((char *)); + static void serve_entry (arg) char *arg; @@ -1263,6 +1380,45 @@ serve_entry (arg) entries = p; } +static void serve_kopt PROTO ((char *)); + +static void +serve_kopt (arg) + char *arg; +{ + if (error_pending ()) + return; + + if (kopt != NULL) + { + if (alloc_pending (80 + strlen (arg))) + sprintf (pending_error_text, + "E protocol error: duplicate Kopt request: %s", arg); + return; + } + + /* Do some sanity checks. In particular, that it is not too long. + This lets the rest of the code not worry so much about buffer + overrun attacks. Probably should call RCS_check_kflag here, + but that would mean changing RCS_check_kflag to handle errors + other than via exit(), fprintf(), and such. */ + if (strlen (arg) > 10) + { + if (alloc_pending (80 + strlen (arg))) + sprintf (pending_error_text, + "E protocol error: invalid Kopt request: %s", arg); + return; + } + + kopt = malloc (strlen (arg) + 1); + if (kopt == NULL) + { + pending_error = ENOMEM; + return; + } + strcpy (kopt, arg); +} + static void server_write_entries () { @@ -1610,6 +1766,7 @@ serve_set (arg) } #ifdef ENCRYPTION + #ifdef HAVE_KERBEROS static void @@ -1627,7 +1784,68 @@ serve_kerberos_encrypt (arg) } #endif /* HAVE_KERBEROS */ + +#ifdef HAVE_GSSAPI + +static void +serve_gssapi_encrypt (arg) + char *arg; +{ + if (cvs_gssapi_wrapping) + { + /* We're already using a gssapi_wrap buffer for stream + authentication. Flush everything we've output so far, and + turn on encryption for future data. On the input side, we + should only have unwrapped as far as the Gssapi-encrypt + command, so future unwrapping will become encrypted. */ + buf_flush (buf_to_net, 1); + cvs_gssapi_encrypt = 1; + return; + } + + /* All future communication with the client will be encrypted. */ + + cvs_gssapi_encrypt = 1; + + buf_to_net = cvs_gssapi_wrap_buffer_initialize (buf_to_net, 0, + gcontext, + buf_to_net->memory_error); + buf_from_net = cvs_gssapi_wrap_buffer_initialize (buf_from_net, 1, + gcontext, + buf_from_net->memory_error); + + cvs_gssapi_wrapping = 1; +} + +#endif /* HAVE_GSSAPI */ + #endif /* ENCRYPTION */ + +#ifdef HAVE_GSSAPI + +static void +serve_gssapi_authenticate (arg) + char *arg; +{ + if (cvs_gssapi_wrapping) + { + /* We're already using a gssapi_wrap buffer for encryption. + That includes authentication, so we don't have to do + anything further. */ + return; + } + + buf_to_net = cvs_gssapi_wrap_buffer_initialize (buf_to_net, 0, + gcontext, + buf_to_net->memory_error); + buf_from_net = cvs_gssapi_wrap_buffer_initialize (buf_from_net, 1, + gcontext, + buf_from_net->memory_error); + + cvs_gssapi_wrapping = 1; +} + +#endif /* HAVE_GSSAPI */ #ifdef SERVER_FLOWCONTROL /* The maximum we'll queue to the remote client before blocking. */ @@ -1815,10 +2033,18 @@ check_command_legal_p (cmd_name) CVSROOTADM, CVSROOTADM_READERS); fp = fopen (fname, "r"); - free (fname); if (fp == NULL) - goto do_writers; + { + if (!existence_error (errno)) + { + /* Need to deny access, so that attackers can't fool + us with some sort of denial of service attack. */ + error (0, errno, "cannot open %s", fname); + free (fname); + return 0; + } + } else /* successfully opened readers file */ { while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0) @@ -1836,16 +2062,19 @@ check_command_legal_p (cmd_name) if (strcmp (linebuf, CVS_Username) == 0) goto handle_illegal; } + if (num_red < 0 && !feof (fp)) + error (0, errno, "cannot read %s", fname); /* If not listed specifically as a reader, then this user has write access by default unless writers are also specified in a file . */ - fclose (fp); - goto do_writers; + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", fname); } + free (fname); + + /* Now check the writers file. */ - do_writers: - flen = strlen (CVSroot_directory) + strlen (CVSROOTADM) + strlen (CVSROOTADM_WRITERS) @@ -1856,19 +2085,28 @@ check_command_legal_p (cmd_name) CVSROOTADM, CVSROOTADM_WRITERS); fp = fopen (fname, "r"); - free (fname); if (fp == NULL) { - /* writers file does not exist, so everyone is a writer, - by default */ if (linebuf) free (linebuf); - return 1; + if (existence_error (errno)) + { + /* Writers file does not exist, so everyone is a writer, + by default. */ + free (fname); + return 1; + } + else + { + /* Need to deny access, so that attackers can't fool + us with some sort of denial of service attack. */ + error (0, errno, "cannot read %s", fname); + free (fname); + return 0; + } } - /* else */ - found_it = 0; while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0) { @@ -1882,20 +2120,26 @@ check_command_legal_p (cmd_name) break; } } + if (num_red < 0 && !feof (fp)) + error (0, errno, "cannot read %s", fname); if (found_it) { - fclose (fp); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", fname); if (linebuf) free (linebuf); + free (fname); return 1; } else /* writers file exists, but this user not listed in it */ { handle_illegal: - fclose (fp); + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", fname); if (linebuf) free (linebuf); + free (fname); return 0; } } @@ -2814,7 +3058,7 @@ static void serve_log (arg) char *arg; { - do_cvs_command ("cvslog", cvslog); + do_cvs_command ("log", cvslog); } static void @@ -3086,6 +3330,8 @@ server_modtime (finfo, vers_ts) {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + assert (vers_ts->vn_rcs != NULL); + if (!supported_response ("Mod-time")) return; @@ -3118,7 +3364,18 @@ server_updated (finfo, vers, updated, file_info, checksum) unsigned char *checksum; { if (noexec) + { + /* Hmm, maybe if we did the same thing for entries_file, we + could get rid of the kludges in server_register and + server_scratch which refrain from warning if both + Scratch_Entry and Register get called. Maybe. */ + if (scratched_file) + { + free (scratched_file); + scratched_file = NULL; + } return; + } if (entries_line != NULL && scratched_file == NULL) { @@ -3560,6 +3817,11 @@ expand_proc (pargc, argv, where, mwhere, mfile, shorten, if (mwhere != NULL) { buf_output0 (buf_to_net, "Module-expansion "); + if (server_dir != NULL) + { + buf_output0 (buf_to_net, server_dir); + buf_output0 (buf_to_net, "/"); + } buf_output0 (buf_to_net, mwhere); if (mfile != NULL) { @@ -3575,6 +3837,11 @@ expand_proc (pargc, argv, where, mwhere, mfile, shorten, if (*pargc == 1) { buf_output0 (buf_to_net, "Module-expansion "); + if (server_dir != NULL) + { + buf_output0 (buf_to_net, server_dir); + buf_output0 (buf_to_net, "/"); + } buf_output0 (buf_to_net, dir); buf_append_char (buf_to_net, '\n'); } @@ -3583,6 +3850,11 @@ expand_proc (pargc, argv, where, mwhere, mfile, shorten, for (i = 1; i < *pargc; ++i) { buf_output0 (buf_to_net, "Module-expansion "); + if (server_dir != NULL) + { + buf_output0 (buf_to_net, server_dir); + buf_output0 (buf_to_net, "/"); + } buf_output0 (buf_to_net, dir); buf_append_char (buf_to_net, '/'); buf_output0 (buf_to_net, argv[i]); @@ -3761,6 +4033,7 @@ struct request requests[] = REQ_LINE("Checkin-prog", serve_checkin_prog, rq_optional), REQ_LINE("Update-prog", serve_update_prog, rq_optional), REQ_LINE("Entry", serve_entry, rq_essential), + REQ_LINE("Kopt", serve_kopt, rq_optional), REQ_LINE("Modified", serve_modified, rq_essential), REQ_LINE("Is-modified", serve_is_modified, rq_optional), @@ -3779,9 +4052,15 @@ struct request requests[] = REQ_LINE("Gzip-stream", serve_gzip_stream, rq_optional), REQ_LINE("Set", serve_set, rq_optional), #ifdef ENCRYPTION -#ifdef HAVE_KERBEROS +# ifdef HAVE_KERBEROS REQ_LINE("Kerberos-encrypt", serve_kerberos_encrypt, rq_optional), +# endif +# ifdef HAVE_GSSAPI + REQ_LINE("Gssapi-encrypt", serve_gssapi_encrypt, rq_optional), +# endif #endif +#ifdef HAVE_GSSAPI + REQ_LINE("Gssapi-authenticate", serve_gssapi_authenticate, rq_optional), #endif REQ_LINE("expand-modules", serve_expand_modules, rq_optional), REQ_LINE("ci", serve_ci, rq_essential), @@ -4025,50 +4304,6 @@ server (argc, argv) protocol to report error messages. */ error_use_protocol = 1; - /* - * Put Rcsbin at the start of PATH, so that rcs programs can find - * themselves. - */ -#ifdef HAVE_PUTENV - if (Rcsbin != NULL && *Rcsbin) - { - char *p; - char *env; - - p = getenv ("PATH"); - if (p != NULL) - { - env = malloc (strlen (Rcsbin) + strlen (p) + sizeof "PATH=:"); - if (env != NULL) - sprintf (env, "PATH=%s:%s", Rcsbin, p); - } - else - { - env = malloc (strlen (Rcsbin) + sizeof "PATH="); - if (env != NULL) - sprintf (env, "PATH=%s", Rcsbin); - } - if (env == NULL) - { - printf ("E Fatal server error, aborting.\n\ -error ENOMEM Virtual memory exhausted.\n"); - - /* I'm doing this manually rather than via error_exit () - because I'm not sure whether we want to call server_cleanup. - Needs more investigation.... */ - -#ifdef SYSTEM_CLEANUP - /* Hook for OS-specific behavior, for example socket subsystems on - NT and OS2 or dealing with windows and arguments on Mac. */ - SYSTEM_CLEANUP (); -#endif - - exit (EXIT_FAILURE); - } - putenv (env); - } -#endif - /* OK, now figure out where we stash our temporary files. */ { char *p; @@ -4221,8 +4456,6 @@ error ENOMEM Virtual memory exhausted.\n"); by this server" or something like that instead of usage message. */ argument_vector[0] = "cvs server"; - server_active = 1; - while (1) { char *cmd, *orig_cmd; @@ -4275,7 +4508,7 @@ error ENOMEM Virtual memory exhausted.\n"); } -#if defined (HAVE_KERBEROS) || defined (AUTH_SERVER_SUPPORT) +#if defined (HAVE_KERBEROS) || defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI) static void switch_to_user PROTO((const char *)); static void @@ -4302,6 +4535,15 @@ error 0 %s: no such user\n", username); exit (EXIT_FAILURE); } + /* FIXME? We don't check for errors from initgroups, setuid, &c. + I think this mainly would come up if someone is trying to run + the server as a non-root user. I think we should be checking for + errors and aborting (as with the error above from getpwnam) if + there is an error (presumably EPERM). That means that pserver + should continue to work right if all of the "system usernames" + in CVSROOT/passwd match the user which the server is being run + as (in inetd.conf), but fail otherwise. */ + #if HAVE_INITGROUPS initgroups (pw->pw_name, pw->pw_gid); #endif /* HAVE_INITGROUPS */ @@ -4472,7 +4714,7 @@ check_password (username, password, repository) /* host_user already set by reference, so just return. */ goto handle_return; } - else if (rc == 0) + else if (rc == 0 && system_auth) { /* No cvs password found, so try /etc/passwd. */ @@ -4531,6 +4773,27 @@ error 0 %s: no such user\n", username); goto handle_return; } } + else if (rc == 0) + { + /* Note that the message _does_ distinguish between the case in + which we check for a system password and the case in which + we do not. It is a real pain to track down why it isn't + letting you in if it won't say why, and I am not convinced + that the potential information disclosure to an attacker + outweighs this. */ + printf ("error 0 no such user %s in CVSROOT/passwd\n", username); + + /* I'm doing this manually rather than via error_exit () + because I'm not sure whether we want to call server_cleanup. + Needs more investigation.... */ + +#ifdef SYSTEM_CLEANUP + /* Hook for OS-specific behavior, for example socket subsystems on + NT and OS2 or dealing with windows and arguments on Mac. */ + SYSTEM_CLEANUP (); +#endif + exit (EXIT_FAILURE); + } else { /* Something strange happened. We don't know what it was, but @@ -4551,6 +4814,10 @@ handle_return: return host_user; } +#endif /* AUTH_SERVER_SUPPORT */ + +#if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI) + /* Read username and password from client (i.e., stdin). If correct, then switch to run as that user and send an ACK to the client via stdout, else send NACK and die. */ @@ -4559,6 +4826,7 @@ pserver_authenticate_connection () { char *tmp = NULL; size_t tmp_allocated = 0; +#ifdef AUTH_SERVER_SUPPORT char *repository = NULL; size_t repository_allocated = 0; char *username = NULL; @@ -4568,6 +4836,7 @@ pserver_authenticate_connection () char *host_user; char *descrambled_password; +#endif /* AUTH_SERVER_SUPPORT */ int verify_and_exit = 0; /* The Authentication Protocol. Client sends: @@ -4632,9 +4901,27 @@ pserver_authenticate_connection () if (strcmp (tmp, "BEGIN VERIFICATION REQUEST\n") == 0) verify_and_exit = 1; - else if (strcmp (tmp, "BEGIN AUTH REQUEST\n") != 0) + else if (strcmp (tmp, "BEGIN AUTH REQUEST\n") == 0) + ; + else if (strcmp (tmp, "BEGIN GSSAPI REQUEST\n") == 0) + { +#ifdef HAVE_GSSAPI + free (tmp); + gserver_authenticate_connection (); + return; +#else + error (1, 0, "GSSAPI authentication not supported by this server"); +#endif + } + else error (1, 0, "bad auth protocol start: %s", tmp); +#ifndef AUTH_SERVER_SUPPORT + + error (1, 0, "Password authentication not supported by this server"); + +#else /* AUTH_SERVER_SUPPORT */ + /* Get the three important pieces of information in order. */ /* See above comment about error handling. */ getline (&repository, &repository_allocated, stdin); @@ -4657,14 +4944,21 @@ pserver_authenticate_connection () error (1, 0, "bad auth protocol end: %s", tmp); } if (!root_allow_ok (repository)) - /* At least for the moment I'm going to do the paranoid - security thing and not tell them how it failed. I'm not - sure that is a good idea; it is a real pain when one needs - to track down what is going on for legitimate reasons. - The other issue is that the protocol doesn't really have - a good way for anything other than I HATE YOU. */ + /* Just give a generic I HATE YOU. This is because CVS 1.9.10 + and older clients do not support "error". Once more recent + clients are more widespread, probably want to fix this (it is + a real pain to track down why it isn't letting you in if it + won't say why, and I am not convinced that the potential + information disclosure to an attacker outweighs this). */ goto i_hate_you; + /* OK, now parse the config file, so we can use it to control how + to check passwords. If there was an error parsing the config + file, parse_config already printed an error. We keep going. + Why? Because if we didn't, then there would be no way to check + in a new CVSROOT/config file to fix the broken one! */ + parse_config (repository); + /* We need the real cleartext before we hash it. */ descrambled_password = descramble (password); host_user = check_password (username, descrambled_password, repository); @@ -4716,9 +5010,11 @@ pserver_authenticate_connection () free (repository); free (username); free (password); -} #endif /* AUTH_SERVER_SUPPORT */ +} + +#endif /* AUTH_SERVER_SUPPORT || HAVE_GSSAPI */ #ifdef HAVE_KERBEROS @@ -4798,6 +5094,122 @@ error 0 kerberos: can't get local name: %s\n", krb_get_err_text(status)); } #endif /* HAVE_KERBEROS */ +#ifdef HAVE_GSSAPI + +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN (256) +#endif + +/* Authenticate a GSSAPI connection. This is called from + pserver_authenticate_connection, and it handles success and failure + the same way. */ + +static void +gserver_authenticate_connection () +{ + char hostname[MAXHOSTNAMELEN]; + struct hostent *hp; + gss_buffer_desc tok_in, tok_out; + char buf[1024]; + OM_uint32 stat_min, ret; + gss_name_t server_name, client_name; + gss_cred_id_t server_creds; + int nbytes; + gss_OID mechid; + + gethostname (hostname, sizeof hostname); + hp = gethostbyname (hostname); + if (hp == NULL) + error (1, 0, "can't get canonical hostname"); + + sprintf (buf, "cvs@%s", hp->h_name); + tok_in.value = buf; + tok_in.length = strlen (buf); + + if (gss_import_name (&stat_min, &tok_in, gss_nt_service_name, + &server_name) != GSS_S_COMPLETE) + error (1, 0, "could not import GSSAPI service name %s", buf); + + /* Acquire the server credential to verify the client's + authentication. */ + if (gss_acquire_cred (&stat_min, server_name, 0, GSS_C_NULL_OID_SET, + GSS_C_ACCEPT, &server_creds, + NULL, NULL) != GSS_S_COMPLETE) + error (1, 0, "could not acquire GSSAPI server credentials"); + + gss_release_name (&stat_min, &server_name); + + /* The client will send us a two byte length followed by that many + bytes. */ + if (fread (buf, 1, 2, stdin) != 2) + error (1, errno, "read of length failed"); + + nbytes = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff); + assert (nbytes <= sizeof buf); + + if (fread (buf, 1, nbytes, stdin) != nbytes) + error (1, errno, "read of data failed"); + + gcontext = GSS_C_NO_CONTEXT; + tok_in.length = nbytes; + tok_in.value = buf; + + if (gss_accept_sec_context (&stat_min, + &gcontext, /* context_handle */ + server_creds, /* verifier_cred_handle */ + &tok_in, /* input_token */ + NULL, /* channel bindings */ + &client_name, /* src_name */ + &mechid, /* mech_type */ + &tok_out, /* output_token */ + &ret, + NULL, /* ignore time_rec */ + NULL) /* ignore del_cred_handle */ + != GSS_S_COMPLETE) + { + error (1, 0, "could not verify credentials"); + } + + /* FIXME: Use Kerberos v5 specific code to authenticate to a user. + We could instead use an authentication to access mapping. */ + { + krb5_context kc; + krb5_principal p; + gss_buffer_desc desc; + + krb5_init_context (&kc); + if (gss_display_name (&stat_min, client_name, &desc, + &mechid) != GSS_S_COMPLETE + || krb5_parse_name (kc, ((gss_buffer_t) &desc)->value, &p) != 0 + || krb5_aname_to_localname (kc, p, sizeof buf, buf) != 0 + || krb5_kuserok (kc, p, buf) != TRUE) + { + error (1, 0, "access denied"); + } + krb5_free_principal (kc, p); + krb5_free_context (kc); + } + + if (tok_out.length != 0) + { + char cbuf[2]; + + cbuf[0] = (tok_out.length >> 8) & 0xff; + cbuf[1] = tok_out.length & 0xff; + if (fwrite (cbuf, 1, 2, stdout) != 2 + || (fwrite (tok_out.value, 1, tok_out.length, stdout) + != tok_out.length)) + error (1, errno, "fwrite failed"); + } + + switch_to_user (buf); + + printf ("I LOVE YOU\n"); + fflush (stdout); +} + +#endif /* HAVE_GSSAPI */ + #endif /* SERVER_SUPPORT */ #if defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT) @@ -4806,320 +5218,232 @@ error 0 kerberos: can't get local name: %s\n", krb_get_err_text(status)); the command line. */ int cvsencrypt; -#ifdef ENCRYPTION +/* This global variable is non-zero if the users requests stream + authentication on the command line. */ +int cvsauthenticate; -#ifdef HAVE_KERBEROS +#ifdef HAVE_GSSAPI -/* An encryption interface using Kerberos. This is built on top of - the buffer structure. We encrypt using a big endian two byte count - field followed by a block of encrypted data. */ +/* An buffer interface using GSSAPI. This is built on top of a + packetizing buffer. */ -/* This structure is the closure field of a Kerberos encryption - buffer. */ +/* This structure is the closure field of the GSSAPI translation + routines. */ -struct krb_encrypt_buffer +struct cvs_gssapi_wrap_data { - /* The underlying buffer. */ - struct buffer *buf; - /* The Kerberos key schedule. */ - Key_schedule sched; - /* The Kerberos DES block. */ - C_Block block; - /* For an input buffer, we may have to buffer up data here. */ - /* This is non-zero if the buffered data is decrypted. Otherwise, - the buffered data is encrypted, and starts with the two byte - count. */ - int clear; - /* The amount of buffered data. */ - int holdsize; - /* The buffer allocated to hold the data. */ - char *holdbuf; - /* The size of holdbuf. */ - int holdbufsize; - /* If clear is set, we need another data pointer to track where we - are in holdbuf. If clear is zero, then this pointer is not - used. */ - char *holddata; + /* The GSSAPI context. */ + gss_ctx_id_t gcontext; }; -static int krb_encrypt_buffer_input PROTO((void *, char *, int, int, int *)); -static int krb_encrypt_buffer_output PROTO((void *, const char *, int, int *)); -static int krb_encrypt_buffer_flush PROTO((void *)); -static int krb_encrypt_buffer_block PROTO((void *, int)); -static int krb_encrypt_buffer_shutdown PROTO((void *)); +static int cvs_gssapi_wrap_input PROTO((void *, const char *, char *, int)); +static int cvs_gssapi_wrap_output PROTO((void *, const char *, char *, int, + int *)); -/* Create an encryption buffer. */ +/* Create a GSSAPI wrapping buffer. We use a packetizing buffer with + GSSAPI wrapping routines. */ struct buffer * -krb_encrypt_buffer_initialize (buf, input, sched, block, memory) +cvs_gssapi_wrap_buffer_initialize (buf, input, gcontext, memory) struct buffer *buf; int input; - Key_schedule sched; - C_Block block; + gss_ctx_id_t gcontext; void (*memory) PROTO((struct buffer *)); { - struct krb_encrypt_buffer *kb; + struct cvs_gssapi_wrap_data *gd; - kb = (struct krb_encrypt_buffer *) xmalloc (sizeof *kb); - memset (kb, 0, sizeof *kb); - - kb->buf = buf; - memcpy (kb->sched, sched, sizeof (Key_schedule)); - memcpy (kb->block, block, sizeof (C_Block)); - if (input) - { - /* We add some space to the buffer to hold the length. */ - kb->holdbufsize = BUFFER_DATA_SIZE + 16; - kb->holdbuf = xmalloc (kb->holdbufsize); - } + gd = (struct cvs_gssapi_wrap_data *) xmalloc (sizeof *gd); + gd->gcontext = gcontext; - return buf_initialize (input ? krb_encrypt_buffer_input : NULL, - input ? NULL : krb_encrypt_buffer_output, - input ? NULL : krb_encrypt_buffer_flush, - krb_encrypt_buffer_block, - krb_encrypt_buffer_shutdown, - memory, - kb); + return (packetizing_buffer_initialize + (buf, + input ? cvs_gssapi_wrap_input : NULL, + input ? NULL : cvs_gssapi_wrap_output, + gd, + memory)); } -/* Input data from a Kerberos encryption buffer. */ +/* Unwrap data using GSSAPI. */ static int -krb_encrypt_buffer_input (closure, data, need, size, got) - void *closure; - char *data; - int need; +cvs_gssapi_wrap_input (fnclosure, input, output, size) + void *fnclosure; + const char *input; + char *output; int size; - int *got; { - struct krb_encrypt_buffer *kb = (struct krb_encrypt_buffer *) closure; + struct cvs_gssapi_wrap_data *gd = + (struct cvs_gssapi_wrap_data *) fnclosure; + gss_buffer_desc inbuf, outbuf; + OM_uint32 stat_min; + int conf; - *got = 0; + inbuf.value = (void *) input; + inbuf.length = size; - if (kb->holdsize > 0 && kb->clear) + if (gss_unwrap (&stat_min, gd->gcontext, &inbuf, &outbuf, &conf, NULL) + != GSS_S_COMPLETE) { - int copy; - - copy = kb->holdsize; - - if (copy > size) - { - memcpy (data, kb->holddata, size); - kb->holdsize -= size; - kb->holddata += size; - *got = size; - return 0; - } - - memcpy (data, kb->holddata, copy); - kb->holdsize = 0; - kb->clear = 0; - - data += copy; - need -= copy; - size -= copy; - *got = copy; + error (1, 0, "gss_unwrap failed"); } - while (need > 0 || *got == 0) - { - int get, status, nread, count, dcount; - char *bytes; - char stackoutbuf[BUFFER_DATA_SIZE + 16]; - char *outbuf; + if (outbuf.length > size) + abort (); - /* If we don't already have the two byte count, get it. */ - if (kb->holdsize < 2) - { - get = 2 - kb->holdsize; - status = buf_read_data (kb->buf, get, &bytes, &nread); - if (status != 0) - { - /* buf_read_data can return -2, but a buffer input - function is only supposed to return -1, 0, or an - error code. */ - if (status == -2) - status = ENOMEM; - return status; - } + memcpy (output, outbuf.value, outbuf.length); - if (nread == 0) - { - /* The buffer is in nonblocking mode, and we didn't - manage to read anything. */ - return 0; - } + /* The real packet size is stored in the data, so we don't need to + remember outbuf.length. */ - if (get == 1) - kb->holdbuf[1] = bytes[0]; - else - { - kb->holdbuf[0] = bytes[0]; - if (nread < 2) - { - /* We only got one byte, but we needed two. Stash - the byte we got, and try again. */ - kb->holdsize = 1; - continue; - } - kb->holdbuf[1] = bytes[1]; - } - kb->holdsize = 2; - } + gss_release_buffer (&stat_min, &outbuf); - /* Read the encrypted block of data. */ + return 0; +} - count = (((kb->holdbuf[0] & 0xff) << 8) - + (kb->holdbuf[1] & 0xff)); +/* Wrap data using GSSAPI. */ - if (count + 2 > kb->holdbufsize) - { - char *n; +static int +cvs_gssapi_wrap_output (fnclosure, input, output, size, translated) + void *fnclosure; + const char *input; + char *output; + int size; + int *translated; +{ + struct cvs_gssapi_wrap_data *gd = + (struct cvs_gssapi_wrap_data *) fnclosure; + gss_buffer_desc inbuf, outbuf; + OM_uint32 stat_min; + int conf_req, conf; - /* This should be impossible, since we should have - allocated space for the largest possible block in the - initialize function. However, we handle it just in - case something changes in the future, so that a current - server can handle a later client. */ + inbuf.value = (void *) input; + inbuf.length = size; - n = realloc (kb->holdbuf, count + 2); - if (n == NULL) - { - (*kb->buf->memory_error) (kb->buf); - return ENOMEM; - } - kb->holdbuf = n; - kb->holdbufsize = count + 2; - } +#ifdef ENCRYPTION + conf_req = cvs_gssapi_encrypt; +#else + conf_req = 0; +#endif - get = count - (kb->holdsize - 2); + if (gss_wrap (&stat_min, gd->gcontext, conf_req, GSS_C_QOP_DEFAULT, + &inbuf, &conf, &outbuf) != GSS_S_COMPLETE) + error (1, 0, "gss_wrap failed"); - status = buf_read_data (kb->buf, get, &bytes, &nread); - if (status != 0) - { - /* buf_read_data can return -2, but a buffer input - function is only supposed to return -1, 0, or an error - code. */ - if (status == -2) - status = ENOMEM; - return status; - } + /* The packetizing buffer only permits us to add 100 bytes. + FIXME: I don't know what, if anything, is guaranteed by GSSAPI. + This may need to be increased for a different GSSAPI + implementation, or we may need a different algorithm. */ + if (outbuf.length > size + 100) + abort (); - if (nread == 0) - { - /* We did not get any data. Presumably the buffer is in - nonblocking mode. */ - return 0; - } + memcpy (output, outbuf.value, outbuf.length); - /* FIXME: We could complicate the code here to avoid this - memcpy in the common case of kb->holdsize == 2 && nread == - get. */ - memcpy (kb->holdbuf + kb->holdsize, bytes, nread); - kb->holdsize += nread; + *translated = outbuf.length; - if (nread < get) - { - /* We did not get all the data we need. buf_read_data - does not promise to return all the bytes requested, so - we must try again. */ - continue; - } + gss_release_buffer (&stat_min, &outbuf); - /* We have a complete encrypted block of COUNT bytes at - KB->HOLDBUF + 2. Decrypt it. */ + return 0; +} - if (count <= sizeof stackoutbuf) - outbuf = stackoutbuf; - else - { - /* I believe this is currently impossible, but we handle - it for the benefit of future client changes. */ - outbuf = malloc (count); - if (outbuf == NULL) - { - (*kb->buf->memory_error) (kb->buf); - return ENOMEM; - } - } +#endif /* HAVE_GSSAPI */ - des_cbc_encrypt ((C_Block *) (kb->holdbuf + 2), (C_Block *) outbuf, - count, kb->sched, &kb->block, 0); +#ifdef ENCRYPTION - /* The first two bytes in the decrypted buffer are the real - (unaligned) length. */ - dcount = ((outbuf[0] & 0xff) << 8) + (outbuf[1] & 0xff); +#ifdef HAVE_KERBEROS - if (((dcount + 2 + 7) & ~7) != count) - error (1, 0, "Decryption failure"); +/* An encryption interface using Kerberos. This is built on top of a + packetizing buffer. */ - if (dcount > size) - { - /* We have too much data for the buffer. We need to save - some of it for the next call. */ +/* This structure is the closure field of the Kerberos translation + routines. */ - memcpy (data, outbuf + 2, size); - *got += size; +struct krb_encrypt_data +{ + /* The Kerberos key schedule. */ + Key_schedule sched; + /* The Kerberos DES block. */ + C_Block block; +}; - kb->holdsize = dcount - size; - memcpy (kb->holdbuf, outbuf + 2 + size, dcount - size); - kb->holddata = kb->holdbuf; - kb->clear = 1; +static int krb_encrypt_input PROTO((void *, const char *, char *, int)); +static int krb_encrypt_output PROTO((void *, const char *, char *, int, + int *)); - if (outbuf != stackoutbuf) - free (outbuf); +/* Create a Kerberos encryption buffer. We use a packetizing buffer + with Kerberos encryption translation routines. */ - return 0; - } +struct buffer * +krb_encrypt_buffer_initialize (buf, input, sched, block, memory) + struct buffer *buf; + int input; + Key_schedule sched; + C_Block block; + void (*memory) PROTO((struct buffer *)); +{ + struct krb_encrypt_data *kd; - memcpy (data, outbuf + 2, dcount); + kd = (struct krb_encrypt_data *) xmalloc (sizeof *kd); + memcpy (kd->sched, sched, sizeof (Key_schedule)); + memcpy (kd->block, block, sizeof (C_Block)); - if (outbuf != stackoutbuf) - free (outbuf); + return packetizing_buffer_initialize (buf, + input ? krb_encrypt_input : NULL, + input ? NULL : krb_encrypt_output, + kd, + memory); +} - kb->holdsize = 0; +/* Decrypt Kerberos data. */ - data += dcount; - need -= dcount; - size -= dcount; - *got += dcount; - } +static int +krb_encrypt_input (fnclosure, input, output, size) + void *fnclosure; + const char *input; + char *output; + int size; +{ + struct krb_encrypt_data *kd = (struct krb_encrypt_data *) fnclosure; + int tcount; + + des_cbc_encrypt ((C_Block *) input, (C_Block *) output, + size, kd->sched, &kd->block, 0); + + /* SIZE is the size of the buffer, which is set by the encryption + routine. The packetizing buffer will arrange for the first two + bytes in the decrypted buffer to be the real (unaligned) + length. As a safety check, make sure that the length in the + buffer corresponds to SIZE. Note that the length in the buffer + is just the length of the data. We must add 2 to account for + the buffer count itself. */ + tcount = ((output[0] & 0xff) << 8) + (output[1] & 0xff); + if (((tcount + 2 + 7) & ~7) != size) + error (1, 0, "Decryption failure"); return 0; } -/* Output data to a Kerberos encryption buffer. */ +/* Encrypt Kerberos data. */ static int -krb_encrypt_buffer_output (closure, data, have, wrote) - void *closure; - const char *data; - int have; - int *wrote; +krb_encrypt_output (fnclosure, input, output, size, translated) + void *fnclosure; + const char *input; + char *output; + int size; + int *translated; { - struct krb_encrypt_buffer *kb = (struct krb_encrypt_buffer *) closure; - char inbuf[BUFFER_DATA_SIZE + 16]; - char outbuf[BUFFER_DATA_SIZE + 16]; + struct krb_encrypt_data *kd = (struct krb_encrypt_data *) fnclosure; int aligned; - if (have > BUFFER_DATA_SIZE) - { - /* It would be easy to malloc a buffer, but I don't think this - case can ever arise. */ - abort (); - } - - inbuf[0] = (have >> 8) & 0xff; - inbuf[1] = have & 0xff; - memcpy (inbuf + 2, data, have); - /* For security against a known plaintext attack, we should initialize any padding bytes to random values. Instead, we just pick up whatever is on the stack, which is at least better than using zero. */ - /* Align (have + 2) (plus 2 for the count) to an 8 byte boundary. */ - aligned = (have + 2 + 7) & ~7; + /* Align SIZE to an 8 byte boundary. Note that SIZE includes the + two byte buffer count at the start of INPUT which was added by + the packetizing buffer. */ + aligned = (size + 7) & ~7; /* We use des_cbc_encrypt rather than krb_mk_priv because the latter sticks a timestamp in the block, and krb_rd_priv expects @@ -5128,65 +5452,12 @@ krb_encrypt_buffer_output (closure, data, have, wrote) fail over a long network connection. We trust krb_recvauth to guard against a replay attack. */ - des_cbc_encrypt ((C_Block *) inbuf, (C_Block *) (outbuf + 2), aligned, - kb->sched, &kb->block, 1); - - outbuf[0] = (aligned >> 8) & 0xff; - outbuf[1] = aligned & 0xff; - - /* FIXME: It would be more efficient to get des_cbc_encrypt to put - its output directly into a buffer_data structure, which we - could then append to kb->buf. That would save a memcpy. */ - - buf_output (kb->buf, outbuf, aligned + 2); - - *wrote = have; - - /* We will only be here because buf_send_output was called on the - encryption buffer. That means that we should now call - buf_send_output on the underlying buffer. */ - return buf_send_output (kb->buf); -} - -/* Flush data to a Kerberos encryption buffer. */ - -static int -krb_encrypt_buffer_flush (closure) - void *closure; -{ - struct krb_encrypt_buffer *kb = (struct krb_encrypt_buffer *) closure; - - /* Flush the underlying buffer. Note that if the original call to - buf_flush passed 1 for the BLOCK argument, then the buffer will - already have been set into blocking mode, so we should always - pass 0 here. */ - return buf_flush (kb->buf, 0); -} - -/* The block routine for a Kerberos encryption buffer. */ + des_cbc_encrypt ((C_Block *) input, (C_Block *) output, aligned, + kd->sched, &kd->block, 1); -static int -krb_encrypt_buffer_block (closure, block) - void *closure; - int block; -{ - struct krb_encrypt_buffer *kb = (struct krb_encrypt_buffer *) closure; - - if (block) - return set_block (kb->buf); - else - return set_nonblock (kb->buf); -} - -/* Shut down a Kerberos encryption buffer. */ - -static int -krb_encrypt_buffer_shutdown (closure) - void *closure; -{ - struct krb_encrypt_buffer *kb = (struct krb_encrypt_buffer *) closure; + *translated = aligned; - return buf_shutdown (kb->buf); + return 0; } #endif /* HAVE_KERBEROS */ @@ -5222,6 +5493,13 @@ cvs_output (str, len) size_t to_write = len; const char *p = str; + /* For symmetry with cvs_outerr we would call fflush (stderr) + here. I guess the assumption is that stderr will be + unbuffered, so we don't need to. That sounds like a sound + assumption from the manpage I looked at, but if there was + something fishy about it, my guess is that calling fflush + would not produce a significant performance problem. */ + while (to_write > 0) { written = fwrite (p, 1, to_write, stdout); @@ -5233,6 +5511,90 @@ cvs_output (str, len) } } +/* Output LEN bytes at STR in binary mode. If LEN is zero, then + output zero bytes. */ + +void +cvs_output_binary (str, len) + char *str; + size_t len; +{ +#ifdef SERVER_SUPPORT + if (error_use_protocol || server_active) + { + struct buffer *buf; + char size_text[40]; + + if (error_use_protocol) + buf = buf_to_net; + else if (server_active) + buf = protocol; + + if (!supported_response ("Mbinary")) + { + error (0, 0, "\ +this client does not support writing binary files to stdout"); + return; + } + + buf_output0 (buf, "Mbinary\012"); + sprintf (size_text, "%lu\012", (unsigned long) len); + buf_output0 (buf, size_text); + + /* Not sure what would be involved in using buf_append_data here + without stepping on the toes of our caller (which is responsible + for the memory allocation of STR). */ + buf_output (buf, str, len); + + if (!error_use_protocol) + buf_send_counted (protocol); + } + else +#endif + { + size_t written; + size_t to_write = len; + const char *p = str; + + /* For symmetry with cvs_outerr we would call fflush (stderr) + here. I guess the assumption is that stderr will be + unbuffered, so we don't need to. That sounds like a sound + assumption from the manpage I looked at, but if there was + something fishy about it, my guess is that calling fflush + would not produce a significant performance problem. */ +#ifdef USE_SETMODE_STDOUT + int oldmode; + + /* It is possible that this should be the same ifdef as + USE_SETMODE_BINARY but at least for the moment we keep them + separate. Mostly this is just laziness and/or a question + of what has been tested where. Also there might be an + issue of setmode vs. _setmode. */ + /* The Windows doc says to call setmode only right after startup. + I assume that what they are talking about can also be helped + by flushing the stream before changing the mode. */ + fflush (stdout); + oldmode = _setmode (_fileno (stdout), _O_BINARY); + if (oldmode < 0) + error (0, errno, "failed to setmode on stdout"); +#endif + + while (to_write > 0) + { + written = fwrite (p, 1, to_write, stdout); + if (written == 0) + break; + p += written; + to_write -= written; + } +#ifdef USE_SETMODE_STDOUT + fflush (stdout); + if (_setmode (_fileno (stdout), oldmode) != _O_BINARY) + error (0, errno, "failed to setmode on stdout"); +#endif + } +} + /* Like CVS_OUTPUT but output is for stderr not stdout. */ void @@ -5326,3 +5688,62 @@ cvs_flushout () #endif fflush (stdout); } + +/* Output TEXT, tagging it according to TAG. There are lots more + details about what TAG means in cvsclient.texi but for the simple + case (e.g. non-client/server), TAG is just "newline" to output a + newline (in which case TEXT must be NULL), and any other tag to + output normal text. + + Note that there is no way to output either \0 or \n as part of TEXT. */ + +void +cvs_output_tagged (tag, text) + char *tag; + char *text; +{ + if (text != NULL && strchr (text, '\n') != NULL) + /* Uh oh. The protocol has no way to cope with this. For now + we dump core, although that really isn't such a nice + response given that this probably can be caused by newlines + in filenames and other causes other than bugs in CVS. Note + that we don't want to turn this into "MT newline" because + this case is a newline within a tagged item, not a newline + as extraneous sugar for the user. */ + assert (0); + + /* Start and end tags don't take any text, per cvsclient.texi. */ + if (tag[0] == '+' || tag[0] == '-') + assert (text == NULL); + +#ifdef SERVER_SUPPORT + if (server_active && supported_response ("MT")) + { + struct buffer *buf; + + if (error_use_protocol) + buf = buf_to_net; + else + buf = protocol; + + buf_output0 (buf, "MT "); + buf_output0 (buf, tag); + if (text != NULL) + { + buf_output (buf, " ", 1); + buf_output0 (buf, text); + } + buf_output (buf, "\n", 1); + + if (!error_use_protocol) + buf_send_counted (protocol); + } + else +#endif + { + if (strcmp (tag, "newline") == 0) + cvs_output ("\n", 1); + else if (text != NULL) + cvs_output (text, 0); + } +} diff --git a/contrib/cvs/src/update.c b/contrib/cvs/src/update.c index def6a34..2aa3032 100644 --- a/contrib/cvs/src/update.c +++ b/contrib/cvs/src/update.c @@ -3,7 +3,7 @@ * 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 1.4 kit. + * specified in the README file that comes with the CVS source distribution. * * "update" updates the version in the present directory with respect to the RCS * repository. The present version must have been created by "checkout". The @@ -64,12 +64,8 @@ static int update_fileproc PROTO ((void *callerdat, struct file_info *)); static int update_filesdone_proc PROTO ((void *callerdat, int err, char *repository, char *update_dir, List *entries)); -static int write_letter PROTO((char *file, int letter, char *update_dir)); -#ifdef SERVER_SUPPORT -static void join_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts)); -#else +static void write_letter PROTO ((struct file_info *finfo, int letter)); static void join_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts)); -#endif static char *options = NULL; static char *tag = NULL; @@ -113,6 +109,7 @@ static const char *const update_usage[] = "\t-j rev\tMerge in changes made between current revision and rev.\n", "\t-I ign\tMore files to ignore (! to reset).\n", "\t-W spec\tWrappers specification line.\n", + "(Specify the --help global option for a list of other help options)\n", NULL }; @@ -259,16 +256,18 @@ update (argc, argv) /* If the server supports the command "update-patches", that means that it knows how to handle the -u argument to update, which - means to send patches instead of complete files. */ + means to send patches instead of complete files. + + We don't send -u if failed_patches != NULL, so that the + server doesn't try to send patches which will just fail + again. At least currently, the client also clobbers the + file and tells the server it is lost, which also will get + a full file instead of a patch, but it seems clean to omit + -u. */ if (failed_patches == NULL) { -#ifndef DONT_USE_PATCH - /* Systems which don't have the patch program ported to them - will want to define DONT_USE_PATCH; then CVS won't try to - invoke patch. */ if (supported_request ("update-patches")) send_arg ("-u"); -#endif } if (failed_patches == NULL) @@ -548,44 +547,17 @@ update_fileproc (callerdat, finfo) break; case T_CONFLICT: /* old punt-type errors */ retval = 1; - (void) write_letter (finfo->file, 'C', finfo->update_dir); + write_letter (finfo, 'C'); break; case T_NEEDS_MERGE: /* needs merging */ if (noexec) { retval = 1; - (void) write_letter (finfo->file, 'C', finfo->update_dir); + write_letter (finfo, 'C'); } else { - if (wrap_merge_is_copy (finfo->file)) -#if 0 - /* Look, we can't clobber the user's file. We - know it is modified and we're going to - overwrite their mod? Puh-leeze. The - correct behavior is probably something like - what merge_file does for -kb, which is to - give the users both files and tell them - what the two filenames are. Of course, -m - in wrappers needs to be documented *much* - better. Anyway, until then, make this a - fatal error. */ - - /* Should we be warning the user that we are - * overwriting the user's copy of the file? */ - retval = - checkout_file (finfo, vers, 0); -#else - { - error (0, 0, "A -m 'COPY' wrapper is specified"); - error (0, 0, "but file %s needs merge", - finfo->fullname); - error (1, 0, "\ -You probably want to avoid -m 'COPY' wrappers"); -#endif - } - else - retval = merge_file (finfo, vers); + retval = merge_file (finfo, vers); } break; case T_MODIFIED: /* locally modified */ @@ -622,7 +594,7 @@ You probably want to avoid -m 'COPY' wrappers"); if (!retcode) { - (void) write_letter (finfo->file, 'C', finfo->update_dir); + write_letter (finfo, 'C'); retval = 1; } else @@ -634,7 +606,10 @@ You probably want to avoid -m 'COPY' wrappers"); } } if (!retval) - retval = write_letter (finfo->file, 'M', finfo->update_dir); + { + write_letter (finfo, 'M'); + retval = 0; + } break; #ifdef SERVER_SUPPORT case T_PATCH: /* needs patch */ @@ -658,10 +633,9 @@ You probably want to avoid -m 'COPY' wrappers"); break; } } - /* Fall through. */ /* If we're not running as a server, just check the - file out. It's simpler and faster than starting up - two new processes (diff and patch). */ + file out. It's simpler and faster than producing + and applying patches. */ /* Fall through. */ #endif case T_CHECKOUT: /* needs checkout */ @@ -674,10 +648,12 @@ You probably want to avoid -m 'COPY' wrappers"); #endif break; case T_ADDED: /* added but not committed */ - retval = write_letter (finfo->file, 'A', finfo->update_dir); + write_letter (finfo, 'A'); + retval = 0; break; case T_REMOVED: /* removed but not committed */ - retval = write_letter (finfo->file, 'R', finfo->update_dir); + write_letter (finfo, 'R'); + retval = 0; break; case T_REMOVE_ENTRY: /* needs to be un-registered */ retval = scratch_file (finfo); @@ -727,7 +703,23 @@ update_ignproc (file, dir) char *file; char *dir; { - (void) write_letter (file, '?', dir); + struct file_info finfo; + + memset (&finfo, 0, sizeof (finfo)); + finfo.file = file; + finfo.update_dir = dir; + if (dir[0] == '\0') + finfo.fullname = xstrdup (file); + else + { + finfo.fullname = xmalloc (strlen (file) + strlen (dir) + 10); + strcpy (finfo.fullname, dir); + strcat (finfo.fullname, "/"); + strcat (finfo.fullname, file); + } + + write_letter (&finfo, '?'); + free (finfo.fullname); } /* ARGSUSED */ @@ -808,7 +800,7 @@ update_dirent_proc (callerdat, dir, repository, update_dir, entries) if (noexec) { /* ignore the missing dir if -n is specified */ - error (0, 0, "New directory `%s' -- ignored", dir); + error (0, 0, "New directory `%s' -- ignored", update_dir); return (R_SKIP_ALL); } else @@ -818,6 +810,7 @@ update_dirent_proc (callerdat, dir, repository, update_dir, entries) Create_Admin (dir, update_dir, repository, tag, date, /* This is a guess. We will rewrite it later via WriteTag. */ + 0, 0); rewrite_tag = 1; nonbranch = 0; @@ -920,7 +913,8 @@ update_dirleave_proc (callerdat, dir, err, update_dir, entries) { if ((cp = strrchr (line, '\n')) != NULL) *cp = '\0'; - run_setup ("%s %s", line, repository); + run_setup (line); + run_arg (repository); cvs_output (program_name, 0); cvs_output (" ", 1); cvs_output (command_name, 0); @@ -1080,6 +1074,9 @@ checkout_file (finfo, vers_ts, adding) int status; int file_is_dead; + /* Solely to suppress a warning from gcc -Wall. */ + backup = NULL; + /* don't screw with backup files if we're going to stdout */ if (!pipeout) { @@ -1093,7 +1090,13 @@ checkout_file (finfo, vers_ts, adding) else /* If -f/-t wrappers are being used to wrap up a directory, then backup might be a directory instead of just a file. */ - (void) unlink_file_dir (backup); + if (unlink_file_dir (backup) < 0) + { + /* Not sure if the existence_error check is needed here. */ + if (!existence_error (errno)) + /* FIXME: should include update_dir in message. */ + error (0, errno, "error removing %s", backup); + } } file_is_dead = RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs); @@ -1133,7 +1136,7 @@ VERS: ", 0); { Vers_TS *xvers_ts; - if (cvswrite == TRUE + if (cvswrite && !file_is_dead && !fileattr_get (finfo->file, "_watched")) xchmod (finfo->file, 1); @@ -1155,13 +1158,11 @@ VERS: ", 0); } /* set the time from the RCS file iff it was unknown before */ - if (vers_ts->vn_user == NULL || - strncmp (vers_ts->ts_rcs, "Initial", 7) == 0) - { - set_time = 1; - } - else - set_time = 0; + set_time = + (!noexec + && (vers_ts->vn_user == NULL || + strncmp (vers_ts->ts_rcs, "Initial", 7) == 0) + && !file_is_dead); wrap_fromcvs_process_file (finfo->file); @@ -1222,7 +1223,7 @@ VERS: ", 0); if (!really_quiet && !file_is_dead) { - write_letter (finfo->file, 'U', finfo->update_dir); + write_letter (finfo, 'U'); } } } @@ -1243,7 +1244,13 @@ VERS: ", 0); { /* If -f/-t wrappers are being used to wrap up a directory, then backup might be a directory instead of just a file. */ - (void) unlink_file_dir (backup); + if (unlink_file_dir (backup) < 0) + { + /* Not sure if the existence_error check is needed here. */ + if (!existence_error (errno)) + /* FIXME: should include update_dir in message. */ + error (0, errno, "error removing %s", backup); + } free (backup); } @@ -1306,6 +1313,27 @@ patch_file (finfo, vers_ts, docheckout, file_info, checksum) return 0; } + /* First check that the first revision exists. If it has been nuked + by cvs admin -o, then just fall back to checking out entire + revisions. In some sense maybe we don't have to do this; after + all cvs.texinfo says "Make sure that no-one has checked out a + copy of the revision you outdate" but then again, that advice + doesn't really make complete sense, because "cvs admin" operates + on a working directory and so _someone_ will almost always have + _some_ revision checked out. */ + { + char *rev; + + rev = RCS_gettag (finfo->rcs, vers_ts->vn_user, 1, NULL); + if (rev == NULL) + { + *docheckout = 1; + return 0; + } + else + free (rev); + } + backup = xmalloc (strlen (finfo->file) + sizeof (CVSADM) + sizeof (CVSPREFIX) @@ -1382,7 +1410,7 @@ patch_file (finfo, vers_ts, docheckout, file_info, checksum) retcode = 0; if (! fail) { - const char *diff_options; + char *diff_options; /* FIXME: It might be better to come up with a diff library which can be shared with the diffutils. */ @@ -1400,21 +1428,22 @@ patch_file (finfo, vers_ts, docheckout, file_info, checksum) } else { - /* FIXME: We should use -a if diff supports it. We should - probably just copy over most or all of the diff - handling in the RCS configure script. */ - /* IMHO, we shouldn't copy over anything which even - vaguely resembles the RCS configure script. That kind of - thing tends to be ugly, slow, and fragile. It also is a - a support headache for CVS to behave differently in subtle - ways based on whether it was installed correctly. Instead we - should come up with a diff library. -kingdon, Apr 1997. */ + /* Now that diff is librarified, we could be passing -a if + we wanted to. However, it is unclear to me whether we + would want to. Does diff -a, in any significant + percentage of cases, produce patches which are smaller + than the files it is patching? I guess maybe text + files with character sets which diff regards as + 'binary'. Conversely, do they tend to be much larger + in the bad cases? This needs some more + thought/investigation, I suspect. */ + diff_options = "-n"; } - run_setup ("%s %s %s %s", DIFF, diff_options, file1, file2); + retcode = diff_exec (file1, file2, diff_options, finfo->file); /* A retcode of 0 means no differences. 1 means some differences. */ - if ((retcode = run_exec (RUN_TTY, finfo->file, RUN_TTY, RUN_NORMAL)) != 0 + if (retcode != 0 && retcode != 1) { fail = 1; @@ -1434,7 +1463,7 @@ patch_file (finfo, vers_ts, docheckout, file_info, checksum) file_info->st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH)) < 0) error (0, errno, "cannot change mode of file %s", finfo->file); - if (cvswrite == TRUE + if (cvswrite && !fileattr_get (finfo->file, "_watched")) xchmod (finfo->file, 1); @@ -1482,7 +1511,7 @@ patch_file (finfo, vers_ts, docheckout, file_info, checksum) if (!really_quiet) { - write_letter (finfo->file, 'P', finfo->update_dir); + write_letter (finfo, 'P'); } } else @@ -1528,7 +1557,7 @@ patch_file_write (callerdat, buffer, len) data->final_nl = (buffer[len - 1] == '\n'); if (data->compute_checksum) - MD5Update (&data->context, buffer, len); + MD5Update (&data->context, (unsigned char *) buffer, len); } #endif /* SERVER_SUPPORT */ @@ -1537,27 +1566,45 @@ patch_file_write (callerdat, buffer, len) * Several of the types we process only print a bit of information consisting * of a single letter and the name. */ -static int -write_letter (file, letter, update_dir) - char *file; +static void +write_letter (finfo, letter) + struct file_info *finfo; int letter; - char *update_dir; { if (!really_quiet) { - char buf[2]; + char *tag = NULL; + /* Big enough for "+updated" or any of its ilk. */ + char buf[80]; + + switch (letter) + { + case 'U': + tag = "updated"; + break; + default: + /* We don't yet support tagged output except for "U". */ + break; + } + + if (tag != NULL) + { + sprintf (buf, "+%s", tag); + cvs_output_tagged (buf, NULL); + } buf[0] = letter; buf[1] = ' '; - cvs_output (buf, 2); - if (update_dir[0]) + buf[2] = '\0'; + cvs_output_tagged ("text", buf); + cvs_output_tagged ("fname", finfo->fullname); + cvs_output_tagged ("newline", NULL); + if (tag != NULL) { - cvs_output (update_dir, 0); - cvs_output ("/", 1); + sprintf (buf, "-%s", tag); + cvs_output_tagged (buf, NULL); } - cvs_output (file, 0); - cvs_output ("\n", 1); } - return (0); + return; } /* @@ -1590,7 +1637,8 @@ merge_file (finfo, vers) copy_file (finfo->file, backup); xchmod (finfo->file, 1); - if (strcmp (vers->options, "-kb") == 0) + if (strcmp (vers->options, "-kb") == 0 + || wrap_merge_is_copy (finfo->file)) { /* For binary files, a merge is always a conflict. We give the user the two files, and let them resolve it. It is possible @@ -1609,11 +1657,15 @@ merge_file (finfo, vers) (struct stat *) NULL, (unsigned char *) NULL); } #endif - error (0, 0, "binary file needs merge"); + /* Is there a better term than "nonmergeable file"? What we + really mean is, not something that CVS cannot or does not + want to merge (there might be an external manual or + automatic merge process). */ + error (0, 0, "nonmergeable file needs merge"); error (0, 0, "revision %s from repository is now in %s", vers->vn_rcs, finfo->fullname); error (0, 0, "file from working directory is now in %s", backup); - write_letter (finfo->file, 'C', finfo->update_dir); + write_letter (finfo, 'C'); history_write ('C', finfo->update_dir, vers->vn_rcs, finfo->file, finfo->repository); @@ -1621,7 +1673,7 @@ merge_file (finfo, vers) goto out; } - status = RCS_merge(vers->srcfile->path, + status = RCS_merge(finfo->rcs, vers->srcfile->path, finfo->file, vers->options, vers->vn_user, vers->vn_rcs); if (status != 0 && status != 1) { @@ -1684,7 +1736,7 @@ merge_file (finfo, vers) if (!noexec) error (0, 0, "conflicts found in %s", finfo->fullname); - write_letter (finfo->file, 'C', finfo->update_dir); + write_letter (finfo, 'C'); history_write ('C', finfo->update_dir, vers->vn_rcs, finfo->file, finfo->repository); @@ -1696,7 +1748,7 @@ merge_file (finfo, vers) } else { - write_letter (finfo->file, 'M', finfo->update_dir); + write_letter (finfo, 'M'); history_write ('G', finfo->update_dir, vers->vn_rcs, finfo->file, finfo->repository); } @@ -1731,14 +1783,6 @@ join_file (finfo, vers) jdate1 = date_rev1; jdate2 = date_rev2; - if (wrap_merge_is_copy (finfo->file)) - { - error (0, 0, - "Cannot merge %s because it is a merge-by-copy file.", - finfo->fullname); - return; - } - /* Determine if we need to do anything at all. */ if (vers->srcfile == NULL || vers->srcfile->path == NULL) @@ -2034,7 +2078,7 @@ join_file (finfo, vers) "failed to check out %s file", finfo->fullname); } #endif - + /* * The users currently modified file is moved to a backup file name * ".#filename.version", so that it will stay around for a few days @@ -2053,14 +2097,96 @@ join_file (finfo, vers) xchmod (finfo->file, 1); options = vers->options; -#ifdef HAVE_RCS5 #if 0 if (*options == '\0') options = "-kk"; /* to ignore keyword expansions */ #endif -#endif - status = RCS_merge (vers->srcfile->path, options, rev1, rev2); + /* If the source of the merge is the same as the working file + revision, then we can just RCS_checkout the target (no merging + as such). In the text file case, this is probably quite + similar to the RCS_merge, but in the binary file case, + RCS_merge gives all kinds of trouble. */ + if (vers->vn_user != NULL + && strcmp (rev1, vers->vn_user) == 0 + /* See comments above about how No_Difference has already been + called. */ + && vers->ts_user != NULL + && strcmp (vers->ts_user, vers->ts_rcs) == 0 + + /* This is because of the worry below about $Name. If that + isn't a problem, I suspect this code probably works for + text files too. */ + && (strcmp (options, "-kb") == 0 + || wrap_merge_is_copy (finfo->file))) + { + /* FIXME: what about nametag? What does RCS_merge do with + $Name? */ + if (RCS_checkout (finfo->rcs, finfo->file, rev2, NULL, options, + RUN_TTY, (RCSCHECKOUTPROC)0, NULL) != 0) + status = 2; + else + status = 0; + + /* OK, this is really stupid. RCS_checkout carefully removes + write permissions, and we carefully put them back. But + until someone gets around to fixing it, that seems like the + easiest way to get what would seem to be the right mode. + I don't check CVSWRITE or _watched; I haven't thought about + that in great detail, but it seems like a watched file should + be checked out (writable) after a merge. */ + xchmod (finfo->file, 1); + + /* Traditionally, the text file case prints a whole bunch of + scary looking and verbose output which fails to tell the user + what is really going on (it gives them rev1 and rev2 but doesn't + indicate in any way that rev1 == vn_user). I think just a + simple "U foo" is good here; it seems analogous to the case in + which the file was added on the branch in terms of what to + print. */ + write_letter (finfo, 'U'); + } + else if (strcmp (options, "-kb") == 0 + || wrap_merge_is_copy (finfo->file)) + { + /* We are dealing with binary files, but real merging would + need to take place. This is a conflict. We give the user + the two files, and let them resolve it. It is possible + that we should require a "touch foo" or similar step before + we allow a checkin. */ + if (RCS_checkout (finfo->rcs, finfo->file, rev2, NULL, options, + RUN_TTY, (RCSCHECKOUTPROC)0, NULL) != 0) + status = 2; + else + status = 0; + + /* OK, this is really stupid. RCS_checkout carefully removes + write permissions, and we carefully put them back. But + until someone gets around to fixing it, that seems like the + easiest way to get what would seem to be the right mode. + I don't check CVSWRITE or _watched; I haven't thought about + that in great detail, but it seems like a watched file should + be checked out (writable) after a merge. */ + xchmod (finfo->file, 1); + + /* Hmm. We don't give them REV1 anywhere. I guess most people + probably don't have a 3-way merge tool for the file type in + question, and might just get confused if we tried to either + provide them with a copy of the file from REV1, or even just + told them what REV1 is so they can get it themself, but it + might be worth thinking about. */ + /* See comment in merge_file about the "nonmergeable file" + terminology. */ + error (0, 0, "nonmergeable file needs merge"); + error (0, 0, "revision %s from repository is now in %s", + rev2, finfo->fullname); + error (0, 0, "file from working directory is now in %s", backup); + write_letter (finfo, 'C'); + } + else + status = RCS_merge (finfo->rcs, vers->srcfile->path, finfo->file, + options, rev1, rev2); + if (status != 0 && status != 1) { error (0, status == -1 ? errno : 0, |