summaryrefslogtreecommitdiffstats
path: root/contrib/cvs/src
diff options
context:
space:
mode:
authorpeter <peter@FreeBSD.org>1996-08-20 23:46:10 +0000
committerpeter <peter@FreeBSD.org>1996-08-20 23:46:10 +0000
commit8982e501c77217c860f79bba431f46a62b607a21 (patch)
tree70187fdf5be4cbefd0baf46bddac7e5e32c13c24 /contrib/cvs/src
parent01ee40fd6a76f6ff7ef247fc1b2cf6e337f216c5 (diff)
downloadFreeBSD-src-8982e501c77217c860f79bba431f46a62b607a21.zip
FreeBSD-src-8982e501c77217c860f79bba431f46a62b607a21.tar.gz
Import of slightly trimmed cvs-1.8 distribution. Generated files
and non-unix code has been left out.
Diffstat (limited to 'contrib/cvs/src')
-rw-r--r--contrib/cvs/src/ChangeLog1373
-rw-r--r--contrib/cvs/src/ChangeLog-9194524
-rw-r--r--contrib/cvs/src/ChangeLog-93953731
-rw-r--r--contrib/cvs/src/Makefile.in198
-rw-r--r--contrib/cvs/src/NOTES60
-rw-r--r--contrib/cvs/src/README-rm-add31
-rw-r--r--contrib/cvs/src/add.c514
-rw-r--r--contrib/cvs/src/admin.c168
-rw-r--r--contrib/cvs/src/checkin.c188
-rw-r--r--contrib/cvs/src/checkout.c889
-rw-r--r--contrib/cvs/src/classify.c493
-rw-r--r--contrib/cvs/src/client.c4490
-rw-r--r--contrib/cvs/src/client.h191
-rw-r--r--contrib/cvs/src/commit.c1824
-rw-r--r--contrib/cvs/src/create_adm.c140
-rw-r--r--contrib/cvs/src/cvs.h690
-rwxr-xr-xcontrib/cvs/src/cvsbug.sh528
-rw-r--r--contrib/cvs/src/cvsrc.c150
-rw-r--r--contrib/cvs/src/diff.c623
-rw-r--r--contrib/cvs/src/edit.c1020
-rw-r--r--contrib/cvs/src/edit.h42
-rw-r--r--contrib/cvs/src/entries.c548
-rw-r--r--contrib/cvs/src/error.c256
-rw-r--r--contrib/cvs/src/error.h47
-rw-r--r--contrib/cvs/src/expand_path.c241
-rw-r--r--contrib/cvs/src/fileattr.c517
-rw-r--r--contrib/cvs/src/fileattr.h125
-rw-r--r--contrib/cvs/src/filesubr.c662
-rw-r--r--contrib/cvs/src/find_names.c271
-rw-r--r--contrib/cvs/src/hash.c442
-rw-r--r--contrib/cvs/src/hash.h58
-rw-r--r--contrib/cvs/src/history.c1484
-rw-r--r--contrib/cvs/src/ignore.c405
-rw-r--r--contrib/cvs/src/import.c1207
-rw-r--r--contrib/cvs/src/lock.c639
-rw-r--r--contrib/cvs/src/log.c163
-rw-r--r--contrib/cvs/src/login.c392
-rw-r--r--contrib/cvs/src/logmsg.c521
-rw-r--r--contrib/cvs/src/main.c814
-rw-r--r--contrib/cvs/src/mkmodules.c742
-rw-r--r--contrib/cvs/src/modules.c900
-rw-r--r--contrib/cvs/src/myndbm.c288
-rw-r--r--contrib/cvs/src/myndbm.h47
-rw-r--r--contrib/cvs/src/no_diff.c129
-rw-r--r--contrib/cvs/src/options.h.in275
-rw-r--r--contrib/cvs/src/parseinfo.c162
-rw-r--r--contrib/cvs/src/patch.c604
-rw-r--r--contrib/cvs/src/rcs.c2262
-rw-r--r--contrib/cvs/src/rcs.h104
-rw-r--r--contrib/cvs/src/rcscmds.c188
-rw-r--r--contrib/cvs/src/recurse.c714
-rw-r--r--contrib/cvs/src/release.c286
-rw-r--r--contrib/cvs/src/remove.c200
-rw-r--r--contrib/cvs/src/repos.c147
-rw-r--r--contrib/cvs/src/root.c177
-rw-r--r--contrib/cvs/src/rtag.c682
-rw-r--r--contrib/cvs/src/run.c541
-rwxr-xr-xcontrib/cvs/src/sanity.sh2869
-rw-r--r--contrib/cvs/src/scramble.c246
-rw-r--r--contrib/cvs/src/server.c4642
-rw-r--r--contrib/cvs/src/server.h138
-rw-r--r--contrib/cvs/src/status.c279
-rw-r--r--contrib/cvs/src/subr.c318
-rw-r--r--contrib/cvs/src/tag.c781
-rw-r--r--contrib/cvs/src/update.c1830
-rw-r--r--contrib/cvs/src/update.h21
-rw-r--r--contrib/cvs/src/vers_ts.c354
-rw-r--r--contrib/cvs/src/version.c29
-rw-r--r--contrib/cvs/src/watch.c521
-rw-r--r--contrib/cvs/src/watch.h56
-rw-r--r--contrib/cvs/src/wrapper.c374
71 files changed, 47565 insertions, 0 deletions
diff --git a/contrib/cvs/src/ChangeLog b/contrib/cvs/src/ChangeLog
new file mode 100644
index 0000000..99161b0
--- /dev/null
+++ b/contrib/cvs/src/ChangeLog
@@ -0,0 +1,1373 @@
+Sun May 5 21:39:02 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * vers_ts.c (Version_TS): If sdtp is NULL, go ahead and check
+ RCS_getexpand for options. Fixes binaries and non-unix clients.
+ * sanity.sh: Fix binfiles-5.5 to test for the correct behavior
+ rather than the buggy behavior which existed when the binfiles-5.5
+ test was written.
+ (binfiles-14c,binfiles-14f): Likewise.
+
+Sun May 5 17:38:21 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ Integrated changes submitted by Ian Taylor <ian@cygnus.com>
+
+ * update.c (update_dirent_proc): cvs co -p doesn't print
+ anything when run from an empty directory.
+
+ * import.c (import_descend_dir): Check for a file in the
+ repository which will be checked out to the same name as the
+ directory.
+
+Thu May 2 13:34:37 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * Version 1.7.88
+
+Thu May 2 01:40:55 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * server.c (HAVE_INITGROUPS): Use initgroups() only if
+ located by configure, in the event a system has crypt(), but
+ no initgroups()
+
+Wed May 1 18:05:02 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh (basica): When testing rejection of reserved tag name,
+ use BASE instead of RESERVED.
+
+Wed May 1 15:15:11 1996 Tom Jarmolowski <tjj@booklink.com>
+
+ * rcs.c (linevector_delete): Only copy up to vec->nlines - nlines,
+ not to vec->nlines.
+
+Wed May 1 15:43:21 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * rcscmds.c (RCS_settag): Instead of reserving all tag names
+ containing only uppercase letters, reserve only BASE and HEAD.
+ * sanity.sh (mflag): Revert 26 Mar change; use all-uppercase tag
+ name again.
+
+Wed May 1 15:15:11 1996 Tom Jarmolowski <tjj@booklink.com>
+
+ * rcs.c (linevector_add): Move increment of i out of larger
+ statement, to avoid assumptions about evaluation order.
+
+Tue Apr 30 15:46:03 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.7.87.
+
+ * server.c (check_password): Don't use ANSI string concatenation.
+ Reindent function.
+
+Wed Apr 24 17:27:53 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * vers_ts.c (Version_TS): xmalloc enough space (1 more
+ byte). Thanks to purify!
+
+Fri Apr 19 11:22:35 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * Version 1.7.86
+
+Thu Apr 18 1996 Jim Kingdon <kingdon@cyclic.com>
+
+ * client.c (try_read_from_server): Compare return value from fwrite
+ with a size_t not an int (Visual C++ lint).
+
+Wed Apr 17 11:56:32 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (try_read_from_server): New function.
+ (read_from_server): Use it.
+ (read_counted_file): New function.
+ * client.c, server.c: Add Template response.
+ * cvs.h (CVSADM_TEMPLATE): Added.
+ * logmsg.c (do_editor): If repository is NULL, use CVSADM_TEMPLATE
+ file in place of rcsinfo.
+ * server.c, server.h (server_template): New function.
+ * create_adm.c (Create_Admin): Call it.
+
+Tue Apr 16 13:56:06 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * repos.c (Name_Repository): Fix comments.
+ * create_adm.c (Create_Admin): Fix indentation.
+
+Wed Apr 10 16:46:54 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * options.h.in: Include relevant information here rather than
+ citing (former) FAQ.
+
+ * ChangeLog-9395: Fix typo in introductory paragraph.
+
+Wed Apr 10 14:55:10 1996 code by Mike Spengler mks@msc.edu
+ comments by Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * filesubr.c (unlink_file_dir,deep_remove_dir): Don't call unlink
+ on something which might be a directory; check using isdir instead.
+
+Wed Apr 10 14:55:10 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * checkout.c (build_dirs_and_chdir): Pass path, not cp, to
+ Create_Admin. The former is the correct update dir.
+ * sanity.sh (modules): New tests modules-155* test, for above fix.
+
+Mon Apr 8 13:53:27 1996 Samuel Tardieu <sam@inf.enst.fr>
+
+ * rcs.c (annotate_fileproc): If the file is not under CVS control,
+ return instead of dumping a core. Don't bug on files with an empty
+ first revision.
+
+Fri Mar 29 16:08:28 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * rcs.c (annotate_fileproc): If last line of add-chunk is not
+ newline terminated, end the loop when we find that out.
+
+Fri Mar 29 16:59:34 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * rcs.c (annotate_fileproc): allow last line of add-chunk not to
+ be newline terminated
+
+Thu Mar 28 10:56:36 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ Add more diff tests:
+ * sanity.sh (basic2): Use dotest for test 61.
+ (basica): Add test basica-6.2.
+ (branches): Add tests branches-14.4 and branches-14.5.
+ (basic1): Remove tests 19, 20, 25, and 26. The only thing this
+ might miss out on is diff's interaction with added and removed
+ files, but those tests didn't test that very well anyway.
+
+ * rcs.c (RCS_getrevtime): Add comment regarding years after 1999.
+
+ * rcs.c: Add "cvs annotate" command and related code.
+ (getrcskey): Move special handling of RCSDESC from here to
+ callers. Handle those keys (desc, log, text) which do not
+ end in a semicolon.
+ * rcs.h (RCSVers): Add author field.
+ * rcs.c (RCS_reparsercsfile): Set it.
+ * cvs.h (annotate), main.c (cmd_usage, cmds), client.h client.c
+ (client_annotate), server.c (serve_annotate, requests): Usual
+ machinery to add a new command.
+ * sanity.sh (basica): Test cvs annotate.
+
+ * sanity.sh (branches): More tests, of things like adding files on
+ the trunk after a branch has been made.
+
+Tue Mar 26 09:48:49 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * expand_path.c: Don't declare free and xmalloc; cvs.h already
+ takes care of that.
+
+ * sanity.sh (mflag): Don't use tag name reserved to CVS.
+
+ NT local changes plus miscellaneous things noticed in the process:
+ * import.c (add_rcs_file): Use binary mode to write RCS file. Use
+ \012 where linefeed is intended. Copy data a small block at a
+ time, until we hit EOF, rather than trying to read the whole file
+ into memory at once.
+ * client.c (send_modified): Add comments regarding st_size.
+ * commit.c (commit): Add comments regarding binary mode and read().
+ * logmsg.c (do_editor): Add comments regarding st_size.
+ * server.c (server_updated): Use binary mode to read file we are
+ sending.
+
+ * rcscmds.c (RCS_settag): Complain if user tries to add a tag name
+ reserved to CVS.
+ * sanity.sh (basica): Test for this behavior.
+
+ * sanity.sh (binfiles): New tests test ability to change keyword
+ expansion.
+
+Mon Mar 25 1996 Jim Kingdon <kingdon@cyclic.com>
+
+ * cvs.h, filesubr.c (expand_wild): New function.
+ * recurse.c (start_recursion): Call expand_wild at beginning and
+ free its results at the end.
+ * cvs.h, subr.c (xrealloc): Make argument and return value void *.
+ * client.h, client.c (send_file_names): Add flags argument. If
+ SEND_EXPAND_WILD flag is passed, call expand_wild at beginning and
+ free its results at the end.
+ * admin.c, add.c, log.c, tag.c, status.c, edit.c, watch.c,
+ update.c, commit.c, remove.c, client.c, diff.c: Update callers.
+
+Fri Mar 22 10:09:55 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * error.c (error, fperror): Exit with status EXIT_FAILURE rather
+ than STATUS. We had been neglecting to check for 256, and the
+ value of providing a count of errors is probably minimal anyway.
+ * add.c, modules.c, mkmodules.c, tag.c, server.c, main.c,
+ import.c, client.c, scramble.c, recurse.c: Exit with status
+ EXIT_FAILURE rather than 1. On VMS, 1 is success, not failure.
+ * main.c (main): Return EXIT_FAILURE or 0. The value of providing
+ a count of errors is minimal.
+
+ * client.c (init_sockaddr): Exit with status 1 rather than
+ EXIT_FAILURE. The latter apparently doesn't exist on SunOS4.
+ Reindent function.
+
+Mon Mar 18 14:28:00 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.h, ignore.c: New variable ign_case.
+ * ignore.c (ign_name): If it is set, match in a case-insensitive
+ fashion.
+ * server.c (serve_case): New function.
+ (requests): Add Case request.
+ * client.c (start_server): If FILENAMES_CASE_INSENSITIVE is
+ defined, send Case request.
+
+Sat Mar 16 08:20:01 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ For reference, this change takes cvs's text segment from 315392
+ bytes to 311296 bytes (one 4096 byte page).
+ * cvs.h (struct file_info): Add fullname field.
+ * recurse.c (do_file_proc): Set it.
+ * commit.c (find_fileproc), client.c (send_fileproc), commit.c
+ (check_fileproc), diff.c (diff_fileproc), edit.c
+ (unedit_fileproc), patch.c (patch_fileproc), remove.c
+ (remove_fileproc), rtag.c (rtag_fileproc), tag.c (tag_fileproc),
+ update.c (update_fileproc), watch.c (watchers_fileproc): Use it
+ instead of computing it each time.
+ * diff.c (diff_fileproc), remove.c (remove_fileproc): Use fullname
+ where we had been (bogusly) omitting the directory from user
+ messages.
+ * edit.c (unedit_fileproc, edit_fileproc): If we cannot close
+ CVSADM_NOTIFY, mention CVSADM_NOTIFY rather than finfo->file in
+ error message.
+ * rtag.c (rtag_fileproc), tag.c (tag_fileproc): Reindent.
+
+Fri Mar 15 15:12:11 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * server.h: fix prototype of server_pause_check (was
+ server_check_pause)
+
+Thu Mar 14 1996 Jim Kingdon <kingdon@cyclic.com>
+
+ * vers_ts.c (Version_TS), entries.c (Scratch_Entry, AddEntryNode):
+ Change findnode to findnode_fn.
+
+ * main.c: Depending on HAVE_WINSOCK_H, include winsock.h or
+ declare gethostname.
+ * cvs.h: Don't declare it here.
+
+Thu Mar 14 07:06:59 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * commit.c (find_fileproc): If vn_user is NULL and ts_user is not,
+ print an error rather than silently succeeding.
+ * sanity.sh (basica-notadded): New test, for above fix.
+ (dotest_internal): New function.
+ (dotest,dotest_fail): Call it instead of duplicating code between
+ these two functions.
+
+ * sanity.sh: Skip tests binfiles-9 through binfiles-13 for remote.
+
+ * options.h.in: Adjust comment to reflect kfogel change.
+
+Thu Mar 14 01:38:30 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * options.h.in (AUTH_CLIENT_SUPPORT): turn on by default.
+
+Wed Mar 13 09:25:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * vers_ts.c (Version_TS): Don't try to override options from rcs
+ file if there isn't an rcs file (e.g. called from send_fileproc).
+ This fixes a bug detected by test 59 in "make remotecheck".
+
+ * rcs.c (RCS_reparsercsfile, RCS_getexpand): Assert that argument
+ is not NULL.
+
+ Fix a gcc -Wall warning:
+ * rcs.c, rcs.h (RCS_getexpand): New function.
+ * vers_ts.c (Version_TS): Call it.
+ * rcs.c (RCS_reparsercsfile): Make static.
+
+ Add a "cvs init" command. This is needed because cvsinit.sh
+ invoked mkmodules which doesn't exist any more.
+ * mkmodules.c: Break filelist out of mkmodules function, rename
+ struct _checkout_file to struct admin_file (for namespace
+ correctness), and add contents field.
+ (init,mkdir_if_needed): New functions.
+ * cvs.h (init): Declare.
+ * main.c (cmds): Add init.
+ (main): If command is init, don't require cvsroot to exist.
+ * client.c, client.h (client_init, send_init_command): New functions.
+ * client.c (start_server): Don't send Root request if command is init.
+ * server.c (serve_init): New function.
+ (requests): Add "init".
+
+Wed Mar 13 09:51:03 MET 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * vers_ts.c (Version_TS): set options to default option if the
+ file if no -k option but -A was given. This avoids the (wrong)
+ update message for binary files which are up-to-date when
+ running 'cvs -A'.
+
+ * update.c (checkout_file): remove test of -k option stored in the
+ file itself because it was moved to vers_ts.c
+
+ * sanity.sh: added tests for the above fix.
+
+Tue Mar 12 13:47:09 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * hash.c (findnode): Adjust comment regarding errors.
+
+ * hash.c (findnode, findnode_fn): Assert that key != NULL. This
+ way the check still happens even if the function is later
+ rewritten to not start out by calling hashp.
+
+Mon Mar 11 10:21:05 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: If expr accepts multi-line patterns but is too
+ liberal in matching them, print a warning but keep going.
+
+ * sanity.sh: Add QUESTION variable, analogous to PLUS. Use it
+ instead of \? to match a question mark.
+
+ * cvs.h (CVSMODULE_OPTS, CVSMODULE_SPEC): Move from here...
+ * modules.c: ...to here. They are only used here and the code to
+ handle the syntax of modules files should not be scattered all over.
+ * modules.c (CVSMODULE_OPTS): Add "+" as first character.
+ * sanity.sh (modules): New tests 148a0 and 148a1 test for
+ above-fixed bug.
+
+Mon Mar 11 13:11:04 1996 Samuel Tardieu <sam@inf.enst.fr>
+
+ * modules.c (cat_module): set optind to 0 to force getopt() to
+ reinitialize its internal nextchar
+
+Mon Mar 11 00:09:14 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * hash.c (findnode, findnode_fn): Revert changes of 7-8 Mar 1996.
+ The correct style is to assert() that key != NULL (see HACKING),
+ which is already done in the hashp function.
+ * fileattr.c (fileattr_delproc): Likewise, assert() that
+ node->data != NULL rather than trying to deal with it being NULL.
+
+Fri Mar 8 01:31:04 1996 Greg A. Woods <woods@most.weird.com>
+
+ * hash.c (findnode_fn): one more place to avoid calling hashp()
+ with a NULL key
+
+Thu Mar 7 17:30:01 1996 Greg A. Woods <woods@most.weird.com>
+
+ * hash.c (findnode): also return NULL if key is not set
+ [[ reported by Chris_Eich@optilink.optilink.dsccc.com, and
+ supposedly in a PR that should be marked "fixed"..... ]]
+
+ * fileattr.c (fileattr_set): set node->data to NULL after freeing
+ it to prevent subsequent accesses
+ (fileattr_delproc): don't free node->data if it's NULL, and set it
+ to NULL after freeing
+ [[ reported by Chris_Eich@optilink.optilink.dsccc.com, and
+ supposedly in a PR that should be marked "fixed"..... ]]
+
+Fri Mar 1 14:56:08 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh (basica): New test basica-4a tests for bug fixed by
+ sam@inf.enst.fr on 1 Mar 96.
+
+Fri Mar 1 18:10:49 1996 Samuel Tardieu <sam@inf.enst.fr>
+
+ * tag.c (check_fileproc): Check for file existence before trying
+ to tag it.
+
+Fri Mar 1 07:51:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (update_entries): If command is export, set options to
+ NULL.
+
+Thu Feb 29 16:54:14 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * lock.c (write_lock, Reader_Lock): Remove
+ BOGUS_UNLESS_PROVEN_OTHERWISE code. It was pretty bogus, and has
+ been ifdeffed out for a long time.
+ * cvs.h (CVSTFL): Removed; no longer used.
+
+ * cvsrc.c, cvs.h (read_cvsrc): Pass in command name rather than
+ using global variable command_name.
+ * main.c (command_name): Initialize to "", not "cvs" so that error
+ messages don't say "cvs cvs". Update calls to read_cvsrc to pass
+ in command_name or "cvs" as appropriate.
+ * sanity.sh (basica): New test basica-9 tests for above-fixed bug.
+
+ * lock.c: Rename unlock to lock_simple_remove to avoid conflict
+ with builtin function on QNX.
+
+Thu Feb 29 17:02:22 1996 Samuel Tardieu <sam@inf.enst.fr>
+
+ * fileattr.c (fileattr_get): Removed NULL pointer dereference
+ which occurred in the absence of default attribute.
+
+Thu Feb 29 07:36:57 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * rcs.c (RCS_isbranch, RCS_whatbranch): Remove no longer used file
+ argument, swap order of remaining two arguments to be like other
+ RCS_* functions.
+ (RCS_nodeisbranch): swap order of arguments to be like other RCS_*
+ functions.
+ * rcs.h (RCS_isbranch, RCS_whatbranch, RCS_nodeisbranch): Update
+ prototypes for above changes.
+ * commit.c, rtag.c, status.c, tag.c: Update for above calling
+ convention changes.
+
+Thu Feb 29 08:39:03 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (start_server): Revert changes which claimed to fall
+ back to a different way of connecting. Add comments explaining
+ why. (I don't think the changes did what they claimed, anyway).
+ Use indentation rather than comments to line up #if, #else, and
+ #endif.
+
+ * patch.c (patch, patch_fileproc): Revert change to add optional
+ arguments to -c and -u. Optional arguments are evil and in
+ violation of the POSIX argument syntax guidelines. The correct
+ way to do this is -C and -U. Also change DIFF back to "diff" in
+ output (see comments).
+
+ gcc -Wall lint:
+ * client.c (copy_a_file): Declare p inside the #ifdef in which is
+ it used.
+ * commit.c (remove_file): Remove unused variable p.
+ * commit.c (checkaddfile): Remove unused variables p.
+ * rcs.c (RCS_isbranch): Remove unused variable p.
+ * rcs.c: Remove unused declarations and definitions of
+ parse_rcs_proc, rcsnode_delproc, rcslist, and repository.
+ * rtag.c (rtag_fileproc): Remove unused variable p.
+ * patch.c (patch_fileproc): Remove unused variable p.
+ * tag.c (val_fileproc): Remove unused variable node.
+ * client.c, import.c, lock.c, server.c: Cast pid_t to long before
+ passing it to %ld.
+
+ * cvs.h: Don't prototype gethostname; merely declare it (on linux,
+ second argument is size_t not int).
+
+Thu Feb 29 10:29:25 MET 1996 Norbert Kiesel (nk) <nk@col.sw-ley.de>
+
+ * sanity.sh: added "cat > /dev/null" to loginfo entry to avoid the
+ SIGPIPE signal
+
+Thu Feb 29 10:28:25 MET 1996 Norbert Kiesel (nk) <nk@col.sw-ley.de>
+
+ * patch.c: added new variable diff_opt
+ (patch): allow optional parameter to -c and -u option, send it to
+ server
+ (patch_fileproc): cleaned up the code which prints the current
+ filename. For "-s" option, print the pathname relative to CVSROOT
+ instead of just the filename.
+
+ * filesubr.c (xchmod): added cast to shut up gcc
+
+ * cvs.h: added prototype for gethostname
+
+Thu Feb 29 10:27:25 MET 1996 Norbert Kiesel (nk) <nk@col.sw-ley.de>
+
+ * lock.c (write_lock), (Reader_Lock), import.c (update_rcs_file),
+ client.c (update_entries), (send_modified), server.c (server),
+ (receive_file), (server_updated): use %ld for printing pid_t
+ variables
+
+Thu Feb 29 02:22:12 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * run.c (run_exec): Added VMS return status support.
+
+Thu Feb 29 01:07:43 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * client.c (send_to_server): wrtn wasn't being declared under
+ VMS for some reason.
+
+Wed Feb 28 23:27:04 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * client.c: Changed #ifdef VMS && NO_SOCKET_TO_FD to
+ #if defined(VMS) && defined(NO_SOCKET_TO_FD)
+
+Wed Feb 28 22:28:43 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * build_src.com: Added DCL command procedure to build
+ and link CVS client for VMS.
+
+Wed Feb 28 22:07:20 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * client.c: VMS CVS client specific changes.
+
+ Added USE_DIRECT_TCP to allow CVS_PORT to be used to specify
+ a TCP connection port (no Kerberos). Changed
+ start_kerberos_server() to start_tcp_server().
+
+ In copy_a_file(): transform a backup file to have a
+ VMS-friendly name.
+
+ Added HAVE_CONFIG_H to include "config.h".
+
+ start_server() will starts the first successful of any
+ mutually exclusive methods of starting the CVS server
+ which might be enabled.
+
+ Initialized use_socket_style and server_sock for VMS in
+ start_server().
+
+Wed Feb 28 21:49:48 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * find_names.c, recurse.c, cvs.h: Changed Find_Dirs() to
+ Find_Directories().
+ * cvs.h: Added VMS filenames enabled through USE_VMS_FILENAMES
+ VMS POSIX will require to use the regular CVS filenames
+ while VMS is #define'd.
+
+Wed Feb 28 21:26:22 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * ignore.c: Added the patterns *.olb *.exe _$* *$ to default
+ ignore list for VMS.
+
+Wed Feb 28 13:32:28 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * logmsg.c (do_editor): Fix indentation.
+
+Wed Feb 28 12:56:49 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * logmsg.c (do_editor): If no editor is defined, exit and print
+ a message.
+
+Wed Feb 28 10:40:25 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * vers_ts.c (time_stamp, time_stamp_server): Reindent and revise
+ comments.
+
+Tue Feb 27 23:57:55 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * vers_ts.c: gmtime() returns NULL on some systems (VMS)
+ revert to local time via ctime() if GMT is not avaiable.
+
+Tue Feb 27 13:07:45 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ The changes listed below cause cvs to parse each rcs file (and
+ free the associated rcsnode after the file has been processed)
+ sequentially. cvs used to parse all files in a directory, an
+ approach that does not scale to huge repositories with lots
+ of revisions/branches/tags/etc.
+
+ * cvs.h (struct file_info): Removed srcfiles field. Added rcs
+ (node) field.
+ * recurse.c (do_recursion): Removed code that pre-parsed all
+ rcs files in the directory.
+ (do_file_proc): Parse current rcs file.
+ * rcs.c (RCS_parsefiles, parse_rcs_proc, RCS_addnode): Removed.
+ (RCS_isbranch, RCS_whatbranch): Changed srcfiles argument to
+ rcs (node).
+ * rcs.h (RCS_parsefiles, RCS_addnode): Removed prototypes.
+ (RCS_isbranch, RCS_whatbranch): Updated prototypes.
+ * add.c, admin.c, checkin.c, checkout.c, classify.c, client.c,
+ commit.c, diff.c, history.c, import.c, log.c, patch.c, remove.c,
+ rtag.c, status.c, tag.c, update.c, vers_ts: Updated for above
+ calling convention / data structure changes.
+
+Mon Feb 26 16:07:56 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.7.3.
+
+ * Version 1.7.2.
+
+Mon Feb 26 1996 Jim Kingdon <kingdon@cyclic.com>
+
+ * recurse.c (start_recursion): Use last_component rather than
+ checking for '/' directly.
+ (do_dir_proc): Likewise.
+
+ Visual C++ lint:
+ * client.c (send_to_server): Change wrtn to size_t.
+ (connect_to_pserver): Put tofd and fromfd declarations inside
+ #ifndef NO_SOCKET_TO_FD.
+ * scramble.c (shifts): Change from array of char to array of
+ unsigned char.
+
+Mon Feb 26 13:31:25 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * server.c (check_repository_password): Remove unused variables
+ linelen, ch.
+
+ * client.c (send_file_names): Translate ISDIRSEP characters to '/'.
+
+Sat Feb 24 21:25:46 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * checkout.c (safe_location): Re-indent one line.
+
+Sat Feb 24 10:50:42 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * checkout.c (safe_location): put assignment to hardpath[x] in an
+ `else'-clause, so we don't do it when x == -1.
+
+Sat Feb 24 01:40:28 1996 Marcus Daniels <marcus@sayre.sysc.pdx.edu>
+ via Karl Fogel <kfogel@floss.red-bean.com>
+
+ * server.c (check_repository_password): Return by reference an
+ optional username, the `host_user', from the passwd file. The
+ host_user will be the user-id under which the cvs repository is
+ run.
+ (check_repository_password): Use `read_line' instead of fgets to
+ allow for passwords larger than 32 characters, as well as the
+ optional host user argument.
+ (check_password): Modify to use host_user.
+ (authenticate_connection): Modify to use host_user.
+
+Sat Feb 24 01:05:21 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * scramble.c (descramble): just shift descrambled string to get
+ rid of tag char, instead of allocating a whole new copy.
+ (scramble): cast return value of xmalloc to avoid unsightly
+ compiler warnings.
+
+ * options.h.in (RCSBIN_DFLT): don't refer to AUTH_SERVER_SUPPORT
+ in comment anymore, now that it's not defined in this file.
+
+Fri Feb 23 1996 Jim Kingdon <kingdon@cyclic.com>
+
+ * client.c: Ifdef HAVE_WINSOCK_H, include winsock.h
+ instead of sys/socket.h and friends.
+ * login.c: Don't include sys/socket.h and friends.
+ * login.c (login): Only fclose fp in the case where it was
+ successfully fopen'd.
+ * login.c: Declare getpass.
+ * filesubr.c, cvs.h (get_homedir): New function.
+ * cvsrc.c, expand_path.c, history.c, login.c: Call it instead
+ of getenv ("HOME").
+
+Fri Feb 23 09:23:20 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (connect_to_pserver): Remove unused variable host.
+ * login.c: Include getline.h.
+ (login): Remove unused variables i and username.
+ (get_cvs_password): Move free of linebuf to where it actually will
+ be called. Add a "return NULL" at the end of the function to shut
+ up gcc -Wall.
+
+ * options.h.in: Remove AUTH_SERVER_SUPPORT.
+ * client.h (authenticate_connection): Declare.
+ * scramble.c (scramble): Cast char to unsigned char before using
+ it to look up in table (char might be signed).
+ * server.c [AUTH_SERVER_SUPPORT]: Include grp.h
+ (authenticate_connection): Remove unused variables len and
+ server_user.
+
+ * sanity.sh (basica): Add comments regarding creating a top-level
+ directory.
+ (basic1): Don't try to remove first-dir and
+ ${CVSROOT_DIRNAME}/first-dir at start of test; tests are now
+ responsible for cleaning up at the end.
+ (PLUS,DOTSTAR,ENDANCHOR): Add comments regarding fixed GNU expr.
+
+Thu Feb 22 22:34:11 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.h: Remove alloca cruft.
+
+Wed Feb 21 07:30:16 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * modules.c (do_module): call free_cwd before exiting.
+
+ * recurse.c: Removed entries global variable.
+ (do_recursion): Declare entries. Moved call to Entries_Close so
+ entries list is closed on all code paths.
+ (start_recursion): Removed call to Entries_Close, entries list has
+ been moved to do_recursion only.
+
+Tue Feb 20 22:10:05 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * update.c (update_dirent_proc): If dir lacks a CVS subdirectory,
+ don't recurse into it.
+ * sanity.sh (conflicts): Test for above-fixed bug.
+
+ * update.c (merge_file): Use write_letter not printf.
+
+Tue Feb 20 12:34:07 EST 1996: Gary Oberbrunner <garyo@avs.com>
+ and Jim Kingdon <kingdon@cyclic.com>
+
+ * history.c (history_write): Change username to char * and call
+ getcaller() to set it. Setting username accidentally got deleted
+ 8 Feb 96.
+ * sanity.sh: Revise test 64 to test for above-fixed bug.
+ * sanity.sh (PLUS): New variable, work around yet another GNU expr
+ bug.
+
+Tue Feb 20 14:07:50 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: Merge test rtags into test basic2. They never were
+ capable of running separately of each other.
+
+ * sanity.sh (deep): New test, to test ability to operate in deeply
+ nested directories (more quickly than basic2 test did).
+ (basic2,rtags): Remove directories dir3 and dir4. Remove file8,
+ file10, file12, file9, file11, file13, file15, file16, file17.
+ These additional files slowed down the tests considerably without
+ significantly increasing coverage.
+
+ * sanity.sh (PROG): New variable. Use it instead of "cvs"
+ to match the name cvs prints out for itself.
+
+Mon Feb 19 09:00:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ This fixes a bug whereby old default attributes would get
+ clobbered instead of added to on "cvs watch add".
+ * hash.c (findnode): Don't check for key == NULL; let the
+ assertion in hashp take care of it.
+ * fileattr.h, fileattr.c (fileattr_get): If filename is NULL,
+ return default attributes.
+
+ * client.c (send_repository): Fix indentation.
+
+Mon Feb 19 01:10:01 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * login.c (login): print out full repos so user knows which server
+ she's logging into.
+
+ * client.c (send_repository): die if `repos' is NULL. This is a
+ lame solution; see comments in code.
+
+Thu Feb 15 15:04:01 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * error.c (error): Free entire and mess when done with them.
+
+ * sanity.sh (info): Correct syntax of .cvsrc file.
+
+ * cvs.h, expand_path.c, edit.c, parseinfo.c, wrapper.c:
+ expand_path now takes arguments containing file and line for error
+ message, and it prints the error message itself.
+ * sanity.sh (info-6a): Test printing of error message.
+
+ * expand_path.c (expand_variable): Add USER internal variable.
+ * sanity.sh (info): Test USER and CVSROOT internal variables too.
+
+Wed Feb 14 19:11:08 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * main.c (usg): Add -s option.
+
+Tue Feb 13 20:26:06 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ gcc -Wall lint:
+ * mkmodules.c (mkmodules_usage): Remove declaration of
+ non-existent function.
+ * cvs.h (mkmodules): Declare.
+
+Mon Feb 12 12:20:04 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * mkmodules.c: Rename main to mkmodules and remove various pieces
+ of scaffolding which it used to emulate non-existent parts of CVS.
+ Change calling convention to just take a char * not argc,argv.
+ Save and restore working directory.
+ * commit.c (commit_filesdoneproc): Call it if checking files into
+ CVSROOT.
+ * Makefile.in (SOURCES): Add mkmodules.c.
+ (OBJECTS): Add mkmodules.o.
+ (MSOURCES,MOBJECTS): Removed.
+ (COMMON_OBJECTS): Removed; move former contents into OBJECTS.
+ Update other rules accordingly.
+ * sanity.sh: Adjust to reflect nonexistence of mkmodules.
+
+ These changes introduce functions cvs_output and cvs_outerr;
+ eventually all server output will go through them rather than
+ stdio directly.
+ * server.c (saved_output, saved_outerr): New variables.
+ (do_cvs_command): Initialize them.
+ (buf_output): Don't require that buf->output be set; saved_* use
+ this to shove some data in a buffer which buf_copy_lines will
+ later want to get data from.
+ * server.c, cvs.h (cvs_output, cvs_outerr): New functions.
+ * mkmodules.c (cvs_outerr): New function, so error() works.
+ * error.c: Reindent. Don't declare program_name and command_name;
+ cvs.h declares them.
+ (error): Use vasprintf and cvs_outerr (or fputs in the
+ error_use_protocol case) rather than stdio directly.
+ * import.c (import_descend_dir): Remove kludge which had prevented
+ messages from error() from being out of order with respect to
+ messages from printf; cvs_output and cvs_outerr are a cleaner
+ solution to the problem.
+ (add_log, import): Use cvs_output not printf.
+ * update.c (write_letter): Use cvs_output not printf.
+ (checkout_file): Use write_letter not printf.
+ * sanity.sh: Use dotest for test 56 (test that output is actually
+ correct). In theory should test that the import.c bug is fixed,
+ but I was unable to reproduce the bug (it is timing dependent).
+
+Mon Feb 12 16:07:45 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * commit.c: define last_register_time
+ (commit): make sure cvs doesn't exit in the same second it wrote
+ the last timestamp
+ (commit_fileproc): set last_register_time
+ (finaladd): set last_register_time
+
+ * run.c, cvs.h: Changed more Popen() to run_popen()
+
+Mon Feb 12 03:06:50 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * release.c, rtag.c, tag.c: changed 'delete' to 'delete_flag'
+ to avoid symbol collision with DEC C RTL function delete()
+
+Mon Feb 12 03:01:48 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * mkmodules.c: changed 'void Lock_Cleanup()' to 'void static
+ Lock_Cleanup() to avoid conflict with more substantial
+ Lock_Cleanup() in lock.c
+
+Mon Feb 12 02:50:19 1996 Benjamin J. Lee <benjamin@cyclic.com>
+
+ * edit.c, logmsg.c, release.c, run.c: Changed Popen() to
+ run_popen(). VMS' linker is not case sensitive and considered
+ popen() and Popen() to be identical symbols.
+
+Sun Feb 11 10:51:14 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * main.c (main) [!CLIENT_SUPPORT]: Silently ignore gzip level
+ rather than printing usage message.
+
+ * cvs.h, expand_path.c (variable_list): New variable.
+ (variable_set): New function.
+ * hash.h (enum ntype), hash.c (nodetypestring): Add VARIABLE.
+ * expand_path.c (expand_path, expand_variable): Reindent.
+ (expand_variable): Use user variables not environment variables
+ for ${=VAR} syntax. The environment variables didn't work
+ client/server.
+ * main.c (main): Process new -s global option.
+ * client.c (send_variable_proc): New function.
+ (start_server): Call it, to send user variables.
+ * server.c (serve_set): New function.
+ (requests): Add Set request.
+ * sanity.sh: Revise info test to use user variables rather than
+ environment variables.
+
+Sat Feb 10 16:55:37 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ By itself this is only a small cleanup, but in the long run it
+ will be way cool (for reference, it takes CVS's text segment from
+ 290816 bytes to 294912, which I expect will be made up by future
+ changes which this enables):
+ * cvs.h (struct file_info): Added.
+ (FILEPROC): Replace 5 args with single struct file_info *.
+ * recurse.c (do_file_proc): Adjust args to fileproc; passed in
+ instead of from globals.
+ (do_recursion): Call do_file_proc accordingly. Remove srcfiles
+ global variable.
+ * update.c (update_fileproc): Renamed from update_file_proc.
+ * admin.c, client.c, commit.c, diff.c, edit.c, log.c, patch.c,
+ remove.c, rtag.c, status.c, tag.c, update.c, watch.c: Update
+ fileprocs to new calling convention.
+
+Fri Feb 9 15:30:32 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * expand_path.c (expand_variable): Accept a variable name starting
+ with '=' as a way to specify an environment variable.
+ * sanity.sh (info): New tests, for above behavior.
+
+ * Makefile.in (clean): Also remove check.log check.plog.
+
+ * import.c (comtable): Remove SYSTEM_COMMENT_TABLE; the table
+ should *not* depend on what kind of machine the server happens to
+ be. Add "mak", "rc", "dlg", "frm", and "bas" types which were
+ formerly included via SYSTEM_COMMENT_TABLE.
+
+ * cvs.h, rcs.h, add.c, checkin.c, classify.c, commit.c, diff.c,
+ import.c, patch.c, rcs.c, update.c, vers_ts.c: Remove
+ DEATH_SUPPORT ifdefs. They were introduced to facilitate merging
+ between Cygnus and Berliner variants of CVS, not because it was
+ intended to subset CVS this way. And they clutter up the code
+ quite a bit.
+ * cvs.h, create_adm.c, main.c, update.c: Likewise, remove
+ CVSADM_ROOT ifdefs (it is still a #define, of course). I believe
+ they had a more-or-less similar motivation.
+
+ * sanity.sh: Move setting of HOME from ignore test to the start of
+ the tests so it applies to all tests.
+ (CVS): Remove -f; the above change takes care of it.
+
+ * rcs.h (RCS_MERGE): Removed; unused.
+
+ * commit.c (checkaddfile): Fix memory leak.
+
+ * admin.c, commit.c, diff.c, log.c, mkmodules.c: Pass -x,v/ to RCS
+ commands.
+
+ * rcscmds.c, cvs.h (RCS_checkin): New function.
+ * checkin.c, commit.c, import.c: Call it, rather than run_*.
+ * cvs.h, commit.c: Remove DEATH_STATE define; the behavior
+ which used to be the default (DEATH_STATE) is now the only one.
+ Failing to define DEATH_STATE has been commented as obsolete at
+ least since CVS 1.5. We still can read repositories created with
+ such a CVS, however.
+ * rcs.h, rcs.c: Adjust comments regarding DEATH_STATE.
+ * subr.c (make_message_rcslegal): Add comment, describing
+ allocation of returned value.
+
+Fri Feb 9 09:53:44 MET 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * sanity.sh: use "${testcvs}" instead of "cvs" in devcom tests
+
+ * hash.c: fix "dereferencing a NULL pointer" bug triggered with
+ "cvs watch add"
+ (findnode): return NULL if key == NULL
+ (hashp): assert (key != NULL)
+
+Fri Feb 9 00:46:47 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * rcs.c (RCS_reparsercsfile): Remove unused variable date.
+
+ * myndbm.c (mydbm_load_file): Fix typo ('015' -> '\015').
+
+Thu Feb 8 13:00:00 1996 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ * rcs.c (RCS_parse, RCS_parsercsfile, RCS_reparsercsfile),
+ fileattr.c (fileattr_read), myndbm.c (myndbm_open):
+ Use FOPEN_BINARY_READ.
+ * fileattr.c (fileattr_write), myndbm.c (myndbm_close):
+ Use FOPEN_BINARY_WRITE.
+ * history.c (history_write, read_hrecs): Specify OPEN_BINARY.
+ * rcs.c: Remove calls to abort.
+ * myndbm.c (myndbm_load_file): Ignore CRs from ends of lines
+ if present.
+ * myndbm.c, fileattr.c: While I am at it, change \n to \012
+ a few places where LF is intended.
+ * history.c (history_write): Use getenv ("HOME"), not getpwnam,
+ to find home directory. If it isn't set, just keep going; don't
+ print a message.
+ * rcscmds.c, cvs.h (RCS_checkout): New function.
+ * update.c, checkin.c, commit.c, diff.c, import.c, no_diff.c,
+ patch.c: Call it instead of run_*.
+ * patch.c (patch_fileproc): Clean up inconsistent handling of
+ noexec flag.
+ * rcscmds.c (RCS_*): Pass -x,v/ to RCS commands; elsewhere in
+ CVS it is assumed that ,v is a suffix.
+
+Fri Feb 2 14:07:32 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * rcs.h (struct rcsnode): Remove dates field (list of rcsversnodes
+ indexed by date). CVS maintained this list for each RCS file even
+ though it was never used. This resulted in higher then necessary
+ memory requirements (and run time too). Even if revision info was
+ needed, CVS' List data structure is inappropriate because can't
+ handle duplicate keys. The above was discovered by tracking down
+ a memory leak.
+ * rcs.c (RCS_reparsercsfile): Don't build dates list.
+ (freercsnode): Don't delete dates list.
+ (rcsvers_delproc): Free date field.
+ (null_delproc): Removed.
+
+Thu Feb 1 12:28:33 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * remove.c (cvsremove): Don't tell user the name of the program
+ which they use to remove files; we don't have any way of knowing
+ that, and besides which they might use a GUI or emacs 'dired' anyway.
+ * update.c (update_filesdone_proc, update_dirleave_proc): Call
+ unlink_file_dir instead of rm -rf.
+ * options.h.in: Remove RM; no longer used.
+
+ * sanity.sh: New tests devcom-a* test "cvs watch add",
+ "cvs watch remove", and "cvs watchers".
+
+ * sanity.sh: New test 171a0 tests for watch.c bug just fixed by kfogel.
+
+ * Most .c files: Remove rcsids.
+ * cvs.h: Remove USE macro.
+
+Thu Feb 1 13:07:15 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * tag.c, rtag.c: Update various comments to reflect function name
+ changes.
+
+Thu Feb 1 14:14:31 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * recurse.c (do_recursion): comment #endif.
+
+ * edit.c (notify_check): surround with #ifdef CLIENT_SUPPORT; else
+ CVS won't compile if CLIENT_SUPPORT is undefined.
+
+ * edit.h (notify_check): surround declaration with #ifdef
+ CLIENT_SUPPORT.
+
+ * watch.c (watch): if argc <= 1, then just give usage (previously
+ was "argc == -1").
+
+Thu Feb 1 12:28:33 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * README-rm-add: Remove information which is now in cvs.texinfo.
+
+ * sanity.sh: Remove basic0 tests. Move a few to new tests
+ basica-1a* (but there is no need to test that *every* command
+ gracefully does nothing on an empty directory; exhaustive testing
+ is impractical and the generic recursion processor handles this
+ anyway).
+
+ * sanity.sh: New tests 69a* test use of update -p to restore old
+ version of dead file.
+
+Wed Jan 31 18:32:34 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * ChangeLog-9395: Remove duplicate entries from 1996 which
+ accidentally got into this file.
+
+ * client.c (read_line, read_from_server): Change "premature end of
+ file from server" message to "end of file from server (consult
+ above messages if any)" because 99% of the time it means rsh has
+ printed an error message and exited.
+
+Wed Jan 31 15:09:51 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * edit.c (ncheck_fileproc): Fix memory leak; free line before
+ returning.
+
+Tue Jan 30 18:06:12 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * recurse.c (do_recursion): Add comment about the fact that we
+ don't have locks in place at certain points.
+
+Tue Jan 30 09:43:34 1996 Vince Demarco <vdemarco@bou.shl.com>
+
+ * edit.c (notify_proc): have notify_proc call expand_path with
+ the name of the filter program. The user may have used a
+ cvs environmental variable. (Popen will expand it, but it may not
+ use the correct value)
+
+Tue Jan 30 09:43:34 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * ChangeLog: take the pre-1996 changes and put them in a new file
+ ChangeLog-9395.
+ * ChangeLog-9194: Renamed from ChangeLog.fsf.
+ * ChangeLog-9194, ChangeLog-9395, ChangeLog: Add additional text
+ explaining the difference between all these logs and pointing to
+ older logs.
+ * Makefile.in (DISTFILES): Add ChangeLog-9194 and ChangeLog-9395;
+ remove ChangeLog.fsf.
+
+ * modules.c (do_module): Don't fall through from 'l' to 'o' case
+ of option processing switch statement.
+
+Tue Jan 30 06:50:19 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * client.c (send_repository): Fix memory leak; free adm_name
+ before returning.
+ * diff.c (diff_file_nodiff): Fix memory leak; free xvers before
+ returning.
+ * rtag.c (rtag_fileproc): Fix memory leak; if branch_mode is set,
+ free rev before returning.
+ * status.c (status_fileproc, tag_list_proc): Fix memory leak; free
+ return value of RCS_whatbranch.
+ * tag.c (tag_fileproc): Fix memory leak; free vers before
+ returning.
+ (val_fileproc): Fix memory leak; free return value of RCS_gettag.
+ * watch.c (watch_modify_watchers): Fix memory leak; free mynewattr
+ before returning.
+
+Tue Jan 30 09:43:34 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * lock.c (readers_exist): If stat gave an error, print an error
+ message saying it was from stat, rather than from "reading
+ directory". Skip the message completely if it was an
+ existence_error.
+
+ * sanity.sh (branches): New tests (branches off of branches, etc.).
+
+Tue Jan 30 11:55:34 MET 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * main.c (main): Add change to run getopt_long twice again.
+
+Mon Jan 29 15:59:31 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ gcc -Wall lint:
+ * client.c: Include edit.h
+
+Sun Jan 28 09:45:53 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * edit.c, edit.h (mark_up_to_date): New function, to remove file
+ in CVS/Base.
+ * client.c (update_entries): Call it if file is up to date.
+ * checkin.c (Checkin): Call it in non-server (local) case.
+ * sanity.sh: New test 182.5, tests for above-fixed bug.
+
+Sun Jan 28 01:07:22 1996 Jim Kingdon (kingdon@beezley)
+
+ * client.c (change_mode): Separate out CHMOD_BROKEN code to parse
+ mode_string, rather than going through a mode_t. Cleaner than
+ the previous CHMOD_BROKEN code (which also had a typo of && not &).
+
+Sat Jan 27 23:29:46 1996 Jim Kingdon (kingdon@beezley)
+
+ * edit.c (edit_fileproc): Check for EACCESS as well as EEXIST.
+
+Sat Jan 27 16:26:30 1996 Karl Fogel (kfogel@floss.cyclic.com)
+
+ * client.c (notified_a_file): use rename_file() instead of
+ rename() (but temporarily set `noexec' to 0 so it runs
+ unconditionally).
+ (change_mode): deal with CHMOD_BROKEN.
+
+Fri Jan 26 00:14:00 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * server.c: renamed `dirname' to `dir_name', to avoid conflicts
+ with system headers.
+
+ * client.c: renamed `dirname' and `last_dirname' to `dir_name' and
+ last_dir_name' (see above). Not strictly necessary, but
+ consistency is nice -- as long as you do it all the time.
+
+Thu Jan 25 00:41:59 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * options.h.in (AUTH_SERVER_SUPPORT, AUTH_CLIENT_SUPPORT): change
+ comment now that no longer under construction.
+
+Wed Jan 24 15:25:22 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.7.1.
+
+ * Version 1.7.
+
+Sat Jan 20 00:05:08 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.6.87.
+
+Mon Jan 15 18:14:55 1996 Gary Oberbrunner <garyo@avs.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * tag.c (val_direntproc): New function to ignore
+ nonexistent dirs when recursing to check tag validity.
+ (tag_check_valid): Pass it to start_recursion.
+ * sanity.sh (death): New tests 65a0-65a6 cause test 74 to test for
+ above-fixed bug.
+
+Mon Jan 15 12:55:37 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * main.c: Revert change to run getopt_long twice. This can go in
+ after 1.7.
+
+Mon Jan 15 13:03:28 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * filesubr.c (deep_remove_dir): added test of EEXIST for nonempty
+ directory (Posix states that both ENOTEMPTY (BSD) and EEXIST
+ (SYSV) are valid)
+
+ * main.c (main): run getopt_long twice to allow command-line
+ suppression of reading the cvsrc file
+
+Fri Jan 12 10:02:43 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.6.86.
+
+Thu Jan 11 23:28:05 1996 J.T. Conklin <jtc@rtl.cygnus.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * fileattr.h (fileattr_startdir): Add comment about REPOS == NULL.
+ * fileattr.c (fileattr_read, fileattr_write): Assert that
+ fileattr_stored_repos != NULL.
+ (fileattr_free): If fileattr_stored_repos is NULL, don't free it.
+
+Thu Jan 11 18:03:21 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * scramble.c (descramble): deal with DIAGNOSTIC better.
+
+Thu Jan 11 12:04:42 1996 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * main.c: remove CVS_NOADMIN.
+
+ * options.h.in: remove CVS_NOADMIN
+
+Thu Jan 11 10:28:44 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * scramble.c (descramble): make sure the string returned is safe
+ to free().
+
+Wed Jan 10 01:11:23 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * server.c (serve_notify): Cast return value from malloc.
+
+ * edit.c (notify_do): Use struct assignment, not struct
+ initialization (which SunOS4 /bin/cc doesn't have).
+
+Tue Jan 9 09:41:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.6.85.
+
+ We use version numbers instead of patchlevels. But there was some
+ confusing patchlevel stuff lying around. Nuke it:
+ * Makefile.in (HEADERS): Remove patchlevel.h
+ * patchlevel.h: Removed.
+ * main.c: Don't include patchlevel.h.
+ (main): Don't print patch level.
+
+ * server.c (check_repository_password): Check for errors from
+ system calls; reindent function.
+
+Tue Jan 9 23:15:30 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * expand_path.c: fix comments (explain expand_path()'s behavior
+ correctly).
+
+Tue Jan 9 09:41:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * edit.c (notify_proc): After copying in string following %s,
+ don't clobber it. Instead set up q to end of string.
+
+ * watch.c (watch_modify_watchers), edit.c (editor_set): Fix sense
+ of test in trying to decide whether attributes are changed.
+
+ * cvs.h (CVSROOTADM_USERS): New macro.
+ * edit.c (notify_do): Look up notifyee in CVSROOTADM_USERS if it
+ exists.
+
+Tue Jan 9 21:39:45 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * expand_path.c: don't redundantly #include things that cvs.h
+ already #includes (i.e., stdio.h, ctype.h, string[s].h).
+
+Tue Jan 9 09:41:29 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * ignore.c (ign_default): Add *.obj.
+
+ * server.c: Put /* */ around #endif comment.
+
+Mon Jan 8 20:37:17 1996 Karl Fogel <kfogel@floss.red-bean.com>
+
+ * client.c (connect_to_pserver): check return value of recv().
+
+Mon Jan 8 11:37:57 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (connect_to_pserver): Check for error from connect;
+ reindent function.
+
+ * sanity.sh (4.75): Use dotest, so we get a PASS if test passes.
+
+ * sanity.sh (dotest): New argument OUTPUT2.
+ (188a): Use it instead of \|.
+
+ * sanity.sh (import): Avoid using string $ followed by Id followed
+ by $ in sanity.sh source, in case sanity.sh itself is under CVS.
+ I hate keyword expansion.
+
+ * sanity.sh: If expr cannot handle multiline expressions, fail and
+ tell the user to get one which can.
+
+ * release.c (release_delete): Remove unused variable retcode.
+
+Fri Jan 5 13:30:00 1996 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ * release.c (release_delete): Call unlink_file_dir rather
+ than "rm -rf".
+
+Thu Jan 4 09:58:30 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * commit.c (find_fileproc): Print "nothing known about foo" and
+ return 1 if the file doesn't exist and isn't in CVS/Entries.
+ (commit): If the recursion over find_fileproc returns an error,
+ print "correct above errors first!" just like local CVS.
+ * sanity.sh (basica): Test for above-fixed bug.
+
+ * release.c (release): If we are the client, only unedit if the
+ server supports it.
+
+ * sanity.sh: Remove STARTANCHOR stuff; expr patterns are
+ automatically anchored to the start. ENDANCHOR remains.
+
+ * commit.c (commit): Don't start the server until we have
+ determined that there is something to commit.
+
+Thu Jan 4 09:48:33 1996 Ben Laurie <ben@gonzo.ben.algroup.co.uk>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (start_server): dup the file descriptor before
+ fdopening it.
+
+Wed Jan 3 18:25:25 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: Remove tests 5, 5.5, and 5.75. All that stuff is
+ tested elsewhere.
+
+ * ignore.c (ign_default): Change CVS* to CVS CVS.adm. CVS* is too
+ broad, especially in a case-insensitive filesystem.
+
+ * Makefile.in (cvsbug): version.c is in srcdir.
+
+Wed Jan 3 17:30:45 1996 Phi-Long Tran <ptran@autodesk.com>
+
+ * modules.c (do_module): Honor error_use_protocol in printing trace.
+ * server.c (server_register): Move check for options NULL to above
+ printing of the trace.
+
+Wed Jan 3 01:19:53 1996 Mark Immel <immel@centerline.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * update.c (checkout_file): Do not resurrect file on join if it
+ doesn't contain the revisions we are joining. Probably not a
+ perfect test, but should be an improvement.
+ * sanity.sh (death): New death-file4-* tests, for bug fixed above.
+
+Wed Jan 3 01:19:53 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * add.c, admin.c, checkout.c, client.c, commit.c, diff.c, edit.c,
+ history.c, import.c, log.c, patch.c, release.c, remove.c, rtag.c,
+ status.c, tag.c, update.c, watch.c: In calling send_to_server,
+ pass \012 not \n. On the Mac \n is CR, not LF, and we want to
+ send LF. I didn't try to deal with whether files in CVSADM should
+ contain CR or LF--in fact there is some code in client.c which
+ reads \n from CVSADM files and passes it to send_to_server; it
+ needs to be cleaned up one way or the other.
+
+ * entries.c (Entries_Open): Don't try to close fpin twice.
+
+ * client.c (update_entries): Fix typo ("strlen (filename + 10)"
+ -> "strlen (filename) + 10").
+
+ * commit.c (checkaddfile): Remove arbitrary limit.
+
+Tue Jan 2 11:25:22 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * commit.c (commit): Only pass files which were modified, added,
+ or removed to send_file_names. This has as a side effect a
+ semantic change--the up-to-date check is now skipped for other
+ files--but probably a good one, or at least not a bad one.
+ * sanity.sh (basica): New test; tests for bug fixed above.
+ * sanity.sh (187a3): Adjust for new 'cvs commit' output. Set up
+ DOTSTAR to match arbitrary text (another GNU expr bug/misfeature,
+ sigh).
+
+ * sanity.sh: Test that the commit in test 43 actually worked.
+ Merge tests basic2 and basic3 and make them independent of basic1.
+ (pass,fail): Don't insert spurious space.
+ (45.5): Fix typo in directory name.
+
+Tue Jan 2 13:00:00 1996 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ Visual C++ lint:
+ * myndbm.c: Prototype write_item.
+
+Tue Jan 2 11:25:22 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ gcc -Wall lint:
+ * client.c (client_expand_modules): Pass error message not "" to error.
+ * client.c (supported_request), server.c (supported_response):
+ Return a value (gcc -Wall can't know that error doesn't return).
+ * commit.c (copy_ulist): Return a value.
+ * history.c (fill_hrec): Don't make assumptions about whether
+ time_t is "int" or "long" or what.
+ * cvs.h: Declare link_file.
+ * server.c: Include fileattr.h.
+ * server.c (server_notify): Remove unused variable val.
+ * tag.c (val_fileproc): Remove unused variable foundtag.
+
+Mon Jan 1 09:49:16 1996 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.6.5.
+
+ * Version 1.6.4.
+
+ * filesubr.c (link_file): Add comment about link vs. copy semantics.
+
+ * cvs.h (struct vers_ts): Fix comments.
+ * commit.c (commit): Before we ask for a log message, figure out
+ what is modified and what is not and pass the information to
+ do_editor.
+ (copy_ulist,find_fileproc): New helper functions for above code.
+
+ * client.c (read_line): When writing to from_server_logfile, write
+ the \n too.
+
+ * client.c (send_files): No longer call send_file_names.
+ * client.h: Update comment.
+ * add.c, admin.c, commit.c, diff.c, edit.c, log.c, remove.c,
+ status.c, tag.c, update.c, watch.c: Call send_file_names before
+ send_files.
+ * client.c: New variables module_argc, module_argv.
+ (client_expand_modules): Set them, to arguments.
+ (client_send_expansions): Use them instead of modules_vector to
+ send arguments.
+ * sanity.sh (modules): Add test of modules -d flag.
+
+
+For older changes see ChangeLog-9395.
diff --git a/contrib/cvs/src/ChangeLog-9194 b/contrib/cvs/src/ChangeLog-9194
new file mode 100644
index 0000000..eb79efc
--- /dev/null
+++ b/contrib/cvs/src/ChangeLog-9194
@@ -0,0 +1,524 @@
+Thu Sep 15 08:20:23 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * subr.c (run_setup, run_args): Check USE_PROTOTYPES if defined
+ instead of __STDC__, just like cvs.h does.
+
+Thu Sep 15 00:14:58 1994 david d `zoo' zuhn <zoo@monad.armadillo.com>
+
+ * main.c: rename nocvsrc to use_cvsrc, don`t read ~/.cvsrc when -H
+ has been seen
+
+Wed Sep 14 21:55:17 1994 david d `zoo' zuhn <zoo@monad.armadillo.com>
+
+ * cvs.h, subr.c: use size_t for xmalloc, xrealloc, and xstrdup
+ parameters
+
+ * cvsrc.c: optimize away two calls of getenv
+
+ * commit.c, subr.c: use mode_t for file mode values (Thanks to jtc@cygnus.com)
+
+ * main.c: update copyrights in -v message
+
+Tue Sep 6 10:29:13 1994 J.T. Conklin (jtc@rtl.cygnus.com)
+
+ * hash.c (hashp): Replace hash function with one from p436 of the
+ Dragon book (via libg++'s hash.cc) which has *much* better
+ behavior.
+
+Wed Aug 17 09:37:44 1994 J.T. Conklin (jtc@cygnus.com)
+
+ * find_names.c (find_dirs): Use 4.4BSD filesystem feature (it
+ contains the file type in the dirent structure) to avoid
+ stat'ing each file.
+
+Tue Aug 16 11:15:12 1994 J.T. Conklin (jtc@cygnus.com)
+
+ * rcs.h (struct rcsnode): add symbols_data field.
+ * rcs.c (RCS_parsercsfile_i): store value of rcs symbols in
+ symbols_data instead of parsing it.
+ (RCS_symbols): New function used for lazy symbols parsing.
+ Build a list out of symbols_data and store it in symbols if it
+ hasn't been done already, and return the list of symbols.
+ (RCS_gettag, RCS_magicrev, RCS_nodeisbranch, RCS_whatbranch):
+ Use RCS_symbols.
+ * status.c: (status_fileproc): Use RCS_symbols.
+
+Thu Jul 14 13:02:51 1994 david d `zoo' zuhn (zoo@monad.armadillo.com)
+
+ * src/diff.c (diff_fileproc): add support for "cvs diff -N" which
+ allows for adding or removing files via patches. (from
+ K. Richard Pixley <rich@cygnus.com>)
+
+Wed Jul 13 10:52:56 1994 J.T. Conklin (jtc@phishhead.cygnus.com)
+
+ * cvs.h: Add macro CVSRFLPAT, a string containing a shell wildcard
+ expression that matches read lock files.
+ * lock.c (readers_exist): Reorganized to use CVSRFLPAT and to not
+ compute the full pathname unless the file matches.
+
+ * rcs.h: Add macro RCSPAT, a string containing a shell wildcard
+ expression that matches RCS files.
+ * find_names.c (find_rcs, find_dirs): Use RCSPAT.
+
+Fri Jul 8 07:02:08 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * entries.c (Register): Pass two arguments to write_ent_proc, in
+ accordance with its declaration.
+
+Thu Jun 30 09:08:57 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * logmsg.c (do_editor): Fix typo ("c)continue" -> "c)ontinue").
+
+Thu Jun 23 18:28:12 1994 J.T. Conklin (jtc@phishhead.cygnus.com)
+
+ * find_names.c (find_rcs, find_dirs): use fnmatch instead of
+ re_comp/re_exec for wildcard matching.
+ * lock.c (readers_exist): Likewise.
+
+Fri May 20 08:13:10 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * modules.c (do_module): If something is aliased to itself, print
+ an error message rather than recursing.
+
+Fri May 6 19:25:28 1994 david d zuhn (zoo@monad.armadillo.com)
+
+ * cvsrc.c (read_cvsrc): use open_file for error checking
+
+Sat Feb 26 10:59:37 1994 david d zuhn (zoo@monad.armadillo.com)
+
+ * import.c: use $TMPDIR if available, instead of relying on /tmp
+
+Mon Jan 24 19:10:03 1994 david d zuhn (zoo@monad.armadillo.com)
+
+ * update.c (joining): compare join_rev1 with NULL instead of
+ casting pointer to an int
+
+ * options.h: remove S_IWRITE, S_IWGRP, S_IWOTH macros
+
+ * logmsg.c: #if 0 around gethostbyname prototype
+
+ * hash.c (printnode), find_names.c (add_entries_proc),
+ entries.c (write_ent_proc): correct declaration for function
+ (added void *closure)
+
+ * cvs.h: header include order reorganization: First include the
+ program config headers (config.h, options.h). Then include any
+ system headers (stdio.h, unistd.h). Last, get the program
+ headers and any cvs supplied library support
+
+ * commit.c: use xstrdup instead of strdup
+
+ * cvs.h: redefined USE(var) macro; comment after an #endif
+
+ * all .c files: remove the semicolon from after the USE(var)
+
+Sat Dec 18 00:17:27 1993 david d zuhn (zoo@monad.armadillo.com)
+
+ * cvs.h: include errno.h if available, otherwise declare errno if
+ it's not somehow else defined
+
+ * commit.c (checkaddfile): remove unused file argument from
+ RCS_nodeisbranch call
+
+ * rcs.c (RCS_nodeisbranch): remove file from arguments (was unused)
+
+ * rcs.h (RCS_nodeisbranch): remove file from prototype
+
+ * main.c: don't use rcsid when printing version number (the CVS
+ version number is independent of the repository that it comes
+ from)
+
+ * hash.c (printlist, printnode): use %p to print pointers, not %x
+ (avoids gcc format warnings)
+
+ * cvs.h: define USE if GCC 2, to avoid unused variable warning
+
+ * all .c files: use USE(rcsid)
+
+ * Makefile.in (VPATH): don't use $(srcdir), but @srcdir@ instead
+ (COMMON_OBJECTS): define, and use in several places
+ (OBJECTS): reorder alphabetically
+
+ * hash.c (nodetypestring): handle default return value better
+
+ * modules.c (do_module): remove extra argument to ign_dir_add
+
+ * main.c (main): initialize cvs_update_env to 0 (zero)
+
+ * modules.c (do_module): return error code when ignoring directory
+ (instead of a bare return). error code should be zero here
+
+ * cvs.h: add prototypes for ignore_directory, ign_dir_add
+
+ * ignore.c: add comments about ignore_directory
+
+ * root.c (Name_Root): remove unused variables has_cvsadm and path
+
+ * checkin.c (Checkin): only use -m<message> when message is non-NULL
+
+ * cvsrc.c (read_cvsrc): make sure homeinit is never used while
+ uninitialized (could have happened if getenv("HOME") had failed)
+
+ * cvs.h: include unistd.h if available
+
+Fri Dec 17 23:54:58 1993 david d zuhn (zoo@monad.armadillo.com)
+
+ * all files: now use strchr, strrchr, and memset instead of index,
+ rindex, and bzero respectively
+
+Sat Dec 11 09:50:03 1993 david d zuhn (zoo@monad.armadillo.com)
+
+ * version.c (version_string): bump to +104z
+
+ * Makefile.in: set standard directory variables, CC, and other
+ variables needed to be able to do 'make all' in this directory
+
+ * import.c: implement -k<subst> options, for setting the RCS
+ keyword expansion mode
+
+ * all files: use PROTO() macro for ANSI function prototypes
+ instead of #ifdef __STDC__/#else/#endif around two sets of
+ declarations
+
+Thu Nov 18 19:02:51 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * add.c (add), import.c (import), commit.c (commit): change
+ xmalloc & strcpy to xstrdup.
+
+ * commit.c (remove_file): correct another static buffer problem.
+
+Wed Nov 10 15:01:34 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * recurse.c (start_recursion): directories in repository but not
+ in working directory should be added to dirlist. Fixes "update
+ -d dir" case.
+
+ * version.c (version_string): bump to +103r.
+
+ * commit.c (checkaddfile): mkdir attic only if it does not already
+ exist. comment changes. changed diagnostic about adding on a
+ branch. if a file is added on a branch, remove and replace the
+ internal representation of that rcs file.
+
+Tue Nov 9 18:02:01 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * add.c (add): if a file is being added on a branch, then say so;
+ add quotes around file names in error messages.
+
+Thu Nov 4 16:58:33 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * version.c (version_string): bump to +102r.
+
+ * recurse.c (unroll_files_proc, addfile): new files, forward
+ decls, and prototypes.
+ (recursion_frame): new struct.
+ (start_recursion): rewrite to handle the case of "file1 file2
+ dir1/file3".
+
+ * rcs.c (RCS_parsercsfile): trap and error out on the case where
+ getrcskey tells us it hit an error while reading the file.
+
+ * commit.c (lock_filesdoneproc): add comment about untrapped error
+ condition.
+
+ * hash.c (addnode): comment change.
+
+ * subr.c: add comment about caching.
+
+ * sanity.sh: updated copyright.
+
+Wed Nov 3 14:49:15 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * version.c (version_string): bump to +101r.
+
+ * hash.c (walklist): add a closure for called routines. All
+ callers, callees, and prototypes changed.
+
+ * hash.c (nodetypestring, printnode, printlist): new functions for
+ dumping lists & nodes.
+
+ * tag.c (tag_fileproc): fatal out on failure to set tag.
+
+Tue Nov 2 14:26:38 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * version.c (version_string): bump version to +99.
+
+Mon Nov 1 15:54:51 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ Change buffer allocation for check in messages from static to
+ dynamic.
+ * add.c (add): dynamically allocate message.
+ (build_entry): check (message != NULL) now that message is a
+ pointer.
+ * commit.c (got_message, commit, commit_fileproc,
+ commit_filesdoneproc, commit_direntproc): removed. Replaced by
+ (message != NULL). Dynamically allocate message.
+ * cvs.h: adjust do_editor prototype and forward decl.
+ (MAXMESGLEN): removed.
+ * import.c (import): dynamically allocate message.
+ * logmsg.c (do_editor): change return type to char *. Remove
+ message parameter. Slight optimization to algorythm for
+ removing CVSEDITPREFIX lines. Add comment about fgets lossage.
+
+ * subr.c (xmalloc): change error message to print number of bytes
+ we were attempting to allocate.
+
+Fri Oct 29 14:22:02 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * add.c (add): prevent adding a directory if there exists a dead
+ file of the same name.
+
+ * sanity.sh: update argument to diff from "+ignore-file" to
+ "--exclude=".
+
+ * Makefile.in (TAGS): extend to work from an objdir.
+
+Mon Oct 18 18:45:45 1993 david d `zoo' zuhn (zoo@rtl.cygnus.com)
+
+ * tag.c, rtag.c: change the default actions to make writing over
+ existing tags harder (but not impossible)
+
+Thu Oct 14 18:00:53 1993 david d `zoo' zuhn (zoo@rtl.cygnus.com)
+
+ CVS/Root changes from Mark Baushke (mdb@cisco.com)
+
+ * Makefile.in: added new file called root.c
+
+ * create_adm.c: will create CVS/Root at the same time that the
+ other CVS files are being created
+
+ * cvs.h: new CVSADM_ROOT define plus new function externs
+
+ * main.c: default to using CVS/Root contents for CVSROOT
+ if neither the environment variable or the command line
+ "-d" switch is given. If either are given, perform a
+ sanity check that this directory belongs to that repository.
+
+ * update.c: if CVS/Root does not exist, then create it
+ during an update -- this may be removed if CVS/Root becomes a
+ standard feature
+
+ * root.c: implement new functions to manipulate CVS/Root
+ [this may be integrated with other utility functions in
+ a future revision if CVS/Root becomes a standard feature.]
+
+Wed Sep 29 17:01:40 1993 david d `zoo' zuhn (zoo@rtl.cygnus.com)
+
+ * patch.c (patch_fileproc): output an Index: line for each file
+
+Mon Sep 6 18:40:22 1993 david d `zoo' zuhn (zoo@rtl.cygnus.com)
+
+ * cvs.h: wrap definition of PATH_MAX in #ifndef PATH_MAX/#endif
+
+Tue Aug 9 21:52:10 1994 Mark Eichin (eichin@cygnus.com)
+
+ * commit.c (remove_file): actually allocate space for the
+ filename, not just the directory.
+
+Tue Jul 6 19:05:37 1993 david d `zoo' zuhn (zoo@cygnus.com)
+
+ * diff.c: patches to print an Index: line
+
+Mon Jun 14 12:19:35 1993 david d `zoo' zuhn (zoo at rtl.cygnus.com)
+
+ * Makefile.in: update install target
+
+Tue Jun 1 17:03:05 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com)
+
+ * Makefile.in: link cvs against libiberty
+
+Wed May 19 14:10:34 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com)
+
+ * ignore.c: add code for keeping lists of directories to ignore.
+
+ * modules.c: new syntax for modules file, !dirname is added to
+ the list of directories to ignore
+
+ * update.c: don't process directories on the ignore list
+
+Tue Apr 6 14:22:48 1993 Ian Lance Taylor (ian@cygnus.com)
+
+ * cvs.h: Removed gethostname prototype, since it is unnecessary
+ and does not match prototype in <unistd.h> on HP/UX.
+
+Mon Mar 22 23:25:16 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com)
+
+ * Makefile.in: rename installtest to installcheck
+
+Mon Feb 1 12:53:34 1993 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ * Makefile.in (check, installtest): set RCSBIN so that we
+ explicitly test the appropriate version of rcs as well.
+
+Fri Jan 29 13:37:35 1993 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ * version.c: bump version to +2.
+
+Thu Jan 28 18:11:34 1993 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ * import.c (update_rcs_file): if a file was dead, be sure to check
+ in the new version.
+
+ * update.c (checkout_file): if file_is_dead and we *did* have an
+ entry, scratch it.
+
+Tue Jan 26 16:16:48 1993 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ * sanity.sh: parcel into pieces for easier truncation when
+ debugging.
+
+ * update.c (checkout_file): print the "no longer pertinent"
+ message only if there was a user file.
+
+Wed Jan 20 17:08:09 1993 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ * update.c (checkout_file): remove unused variable s.
+ (join_file): remove unused variables rev & baserev. Fix a typo.
+
+ * commit.c (commit_fileproc): remove unused variable magicbranch.
+
+ * sanity.sh: bring back test 45 even though it fails. Update
+ tests against imported files.
+
+ * add.c (add_directory): move declaration of unused variable.
+
+ * Makefile.in (xxx): when building in this directory, pass CC for
+ the recursion.
+
+Mon Jan 18 13:48:33 1993 K. Richard Pixley (rich@cygnus.com)
+
+ * commit.c (remove_file): fix for files removed in trunk
+ immediately after import.
+
+ * commit.c (remove_file): initialize some variables. Otherwise we
+ end up free'ing some rather inconvenient things.
+
+Wed Jan 13 15:55:36 1993 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ * Makefile.in (check, install, installtest): use the sanity test.
+
+ * sanity.el: make into real functions and bind to sun keys.
+
+ * sanity.sh: bring back to working order. Add test for death
+ after import.
+
+Tue Dec 22 17:45:19 1992 K. Richard Pixley (rich@cygnus.com)
+
+ * commit.c (remove_file): when checking in a dead revision to a
+ branch as we are creating the branch, do not lock the underlying
+ revision. Also free some malloc'd memory.
+
+Wed Dec 2 13:09:48 1992 K. Richard Pixley (rich@cygnus.com)
+
+ * RCS-patches: new file.
+
+Fri Nov 27 20:12:48 1992 K. Richard Pixley (rich@rtl.cygnus.com)
+
+ Added support for adding previously removed files, as well as
+ adding and removing files in branches.
+
+ * add.c (build_entry): add new argument, tag, so as to store in
+ Entries the per directory sticky tag under which a file is
+ added. Changed prototype and caller.
+ (build_entry): Do not prevent file additions if the file exists
+ in the Attic.
+ (add): if the file being adding was previously dead, say so, and
+ mark the Entries file with the addition.
+ * checkin.c (Checkin): adding with a tag no longer means to add,
+ then tag. Hence, remove the tagging operation.
+ * classify.c (Classify_File): if the base RCS version is dead,
+ then the file is being added. If a file being added already
+ exists in the attic, and the base RCS version is NOT dead, then
+ we have a conflict.
+ * commit.c (checkaddfile): add the list of srcfiles to calling
+ convention. Change prototype and callers.
+ (remove_file): add message and list of srcfiles to calling
+ convention. Change prototype and callers. When removing a file
+ with a tag, remove the tag only when the tag does not represent
+ a branch. Remove files by committing dead revisions in the
+ appropriate branch. When removing files from the trunk, also
+ move the RCS file into the Attic.
+ (check_fileproc): when adding, and looking for previously
+ existing RCS files, do not look in the Attic.
+ (commit_fileproc): adding files with tags now implies adding the
+ file on a branch with that tag.
+ (checkaddfile): When adding a file on a branch, in addition to
+ creating the rcs file in the Attic, also create a dead, initial
+ revision on the trunk and stub in a magic branch tag.
+ * cvs.h (joining, gca): added prototypes.
+ * rcs.c (RCS_getbranch): now global rather than static.
+ remove prototype and forward decl.
+ (parse_rcs_proc): use RCS_addnode.
+ (RCS_addnode): new function.
+ (RCS_parsercsfile): recognize the new RCS revision
+ newphrase, "dead". Mark the node for the revision.
+ (RCS_gettag): requesting the head of a file in the attic now
+ returns the head of the file in the attic rather than NULL.
+ (RCS_isbranch): use RCS_nodeisbranch.
+ (RCS_nodeisbranch): new function.
+ (RCS_isdead): new function.
+ * rcs.h (RCSDEAD): new macro for new rcs keyword.
+ (struct rcsversnode): new field to flag dead revisions.
+ (RCS_nodeisbranch, RCS_isdead, RCS_addnode): new functions,
+ new prototypes, new externs.
+ (RCS_getbranch): now global, so prototype and extern moved
+ to here.
+ * subr.c (gca): new function.
+ * update.c (join_file): add entries list to calling
+ convention. Caller changed.
+ (update): also search the Attic when joining.
+ (checkout_file): when joining, checkout dead revisions too. If
+ a file has died across an update then say so.
+ (join_file): support joins of dead files against live ones, live
+ files against dead ones, and added files. Change the semantic
+ of a join with only rev specified to mean join specified rev
+ against checked out files via the greatest common ancestor of
+ the specified rev and the base rev of the checked out files.
+ (joining): new function.
+ * vers_ts.c (Version_TS): ALWAYS get the rcs version number.
+
+ * update.c (update): write the 'C' letter for conflicts.
+
+ * cvs.h (ParseTag): remove duplicate extern.
+
+ * add.c (add_directory): do not prompt for interactive
+ verification before adding a directory. Doing so prevents
+ scripted testing.
+
+Wed Feb 26 18:04:40 1992 K. Richard Pixley (rich@cygnus.com)
+
+ * Makefile.in, configure.in: removed traces of namesubdir,
+ -subdirs, $(subdir), $(unsubdir), some rcs triggers. Forced
+ copyrights to '92, changed some from Cygnus to FSF.
+
+Tue Dec 10 01:24:40 1991 K. Richard Pixley (rich at cygnus.com)
+
+ * diff.c: do not pass an empty -r option to rcsdiff.
+
+ * update.c: fix bug where return code from rcsmerge wasn't being
+ handled properly.
+
+ * main.c: "rm" and "delete" now synonyms for "remove".
+
+ * commit.c: abort if editor session fails, but remember to clear
+ locks.
+
+ * Makefile.in: remove conf.h and checkin.configured on clean.
+ infodir belongs in datadir.
+
+Thu Dec 5 22:46:03 1991 K. Richard Pixley (rich at rtl.cygnus.com)
+
+ * Makefile.in: idestdir and ddestdir go away. Added copyrights
+ and shift gpl to v2. Added ChangeLog if it didn't exist. docdir
+ and mandir now keyed off datadir by default.
+
+Wed Nov 27 02:47:13 1991 K. Richard Pixley (rich at sendai)
+
+ * brought Makefile.in's up to standards.text.
+
+ * fresh changelog.
+
+
+For older changes, there might be some relevant stuff in the bottom of
+the NEWS file, but I'm afraid probably a lot of them are lost in the
+mists of time.
diff --git a/contrib/cvs/src/ChangeLog-9395 b/contrib/cvs/src/ChangeLog-9395
new file mode 100644
index 0000000..c2d2111
--- /dev/null
+++ b/contrib/cvs/src/ChangeLog-9395
@@ -0,0 +1,3731 @@
+Note: this log overlaps in time with ChangeLog-9194. There was a time
+during which changes which had been merged into the official CVS
+(which produced releases such as 1.4A1 and 1.4A2) went into what has
+become ChangeLog-9194, and changes which existed only at Cygnus went
+into this file (ChangeLog-9395). Eventually the Cygnus release became
+Cyclic CVS (as it was then called), which became CVS 1.5, so probably
+all the changes in both (what are now) ChangeLog-9194 and
+ChangeLog-9395 made it into 1.5.
+
+Sun Dec 31 17:33:47 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * import.c (add_rev): Revert portion of 31 Aug 95 change which
+ passes -u to ci instead of using a hard link.
+ * sanity.sh (import): Add test for above-fixed bug.
+
+Sun Dec 31 16:40:41 1995 Peter Chubb <peterc@bookworm.sw.oz.au>
+ and Jim Kingdon <kingdon@cyclic.com>
+
+ * admin.c (admin_fileproc): Call freevers_ts before returning.
+
+Mon Dec 25 12:20:06 1995 Peter Wemm <peter@haywire.DIALix.COM>
+
+ * logmsg.c (rcsinfo_proc): initialise line and
+ line_chars_allocated so they dont cause malloc problems within
+ getline(). This was causing rcsinfo templates to not work.
+
+Sun Dec 24 01:38:36 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (authenticate_connection): clarify protocol.
+
+ * login.c (login): deprolixify the password prompt.
+
+Sat Dec 23 10:46:41 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * myndbm.h, myndbm.c (dbm_store): New function.
+ * myndbm.h (DBM): Add modified and filename fields.
+ * myndbm.c (dbm_open, dbm_close): Manipulate new fields. dbm_open
+ no longer fails if the file doesn't exist and O_CREAT is set.
+ * cvs.h (CVSROOTADM_VALTAGS): Added.
+ * tag.c, cvs.h (tag_check_valid): New function.
+ * update.c (update), checkout.c (checkout_proc), commit.c (commit),
+ diff.c (diff), patch.c (patch_proc), rtag.c (rtag_proc), tag.c (tag):
+ Call it.
+ * sanity.sh: Test for rejection of invalid tagname.
+
+Fri Dec 22 18:21:39 1995 Karl Fogel <kfogel@csxt.cs.oberlin.edu>
+
+ * client.c (start_server): don't use kerberos if authenticating
+ server was specified.
+
+Fri Dec 22 16:35:57 1995 Karl Fogel <kfogel@csxt.cs.oberlin.edu>
+
+ * login.c (login): deal with new scramble methods.
+ (get_cvs_password): same.
+
+ * server.c (check_repository_password): remove arbitrary limit on
+ line length.
+ (authenticate_connection): use a separate variable for the
+ descrambled password, now that we no longer scramble in place.
+ Set `error_use_protocol' to 1 and just use error() where used to
+ do its job inline.
+
+ * cvs.h (scramble, descramble): adjust prototype.
+
+ * scramble.c (scramble, descramble): return char *.
+
+Fri Dec 22 13:00:00 1995 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ * release.c (release): If SERVER_SUPPORT is not defined, still
+ set up arg_start_idx.
+
+ * release.c (release): When calling unedit, set argv[1] to
+ NULL (since argc is only 1).
+
+ * edit.c: Pass dosrcs 0 to all calls to start_recursion.
+ None of the fileprocs were using it, so it just slowed things
+ down and caused potentially harmful checks for rcs files.
+
+ * edit.c (send_notifications): In client case, do not readlock.
+
+Thu Dec 21 16:00:00 1995 Jim Kingdon <kingdon@peary.cyclic.com>
+
+ Clean up Visual C++ lint:
+ * client.c (read_line): Change input_index and result_size to size_t.
+ (update_entries): Remove unused variables buf2, size_left, size_read.
+ (handle_mode): Prototype.
+ * client.c, client.h (send_to_server, read_from_server): Change
+ len to size_t.
+ * client.c (send_to_server): Change wrtn to size_t.
+ (read_from_server): Change red to size_t.
+ * client.c, myndbm.c, edit.c, fileattr.c: Include getline.h.
+ * checkin.c, commit.c, update.c: Include fileattr.h.
+ * commit.c, update.c: Include edit.h.
+ * edit.c (onoff_filesdoneproc): Prototype.
+ (ncheck_fileproc,edit_fileproc): Change "return" to "return 0".
+ (notify_do): Cast a signed value to unsigned before comparing
+ with unsigned value.
+
+Thu Dec 21 15:24:37 1995 Karl Fogel <kfogel@occs.cs.oberlin.edu>
+
+ * client.c: don't include socket headers twice just because
+ both HAVE_KERBEROS and AUTH_CLIENT_SUPPORT are set.
+ (start_kerberos_server): if fail to connect to kerberos, print out
+ a more specific error message, mainly so pcl-cvs can know what
+ happened and not panic.
+ (start_server): don't assume sprintf() returns len
+ written (only some systems provide this); instead, have
+ send_to_server() calculate the length itself.
+ (send_modified): same.
+ (send_fileproc): same.
+ (send_file_names): same.
+
+Wed Dec 20 14:00:28 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * update.c (ignore_files): Move from here...
+ * ignore.c (ignore_files): ...to here. No longer static. Take
+ new argument PROC.
+ * cvs.h (ignore_files): Declare.
+ * client.c (send_filesdoneproc): Split off from
+ update_filesdone_proc. Pass new function send_ignproc to
+ ignore_files (to ask server about ignored file before printing
+ "?").
+ * server.c: Rename outbuf from but_to_net and take it from
+ do_cvs_command to a global. Move initialization accordingly.
+ (serve_questionable): New function.
+ (requests): Add it.
+ * update.c (update_filesdone_proc): Remove client stuff. Pass new
+ function update_ignproc to ignore_files.
+ * cvs.h (joining, do_update): Move declarations from here...
+ * update.h: ...to here.
+ * cvs.h: Include update.h.
+ * update.c, client.c: Don't include update.h
+ * ignore.c, cvs.h: New variable ign_inhibit_server, set on -I !.
+ * import.c (import): Pass -I ! to server if specified.
+ (import_descend): If server, ignore CVS directories even if -I !.
+ * update.c (update), import.c (import): Only call ign_setup before
+ argument processing; don't call it again afterwards in client case.
+ * sanity.sh (ignore): Test above-fixed bugs and other ignore behaviors.
+ (dotest): New function.
+ Move modules checkin from modules test to start, so that other
+ tests can use mkmodules without a warning message.
+
+Wed Dec 20 13:06:17 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (send_to_server): don't check string's length twice.
+
+Wed Dec 20 02:05:19 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * login.c (login): took out debugging printf's.
+ (login): Removed unused variable `p'.
+
+Wed Dec 20 00:27:36 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * login.c (login): prefix scrambled password with 'A', so we know
+ which version of scrambling was used. This may be useful in the
+ future.
+ (get_cvs_password): skip past the leading 'A'.
+ Scramble $CVS_PASSWORD before returning it.
+
+ * scramble.c: made this work.
+
+Tue Dec 19 17:45:11 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * login.c (cvs_password): new static var, init to NULL.
+ (login): scramble() the password before using it.
+ Verify the password with the server.
+ Check CVSroot more carefully to insure that it is
+ "fully-qualified".
+ (get_cvs_password): if cvs_password is not NULL, just return it.
+ Never prompt -- just tell user why failed, then exit.
+ Try CVS_PASSWORD environment variable first.
+ (construct_cvspass_filename): try CVS_PASSFILE environment
+ variable first.
+
+ * client.h (connect_to_pserver): update prototype.
+
+ * client.c (cvsroot_parsed): new static var.
+ (parse_cvsroot): set `cvsroot_parsed' to 1 when done.
+ (connect_to_pserver): return int.
+ Take `verify_only' arg. If it is non-zero, perform password
+ verification with the server and then shut down the connection and
+ return.
+ Call parse_cvsroot() before doing anything.
+
+ * server.c (authenticate_connection): deal with verification
+ requests as well as authorization requests.
+ descramble() the password before hashing it.
+
+ * cvs.h: prototype scramble() and descramble().
+
+ * Makefile.in: build scramble.o.
+
+ * scramble.c: new file, provides trivial encoding but NOT real
+ encryption.
+
+Mon Dec 18 20:57:58 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * login.c (login): don't insert extra newlines. They were
+ harmless, but confusing.
+
+Mon Dec 18 15:32:32 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * hash.c, hash.h (findnode_fn): New function.
+ * hash.c (hashp): Tweak hash function so that findnode_fn works.
+ * update.c (ignore_files): Call findnode_fn, not findnode.
+
+Mon Dec 18 09:34:56 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * myndbm.c: Remove arbitrary limit.
+
+ * client.c: Fix comment--Windows 95 requires NO_SOCKET_TO_FD, not
+ Windows NT.
+
+Mon Dec 18 01:06:20 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (server_sock): replaces `server_socket'.
+ (start_kerberos_server): added FIXME comment about how
+ NO_SOCKET_TO_FD is not dealt with in the kerberos case.
+ (connect_to_pserver): deal with NO_SOCKET_TO_FD case.
+ (read_line): deal with NO_SOCKET_TO_FD case.
+ (read_from_server): deal with NO_SOCKET_TO_FD case.
+ (send_to_server): deal with NO_SOCKET_TO_FD case.
+ (get_responses_and_close): deal with NO_SOCKET_TO_FD case.
+
+ * client.c (send_to_server): error check logging.
+ (start_server): error check opening of logfiles.
+ (read_from_server): error check logging.
+ (read_line): use fwrite() to log, & error_check it.
+ Don't log if using socket style, because read_from_server()
+ already logged for us.
+
+Mon Dec 18 00:52:26 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (use_socket_style): new static var, init to 0.
+ (server_socket): new static var.
+ (connect_to_pserver): don't deal with logging here.
+ Caller changed.
+ (start_kerberos_server): don't deal with logging here either.
+ Caller changed.
+
+Mon Dec 18 00:40:46 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (send_modified): don't error-check `to_server';
+ send_to_server() does that now.
+
+Mon Dec 18 00:19:16 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * login.c (get_cvs_password): Init `linebuf' to NULL.
+ free() `linebuf' and reset it for each new line.
+ (login): same as above.
+
+ * client.c: Removed all the varargs prototyping gunk.
+ (to_server, from_server): make these static.
+ (from_server_logfile, to_server_logfile): new vars.
+ (start_server): init above two new vars to NULL.
+ (send_to_server): return void.
+ Correct bug in which amount to be written would be too high if the
+ loop ever ran more than once.
+ Log to `to_server_logfile' if it's non-NULL.
+ (read_from_server): new func, does raw reading from server.
+ Logs to `from_server_logfile' if it's non-NULL.
+ (update_entries): just use read_from_server() instead of looping
+ to fread() directly from `from_server'.
+ (read_line): Log to `from_server_logfile' if it's non-NULL.
+
+ * client.h: send_to_server() returns void now.
+ (read_from_server): prototype.
+
+Sun Dec 17 19:38:03 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * checkout.c (checkout_proc), client.c, lock.c (readers_exist),
+ login.c, modules.c (cat_module, do_module): Remove arbitrary limits.
+
+ * client.c (send_to_server): Fix typo (NULL -> '\0').
+ (get_responses_and_close): Set server_started to 0 instead of
+ setting to_server and from_server to NULL.
+ * client.c: Make to_server and from_server static.
+
+Sun Dec 17 17:59:04 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.h (to_server, from_server): don't declare these anymore.
+ They are now entirely private to client.c (and in fact will go
+ away soon there too).
+
+Sun Dec 17 15:40:58 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.h: update prototype of send_to_server().
+
+ * client.c, watch.c, update.c, tag.c, status.c, rtag.c, remove.c,
+ release.c, patch.c, log.c, import.c, history.c, edit.c, diff.c,
+ commit.c, client.c, checkout.c, admin.c, add.c:
+ Convert all send_to_server() calls that used formatting to send
+ pre-formatted strings instead. And don't error check
+ send_to_server(), because it does its own error checking now.
+
+ * client.c (send_to_server): don't use vasprintf(), just fwrite a
+ certain number of bytes to the server. And do error checking
+ here, so our callers don't have to.
+ (send_arg): use send_to_server() instead of putc()'ing
+ directly to `to_server'.
+
+Sun Dec 17 14:37:52 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * options.h.in (AUTH_CLIENT_SUPPORT, AUTH_SERVER_SUPPORT):
+ Define to 1 but leave commented out, instead of #undef'ing them.
+ This treats them like everything else in this file.
+
+ * client.c: define server_started, init to 0.
+ (start_server): set server_started to 1.
+
+ * client.h: declare `server_started', extern.
+ AUTH_CLIENT_SUPPORT moved here from cvs.h.
+
+ * cvs.h: moved AUTH_CLIENT_SUPPORT stuff to client.h.
+
+ * edit.c (notify_check): use new var server_started.
+
+Sun Dec 17 00:44:17 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (get_responses_and_close): Really stop ignoring ECHILD
+ errors. The Nov 30 1995 change claimed to do this, but the code
+ was not actually changed.
+
+ * update.c (ignore_files): Revert H.J. Lu change; it was wrong for
+ directories and sometimes looked at sb.st_mode when it wasn't set.
+ * import.c (import_descend): Revert H.J. Lu change; it was wrong
+ for directories and the extra lstat call was an unnecessary
+ performance hit.
+ * sanity.sh (import): Add test for the second of these two bugs.
+
+Sat Dec 16 17:26:08 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (send_to_server): Remove arbitrary limit. Also remove
+ !HAVE_VPRINTF code; all relevant systems have vprintf these days.
+
+Sat Dec 16 21:35:31 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * checkout.c (checkout): use send_to_server() now.
+
+Sat Dec 16 21:18:16 1995 H.J. Lu (hjl@gnu.ai.mit.edu)
+ (applied by kfogel@cyclic.com)
+
+ * import.c (import_descend): We ignore an entry if it is
+ 1. not a file, nor a link, nor a directory, or
+ 2. a file and on the ignore list.
+
+ * update.c (ignore_files): We ignore any thing which is
+ 1. not a file, or
+ 2. it is a file on the ignore list.
+
+Sat Dec 16 00:14:19 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (send_to_server): corrected comment.
+
+ * client.h: prototype new func send_to_server().
+
+ * add.c, admin.c, client.c, commit.c, diff.c, edit.c, history.c,
+ import.c, log.c, patch.c, release.c, remove.c, rtag.c, status.c,
+ tag.c, update.c, watch.c:
+ Use send_to_server() instead of writing directly to to_server.
+
+ * client.c: conditionally include the right stuff for variable arg
+ lists.
+ (send_to_server): new func.
+
+Fri Dec 15 23:10:22 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * error.c: expanded comments.
+
+ * client.c (connect_to_pserver): verbosify errors.
+ (connect_to_pserver): use send() and recv(), not write() and
+ read(). Sockets are not file descriptors on all systems.
+
+Fri Dec 15 22:36:05 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (connect_to_pserver): oops, removed old debugging
+ printf.
+
+Fri Dec 15 18:21:16 1995 Karl Fogel (kfogel@floss.cyclic.com)
+
+ * client.c (auth_server_port_number): don't call htons();
+ init_sockaddr() does that for us.
+ (init_sockaddr): zero the sockadder_in struct before doing
+ anything with it. IBM TCP/IP docs recommend this, and it can't
+ hurt.
+
+Fri Dec 15 15:21:53 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (connect_to_pserver): new var `port_number', initialize
+ with new func auth_server_port_number() and pass to
+ init_sockaddr().
+ (auth_server_port_number): new func. Right now it just returns
+ `htons (CVS_AUTH_PORT)'. We'll probably add the ability to
+ specify the port at run time soon, anyway, so having this function
+ will make that easier.
+
+Wed Dec 6 18:08:40 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.h: Add CVSREP.
+ * find_names.c (find_dirs): Skip CVSREP too.
+ * fileattr.h, fileattr.c: New files, to manipulate file attributes.
+ * hash.c (nodetypestring), hash.h (enum ntype): Add FILEATTR.
+ * hash.c, hash.h (list_isempty): New function.
+ * recurse.c (do_recursion): Call fileattr_startdir before
+ processing files in a directory and fileattr_write and
+ fileattr_free (after files, before recursing).
+ * watch.c, watch.h: New files, to handle notification features.
+ * edit.c, edit.h: New file, to handle new read-only checkout features.
+ * client.c, server.c: Add "Mode" request, to change the mode of a file
+ when it is checked in.
+ * main.c (cmds): Add "watch", "edit", "unedit", "watchers", "editors".
+ * main.c: Split command help from usg into new variable cmd_usage,
+ which.
+ (main): Add --help-commands option to print out cmd_usage.
+ * cvs.h: Declare watch, edit, unedit, watchers, editors.
+ * client.c, client.h: Add client_watch, client_edit, client_unedit,
+ client_watchers, client_editors.
+ * client.c, server.c: Add notification stuff.
+ * update.c (checkout_file, patch_file), checkin.c (Checkin): Check
+ _watched attribute when deciding read-only or read-write.
+ * commit.c (checkaddfile): Call fileattr_newfile to set attributes
+ on newly created files.
+ * release.c (release):
+ * cvs.h: Add CVSADM_NOTIFY and CVSADM_NOTIFYBAK.
+ * recurse.c (do_recursion): Call notify_check.
+ * commit.c (commit_fileproc): Call notify_do after committing file.
+ * client.c (get_responses_and_close): Set to_server and from_server
+ to NULL so that it is possible to tell whether we are speaking to
+ the server.
+ * cvs.h: Add CVSROOTADM_NOTIFY.
+ * mkmodules.c (main): Add CVSROOTADM_NOTIFY to filelist.
+ * Makefile.in (SOURCES,OBJECTS,HEADERS): Add new files mentioned above.
+ * lock.c, cvs.h (lock_tree_for_write, lock_tree_cleanup): New
+ functions, taken from old commit.c writelock code. As part of
+ this, fsortcmp and lock_filesdoneproc go from commit.c to lock.c.
+ So does locklist but it gets renamed to lock_tree_list.
+ * commit.c: Use lock_tree_*.
+
+Fri Dec 15 10:37:00 1995 J.T. Conklin <jtc@slave.cygnus.com>
+
+ * tag.c (tag_usage): Added -r and -D flags to usage string.
+ (tag): Detect when user specifies both -r and -D arguments.
+ Pass -r and -D arguments to server.
+
+Thu Dec 14 11:56:13 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (start_rsh_server): use RSH_NEEDS_BINARY_FLAG to
+ conditionalize "-b" option to "rsh".
+
+ * run.c (filter_stream_through_program): document return value and
+ error behavior.
+
+ * client.c (filter_through_gunzip): pass the supposedly
+ superfluous "-d" option to gunzip, to avoid stimulating what seems
+ to be an argument-passing bug in spawn() under OS/2 with IBM
+ C/C++. Yucko.
+
+Wed Dec 13 20:08:37 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * options.h.in (RCSBIN_DFLT): Recommend specifying -b in
+ inetd.conf for pserver. That is a pretty good solution.
+
+Wed Dec 13 18:29:59 1995 Preston L. Bannister <pbannister@ca.mdis.com>
+ and Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (send_modified): make sure that vers and vers->options
+ are non-NULL before strcmp()'ing them with "-kb".
+ Initialize `bin' near where it is used, not at beginning of
+ function.
+ (update_entries): make sure `options' is non-NULL before
+ strcmp()'ing with "-kb".
+ Initialize `bin' near where it is used, not at beginning of
+ function.
+
+Tue Dec 12 18:56:38 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * options.h.in (RCSBIN_DFLT): document the probable need for this
+ to be set in the authenticating server.
+
+Tue Dec 12 11:56:43 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * server.c (expand_proc): If mfile is non-NULL, return it too as
+ part of the expansion.
+ * sanity.sh (modules): Add tests for above-fixed bug.
+
+Mon Dec 11 21:39:07 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * dog.c (flea_bath): Take `suds' arg.
+ All collars changed.
+
+Mon Dec 11 15:58:47 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * login.c (login): if client password file doesn't exist, create
+ it, duh.
+
+ * main.c (main): die if CVSroot has access-method but no
+ username.
+
+ * root.c: added some comments.
+
+ * main.c: removed all code pertaining to the "-a" option. We
+ specify access-method in CVSroot now.
+
+ * client.c (parse_cvsroot): new var, `access_method'. If CVSroot
+ is prepended with an access method (i.e.,
+ ":pserver:user@host:/path"), then handle it.
+
+ * login.c (login): use || when checking if CVSroot is "fully
+ qualified".
+ Prepend ":pserver:" before writing to ~/.cvspass.
+ (get_cvs_password): Take no parameters; we'll just use CVSroot to
+ get the password.
+
+Mon Dec 11 12:43:35 1995 adamg <adamg@microsoft.com>
+
+ * error.c, client.c, remove.c, main.c: Add explicit casts for some
+ function pointers to remove warnings under MS VC.
+ * main.c (main): remove use of NEED_CALL_SOCKINIT in favor of the
+ more generic INITIALIZE_SOCKET_SUBSYSTEM. Note that the code assumes
+ that if INITIALIZE_SOCKET_SUBSYSTEM() returns, socket subsystem
+ initialization has been successful.
+
+Sat Dec 9 22:01:41 1995 Dan O'Connor <doconnor@tii.com>
+
+ * commit.c (check_fileproc): pass RUN_REALLY flag to run_exec,
+ because it's okay to examine the file with noexec set.
+
+Sat Dec 9 20:28:01 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (update_entries): new var, `bin, init to 0.
+ Use it in determining whether to convert the file.
+ (send_modified): same as above.
+
+Fri Dec 8 17:47:39 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (downcase_string): removed.
+ (check_repository_password): don't deal with case-insensitivity
+ anymore.
+
+ * options.h.in (CVS_PASSWORDS_CASE_SENSITIVE): deleted this. No
+ need for it anymore.
+
+Thu Dec 7 21:08:39 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (check_repository_password): when checking for false
+ prefix-matches, look for ':', not '@'. Duh.
+
+Thu Dec 7 18:44:51 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * options.h.in (CVS_PASSWORDS_CASE_SENSITIVE): replaces
+ CVS_PASSWORDS_CASE_INSENSITIVE; passwords are now insensitive by
+ default. Expanded explanatory comment.
+
+ * login.c (get_cvs_password): Use memset(), not bzero(). I
+ botched this change earlier.
+
+ * server.c (check_repository_password): no need to check
+ xmalloc()'s return value.
+ (check_repository_password): check for false prefix-matches (for
+ example, username is "theo" and linebuf contains user
+ "theocracy").
+
+Thu Dec 7 14:49:16 1995 Jim Meyering (meyering@comco.com)
+
+ * filesubr.c (isaccessible): Rename from isaccessable.
+ Update callers.
+ * cvs.h: Update prototype.
+ * main.c (main): Update callers.
+ * server.c (main): Update callers.
+
+Thu Dec 7 12:50:20 1995 Adam Glass <glass@NetBSD.ORG>
+
+ * cvs.h: "isaccessible" is the correct spelling.
+ Also add "const" to second arg to make prototype match
+ declaration.
+
+Thu Dec 7 11:06:51 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c, login.c: memset() instead of bzero().
+
+Thu Dec 7 00:08:53 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (authenticate_connection): document server's side of
+ the Authentication Protocol too.
+
+ * client.c (connect_to_pserver): when printing out "unrecognized
+ response", also print out the offending response.
+
+ * server.c (check_password): take `repository' arg too now.
+ Call check_repository_password() before checking /etc/passwd.
+ (check_repository_password): new func.
+
+ * options.h.in (CVS_PASSWORDS_CASE_INSENSITIVE): new define, unset
+ by default.
+
+Wed Dec 6 18:51:16 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (check_password): If user has a null password, then
+ return 1 if arg is also null.
+ Reverse sense of return value. Caller changed.
+
+Wed Dec 6 14:42:57 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (check_password): new func.
+ (authenticate_connection): call above new func.
+
+ * login.c (login): use construct_cvspass_filename().
+ If CVSroot is not "fully-qualified", then insist the user qualify
+ it before going on.
+ (get_cvs_password): fleshed out. Now reads from ~/.cvspass, or
+ prompts if no appropriate password found.
+ (construct_cvspass_filename): new func.
+
+ * server.c (authenticate_connection): send ACK or NACK to client.
+
+ * client.c (connect_to_pserver): check for ACK vs NACK response
+ from server after sending authorization request.
+
+ * login.c (get_cvs_password): new func.
+
+ * client.c (connect_to_pserver): use new func get_cvs_password().
+ Prototype it at top of file. Hmmm.
+
+Wed Dec 6 13:29:22 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c: same as below (AUTH_SERVER_SUPPORT).
+
+ * main.c: same as below (AUTH_SERVER_SUPPORT where appropriate).
+
+ * login.c: same same as below.
+
+ * cvs.h: same as below.
+
+ * client.c: use AUTH_CLIENT_SUPPORT, not CVS_LOGIN.
+
+ * options.h.in (AUTH_CLIENT_SUPPORT, AUTH_SERVER_SUPPORT): these
+ replace CVS_LOGIN.
+
+Wed Dec 6 00:04:58 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (authenticate_connection): expanded comment.
+
+Tue Dec 5 23:37:39 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (connect_to_pserver): read password from prompt for
+ now.
+
+ * server.c (authenticate_connection): if the password passes
+ muster, then don't abort.
+
+Tue Dec 5 22:46:37 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * subr.c (strip_trailing_newlines): new func.
+
+ * client.c (connect_to_pserver): took out print statements.
+
+ * server.c (authenticate_connection): removed print statments.
+ Use new func strip_trailing_newlines() to purify `repository',
+ `username', and `password'.
+ Run a primitive password check, just for testing.
+
+ * client.c (connect_to_pserver): use CVS_AUTH_PORT.
+ Take tofdp, fromfdp, and log args. Caller changed.
+ (get_responses_and_close): either kerberos and CVS_LOGIN might
+ have one fd for both directions, so adjust #ifdef accordingly.
+
+ * cvs.h (CVS_AUTH_PORT): new define, default to 2401.
+ Prototype strip_trailing_newlines().
+
+Tue Dec 5 16:53:35 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * server.c (authenticate_connection): new func.
+
+ * client.c (init_sockaddr): func moved here from login.c.
+ (connect_to_pserver): same as above. Take no args, now.
+ Include <sys/socket.h>, <netinet/in.h>, <netdb.h>, if CVS_LOGIN.
+
+ * cvs.h: Declare use_authenticating_server, as extern int.
+ Declare connect_to_pserver().
+
+ * main.c (main): call authenticate_connection(). Removed testing
+ code.
+ Add 'a' to the short-option string in the getopt() call.
+
+ * login.c (connect_to_pserver): moved to client.c.
+
+Tue Dec 5 16:01:42 1995 Peter Chubb <peterc@bookworm.sw.oz.au>
+ (patch applied by Karl Fogel <kfogel@cyclic.com>)
+
+ * update.c (join_file): if vers->vn_user is "0", file has been
+ removed on the current branch, so print an error and return.
+
+Mon Dec 4 14:27:42 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.6.3.
+
+Mon Dec 4 16:28:25 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * release.c (release): add return (0) as last line
+
+ * cvs.h: declare program_path
+
+ * main.c define program_path
+ (main): set program_path
+
+ * release.c (release): use program_path for update_cmd
+
+Mon Dec 4 11:22:42 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Version 1.6.2.
+
+Sun Dec 3 20:02:29 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * rcs.h (struct rcsnode), rcs.c (freercsnode): Add expand field.
+ * rcs.h (RCSEXPAND): New #define.
+ * rcs.c (RCS_reparsercsfile): Record keyword expansion in expand
+ field of struct rcsnode.
+ * update.c (checkout_file): Set keyword expansion in Entries file
+ from rcs file if there is nowhere else to set it from.
+ * client.c (send_modified, update_entries) [LINES_CRLF_TERMINATED]:
+ If -kb is in effect, don't convert.
+
+ * update.c (update_file_proc), commit.c (check_fileproc),
+ rcscmds.c (RCS_merge): Direct stdout to DEVNULL rather than
+ passing -s option to grep. This avoids trouble with respect to
+ finding a grep which support -s and whether we should use the (GNU
+ grep) -q option if it exists.
+ * options.h.in: Change "@ggrep_path@" to "grep".
+
+Fri Dec 1 11:53:19 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * rcs.c (RCS_gettag): new parameter return_both force return both
+ tags: the symbolic and the numeric one.
+ (RCS_getversion): new parameter return_both is forwarded to
+ RCS_gettag.
+
+ * rtag.c, tag.c, commit.c, patch.c, update.c: pass 0 as additional
+ last parameter to RCS_getversion and RCS_gettag
+
+ * rcs.h (RCS_gettag): new parameter return_both.
+ (RCS_getversion): new parameter return_both.
+
+ * cvs.h (struct vers_ts): add vn_tag slot for symbolic tag name
+
+ * vers_ts.c (Version_TS): call RCS_getversion with 1 for
+ return_both and split output into vn_rcs and vn_tag
+ (freevers_ts): free vn_tag
+
+ * update.c (checkout_file): use vn_tag instead of vn_rcs when
+ calling 'rcs co' to allow rcs expansion of :$Name :
+
+Thu Nov 30 20:44:30 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (get_responses_and_close): undo previous change
+ regarding waitpid(). The problem has been solved by modifying
+ os2/waitpid.c instead of its callers.
+
+Thu Nov 30 16:37:10 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c: All these changes are for OS/2, which will no longer have
+ a separate client.c:
+ (start_kerberos_server): new func, contains code that
+ used to be in start_server().
+ (start_server): moved kerberos code to above function, reorganized
+ the rest. Added authentication clause.
+ (call_in_directory): test errno against EACCESS, if EACCESS is
+ defined (this is for OS/2's oddball mkdir).
+ (change_mode): don't set execute permission on anything if
+ EXECUTE_PERMISSION_LOSES is defined.
+ (get_responses_and_close): if START_RSH_WITH_POPEN_RW, then use
+ pclose() instead of fclose().
+ If waitpid errors with ECHILD, don't die. This is okay.
+ (start_rsh_server): alternate definition if
+ START_RSH_WITH_POPEN_RW.
+
+ * main.c: [all these changes conditional on CVS_LOGIN: ]
+ Don't prototype connect_to_pserver, don't enter it in cmds[]
+ (actually, it was never in there, I don't know why my previous
+ change said it was).
+ (use_authenticating_server): new global var.
+ (main): if "-a", then set above new var to TRUE.
+ (usg): document "-a" option.
+
+Wed Nov 29 12:55:10 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * main.c: Prototype connect_to_pserver(), and enter it in cmds[].
+ (main): test some extremely primitive authentication.
+
+ * login.c: Include <sys/socket.h>
+ (connect_to_pserver): new func.
+ (init_sockaddr): new func.
+
+Mon Nov 20 14:07:41 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (TAGFILES): Separate out from DISTFILES, for C code.
+ (TAGS,tags): Use TAGFILES not DISTFILES.
+
+Sun Nov 19 11:22:43 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * recurse.c (do_recursion): Don't call server_pause_check if there
+ are writelocks around. Revise comment to reflect fact we are no
+ longer relying on a writelock'd operations being "unable" to
+ generate enough data to pause.
+
+Sun Nov 19 10:04:50 1995 Peter Wemm <peter@haywire.DIALix.COM>
+
+ * server.c, server.h, options.h.in: Implement hooks for doing
+ simple flow control on the server to prevent VM exhaustion on a
+ slow network with a fast server.
+ * recurse.c: Call the flow control check at a convenient location
+ while no locks are active. This is a convenience tradeoff against
+ accurate flow control - if you have a large directory it will all
+ be queued up, bypassing the flow control check until the next
+ directory is processed.
+
+Sat Nov 18 16:22:06 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c, update.c, vers_ts.c, server.c, rcs.c, lock.c,
+ ignore.c, entries.c, diff.c, commit.c, checkin.c:
+ Use new macro `existence_error', instead of comparing errno to
+ ENOENT directly.
+
+Fri Nov 17 14:56:12 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (start_server): removed alternate version of this func,
+ since os2/client.c will now be used under OS/2.
+
+Thu Nov 16 22:57:12 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (start_server): ifdef HAVE_POPEN_RW, use a different
+ version of start_server(). This is maybe not the cleanest cut to
+ make, but it's better than mucking around with yet more #ifdefs in
+ the middle of the old start_server() function. Once things are
+ up, I may reposition this code.
+
+Wed Nov 15 15:33:37 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * main.c (main): ifdef NEED_CALL_SOCKINIT, then call SockInit().
+ Only OS/2 needs this initialization.
+
+Tue Nov 14 18:54:01 1995 Greg A. Woods <woods@most.weird.com>
+
+ * patch.c:
+ - fix orientation of test for result of getline() call
+ - use fputs() not printf() when just copying file out
+
+ * cvsbug.sh:
+ - add space after #!
+ - new rcs id
+ - allow version to be edited by Makefile.
+
+ * Makefile.in:
+ - make Makefile a dependent of all (this might not be perfect, but
+ it at least gives you a chance to catch up on the second
+ go-around).
+ - filter cvsbug.sh in a manner similar to cvsinit.sh to get the
+ version number set from version.c
+
+Tue Nov 14 13:28:17 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: Call old log file check.plog, not check.olog.
+
+ * sanity.sh: Convert remaining tests from old-style ('***' on fail
+ and nothing on pass), to new-style (FAIL on fail and PASS on pass).
+
+ * sanity.sh: Fix ability to run only some of the tests (always run
+ tests 1-4.75 to set up repository, document better how it works).
+
+ * sanity.sh: Change "completed successfully" to "completed" in
+ message--many tests, but not all, exit if they fail.
+
+Tue Nov 14 15:10:00 1995 Greg A. Woods <woods@most.weird.com>
+
+ * sanity.sh: test 63 doesn't work and probably can't
+
+Tue Nov 14 12:22:00 1995 Greg A. Woods <woods@most.weird.com>
+
+ * sanity.sh: many minor tweaks:
+ - make the optional arguments almost work
+ - use a function 'directory_cmp' instead of 'diff -r'
+ - fix up a few more tests that weren't working....
+
+Mon Nov 13 07:33:55 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * cvs.h: ifdef USE_OWN_POPEN, #include "popen.h". Only OS/2 has
+ its own popen()/pclose() right now.
+
+Mon Nov 13 04:06:10 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * cvs.h: conform to 80 column standard (yes, I'm a pedant).
+
+Sat Nov 11 13:45:13 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * client.c (process_prune_candidates): use unlink_file_dir() to
+ remove the directory, instead of invoking "rm" via run_exec().
+
+Fri Nov 10 14:38:56 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * main.c (main): removed "#define KF_GETOPT_LONG 1", since that
+ change is no longer in testing.
+
+Thu Nov 9 20:32:12 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * release.c (release): Use Popen(), not popen().
+
+Wed Nov 8 10:20:20 1995 Jim Meyering (meyering@comco.com)
+
+ * entries.c (ParseTag): Remove dcl of unused local.
+
+ * patch.c: Include getline.h.
+
+Wed Nov 8 11:57:31 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * options.h.in: add configuration option STEXID_SUPPORT (default
+ is off i.e. old semantics)
+
+ * filesubr.c (isaccessable): new function. Checks access-rights
+ for files like access(), but is getxid-safe. Falls back to
+ access() if SETXID_SUPPORT is not enabled.
+ (isfile): replace stat() by isaccessable(file, F_OK)
+ (isreadable): replace access() by isaccessable()
+ (iswritable): ditto
+ (make_directory): rename local variable buf to sb
+
+ * cvs.h: add prototype for new function isaccessable.
+
+ * server.c (serve_root): replace access() by isaccessable()
+
+ * cvsrc.c (read_cvsrc): replace access() by isreadable()
+
+ * main.c (main): replace access() by isaccessable()
+
+Wed Nov 8 10:22:41 1995 Greg A. Woods <woods@most.weird.com>
+
+ * entries.c (fgetentent): change definition to static to match the
+ declaration at the top of the file
+
+Tue Nov 7 16:59:25 1995 J.T. Conklin <jtc@lestat.cygnus.com>
+
+ * rcs.c (RCS_getbranch, RCS_getdate, RCS_getrevtime, RCS_gettag,
+ RCS_getversion, RCS_head): Use assert() instead of attempting to
+ "do the right thing" with a bogus RCSNode argument.
+
+Mon Nov 6 14:24:34 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * vers_ts.c: Remove ctime define. It is just asking for trouble.
+
+Mon Nov 6 11:58:26 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * vers_ts.c: ifdef ctime, undef it before redefining it. It is a
+ macro on some systems.
+
+ * lock.c: don't prototype ctime() here. (See note below about
+ fgetentent() in entries.c.)
+
+Sun Nov 5 16:06:01 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * entries.c (fgetentent): don't prototype ctime here; we include
+ cvs.h, which includes system.h, which includes <time.h>
+ unconditionally (either as <time.h> or <sys/time.h>). Anyway, IBM
+ C/C++ chokes on mid-function, or even mid-file, prototypes. Sigh.
+
+Thu Nov 2 21:51:04 1995 Dan Wilder <dan@gasboy.com>
+
+ * rtag.c (rtag): Fix typo ("-T" -> "-F").
+
+Tue Oct 31 19:09:11 1995 Dan Wilder <dan@gasboy.com>
+
+ * diff.c (diff_dirproc): just return R_SKIP_ALL if dir not exist.
+ (diff_file_nodiff): don't complain if file doesn't exist, just
+ ignore.
+
+Tue Oct 31 09:25:10 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * sanity.sh: Use absolute pathname for mkmodules.
+
+Sat Oct 28 01:01:41 1995 Jim Meyering (meyering@comco.com)
+
+ * entries.c (ParseTag): Use getline instead of fgets.
+
+Fri Oct 27 13:44:20 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * cvs.h: do nothing about alloca ifdef ALLOCA_IN_STDLIB. I am
+ rather suspicious of this solution, and will not be surprised to
+ find out that there's a Right Way to handle this situation ("this
+ situation" being that OS/2 simply declares alloca in <stdlib.h>).
+ Suggestions are welcome; see src/cvs.h and lib/system.h to see why
+ I was getting a conflict in the first place.
+
+Wed Oct 25 16:03:20 1995 J.T. Conklin <jtc@slave.cygnus.com>
+
+ * cvs.h (struct entnode): Add user field.
+ * entries.c (fputentent): New function, write entries line.
+ (write_ent_proc): Call fputentent to write entries line.
+ (Entnode_Create): New function, construct new Entnode.
+ (Entnode_Destroy): New function, destruct old Entnode.
+ (AddEntryNode): Changed to take an Entnode argument instead of
+ separate user, version, timestamp, etc. arguments.
+ (fgetentent): Changed to return Entnode.
+ (struct entent, free_entent): Removed.
+
+Wed Oct 25 12:44:32 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * admin.c (admin): Don't rely on ANSI C string concatenation;
+ SunOS 4.1.3 /bin/cc doesn't support it.
+
+Tue Oct 24 22:34:22 1995 Anthony J. Lill <ajlill@ajlc.waterloo.on.ca>
+
+ * import.c (expand_at_signs): Check errno as well as return value
+ from putc. Some systems bogusly return EOF when successfully
+ writing 0xff.
+
+Tue Oct 24 14:32:45 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * admin.c (admin): use getcaller() instead of getpwuid
+
+ * subr.c (getcaller): prefer getlogin() to $USER and $LOGNAME
+ (especially useful for NT where getuid always returns 0)
+
+Tue Oct 24 06:22:08 1995 Jim Meyering (meyering@comco.com)
+
+ * cvsrc.c (read_cvsrc): Use getline instead of fgets.
+ * patch.c (patch_fileproc): Use getline instead of fgets.
+
+ * entries.c (fgetentent): Use getline instead of fgets.
+ Use xmalloc to allocate space for each returned entry.
+ Since LINE is no longer static, save it in struct entent.
+ (struct entent): New member, line.
+ (free_entent): New function.
+ (Entries_Open): Call it after each call to fgetentent.
+
+Tue Oct 24 11:13:15 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * cvs.h: Declare valloc again, but this time with the right
+ signature (also changed in libs/valloc.c)
+
+Mon Oct 23 12:17:03 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * logmsg.c (do_editor): Check for errors from stdio calls.
+
+Mon Oct 23 12:37:06 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.h: Don't declare valloc. Some systems (e.g. linux) declare
+ it in stdlib.h in a conflicting way.
+
+Mon Oct 23 08:41:25 1995 Jim Meyering (meyering@comco.com)
+
+ * commit.c (commit_filesdoneproc): Use getline instead of fgets.
+
+ * logmsg.c (do_editor): Use getline instead of fgets.
+ (rcsinfo_proc): Likewise.
+
+ * logmsg.c (do_editor): Lose if fclose of temp file output
+ stream fails.
+
+Mon Oct 23 11:59:41 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * cvs.h: add valloc declaration
+
+ * server.h: add server_cleanup prototype
+
+ * server.c: remove server_cleanup prototype
+
+ * mkmodules.c (server_cleanup): fix parameter type
+
+ * server.c: encapsulate wait_sig in #ifdef sun (it's only used in
+ code which is also encapsulated in #ifdef sun)
+
+ * rcscmds.c (RCS_deltag, RCS_lock): add definition of noerr
+ parameter
+
+ * error.c: include cvs.h instead of config.h, add USE(rcsid)
+
+ * error.c (error): fix parameter type
+
+ * update.c (join_file): encapsulate recent changes from garyo
+ within #ifdef SERVER_SUPPORT
+
+Sun Oct 22 13:47:53 1995 J.T. Conklin <jtc@slave.cygnus.com>
+
+ * client.c (update_entries): Fix memory leak; free mode_string and
+ file_timestamp.
+ (send_fileproc): Fix memory leak; call freevers_ts before exiting.
+
+ * module.c (do_module): Partially fix memory leak; added
+ variable so that the address of memory allocated by line2argv
+ is retained, but comment out the call to free_names. Freeing
+ the vector at that point loses because some of the elements
+ may be used later in the function.
+ (cat_module): fix memory leak.
+
+ * recurse.c (start_recursion): Fix memory leak; free return
+ value of Name_Repository after it has been used.
+
+Sat Oct 21 23:24:26 1995 Jim Meyering (meyering@comco.com)
+
+ * client.c (send_modified) [LINES_CRLF_TERMINATED]: Comment text
+ after #endif.
+
+Fri Oct 20 14:41:49 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: Add test 87a, to test for bug fixed by garyo in
+ change below.
+
+Fri Oct 20 10:59:58 1995 Gary Oberbrunner <garyo@darkstar.avs.com>
+
+ * update.c (join_file): send file back to client even if no
+ conflicts were detected, by calling Register().
+
+Fri Oct 20 10:46:45 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * lock.c: Add prototype for Check_Owner
+
+Thu Oct 19 16:38:14 1995 Jim Meyering (meyering@comco.com)
+
+ * lock.c (Check_Owner): Declare function `static int'.
+
+Thu Oct 19 14:58:40 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * expand_path.c (expand_variable): Fix typo ('*'->'(').
+
+Thu Oct 19 14:58:40 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * commit.c (commit_filesdoneproc): Check for errors from fopen,
+ fgets, and fclose.
+
+ * rcscmds.c (RCS_merge): Remove comment about rcsmerge -E.
+ Hacking CVS was never a very good solution; the situation is fixed
+ in RCS 5.7, and is documented in ../INSTALL.
+
+Thu Oct 19 15:06:15 1995 Jim Meyering (meyering@comco.com)
+
+ * filesubr.c (xchmod): Parenthesize arithmetic in operand of |
+ to placate gcc -Wall.
+
+ * expand_path.c (expand_path): Parenthesize assignments used as
+ truth values to placate gcc -Wall.
+
+ * commit.c (checkaddfile): Remove dcls of unused variables.
+ * lock.c (unlock): Remove dcl of unused variable.
+
+Thu Oct 19 14:58:40 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * root.c (Create_Root): If noexec, don't create CVS/Root.
+
+Wed Oct 18 11:19:40 1995 J.T. Conklin <jtc@slave.cygnus.com>
+
+ * lock.c (unlock): Change order of comparison so that Check_Owner
+ is called only if other conditions are true. This performance
+ enhancement was broken when the AFS support was added.
+
+Wed Oct 18 12:51:33 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * main.c (main): check if argv[0] is "pserver" with else-if, not
+ if, since we've already asked if it's "kserver".
+
+Tue Oct 17 18:09:23 1995 Warren Jones <wjones@tc.fluke.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: Deal with supplying a relative cvs filename, or
+ with a cvs filename which doesn't have basename "cvs".
+
+Mon Oct 16 15:58:31 1995 Vince Demarco <vdemarco@bou.shl.com>
+
+ * parseinfo.c (Parse_Info): if the Keyword isn't ALL the current
+ version doesn't use the expanded variable, It should.
+
+Mon Oct 16 15:58:31 1995 Gary Oberbrunner <garyo@avs.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * server.c (server_register): Don't pass NULL to printf if tag,
+ date, or conflict is NULL.
+
+Thu Oct 12 12:13:42 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * main.c (main): begin to handle "pserver"; support not complete
+ yet, however.
+
+Thu Oct 12 02:52:13 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * expand_path.c: Don't #include <pwd.h>, since cvs.h already does,
+ and not all systems' <pwd.h>s are protected from multiple inclusion.
+ * login.c: Likewise.
+
+Wed Oct 11 15:23:24 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * login.c (login): handle everything correctly now.
+
+Wed Oct 11 12:02:48 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * rcs.c (RCS_gettag): support RCS keyword Name
+
+Tue Oct 10 19:11:16 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * options.h.in (CVS_LOGIN): discuss, but leave commented out.
+ The "cvs login" command is still under construction; however, the
+ repository was changing so fast that instead of creating a branch
+ and dealing with the attendant hair, I'm just developing on the
+ trunk, making sure that everything is surrounded by "#ifdef
+ CVS_LOGIN ... #endif" so I don't get in anyone's way.
+
+ * login.c: include cvs.h before checking CVS_LOGIN, so it has a
+ chance to get defined before we ask if it's defined.
+ (login): oops, use semi not comma in `for' loop init.
+
+ * Makefile.in (SOURCES, OBJECTS): include login.c, login.o.
+
+ * main.c: added protoype for login().
+ Added "login" entry to cmds[].
+ (usg): added line about "login".
+
+ * login.c: new file.
+
+Tue Oct 10 18:33:47 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * Makefile.in (COMMON_OBJECTS): added error.o.
+ (OBJECTS): took error.o out; it's in COMMON_OBJECTS now.
+
+Tue Oct 10 12:02:37 1995 Thorsten Lockert <tholo@sigmasoft.com>
+
+ * cvsbug.sh: Cater to lame versions of sh (4.4BSD ash) by using
+ ${foo-bar} instead of `if....`.
+
+Tue Oct 10 12:02:37 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * remove.c (remove_fileproc): If noexec, don't remove file. Check
+ for error when removing file.
+
+Sun Oct 8 12:32:15 1995 Peter Wemm <peter@haywire.DIALix.COM>
+
+ * run.c: detect/use POSIX/BSD style reliable signals for critical
+ section masking etc. Helps prevent stray locks on interruption.
+
+Sat Oct 7 23:26:54 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * admin.c (admin): If group CVS_ADMIN_GROUP exists, allow only
+ users in that group to use "cvs admin".
+ * options.h.in: Default CVS_ADMIN_GROUP to "cvsadmin".
+
+Sat Oct 7 23:05:24 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * add.c, checkout.c, commit.c, cvs.h, filesubr.c, import.c,
+ lock.c, main.c, modules.c, options.h.in: New variable cvsumask
+ which is used to set mode of files in repository (regardless of
+ umask in effect when cvs is run).
+
+Sat Oct 7 22:40:17 1995 Stephen Bailey <sjbailey@sand.npl.washington.edu>
+
+ * lock.c: Include AFSCVS ifdefs to deal with AFS's lack of
+ correspondance between userid's from stat and from geteuid.
+
+Sat Oct 7 22:28:49 1995 Scott Carson <sdc@TracerTech.COM>
+
+ * add.c (add): Pass -ko, not -k -ko, to set keyword expansion options.
+
+ * admin.c (admin): Don't skip first argument when sending to server.
+
+Fri Oct 6 21:45:03 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * version.c: Version 1.6.1.
+
+Fri Oct 6 21:31:28 1995 Jeff Johnson <jbj@brewster.jbj.org>
+
+ * cvs.h, admin.c, client.c, commit.c, log.c, modules.c,
+ parseinfo.c, patch.c, recurse.c, rtag.c, status.c, tag.c:
+ Prototype when dealing in pointers to functions.
+
+Fri Oct 6 21:07:22 1995 Mark H. Wilkinson <mhw@minster.york.ac.uk>
+
+ * cvsrc.c (read_cvsrc): fix look up of command names in cvsrc file
+ to use full name from command table rather than possible nickname
+ in argv. Fixes errors with things like `cvs di' when cvsrc has
+ `diff -u5' in it.
+
+Thu Aug 3 01:03:52 1995 Vince DeMarco <vdemarco@bou.shl.com>
+
+ * parseinfo.c (Parse_Info): Add code to call expand_path function
+ instead of using built in code.
+
+ * wrapper.c (wrap_add): Add code to call expand_path function to
+ expand all built in variables.
+
+ * expand_path.c (New file): expand things that look like
+ environmental variables (only expand local CVS environmental
+ variables) and user names like ~/.
+ * cvs.h: Declare expand_path.
+
+ * Makefile.in (SOURCES, OBJECTS): Added expand_path.c,
+ expand_path.o.
+
+Fri Oct 6 14:03:09 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * ignore.c (ign_setup): Don't try to look for a file in CVSroot if
+ client. (The recent tightening of the error checking detects this).
+
+ * commit.c (checkaddfile): Don't try to pass options if it is "".
+
+Thu Oct 5 18:04:46 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * sanity.sh: unset CVSREAD, since it causes the script to bomb.
+
+Thu Oct 5 18:29:17 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * remove.c, add.c, commit.c, cvs.h: Remove CVSEXT_OPT stuff; it
+ has been broken for ages and the options are already stored in the
+ Entries file.
+
+Thu Oct 5 18:20:13 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * commit.c (checkaddfile): New argument options; pass it to RCS.
+ (commit_fileproc): Pass it.
+
+Tue Oct 3 09:26:00 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * version.c: upped to 1.6.
+
+Mon Oct 2 18:10:35 1995 Larry Jones <larry.jones@sdrc.com>
+
+ * server.c: if HAVE_SYS_BSDTYPES_H, include <sys/bsdtypes.h>.
+
+Mon Oct 2 10:34:53 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * version.c: Upped version to 1.5.95.
+
+Mon Oct 2 15:16:47 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * tag.c, rtag.c: pass "mov" instead of "add" if tag will be moved
+ (i.e. invoked with -F)
+
+Sun Oct 1 18:36:34 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * version.c: upped to 1.5.94.
+
+ * server.c: reverted earlier ISC change (of Sep. 28).
+
+ * version.c: upped to 1.5.93, for Peter Wemm's new SVR4 patch.
+
+Sun Oct 1 14:51:59 1995 Harlan Stenn <Harlan.Stenn@pfcs.com>
+
+ * main.c: don't #include <pwd.h>; cvs.h does that already.
+
+Fri Sep 29 15:21:35 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * version.c: upped to 1.5.91 for another pre-1.6 release.
+
+Fri Sep 29 14:41:14 1995 <bmeier@rzu.unizh.ch>
+
+ * root.c: start rcsid[] with "CVSid".
+
+Fri Sep 29 13:22:44 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * diff.c (diff): Doc fix.
+
+Fri Sep 29 14:32:36 1995 Norbert Kiesel <nk@col.sw-ley.de>
+
+ * repos.c (Short_Repository): chop superfluous "/".
+
+ * tag.c (pretag_proc): correct user-visible string.
+
+ * rtag.c (pretag_proc): correct user-visible string.
+
+Fri Sep 29 13:45:36 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * cvs.h (USE): if __GNUC__ != 2, expand to a dummy var instead of
+ nothing.
+
+Thu Sep 28 13:37:05 1995 Larry Jones <larry.jones@sdrc.com>
+
+ * server.c: ifdef ISC, include <sys/bsdtypes.h>.
+
+Fri Sep 29 07:54:22 1995 Mike Sutton <mws115@llcoolj.dayton.saic.com>
+
+ * filesubr.c (last_component): Don't use ANSI style declaration.
+
+Wed Sep 27 15:24:00 1995 Del <del@matra.com.au>
+
+ * tag.c, rtag.c: Pass a few extra options to the script
+ named in taginfo (del/add, and revision number).
+
+ * tag.c: Support a -r option (at long last). Also needs
+ a -f option to tag the head if there is no matching -r tag.
+
+Tue Sep 26 11:41:08 1995 Karl Fogel <kfogel@totoro.cyclic.com>
+
+ * version.c: Upped version to 1.5.89 for test release preceding
+ 1.6.
+
+Wed Sep 20 15:32:49 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * ignore.c (ign_add_file): Check for errors from fopen and fclose.
+
+Tue Sep 19 18:02:16 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * Makefile.in (DISTFILES): Remove sanity.el from this list; the
+ file has been deleted.
+
+Thu Sep 14 14:17:52 1995 Peter Wemm <peter@haywire.dialix.com>
+
+ * import.c: Recover from being unable to open the user file.
+
+ * update.c (join_file): Print a message in the case where the file
+ was added.
+
+ * mkmodules.c: Deal with .db as well as .pag/.dir (for use with
+ BSD 4.4 and real dbm support).
+
+Mon Sep 11 15:44:13 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * release.c (release): Revise comment regarding why and how we
+ skip argv[0].
+
+Mon Sep 11 10:03:59 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * release.c (release): use return value of pclose to determine
+ success of update.
+
+Mon Sep 11 09:56:33 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * release.c (release_delete): Fix comment.
+
+Sun Sep 10 18:48:35 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * release.c (release): made work with client/server.
+ Don't ask if <arg> is mentioned in `modules'.
+
+Fri Sep 8 13:25:55 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: When committing a removal, send stdout to LOGFILE;
+ this is no longer a silent operation.
+
+ * sanity.sh: Remove OUTPUT variable; it is unused.
+
+ * client.c: Add comment regarding deleting temp file.
+ * main.c: Add comment regarding getopt REQUIRE_ORDER.
+
+Thu Sep 7 20:24:46 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * main.c (main): use getopt_long(), accept "--help" and
+ "--version".
+ Don't assume EOF is -1.
+
+Thu Sep 7 19:18:00 1995 Jim Blandy <jimb@cyclic.com>
+
+ * cvs.h (unlink_file_dir): Add prototype for this.
+
+Thu Sep 7 14:38:06 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * ALL FILES: add semicolon, as indicated below.
+
+ * cvs.h (USE): don't provide semicolon in the expansion of the USE
+ macro; we'd rather the callers provided it themselves because that
+ way etags doesn't get fooled.
+
+Mon Sep 4 23:30:41 1995 Magnus Hyllander <mhy@os.se>
+
+ * checkout.c: cvs export now takes -k option and does not default
+ to -kv.
+ * checkout.c, cvs.h, modules.c: Modules file now takes -e option
+ for cvs export.
+
+Mon Sep 4 23:30:41 1995 Kirby Koster <koster@sctc.com>
+
+ * commit.c: When committing a removal, print a message saying what
+ we are doing.
+
+Wed Aug 2 10:06:51 1995 Vince DeMarco <vdemarco@bou.shl.com>
+
+ * server.c: fix compiler warnings (on NeXT) (declare functions as
+ static inline instead of just static) functions: get_buffer_date,
+ buf_append_char, and buf_append_data
+
+Mon Sep 4 22:31:28 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (update_entries), import.c (expand_at_signs): Check for
+ errors from fread and putc.
+
+Fri Sep 1 00:03:17 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * sanity.sh: Fix TODO item pathname.
+
+ * sanity.el: Removed. It was out of date, didn't do much, and I
+ doubt anyone was using it.
+
+ * no_diff.c (No_Difference): Don't change the modes of the files.
+
+Thu Aug 31 13:14:34 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * version.c: Change version to 1.5.1.
+
+ * client.c (start_rsh_server): Don't pass -d to "cvs server"
+ invocation via rsh (restore change which was lost when NT stuff
+ was merged in).
+ * sanity.sh: Add TODO item suggesting test for bug which this fixes.
+
+Wed Aug 30 12:36:37 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * sanity.sh (basic1): Make sure first-dir is deleted before
+ running this set of tests.
+
+ * subr.c: Extract file twiddling functions to a different file,
+ because we want to use different versions of many of these
+ routines under Windows NT.
+ (copy_file, isdir, islink, isfile, isreadable, iswritable,
+ open_file, make_directory, make_directories, xchmod,
+ rename_file, link_file, unlink_file, xcmp, tmpnam,
+ unlink_file_dir, deep_remove_dir): Moved to...
+ * filesubr.c: ...this file, which is new.
+ * Makefile.in (SOURCES): Mention filesubr.c.
+ (COMMON_OBJECTS): Mention filesubr.o.
+
+ * subr.c: Extract process execution guts to a different file,
+ because we want to replace these routines entirely under
+ Windows NT.
+ (VA_START, va_alist, va_dcl): Move this stuff...
+ (run_add_arg, run_init_prog): and these declarations...
+ (run_prog, run_argv, run_argc, run_argc_allocated): and these
+ variables...
+ (run_setup, run_arg, run_args, run_add_arg, run_init_prog,
+ run_exec, run_print, Popen): and these functions...
+ * run.c: To this file, which is new.
+ * Makefile.in (SOURCES): Mention run.c.
+ (COMMON_OBJECTS): Mention run.o.
+
+ * status.c (status): Call ign_setup, if client_active. Otherwise,
+ we don't end up ignoring CVS directories and such.
+
+ * server.c (mkdir_p, dirswitch): Use CVS_MKDIR instead of mkdir.
+
+ * repos.c (Name_Repository): Use the isabsolute function instead of
+ checking the first character of the path.
+ * root.c (Name_Root): Same.
+
+ * release.c (release): Use fncmp instead of strcmp to compare
+ filenames.
+
+ * rcs.c (RCS_parse, RCS_parsercsfile) [LINES_CRLF_TERMINATED]:
+ Abort, because we have strong reason to believe this code is
+ wrong.
+
+ * patch.c (patch): Register signal handlers iff the signal name is
+ #defined.
+
+ * no_diff.c (No_Difference): Don't try to include server_active in
+ trace message unless SERVER_SUPPORT is #defined.
+
+ * modules.c (do_module): Use CVS_MKDIR instead of mkdir.
+
+ * mkmodules.c (main): Call last_component instead of writing it out.
+
+ * main.c (main): Call last_component instead of writing it out.
+ Break up the long copyright string into several strings; Microsoft
+ Visual C++ can't handle a line that long. Feh.
+ Use fncmp instead of strcmp to compare filenames.
+ Register signal handlers iff the signal name is #defined.
+
+ * lock.c (readers_exist): Don't check return value of closedir.
+ Most of the rest of the code doesn't, and some systems don't
+ provide a return value anyway.
+ (set_lock): Use CVS_MKDIR instead of mkdir.
+
+ * import.c (import): Use the isabsolute function instead of
+ checking the first character of the path.
+ Try to delete the temporary file again after we close it, so it'll
+ get deleted on systems that don't let you delete files that are
+ open.
+ (add_rev): Instead of making a hard link to the working file and
+ checking in the revision with ci -r, use ci -u and restore the
+ permission bits.
+ (comtable): Include lines from SYSTEM_COMMENT_TABLE, if it is
+ #defined.
+ (add_rcs_file) [LINES_CRLF_TERMINATED]: Abort, because we have
+ strong reason to believe this code is wrong.
+ (import_descend_dir): Use CVS_MKDIR instead of mkdir.
+
+ * history.c (read_hrecs): Open the file with OPEN_BINARY.
+
+ * find_names.c (add_entries_proc, fsortcmp): Add prototypes.
+ * entries.c (write_ent_proc): Add prototype.
+ * hash.c (walklist): Add prototype for PROC argument.
+ (sortlist): Add prototype for COMP argument.
+ (printnode): Add a prototype, and make it static.
+
+ * cvs.h (wrap_add_file, wrap_add): Add extern decls for these;
+ they're used in import.c and update.c.
+ * wrapper.c (wrap_add_file, wrap_add): Remove them from here.
+
+ * cvs.h (RUN_NORMAL, RUN_COMBINED, RUN_REALLY, RUN_STDOUT_APPEND,
+ RUN_STDERR_APPEND, RUN_SIGNIGNORE, RUN_TTY, run_arg, run_print,
+ run_setup, run_args, run_exec, Popen, piped_child, close_on_exec,
+ filter_stream_through_program, waitpid): Move all these
+ declarations and definitions to the same section.
+
+ * cvs.h (error_set_cleanup): Fix prototype.
+
+ * cvs.h (isabsolute, last_component): New extern decls.
+
+ * cvs.h (link_file): Function is deleted; remove extern decl.
+
+ * cvs.h (DEATH_STATE, DEATH_SUPPORT): Move #definitions of these
+ above the point where we #include rcs.h, since rcs.h tests them
+ (or DEATH_SUPPORT, at least).
+
+ * cvs.h (DEVNULL): #define this iff it isn't already #defined.
+ config.h may want to override it.
+
+ * cvs.h (SERVER_SUPPORT, CLIENT_SUPPORT): Don't #define these
+ here; let config.h do that. On some systems, we don't have any
+ server support.
+
+ * cvs.h: Don't #include <io.h> or <direct.h>; we take care of
+ those in lib/system.h.
+
+ * commit.c (commit): Open logfile with the OPEN_BINARY flag.
+ (precommit_proc): Use the isabsolute function, instead of
+ comparing the first character with /.
+ (remove_file, checkaddfile): Use CVS_MKDIR instead of mkdir.
+
+ * client.c (send_repository): Use larger line buffers.
+
+ * client.c [LINES_CRLF_TERMINATED] (update_entries): If we've just
+ received a gzipped file, copy it over, converting LF to CRLF,
+ instead of just renaming it into place.
+ [LINES_CRLF_TERMINATED] (send_modified): Convert file to LF format
+ before sending with gzip.
+ (send_modified): Don't be disturbed if we get fewer than
+ sb.st_size characters when we read. The read function may be
+ collapsing CRLF to LF for us.
+
+ * client.c: Add forward declarations for all the cvs command
+ functions we call.
+
+ * client.c: Add forward static declarations for all the
+ handle_mumble functions.
+
+ On some systems, RSH converts LF to CRLF; this screws us up.
+ * client.c (rsh_pid): Declare this iff RSH_NOT_TRANSPARENT is not
+ #defined.
+ (get_responses_and_close): Use SHUTDOWN_SERVER if it is #defined.
+ Only wait for rsh process to exit if RSH_NOT_TRANSPARENT is not
+ #defined.
+ (start_rsh_server): Declare and define only if
+ RSH_NOT_TRANSPARENT is not #defined. Use piped_child, instead of
+ writing all that out.
+ (start_server): Only try to call start_rsh_server if
+ RSH_NOT_TRANSPARENT is not #defined. Use START_SERVER if it is
+ #defined. Convert file descriptors to stdio file pointers using
+ the FOPEN_BINARY_WRITE and FOPEN_BINARY_READ strings.
+
+ * client.h (rsh_pid): Don't declare this; it's never used elsewhere.
+ (supported_request): Add external declaration for this;
+ it's used in checkout.c.
+
+ Move process-running functions to run.c; we need to totally
+ replace these on other systems, like Windows NT.
+ * client.c (close_on_exec, filter_stream_through_program): Moved
+ to run.c.
+ * run.c (close_on_exec, filter_stream_through_program): Here they
+ are.
+
+ * add.c (add_directory): Use CVS_MKDIR instead of straight mkdir.
+ * checkout.c (checkout, build_dirs_and_chdir): Same.
+ (checkout_proc): Use fncmp instead of strcmp.
+ * client.c (call_in_directory): Use CVS_MKDIR instead of straight
+ mkdir.
+
+ * client.c (handle_checksum): Cast return value of strtol.
+
+Wed Aug 30 10:35:46 1995 Stefan Monnier <stefan.monnier@epfl.ch>
+
+ * main.c (main): Allow -d to override CVSROOT_ENV.
+
+Thu Aug 24 18:57:49 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.h, rcscmds.c (RCS_unlock, RCS_deltag, RCS_lock): Add extra
+ parameter for whether to direct stderr to DEVNULL.
+ * checkin.c, tag.c, rtag.c, import.c, commit.c: Pass extra
+ argument. 1 if stderr had been directed to DEVNULL before
+ rcscmds.c was in use, 0 if it was RUN_TTY.
+
+ * cvs.h: Add comment regarding attic.
+
+Tue Aug 22 10:09:29 1995 Alexander Dupuy <dupuy@smarts.com>
+
+ * rcs.c (whitespace): Cast to unsigned char in case char is signed
+ and value is negative.
+
+Tue Aug 22 10:09:29 1995 Kirby Koster <koster@sctc.com>
+ and Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * update.c (join_file): If vers->vn_user is NULL, just return.
+
+Tue Aug 22 10:09:29 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * server.c, client.c: Add comments about modes and umasks.
+
+Mon Aug 21 12:54:14 1995 Rick Sladkey <jrs@world.std.com>
+
+ * update.c (update_filesdone_proc): If pipeout, don't try to
+ create CVS/Root.
+
+Mon Aug 21 12:54:14 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c (start_rsh_server): Don't pass -d to "cvs server"
+ invocation via rsh.
+
+ * server.c (serve_root): Report errors via pending_error_text.
+ (serve_valid_requests): Check for pending errors.
+
+Sun Aug 20 00:59:46 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * options.h.in: Document usage of DIFF in update.c
+ * update.c: Use DIFF -c, not DIFF -u. The small improvement in
+ diff size is not worth the hassle in terms of everyone having to
+ make sure that DIFF is GNU diff (IMHO).
+
+Sat Aug 19 22:05:46 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * recurse.c (start_recursion): Doc fix.
+
+ * server.c (do_cvs_command): Clear error_use_protocol in the
+ child.
+ (server): Set error_use_protocol.
+
+Sun Aug 13 15:33:37 1995 Jim Kingdon <kingdon@harvey.cyclic.com>
+
+ * server.c (do_cvs_command): Don't select on exceptions.
+
+Fri Aug 4 00:13:47 1995 Jim Meyering (meyering@comco.com)
+
+ * Makefile.in (LDFLAGS): Set to @LDFLAGS@.
+ (options.h): Depend on ../config.status and options.h.in.
+ Add rule to build it from dependents.
+
+ * add.c: Include save-cwd.h.
+ (add_directory): Use save_cwd and restore_cwd instead of
+ explicit getwd then chdir.
+ * import.c (import_descend_dir): Likewise.
+ * modules.c (do_module): Likewise.
+
+ * recurse.c (save_cwd, restore_cwd, free_cwd): Remove functions.
+ New versions have been broken out into save-cwd.c.
+ (do_dir_proc): Adapt to handle status code returned by new versions
+ of save_cwd and restore_cwd -- and one fewer argument to restore_cwd.
+ (unroll_files_proc): Likewise.
+
+ * wrapper.c (wrap_name_has): Add default: abort () to switch
+ statement to avoid warning from gcc -Wall.
+ (wrap_matching_entry): Remove dcl of unused TEMP.
+ (wrap_tocvs_process_file): Remove dcl of unused ERR.
+ (wrap_fromcvs_process_file): Likewise.
+
+ * cvs.h: Remove prototype for error. Instead, include error.h.
+ Also, remove trailing white space.
+
+Thu Aug 3 10:12:20 1995 Jim Meyering (meyering@comco.com)
+
+ * import.c (import_descend_dir): Don't print probably-bogus CWD
+ in error messages saying `cannot get working directory'.
+
+Sun Jul 30 20:52:04 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * parseinfo.c (Parse_Info): Revise comments and indentation.
+
+Sun Jul 30 15:30:16 1995 Vince DeMarco <vdemarco@bou.shl.com>
+
+ * history.c: put ifdef SERVER_SUPPORT around tracing code incase
+ the client/server code is not compiled into the program.
+
+Sat Jul 29 16:59:49 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * subr.c (deep_remove_dir): Use struct dirent, not struct direct.
+
+Sat Jul 29 18:32:06 1995 Vince DeMarco <vdemarco@bou.shl.com>
+
+ * add.c: Check wrap_name_has.
+
+ * diff.c, checkin.c, import.c: have code call unlink_file_dir in
+ the appropriate places instead of just calling unlink_file.
+
+ * checkin.c: Remove one unlink call.
+
+ * import.c (comtable): Add .m .psw .pswm.
+
+ * import.c (add_rcs_file): Remove tocvsPath before returning.
+
+ * subr.c (unlink_file_dir): Add new function. unlinks the file if
+ it is a file. or will do a recursive delete if the path is
+ actually a directory.
+ (deep_remove_dir): New function, helps unlink_file_dir.
+
+ * mkmodules.c: Added CVSROOTADM_WRAPPER (cvswrappers file) to the
+ checkout file list.
+
+Fri Jul 28 16:27:56 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * checkout.c (safe_location): Use PATH_MAX not MAXPATHLEN.
+
+Fri Jul 28 19:37:03 1995 Paul Eggert <eggert@twinsun.com>
+
+ * log.c (cvslog, log_fileproc): Pass all options (except -l)
+ to rlog as-is, so that users can put spaces in options,
+ can specify multiple -d options, etc.
+ (ac, av): New variables.
+ (log_option_with_arg, options): Remove.
+
+ (log_fileproc): Don't prepend `/' to file name if update_dir is empty.
+
+Tue Jul 25 00:52:26 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * checkout.c (safe_location): Don't use PROTO in function definition.
+
+Mon Jul 24 18:32:06 1995 Vince DeMarco <vdemarco@bou.shl.com>
+
+ * checkout.c (safe_location): fix a compiler warning. (Declare
+ safe_location). Changed code in safe_location to call getwd
+ instead of getcwd. getwd is declared in the ../lib directory and
+ used exclusively thoughout the code. (this helps portability on
+ non POSIX systems).
+
+ * wrapper.c: updated Andrew Athan's email address.
+
+ * main.c: fix an ifdef so the code will compile. syntax error in
+ the ifdef for CVS_NOADMIN.
+
+Mon Jul 24 13:25:00 1995 Del <del@babel.dialix.oz.au>
+
+ * checkout.c: New procedure safe_location.
+ Ensures that you don't check out into the repository
+ itself.
+
+ * tag.c, rtag.c, cvs.h, mkmodules.c: Added a "taginfo" file in
+ CVSROOT to perform pre-tag checks.
+
+ * main.c, options.h.in: Added a compile time option to
+ disable the admin command.
+
+Fri Jul 21 17:07:42 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * update.c, status.c, patch.c, checkout.c, import.c, release.c,
+ rtag.c, tag.c: Now -q and -Q options just print an error message
+ telling you to use global -q and -Q options. The non-global
+ options were a mess because some commands accepted them and some
+ did not, and they were redundant with -q and -Q global options.
+
+ * rcs.c, cvs.h, commit.c, log.c, find_names.c: Remove CVS.dea
+ stuff. It is slower than the alternatives and I don't think
+ anyone ever actually used it.
+
+Fri Jul 21 10:35:10 1995 Vince DeMarco <vdemarco@bou.shl.com>
+
+ * Makefile.in (SOURCES, OBJECTS): Add wrapper.c, wrapper.o.
+ * add.c, admin.c, checkout.c, commit.c, diff.c, import.c, log.c,
+ remove.c, status.c: Call wrap_setup at start of commands.
+ * add.c (add): Check for wrapper, as well as directory, in repository.
+ * checkin.c: Add tocvsPath variable and associated handling.
+ * cvs.h: Add wrapper declarations.
+ * diff.c: Add tocvsPath variable and associated handling.
+ * import.c: Add -W option, CVSDOTWRAPPER handling.
+ (import_descend): check wrap_name_has.
+ (update_rcs_file, add_rev, add_rcs_file): add tocvsPath
+ variable and associated handling.
+ * no_diff.c: Add tocvsPath variable and associated handling.
+ * recurse.c (start_recursion): Check wrap_name_has.
+ * update.c: Copy, don't merge, copy-by-merge files. Attempt to
+ use -j on a copy-by-merge file generates a warning and no further
+ action.
+ * update.c: Add CVSDOTWRAPPER handling.
+ * wrapper.c: Added.
+
+Fri Jul 21 00:20:52 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * client.c: Revert David Lamkin patch, except for the bits about
+ removing temp_filename and the .rej file.
+ * sanity.sh (errmsg1): Test for the underlying bug which Lamkin
+ kludged around.
+ * client.c (call_in_directory): Set short_pathname to include the
+ filename, not just the directory. Improve comments regarding what
+ is passed to FUNC.
+
+Thu Jul 20 17:51:54 1995 David Lamkin <drl@net-tel.co.uk>
+
+ * client.c (short_pathname): Fixes the fetching of the whole file
+ after a patch to bring it up to date has failed:
+ - failed_patches[] now holds short path to file that failed
+ - patch temp files are unlinked where the patch is done
+
+Thu Jul 20 12:37:10 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * cvs.h: Declare error_set_cleanup
+ * main.c: Call it.
+ (error_cleanup): New function.
+
+Thu Jul 20 12:17:16 1995 Mark H. Wilkinson <mhw@minster.york.ac.uk>
+
+ * add.c, admin.c, checkin.c, checkout.c, classify.c, client.c,
+ client.h, commit.c, create_adm.c, cvs.h, diff.c, entries.c,
+ history.c, import.c, log.c, main.c, modules.c, no_diff.c, patch.c,
+ release.c, remove.c, repos.c, rtag.c, server.c, server.h,
+ status.c, subr.c, tag.c, update.c, vers_ts.c, version.c: Put
+ client code inside #ifdef CLIENT_SUPPORT, server code inside
+ #ifdef SERVER_SUPPORT. When reporting version, report whether
+ client and/or server are compiled in.
+
+Wed Jul 19 18:00:00 1995 Jim Blandy <jimb@cyclic.com>
+
+ * subr.c (copy_file): Declare local var n to be an int,
+ not a size_t. size_t is unsigned, and the return values
+ of read and write are definitely not unsigned.
+
+ * cvs.h [HAVE_IO_H]: #include <io.h>.
+ [HAVE_DIRECT_H]: #include <direct.h>.
+
+Fri Jul 14 22:28:46 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * server.c (dirswitch, serve_static_directory, serve_sticky,
+ serve_lost, server_write_entries, serve_checkin_prog,
+ serve_update_prog): Include more information in error messages.
+ (Thanks, DJM.)
+
+ * cvsbug.sh: Use /usr/sbin/sendmail, unless it doesn't
+ exist, in which case use /usr/lib/sendmail. (Thanks, DJM.)
+
+ * server.c (server, server_cleanup): Use "/tmp" instead of
+ "/usr/tmp" when the TMPDIR environment variable isn't set. This
+ is what the rest of the code uses.
+
+Thu Jul 13 11:03:17 1995 Jim Meyering (meyering@comco.com)
+
+ * recurse.c (free_cwd): New function.
+ (save_cwd, restore_cwd): Use it instead of simply freeing any
+ string. The function also closes any open file descriptor.
+
+ * import.c (comtable): Now static.
+ (comtable): Put braces around each element of initializer.
+
+ * cvs.h: Add prototype for xgetwd.
+ * recurse.c (save_cwd, restore_cwd): New functions to encapsulate
+ run-time solution to secure-SunOS vs. fchown problem.
+ (do_dir_proc, unroll_files_proc): Use new functions instead of
+ open-coded fchdir/chdir calls with cpp directives.
+
+ * sanity.sh: Change out of TESTDIR before removing it.
+ Some versions of rm fail when asked to delete the current directory.
+
+Wed Jul 12 22:35:04 1995 Jim Meyering (meyering@comco.com)
+
+ * client.c (get_short_pathname): Add const qualifier to parameter dcl.
+ (copy_a_file): Remove set-but-not-used variable, LEN.
+ (handle_clear_static_directory): Likewise: SHORT_PATHNAME.
+ (set_sticky): Likewise: LEN.
+ (handle_set_sticky): Likewise: SHORT_PATHNAME.
+ (handle_clear_sticky): Likewise: SHORT_PATHNAME.
+ (start_rsh_server): Convert perl-style `cond || stmt' to more
+ conventional C-style `if (cond) stmt.' Sheesh.
+ Remove dcl of unused file-static, SEND_CONTENTS.
+
+ * history.c: Remove dcls of set-but-not-used file-statics,
+ HISTSIZE, HISTDATA.
+ (read_hrecs): Don't set them.
+
+ * import.c (add_rev): Remove dcl of set-but-not-used local, RETCODE.
+
+ * repos.c (Name_Repository): Remove dcl of set-but-not-used local,
+ HAS_CVSADM.
+
+ * cvsrc.c (read_cvsrc): Parenthesize assignment used as truth value.
+
+Tue Jul 11 16:49:41 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * hash.h (struct entnode, Entnode): moved from here...
+ * cvs.h: to here.
+
+Wed Jul 12 19:45:24 1995 Dominik Westner (dominik@gowest.ppp.informatik.uni-muenchen.de)
+
+ * client.c (server_user): new var.
+ (parse_cvsroot): set above if repo is "user@host:/dir".
+ (start_rsh_server): if server_user set, then use it.
+
+Wed Jul 12 10:53:36 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * sanity.sh: remove the TESTDIR after done.
+
+ * cvsbug.sh (GNATS_ADDR): now bug-cvs@prep.ai.mit.edu again.
+
+Tue Jul 11 15:53:08 1995 Greg A. Woods <woods@most.weird.com>
+
+ * options.h.in: depend on configure for grep and diff, now that
+ changes to configure.in are applied.
+
+Tue Jul 11 14:32:14 1995 Michael Shields <shields@tembel.org>
+
+ * Makefile.in (LDFLAGS): Pick up from configure.
+
+Tue Jul 11 14:20:00 1995 Loren James Rittle <rittle@supra.comm.mot.com>
+
+ * import.c (add_rev), commit.c (remove_file, ci_new_rev),
+ checkin.c (Checkin), subr.c (make_message_rcslegal), cvs.h:
+ Always perform sanity check and fix-up on messages to be passed
+ directly to RCS via the '-m' switch. RCS 5.7 requires that a
+ non-total-whitespace, non-null message be provided or it will
+ abort with an error. CVS is not setup to handle any returned
+ error from 'ci' gracefully and, thus, the repository entered a
+ trashed state.
+
+ * sanity.sh: Add regression tests for new code and interactions
+ with RCS 5.7.
+
+Sun Jul 9 19:03:00 1995 Greg A. Woods <woods@most.weird.com>
+
+ * .cvsignore: added new backup file
+
+ * options.h.in: our new configure.in finds the right diff and
+ grep paths now....
+
+ * subr.c: quote the string in run_print() for visibility
+ - indent a comment
+ - Jun Hamano's xchmod() patch to prevent writable files
+ (from previous local changes)
+
+ * logmsg.c: fix a NULL pointer de-reference
+ - clean up some string handling code...
+ (from previous local changes)
+
+ * parseinfo.c: add hack to expand $CVSROOT in an *info file.
+ - document "ALL" and "DEFAULT" in opening comment for Parse_Info()
+ - fix the code to match the comments w.r.t. callbacks for "ALL"
+ - add a line of trace output...
+ (from previous local changes)
+
+ * mkmodules.c: add support for comments in CVSROOT/checkoutlist
+ - add CVSroot used by something other .o, ala main.c
+ (from previous local changes)
+
+ * main.c, cvs.h: add support for $VISUAL as log msg editor
+ (from previous local changes)
+
+ * status.c: add support for -q and -Q (from previous local changes)
+
+
+Sun Jul 9 18:44:32 1995 Karl Fogel <kfogel@floss.cyclic.com>
+
+ * log.c: trivial change to test ChangeLog stuff.
+
+Sat Jul 8 20:33:57 1995 Paul Eggert <eggert@twinsun.com>
+
+ * history.c: (history_write): Don't assume that fopen(..., "a")
+ lets one interleave writes to the history file from different processes
+ without interlocking. Use open's O_APPEND option instead.
+ Throw in an lseek to lessen the race bugs on non-Posix hosts.
+ * cvs.h, subr.c (Fopen): Remove.
+
+ * log.c (log_fileproc): Pass working file name to rlog, so that
+ the name is reported correctly.
+
+Fri Jul 7 18:29:37 1995 Michael Hohmuth <hohmuth@inf.tu-dresden.de>
+
+ * client.c, client.h (client_import_setup): New function.
+ (client_import_done, client_process_import_file): Add comments
+ regarding now-redundant code.
+ * import.c (import): Call client_import_setup.
+
+Tue Jul 4 09:21:26 1995 Bernd Leibing <bernd.leibing@rz.uni-ulm.de>
+
+ * rcs.c (RCS_parsercsfile_i): Rename error to l_error; SunOS4 /bin/cc
+ doesn't like a label and function with the same name.
+
+Sun Jul 2 12:51:33 1995 Fred Appelman <Fred.Appelman@cv.ruu.nl>
+
+ * logmsg.c: Rename strlist to str_list to avoid conflict with
+ Unixware 2.01.
+
+Thu Jun 29 17:37:22 1995 Paul Eggert <eggert@twinsun.com>
+
+ * rcs.c (RCS_check_kflag): Allow RCS 5.7's new -kb option.
+
+Wed Jun 28 09:53:14 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (HEADERS): Remove options.h.in.
+ (DISTFILES): Add options.h.in.
+ Depend on options.h in addition to HEADERS.
+
+Tue Jun 27 22:37:28 1995 Vince Demarco <vdemarco@bou.shl.com>
+
+ * subr.c: Don't try to do fancy waitstatus stuff for NeXT,
+ lib/wait.h is sufficient.
+
+Mon Jun 26 15:17:45 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * Makefile.in (DISTFILES): Remove RCS-patches and convert.sh.
+
+Fri Jun 23 13:38:28 1995 J.T. Conklin (jtc@rtl.cygnus.com)
+
+ * server.c (dirswitch, serve_co): Use CVSADM macro instead of
+ literal "CVS".
+
+Fri Jun 23 00:00:51 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * README-rm-add: Do not talk about patching RCS, that only
+ confuses people.
+ * RCS-patches, convert.sh: Removed (likewise).
+
+Thu Jun 22 10:41:41 1995 James Kingdon <kingdon@harvey.cyclic.com>
+
+ * subr.c: Change -1 to (size_t)-1 when comparing against a size_t.
+
+Wed Jun 21 16:51:54 1995 nk@ipgate.col.sw-ley.de (Norbert Kiesel)
+
+ * create_adm.c, entries.c, modules.c: Avoid coredumps if
+ timestamps, tags, etc., are NULL.
+
+Tue Jun 20 15:52:53 1995 Jim Meyering (meyering@comco.com)
+
+ * checkout.c (checkout): Remove dcl of unused variable.
+ * client.c (call_in_directory, handle_clear_static_directory,
+ handle_set_sticky, handle_clear_sticky, send_a_repository,
+ send_modified, send_dirent_proc): Remove dcls of unused variables.
+ * server.c (receive_file, serve_modified, server_cleanup):
+ Remove dcls of unused variables.
+ * subr.c (copy_file): Remove dcl of unused variable.
+ * vers_ts.c (time_stamp_server): Remove dcl of unused variable.
+
+Mon Jun 19 13:49:35 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * sanity.sh: Fix commencement message --- the test suite says
+ "Ok." when it's done.
+
+Fri Jun 16 11:23:44 1995 Jim Meyering (meyering@comco.com)
+
+ * entries.c (fgetentent): Parenthesize assignment in if-conditional.
+
+Thu Jun 15 17:33:28 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * server.c (get_buffer_data, buf_append_char, buf_append_data):
+ Don't conditionalize use of "inline". Autoconf takes care of
+ defining it away on systems that don't grok it.
+
+Thu Jun 15 13:43:38 1995 Jim Kingdon (kingdon@cyclic.com)
+
+ * options.h.in (DIFF): Default to "diff" not "diff -a" since diff
+ might not support the -a option.
+
+Wed Jun 14 11:29:42 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * import.c (import_descend): Initialize dirlist to NULL.
+
+ * subr.c (copy_file): Fix infinite loop.
+
+ * server.c (serve_directory): fix a memory leak.
+
+ * checkout.c, commit.c, diff.c, history.c, import.c, log.c,
+ patch.c, release.c, remove.c, rtag.c, status.c, tag.c, update.c:
+ Use send_arg() to send command line arguments to server.
+
+ * commit.c (fsortcmp), find_names (fsortcmp), hash.c (hashp,
+ findnode), hash.h (findnode), rcs.c (RCS_addnode,
+ RCS_check_kflag, RCS_check_tag, RCS_isdead, RCS_parse,
+ RCS_parsercsfile_i), rcs.h (RCS_addnode, RCS_check_kflag,
+ RCS_check_tag, RCS_parse): Added const qualifiers as
+ appropriate.
+ * rcs.h (RCS_isdead): Added prototype.
+
+ * hash.h (walklist, sortlist): correct function prototypes.
+
+ * ignore.c (ign_setup): don't bother checking to see if file
+ exists before calling ign_add_file.
+
+Fri Jun 9 11:24:06 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * all source files (rcsid): Added const qualifer.
+ * ignore.c (ign_default): Added const qualifier.
+ * subr.c (numdots): Added const qualifier to function argument.
+ * cvs.h (numdots): Added const qualifier to prototype argument.
+
+ * client.c (change_mode): Tied consecutive if statements testing
+ the same variable together with else if.
+
+ * import.c (import_descend): Build list of subdirectories when
+ reading directory, and then process the subdirectories in that
+ list. This change avoids I/O overhead of rereading directory
+ and reloading ignore list (.cvsignore) for each subdirectory.
+
+Thu Jun 8 11:54:24 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * import.c (import_descend): Use 4.4BSD d_type field if it is
+ present.
+
+ * lock.c (set_lockers_name): Use %lu in format and cast st_uid
+ field to unsigned long.
+
+ * import.c (import): Use RCS_check_kflag() to check -k options.
+ (keyword_usage, str2expmode, strn2expmode, expand_names):
+ Removed.
+ * rcs.c (RCS_check_kflag): Added keyword_usage array from import.c
+ for more descriptive error messages.
+
+ * subr.c (run_setup, run_args): Changed variable argument
+ processing to work on machines that use <varargs.h>.
+
+ * subr.c (copy_file, xcmp): Changed to read the file(s) by blocks
+ rather than by reading the whole file into a huge buffer. The
+ claim that this was reasonable because source files tend to be
+ small does not hold up in real world situations. CVS is used
+ to manage non-source files, and mallocs of 400K+ buffers (x2
+ for xcmp) can easily fail due to lack of available memory or
+ even memory pool fragmentation.
+ (block_read): New function, taken from GNU cmp and slightly
+ modified.
+
+ * subr.c (xcmp): Added const qualifier to function arguments.
+ * cvs.h (xcmp): Added const qualifer to prototype arguments.
+
+Wed Jun 7 11:28:31 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * cvs.h (Popen): Added prototype.
+ (Fopen, open_file, isreadable, iswritable, isdir, isfile,
+ islink, make_directory, make_directories, rename_file,
+ link_file, unlink_file, copy_file): Added const qualifer to
+ prototype arguments.
+ * subr.c (Fopen, Popen, open_file, isreadable, iswritable, isdir,
+ isfile, islink, make_directory, make_directories, rename_file,
+ link_file, unlink_file, copy_file): Added const qualifier to
+ function arguments.
+
+ * logmsg.c (logfile_write), recurse.c (do_recursion, addfile):
+ Don't cast void functions to a void expression. There is at
+ least one compiler (MPW) that balks at this.
+
+ * rcs.c (keysize, valsize): Change type to size_t.
+
+ * add.c (add_directory): Don't cast umask() argument to int.
+
+ * import.c (add_rcs_file): Changed type of mode to mode_t.
+
+ * rcscmds.c (RCS_merge): New function.
+ * cvs.h (RCS_merge): Declare.
+ * update.c (merge_file, join_file): Call RCS_merge instead of
+ invoking rcsmerge directly.
+
+ * cvs.h: Include <stdlib.h> if HAVE_STDC_HEADERS, otherwise
+ declared getenv().
+ * cvsrc.c, ignore.c, main.c: Removed getenv() declaration.
+
+ * client.c (mode_to_string): Changed to take mode_t instead of
+ struct statb argument. Simplified implementation, no longer
+ overallocates storage for returned mode string.
+ * client.h (mode_to_string): Updated declaration.
+ * server.c (server_updated): Updated for new calling conventions,
+ pass st_mode instead of pointer to struct statb.
+
+ * cvs.h (CONST): Removed definition, use of const qualifier is
+ determined by autoconf.
+ * history.c, modules.c, parseinfo.c: Use const instead of CONST.
+
+ * add.c, admin.c, checkout.c, commit.c, diff.c, import.c, log.c,
+ main.c, mkmodules.c, patch.c, recurse.c, remove.c, rtag.c,
+ server.c, status.c, subr.c, tag.c, update.c: Changed function
+ arguments "char *argv[]" to "char **argv" to silence lint
+ warnings about performing arithmetic on arrays.
+
+Tue Jun 6 18:57:21 1995 Jim Blandy <jimb@totoro.cyclic.com>
+
+ * version.c: Fix up version string, to say that this is Cyclic
+ CVS.
+
+Tue Jun 6 15:26:16 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * subr.c (run_setup, run_args, run_add_arg, xstrdup): Add const
+ qualifier to format argument.
+ * cvs.h (run_setup, run_args, xstrdup): Likewise.
+
+ * Makefile.in (SOURCES): Added rcscmds.c.
+ (OBJECTS): Added rcscmds.o.
+
+ * rcscmds.c: New file, with new functions RCS_settag, RCS_deltag,
+ RCS_setbranch, RCS_lock, RCS_unlock.
+ * checkin.c, commit.c, import.c, rtag.c, tag.c: Call above
+ functions instead of exec'ing rcs commands.
+ * cvs.h: Declare new functions.
+
+Mon May 29 21:40:54 1995 J.T. Conklin (jtc@rtl.cygnus.com)
+
+ * recurse.c (start_recursion, do_recursion): Set entries to NULL
+ after calling Entries_Close().
+
+Sat May 27 08:08:18 1995 Jim Meyering (meyering@comco.com)
+
+ * Makefile.in (check): Export RCSBIN only if there exists an
+ `rcs' executable in ../../rcs/src. Before, tests would fail when
+ the directory existed but contained no executables.
+ (distclean): Remove options.h, now that it's generated.
+ (Makefile): Regenerate only *this* file when Makefile.in is
+ out of date. Depend on ../config.status.
+
+Fri May 26 14:34:28 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * entries.c (Entries_Open): Added missing fclose().
+ (Entries_Close): Don't write Entries unless Entries.Log exists.
+
+ * entries.c (Entries_Open): Renamed from ParseEntries; changed to
+ process Entries Log files left over from previous crashes or
+ aborted runs.
+ (Entries_Close): New function, write out Entries file if
+ neccessary and erase Log file.
+ (Register): Append changed records to Log file instead of
+ re-writing file.
+ (fgetentent): New function, parse one Entry record from a file.
+ (AddEntryNode): It's no longer an error for two records with the
+ same name to be added to the list. New records replace older
+ ones.
+ * cvs.h (Entries_Open, Entries_Close): Add prototypes.
+ (CVSADM_ENTLOG): New constant, name of Entries Log file.
+ * add.c, checkout.c, client.c, find_names.c, recurse.c: Use
+ Entries_Open()/Entries_Close() instead of ParseEntries()/dellist().
+
+ * add.c, admin.c, checkout.c, client.c, commit.c, diff.c,
+ history.c, import.c, log.c, patch.c, release.c, remove.c,
+ rtag.c, server.c, status.c, tag.c, update.c: Changed
+ conditionals so that return value of *printf is tested less than
+ 0 instead of equal to EOF.
+
+Thu May 25 08:30:12 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * subr.c (xmalloc): Never try to malloc zero bytes; if the user
+ asks for zero bytes, malloc one instead.
+
+Wed May 24 12:44:25 1995 Ken Raeburn <raeburn@cujo.cygnus.com>
+
+ * subr.c (xmalloc): Don't complain about NULL if zero bytes were
+ requested.
+
+Tue May 16 21:49:05 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * subr.c (xmalloc): Never try to malloc zero bytes; if the user
+ asks for zero bytes, malloc one instead.
+
+Mon May 15 14:35:11 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * lock.c (L_LOCK_OWNED): Removed.
+
+ * add.c, checkout.c, client.c, create_adm.c, cvs.h, entries.c,
+ find_names.c modules.c, recurse.c, release.c, repos.c, update.c:
+ removed CVS 1.2 compatibility/upgrade code.
+
+Mon May 8 11:25:07 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * lock.c (write_lock): Missed one instance where rmdir(tmp) should
+ have been changed to clear_lock().
+
+Wed May 3 11:08:32 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * create_adm.c, entries.c, import.c, root.c: Changed conditionals
+ so that return value of *printf is tested less than 0 instead of
+ equal to EOF --- That's all Standard C requires.
+
+Wed May 3 18:03:37 1995 Samuel Tardieu <tardieu@emma.enst.fr>
+
+ * rcs.h: removed #ifdef CVS_PRIVATE and #endif because cvs didn't
+ compile anymore.
+
+Mon May 1 13:58:53 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * rcs.c, rcs.h: Implemented lazy parsing of rcs files.
+ RCS_parsercsfile_i modified to read only the first two records
+ of rcs files, a new function RCS_reparsercsfile is called only
+ when additional information (tags, revision numbers, dates,
+ etc.) is required.
+
+Mon May 1 12:20:02 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * Makefile.in (INCLUDES): Include -I. for options.h.
+
+Fri Apr 28 16:16:33 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * Makefile.in (SOURCES, HEADERS, DISTFILES): Updated.
+ (dist-dir): Renamed from dist; changed to work with DISTDIR
+ variable passed from parent.
+
+ We don't want to include a file the user has to edit in the
+ distribution.
+ * options.h: No longer distributed.
+ * options.h.in: Distribute this instead.
+ * ../INSTALL, ../README: Installation instructions updated.
+
+ * client.c (start_rsh_server): Send the remote command to rsh as a
+ single string.
+
+Fri Apr 28 00:29:49 1995 Noel Cragg <noel@vo.com>
+
+ * commit.c: Added initializer for FORCE_CI
+
+ * sanity.sh: Fix tests added 25 Apr -- they were expecting the
+ server to make noise, but the CVS_SERVER variable had been
+ accidentally set with the `-Q' flag. Ran all tests -- both
+ locally and remotely -- to verify that the change didn't break
+ anything.
+
+Thu Apr 27 12:41:52 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * Makefile.in: Revise comment regarding check vs. remotecheck.
+
+Thu Apr 27 12:52:28 1995 Bryan O'Sullivan <bos@cyclic.com>
+
+ * client.c (start_rsh_server): If the CVS_RSH environment variable
+ is set, use its contents as the name of the program to invoke
+ instead of `rsh'.
+
+Thu Apr 27 12:18:38 1995 Noel Cragg <noel@vo.com>
+
+ * checkout.c (checkout): To fix new bug created by Apr 23 change,
+ re-enabled "expand-module" functionality, because it has the side
+ effect of setting the checkin/update programs for a directory. To
+ solve the local/remote checkout problem that prompted this change
+ in the first place, I performed the next change.
+ * server.c (expand_proc): Now returns expansions for aliases only.
+
+Wed Apr 26 12:07:42 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * rcs.c (getrcskey): Rewritten to process runs of whitespace chars
+ and rcs @ strings instead of using state variables "white" and
+ "funky".
+
+Fri Apr 7 15:49:25 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * lock.c (unlock): Only call stat if we need to.
+
+Wed Apr 26 10:48:44 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (new_entries_line): Don't prototype.
+
+Tue Apr 25 22:19:16 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * sanity.sh: Add new tests to catch bugs in Apr 23 change.
+
+Tue Apr 25 17:10:55 1995 Roland McGrath <roland@baalperazim.frob.com>
+
+ * create_adm.c (Create_Admin): Use getwd instead of getcwd.
+
+Sun Apr 23 20:58:32 1995 Noel Cragg <noel@vo.com>
+
+ * checkout.c (checkout): Disabled "expand-module" functionality on
+ remote checkout, since it makes modules behave like aliases (see
+ longer note there). This change necessitated the change below.
+ Also merged the like parts of a conditional.
+
+ * client.c (call_in_directory): Changed the algorithm that created
+ nested and directories and the "CVS" administration directories
+ therein. The algoithm wrongly assumed that the name of the
+ directory that that was to be created and the repository name were
+ the same, which breaks modules.
+
+ * create_adm.c (Create_Admin), module.c (do_module), server.c
+ (server_register), subr.c, entries.c: Added fprintfs for trace-mode
+ debugging.
+
+ * client.c (client_send_expansions): Argument to function didn't
+ have a type -- added one.
+
+ * server.c (new_entries_line): Arguments to this function are
+ never used -- reoved them and fixed callers.
+
+Sat Apr 22 11:17:20 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * rcs.c (RCS_parse): If we can't open the file, give an error
+ message (except for ENOENT in case callers rely on that).
+
+Wed Apr 19 08:52:37 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (send_repository): Check for CVSADM_ENTSTAT in `dir', not
+ in `.'.
+
+ * sanity.sh: Add TODO list. Revise some comments. Add tests of
+ one working directory adding a file and other updating it.
+
+Sat Apr 8 14:52:55 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * Makefile.in (CFLAGS): Let configure set the default for CFLAGS.
+ Under GCC, we want -g -O.
+
+Fri Apr 7 15:49:25 1995 J.T. Conklin <jtc@rtl.cygnus.com>
+
+ * root.c (Name_Root): merge identical adjacent conditionals.
+
+ * create_admin.c (Create_Admin): Rearranged check for CVSADM and
+ OCVSADM directories so that CVSADM pathname is only built once.
+
+ * update.c (update_dirleave_proc): Removed code to remove CVS
+ administration directory if command_name == "export" and to
+ create CVS/Root file if it is not present. Identical code
+ in update_filesdone_proc() will perform these same actions.
+ Also removed code that read and verfied CVS/Root. This is
+ expensive, and if it is necessary should happen in the
+ general recursion processor rather than in the update
+ callbacks.
+
+ * lock.c (masterlock): New variable, pathname of master lockdir.
+ (set_lock): removed lockdir argument, now constructs it itself
+ and stores it in masterlock.
+ (clear_lock): new function, removes master lockdir.
+ (Reader_Lock, write_lock): call clear_lock instead of removing
+ master lockdir.
+ (Reader_Lock, write_lock): #ifdef'd out CVSTFL code.
+
+ * main.c (main): register Lock_Cleanup signal handler.
+ * lock.c (Reader_Lock, write_lock): no longer register
+ Lock_Cleanup.
+
+ * main.c (main): initialize new array hostname.
+ * lock.c (Reader_Lock, write_lock): Use global hostname array.
+ * logmsg.c (logfile_write): Likewise.
+
+ * recurse.c (do_dir_proc, unroll_files_proc): Use open()/fchdir()
+ instead of getwd()/chdir() on systems that support the fchdir()
+ system call.
+
+Fri Apr 7 06:57:20 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c: Include the word "server" in error message for memory
+ exhausted, so the user knows which machine ran out of memory.
+
+ * sanity.sh: For remote, set CVS_SERVER to test the right server,
+ rather than a random one from the PATH.
+
+ * commit.c [DEATH_STATE]: Pass -f to `ci'.
+
+Thu Apr 6 13:05:15 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * commit.c (checkaddfile): If we didn't manage to fopen the file,
+ don't try to fclose it.
+
+ * client.c (handle_m, handle_e): Use fwrite, rather than a loop of
+ putc's. Sometimes these streams are unbuffered.
+
+Tue Apr 4 11:33:56 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * (DISTFILES): Include cvsbug.sh, ChangeLog, NOTES, RCS-patches,
+ README-rm-add, ChangeLog.fsf, sanity.sh, sanity.el, and
+ .cvsignore.
+
+Mon Mar 27 08:58:42 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * rcs.c (RCS_parsercsfile_i): Accept `dead' state regardless of
+ DEATH_STATE define. Revise comments regarding DEATH_STATE versus
+ CVSDEA versus the scheme which uses a patched RCS.
+ * README-rm-add, RCS-patches: Explain what versions of CVS need
+ RCS patches.
+
+Sat Mar 25 18:51:39 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * server.c (server_cleanup): Only do the abysmal kludge of waiting
+ for command and draining the pipe #ifdef sun. The code makes
+ assumptions not valid on all systems, and is only there to
+ workaround a SunOS bug.
+
+Wed Mar 22 21:55:56 1995 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (mkdir_p): Call stat only if we get the EACCES. Faster
+ and more elegant.
+
+Tue Jan 31 20:59:19 1995 Ken Raeburn <raeburn@cujo.cygnus.com>
+
+ * server.c: Try to avoid starting the "rm -rf" at cleanup time
+ until after subprocesses have finished.
+ (command_fds_to_drain, max_command_fd): New variables.
+ (do_cvs_command): Set them.
+ (command_pid_is_dead): New variable.
+ (wait_sig): New function.
+ (server_cleanup): If command_pid is nonzero, wait for it to die,
+ draining output from it in the meantime. If nonzero SIG was
+ passed, send a signal to the subprocess, to encourage it to die
+ soon.
+
+ * main.c (usage): Argument is now `const char *const *'.
+ * cvs.h (usage): Changed prototype.
+ (USE): Make new variable `const'.
+ * add.c (add_usage), admin.c (admin_usage), checkout.c
+ (checkout_usage, export_usage, checkout), commit.c (commit_usage),
+ diff.c (diff_usage), history.c (history_usg), import.c
+ (import_usage, keyword_usage), log.c (log_usage), main.c (usg),
+ patch.c (patch_usage), release.c (release_usage), remove.c
+ (remove_usage), rtag.c (rtag_usage), server.c (server), status.c
+ (status_usage), tag.c (tag_usage), update.c (update_usage): Usage
+ messages are now const arrays of pointers to const char.
+
+ * import.c (comtable): Now const.
+ * main.c (rcsid): Now static.
+ (cmd): Now const.
+ (main): Local variable CM now points to const.
+ * server.c (outbuf_memory_error): Local var MSG now const.
+
+ * client.c (client_commit_usage): Deleted.
+
+Sat Dec 31 15:51:55 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * logmsg.c (do_editor): Allocate enough space for trailing '\0'.
+
+Fri Mar 3 11:59:49 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * cvsbug.sh: Call it "Cyclic CVS" now, not "Remote CVS". Call it
+ version C1.4A, not 1.4A2-remote. Send bugs to cyclic-cvs, not
+ remote-cvs.
+
+ * classify.c (Classify_File): Put check for dead file inside
+ "#ifdef DEATH_SUPPORT".
+
+Thu Feb 23 23:03:43 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * update.c (join_file): Don't pass the -E option to rcsmerge here,
+ either (see Jan 22 change).
+
+Mon Feb 13 13:28:46 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * cvsbug.sh: Send bug reports to remote-cvs@cyclic.com, rather
+ than to the ordinary CVS bug address. This does mean we'll have
+ to wade through GNATS-style bug reports, sigh.
+
+Wed Feb 8 06:42:27 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * server.c: Don't include <sys/stat.h>; system.h already does, and
+ 4.3BSD can't take it twice.
+
+ * subr.c [! HAVE_VPRINTF] (run_setup, run_args): Don't use va_dcl
+ in declaration. Declare the a1..a8 args which are used in the
+ sprintf call.
+ * cvs.h [! HAVE_VPRINTF] (run_setup, run_args): Don't prototype
+ args, to avoid conflicting with the function definitions
+ themselves.
+
+Tue Feb 7 20:10:00 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * client.c (update_entries): Pass the patch subprocess the switch
+ "-b ~", not "-b~"; the latter form seems not to work with patch
+ version 2.0 and earlier --- it takes the next argv element as the
+ backup suffix, and thus doesn't notice that the patch file's name
+ has been specified, thus doesn't find the patch, thus... *aargh*
+
+Fri Feb 3 20:28:21 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * log.c (log_option_with_arg): New function.
+ (cvslog): Use it and send_arg to handle the rlog options that take
+ arguments. The code used to use send_option_string for
+ everything, which assumes that "-d1995/01/02" is equivalent to
+ "-d -1 -9 -9 -5 ...".
+
+Tue Jan 31 15:02:01 1995 Jim Blandy <jimb@floss.life.uiuc.edu>
+
+ * server.c: #include <sys/stat.h> for the new stat call in mkdir_p.
+ (mkdir_p): Don't try to create the intermediate directory if it
+ exists already. Some systems return EEXIST, but others return
+ EACCES, which we can't otherwise distinguish from a real access
+ problem.
+
+Sun Jan 22 15:25:45 1995 Jim Blandy <jimb@totoro.bio.indiana.edu>
+
+ * update.c (merge_file): My rcsmerge doesn't accept a -E option,
+ and it doesn't look too important, so don't pass it.
+
+Fri Jan 20 14:24:58 1995 Ian Lance Taylor <ian@sanguine.cygnus.com>
+
+ * client.c (do_deferred_progs): Don't try to chdir to toplevel_wd
+ if it has not been set.
+ (process_prune_candidates): Likewise.
+
+Mon Nov 28 09:59:14 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (client_commit): Move guts of function from here...
+ * commit.c (commit): ...to here.
+
+Mon Nov 28 15:14:36 1994 Ken Raeburn <raeburn@cujo.cygnus.com>
+
+ * server.c (buf_input_data, buf_send_output): Start cpp directives
+ in column 1, otherwise Sun 4 pcc complains.
+
+Mon Nov 28 09:59:14 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (add_prune_candidate): Don't try to prune ".".
+
+Tue Nov 22 05:27:10 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c, client.c: More formatting cleanups.
+
+ * client.h, client.c: New variable client_prune_dirs.
+ * update.c (update), checkout.c (checkout): Set it.
+ * client.c (add_prune_candidate, process_prune_candidates): New
+ functions.
+ (send_repository, call_in_directory, get_responses_and_close):
+ Call them.
+
+Wed Nov 23 01:17:32 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * server.c (do_cvs_command): Don't select on STDOUT_FILENO unless
+ we have something to write.
+
+Tue Nov 22 05:27:10 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * remove.c (remove_fileproc): Only call server_checked_in if we
+ actually are changing the entries file.
+
+ * server.c (server_write_entries): New function.
+ (dirswitch, do_cvs_command): Call it.
+ (serve_entry, serve_updated): Just update in-memory data
+ structures, don't mess with CVS/Entries file.
+
+Mon Nov 21 10:15:11 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (server_checked_in): Set scratched_file to NULL after
+ using it.
+
+ * checkin.c (Checkin): If the file was changed by the checkin,
+ call server_updated not server_checked_in.
+
+Sun Nov 20 08:01:51 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (send_repository): Move check for update_dir NULL to
+ before where we check last_update_dir. Check for "" here too.
+
+ * client.c (send_repository): Use new argument dir.
+
+ * client.c: Pass new argument dir to send_repository and
+ send_a_repository.
+
+ * server.c, server.h (server_prog): New function.
+ * modules.c (do_modules): Call it if server_expanding.
+ * client.c: Support Set-checkin-prog and Set-update-prog responses.
+ * server.c, client.c: Add Checkin-prog and Update-prog requests.
+
+Fri Nov 18 14:04:38 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (get_short_pathname, is_cvsroot_level,
+ call_in_directory): Base whether this is new-style or
+ old-style based on whether we actually used the Directory request,
+ not based on whether the pathname is absolute. Rename
+ directory_supported to use_directory.
+ * server.c: Rename use_relative_pathnames to use_dir_and_repos.
+ * client.c (send_a_repository): If update_dir is absolute, don't
+ use it to try to reconstruct how far we have recursed.
+
+ * server.c, server.h, client.c, client.h, vers_ts.c, update.h:
+ More cosmetic changes (identation, PARAMS vs. PROTO, eliminate
+ alloca, etc.) to remote CVS to make it more like the rest of CVS.
+
+ * server.c: Make server_temp_dir just the dir name, not the name
+ with "%s" at the end.
+ * server.c, client.c: Add "Max-dotdot" request, and use it to make
+ extra directories in server_temp_dir if needed.
+
+Thu Nov 17 09:03:28 1994 Jim Kingdon <kingdon@cygnus.com>
+
+ * client.c: Fix two cases where NULL was used and 0 was meant.
+
+Mon Nov 14 08:48:41 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (serve_unchanged): Set noexec to 0 when calling Register.
+
+ * update.c (merge_file): Don't call xcmp if noexec.
+
+Fri Nov 11 13:58:22 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (call_in_directory): Deal with it if reposdirname is
+ not a subdirectory of toplevel_repos.
+
+Mon Nov 7 09:12:01 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * patch.c: If file is removed and we don't have a tag or date,
+ just print "current release".
+
+ * classify.c (Classify_File): Treat dead files appropriately.
+
+Fri Nov 4 07:33:03 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * main.c (main) [SERVER_SUPPORT]: Move call to getwd past where we
+ know whether we are the server or not. Set CurDir to "<remote>"
+ if we are the server.
+
+ * client.c: Remove #if 0'd function option_with_arg.
+ Remove #if 0'd code pertaining to the old way of logging the
+ session.
+
+ * client.c (start_rsh_server): Don't invoke the server with the
+ -d option.
+ * server.c (serve_root): Test root for validity, just like main.c
+ does for non-remote CVS.
+ * main.c (main): If `cvs server' happens with a colon in the
+ CVSroot, just handle it normally; don't make it an error.
+
+Wed Nov 2 11:09:38 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (send_dirent_proc): If dir does not exist, just return
+ R_SKIP_ALL.
+
+ * server.c, client.c: Add Directory request and support for
+ local relative pathnames (along with the repository absolute
+ pathnames).
+ * update.c, add.c, checkout.c, checkin.c, cvs.h, create_adm.c,
+ commit.c, modules.c, server.c, server.h, remove.c, client.h:
+ Pass update_dir to server_* functions. Include update_dir in
+ more error messages.
+
+Fri Oct 28 08:54:00 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c: Reformat to bring closer to cvs standards for brace
+ position, comment formatting, etc.
+
+ * sanity.sh: Remove wrong "last mod" line. Convert more tests to
+ put PASS or FAIL in log file. Change it so arguments to the
+ script specify which tests to run.
+
+ * client.c, client.h, server.c, checkout.c: Expand modules in
+ separate step from the checkout itself.
+
+Sat Oct 22 20:33:35 1994 Ken Raeburn (raeburn@kr-pc.cygnus.com)
+
+ * update.c (join_file): When checking for null return from
+ RCS_getversion, still do return even if quiet flag is set.
+
+Thu Oct 13 07:36:11 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (send_files): Call send_repository even if
+ toplevel_repos was NULL.
+
+ * server.c (server_updated): If joining, don't remove file.
+
+ * update.c (join_file): If server and file is unmodified, check it
+ out before joining. After joining, call server_updated. New
+ argument repository.
+
+ * server.c, server.h (server_copy_file): New function.
+ * update.c (update_file_proc, join_file): Call it.
+ * client.c (copy_file, handle_copy_file): New functions.
+ * client.c (responses): Add "Copy-file".
+
+ * client.c, client.h: Make toplevel_wd, failed_patches and
+ failed_patches_count extern.
+ * client.c (client_update): Move guts of function from here...
+ * update.c (update): ...to here.
+
+ * client.c, checkout.c: Likewise for checkout.
+
+ * client.c (is_cvsroot_level): New function.
+ (handle_set_sticky, handle_clear_sticky,
+ handle_clear_static_directory): Call it, instead of checking
+ short_pathname for a slash.
+
+ * client.c, client.h (client_process_import_file,
+ client_import_done): New functions.
+ * import.c (import, import_descend): Use them.
+ * import.c (import_descend): If server, don't mention ignored CVS
+ directories.
+ * import.c (import_descend_dir): If client, don't print warm
+ fuzzies, or make directories in repository. If server, print warm
+ fuzzies to stdout not stderr.
+ * client.c (send_modified): New function, broken out from
+ send_fileproc.
+ (send_fileproc): Call it.
+
+ * client.c (handle_clear_sticky, handle_set_sticky,
+ handle_clear_static_directory, handle_set_static_directory): If
+ command is export, just return.
+ (call_in_directory, update_entries): If command is export, don't
+ create CVS directories, CVS/Entries files, etc.
+ * update.c (update_filesdone_proc): Don't remove CVS directories if
+ client_active.
+
+ * client.c (send_a_repository): Instead of insisting that
+ repository end with update_dir, just strip as many pathname
+ components from the end as there are in update_dir.
+
+ * Makefile.in (remotecheck): New target, pass -r to sanity.sh.
+ * sanity.sh: Accept -r argument which means to test remote cvs.
+
+ * tag.c (tag), rtag.c (rtag), patch.c (patch), import.c (import),
+ admin.c (admin), release.c (release): If client_active, connect to
+ the server and send the right requests.
+ * main.c (cmds): Add these commands.
+ (main): Remove code which would strip hostname off cvsroot and try
+ the command locally. There are no longer any commands which are
+ not supported.
+ * client.c, client.h (client_rdiff, client_tag, client_rtag,
+ client_import, client_admin, client_export, client_history,
+ client_release): New functions.
+ * server.c (serve_rdiff, serve_tag, serve_rtag, serve_import,
+ serve_admin, serve_export, serve_history, serve_release): New
+ functions.
+ (requests): List them.
+ * server.c: Declare cvs commands (add, admin, etc.).
+ * cvs.h, server.h: Don't declare any of them here.
+ * main.c: Restore declarations of cvs commands which were
+ previously removed.
+
+ * cvs.h: New define DEATH_STATE, commented out for now.
+ * rcs.c (RCS_parsercsfile_i), commit.c (remove_file, checkaddfile)
+ [DEATH_STATE]: Use RCS state to record a dead file.
+
+Mon Oct 3 09:44:54 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * status.c (status_fileproc): Now that ts_rcs is just one time,
+ don't try to print the second time from it. (Same as raeburn 20
+ Aug change, it accidentally got lost in 1.4 Alpha-1 merge).
+
+ * cvs.h (CVSDEA): Added (but commented out for now).
+ * rcs.c (RCS_parsercsfile_i) [CVSDEA]: Also look in CVSDEA to see if
+ something is dead.
+ * commit.c (ci_new_rev, mark_file) [CVSDEA]: New functions.
+ (remove_file, checkaddfile) [CVSDEA]: Use them instead of ci -K.
+ * find_names.c (find_dirs) [CVSDEA]: Don't match CVSDEA directories.
+ * update.c (checkout_file): Check RCS_isdead rather than relying
+ on co to not create the file.
+
+ * sanity.sh: Direct output to logfile, not /dev/null.
+
+ * subr.c (run_exec): Print error message if we are unable to exec.
+
+ * commit.c (remove_file): Call Scratch_Entry when removing tag
+ from file. The DEATH_SUPPORT ifdef was erroneous.
+
+Sun Oct 2 20:33:27 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * commit.c (checkaddfile): Instead of calling isdir before
+ attempting to create the directory, just ignore EEXIST errors from
+ mkdir. (This removes some DEATH_SUPPORT ifdefs which actually had
+ nothing to do with death support).
+
+Thu Sep 29 09:23:57 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * diff.c (diff): Search attic too if we have a second tag/date.
+ (diff_fileproc): If we have a second tag/date, don't do all the
+ checking regarding the user file.
+
+Mon Sep 26 12:02:15 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * checkin.c (Checkin): Check for error from unlink_file.
+
+Mon Sep 26 08:51:10 1994 Anthony J. Lill (ajlill@ajlc.waterloo.on.ca)
+
+ * rcs.c (getrcskey): Allocate space for terminating '\0' if
+ necessary.
+
+Sat Sep 24 09:07:37 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * commit.c (commit_fileproc): Set got_message = 1 when calling
+ do_editor (accidentally omitted from last change).
+
+Fri Sep 23 11:59:25 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ Revert buggy parts of Rich's change of 1 Nov 1993 (keeping the
+ dynamic buffer allocation, which was the point of that change).
+ * logmsg.c (do_editor): Reinstate message arg, but make it char
+ **messagep instead of char *message. Change occurances of message
+ to *messagep. Char return type from char * back to void.
+ * cvs.h: Change do_editor declaration.
+ * commit.c: Reinstate got_message variable
+ (commit_filesdoneproc, commit_fileproc, commit_direntproc): Use it.
+ * import.c (import), commit.c (commit_fileproc,
+ commit_direntproc): Pass &message to do_editor; don't expect it to
+ return a value.
+ * client.c (client_commit): Likewise.
+ * import.c (import): Deal with it if message is NULL.
+
+Wed Sep 21 09:43:25 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (server_updated): If the file doesn't exist, skip it.
+
+ * diff.c, client.h, client.c: Rename diff_client_senddate to
+ client_senddate and move from diff.c to client.c.
+ * client.c (client_update, client_checkout): Use it.
+
+Sat Sep 17 08:36:58 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * checkout.c (checkout_proc): Don't pass NULL to Register for
+ version. (should fix "cvs co -r <nonexistent-tag> <file>"
+ coredump on Solaris).
+
+Fri Sep 16 08:38:02 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * diff.c (diff_fileproc): Set top_rev from vn_user, not vn_rcs.
+ Rename it to user_file_rev because it need not be the head of any
+ branch.
+ (diff_file_nodiff): After checking user_file_rev, if we have both
+ use_rev1 and use_rev2, compare them instead of going on to code
+ which assumes use_rev2 == NULL.
+
+Thu Sep 15 08:20:23 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * status.c (status): Return a value in client_active case.
+
+Thu Sep 15 15:02:12 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * server.c (serve_modified): Create the file even if the size is
+ zero.
+
+Thu Sep 15 08:20:23 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * lock.c (readers_exist): Clear errno each time around the loop,
+ not just the first time.
+
+ * client.c (start_server): Don't send Global_option -q twice.
+
+ * no_diff.c (No_Difference): Check for error from unlink.
+
+ * no_diff.c, cvs.h (No_Difference): New args repository,
+ update_dir. Call server_update_entries if needed. Use update_dir
+ in error message.
+ * classify.c (Classify_File): Pass new args to No_Difference.
+
+ * server.c (server_update_entries, server_checked_in,
+ server_updated): Don't do anything if noexec.
+
+ * client.c (send_fileproc): Rather than guessing how big the gzip
+ output may be, just realloc the buffer as needed.
+
+Tue Sep 13 13:22:03 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * lock.c: Check for errors from unlink, readdir, and closedir.
+
+ * classify.c (Classify_File): Pass repository and update_dir to
+ sticky_ck.
+ (sticky_ck): New args repository and update_dir.
+ * server.c, server.h (server_update_entries): New function.
+ * classify.c (sticky_ck): Call it.
+ * client.c: New response "New-entry".
+ * client.c (send_fileproc): Send tag/date from vers->entdata, not
+ from vers itself.
+
+Mon Sep 12 07:07:05 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c: Clean up formatting ("= (errno)" -> "= errno").
+
+ * cvs.h: Declare strerror.
+
+ * client.c: Add code to deal with Set-sticky and Clear-sticky
+ responses, and Sticky request.
+ * server.c: Add code to deal with Sticky request.
+ * server.c, server.h (server_set_sticky): New function.
+ * create_adm.c (Create_Admin), update.c (update, update_dirent_proc),
+ commit.c (commit_dirleaveproc): Call it.
+ * client.c, client.h (send_files): Add parameter aflag.
+ * add.c (add), diff.c (diff), log.c (cvslog), remove.c (cvsremove),
+ status.c (status),
+ client.c (client_commit, client_update, client_checkout): Pass it.
+ * client.c (client_update): Add -A flag.
+
+Fri Sep 9 07:05:35 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * entries.c (WriteTag): Check for error from unlink_file.
+
+ * server.c (server_updated): Initialize size to 0. Previously if
+ the file was zero length, the variable size got used without being
+ set.
+
+Thu Sep 8 14:23:05 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (serve_repository): Check for error from fopen on
+ CVSADM_ENT.
+
+ * update.c (update, update_dirent_proc): Check for errors when
+ removing Entries.Static.
+
+ * client.c: Add code to deal with Set-static-directory and
+ Clear-static-directory responses, and Static-directory request.
+ * server.c, server.h (server_clear_entstat, server_set_entstat):
+ New functions.
+ * update.c, checkout.c, modules.c: Call them.
+ * server.c: Add code to deal with Static-directory request.
+
+ * server.c, client.c: Use strchr and strrchr instead of index and
+ rindex.
+
+ * server.c (serve_unchanged, serve_lost): Change comments which
+ referred to changing timestamp; we don't always change the
+ timestamp in those cases anymore.
+
+Wed Sep 7 10:58:12 1994 J.T. Conklin (jtc@rtl.cygnus.com)
+
+ * cvsrc.c (read_cvsrc): Don't call getenv() three times when one
+ time will do.
+
+ * subr.c (xmalloc, xrealloc): Change type of bytes argument from
+ int to size_t and remove the test that checks if it is less than
+ zero.
+ * cvs.h (xmalloc, xrealloc): Update prototype.
+
+Thu Sep 1 12:22:20 1994 Jim Kingdon (kingdon@cygnus.com)
+
+ * update.c (merge_file, join_file): Pass -E to rcsmerge.
+ (merge_file): If rcsmerge doesn't change the file, say so.
+
+ * recurse.c, cvs.h (start_recursion): New argument wd_is_repos.
+ * recurse.c (start_recursion): Use it instead of checking whether
+ command_name is rtag to find out if we are cd'd to the repository.
+ * client.c, update.c, commit.c, status.c, diff.c, log.c, admin.c,
+ remove.c, tag.c: Pass 0 for wd_is_repos.
+ * rtag.c, patch.c: Pass 1 for wd_is_repos.
+
+ * classify.c, cvs.h (Classify_File): New argument pipeout.
+ * classify.c (Classify_File): If pipeout, don't complain if the
+ file is already there.
+ * update.c, commit.c, status.c: Change callers.
+
+ * mkmodules.c (main): Don't print "reminders" if commitinfo,
+ loginfo, rcsinfo, or editinfo files are missing.
+
+Mon Aug 22 23:22:59 1994 Ken Raeburn (raeburn@kr-pc.cygnus.com)
+
+ * server.c (strerror): Static definition replaced by extern
+ declaration.
+
+Sun Aug 21 07:16:27 1994 Ken Raeburn (raeburn@kr-pc.cygnus.com)
+
+ * client.c (update_entries): Run "patch" with input from
+ /dev/null, so if it's the wrong version, it fails quickly rather
+ than waiting for EOF from terminal before failing.
+
+Sat Aug 20 04:16:33 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * server.c (serve_unchanged): Instead of creating a file with a
+ zero timestamp, rewrite the entries file to have "=" in the
+ timestamp field.
+ * vers_ts.c (mark_lost, mark_unchanged): New macros.
+ (time_stamp_server): Use them, for clarity. Interpret "="
+ timestamp as an unchanged file. A zero-timestamp file should
+ never be encountered now in use_unchanged mode.
+
+ * client.c (start_server): If CVS_CLIENT_PORT indicates a
+ non-positive port number, skip straight to rsh connection.
+
+ * status.c (status_fileproc): Fix ts_rcs reference when printing
+ version info, to correspond to new Entries file format. Don't
+ print it at all if server_active, because it won't have any useful
+ data.
+
+Thu Aug 18 14:38:21 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * cvs.h (status): Declare.
+ * client.c (client_status): New function.
+
+ * client.h (client_status): Declare.
+ * main.c (cmds): Include it.
+ * server.c (serve_status): New function.
+ (requests): Add it.
+ * status.c (status): Do the remote thing if client_active.
+
+ * client.c (supported_request): New function.
+ (start_server): Use it.
+
+ * server.c (receive_partial_file): New function, broken out from
+ serve_modified. Operate with fixed-size local buffer, instead of
+ growing stack frame by entire file size.
+ (receive_file): New function, broken out from serve_modified.
+ (serve_modified): Call it.
+ (server): Print out name of unrecognized request.
+
+ More generic stream-filtering support:
+ * client.c (close_on_exec, filter_stream_through_program): New
+ functions.
+ (server_fd): New variable.
+ (get_responses_and_close): Direct non-rsh connection is now
+ indicated by server_fd being non-negative. File descriptors for
+ to_server and from_server may now be different in case "tee"
+ filtering is being done. Wait for rsh_pid specifically.
+ (start_server): Use filter_stream_through_program for "tee"
+ filter, and enable it for direct Kerberos-authenticated
+ connections. Use dup to create new file descriptors for server
+ connection if logging is enabled.
+ (start_rsh_server): Disable code that deals with logging.
+
+ Per-file compression support:
+ * cvs.h (gzip_level): Declare.
+ * main.c (usg): Describe new -z argument.
+ (main): Recognize it and set gzip_level.
+ * client.c (filter_through_gzip, filter_through_gunzip): New
+ functions to handle compression.
+ (update_entries): If size starts with "z", uncompress
+ (start_server): If gzip_level is non-zero and server supports it,
+ issue gzip-file-contents request.
+ (send_fileproc): Optionally compress file contents. Use a
+ slightly larger buffer, anticipating the worst case.
+ * server.c (gzip_level): Define here.
+ (receive_file): Uncompress file contents if needed.
+ (serve_modified): Recognize "z" in file size and pass receive_file
+ appropriate flag.
+ (buf_read_file_to_eof, buf_chain_length): New functions.
+ (server_updated): Call them when sending a compressed file.
+ (serve_gzip_contents): New function; set gzip_level.
+ (requests): Added gzip-file-contents request.
+
+Wed Aug 17 09:37:44 1994 J.T. Conklin (jtc@cygnus.com)
+
+ * find_names.c (find_dirs): Use 4.4BSD filesystem feature (it
+ contains the file type in the dirent structure) to avoid
+ stat'ing each file.
+
+ * commit.c (remove_file,checkaddfile): Change type of umask
+ variables from int to mode_t.
+ * subr.c (): Likewise.
+
+Tue Aug 16 19:56:34 1994 Mark Eichin (eichin@cygnus.com)
+
+ * diff.c (diff_fileproc): Don't use diff_rev* because they're
+ invariant across calls -- add new variable top_rev.
+ (diff_file_nodiff): After checking possible use_rev* values, if
+ top_rev is set drop it in as well (if we don't already have two
+ versions) and then clear it for next time around.
+
+Wed Aug 10 20:50:47 1994 Mark Eichin (eichin@cygnus.com)
+
+ * diff.c (diff_fileproc): if ts_user and ts_rcs match, then the
+ file is at the top of the tree -- so we might not even have a
+ copy. Put the revision into diff_rev1 or diff_rev2.
+
+Wed Aug 10 14:55:38 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * server.c (do_cvs_command): Use waitpid.
+
+ * subr.c (run_exec): Always use waitpid.
+
+ * Makefile.in (CC, LIBS): Define here, in case "make" is run in
+ this directory instead of top level.
+
+Wed Aug 10 13:57:06 1994 Mark Eichin (eichin@cygnus.com)
+
+ * client.c (krb_get_err_text): use HAVE_KRB_GET_ERR_TEXT to
+ determine if we need to use the array or the function.
+ * main.c: ditto.
+
+Tue Aug 9 16:43:30 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * entries.c (ParseEntries): If timestamp is in old format, rebuild
+ it in the new format. Fudge an unmatchable entry that won't
+ trigger this code next time around, if the file is modified.
+
+ * vers_ts.c (time_stamp): Only put st_mtime field into timestamp,
+ and use GMT time for it. With st_ctime or in local time, copying
+ trees between machines in different time zones makes all the files
+ look modified.
+ (time_stamp_server): Likewise.
+
+Tue Aug 9 19:40:51 1994 Mark Eichin (eichin@cygnus.com)
+
+ * main.c (main): use krb_get_err_text function instead of
+ krb_err_txt array.
+
+Thu Aug 4 15:37:50 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * main.c (main): When invoked as kserver, set LOGNAME and USER
+ environment variables to the remote user name.
+
+Thu Aug 4 07:44:37 1994 Mark Eichin (eichin@cygnus.com)
+
+ * client.c: (handle_valid_requests): if we get an option that has
+ rq_enableme set, then send that option. If it is UseUnchanged, set
+ use_unchanged so that the rest of the client knows about
+ it. (Could become a more general method for dealing with protocol
+ upgrades.)
+ (send_fileproc): if use_unchanged didn't get set, send an
+ old-style "Lost" request, otherwise send an "Unchanged" request.
+ * server.c (serve_unchanged): new function, same as serve_lost,
+ but used in the opposite case.
+ (requests): add new UseUnchanged and Unchanged requests, and make
+ "Lost" optional (there isn't a good way to interlock these.)
+ * server.h (request.status): rq_enableme, new value for detecting
+ compatibility changes.
+ * vers_ts.c (time_stamp_server): swap meaning of zero timestamp if
+ use_unchanged is set.
+
+Tue Jul 26 10:19:30 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * sanity.sh: Separate CVSROOT_FILENAME, which must be the filename
+ of the root, from CVSROOT, which can include a hostname for
+ testing remote CVS. (but the tests aren't yet prepared to deal
+ with the bugs in remote CVS).
+
+ * import.c (update_rcs_file): Change temporary file name in TMPDIR
+ from FILE_HOLDER to cvs-imp<process-id>.
+
+ * sanity.sh: Add ">/dev/null" and "2>/dev/null" many places to
+ suppress spurious output. Comment out tests which don't work (cvs
+ add on top-level directory, cvs diff when non-committed adds or
+ removes have been made, cvs release, test 53 (already commented as
+ broken), retagging without deleting old tag, test 63). Now 'make
+ check' runs without any failures.
+
+Fri Jul 15 12:58:29 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * Makefile.in (install): Do not depend upon installdirs.
+
+Thu Jul 14 15:49:42 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * client.c, server.c: Don't try to handle alloca here; it's
+ handled by cvs.h.
+
+Tue Jul 12 13:32:40 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * client.c (update_entries): Reset stored_checksum_valid if we
+ quit early because of a patch failure.
+
+Fri Jul 8 11:13:05 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * client.c (responses): Mark "Remove-entry" as optional.
+
+Thu Jul 7 14:07:58 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * server.c (server_updated): Add new checksum argument. If it is
+ not NULL, and the client supports the "Checksum" response, send
+ it.
+ * server.h (server_updated): Update prototype.
+ * update.c: Include md5.h.
+ (update_file_proc): Pass new arguments to patch_file and
+ server_updated.
+ (patch_file): Add new checksum argument. Set it to the MD5
+ checksum of the version of the file being checked out.
+ (merge_file): Pass new argument to server_updated.
+ * client.c: Include md5.h.
+ (stored_checksum_valid, stored_checksum): New static variables.
+ (handle_checksum): New static function.
+ (update_entries): If a checksum was received, check it against the
+ MD5 checksum of the final file.
+ (responses): Add "Checksum".
+ (start_server): Clear stored_checksum_valid.
+ * commit.c (commit_fileproc): Pass new argument to server_updated.
+
+ * client.h (struct response): Move definition in from client.c,
+ add status field.
+ (responses): Declare.
+ * client.c (struct response): Remove definition; moved to
+ client.h.
+ (responses): Make non-static. Initialize status field.
+ * server.c (serve_valid_responses): Check and record valid
+ responses, just as in handle_valid_requests in client.c.
+
+ * diff.c (diff_client_senddate): New function.
+ (diff): Use it to send -D arguments to server.
+
+Wed Jul 6 12:52:37 1994 J.T. Conklin (jtc@phishhead.cygnus.com)
+
+ * rcs.c (RCS_parsercsfile_i): New function, parse RCS file
+ referenced by file ptr argument.
+ (RCS_parsercsfile): Open file and pass its file ptr to above function.
+ (RCS_parse): Likewise.
+
+Wed Jul 6 01:25:38 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * client.c (update_entries): Print message indicating that an
+ unpatchable file will be refetched.
+ (client_update): Print message when refetching unpatchable files.
+
+Fri Jul 1 07:16:29 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * client.c (send_dirent_proc): Don't call send_a_repository if
+ repository is "".
+
+Fri Jul 1 13:58:11 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * client.c (last_dirname, last_repos): Move out of function.
+ (failed_patches, failed_patches_count): New static variables.
+ (update_entries): If patch program fails, save short_pathname in
+ failed_patches array, only exit program if retcode is -1, and
+ return out of the function rather than update the Entries line.
+ (start_server): Clear toplevel_repos, last_dirname, last_repos.
+ (client_update): If failed_patches is not NULL after doing first
+ update, do another update, but remove all the failed files first.
+
+Thu Jun 30 09:08:57 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (requests): Add request "Global_option".
+ (serve_global_option): New function, to handle it.
+ * client.c (start_server): Deal with global options. Check for
+ errors from fprintf.
+
+ * client.c (send_fileproc): Split out code which sends repository
+ into new function send_a_repository. Also, deal with update_dir
+ being ".".
+ (send_dirent_proc): Call send_a_repository.
+ * add.c (add): If client_active, do special processing for
+ directories.
+ (add_directory): If server_active, don't try to create CVSADM
+ directory.
+
+Thu Jun 30 11:58:52 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * client.c (update_entries): If patch succeeds, remove the backup
+ file.
+ * server.c (server_updated): Add new argument file_info. If it is
+ not NULL, use it rather than sb to get the file mode.
+ * server.h (server_updated): Update prototype for new argument.
+ * update.c (update_file_proc): Pass new arguments to patch_file
+ and server_updated.
+ (patch_file): Add new argument file_info. Don't use -p to check
+ out new version, check it out into file and rename that to file2.
+ If result is not readable, assume file is dead and set docheckout.
+ Call xchmod on file2. Close the patch file after checking for a
+ binary diff. Set file_info to the results of stat on file2.
+ (merge_file): Pass new argument to server_updated.
+ * commit.c (commit_fileproc): Pass new argument to server_updated.
+
+Wed Jun 29 13:00:41 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * client.c (krb_realmofhost): Declare, since it's not the current
+ <krb.h>.
+ (start_server): Save the name returned by gethostbyname. Call
+ krb_realmofhost to get the realm. Pass the resulting realm to
+ krb_sendauth. Pass the saved real name to krb_sendauth, rather
+ than server_host.
+
+ * update.c (update_file_proc): Pass &docheckout to patch_file. If
+ it is set to 1, fall through to T_CHECKOUT case.
+ (patch_file): Add docheckout argument. Set it to 1 if we can't
+ make a patch. Check out the files and run diff rather than
+ rcsdiff. If either file does not end in a newline, we can't make
+ a patch. If the patch starts with the string "Binary", assume
+ one or the other is a binary file, and that we can't make a patch.
+
+Tue Jun 28 11:57:29 1994 Ian Lance Taylor (ian@sanguine.cygnus.com)
+
+ * client.c (update_entries): If the patch file is empty, don't run
+ patch program; avoids error message.
+
+ * classify.c (Classify_File): Return T_CHECKOUT, not T_PATCH, if
+ the file is in the Attic.
+
+ * cvs.h (enum classify_type): Add T_PATCH.
+ * config.h (PATCH_PROGRAM): Define.
+ * classify.c (Classify_File): If user file exists and is not
+ modified, and using the same -k options, return T_PATCH instead of
+ T_CHECKOUT.
+ * update.c (patches): New static variable.
+ (update): Add u to gnu_getopt argument. Handle it.
+ (update_file_proc): Handle T_PATCH.
+ (patch_file): New static function.
+ * server.h (enum server_updated_arg4): Add SERVER_PATCHED.
+ * server.c (server_updated): Handle SERVER_PATCHED by sending
+ "Patched" command.
+ (serve_ignore): New static function.
+ (requests): Add "update-patches".
+ (client_update): If the server supports "update-patches", send -u.
+ * client.c (struct update_entries_data): Change contents field
+ from int to an unnamed enum.
+ (update_entries): Correponding change. If contents is
+ UPDATE_ENTRIES_PATCH, pass the input to the patch program.
+ (handle_checked_in): Initialize contents to enum value, not int.
+ (handle_updated, handle_merged): Likewise.
+ (handle_patched): New static function.
+ (responses): Add "Patched".
+ * commit.c (check_fileproc): Handle T_PATCH.
+ * status.c (status_fileproc): Likewise.
+
+ * client.c (start_server): If CVS_CLIENT_PORT is set in the
+ environment, connect to that port, rather than looking up "cvs" in
+ /etc/services. For debugging.
+
+Tue Jun 21 12:48:16 1994 Ken Raeburn (raeburn@cujo.cygnus.com)
+
+ * update.c (joining): Return result of comparing pointer with
+ NULL, not result of casting (truncating, on Alpha) pointer to int.
+
+ * main.c (main) [HAVE_KERBEROS]: Impose a umask if starting as
+ Kerberos server, so temp directories won't be world-writeable.
+
+ * update.c (update_filesdone_proc) [CVSADM_ROOT]: If environment
+ variable CVS_IGNORE_REMOTE_ROOT is set and repository is remote,
+ don't create CVS/Root file.
+ * main.c (main): If env var CVS_IGNORE_REMOTE_ROOT is set, don't
+ check CVS/Root.
+
+Fri Jun 10 18:48:32 1994 Mark Eichin (eichin@cygnus.com)
+
+ * server.c (O_NDELAY): use POSIX O_NONBLOCK by default, unless it
+ isn't available (in which case substitute O_NDELAY.)
+
+Thu Jun 9 19:17:44 1994 Mark Eichin (eichin@cygnus.com)
+
+ * server.c (server_cleanup): chdir out of server_temp_dir before
+ deleting it (so that it works on non-BSD systems.) Code for choice
+ of directory cloned from server().
+
+Fri May 27 18:16:01 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * client.c (update_entries): Add return type of void.
+ (get_responses_and_close): If using Kerberos and from_server and
+ to_server are using the same file descriptor, use shutdown, not
+ fclose. Close from_server.
+ (start_server): New function; most of old version renamed to
+ start_rsh_server.
+ (start_rsh_server): Mostly renamed from old start_server.
+ (send_fileproc): Use %lu and cast sb.st_size in fprintf call.
+ (send_files): Remove unused variables repos and i.
+ (option_no_arg): Comment out; unused.
+ * main.c (main): Initialize cvs_update_env to 0. If command is
+ "kserver", authenticate and change command to "server". If
+ command is "server", don't call Name_Root, don't check access to
+ history file, and don't assume that CVSroot is not NULL.
+ * server.c (my_memmove): Removed.
+ (strerror): Change check from STRERROR_MISSING to HAVE_STRERROR.
+ (serve_root): Likewise for putenv.
+ (serve_modified): Initialize buf to NULL.
+ (struct output_buffer, buf_try_send): Remove old buffering code.
+ (struct buffer, struct buffer_data, BUFFER_DATA_SIZE,
+ allocate_buffer_datas, get_buffer_data, buf_empty_p,
+ buf_append_char, buf_append_data, buf_read_file, buf_input_data,
+ buf_copy_lines): New buffering code.
+ (buf_output, buf_output0, buf_send_output, set_nonblock,
+ set_block, buf_send_counted, buf_copy_counted): Rewrite for new
+ buffering code.
+ (protocol, protocol_memory_error, outbuf_memory_error,
+ do_cvs_command, server_updated): Rewrite for new buffering code.
+ (input_memory_error): New function.
+ (server): Put Rcsbin at start of PATH in environment.
+ * Makefile.in: Add @includeopt@ to DEFS.
+
+Fri May 20 08:13:10 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * cvs.h, classify.c (Classify_File): New argument update_dir.
+ Include it in user messages.
+ * commit.c (check_fileproc), status.c (status_fileproc), update.c
+ (update_file_proc): Pass update_dir to Classify_File.
+ * commit.c (check_fileproc), update.c (checkout_file):
+ Include update_dir in user messages.
+ * commit.c (check_fileproc) update.c (update_file_proc): Re-word
+ "unknown status" message.
+
+ * server.c (server_checked_in): Deal with the case where
+ scratched_file is set rather than entries_line.
+
+ * entries.c (Register): Write file even if server_active.
+ * add.c (add): Add comment about how we depend on above behavior.
+
+Tue May 17 08:16:42 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * mkmodules.c: Add dummy server_active and server_cleanup, to go
+ with the dummy Lock_Cleanup already there.
+
+ * server.c (server_cleanup): No longer static.
+
+Sat May 7 10:17:17 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ Deal with add and remove:
+ * commit.c (checkaddfile): If CVSEXT_OPT or CVSEXT_LOG file does
+ not exist, just silently keep going.
+ (remove_file): If server_active, remove file before creating
+ temporary file with that name.
+ * server.c (serve_remove, serve_add): New functions.
+ (requests): Add them.
+ * server.c (server_register): If options is NULL, it means there
+ are no options.
+ * server.c, server.h (server_scratch_entry_only): New function.
+ New variable kill_scratched_file.
+ (server_scratch, server_updated): Deal with kill_scratched_file.
+ * commit.c (commit_fileproc): If server_active, call
+ server_scratch_entry_only and server_updated.
+ * add.c (add): Add client_active code.
+ (add): If server_active, call server_checked_in for each file added.
+ * remove.c (remove): Add client_active code.
+ (remove_fileproc): If server_active, call server_checked_in.
+ * main.c (cmds), client.c, client.h: New functions client_add and
+ client_remove.
+ * Move declarations of add, cvsremove, diff, and cvslog from
+ main.c to cvs.h.
+ * client.c (call_in_directory): Update comment regarding Root and
+ Repository files.
+ (send_fileproc): Only send Entries line if Version_TS really finds
+ an entry. If it doesn't find one, send Modified.
+ (update_entries): If version is empty or starts with 0 or -,
+ create a dummy timestamp.
+
+Thu May 5 19:02:51 1994 Per Bothner (bothner@kalessin.cygnus.com)
+
+ * recurse/c (start_recursion): If we're doing rtag, and thus
+ have cd'd to the reporsitory, add ,v to a file name before stat'ing.
+
+Wed Apr 20 15:01:45 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * client.c (client_commit): Call ign_setup.
+ (client_update, client_checkout): Likewise.
+ * diff.c (diff): If client, call ign_setup.
+ * log.c (cvslog): Likewise.
+ * update.h (ignlist): Change definition to declaration to avoid
+ depending upon common semantics (not required by ANSI C, and not
+ the default on Irix 5).
+ * update.c (ignlist): Define.
+
+Tue Apr 19 00:02:54 1994 John Gilmore (gnu@cygnus.com)
+
+ Add support for remote `cvs log'; clean up `cvs diff' a bit.
+
+ * client.c (send_arg): Make external.
+ (send_option_string): New function.
+ (client_diff_usage): Remove, unused.
+ (client_diff): Just call diff, not do_diff.
+ (client_log): Add.
+ * client.h (client_log, send_arg, send_option_string): Declare.
+ * cvs.h (cvslog): Declare.
+ * diff.c (do_diff): Fold back into diff(), distinguish by checking
+ client_active.
+ (diff): Remove `-*' arg parsing crud; use send_option_string.
+ * log.c (cvslog): If a client, start the server, pass options
+ and files, and handle server responses.
+ * main.c (cmds): Add client_log.
+ (main): Remove obnoxious message every time CVS/Root is used.
+ Now CVS will be quiet about it -- unless there is a conflict
+ between $CVSROOT or -d value versus CVS/Root.
+ * server.c (serve_log): Add.
+ (requests): Add "log".
+
+Mon Apr 18 22:07:53 1994 John Gilmore (gnu@cygnus.com)
+
+ Add support for remote `cvs diff'.
+
+ * diff.c (diff): Break guts out into new fn do_diff.
+ Add code to handle starting server, writing args,
+ sending files, and retrieving responses.
+ (includes): Use PARAMS for static function declarations.
+ * client.c (to_server, from_server, rsh_pid,
+ get_responses_and_close, start_server, send_files,
+ option_with_arg): Make external.
+ (send_file_names): New function.
+ (client_diff): New function.
+ * client.h (client_diff, to_server, from_server,
+ rsh_pid, option_with_arg, get_responses_and_close, start_server,
+ send_file_names, send_files): Declare.
+ * cvs.h (diff): Declare.
+ * main.c (cmds): Add client_diff to command table.
+ * server.c (serve_diff): New function.
+ (requests): Add serve_diff.
+ (server): Bug fix: avoid free()ing incremented cmd pointer.
+ * update.h (update_filesdone_proc): Declare with PARAMS.
+
+Sat Apr 16 04:20:09 1994 John Gilmore (gnu@cygnus.com)
+
+ * root.c (Name_root): Fix tyop (CVSroot when root meant).
+
+Sat Apr 16 03:49:36 1994 John Gilmore (gnu@cygnus.com)
+
+ Clean up remote `cvs update' to properly handle ignored
+ files (and files that CVS can't identify), and to create
+ CVS/Root entries on the client side, not the server side.
+
+ * client.c (send_fileproc): Handle the ignore list.
+ (send_dirent_proc): New function for handling ignores.
+ (send_files): Use update_filesdone_proc and send_dirent_proc
+ while recursing through the local filesystem.
+ * update.h: New file.
+ * update.c: Move a few things into update.h so that client.c
+ can use them.
+
+Fri Mar 11 13:13:20 1994 Ian Lance Taylor (ian@tweedledumb.cygnus.com)
+
+ * server.c: If O_NDELAY is not defined, but O_NONBLOCK is, define
+ O_NDELAY to O_NONBLOCK.
+
+Wed Mar 9 21:08:30 1994 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ Fix some spurious remote CVS errors caused by the CVS/Root patches:
+ * update.c (update_filesdone_proc): If server_active, don't try to
+ create CVS/Root.
+ * root.c (Name_Root): Make error messages which happen if root is
+ not an absolute pathname or if it doesn't exist a bit clearer.
+ Skip them if root contains a colon.
+
+Mon Nov 1 15:54:51 1993 K. Richard Pixley (rich@sendai.cygnus.com)
+
+ * client.c (client_commit): dynamically allocate message.
+
+Tue Jun 1 17:03:05 1993 david d `zoo' zuhn (zoo at cirdan.cygnus.com)
+
+ * server.h: remove alloca cruft
+
+ * server.c: replace with better alloca cruft
+
+Mon May 24 11:25:11 1993 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * entries.c (Scratch_Entry): Update our local Entries file even if
+ server_active.
+
+ * server.c (server_scratch, server_register): If both Register
+ and Scratch_Entry happen, use whichever one happened later.
+ If neither happen, silently continue.
+
+ * client.c (client_checkout): Initialize tag and date (eichin and
+ I independently discovered this bug at the same time).
+
+Wed May 19 10:11:51 1993 Mark Eichin (eichin@cygnus.com)
+
+ * client.c (update_entries): handle short reads over the net
+ (SVR4 fread is known to be broken, specifically for short
+ reads off of streams.)
+
+Tue May 18 15:53:44 1993 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ * server.c (do_cvs_command): Fix fencepost error in setting
+ num_to_check.
+
+ * server.c (do_cvs_command): If terminated with a core dump, print
+ message and set dont_delete_temp.
+ (server_cleanup): If dont_delete_temp, don't delete it.
+
+ * client.c (get_server_responses): Don't change cmd since we
+ are going to "free (cmd)".
+
+ * server.c: Rename memmove to my_memmove pending a real fix.
+
+ * server.c (do_cvs_command): Set num_to_check to largest descriptor
+ we try to use, rather than using (non-portable) getdtablesize.
+
+Wed May 12 15:31:40 1993 Jim Kingdon (kingdon@lioth.cygnus.com)
+
+ Add CVS client feature:
+ * client.{c,h}: New files.
+ * cvs.h: Include client.h.
+ * main.c: If CVSROOT has a colon, use client commands instead.
+ * vers_ts.c (Version_TS): If repository arg is NULL, don't worry
+ about the repository.
+ * logmsg.c (do_editor): If repository or changes is NULL, just don't
+ use those features.
+ * create_adm.c (Create_Admin), callers: Move the test for whether
+ the repository exists from here to callers.
+ * repos.c (Name_Repository): Don't test whether the repository exists
+ if client_active set (might be better to move test to callers).
+
+ Add CVS server feature:
+ * server.{c,h}: New files.
+ * cvs.h: Include server.h.
+ * checkin.c (Checkin): Call server_checked_in.
+ * update.c (update_file_proc, merge_files): Call server_updated.
+ * entries.c (Register): Call server_register.
+ (Scratch_Entry): Call server_scratch.
+ * main.c: Add server to cmds.
+ * vers_ts.c (Version_TS): If server_active, call new function
+ time_stamp_server to set ts_user.
+
+
+For older changes, there might be some relevant stuff in the bottom of
+the NEWS file, but I'm afraid probably a lot of them are lost in the
+mists of time.
diff --git a/contrib/cvs/src/Makefile.in b/contrib/cvs/src/Makefile.in
new file mode 100644
index 0000000..acb3343
--- /dev/null
+++ b/contrib/cvs/src/Makefile.in
@@ -0,0 +1,198 @@
+# Makefile for GNU CVS program.
+# Do not use this makefile directly, but only from `../Makefile'.
+# Copyright (C) 1986, 1988-1990 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# $CVSid: @(#)Makefile.in 1.19 94/09/29 $
+
+SHELL = /bin/sh
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+
+# Where to install the executables.
+bindir = $(exec_prefix)/bin
+
+# Where to put the system-wide .cvsrc file
+libdir = $(prefix)/lib
+
+# Where to put the manual pages.
+mandir = $(prefix)/man
+
+# Use cp if you don't have install.
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+
+LIBS = @LIBS@
+
+SOURCES = add.c admin.c checkin.c checkout.c classify.c client.c commit.c \
+create_adm.c cvsrc.c diff.c edit.c entries.c error.c expand_path.c \
+fileattr.c find_names.c hash.c history.c ignore.c import.c \
+lock.c log.c login.c logmsg.c main.c mkmodules.c modules.c myndbm.c no_diff.c \
+parseinfo.c patch.c rcs.c rcscmds.c recurse.c release.c remove.c repos.c \
+root.c rtag.c scramble.c server.c status.c subr.c filesubr.c run.c \
+tag.c update.c watch.c wrapper.c vers_ts.c version.c
+
+OBJECTS = add.o admin.o checkin.o checkout.o classify.o client.o commit.o \
+create_adm.o cvsrc.o diff.o edit.o entries.o expand_path.o \
+fileattr.o find_names.o hash.o history.o ignore.o import.o \
+lock.o log.o login.o logmsg.o main.o mkmodules.o modules.o myndbm.o no_diff.o \
+parseinfo.o patch.o rcs.o rcscmds.o recurse.o release.o remove.o repos.o \
+root.o rtag.o scramble.o server.o status.o tag.o update.o \
+watch.o wrapper.o vers_ts.o \
+subr.o filesubr.o run.o version.o error.o
+
+HEADERS = cvs.h rcs.h hash.h myndbm.h \
+ update.h server.h client.h error.h fileattr.h edit.h watch.h
+
+TAGFILES = $(HEADERS) options.h.in $(SOURCES)
+
+DISTFILES = .cvsignore Makefile.in ChangeLog ChangeLog-9395 ChangeLog-9194 \
+ NOTES README-rm-add \
+ sanity.sh cvsbug.sh $(TAGFILES)
+
+PROGS = cvs cvsbug
+
+DEFS = @DEFS@ @includeopt@
+
+CC = @CC@
+CFLAGS = @CFLAGS@
+CPPFLAGS =
+LDFLAGS = @LDFLAGS@
+
+INCLUDES = -I. -I.. -I$(srcdir) -I$(top_srcdir)/lib
+.c.o:
+ $(CC) $(CPPFLAGS) $(INCLUDES) $(DEFS) $(CFLAGS) -c $<
+
+all: Makefile $(PROGS)
+.PHONY: all
+
+saber_cvs:
+ @cd ..; $(MAKE) saber SUBDIRS=src
+
+lint:
+ @cd ..; $(MAKE) lint SUBDIRS=src
+
+# CYGNUS LOCAL: Do not depend upon installdirs
+install:
+ @for prog in $(PROGS); do \
+ echo Installing $$prog in $(bindir); \
+ $(INSTALL) $$prog $(bindir)/$$prog ; \
+ done
+
+installdirs:
+ $(SHELL) $(top_srcdir)/mkinstalldirs $(bindir)
+
+.PHONY: install installdirs
+
+installcheck:
+ RCSBIN=$(bindir) ; export RCSBIN ; $(SHELL) $(srcdir)/sanity.sh $(bindir)/cvs
+.PHONY: installcheck
+
+check: all
+ if [ -x ../../rcs/src/rcs ] ; then \
+ RCSBIN=`pwd`/../../rcs/src ; \
+ export RCSBIN ; \
+ fi ; \
+ $(SHELL) $(srcdir)/sanity.sh `pwd`/cvs
+.PHONY: check
+
+# I'm not sure there is any remaining reason for this to be separate from
+# `make check'.
+remotecheck: all
+ $(SHELL) $(srcdir)/sanity.sh -r `pwd`/cvs
+.PHONY: remotecheck
+
+tags: $(TAGFILES)
+ ctags $(TAGFILES)
+
+TAGS: $(TAGFILES)
+ etags `for i in $(TAGFILES); do echo $(srcdir)/$$i; done`
+
+ls:
+ @echo $(DISTFILES)
+.PHONY: ls
+
+clean:
+ /bin/rm -f $(PROGS) *.o core check.log check.plog
+.PHONY: clean
+
+distclean: clean
+ rm -f tags TAGS Makefile options.h
+.PHONY: distclean
+
+realclean: distclean
+.PHONY: realclean
+
+dist-dir:
+ mkdir ${DISTDIR}
+ for i in ${DISTFILES}; do \
+ ln $(srcdir)/$${i} ${DISTDIR}; \
+ done
+.PHONY: dist-dir
+
+# Linking rules.
+
+$(PROGS): ../lib/libcvs.a
+
+cvs: $(OBJECTS)
+ $(CC) $(OBJECTS) ../lib/libcvs.a $(LIBS) $(LDFLAGS) -o $@
+
+xlint: $(SOURCES)
+ files= ; \
+ for i in $(SOURCES) ; do \
+ files="$$files $(srcdir)/$$i" ; \
+ done ; \
+ sh -c "lint $(DEFS) $(INCLUDES) $$files | grep -v \"possible pointer alignment problem\" \
+ | grep -v \"argument closure unused\""
+
+saber: $(SOURCES)
+ # load $(CFLAGS) $(SOURCES)
+ # load ../lib/libcvs.a $(LIBS)
+
+cvsbug: cvsbug.sh
+ echo > .fname \
+ cvs-`sed < $(srcdir)/version.c \
+ -e '/version_string/!d' \
+ -e 's/[^0-9.]*\([0-9.]*\).*/\1/' \
+ -e q`
+ sed -e 's,xLIBDIRx,$(libdir)/cvs,g' \
+ -e "s,xVERSIONx,`cat .fname`,g" $(srcdir)/$@.sh > $@-t
+ rm -f .fname
+ mv $@-t $@
+ chmod a+x $@
+
+# Compilation rules.
+
+$(OBJECTS): $(HEADERS) options.h
+
+subdir = src
+Makefile: ../config.status Makefile.in
+ cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status
+
+options.h: ../config.status options.h.in
+ cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status
+
+#../config.status: ../configure
+# cd .. ; $(SHELL) config.status --recheck
+
+#../configure: ../configure.in
+# cd $(top_srcdir) ; autoconf
diff --git a/contrib/cvs/src/NOTES b/contrib/cvs/src/NOTES
new file mode 100644
index 0000000..646ebdf
--- /dev/null
+++ b/contrib/cvs/src/NOTES
@@ -0,0 +1,60 @@
+wishlist - Tue Nov 2 15:22:58 PST 1993
+
+* bcopy -> memcpy & friends.
+ ** done 12/18/93
+
+* remove static buffers.
+* replace list & node cache with recursive obstacks, (xmalloc,
+ getnode, getlist)
+* check all io functions for error return codes. also check all
+ system calls.
+* error check mkdir.
+
+---
+Old notes...
+
+* All sizing limits are gone. The rest of these items were incidental
+ in that effort.
+
+* login name from history was duplicated. taught existing routine to
+ cache and use that instead. Also add routines to cache uid, pid,
+ etc.
+
+* ign strings were never freed. Now they are.
+
+* there was a printf("... %s ...", cp) vs *cp bug in history.c. Now
+ fixed.
+
+* The environment variables TMPDIR, HOME, and LOGNAME were not
+ honored. Now they are.
+
+* extra line inserted by do_editor() is gone. Then obviated. Editor
+ is now called exactly once per checkin.
+
+* revised editor behaviour. Never use /dev/tty. If the editor
+ session fails, we haven't yet done anything. Therefor the user can
+ safely rerun cvs and we should just fail. Also use the editor for
+ initial log messages on added files. Also omit the confirmation
+ when adding directories. Adding directories will require an
+ explicit "commit" step soon. Make it possible to prevent null login
+ messages using #define REQUIRE_LOG_MESSAGES
+
+* prototypes for all callbacks.
+
+* all callbacks get ref pointers.
+
+* do_recursion/start_recursion now use recusion_frame's rather than a
+ list of a lot of pointers and global variables.
+
+* corrected types on status_dirproc().
+
+* CONFIRM_DIRECTORY_ADDS
+
+* re_comp was innappropriate in a few places. I've eliminated it.
+
+* FORCE_MESSAGE_ON_ADD
+
+* So I built a regression test. Let's call it a sanity check to be
+ less ambitious. It exposed that cvs is difficult to call from
+ scripts.
+
diff --git a/contrib/cvs/src/README-rm-add b/contrib/cvs/src/README-rm-add
new file mode 100644
index 0000000..87fd7c6
--- /dev/null
+++ b/contrib/cvs/src/README-rm-add
@@ -0,0 +1,31 @@
+WHAT THE "DEATH SUPPORT" FEATURES DO:
+
+(Some of the death support stuff is documented in the main manual, but
+this file is for stuff which noone has gotten around to adding to the
+main manual yet).
+
+CVS with death support can record when a file is active, or alive, and
+when it is removed, or dead. With this facility you can record the
+history of a file, including the fact that at some point in its life
+the file was removed and then later added.
+
+Files can now be added or removed in a branch and later merged
+into the trunk.
+
+ cvs update -A
+ touch a b c
+ cvs add a b c ; cvs ci -m "added" a b c
+ cvs tag -b branchtag
+ cvs update -r branchtag
+ touch d ; cvs add d
+ rm a ; cvs rm a
+ cvs ci -m "added d, removed a"
+ cvs update -A
+ cvs update -jbranchtag
+
+Added and removed files may also be merged between branches.
+
+Files removed in the trunk may be merged into branches.
+
+Files added on the trunk are a special case. They cannot be merged
+into a branch. Instead, simply branch the file by hand.
diff --git a/contrib/cvs/src/add.c b/contrib/cvs/src/add.c
new file mode 100644
index 0000000..82efefe
--- /dev/null
+++ b/contrib/cvs/src/add.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Add
+ *
+ * Adds a file or directory to the RCS source repository. For a file,
+ * the entry is marked as "needing to be added" in the user's own CVS
+ * directory, and really added to the repository when it is committed.
+ * For a directory, it is added at the appropriate place in the source
+ * repository and a CVS directory is generated within the directory.
+ *
+ * The -m option is currently the only supported option. Some may wish to
+ * supply standard "rcs" options here, but I've found that this causes more
+ * trouble than anything else.
+ *
+ * The user files or directories must already exist. For a directory, it must
+ * not already have a CVS file in it.
+ *
+ * An "add" on a file that has been "remove"d but not committed will cause the
+ * file to be resurrected.
+ */
+
+#include "cvs.h"
+#include "savecwd.h"
+
+static int add_directory PROTO((char *repository, char *dir));
+static int build_entry PROTO((char *repository, char *user, char *options,
+ char *message, List * entries, char *tag));
+
+static const char *const add_usage[] =
+{
+ "Usage: %s %s [-k rcs-kflag] [-m message] files...\n",
+ "\t-k\tUse \"rcs-kflag\" to add the file with the specified kflag.\n",
+ "\t-m\tUse \"message\" for the creation log.\n",
+ NULL
+};
+
+int
+add (argc, argv)
+ int argc;
+ char **argv;
+{
+ char *message = NULL;
+ char *user;
+ int i;
+ char *repository;
+ int c;
+ int err = 0;
+ int added_files = 0;
+ char *options = NULL;
+ List *entries;
+ Vers_TS *vers;
+
+ if (argc == 1 || argc == -1)
+ usage (add_usage);
+
+ wrap_setup ();
+
+ /* parse args */
+ optind = 1;
+ while ((c = getopt (argc, argv, "k:m:")) != -1)
+ {
+ switch (c)
+ {
+ case 'k':
+ if (options)
+ free (options);
+ options = RCS_check_kflag (optarg);
+ break;
+
+ case 'm':
+ message = xstrdup (optarg);
+ break;
+ case '?':
+ default:
+ usage (add_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc <= 0)
+ usage (add_usage);
+
+ /* find the repository associated with our current dir */
+ repository = Name_Repository ((char *) NULL, (char *) NULL);
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ int i;
+ start_server ();
+ ign_setup ();
+ if (options) send_arg(options);
+ option_with_arg ("-m", message);
+ for (i = 0; i < argc; ++i)
+ /* FIXME: Does this erroneously call Create_Admin in error
+ conditions which are only detected once the server gets its
+ hands on things? */
+ if (isdir (argv[i]))
+ {
+ char *tag;
+ char *date;
+ char *rcsdir = xmalloc (strlen (repository)
+ + strlen (argv[i]) + 10);
+
+ /* before we do anything else, see if we have any
+ per-directory tags */
+ ParseTag (&tag, &date);
+
+ sprintf (rcsdir, "%s/%s", repository, argv[i]);
+
+ Create_Admin (argv[i], argv[i], rcsdir, tag, date);
+
+ if (tag)
+ free (tag);
+ if (date)
+ free (date);
+ free (rcsdir);
+ }
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ send_files (argc, argv, 0, 0);
+ send_to_server ("add\012", 0);
+ return get_responses_and_close ();
+ }
+#endif
+
+ entries = Entries_Open (0);
+
+ /* walk the arg list adding files/dirs */
+ for (i = 0; i < argc; i++)
+ {
+ int begin_err = err;
+ int begin_added_files = added_files;
+
+ user = argv[i];
+ strip_trailing_slashes (user);
+ if (strchr (user, '/') != NULL)
+ {
+ error (0, 0,
+ "cannot add files with '/' in their name; %s not added", user);
+ err++;
+ continue;
+ }
+
+ vers = Version_TS (repository, options, (char *) NULL, (char *) NULL,
+ user, 0, 0, entries, (RCSNode *) NULL);
+ if (vers->vn_user == NULL)
+ {
+ /* No entry available, ts_rcs is invalid */
+ if (vers->vn_rcs == NULL)
+ {
+ /* There is no RCS file either */
+ if (vers->ts_user == NULL)
+ {
+ /* There is no user file either */
+ error (0, 0, "nothing known about %s", user);
+ err++;
+ }
+ else if (!isdir (user) || wrap_name_has (user, WRAP_TOCVS))
+ {
+ /*
+ * See if a directory exists in the repository with
+ * the same name. If so, blow this request off.
+ */
+ char dname[PATH_MAX];
+ (void) sprintf (dname, "%s/%s", repository, user);
+ if (isdir (dname))
+ {
+ error (0, 0,
+ "cannot add file `%s' since the directory",
+ user);
+ error (0, 0, "`%s' already exists in the repository",
+ dname);
+ error (1, 0, "illegal filename overlap");
+ }
+
+ /* There is a user file, so build the entry for it */
+ if (build_entry (repository, user, vers->options,
+ message, entries, vers->tag) != 0)
+ err++;
+ else
+ {
+ added_files++;
+ if (!quiet)
+ {
+ if (vers->tag)
+ error (0, 0, "\
+scheduling %s `%s' for addition on branch `%s'",
+ (wrap_name_has (user, WRAP_TOCVS)
+ ? "wrapper"
+ : "file"),
+ user, vers->tag);
+ else
+ error (0, 0, "scheduling %s `%s' for addition",
+ (wrap_name_has (user, WRAP_TOCVS)
+ ? "wrapper"
+ : "file"),
+ user);
+ }
+ }
+ }
+ }
+ else if (RCS_isdead (vers->srcfile, vers->vn_rcs))
+ {
+ if (isdir (user) && !wrap_name_has (user, WRAP_TOCVS))
+ {
+ error (0, 0, "the directory `%s' cannot be added because a file of the", user);
+ error (1, 0, "same name already exists in the repository.");
+ }
+ else
+ {
+ if (vers->tag)
+ error (0, 0, "file `%s' will be added on branch `%s' from version %s",
+ user, vers->tag, vers->vn_rcs);
+ else
+ error (0, 0, "version %s of `%s' will be resurrected",
+ vers->vn_rcs, user);
+ Register (entries, user, "0", vers->ts_user, NULL,
+ vers->tag, NULL, NULL);
+ ++added_files;
+ }
+ }
+ else
+ {
+ /*
+ * There is an RCS file already, so somebody else must've
+ * added it
+ */
+ error (0, 0, "%s added independently by second party", user);
+ err++;
+ }
+ }
+ else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
+ {
+
+ /*
+ * An entry for a new-born file, ts_rcs is dummy, but that is
+ * inappropriate here
+ */
+ error (0, 0, "%s has already been entered", user);
+ err++;
+ }
+ else if (vers->vn_user[0] == '-')
+ {
+ /* An entry for a removed file, ts_rcs is invalid */
+ if (vers->ts_user == NULL)
+ {
+ /* There is no user file (as it should be) */
+ if (vers->vn_rcs == NULL)
+ {
+
+ /*
+ * There is no RCS file, so somebody else must've removed
+ * it from under us
+ */
+ error (0, 0,
+ "cannot resurrect %s; RCS file removed by second party", user);
+ err++;
+ }
+ else
+ {
+
+ /*
+ * There is an RCS file, so remove the "-" from the
+ * version number and restore the file
+ */
+ char *tmp = xmalloc (strlen (user) + 50);
+
+ (void) strcpy (tmp, vers->vn_user + 1);
+ (void) strcpy (vers->vn_user, tmp);
+ (void) sprintf (tmp, "Resurrected %s", user);
+ Register (entries, user, vers->vn_user, tmp, vers->options,
+ vers->tag, vers->date, vers->ts_conflict);
+ free (tmp);
+
+ /* XXX - bugs here; this really resurrect the head */
+ /* Note that this depends on the Register above actually
+ having written Entries, or else it won't really
+ check the file out. */
+ if (update (2, argv + i - 1) == 0)
+ {
+ error (0, 0, "%s, version %s, resurrected", user,
+ vers->vn_user);
+ }
+ else
+ {
+ error (0, 0, "could not resurrect %s", user);
+ err++;
+ }
+ }
+ }
+ else
+ {
+ /* The user file shouldn't be there */
+ error (0, 0, "%s should be removed and is still there (or is back again)", user);
+ err++;
+ }
+ }
+ else
+ {
+ /* A normal entry, ts_rcs is valid, so it must already be there */
+ error (0, 0, "%s already exists, with version number %s", user,
+ vers->vn_user);
+ err++;
+ }
+ freevers_ts (&vers);
+
+ /* passed all the checks. Go ahead and add it if its a directory */
+ if (begin_err == err
+ && isdir (user)
+ && !wrap_name_has (user, WRAP_TOCVS))
+ {
+ err += add_directory (repository, user);
+ continue;
+ }
+#ifdef SERVER_SUPPORT
+ if (server_active && begin_added_files != added_files)
+ server_checked_in (user, ".", repository);
+#endif
+ }
+ if (added_files)
+ error (0, 0, "use 'cvs commit' to add %s permanently",
+ (added_files == 1) ? "this file" : "these files");
+
+ Entries_Close (entries);
+
+ if (message)
+ free (message);
+
+ return (err);
+}
+
+/*
+ * The specified user file is really a directory. So, let's make sure that
+ * it is created in the RCS source repository, and that the user's directory
+ * is updated to include a CVS directory.
+ *
+ * Returns 1 on failure, 0 on success.
+ */
+static int
+add_directory (repository, dir)
+ char *repository;
+ char *dir;
+{
+ char rcsdir[PATH_MAX];
+ struct saved_cwd cwd;
+ char message[PATH_MAX + 100];
+ char *tag, *date;
+
+ if (strchr (dir, '/') != NULL)
+ {
+ error (0, 0,
+ "directory %s not added; must be a direct sub-directory", dir);
+ return (1);
+ }
+ if (strcmp (dir, CVSADM) == 0)
+ {
+ error (0, 0, "cannot add a `%s' directory", CVSADM);
+ return (1);
+ }
+
+ /* before we do anything else, see if we have any per-directory tags */
+ ParseTag (&tag, &date);
+
+ /* now, remember where we were, so we can get back */
+ if (save_cwd (&cwd))
+ return (1);
+ if (chdir (dir) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", dir);
+ return (1);
+ }
+#ifdef SERVER_SUPPORT
+ if (!server_active && isfile (CVSADM))
+#else
+ if (isfile (CVSADM))
+#endif
+ {
+ error (0, 0, "%s/%s already exists", dir, CVSADM);
+ goto out;
+ }
+
+ (void) sprintf (rcsdir, "%s/%s", repository, dir);
+ if (isfile (rcsdir) && !isdir (rcsdir))
+ {
+ error (0, 0, "%s is not a directory; %s not added", rcsdir, dir);
+ goto out;
+ }
+
+ /* setup the log message */
+ (void) sprintf (message, "Directory %s added to the repository\n", rcsdir);
+ if (tag)
+ {
+ (void) strcat (message, "--> Using per-directory sticky tag `");
+ (void) strcat (message, tag);
+ (void) strcat (message, "'\n");
+ }
+ if (date)
+ {
+ (void) strcat (message, "--> Using per-directory sticky date `");
+ (void) strcat (message, date);
+ (void) strcat (message, "'\n");
+ }
+
+ if (!isdir (rcsdir))
+ {
+ mode_t omask;
+ Node *p;
+ List *ulist;
+
+#if 0
+ char line[MAXLINELEN];
+
+ (void) printf ("Add directory %s to the repository (y/n) [n] ? ",
+ rcsdir);
+ (void) fflush (stdout);
+ clearerr (stdin);
+ if (fgets (line, sizeof (line), stdin) == NULL ||
+ (line[0] != 'y' && line[0] != 'Y'))
+ {
+ error (0, 0, "directory %s not added", rcsdir);
+ goto out;
+ }
+#endif
+
+ omask = umask (cvsumask);
+ if (CVS_MKDIR (rcsdir, 0777) < 0)
+ {
+ error (0, errno, "cannot mkdir %s", rcsdir);
+ (void) umask (omask);
+ goto out;
+ }
+ (void) umask (omask);
+
+ /*
+ * Set up an update list with a single title node for Update_Logfile
+ */
+ ulist = getlist ();
+ p = getnode ();
+ p->type = UPDATE;
+ p->delproc = update_delproc;
+ p->key = xstrdup ("- New directory");
+ p->data = (char *) T_TITLE;
+ (void) addnode (ulist, p);
+ Update_Logfile (rcsdir, message, (char *) NULL, (FILE *) NULL, ulist);
+ dellist (&ulist);
+ }
+
+#ifdef SERVER_SUPPORT
+ if (!server_active)
+ Create_Admin (".", dir, rcsdir, tag, date);
+#else
+ Create_Admin (".", dir, rcsdir, tag, date);
+#endif
+ if (tag)
+ free (tag);
+ if (date)
+ free (date);
+
+ (void) printf ("%s", message);
+out:
+ if (restore_cwd (&cwd, NULL))
+ exit (EXIT_FAILURE);
+ free_cwd (&cwd);
+ return (0);
+}
+
+/*
+ * Builds an entry for a new file and sets up "CVS/file",[pt] by
+ * interrogating the user. Returns non-zero on error.
+ */
+static int
+build_entry (repository, user, options, message, entries, tag)
+ char *repository;
+ char *user;
+ char *options;
+ char *message;
+ List *entries;
+ char *tag;
+{
+ char fname[PATH_MAX];
+ char line[MAXLINELEN];
+ FILE *fp;
+
+ if (noexec)
+ return (0);
+
+ /*
+ * The requested log is read directly from the user and stored in the
+ * file user,t. If the "message" argument is set, use it as the
+ * initial creation log (which typically describes the file).
+ */
+ (void) sprintf (fname, "%s/%s%s", CVSADM, user, CVSEXT_LOG);
+ fp = open_file (fname, "w+");
+ if (message && fputs (message, fp) == EOF)
+ error (1, errno, "cannot write to %s", fname);
+ if (fclose(fp) == EOF)
+ error(1, errno, "cannot close %s", fname);
+
+ /*
+ * Create the entry now, since this allows the user to interrupt us above
+ * without needing to clean anything up (well, we could clean up the
+ * ,t file, but who cares).
+ */
+ (void) sprintf (line, "Initial %s", user);
+ Register (entries, user, "0", line, options, tag, (char *) 0, (char *) 0);
+ return (0);
+}
diff --git a/contrib/cvs/src/admin.c b/contrib/cvs/src/admin.c
new file mode 100644
index 0000000..214318a
--- /dev/null
+++ b/contrib/cvs/src/admin.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Administration
+ *
+ * For now, this is basically a front end for rcs. All options are passed
+ * directly on.
+ */
+
+#include "cvs.h"
+#ifdef CVS_ADMIN_GROUP
+#include <grp.h>
+#endif
+
+static Dtype admin_dirproc PROTO((char *dir, char *repos, char *update_dir));
+static int admin_fileproc PROTO((struct file_info *finfo));
+
+static const char *const admin_usage[] =
+{
+ "Usage: %s %s rcs-options files...\n",
+ NULL
+};
+
+static int ac;
+static char **av;
+
+int
+admin (argc, argv)
+ int argc;
+ char **argv;
+{
+ int err;
+#ifdef CVS_ADMIN_GROUP
+ struct group *grp;
+#endif
+ if (argc <= 1)
+ usage (admin_usage);
+
+#ifdef CVS_ADMIN_GROUP
+ grp = getgrnam(CVS_ADMIN_GROUP);
+ /* skip usage right check if group CVS_ADMIN_GROUP does not exist */
+ if (grp != NULL)
+ {
+ char *me = getcaller();
+ char **grnam = grp->gr_mem;
+ int denied = 1;
+
+ while (*grnam)
+ {
+ if (strcmp(*grnam, me) == 0)
+ {
+ denied = 0;
+ break;
+ }
+ grnam++;
+ }
+
+ if (denied)
+ error (1, 0, "usage is restricted to members of the group %s",
+ CVS_ADMIN_GROUP);
+ }
+#endif
+
+ wrap_setup ();
+
+ /* skip all optional arguments to see if we have any file names */
+ for (ac = 1; ac < argc; ac++)
+ if (argv[ac][0] != '-')
+ break;
+ argc -= ac;
+ av = argv + 1;
+ argv += ac;
+ ac--;
+ if (ac == 0 || argc == 0)
+ usage (admin_usage);
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ int i;
+
+ /* We're the client side. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ for (i = 0; i <= ac; ++i) /* XXX send -ko too with i = 0 */
+ send_arg (av[i]);
+
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* FIXME: We shouldn't have to send current files, but I'm not sure
+ whether it works. So send the files --
+ it's slower but it works. */
+ send_files (argc, argv, 0, 0);
+ send_to_server ("admin\012", 0);
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ /* start the recursion processor */
+ err = start_recursion (admin_fileproc, (FILESDONEPROC) NULL, admin_dirproc,
+ (DIRLEAVEPROC) NULL, argc, argv, 0,
+ W_LOCAL, 0, 1, (char *) NULL, 1, 0);
+ return (err);
+}
+
+/*
+ * Called to run "rcs" on a particular file.
+ */
+/* ARGSUSED */
+static int
+admin_fileproc (finfo)
+ struct file_info *finfo;
+{
+ Vers_TS *vers;
+ char *version;
+ char **argv;
+ int argc;
+ int retcode = 0;
+ int status = 0;
+
+ vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL,
+ finfo->file, 0, 0, finfo->entries, finfo->rcs);
+
+ version = vers->vn_user;
+ if (version == NULL)
+ goto exitfunc;
+ else if (strcmp (version, "0") == 0)
+ {
+ error (0, 0, "cannot admin newly added file `%s'", finfo->file);
+ goto exitfunc;
+ }
+
+ run_setup ("%s%s -x,v/", Rcsbin, RCS);
+ for (argc = ac, argv = av; argc; argc--, argv++)
+ run_arg (*argv);
+ run_arg (vers->srcfile->path);
+ if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "%s failed for `%s'", RCS, finfo->file);
+ status = 1;
+ goto exitfunc;
+ }
+ exitfunc:
+ freevers_ts (&vers);
+ return status;
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+admin_dirproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "Administrating %s", update_dir);
+ return (R_PROCESS);
+}
diff --git a/contrib/cvs/src/checkin.c b/contrib/cvs/src/checkin.c
new file mode 100644
index 0000000..e1ce9cb
--- /dev/null
+++ b/contrib/cvs/src/checkin.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Check In
+ *
+ * Does a very careful checkin of the file "user", and tries not to spoil its
+ * modification time (to avoid needless recompilations). When RCS ID keywords
+ * get expanded on checkout, however, the modification time is updated and
+ * there is no good way to get around this.
+ *
+ * Returns non-zero on error.
+ */
+
+#include "cvs.h"
+#include "fileattr.h"
+#include "edit.h"
+
+int
+Checkin (type, file, update_dir, repository,
+ rcs, rev, tag, options, message, entries)
+ int type;
+ char *file;
+ char *update_dir;
+ char *repository;
+ char *rcs;
+ char *rev;
+ char *tag;
+ char *options;
+ char *message;
+ List *entries;
+{
+ char fname[PATH_MAX];
+ Vers_TS *vers;
+ int set_time;
+ char *fullname;
+
+ char *tocvsPath = NULL;
+
+ fullname = xmalloc (strlen (update_dir) + strlen (file) + 10);
+ if (update_dir[0] == '\0')
+ strcpy (fullname, file);
+ else
+ sprintf (fullname, "%s/%s", update_dir, file);
+
+ (void) printf ("Checking in %s;\n", fullname);
+ (void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, file);
+
+ /*
+ * Move the user file to a backup file, so as to preserve its
+ * modification times, then place a copy back in the original file name
+ * for the checkin and checkout.
+ */
+
+ tocvsPath = wrap_tocvs_process_file (fullname);
+
+ if (!noexec)
+ {
+ if (tocvsPath)
+ {
+ copy_file (tocvsPath, fname);
+ if (unlink_file_dir (file) < 0)
+ if (! existence_error (errno))
+ error (1, errno, "cannot remove %s", file);
+ copy_file (tocvsPath, file);
+ }
+ else
+ {
+ copy_file (file, fname);
+ }
+ }
+
+ switch (RCS_checkin (rcs, NULL, message, rev, 0, 0))
+ {
+ case 0: /* everything normal */
+
+ /*
+ * The checkin succeeded, so now check the new file back out and
+ * see if it matches exactly with the one we checked in. If it
+ * does, just move the original user file back, thus preserving
+ * the modes; otherwise, we have no recourse but to leave the
+ * newly checkout file as the user file and remove the old
+ * original user file.
+ */
+
+ if (strcmp (options, "-V4") == 0) /* upgrade to V5 now */
+ options[0] = '\0';
+
+ /* FIXME: should be checking for errors. */
+ (void) RCS_checkout (rcs, "", rev, options, RUN_TTY, 0, 0);
+
+ xchmod (file, 1);
+ if (xcmp (file, fname) == 0)
+ {
+ rename_file (fname, file);
+ /* the time was correct, so leave it alone */
+ set_time = 0;
+ }
+ else
+ {
+ if (unlink_file (fname) < 0)
+ error (0, errno, "cannot remove %s", fname);
+ /* sync up with the time from the RCS file */
+ set_time = 1;
+ }
+
+ wrap_fromcvs_process_file (file);
+
+ /*
+ * If we want read-only files, muck the permissions here, before
+ * getting the file time-stamp.
+ */
+ if (cvswrite == FALSE || fileattr_get (file, "_watched"))
+ xchmod (file, 0);
+
+ /* re-register with the new data */
+ vers = Version_TS (repository, (char *) NULL, tag, (char *) NULL,
+ file, 1, set_time, entries, (RCSNode *) NULL);
+ if (strcmp (vers->options, "-V4") == 0)
+ vers->options[0] = '\0';
+ Register (entries, file, vers->vn_rcs, vers->ts_user,
+ vers->options, vers->tag, vers->date, (char *) 0);
+ history_write (type, (char *) 0, vers->vn_rcs, file, repository);
+ freevers_ts (&vers);
+
+ if (tocvsPath)
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ break;
+
+ case -1: /* fork failed */
+ if (tocvsPath)
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ if (!noexec)
+ error (1, errno, "could not check in %s -- fork failed",
+ fullname);
+ return (1);
+
+ default: /* ci failed */
+
+ /*
+ * The checkin failed, for some unknown reason, so we restore the
+ * original user file, print an error, and return an error
+ */
+ if (tocvsPath)
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ if (!noexec)
+ {
+ rename_file (fname, file);
+ error (0, 0, "could not check in %s", fullname);
+ }
+ return (1);
+ }
+
+ /*
+ * When checking in a specific revision, we may have locked the wrong
+ * branch, so to be sure, we do an extra unlock here before
+ * returning.
+ */
+ if (rev)
+ {
+ (void) RCS_unlock (rcs, NULL, 1);
+ }
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ if (set_time)
+ /* Need to update the checked out file on the client side. */
+ server_updated (file, update_dir, repository, SERVER_UPDATED,
+ NULL, NULL);
+ else
+ server_checked_in (file, update_dir, repository);
+ }
+ else
+#endif
+ mark_up_to_date (file);
+
+ return (0);
+}
diff --git a/contrib/cvs/src/checkout.c b/contrib/cvs/src/checkout.c
new file mode 100644
index 0000000..64aa10e
--- /dev/null
+++ b/contrib/cvs/src/checkout.c
@@ -0,0 +1,889 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Create Version
+ *
+ * "checkout" creates a "version" of an RCS repository. This version is owned
+ * totally by the user and is actually an independent copy, to be dealt with
+ * as seen fit. Once "checkout" has been called in a given directory, it
+ * never needs to be called again. The user can keep up-to-date by calling
+ * "update" when he feels like it; this will supply him with a merge of his
+ * own modifications and the changes made in the RCS original. See "update"
+ * for details.
+ *
+ * "checkout" can be given a list of directories or files to be updated and in
+ * the case of a directory, will recursivley create any sub-directories that
+ * exist in the repository.
+ *
+ * When the user is satisfied with his own modifications, the present version
+ * can be committed by "commit"; this keeps the present version in tact,
+ * usually.
+ *
+ * The call is cvs checkout [options] <module-name>...
+ *
+ * "checkout" creates a directory ./CVS, in which it keeps its administration,
+ * in two files, Repository and Entries. The first contains the name of the
+ * repository. The second contains one line for each registered file,
+ * consisting of the version number it derives from, its time stamp at
+ * derivation time and its name. Both files are normal files and can be
+ * edited by the user, if necessary (when the repository is moved, e.g.)
+ */
+
+#include "cvs.h"
+
+static char *findslash PROTO((char *start, char *p));
+static int build_dirs_and_chdir PROTO((char *dir, char *prepath, char *realdir,
+ int sticky));
+static int checkout_proc PROTO((int *pargc, char **argv, char *where,
+ char *mwhere, char *mfile, int shorten,
+ int local_specified, char *omodule,
+ char *msg));
+static int safe_location PROTO((void));
+
+static const char *const checkout_usage[] =
+{
+ "Usage:\n %s %s [-ANPcflnps] [-r rev | -D date] [-d dir] [-k kopt] modules...\n",
+ "\t-A\tReset any sticky tags/date/kopts.\n",
+ "\t-N\tDon't shorten module paths if -d specified.\n",
+ "\t-P\tPrune empty directories.\n",
+ "\t-c\t\"cat\" the module database.\n",
+ "\t-f\tForce a head revision match if tag/date not found.\n",
+ "\t-l\tLocal directory only, not recursive\n",
+ "\t-n\tDo not run module program (if any).\n",
+ "\t-p\tCheck out files to standard output.\n",
+ "\t-s\tLike -c, but include module status.\n",
+ "\t-r rev\tCheck out revision or tag. (implies -P)\n",
+ "\t-D date\tCheck out revisions as of date. (implies -P)\n",
+ "\t-d dir\tCheck out into dir instead of module name.\n",
+ "\t-k kopt\tUse RCS kopt -k option on checkout.\n",
+ "\t-j rev\tMerge in changes made between current revision and rev.\n",
+ NULL
+};
+
+static const char *const export_usage[] =
+{
+ "Usage: %s %s [-NPfln] [-r rev | -D date] [-d dir] [-k kopt] module...\n",
+ "\t-N\tDon't shorten module paths if -d specified.\n",
+ "\t-f\tForce a head revision match if tag/date not found.\n",
+ "\t-l\tLocal directory only, not recursive\n",
+ "\t-n\tDo not run module program (if any).\n",
+ "\t-r rev\tCheck out revision or tag.\n",
+ "\t-D date\tCheck out revisions as of date.\n",
+ "\t-d dir\tCheck out into dir instead of module name.\n",
+ "\t-k kopt\tUse RCS kopt -k option on checkout.\n",
+ NULL
+};
+
+static int checkout_prune_dirs;
+static int force_tag_match = 1;
+static int pipeout;
+static int aflag;
+static char *options = NULL;
+static char *tag = NULL;
+static int tag_validated = 0;
+static char *date = NULL;
+static char *join_rev1 = NULL;
+static char *join_rev2 = NULL;
+static char *preload_update_dir = NULL;
+
+int
+checkout (argc, argv)
+ int argc;
+ char **argv;
+{
+ int i;
+ int c;
+ DBM *db;
+ int cat = 0, err = 0, status = 0;
+ int run_module_prog = 1;
+ int local = 0;
+ int shorten = -1;
+ char *where = NULL;
+ char *valid_options;
+ const char *const *valid_usage;
+ enum mtype m_type;
+
+ /*
+ * A smaller subset of options are allowed for the export command, which
+ * is essentially like checkout, except that it hard-codes certain
+ * options to be default (like -kv) and takes care to remove the CVS
+ * directory when it has done its duty
+ */
+ if (strcmp (command_name, "export") == 0)
+ {
+ m_type = EXPORT;
+ valid_options = "Nnk:d:flRQqr:D:";
+ valid_usage = export_usage;
+ }
+ else
+ {
+ m_type = CHECKOUT;
+ valid_options = "ANnk:d:flRpQqcsr:D:j:P";
+ valid_usage = checkout_usage;
+ }
+
+ if (argc == -1)
+ usage (valid_usage);
+
+ ign_setup ();
+ wrap_setup ();
+
+ optind = 1;
+ while ((c = getopt (argc, argv, valid_options)) != -1)
+ {
+ switch (c)
+ {
+ case 'A':
+ aflag = 1;
+ break;
+ case 'N':
+ shorten = 0;
+ break;
+ case 'k':
+ if (options)
+ free (options);
+ options = RCS_check_kflag (optarg);
+ break;
+ case 'n':
+ run_module_prog = 0;
+ break;
+ case 'Q':
+ case 'q':
+#ifdef SERVER_SUPPORT
+ /* The CVS 1.5 client sends these options (in addition to
+ Global_option requests), so we must ignore them. */
+ if (!server_active)
+#endif
+ error (1, 0,
+ "-q or -Q must be specified before \"%s\"",
+ command_name);
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'P':
+ checkout_prune_dirs = 1;
+ break;
+ case 'p':
+ pipeout = 1;
+ run_module_prog = 0; /* don't run module prog when piping */
+ noexec = 1; /* so no locks will be created */
+ break;
+ case 'c':
+ cat = 1;
+ break;
+ case 'd':
+ where = optarg;
+ if (shorten == -1)
+ shorten = 1;
+ break;
+ case 's':
+ status = 1;
+ break;
+ case 'f':
+ force_tag_match = 0;
+ break;
+ case 'r':
+ tag = optarg;
+ checkout_prune_dirs = 1;
+ break;
+ case 'D':
+ date = Make_Date (optarg);
+ checkout_prune_dirs = 1;
+ break;
+ case 'j':
+ if (join_rev2)
+ error (1, 0, "only two -j options can be specified");
+ if (join_rev1)
+ join_rev2 = optarg;
+ else
+ join_rev1 = optarg;
+ break;
+ case '?':
+ default:
+ usage (valid_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (shorten == -1)
+ shorten = 0;
+
+ if ((!(cat + status) && argc == 0) || ((cat + status) && argc != 0)
+ || (tag && date))
+ usage (valid_usage);
+
+ if (where && pipeout)
+ error (1, 0, "-d and -p are mutually exclusive");
+
+ if (strcmp (command_name, "export") == 0)
+ {
+ if (!tag && !date)
+ {
+ error (0, 0, "must specify a tag or date");
+ usage (valid_usage);
+ }
+ if (tag && isdigit (tag[0]))
+ error (1, 0, "tag `%s' must be a symbolic tag", tag);
+/*
+ * mhy 950615: -kv doesn't work for binaries with RCS keywords.
+ * Instead use the default provided in the RCS file (-ko for binaries).
+ */
+#if 0
+ if (!options)
+ options = RCS_check_kflag ("v");/* -kv is default */
+#endif
+ }
+
+ if (!safe_location()) {
+ error(1, 0, "Cannot check out files into the repository itself");
+ }
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ int expand_modules;
+
+ start_server ();
+
+ ign_setup ();
+
+ /* We have to expand names here because the "expand-modules"
+ directive to the server has the side-effect of having the
+ server send the check-in and update programs for the
+ various modules/dirs requested. If we turn this off and
+ simply request the names of the modules and directories (as
+ below in !expand_modules), those files (CVS/Checking.prog
+ or CVS/Update.prog) don't get created. Grrr. */
+
+ expand_modules = (!cat && !status && !pipeout
+ && supported_request ("expand-modules"));
+
+ if (expand_modules)
+ {
+ /* This is done here because we need to read responses
+ from the server before we send the command checkout or
+ export files. */
+
+ client_expand_modules (argc, argv, local);
+ }
+
+ if (!run_module_prog) send_arg ("-n");
+ if (local) send_arg ("-l");
+ if (pipeout) send_arg ("-p");
+ if (!force_tag_match) send_arg ("-f");
+ if (aflag)
+ send_arg("-A");
+ if (!shorten)
+ send_arg("-N");
+ if (checkout_prune_dirs && strcmp (command_name, "export") != 0)
+ send_arg("-P");
+ client_prune_dirs = checkout_prune_dirs;
+ if (cat)
+ send_arg("-c");
+ if (where != NULL)
+ {
+ option_with_arg ("-d", where);
+ }
+ if (status)
+ send_arg("-s");
+ if (strcmp (command_name, "export") != 0
+ && options != NULL
+ && options[0] != '\0')
+ send_arg (options);
+ option_with_arg ("-r", tag);
+ if (date)
+ client_senddate (date);
+ if (join_rev1 != NULL)
+ option_with_arg ("-j", join_rev1);
+ if (join_rev2 != NULL)
+ option_with_arg ("-j", join_rev2);
+
+ if (expand_modules)
+ {
+ client_send_expansions (local);
+ }
+ else
+ {
+ int i;
+ for (i = 0; i < argc; ++i)
+ send_arg (argv[i]);
+ client_nonexpanded_setup ();
+ }
+
+ send_to_server (strcmp (command_name, "export") == 0 ?
+ "export\012" : "co\012",
+ 0);
+
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ if (cat || status)
+ {
+ cat_module (status);
+ return (0);
+ }
+ db = open_module ();
+
+ /*
+ * if we have more than one argument and where was specified, we make the
+ * where, cd into it, and try to shorten names as much as possible.
+ * Otherwise, we pass the where as a single argument to do_module.
+ */
+ if (argc > 1 && where != NULL)
+ {
+ char repository[PATH_MAX];
+
+ (void) CVS_MKDIR (where, 0777);
+ if (chdir (where) < 0)
+ error (1, errno, "cannot chdir to %s", where);
+ preload_update_dir = xstrdup (where);
+ where = (char *) NULL;
+ if (!isfile (CVSADM))
+ {
+ (void) sprintf (repository, "%s/%s/%s", CVSroot, CVSROOTADM,
+ CVSNULLREPOS);
+ if (!isfile (repository))
+ {
+ mode_t omask;
+ omask = umask (cvsumask);
+ (void) CVS_MKDIR (repository, 0777);
+ (void) umask (omask);
+ }
+
+ /* I'm not sure whether this check is redundant. */
+ if (!isdir (repository))
+ error (1, 0, "there is no repository %s", repository);
+
+ Create_Admin (".", where, repository,
+ (char *) NULL, (char *) NULL);
+ if (!noexec)
+ {
+ FILE *fp;
+
+ fp = open_file (CVSADM_ENTSTAT, "w+");
+ if (fclose(fp) == EOF)
+ error(1, errno, "cannot close %s", CVSADM_ENTSTAT);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_entstat (preload_update_dir, repository);
+#endif
+ }
+ }
+ }
+
+ /*
+ * if where was specified (-d) and we have not taken care of it already
+ * with the multiple arg stuff, and it was not a simple directory name
+ * but rather a path, we strip off everything but the last component and
+ * attempt to cd to the indicated place. where then becomes simply the
+ * last component
+ */
+ if (where != NULL && strchr (where, '/') != NULL)
+ {
+ char *slash;
+
+ slash = strrchr (where, '/');
+ *slash = '\0';
+
+ if (chdir (where) < 0)
+ error (1, errno, "cannot chdir to %s", where);
+
+ preload_update_dir = xstrdup (where);
+
+ where = slash + 1;
+ if (*where == '\0')
+ where = NULL;
+ }
+
+ for (i = 0; i < argc; i++)
+ err += do_module (db, argv[i], m_type, "Updating", checkout_proc,
+ where, shorten, local, run_module_prog,
+ (char *) NULL);
+ close_module (db);
+ return (err);
+}
+
+static int
+safe_location ()
+{
+ char current[PATH_MAX];
+ char hardpath[PATH_MAX+5];
+ int x;
+
+ x = readlink(CVSroot, hardpath, sizeof hardpath - 1);
+ if (x == -1)
+ {
+ strcpy(hardpath, CVSroot);
+ }
+ else
+ {
+ hardpath[x] = '\0';
+ }
+ getwd (current);
+ if (strncmp(current, hardpath, strlen(hardpath)) == 0)
+ {
+ return (0);
+ }
+ return (1);
+}
+
+/*
+ * process_module calls us back here so we do the actual checkout stuff
+ */
+/* ARGSUSED */
+static int
+checkout_proc (pargc, argv, where, mwhere, mfile, shorten,
+ local_specified, omodule, msg)
+ int *pargc;
+ char **argv;
+ char *where;
+ char *mwhere;
+ char *mfile;
+ int shorten;
+ int local_specified;
+ char *omodule;
+ char *msg;
+{
+ int err = 0;
+ int which;
+ char *cp;
+ char *cp2;
+ char repository[PATH_MAX];
+ char xwhere[PATH_MAX];
+ char *oldupdate = NULL;
+ char *prepath;
+ char *realdirs;
+
+ /*
+ * OK, so we're doing the checkout! Our args are as follows:
+ * argc,argv contain either dir or dir followed by a list of files
+ * where contains where to put it (if supplied by checkout)
+ * mwhere contains the module name or -d from module file
+ * mfile says do only that part of the module
+ * shorten = TRUE says shorten as much as possible
+ * omodule is the original arg to do_module()
+ */
+
+ /* set up the repository (maybe) for the bottom directory */
+ (void) sprintf (repository, "%s/%s", CVSroot, argv[0]);
+
+ /* save the original value of preload_update_dir */
+ if (preload_update_dir != NULL)
+ oldupdate = xstrdup (preload_update_dir);
+
+ /* fix up argv[] for the case of partial modules */
+ if (mfile != NULL)
+ {
+ char file[PATH_MAX];
+
+ /* if mfile is really a path, straighten it out first */
+ if ((cp = strrchr (mfile, '/')) != NULL)
+ {
+ *cp = 0;
+ (void) strcat (repository, "/");
+ (void) strcat (repository, mfile);
+
+ /*
+ * Now we need to fill in the where correctly. if !shorten, tack
+ * the rest of the path onto where if where is filled in
+ * otherwise tack the rest of the path onto mwhere and make that
+ * the where
+ *
+ * If shorten is enabled, we might use mwhere to set where if
+ * nobody set it yet, so we'll need to setup mwhere as the last
+ * component of the path we are tacking onto repository
+ */
+ if (!shorten)
+ {
+ if (where != NULL)
+ (void) sprintf (xwhere, "%s/%s", where, mfile);
+ else
+ (void) sprintf (xwhere, "%s/%s", mwhere, mfile);
+ where = xwhere;
+ }
+ else
+ {
+ char *slash;
+
+ if ((slash = strrchr (mfile, '/')) != NULL)
+ mwhere = slash + 1;
+ else
+ mwhere = mfile;
+ }
+ mfile = cp + 1;
+ }
+
+ (void) sprintf (file, "%s/%s", repository, mfile);
+ if (isdir (file))
+ {
+
+ /*
+ * The portion of a module was a directory, so kludge up where to
+ * be the subdir, and fix up repository
+ */
+ (void) strcpy (repository, file);
+
+ /*
+ * At this point, if shorten is not enabled, we make where either
+ * where with mfile concatenated, or if where hadn't been set we
+ * set it to mwhere with mfile concatenated.
+ *
+ * If shorten is enabled and where hasn't been set yet, then where
+ * becomes mfile
+ */
+ if (!shorten)
+ {
+ if (where != NULL)
+ (void) sprintf (xwhere, "%s/%s", where, mfile);
+ else
+ (void) sprintf (xwhere, "%s/%s", mwhere, mfile);
+ where = xwhere;
+ }
+ else if (where == NULL)
+ where = mfile;
+ }
+ else
+ {
+ int i;
+
+ /*
+ * The portion of a module was a file, so kludge up argv to be
+ * correct
+ */
+ for (i = 1; i < *pargc; i++)/* free the old ones */
+ free (argv[i]);
+ argv[1] = xstrdup (mfile); /* set up the new one */
+ *pargc = 2;
+
+ /* where gets mwhere if where isn't set */
+ if (where == NULL)
+ where = mwhere;
+ }
+ }
+
+ /*
+ * if shorten is enabled and where isn't specified yet, we pluck the last
+ * directory component of argv[0] and make it the where
+ */
+ if (shorten && where == NULL)
+ {
+ if ((cp = strrchr (argv[0], '/')) != NULL)
+ {
+ (void) strcpy (xwhere, cp + 1);
+ where = xwhere;
+ }
+ }
+
+ /* if where is still NULL, use mwhere if set or the argv[0] dir */
+ if (where == NULL)
+ {
+ if (mwhere)
+ where = mwhere;
+ else
+ {
+ (void) strcpy (xwhere, argv[0]);
+ where = xwhere;
+ }
+ }
+
+ if (preload_update_dir != NULL)
+ {
+ char tmp[PATH_MAX];
+
+ (void) sprintf (tmp, "%s/%s", preload_update_dir, where);
+ free (preload_update_dir);
+ preload_update_dir = xstrdup (tmp);
+ }
+ else
+ preload_update_dir = xstrdup (where);
+
+ /*
+ * At this point, where is the directory we want to build, repository is
+ * the repository for the lowest level of the path.
+ */
+
+ /*
+ * If we are sending everything to stdout, we can skip a whole bunch of
+ * work from here
+ */
+ if (!pipeout)
+ {
+
+ /*
+ * We need to tell build_dirs not only the path we want it to build,
+ * but also the repositories we want it to populate the path with. To
+ * accomplish this, we pass build_dirs a ``real path'' with valid
+ * repositories and a string to pre-pend based on how many path
+ * elements exist in where. Big Black Magic
+ */
+ prepath = xstrdup (repository);
+ cp = strrchr (where, '/');
+ cp2 = strrchr (prepath, '/');
+ while (cp != NULL)
+ {
+ cp = findslash (where, cp - 1);
+ cp2 = findslash (prepath, cp2 - 1);
+ }
+ *cp2 = '\0';
+ realdirs = cp2 + 1;
+
+ /*
+ * build dirs on the path if necessary and leave us in the bottom
+ * directory (where if where was specified) doesn't contain a CVS
+ * subdir yet, but all the others contain CVS and Entries.Static
+ * files
+ */
+ if (build_dirs_and_chdir (where, prepath, realdirs, *pargc <= 1) != 0)
+ {
+ error (0, 0, "ignoring module %s", omodule);
+ free (prepath);
+ free (preload_update_dir);
+ preload_update_dir = oldupdate;
+ return (1);
+ }
+
+ /* clean up */
+ free (prepath);
+
+ /* set up the repository (or make sure the old one matches) */
+ if (!isfile (CVSADM))
+ {
+ FILE *fp;
+
+ if (!noexec && *pargc > 1)
+ {
+ /* I'm not sure whether this check is redundant. */
+ if (!isdir (repository))
+ error (1, 0, "there is no repository %s", repository);
+
+ Create_Admin (".", where, repository,
+ (char *) NULL, (char *) NULL);
+ fp = open_file (CVSADM_ENTSTAT, "w+");
+ if (fclose(fp) == EOF)
+ error(1, errno, "cannot close %s", CVSADM_ENTSTAT);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_entstat (where, repository);
+#endif
+ }
+ else
+ {
+ /* I'm not sure whether this check is redundant. */
+ if (!isdir (repository))
+ error (1, 0, "there is no repository %s", repository);
+
+ Create_Admin (".", where, repository, tag, date);
+ }
+ }
+ else
+ {
+ char *repos;
+
+ /* get the contents of the previously existing repository */
+ repos = Name_Repository ((char *) NULL, preload_update_dir);
+ if (fncmp (repository, repos) != 0)
+ {
+ error (0, 0, "existing repository %s does not match %s",
+ repos, repository);
+ error (0, 0, "ignoring module %s", omodule);
+ free (repos);
+ free (preload_update_dir);
+ preload_update_dir = oldupdate;
+ return (1);
+ }
+ free (repos);
+ }
+ }
+
+ /*
+ * If we are going to be updating to stdout, we need to cd to the
+ * repository directory so the recursion processor can use the current
+ * directory as the place to find repository information
+ */
+ if (pipeout)
+ {
+ if (chdir (repository) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", repository);
+ free (preload_update_dir);
+ preload_update_dir = oldupdate;
+ return (1);
+ }
+ which = W_REPOS;
+ if (tag != NULL && !tag_validated)
+ {
+ tag_check_valid (tag, *pargc - 1, argv + 1, 0, aflag, NULL);
+ tag_validated = 1;
+ }
+ }
+ else
+ {
+ which = W_LOCAL | W_REPOS;
+ if (tag != NULL && !tag_validated)
+ {
+ tag_check_valid (tag, *pargc - 1, argv + 1, 0, aflag,
+ repository);
+ tag_validated = 1;
+ }
+ }
+
+ if (tag != NULL || date != NULL)
+ which |= W_ATTIC;
+
+ /* FIXME: We don't call tag_check_valid on join_rev1 and join_rev2
+ yet (make sure to handle ':' correctly if we do, though). */
+
+ /*
+ * if we are going to be recursive (building dirs), go ahead and call the
+ * update recursion processor. We will be recursive unless either local
+ * only was specified, or we were passed arguments
+ */
+ if (!(local_specified || *pargc > 1))
+ {
+ if (strcmp (command_name, "export") != 0 && !pipeout)
+ history_write ('O', preload_update_dir, tag ? tag : date, where,
+ repository);
+ err += do_update (0, (char **) NULL, options, tag, date,
+ force_tag_match, 0 /* !local */ ,
+ 1 /* update -d */ , aflag, checkout_prune_dirs,
+ pipeout, which, join_rev1, join_rev2,
+ preload_update_dir);
+ free (preload_update_dir);
+ preload_update_dir = oldupdate;
+ return (err);
+ }
+
+ if (!pipeout)
+ {
+ int i;
+ List *entries;
+
+ /* we are only doing files, so register them */
+ entries = Entries_Open (0);
+ for (i = 1; i < *pargc; i++)
+ {
+ char *line;
+ char *user;
+ Vers_TS *vers;
+
+ user = argv[i];
+ vers = Version_TS (repository, options, tag, date, user,
+ force_tag_match, 0, entries, (RCSNode *) NULL);
+ if (vers->ts_user == NULL)
+ {
+ line = xmalloc (strlen (user) + 15);
+ (void) sprintf (line, "Initial %s", user);
+ Register (entries, user, vers->vn_rcs ? vers->vn_rcs : "0",
+ line, vers->options, vers->tag,
+ vers->date, (char *) 0);
+ free (line);
+ }
+ freevers_ts (&vers);
+ }
+
+ Entries_Close (entries);
+ }
+
+ /* Don't log "export", just regular "checkouts" */
+ if (strcmp (command_name, "export") != 0 && !pipeout)
+ history_write ('O', preload_update_dir, (tag ? tag : date), where,
+ repository);
+
+ /* go ahead and call update now that everything is set */
+ err += do_update (*pargc - 1, argv + 1, options, tag, date,
+ force_tag_match, local_specified, 1 /* update -d */,
+ aflag, checkout_prune_dirs, pipeout, which, join_rev1,
+ join_rev2, preload_update_dir);
+ free (preload_update_dir);
+ preload_update_dir = oldupdate;
+ return (err);
+}
+
+static char *
+findslash (start, p)
+ char *start;
+ char *p;
+{
+ while (p >= start && *p != '/')
+ p--;
+ if (p < start)
+ return (NULL);
+ else
+ return (p);
+}
+
+/*
+ * build all the dirs along the path to dir with CVS subdirs with appropriate
+ * repositories and Entries.Static files
+ */
+static int
+build_dirs_and_chdir (dir, prepath, realdir, sticky)
+ char *dir;
+ char *prepath;
+ char *realdir;
+ int sticky;
+{
+ FILE *fp;
+ char repository[PATH_MAX];
+ char path[PATH_MAX];
+ char path2[PATH_MAX];
+ char *slash;
+ char *slash2;
+ char *cp;
+ char *cp2;
+
+ (void) strcpy (path, dir);
+ (void) strcpy (path2, realdir);
+ for (cp = path, cp2 = path2;
+ (slash = strchr (cp, '/')) != NULL && (slash2 = strchr (cp2, '/')) != NULL;
+ cp = slash + 1, cp2 = slash2 + 1)
+ {
+ *slash = '\0';
+ *slash2 = '\0';
+ (void) CVS_MKDIR (cp, 0777);
+ if (chdir (cp) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", cp);
+ return (1);
+ }
+ if (!isfile (CVSADM) && strcmp (command_name, "export") != 0)
+ {
+ (void) sprintf (repository, "%s/%s", prepath, path2);
+ /* I'm not sure whether this check is redundant. */
+ if (!isdir (repository))
+ error (1, 0, "there is no repository %s", repository);
+ Create_Admin (".", path, repository, sticky ? (char *) NULL : tag,
+ sticky ? (char *) NULL : date);
+ if (!noexec)
+ {
+ fp = open_file (CVSADM_ENTSTAT, "w+");
+ if (fclose(fp) == EOF)
+ error(1, errno, "cannot close %s", CVSADM_ENTSTAT);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_entstat (path, repository);
+#endif
+ }
+ }
+ *slash = '/';
+ *slash2 = '/';
+ }
+ (void) CVS_MKDIR (cp, 0777);
+ if (chdir (cp) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", cp);
+ return (1);
+ }
+ return (0);
+}
diff --git a/contrib/cvs/src/classify.c b/contrib/cvs/src/classify.c
new file mode 100644
index 0000000..924314b
--- /dev/null
+++ b/contrib/cvs/src/classify.c
@@ -0,0 +1,493 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ */
+
+#include "cvs.h"
+
+#ifdef SERVER_SUPPORT
+static void sticky_ck PROTO((char *file, int aflag, Vers_TS * vers,
+ List * entries,
+ char *repository, char *update_dir));
+#else
+static void sticky_ck PROTO((char *file, int aflag, Vers_TS * vers, List * entries));
+#endif
+
+/*
+ * Classify the state of a file
+ */
+Ctype
+Classify_File (file, tag, date, options, force_tag_match, aflag, repository,
+ entries, rcsnode, versp, update_dir, pipeout)
+ char *file;
+ char *tag;
+ char *date;
+ char *options;
+ int force_tag_match;
+ int aflag;
+ char *repository;
+ List *entries;
+ RCSNode *rcsnode;
+ Vers_TS **versp;
+ char *update_dir;
+ int pipeout;
+{
+ Vers_TS *vers;
+ Ctype ret;
+ char *fullname;
+
+ fullname = xmalloc (strlen (update_dir) + strlen (file) + 10);
+ if (update_dir[0] == '\0')
+ strcpy (fullname, file);
+ else
+ sprintf (fullname, "%s/%s", update_dir, file);
+
+ /* get all kinds of good data about the file */
+ vers = Version_TS (repository, options, tag, date, file,
+ force_tag_match, 0, entries, rcsnode);
+
+ if (vers->vn_user == NULL)
+ {
+ /* No entry available, ts_rcs is invalid */
+ if (vers->vn_rcs == NULL)
+ {
+ /* there is no RCS file either */
+ if (vers->ts_user == NULL)
+ {
+ /* there is no user file */
+ if (!force_tag_match || !(vers->tag || vers->date))
+ if (!really_quiet)
+ error (0, 0, "nothing known about %s", fullname);
+ ret = T_UNKNOWN;
+ }
+ else
+ {
+ /* there is a user file */
+ if (!force_tag_match || !(vers->tag || vers->date))
+ if (!really_quiet)
+ error (0, 0, "use `cvs add' to create an entry for %s",
+ fullname);
+ ret = T_UNKNOWN;
+ }
+ }
+ else if (RCS_isdead (vers->srcfile, vers->vn_rcs))
+ {
+ if (vers->ts_user == NULL)
+ /*
+ * Logically seems to me this should be T_UPTODATE.
+ * But the joining code in update.c seems to expect
+ * T_CHECKOUT, and that is what has traditionally been
+ * returned for this case.
+ */
+ ret = T_CHECKOUT;
+ else
+ {
+ error (0, 0, "use `cvs add' to create an entry for %s",
+ fullname);
+ ret = T_UNKNOWN;
+ }
+ }
+ else
+ {
+ /* there is an rcs file */
+
+ if (vers->ts_user == NULL)
+ {
+ /* There is no user file; needs checkout */
+ ret = T_CHECKOUT;
+ }
+ else
+ {
+ if (pipeout)
+ {
+ /*
+ * The user file doesn't necessarily have anything
+ * to do with this.
+ */
+ ret = T_CHECKOUT;
+ }
+ /*
+ * There is a user file; print a warning and add it to the
+ * conflict list, only if it is indeed different from what we
+ * plan to extract
+ */
+ else if (No_Difference (file, vers, entries,
+ repository, update_dir))
+ {
+ /* the files were different so it is a conflict */
+ if (!really_quiet)
+ error (0, 0, "move away %s; it is in the way",
+ fullname);
+ ret = T_CONFLICT;
+ }
+ else
+ /* since there was no difference, still needs checkout */
+ ret = T_CHECKOUT;
+ }
+ }
+ }
+ else if (strcmp (vers->vn_user, "0") == 0)
+ {
+ /* An entry for a new-born file; ts_rcs is dummy */
+
+ if (vers->ts_user == NULL)
+ {
+ /*
+ * There is no user file, but there should be one; remove the
+ * entry
+ */
+ if (!really_quiet)
+ error (0, 0, "warning: new-born %s has disappeared", fullname);
+ ret = T_REMOVE_ENTRY;
+ }
+ else
+ {
+ /* There is a user file */
+
+ if (vers->vn_rcs == NULL)
+ /* There is no RCS file, added file */
+ ret = T_ADDED;
+ else if (RCS_isdead (vers->srcfile, vers->vn_rcs))
+ /* we are resurrecting. */
+ ret = T_ADDED;
+ else
+ {
+ if (vers->srcfile->flags & INATTIC
+ && vers->srcfile->flags & VALID)
+ {
+ /* This file has been added on some branch other than
+ the one we are looking at. In the branch we are
+ looking at, the file was already valid. */
+ if (!really_quiet)
+ error (0, 0,
+ "\
+conflict: %s has been added, but already exists",
+ fullname);
+ }
+ else
+ {
+ /*
+ * There is an RCS file, so someone else must have checked
+ * one in behind our back; conflict
+ */
+ if (!really_quiet)
+ error (0, 0,
+ "\
+conflict: %s created independently by second party",
+ fullname);
+ }
+ ret = T_CONFLICT;
+ }
+ }
+ }
+ else if (vers->vn_user[0] == '-')
+ {
+ /* An entry for a removed file, ts_rcs is invalid */
+
+ if (vers->ts_user == NULL)
+ {
+ char tmp[PATH_MAX];
+
+ /* There is no user file (as it should be) */
+
+ (void) sprintf (tmp, "-%s", vers->vn_rcs ? vers->vn_rcs : "");
+
+ if (vers->vn_rcs == NULL)
+ {
+
+ /*
+ * There is no RCS file; this is all-right, but it has been
+ * removed independently by a second party; remove the entry
+ */
+ ret = T_REMOVE_ENTRY;
+ }
+ else if (strcmp (tmp, vers->vn_user) == 0)
+
+ /*
+ * The RCS file is the same version as the user file was, and
+ * that's OK; remove it
+ */
+ ret = T_REMOVED;
+ else
+ {
+
+ /*
+ * The RCS file is a newer version than the removed user file
+ * and this is definitely not OK; make it a conflict.
+ */
+ if (!really_quiet)
+ error (0, 0,
+ "conflict: removed %s was modified by second party",
+ fullname);
+ ret = T_CONFLICT;
+ }
+ }
+ else
+ {
+ /* The user file shouldn't be there */
+ if (!really_quiet)
+ error (0, 0, "%s should be removed and is still there",
+ fullname);
+ ret = T_REMOVED;
+ }
+ }
+ else
+ {
+ /* A normal entry, TS_Rcs is valid */
+ if (vers->vn_rcs == NULL)
+ {
+ /* There is no RCS file */
+
+ if (vers->ts_user == NULL)
+ {
+ /* There is no user file, so just remove the entry */
+ if (!really_quiet)
+ error (0, 0, "warning: %s is not (any longer) pertinent",
+ fullname);
+ ret = T_REMOVE_ENTRY;
+ }
+ else if (strcmp (vers->ts_user, vers->ts_rcs) == 0)
+ {
+
+ /*
+ * The user file is still unmodified, so just remove it from
+ * the entry list
+ */
+ if (!really_quiet)
+ error (0, 0, "%s is no longer in the repository",
+ fullname);
+ ret = T_REMOVE_ENTRY;
+ }
+ else
+ {
+ /*
+ * The user file has been modified and since it is no longer
+ * in the repository, a conflict is raised
+ */
+ if (No_Difference (file, vers, entries,
+ repository, update_dir))
+ {
+ /* they are different -> conflict */
+ if (!really_quiet)
+ error (0, 0,
+ "conflict: %s is modified but no longer in the repository",
+ fullname);
+ ret = T_CONFLICT;
+ }
+ else
+ {
+ /* they weren't really different */
+ if (!really_quiet)
+ error (0, 0,
+ "warning: %s is not (any longer) pertinent",
+ fullname);
+ ret = T_REMOVE_ENTRY;
+ }
+ }
+ }
+ else if (strcmp (vers->vn_rcs, vers->vn_user) == 0)
+ {
+ /* The RCS file is the same version as the user file */
+
+ if (vers->ts_user == NULL)
+ {
+
+ /*
+ * There is no user file, so note that it was lost and
+ * extract a new version
+ */
+ if (strcmp (command_name, "update") == 0)
+ if (!really_quiet)
+ error (0, 0, "warning: %s was lost", fullname);
+ ret = T_CHECKOUT;
+ }
+ else if (strcmp (vers->ts_user, vers->ts_rcs) == 0)
+ {
+
+ /*
+ * The user file is still unmodified, so nothing special at
+ * all to do -- no lists updated, unless the sticky -k option
+ * has changed. If the sticky tag has changed, we just need
+ * to re-register the entry
+ */
+ if (vers->entdata->options &&
+ strcmp (vers->entdata->options, vers->options) != 0)
+ ret = T_CHECKOUT;
+ else
+ {
+#ifdef SERVER_SUPPORT
+ sticky_ck (file, aflag, vers, entries,
+ repository, update_dir);
+#else
+ sticky_ck (file, aflag, vers, entries);
+#endif
+ ret = T_UPTODATE;
+ }
+ }
+ else
+ {
+
+ /*
+ * The user file appears to have been modified, but we call
+ * No_Difference to verify that it really has been modified
+ */
+ if (No_Difference (file, vers, entries,
+ repository, update_dir))
+ {
+
+ /*
+ * they really are different; modified if we aren't
+ * changing any sticky -k options, else needs merge
+ */
+#ifdef XXX_FIXME_WHEN_RCSMERGE_IS_FIXED
+ if (strcmp (vers->entdata->options ?
+ vers->entdata->options : "", vers->options) == 0)
+ ret = T_MODIFIED;
+ else
+ ret = T_NEEDS_MERGE;
+#else
+ ret = T_MODIFIED;
+#ifdef SERVER_SUPPORT
+ sticky_ck (file, aflag, vers, entries,
+ repository, update_dir);
+#else
+ sticky_ck (file, aflag, vers, entries);
+#endif /* SERVER_SUPPORT */
+#endif
+ }
+ else
+ {
+ /* file has not changed; check out if -k changed */
+ if (strcmp (vers->entdata->options ?
+ vers->entdata->options : "", vers->options) != 0)
+ {
+ ret = T_CHECKOUT;
+ }
+ else
+ {
+
+ /*
+ * else -> note that No_Difference will Register the
+ * file already for us, using the new tag/date. This
+ * is the desired behaviour
+ */
+ ret = T_UPTODATE;
+ }
+ }
+ }
+ }
+ else
+ {
+ /* The RCS file is a newer version than the user file */
+
+ if (vers->ts_user == NULL)
+ {
+ /* There is no user file, so just get it */
+
+ if (strcmp (command_name, "update") == 0)
+ if (!really_quiet)
+ error (0, 0, "warning: %s was lost", fullname);
+ ret = T_CHECKOUT;
+ }
+ else if (strcmp (vers->ts_user, vers->ts_rcs) == 0)
+ {
+
+ /*
+ * The user file is still unmodified, so just get it as well
+ */
+#ifdef SERVER_SUPPORT
+ if (strcmp (vers->entdata->options ?
+ vers->entdata->options : "", vers->options) != 0
+ || (vers->srcfile != NULL
+ && (vers->srcfile->flags & INATTIC) != 0))
+ ret = T_CHECKOUT;
+ else
+ ret = T_PATCH;
+#else
+ ret = T_CHECKOUT;
+#endif
+ }
+ else
+ {
+ if (No_Difference (file, vers, entries,
+ repository, update_dir))
+ /* really modified, needs to merge */
+ ret = T_NEEDS_MERGE;
+#ifdef SERVER_SUPPORT
+ else if ((strcmp (vers->entdata->options ?
+ vers->entdata->options : "", vers->options)
+ != 0)
+ || (vers->srcfile != NULL
+ && (vers->srcfile->flags & INATTIC) != 0))
+ /* not really modified, check it out */
+ ret = T_CHECKOUT;
+ else
+ ret = T_PATCH;
+#else
+ else
+ /* not really modified, check it out */
+ ret = T_CHECKOUT;
+#endif
+ }
+ }
+ }
+
+ /* free up the vers struct, or just return it */
+ if (versp != (Vers_TS **) NULL)
+ *versp = vers;
+ else
+ freevers_ts (&vers);
+
+ free (fullname);
+
+ /* return the status of the file */
+ return (ret);
+}
+
+static void
+#ifdef SERVER_SUPPORT
+sticky_ck (file, aflag, vers, entries, repository, update_dir)
+#else
+sticky_ck (file, aflag, vers, entries)
+#endif
+ char *file;
+ int aflag;
+ Vers_TS *vers;
+ List *entries;
+#ifdef SERVER_SUPPORT
+ char *repository;
+ char *update_dir;
+#endif
+{
+ if (aflag || vers->tag || vers->date)
+ {
+ char *enttag = vers->entdata->tag;
+ char *entdate = vers->entdata->date;
+
+ if ((enttag && vers->tag && strcmp (enttag, vers->tag)) ||
+ ((enttag && !vers->tag) || (!enttag && vers->tag)) ||
+ (entdate && vers->date && strcmp (entdate, vers->date)) ||
+ ((entdate && !vers->date) || (!entdate && vers->date)))
+ {
+ Register (entries, file, vers->vn_user, vers->ts_rcs,
+ vers->options, vers->tag, vers->date, vers->ts_conflict);
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ /* We need to update the entries line on the client side.
+ It is possible we will later update it again via
+ server_updated or some such, but that is OK. */
+ server_update_entries
+ (file, update_dir, repository,
+ strcmp (vers->ts_rcs, vers->ts_user) == 0 ?
+ SERVER_UPDATED : SERVER_MERGED);
+ }
+#endif
+ }
+ }
+}
diff --git a/contrib/cvs/src/client.c b/contrib/cvs/src/client.c
new file mode 100644
index 0000000..c0557ce
--- /dev/null
+++ b/contrib/cvs/src/client.c
@@ -0,0 +1,4490 @@
+/* CVS client-related stuff. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include "cvs.h"
+#include "getline.h"
+#include "edit.h"
+
+#ifdef CLIENT_SUPPORT
+
+#include "md5.h"
+
+#if defined(AUTH_CLIENT_SUPPORT) || HAVE_KERBEROS || USE_DIRECT_TCP
+# ifdef HAVE_WINSOCK_H
+# include <winsock.h>
+# else /* No winsock.h */
+# include <sys/socket.h>
+# include <netinet/in.h>
+# include <netdb.h>
+# endif /* No winsock.h */
+#endif /* defined(AUTH_CLIENT_SUPPORT) || HAVE_KERBEROS || USE_DIRECT_TCP */
+
+#ifdef AUTH_CLIENT_SUPPORT
+char *get_cvs_password PROTO((char *user, char *host, char *cvsrooot));
+#endif /* AUTH_CLIENT_SUPPORT */
+
+#if HAVE_KERBEROS || USE_DIRECT_TCP
+#define CVS_PORT 1999
+
+#if HAVE_KERBEROS
+#include <krb.h>
+
+extern char *krb_realmofhost ();
+#ifndef HAVE_KRB_GET_ERR_TEXT
+#define krb_get_err_text(status) krb_err_txt[status]
+#endif /* HAVE_KRB_GET_ERR_TEXT */
+#endif /* HAVE_KERBEROS */
+
+#endif /* HAVE_KERBEROS || USE_DIRECT_TCP */
+
+static void add_prune_candidate PROTO((char *));
+
+/* All the commands. */
+int add PROTO((int argc, char **argv));
+int admin PROTO((int argc, char **argv));
+int checkout PROTO((int argc, char **argv));
+int commit PROTO((int argc, char **argv));
+int diff PROTO((int argc, char **argv));
+int history PROTO((int argc, char **argv));
+int import PROTO((int argc, char **argv));
+int cvslog PROTO((int argc, char **argv));
+int patch PROTO((int argc, char **argv));
+int release PROTO((int argc, char **argv));
+int cvsremove PROTO((int argc, char **argv));
+int rtag PROTO((int argc, char **argv));
+int status PROTO((int argc, char **argv));
+int tag PROTO((int argc, char **argv));
+int update PROTO((int argc, char **argv));
+
+/* All the response handling functions. */
+static void handle_ok PROTO((char *, int));
+static void handle_error PROTO((char *, int));
+static void handle_valid_requests PROTO((char *, int));
+static void handle_checked_in PROTO((char *, int));
+static void handle_new_entry PROTO((char *, int));
+static void handle_checksum PROTO((char *, int));
+static void handle_copy_file PROTO((char *, int));
+static void handle_updated PROTO((char *, int));
+static void handle_merged PROTO((char *, int));
+static void handle_patched PROTO((char *, int));
+static void handle_removed PROTO((char *, int));
+static void handle_remove_entry PROTO((char *, int));
+static void handle_set_static_directory PROTO((char *, int));
+static void handle_clear_static_directory PROTO((char *, int));
+static void handle_set_sticky PROTO((char *, int));
+static void handle_clear_sticky PROTO((char *, int));
+static void handle_set_checkin_prog PROTO((char *, int));
+static void handle_set_update_prog PROTO((char *, int));
+static void handle_module_expansion PROTO((char *, int));
+static void handle_m PROTO((char *, int));
+static void handle_e PROTO((char *, int));
+static void handle_notified PROTO((char *, int));
+
+static size_t try_read_from_server PROTO ((char *, size_t));
+#endif /* CLIENT_SUPPORT */
+
+#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT)
+
+/* Shared with server. */
+
+/*
+ * Return a malloc'd, '\0'-terminated string
+ * corresponding to the mode in SB.
+ */
+char *
+#ifdef __STDC__
+mode_to_string (mode_t mode)
+#else /* ! __STDC__ */
+mode_to_string (mode)
+ mode_t mode;
+#endif /* __STDC__ */
+{
+ char buf[18], u[4], g[4], o[4];
+ int i;
+
+ i = 0;
+ if (mode & S_IRUSR) u[i++] = 'r';
+ if (mode & S_IWUSR) u[i++] = 'w';
+ if (mode & S_IXUSR) u[i++] = 'x';
+ u[i] = '\0';
+
+ i = 0;
+ if (mode & S_IRGRP) g[i++] = 'r';
+ if (mode & S_IWGRP) g[i++] = 'w';
+ if (mode & S_IXGRP) g[i++] = 'x';
+ g[i] = '\0';
+
+ i = 0;
+ if (mode & S_IROTH) o[i++] = 'r';
+ if (mode & S_IWOTH) o[i++] = 'w';
+ if (mode & S_IXOTH) o[i++] = 'x';
+ o[i] = '\0';
+
+ sprintf(buf, "u=%s,g=%s,o=%s", u, g, o);
+ return xstrdup(buf);
+}
+
+/*
+ * Change mode of FILENAME to MODE_STRING.
+ * Returns 0 for success or errno code.
+ */
+int
+change_mode (filename, mode_string)
+ char *filename;
+ char *mode_string;
+{
+#ifdef CHMOD_BROKEN
+ char *p;
+ int writeable = 0;
+
+ /* We can only distinguish between
+ 1) readable
+ 2) writeable
+ 3) Picasso's "Blue Period"
+ We handle the first two. */
+ p = mode_string;
+ while (*p != '\0')
+ {
+ if ((p[0] == 'u' || p[0] == 'g' || p[0] == 'o') && p[1] == '=')
+ {
+ char *q = p + 2;
+ while (*q != ',' && *q != '\0')
+ {
+ if (*q == 'w')
+ writeable = 1;
+ ++q;
+ }
+ }
+ /* Skip to the next field. */
+ while (*p != ',' && *p != '\0')
+ ++p;
+ if (*p == ',')
+ ++p;
+ }
+
+ xchmod (filename, writeable);
+ return 0;
+
+#else /* ! CHMOD_BROKEN */
+
+ char *p;
+ mode_t mode = 0;
+
+ p = mode_string;
+ while (*p != '\0')
+ {
+ if ((p[0] == 'u' || p[0] == 'g' || p[0] == 'o') && p[1] == '=')
+ {
+ int can_read = 0, can_write = 0, can_execute = 0;
+ char *q = p + 2;
+ while (*q != ',' && *q != '\0')
+ {
+ if (*q == 'r')
+ can_read = 1;
+ else if (*q == 'w')
+ can_write = 1;
+ else if (*q == 'x')
+ can_execute = 1;
+ ++q;
+ }
+ if (p[0] == 'u')
+ {
+ if (can_read)
+ mode |= S_IRUSR;
+ if (can_write)
+ mode |= S_IWUSR;
+ if (can_execute)
+ mode |= S_IXUSR;
+ }
+ else if (p[0] == 'g')
+ {
+ if (can_read)
+ mode |= S_IRGRP;
+ if (can_write)
+ mode |= S_IWGRP;
+ if (can_execute)
+ mode |= S_IXGRP;
+ }
+ else if (p[0] == 'o')
+ {
+ if (can_read)
+ mode |= S_IROTH;
+ if (can_write)
+ mode |= S_IWOTH;
+ if (can_execute)
+ mode |= S_IXOTH;
+ }
+ }
+ /* Skip to the next field. */
+ while (*p != ',' && *p != '\0')
+ ++p;
+ if (*p == ',')
+ ++p;
+ }
+
+ if (chmod (filename, mode) < 0)
+ return errno;
+ return 0;
+#endif /* ! CHMOD_BROKEN */
+}
+
+#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */
+
+#ifdef CLIENT_SUPPORT
+
+/* The host part of CVSROOT. */
+static char *server_host;
+/* The user part of CVSROOT */
+static char *server_user;
+/* The repository part of CVSROOT. */
+static char *server_cvsroot;
+
+int client_active;
+
+int client_prune_dirs;
+
+static int cvsroot_parsed = 0;
+
+static List *ignlist = (List *) NULL;
+
+/* Set server_host and server_cvsroot. */
+static void
+parse_cvsroot ()
+{
+ char *p;
+#ifdef AUTH_CLIENT_SUPPORT
+ static char *access_method;
+#endif /* AUTH_CLIENT_SUPPORT */
+
+ /* Don't go through the trouble twice. */
+ if (cvsroot_parsed)
+ return;
+
+ server_host = xstrdup (CVSroot);
+
+#ifdef AUTH_CLIENT_SUPPORT
+ if ((server_host[0] == ':'))
+ {
+ /* Access method specified, as in
+ * "cvs -d :pserver:user@host:/path".
+ * We need to get past that part of CVSroot before parsing the
+ * rest of it.
+ */
+ access_method = p = &(server_host[1]);
+
+ if (! *access_method)
+ error (1, 0, "bad CVSroot: %s", CVSroot);
+
+ if (! *(p = strchr (access_method, ':')))
+ error (1, 0, "bad CVSroot: %s", CVSroot);
+
+ *p = '\0';
+ p++;
+
+ server_host = p;
+
+ if (! *server_host)
+ error (1, 0, "bad CVSroot: %s", CVSroot);
+
+ if (strcmp (access_method, "pserver") == 0)
+ use_authenticating_server = 1;
+ else
+ error (1, 0, "unknown access method: %s", access_method);
+ }
+#endif /* AUTH_CLIENT_SUPPORT */
+
+ /* First get just the pathname. */
+ server_cvsroot = strchr (server_host, ':');
+ *server_cvsroot = '\0';
+ ++server_cvsroot;
+
+ /* Then deal with host and possible user. */
+ if ( (p = strchr (server_host, '@')) == NULL)
+ {
+ server_user = NULL;
+ }
+ else
+ {
+ server_user = server_host;
+ server_host = p;
+ ++server_host;
+ *p = '\0';
+ }
+
+ client_active = 1;
+ cvsroot_parsed = 1;
+}
+
+#ifdef NO_SOCKET_TO_FD
+/* Under certain circumstances, we must communicate with the server
+ via a socket using send() and recv(). This is because under some
+ operating systems (OS/2 and Windows 95 come to mind), a socket
+ cannot be converted to a file descriptor -- it must be treated as a
+ socket and nothing else. */
+static int use_socket_style = 0;
+static int server_sock;
+#endif /* NO_SOCKET_TO_FD */
+
+/* Stream to write to the server. */
+static FILE *to_server;
+/* Stream to read from the server. */
+static FILE *from_server;
+
+/* We might want to log client/server traffic. */
+static FILE *from_server_logfile;
+static FILE *to_server_logfile;
+
+#if ! RSH_NOT_TRANSPARENT
+/* Process ID of rsh subprocess. */
+static int rsh_pid = -1;
+#endif /* ! RSH_NOT_TRANSPARENT */
+
+
+/*
+ * Read a line from the server. Result does not include the terminating \n.
+ *
+ * Space for the result is malloc'd and should be freed by the caller.
+ *
+ * Returns number of bytes read. If EOF_OK, then return 0 on end of file,
+ * else end of file is an error.
+ */
+static int
+read_line (resultp, eof_ok)
+ char **resultp;
+ int eof_ok;
+{
+ int c;
+ char *result;
+ size_t input_index = 0;
+ size_t result_size = 80;
+
+#ifdef NO_SOCKET_TO_FD
+ if (! use_socket_style)
+#endif /* NO_SOCKET_TO_FD */
+ fflush (to_server);
+
+ result = (char *) xmalloc (result_size);
+
+ while (1)
+ {
+
+#ifdef NO_SOCKET_TO_FD
+ if (use_socket_style)
+ {
+ char ch;
+ /* Yes, this sucks performance-wise. Short of implementing
+ our own buffering, I'm not sure how to effect a big
+ improvement. We could at least avoid calling
+ read_from_server() for each character if we were willing
+ to duplicate a lot of its code, but I'm not sure that's
+ worth it. */
+ read_from_server (&ch, 1);
+ c = ch;
+ }
+ else
+#endif /* NO_SOCKET_TO_FD */
+ c = getc (from_server);
+
+ if (c == EOF)
+ {
+ free (result);
+
+#ifdef NO_SOCKET_TO_FD
+ if (! use_socket_style)
+#endif /* NO_SOCKET_TO_FD */
+ if (ferror (from_server))
+ error (1, errno, "reading from server");
+
+ /* It's end of file. */
+ if (eof_ok)
+ return 0;
+ else
+ error (1, 0, "end of file from server (consult above messages if any)");
+ }
+
+ if (c == '\n')
+ break;
+
+ result[input_index++] = c;
+ while (input_index + 1 >= result_size)
+ {
+ result_size *= 2;
+ result = (char *) xrealloc (result, result_size);
+ }
+ }
+
+ if (resultp)
+ *resultp = result;
+
+ /* Terminate it just for kicks, but we *can* deal with embedded NULs. */
+ result[input_index] = '\0';
+
+#ifdef NO_SOCKET_TO_FD
+ if (! use_socket_style)
+#endif /* NO_SOCKET_TO_FD */
+ {
+ /*
+ * If we're using socket style, then everything has already
+ * been logged because read_from_server() was used to get the
+ * individual chars, and read_from_server() logs already.
+ */
+ if (from_server_logfile)
+ {
+ if (fwrite (result, 1, input_index, from_server_logfile)
+ < input_index)
+ error (0, errno, "writing to from-server logfile");
+ putc ('\n', from_server_logfile);
+ }
+ }
+
+ if (resultp == NULL)
+ free (result);
+ return input_index;
+}
+
+#endif /* CLIENT_SUPPORT */
+
+
+#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT)
+
+/*
+ * Zero if compression isn't supported or requested; non-zero to indicate
+ * a compression level to request from gzip.
+ */
+int gzip_level;
+
+int filter_through_gzip (fd, dir, level, pidp)
+ int fd, dir, level;
+ pid_t *pidp;
+{
+ static char buf[5] = "-";
+ static char *gzip_argv[3] = { "gzip", buf };
+
+ sprintf (buf+1, "%d", level);
+ return filter_stream_through_program (fd, dir, &gzip_argv[0], pidp);
+}
+
+int filter_through_gunzip (fd, dir, pidp)
+ int fd, dir;
+ pid_t *pidp;
+{
+ static char *gunzip_argv[3] = { "gunzip", "-d" };
+ return filter_stream_through_program (fd, dir, &gunzip_argv[0], pidp);
+}
+
+#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */
+
+#ifdef CLIENT_SUPPORT
+
+/*
+ * The Repository for the top level of this command (not necessarily
+ * the CVSROOT, just the current directory at the time we do it).
+ */
+static char *toplevel_repos;
+
+/* Working directory when we first started. */
+char toplevel_wd[PATH_MAX];
+
+static void
+handle_ok (args, len)
+ char *args;
+ int len;
+{
+ return;
+}
+
+static void
+handle_error (args, len)
+ char *args;
+ int len;
+{
+ int something_printed;
+
+ /*
+ * First there is a symbolic error code followed by a space, which
+ * we ignore.
+ */
+ char *p = strchr (args, ' ');
+ if (p == NULL)
+ {
+ error (0, 0, "invalid data from cvs server");
+ return;
+ }
+ ++p;
+ len -= p - args;
+ something_printed = 0;
+ for (; len > 0; --len)
+ {
+ something_printed = 1;
+ putc (*p++, stderr);
+ }
+ if (something_printed)
+ putc ('\n', stderr);
+}
+
+static void
+handle_valid_requests (args, len)
+ char *args;
+ int len;
+{
+ char *p = args;
+ char *q;
+ struct request *rq;
+ do
+ {
+ q = strchr (p, ' ');
+ if (q != NULL)
+ *q++ = '\0';
+ for (rq = requests; rq->name != NULL; ++rq)
+ {
+ if (strcmp (rq->name, p) == 0)
+ break;
+ }
+ if (rq->name == NULL)
+ /*
+ * It is a request we have never heard of (and thus never
+ * will want to use). So don't worry about it.
+ */
+ ;
+ else
+ {
+ if (rq->status == rq_enableme)
+ {
+ /*
+ * Server wants to know if we have this, to enable the
+ * feature.
+ */
+ send_to_server (rq->name, 0);
+ send_to_server ("\012", 0);
+
+ if (!strcmp("UseUnchanged",rq->name))
+ use_unchanged = 1;
+ }
+ else
+ rq->status = rq_supported;
+ }
+ p = q;
+ } while (q != NULL);
+ for (rq = requests; rq->name != NULL; ++rq)
+ {
+ if (rq->status == rq_essential)
+ error (1, 0, "request `%s' not supported by server", rq->name);
+ else if (rq->status == rq_optional)
+ rq->status = rq_not_supported;
+ }
+}
+
+static int use_directory = -1;
+
+static char *get_short_pathname PROTO((const char *));
+
+static char *
+get_short_pathname (name)
+ const char *name;
+{
+ const char *retval;
+ if (use_directory)
+ return (char *) name;
+ if (strncmp (name, toplevel_repos, strlen (toplevel_repos)) != 0)
+ error (1, 0, "server bug: name `%s' doesn't specify file in `%s'",
+ name, toplevel_repos);
+ retval = name + strlen (toplevel_repos) + 1;
+ if (retval[-1] != '/')
+ error (1, 0, "server bug: name `%s' doesn't specify file in `%s'",
+ name, toplevel_repos);
+ return (char *) retval;
+}
+
+/*
+ * Do all the processing for PATHNAME, where pathname consists of the
+ * repository and the filename. The parameters we pass to FUNC are:
+ * DATA is just the DATA parameter which was passed to
+ * call_in_directory; ENT_LIST is a pointer to an entries list (which
+ * we manage the storage for); SHORT_PATHNAME is the pathname of the
+ * file relative to the (overall) directory in which the command is
+ * taking place; and FILENAME is the filename portion only of
+ * SHORT_PATHNAME. When we call FUNC, the curent directory points to
+ * the directory portion of SHORT_PATHNAME. */
+
+static char *last_dir_name;
+
+static void
+call_in_directory (pathname, func, data)
+ char *pathname;
+ void (*func) PROTO((char *data, List *ent_list, char *short_pathname,
+ char *filename));
+ char *data;
+{
+ static List *last_entries;
+
+ char *dir_name;
+ char *filename;
+ /* Just the part of pathname relative to toplevel_repos. */
+ char *short_pathname = get_short_pathname (pathname);
+ char *p;
+
+ /*
+ * Do the whole descent in parallel for the repositories, so we
+ * know what to put in CVS/Repository files. I'm not sure the
+ * full hair is necessary since the server does a similar
+ * computation; I suspect that we only end up creating one
+ * directory at a time anyway.
+ *
+ * Also note that we must *only* worry about this stuff when we
+ * are creating directories; `cvs co foo/bar; cd foo/bar; cvs co
+ * CVSROOT; cvs update' is legitimate, but in this case
+ * foo/bar/CVSROOT/CVS/Repository is not a subdirectory of
+ * foo/bar/CVS/Repository.
+ */
+ char *reposname;
+ char *short_repos;
+ char *reposdirname;
+ char *rdirp;
+ int reposdirname_absolute;
+
+ reposname = NULL;
+ if (use_directory)
+ read_line (&reposname, 0);
+
+ reposdirname_absolute = 0;
+ if (reposname != NULL)
+ {
+ if (strncmp (reposname, toplevel_repos, strlen (toplevel_repos)) != 0)
+ {
+ reposdirname_absolute = 1;
+ short_repos = reposname;
+ }
+ else
+ {
+ short_repos = reposname + strlen (toplevel_repos) + 1;
+ if (short_repos[-1] != '/')
+ {
+ reposdirname_absolute = 1;
+ short_repos = reposname;
+ }
+ }
+ }
+ else
+ {
+ short_repos = short_pathname;
+ }
+ reposdirname = xstrdup (short_repos);
+ p = strrchr (reposdirname, '/');
+ if (p == NULL)
+ {
+ reposdirname = xrealloc (reposdirname, 2);
+ reposdirname[0] = '.'; reposdirname[1] = '\0';
+ }
+ else
+ *p = '\0';
+
+ dir_name = xstrdup (short_pathname);
+ p = strrchr (dir_name, '/');
+ if (p == NULL)
+ {
+ dir_name = xrealloc (dir_name, 2);
+ dir_name[0] = '.'; dir_name[1] = '\0';
+ }
+ else
+ *p = '\0';
+ if (client_prune_dirs)
+ add_prune_candidate (dir_name);
+
+ filename = strrchr (short_repos, '/');
+ if (filename == NULL)
+ filename = short_repos;
+ else
+ ++filename;
+
+ if (reposname != NULL)
+ {
+ /* This is the use_directory case. */
+
+ short_pathname = xmalloc (strlen (pathname) + strlen (filename) + 5);
+ strcpy (short_pathname, pathname);
+ strcat (short_pathname, filename);
+ }
+
+ if (last_dir_name == NULL
+ || strcmp (last_dir_name, dir_name) != 0)
+ {
+ if (last_dir_name)
+ free (last_dir_name);
+ last_dir_name = dir_name;
+
+ if (toplevel_wd[0] == '\0')
+ if (getwd (toplevel_wd) == NULL)
+ error (1, 0,
+ "could not get working directory: %s", toplevel_wd);
+
+ if (chdir (toplevel_wd) < 0)
+ error (1, errno, "could not chdir to %s", toplevel_wd);
+ if (chdir (dir_name) < 0)
+ {
+ char *dir;
+ char *dirp;
+
+ if (! existence_error (errno))
+ error (1, errno, "could not chdir to %s", dir_name);
+
+ /* Directory does not exist, we need to create it. */
+ dir = xmalloc (strlen (dir_name) + 1);
+ dirp = dir_name;
+ rdirp = reposdirname;
+
+ /* This algorithm makes nested directories one at a time
+ and create CVS administration files in them. For
+ example, we're checking out foo/bar/baz from the
+ repository:
+
+ 1) create foo, point CVS/Repository to <root>/foo
+ 2) .. foo/bar .. <root>/foo/bar
+ 3) .. foo/bar/baz .. <root>/foo/bar/baz
+
+ As you can see, we're just stepping along DIR_NAME (with
+ DIRP) and REPOSDIRNAME (with RDIRP) respectively.
+
+ We need to be careful when we are checking out a
+ module, however, since DIR_NAME and REPOSDIRNAME are not
+ going to be the same. Since modules will not have any
+ slashes in their names, we should watch the output of
+ STRCHR to decide whether or not we should use STRCHR on
+ the RDIRP. That is, if we're down to a module name,
+ don't keep picking apart the repository directory name. */
+
+ do
+ {
+ dirp = strchr (dirp, '/');
+ if (dirp)
+ {
+ strncpy (dir, dir_name, dirp - dir_name);
+ dir[dirp - dir_name] = '\0';
+ /* Skip the slash. */
+ ++dirp;
+ if (rdirp == NULL)
+ error (0, 0,
+ "internal error: repository string too short.");
+ else
+ rdirp = strchr (rdirp, '/');
+ }
+ else
+ {
+ /* If there are no more slashes in the dir name,
+ we're down to the most nested directory -OR- to
+ the name of a module. In the first case, we
+ should be down to a DIRP that has no slashes,
+ so it won't help/hurt to do another STRCHR call
+ on DIRP. It will definitely hurt, however, if
+ we're down to a module name, since a module
+ name can point to a nested directory (that is,
+ DIRP will still have slashes in it. Therefore,
+ we should set it to NULL so the routine below
+ copies the contents of REMOTEDIRNAME onto the
+ root repository directory (does this if rdirp
+ is set to NULL, because we used to do an extra
+ STRCHR call here). */
+
+ rdirp = NULL;
+ strcpy (dir, dir_name);
+ }
+
+ if (CVS_MKDIR (dir, 0777) < 0)
+ {
+ /* Now, let me get this straight. In IBM C/C++
+ * under OS/2, the error string for EEXIST is:
+ *
+ * "The file already exists",
+ *
+ * and the error string for EACCESS is:
+ *
+ * "The file or directory specified is read-only".
+ *
+ * Nonetheless, mkdir() will set EACCESS if the
+ * directory *exists*, according both to the
+ * documentation and its actual behavior.
+ *
+ * I'm sure that this made sense, to someone,
+ * somewhere, sometime. Just not me, here, now.
+ */
+#ifdef EACCESS
+ if ((errno != EACCESS) && (errno != EEXIST))
+ error (1, errno, "cannot make directory %s", dir);
+#else /* ! defined(EACCESS) */
+ if ((errno != EEXIST))
+ error (1, errno, "cannot make directory %s", dir);
+#endif /* defined(EACCESS) */
+
+ /* It already existed, fine. Just keep going. */
+ }
+ else if (strcmp (command_name, "export") == 0)
+ /* Don't create CVSADM directories if this is export. */
+ ;
+ else
+ {
+ /*
+ * Put repository in CVS/Repository. For historical
+ * (pre-CVS/Root) reasons, this is an absolute pathname,
+ * but what really matters is the part of it which is
+ * relative to cvsroot.
+ */
+ char *repo;
+ char *r;
+
+ repo = xmalloc (strlen (reposdirname)
+ + strlen (toplevel_repos)
+ + 80);
+ if (reposdirname_absolute)
+ r = repo;
+ else
+ {
+ strcpy (repo, toplevel_repos);
+ strcat (repo, "/");
+ r = repo + strlen (repo);
+ }
+
+ if (rdirp)
+ {
+ strncpy (r, reposdirname, rdirp - reposdirname);
+ r[rdirp - reposdirname] = '\0';
+ }
+ else
+ strcpy (r, reposdirname);
+
+ Create_Admin (dir, dir, repo,
+ (char *)NULL, (char *)NULL);
+ free (repo);
+ }
+
+ if (rdirp != NULL)
+ {
+ /* Skip the slash. */
+ ++rdirp;
+ }
+
+ } while (dirp != NULL);
+ free (dir);
+ /* Now it better work. */
+ if (chdir (dir_name) < 0)
+ error (1, errno, "could not chdir to %s", dir_name);
+ }
+
+ if (strcmp (command_name, "export") != 0)
+ {
+ if (last_entries)
+ Entries_Close (last_entries);
+ last_entries = Entries_Open (0);
+ }
+ }
+ else
+ free (dir_name);
+ free (reposdirname);
+ (*func) (data, last_entries, short_pathname, filename);
+ if (reposname != NULL)
+ {
+ free (short_pathname);
+ free (reposname);
+ }
+}
+
+static void
+copy_a_file (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ char *newname;
+#ifdef USE_VMS_FILENAMES
+ char *p;
+#endif
+
+ read_line (&newname, 0);
+
+#ifdef USE_VMS_FILENAMES
+ /* Mogrify the filename so VMS is happy with it. */
+ for(p = newname; *p; p++)
+ if(*p == '.' || *p == '#') *p = '_';
+#endif
+
+ copy_file (filename, newname);
+ free (newname);
+}
+
+static void
+handle_copy_file (args, len)
+ char *args;
+ int len;
+{
+ call_in_directory (args, copy_a_file, (char *)NULL);
+}
+
+
+static void read_counted_file PROTO ((char *, char *));
+
+/* Read from the server the count for the length of a file, then read
+ the contents of that file and write them to FILENAME. FULLNAME is
+ the name of the file for use in error messages. FIXME-someday:
+ extend this to deal with compressed files and make update_entries
+ use it. On error, gives a fatal error. */
+static void
+read_counted_file (filename, fullname)
+ char *filename;
+ char *fullname;
+{
+ char *size_string;
+ size_t size;
+ char *buf;
+
+ /* Pointers in buf to the place to put data which will be read,
+ and the data which needs to be written, respectively. */
+ char *pread;
+ char *pwrite;
+ /* Number of bytes left to read and number of bytes in buf waiting to
+ be written, respectively. */
+ size_t nread;
+ size_t nwrite;
+
+ FILE *fp;
+
+ read_line (&size_string, 0);
+ if (size_string[0] == 'z')
+ error (1, 0, "\
+protocol error: compressed files not supported for that operation");
+ /* FIXME: should be doing more error checking, probably. Like using
+ strtoul and making sure we used up the whole line. */
+ size = atoi (size_string);
+ free (size_string);
+
+ /* A more sophisticated implementation would use only a limited amount
+ of buffer space (8K perhaps), and read that much at a time. We allocate
+ a buffer for the whole file only to make it easy to keep track what
+ needs to be read and written. */
+ buf = xmalloc (size);
+
+ /* FIXME-someday: caller should pass in a flag saying whether it
+ is binary or not. I haven't carefully looked into whether
+ CVS/Template files should use local text file conventions or
+ not. */
+ fp = fopen (filename, "wb");
+ if (fp == NULL)
+ error (1, errno, "cannot write %s", fullname);
+ nread = size;
+ nwrite = 0;
+ pread = buf;
+ pwrite = buf;
+ while (nread > 0 || nwrite > 0)
+ {
+ size_t n;
+
+ if (nread > 0)
+ {
+ n = try_read_from_server (pread, nread);
+ nread -= n;
+ pread += n;
+ nwrite += n;
+ }
+
+ if (nwrite > 0)
+ {
+ n = fwrite (pwrite, 1, nwrite, fp);
+ if (ferror (fp))
+ error (1, errno, "cannot write %s", fullname);
+ nwrite -= n;
+ pwrite += n;
+ }
+ }
+ free (buf);
+ if (fclose (fp) < 0)
+ error (1, errno, "cannot close %s", fullname);
+}
+
+/*
+ * The Checksum response gives the checksum for the file transferred
+ * over by the next Updated, Merged or Patch response. We just store
+ * it here, and then check it in update_entries.
+ */
+
+static int stored_checksum_valid;
+static unsigned char stored_checksum[16];
+
+static void
+handle_checksum (args, len)
+ char *args;
+ int len;
+{
+ char *s;
+ char buf[3];
+ int i;
+
+ if (stored_checksum_valid)
+ error (1, 0, "Checksum received before last one was used");
+
+ s = args;
+ buf[2] = '\0';
+ for (i = 0; i < 16; i++)
+ {
+ char *bufend;
+
+ buf[0] = *s++;
+ buf[1] = *s++;
+ stored_checksum[i] = (char) strtol (buf, &bufend, 16);
+ if (bufend != buf + 2)
+ break;
+ }
+
+ if (i < 16 || *s != '\0')
+ error (1, 0, "Invalid Checksum response: `%s'", args);
+
+ stored_checksum_valid = 1;
+}
+
+static int stored_mode_valid;
+static char *stored_mode;
+
+static void handle_mode PROTO ((char *, int));
+
+static void
+handle_mode (args, len)
+ char *args;
+ int len;
+{
+ if (stored_mode_valid)
+ error (1, 0, "protocol error: duplicate Mode");
+ if (stored_mode != NULL)
+ free (stored_mode);
+ stored_mode = xstrdup (args);
+ stored_mode_valid = 1;
+}
+
+/*
+ * If we receive a patch, but the patch program fails to apply it, we
+ * want to request the original file. We keep a list of files whose
+ * patches have failed.
+ */
+
+char **failed_patches;
+int failed_patches_count;
+
+struct update_entries_data
+{
+ enum {
+ /*
+ * We are just getting an Entries line; the local file is
+ * correct.
+ */
+ UPDATE_ENTRIES_CHECKIN,
+ /* We are getting the file contents as well. */
+ UPDATE_ENTRIES_UPDATE,
+ /*
+ * We are getting a patch against the existing local file, not
+ * an entire new file.
+ */
+ UPDATE_ENTRIES_PATCH
+ } contents;
+
+ /*
+ * String to put in the timestamp field or NULL to use the timestamp
+ * of the file.
+ */
+ char *timestamp;
+};
+
+/* Update the Entries line for this file. */
+static void
+update_entries (data_arg, ent_list, short_pathname, filename)
+ char *data_arg;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ char *entries_line;
+ struct update_entries_data *data = (struct update_entries_data *)data_arg;
+
+ char *cp;
+ char *user;
+ char *vn;
+ /* Timestamp field. Always empty according to the protocol. */
+ char *ts;
+ char *options;
+ char *tag;
+ char *date;
+ char *tag_or_date;
+ char *scratch_entries;
+ int bin;
+
+ read_line (&entries_line, 0);
+
+ /*
+ * Parse the entries line.
+ */
+ if (strcmp (command_name, "export") != 0)
+ {
+ scratch_entries = xstrdup (entries_line);
+
+ if (scratch_entries[0] != '/')
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ user = scratch_entries + 1;
+ if ((cp = strchr (user, '/')) == NULL)
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ *cp++ = '\0';
+ vn = cp;
+ if ((cp = strchr (vn, '/')) == NULL)
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ *cp++ = '\0';
+
+ ts = cp;
+ if ((cp = strchr (ts, '/')) == NULL)
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ *cp++ = '\0';
+ options = cp;
+ if ((cp = strchr (options, '/')) == NULL)
+ error (1, 0, "bad entries line `%s' from server", entries_line);
+ *cp++ = '\0';
+ tag_or_date = cp;
+
+ /* If a slash ends the tag_or_date, ignore everything after it. */
+ cp = strchr (tag_or_date, '/');
+ if (cp != NULL)
+ *cp = '\0';
+ tag = (char *) NULL;
+ date = (char *) NULL;
+ if (*tag_or_date == 'T')
+ tag = tag_or_date + 1;
+ else if (*tag_or_date == 'D')
+ date = tag_or_date + 1;
+ }
+ else
+ /* For cvs export, assume it is a text file. FIXME: This is
+ broken behavior--we should be having the server tell us
+ whether it is text or binary and dealing accordingly. I
+ think maybe we can parse the entries line, get the options,
+ and then ignore the entries line otherwise, but I haven't
+ checked to see whether the server sends the entries line
+ correctly in this case. */
+ options = NULL;
+
+ if (data->contents == UPDATE_ENTRIES_UPDATE
+ || data->contents == UPDATE_ENTRIES_PATCH)
+ {
+ char *size_string;
+ char *mode_string;
+ int size;
+ int fd;
+ char *buf;
+ char *temp_filename;
+ int use_gzip, gzip_status;
+ pid_t gzip_pid = 0;
+
+ read_line (&mode_string, 0);
+
+ read_line (&size_string, 0);
+ if (size_string[0] == 'z')
+ {
+ use_gzip = 1;
+ size = atoi (size_string+1);
+ }
+ else
+ {
+ use_gzip = 0;
+ size = atoi (size_string);
+ }
+ free (size_string);
+
+ temp_filename = xmalloc (strlen (filename) + 80);
+#ifdef USE_VMS_FILENAMES
+ /* A VMS rename of "blah.dat" to "foo" to implies a
+ destination of "foo.dat" which is unfortinate for CVS */
+ sprintf (temp_filename, "%s_new_", filename);
+#else
+#ifdef _POSIX_NO_TRUNC
+ sprintf (temp_filename, ".new.%.9s", filename);
+#else /* _POSIX_NO_TRUNC */
+ sprintf (temp_filename, ".new.%s", filename);
+#endif /* _POSIX_NO_TRUNC */
+#endif /* USE_VMS_FILENAMES */
+ buf = xmalloc (size);
+
+ /* Some systems, like OS/2 and Windows NT, end lines with CRLF
+ instead of just LF. Format translation is done in the C
+ library I/O funtions. Here we tell them whether or not to
+ convert -- if this file is marked "binary" with the RCS -kb
+ flag, then we don't want to convert, else we do (because
+ CVS assumes text files by default). */
+
+ if (options)
+ bin = !(strcmp (options, "-kb"));
+ else
+ bin = 0;
+
+ fd = open (temp_filename,
+ O_WRONLY | O_CREAT | O_TRUNC | (bin ? OPEN_BINARY : 0),
+ 0777);
+
+ if (fd < 0)
+ error (1, errno, "writing %s", short_pathname);
+
+ if (use_gzip)
+ fd = filter_through_gunzip (fd, 0, &gzip_pid);
+
+ if (size > 0)
+ {
+ read_from_server (buf, size);
+
+ if (write (fd, buf, size) != size)
+ error (1, errno, "writing %s", short_pathname);
+ }
+
+ if (close (fd) < 0)
+ error (1, errno, "writing %s", short_pathname);
+ if (gzip_pid > 0)
+ {
+ if (waitpid (gzip_pid, &gzip_status, 0) == -1)
+ error (1, errno, "waiting for gzip process %ld",
+ (long) gzip_pid);
+ else if (gzip_status != 0)
+ error (1, 0, "gzip process exited %d", gzip_status);
+ }
+
+ gzip_pid = -1;
+
+ /* Since gunzip writes files without converting LF to CRLF
+ (a reasonable behavior), we now have a patch file in LF
+ format. Leave the file as is if we're just going to feed
+ it to patch; patch can handle it. However, if it's the
+ final source file, convert it. */
+
+ if (data->contents == UPDATE_ENTRIES_UPDATE)
+ {
+#ifdef LINES_CRLF_TERMINATED
+
+ /* `bin' is non-zero iff `options' contains "-kb", meaning
+ treat this file as binary. */
+
+ if (use_gzip && (! bin))
+ {
+ convert_file (temp_filename, O_RDONLY | OPEN_BINARY,
+ filename, O_WRONLY | O_CREAT | O_TRUNC);
+ if (unlink (temp_filename) < 0)
+ error (0, errno, "warning: couldn't delete %s",
+ temp_filename);
+ }
+ else
+ rename_file (temp_filename, filename);
+
+#else /* ! LINES_CRLF_TERMINATED */
+ rename_file (temp_filename, filename);
+#endif /* LINES_CRLF_TERMINATED */
+ }
+ else
+ {
+ int retcode;
+ char backup[PATH_MAX];
+ struct stat s;
+
+ (void) sprintf (backup, "%s~", filename);
+ (void) unlink_file (backup);
+ if (!isfile (filename))
+ error (1, 0, "patch original file %s does not exist",
+ short_pathname);
+ if (stat (temp_filename, &s) < 0)
+ error (1, 1, "can't stat patch file %s", temp_filename);
+ if (s.st_size == 0)
+ retcode = 0;
+ else
+ {
+ run_setup ("%s -f -s -b ~ %s %s", PATCH_PROGRAM,
+ filename, temp_filename);
+ retcode = run_exec (DEVNULL, RUN_TTY, RUN_TTY, RUN_NORMAL);
+ }
+ /* FIXME: should we really be silently ignoring errors? */
+ (void) unlink_file (temp_filename);
+ if (retcode == 0)
+ {
+ /* FIXME: should we really be silently ignoring errors? */
+ (void) unlink_file (backup);
+ }
+ else
+ {
+ int old_errno = errno;
+ char *path_tmp;
+
+ if (isfile (backup))
+ rename_file (backup, filename);
+
+ /* Get rid of the patch reject file. */
+ path_tmp = xmalloc (strlen (filename) + 10);
+ strcpy (path_tmp, filename);
+ strcat (path_tmp, ".rej");
+ /* FIXME: should we really be silently ignoring errors? */
+ (void) unlink_file (path_tmp);
+ free (path_tmp);
+
+ /* Save this file to retrieve later. */
+ failed_patches =
+ (char **) xrealloc ((char *) failed_patches,
+ ((failed_patches_count + 1)
+ * sizeof (char *)));
+ failed_patches[failed_patches_count] =
+ xstrdup (short_pathname);
+ ++failed_patches_count;
+
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
+ "could not patch %s%s", filename,
+ retcode == -1 ? "" : "; will refetch");
+
+ stored_checksum_valid = 0;
+
+ return;
+ }
+ }
+ free (temp_filename);
+
+ if (stored_checksum_valid)
+ {
+ FILE *e;
+ struct MD5Context context;
+ unsigned char buf[8192];
+ unsigned len;
+ unsigned char checksum[16];
+
+ /*
+ * Compute the MD5 checksum. This will normally only be
+ * used when receiving a patch, so we always compute it
+ * here on the final file, rather than on the received
+ * data.
+ *
+ * Note that if the file is a text file, we should read it
+ * here using text mode, so its lines will be terminated the same
+ * way they were transmitted.
+ */
+ e = fopen (filename, "r");
+ if (e == NULL)
+ error (1, errno, "could not open %s", short_pathname);
+
+ MD5Init (&context);
+ while ((len = fread (buf, 1, sizeof buf, e)) != 0)
+ MD5Update (&context, buf, len);
+ if (ferror (e))
+ error (1, errno, "could not read %s", short_pathname);
+ MD5Final (checksum, &context);
+
+ fclose (e);
+
+ stored_checksum_valid = 0;
+
+ if (memcmp (checksum, stored_checksum, 16) != 0)
+ {
+ if (data->contents != UPDATE_ENTRIES_PATCH)
+ error (1, 0, "checksum failure on %s",
+ short_pathname);
+
+ error (0, 0,
+ "checksum failure after patch to %s; will refetch",
+ short_pathname);
+
+ /* Save this file to retrieve later. */
+ failed_patches =
+ (char **) xrealloc ((char *) failed_patches,
+ ((failed_patches_count + 1)
+ * sizeof (char *)));
+ failed_patches[failed_patches_count] =
+ xstrdup (short_pathname);
+ ++failed_patches_count;
+
+ return;
+ }
+ }
+
+ {
+ /* FIXME: we should be respecting the umask. */
+ int status = change_mode (filename, mode_string);
+ if (status != 0)
+ error (0, status, "cannot change mode of %s", short_pathname);
+ }
+
+ free (mode_string);
+ free (buf);
+ }
+
+ if (stored_mode_valid)
+ change_mode (filename, stored_mode);
+ stored_mode_valid = 0;
+
+ /*
+ * Process the entries line. Do this after we've written the file,
+ * since we need the timestamp.
+ */
+ if (strcmp (command_name, "export") != 0)
+ {
+ char *local_timestamp;
+ char *file_timestamp;
+
+ local_timestamp = data->timestamp;
+ if (local_timestamp == NULL || ts[0] == '+')
+ file_timestamp = time_stamp (filename);
+ else
+ file_timestamp = NULL;
+
+ /*
+ * These special version numbers signify that it is not up to
+ * date. Create a dummy timestamp which will never compare
+ * equal to the timestamp of the file.
+ */
+ if (vn[0] == '\0' || vn[0] == '0' || vn[0] == '-')
+ local_timestamp = "dummy timestamp";
+ else if (local_timestamp == NULL)
+ {
+ local_timestamp = file_timestamp;
+ mark_up_to_date (filename);
+ }
+
+ Register (ent_list, filename, vn, local_timestamp,
+ options, tag, date, ts[0] == '+' ? file_timestamp : NULL);
+
+ if (file_timestamp)
+ free (file_timestamp);
+
+ free (scratch_entries);
+ }
+ free (entries_line);
+}
+
+static void
+handle_checked_in (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_CHECKIN;
+ dat.timestamp = NULL;
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+handle_new_entry (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_CHECKIN;
+ dat.timestamp = "dummy timestamp from new-entry";
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+handle_updated (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_UPDATE;
+ dat.timestamp = NULL;
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+handle_merged (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_UPDATE;
+ dat.timestamp = "Result of merge";
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+handle_patched (args, len)
+ char *args;
+ int len;
+{
+ struct update_entries_data dat;
+ dat.contents = UPDATE_ENTRIES_PATCH;
+ dat.timestamp = NULL;
+ call_in_directory (args, update_entries, (char *)&dat);
+}
+
+static void
+remove_entry (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ Scratch_Entry (ent_list, filename);
+}
+
+static void
+handle_remove_entry (args, len)
+ char *args;
+ int len;
+{
+ call_in_directory (args, remove_entry, (char *)NULL);
+}
+
+static void
+remove_entry_and_file (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ Scratch_Entry (ent_list, filename);
+ if (unlink_file (filename) < 0)
+ error (0, errno, "unable to remove %s", short_pathname);
+}
+
+static void
+handle_removed (args, len)
+ char *args;
+ int len;
+{
+ call_in_directory (args, remove_entry_and_file, (char *)NULL);
+}
+
+/* Is this the top level (directory containing CVSROOT)? */
+static int
+is_cvsroot_level (pathname)
+ char *pathname;
+{
+ char *short_pathname;
+
+ if (strcmp (toplevel_repos, server_cvsroot) != 0)
+ return 0;
+
+ if (!use_directory)
+ {
+ if (strncmp (pathname, server_cvsroot, strlen (server_cvsroot)) != 0)
+ error (1, 0,
+ "server bug: pathname `%s' doesn't specify file in `%s'",
+ pathname, server_cvsroot);
+ short_pathname = pathname + strlen (server_cvsroot) + 1;
+ if (short_pathname[-1] != '/')
+ error (1, 0,
+ "server bug: pathname `%s' doesn't specify file in `%s'",
+ pathname, server_cvsroot);
+ return strchr (short_pathname, '/') == NULL;
+ }
+ else
+ {
+ return strchr (pathname, '/') == NULL;
+ }
+}
+
+static void
+set_static (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ FILE *fp;
+ fp = open_file (CVSADM_ENTSTAT, "w+");
+ if (fclose (fp) == EOF)
+ error (1, errno, "cannot close %s", CVSADM_ENTSTAT);
+}
+
+static void
+handle_set_static_directory (args, len)
+ char *args;
+ int len;
+{
+ if (strcmp (command_name, "export") == 0)
+ {
+ /* Swallow the repository. */
+ read_line (NULL, 0);
+ return;
+ }
+ call_in_directory (args, set_static, (char *)NULL);
+}
+
+static void
+clear_static (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ if (unlink_file (CVSADM_ENTSTAT) < 0 && ! existence_error (errno))
+ error (1, errno, "cannot remove file %s", CVSADM_ENTSTAT);
+}
+
+static void
+handle_clear_static_directory (pathname, len)
+ char *pathname;
+ int len;
+{
+ if (strcmp (command_name, "export") == 0)
+ {
+ /* Swallow the repository. */
+ read_line (NULL, 0);
+ return;
+ }
+
+ if (is_cvsroot_level (pathname))
+ {
+ /*
+ * Top level (directory containing CVSROOT). This seems to normally
+ * lack a CVS directory, so don't try to create files in it.
+ */
+ return;
+ }
+ call_in_directory (pathname, clear_static, (char *)NULL);
+}
+
+static void
+set_sticky (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ char *tagspec;
+ FILE *f;
+
+ read_line (&tagspec, 0);
+ f = open_file (CVSADM_TAG, "w+");
+ if (fprintf (f, "%s\n", tagspec) < 0)
+ error (1, errno, "writing %s", CVSADM_TAG);
+ if (fclose (f) == EOF)
+ error (1, errno, "closing %s", CVSADM_TAG);
+ free (tagspec);
+}
+
+static void
+handle_set_sticky (pathname, len)
+ char *pathname;
+ int len;
+{
+ if (strcmp (command_name, "export") == 0)
+ {
+ /* Swallow the repository. */
+ read_line (NULL, 0);
+ /* Swallow the tag line. */
+ (void) read_line (NULL, 0);
+ return;
+ }
+ if (is_cvsroot_level (pathname))
+ {
+ /*
+ * Top level (directory containing CVSROOT). This seems to normally
+ * lack a CVS directory, so don't try to create files in it.
+ */
+
+ /* Swallow the repository. */
+ read_line (NULL, 0);
+ /* Swallow the tag line. */
+ (void) read_line (NULL, 0);
+ return;
+ }
+
+ call_in_directory (pathname, set_sticky, (char *)NULL);
+}
+
+static void
+clear_sticky (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ if (unlink_file (CVSADM_TAG) < 0 && ! existence_error (errno))
+ error (1, errno, "cannot remove %s", CVSADM_TAG);
+}
+
+static void
+handle_clear_sticky (pathname, len)
+ char *pathname;
+ int len;
+{
+ if (strcmp (command_name, "export") == 0)
+ {
+ /* Swallow the repository. */
+ read_line (NULL, 0);
+ return;
+ }
+
+ if (is_cvsroot_level (pathname))
+ {
+ /*
+ * Top level (directory containing CVSROOT). This seems to normally
+ * lack a CVS directory, so don't try to create files in it.
+ */
+ return;
+ }
+
+ call_in_directory (pathname, clear_sticky, (char *)NULL);
+}
+
+
+static void template PROTO ((char *, List *, char *, char *));
+
+static void
+template (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ /* FIXME: should be computing second argument from CVSADM_TEMPLATE
+ and short_pathname. */
+ read_counted_file (CVSADM_TEMPLATE, "<CVS/Template file>");
+}
+
+static void handle_template PROTO ((char *, int));
+
+static void
+handle_template (pathname, len)
+ char *pathname;
+ int len;
+{
+ call_in_directory (pathname, template, NULL);
+}
+
+
+struct save_prog {
+ char *name;
+ char *dir;
+ struct save_prog *next;
+};
+
+static struct save_prog *checkin_progs;
+static struct save_prog *update_progs;
+
+/*
+ * Unlike some responses this doesn't include the repository. So we can't
+ * just call call_in_directory and have the right thing happen; we save up
+ * the requests and do them at the end.
+ */
+static void
+handle_set_checkin_prog (args, len)
+ char *args;
+ int len;
+{
+ char *prog;
+ struct save_prog *p;
+ read_line (&prog, 0);
+ p = (struct save_prog *) xmalloc (sizeof (struct save_prog));
+ p->next = checkin_progs;
+ p->dir = xstrdup (args);
+ p->name = prog;
+ checkin_progs = p;
+}
+
+static void
+handle_set_update_prog (args, len)
+ char *args;
+ int len;
+{
+ char *prog;
+ struct save_prog *p;
+ read_line (&prog, 0);
+ p = (struct save_prog *) xmalloc (sizeof (struct save_prog));
+ p->next = update_progs;
+ p->dir = xstrdup (args);
+ p->name = prog;
+ update_progs = p;
+}
+
+static void do_deferred_progs PROTO((void));
+
+static void
+do_deferred_progs ()
+{
+ struct save_prog *p;
+ struct save_prog *q;
+
+ char fname[PATH_MAX];
+ FILE *f;
+ if (toplevel_wd[0] != '\0')
+ {
+ if (chdir (toplevel_wd) < 0)
+ error (1, errno, "could not chdir to %s", toplevel_wd);
+ }
+ for (p = checkin_progs; p != NULL; )
+ {
+ sprintf (fname, "%s/%s", p->dir, CVSADM_CIPROG);
+ f = open_file (fname, "w");
+ if (fprintf (f, "%s\n", p->name) < 0)
+ error (1, errno, "writing %s", fname);
+ if (fclose (f) == EOF)
+ error (1, errno, "closing %s", fname);
+ free (p->name);
+ free (p->dir);
+ q = p->next;
+ free (p);
+ p = q;
+ }
+ checkin_progs = NULL;
+ for (p = update_progs; p != NULL; p = p->next)
+ {
+ sprintf (fname, "%s/%s", p->dir, CVSADM_UPROG);
+ f = open_file (fname, "w");
+ if (fprintf (f, "%s\n", p->name) < 0)
+ error (1, errno, "writing %s", fname);
+ if (fclose (f) == EOF)
+ error (1, errno, "closing %s", fname);
+ free (p->name);
+ free (p->dir);
+ free (p);
+ }
+ update_progs = NULL;
+}
+
+static int client_isemptydir PROTO((char *));
+
+/*
+ * Returns 1 if the argument directory exists and is completely empty,
+ * other than the existence of the CVS directory entry. Zero otherwise.
+ */
+static int
+client_isemptydir (dir)
+ char *dir;
+{
+ DIR *dirp;
+ struct dirent *dp;
+
+ if ((dirp = opendir (dir)) == NULL)
+ {
+ if (! existence_error (errno))
+ error (0, errno, "cannot open directory %s for empty check", dir);
+ return (0);
+ }
+ errno = 0;
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (strcmp (dp->d_name, ".") != 0 && strcmp (dp->d_name, "..") != 0 &&
+ strcmp (dp->d_name, CVSADM) != 0)
+ {
+ (void) closedir (dirp);
+ return (0);
+ }
+ }
+ if (errno != 0)
+ {
+ error (0, errno, "cannot read directory %s", dir);
+ (void) closedir (dirp);
+ return (0);
+ }
+ (void) closedir (dirp);
+ return (1);
+}
+
+struct save_dir {
+ char *dir;
+ struct save_dir *next;
+};
+
+struct save_dir *prune_candidates;
+
+static void
+add_prune_candidate (dir)
+ char *dir;
+{
+ struct save_dir *p;
+
+ if (dir[0] == '.' && dir[1] == '\0')
+ return;
+ p = (struct save_dir *) xmalloc (sizeof (struct save_dir));
+ p->dir = xstrdup (dir);
+ p->next = prune_candidates;
+ prune_candidates = p;
+}
+
+static void process_prune_candidates PROTO((void));
+
+static void
+process_prune_candidates ()
+{
+ struct save_dir *p;
+ struct save_dir *q;
+
+ if (toplevel_wd[0] != '\0')
+ {
+ if (chdir (toplevel_wd) < 0)
+ error (1, errno, "could not chdir to %s", toplevel_wd);
+ }
+ for (p = prune_candidates; p != NULL; )
+ {
+ if (client_isemptydir (p->dir))
+ {
+ unlink_file_dir (p->dir);
+ }
+ free (p->dir);
+ q = p->next;
+ free (p);
+ p = q;
+ }
+}
+
+/* Send a Repository line. */
+
+static char *last_repos;
+static char *last_update_dir;
+
+static void send_repository PROTO((char *, char *, char *));
+
+static void
+send_repository (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ char *adm_name;
+
+ /* FIXME: this is probably not the best place to check; I wish I
+ * knew where in here's callers to really trap this bug. To
+ * reproduce the bug, just do this:
+ *
+ * mkdir junk
+ * cd junk
+ * cvs -d some_repos update foo
+ *
+ * Poof, CVS seg faults and dies! It's because it's trying to
+ * send a NULL string to the server but dies in send_to_server.
+ * That string was supposed to be the repository, but it doesn't
+ * get set because there's no CVSADM dir, and somehow it's not
+ * getting set from the -d argument either... ?
+ */
+ if (repos == NULL)
+ {
+ /* Lame error. I want a real fix but can't stay up to track
+ this down right now. */
+ error (1, 0, "no repository");
+ }
+
+ if (update_dir == NULL || update_dir[0] == '\0')
+ update_dir = ".";
+
+ if (last_repos != NULL
+ && strcmp (repos, last_repos) == 0
+ && last_update_dir != NULL
+ && strcmp (update_dir, last_update_dir) == 0)
+ /* We've already sent it. */
+ return;
+
+ if (client_prune_dirs)
+ add_prune_candidate (update_dir);
+
+ /* 80 is large enough for any of CVSADM_*. */
+ adm_name = xmalloc (strlen (dir) + 80);
+
+ if (use_directory == -1)
+ use_directory = supported_request ("Directory");
+
+ if (use_directory)
+ {
+ send_to_server ("Directory ", 0);
+ send_to_server (update_dir, 0);
+ send_to_server ("\012", 1);
+ send_to_server (repos, 0);
+ send_to_server ("\012", 1);
+ }
+ else
+ {
+ send_to_server ("Repository ", 0);
+ send_to_server (repos, 0);
+ send_to_server ("\012", 1);
+ }
+ if (supported_request ("Static-directory"))
+ {
+ adm_name[0] = '\0';
+ if (dir[0] != '\0')
+ {
+ strcat (adm_name, dir);
+ strcat (adm_name, "/");
+ }
+ strcat (adm_name, CVSADM_ENTSTAT);
+ if (isreadable (adm_name))
+ {
+ send_to_server ("Static-directory\012", 0);
+ }
+ }
+ if (supported_request ("Sticky"))
+ {
+ FILE *f;
+ if (dir[0] == '\0')
+ strcpy (adm_name, CVSADM_TAG);
+ else
+ sprintf (adm_name, "%s/%s", dir, CVSADM_TAG);
+
+ f = fopen (adm_name, "r");
+ if (f == NULL)
+ {
+ if (! existence_error (errno))
+ error (1, errno, "reading %s", adm_name);
+ }
+ else
+ {
+ char line[80];
+ char *nl;
+ send_to_server ("Sticky ", 0);
+ while (fgets (line, sizeof (line), f) != NULL)
+ {
+ send_to_server (line, 0);
+ nl = strchr (line, '\n');
+ if (nl != NULL)
+ break;
+ }
+ if (nl == NULL)
+ send_to_server ("\012", 1);
+ if (fclose (f) == EOF)
+ error (0, errno, "closing %s", adm_name);
+ }
+ }
+ if (supported_request ("Checkin-prog"))
+ {
+ FILE *f;
+ if (dir[0] == '\0')
+ strcpy (adm_name, CVSADM_CIPROG);
+ else
+ sprintf (adm_name, "%s/%s", dir, CVSADM_CIPROG);
+
+ f = fopen (adm_name, "r");
+ if (f == NULL)
+ {
+ if (! existence_error (errno))
+ error (1, errno, "reading %s", adm_name);
+ }
+ else
+ {
+ char line[80];
+ char *nl;
+
+ send_to_server ("Checkin-prog ", 0);
+
+ while (fgets (line, sizeof (line), f) != NULL)
+ {
+ send_to_server (line, 0);
+
+ nl = strchr (line, '\n');
+ if (nl != NULL)
+ break;
+ }
+ if (nl == NULL)
+ send_to_server ("\012", 1);
+ if (fclose (f) == EOF)
+ error (0, errno, "closing %s", adm_name);
+ }
+ }
+ if (supported_request ("Update-prog"))
+ {
+ FILE *f;
+ if (dir[0] == '\0')
+ strcpy (adm_name, CVSADM_UPROG);
+ else
+ sprintf (adm_name, "%s/%s", dir, CVSADM_UPROG);
+
+ f = fopen (adm_name, "r");
+ if (f == NULL)
+ {
+ if (! existence_error (errno))
+ error (1, errno, "reading %s", adm_name);
+ }
+ else
+ {
+ char line[80];
+ char *nl;
+
+ send_to_server ("Update-prog ", 0);
+
+ while (fgets (line, sizeof (line), f) != NULL)
+ {
+ send_to_server (line, 0);
+
+ nl = strchr (line, '\n');
+ if (nl != NULL)
+ break;
+ }
+ if (nl == NULL)
+ send_to_server ("\012", 1);
+ if (fclose (f) == EOF)
+ error (0, errno, "closing %s", adm_name);
+ }
+ }
+ free (adm_name);
+ if (last_repos != NULL)
+ free (last_repos);
+ if (last_update_dir != NULL)
+ free (last_update_dir);
+ last_repos = xstrdup (repos);
+ last_update_dir = xstrdup (update_dir);
+}
+
+/* Send a Repository line and set toplevel_repos. */
+static void send_a_repository PROTO((char *, char *, char *));
+
+static void
+send_a_repository (dir, repository, update_dir)
+ char *dir;
+ char *repository;
+ char *update_dir;
+{
+ if (toplevel_repos == NULL && repository != NULL)
+ {
+ if (update_dir[0] == '\0'
+ || (update_dir[0] == '.' && update_dir[1] == '\0'))
+ toplevel_repos = xstrdup (repository);
+ else
+ {
+ /*
+ * Get the repository from a CVS/Repository file if update_dir
+ * is absolute. This is not correct in general, because
+ * the CVS/Repository file might not be the top-level one.
+ * This is for cases like "cvs update /foo/bar" (I'm not
+ * sure it matters what toplevel_repos we get, but it does
+ * matter that we don't hit the "internal error" code below).
+ */
+ if (update_dir[0] == '/')
+ toplevel_repos = Name_Repository (update_dir, update_dir);
+ else
+ {
+ /*
+ * Guess the repository of that directory by looking at a
+ * subdirectory and removing as many pathname components
+ * as are in update_dir. I think that will always (or at
+ * least almost always) be 1.
+ *
+ * So this deals with directories which have been
+ * renamed, though it doesn't necessarily deal with
+ * directories which have been put inside other
+ * directories (and cvs invoked on the containing
+ * directory). I'm not sure the latter case needs to
+ * work.
+ */
+ /*
+ * This gets toplevel_repos wrong for "cvs update ../foo"
+ * but I'm not sure toplevel_repos matters in that case.
+ */
+ int slashes_in_update_dir;
+ int slashes_skipped;
+ char *p;
+
+ slashes_in_update_dir = 0;
+ for (p = update_dir; *p != '\0'; ++p)
+ if (*p == '/')
+ ++slashes_in_update_dir;
+
+ slashes_skipped = 0;
+ p = repository + strlen (repository);
+ while (1)
+ {
+ if (p == repository)
+ error (1, 0,
+ "internal error: not enough slashes in %s",
+ repository);
+ if (*p == '/')
+ ++slashes_skipped;
+ if (slashes_skipped < slashes_in_update_dir + 1)
+ --p;
+ else
+ break;
+ }
+ toplevel_repos = xmalloc (p - repository + 1);
+ /* Note that we don't copy the trailing '/'. */
+ strncpy (toplevel_repos, repository, p - repository);
+ toplevel_repos[p - repository] = '\0';
+ }
+ }
+ }
+
+ send_repository (dir, repository, update_dir);
+}
+
+/* The "expanded" modules. */
+static int modules_count;
+static int modules_allocated;
+static char **modules_vector;
+
+static void
+handle_module_expansion (args, len)
+ char *args;
+ int len;
+{
+ if (modules_vector == NULL)
+ {
+ modules_allocated = 1; /* Small for testing */
+ modules_vector = (char **) xmalloc
+ (modules_allocated * sizeof (modules_vector[0]));
+ }
+ else if (modules_count >= modules_allocated)
+ {
+ modules_allocated *= 2;
+ modules_vector = (char **) xrealloc
+ ((char *) modules_vector,
+ modules_allocated * sizeof (modules_vector[0]));
+ }
+ modules_vector[modules_count] = xmalloc (strlen (args) + 1);
+ strcpy (modules_vector[modules_count], args);
+ ++modules_count;
+}
+
+/* Original, not "expanded" modules. */
+static int module_argc;
+static char **module_argv;
+
+void
+client_expand_modules (argc, argv, local)
+ int argc;
+ char **argv;
+ int local;
+{
+ int errs;
+ int i;
+
+ module_argc = argc;
+ module_argv = (char **) xmalloc ((argc + 1) * sizeof (module_argv[0]));
+ for (i = 0; i < argc; ++i)
+ module_argv[i] = xstrdup (argv[i]);
+ module_argv[argc] = NULL;
+
+ for (i = 0; i < argc; ++i)
+ send_arg (argv[i]);
+ send_a_repository ("", server_cvsroot, "");
+
+ send_to_server ("expand-modules\012", 0);
+
+ errs = get_server_responses ();
+ if (last_repos != NULL)
+ free (last_repos);
+ last_repos = NULL;
+ if (last_update_dir != NULL)
+ free (last_update_dir);
+ last_update_dir = NULL;
+ if (errs)
+ error (errs, 0, "cannot expand modules");
+}
+
+void
+client_send_expansions (local)
+ int local;
+{
+ int i;
+ char *argv[1];
+
+ /* Send the original module names. The "expanded" module name might
+ not be suitable as an argument to a co request (e.g. it might be
+ the result of a -d argument in the modules file). It might be
+ cleaner if we genuinely expanded module names, all the way to a
+ local directory and repository, but that isn't the way it works
+ now. */
+ send_file_names (module_argc, module_argv, 0);
+
+ for (i = 0; i < modules_count; ++i)
+ {
+ argv[0] = modules_vector[i];
+ if (isfile (argv[0]))
+ send_files (1, argv, local, 0);
+ }
+ send_a_repository ("", server_cvsroot, "");
+}
+
+void
+client_nonexpanded_setup ()
+{
+ send_a_repository ("", server_cvsroot, "");
+}
+
+static void
+handle_m (args, len)
+ char *args;
+ int len;
+{
+ fwrite (args, len, sizeof (*args), stdout);
+ putc ('\n', stdout);
+}
+
+static void
+handle_e (args, len)
+ char *args;
+ int len;
+{
+ fwrite (args, len, sizeof (*args), stderr);
+ putc ('\n', stderr);
+}
+
+#endif /* CLIENT_SUPPORT */
+#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT)
+
+/* This table must be writeable if the server code is included. */
+struct response responses[] =
+{
+#ifdef CLIENT_SUPPORT
+#define RSP_LINE(n, f, t, s) {n, f, t, s}
+#else /* ! CLIENT_SUPPORT */
+#define RSP_LINE(n, f, t, s) {n, s}
+#endif /* CLIENT_SUPPORT */
+
+ RSP_LINE("ok", handle_ok, response_type_ok, rs_essential),
+ RSP_LINE("error", handle_error, response_type_error, rs_essential),
+ RSP_LINE("Valid-requests", handle_valid_requests, response_type_normal,
+ rs_essential),
+ RSP_LINE("Checked-in", handle_checked_in, response_type_normal,
+ rs_essential),
+ RSP_LINE("New-entry", handle_new_entry, response_type_normal, rs_optional),
+ RSP_LINE("Checksum", handle_checksum, response_type_normal, rs_optional),
+ RSP_LINE("Copy-file", handle_copy_file, response_type_normal, rs_optional),
+ RSP_LINE("Updated", handle_updated, response_type_normal, rs_essential),
+ RSP_LINE("Merged", handle_merged, response_type_normal, rs_essential),
+ RSP_LINE("Patched", handle_patched, response_type_normal, rs_optional),
+ RSP_LINE("Mode", handle_mode, response_type_normal, rs_optional),
+ RSP_LINE("Removed", handle_removed, response_type_normal, rs_essential),
+ RSP_LINE("Remove-entry", handle_remove_entry, response_type_normal,
+ rs_optional),
+ RSP_LINE("Set-static-directory", handle_set_static_directory,
+ response_type_normal,
+ rs_optional),
+ RSP_LINE("Clear-static-directory", handle_clear_static_directory,
+ response_type_normal,
+ rs_optional),
+ RSP_LINE("Set-sticky", handle_set_sticky, response_type_normal,
+ rs_optional),
+ RSP_LINE("Clear-sticky", handle_clear_sticky, response_type_normal,
+ rs_optional),
+ RSP_LINE("Template", handle_template, response_type_normal,
+ rs_optional),
+ RSP_LINE("Set-checkin-prog", handle_set_checkin_prog, response_type_normal,
+ rs_optional),
+ RSP_LINE("Set-update-prog", handle_set_update_prog, response_type_normal,
+ rs_optional),
+ RSP_LINE("Notified", handle_notified, response_type_normal, rs_optional),
+ RSP_LINE("Module-expansion", handle_module_expansion, response_type_normal,
+ rs_optional),
+ RSP_LINE("M", handle_m, response_type_normal, rs_essential),
+ RSP_LINE("E", handle_e, response_type_normal, rs_essential),
+ /* Possibly should be response_type_error. */
+ RSP_LINE(NULL, NULL, response_type_normal, rs_essential)
+
+#undef RSP_LINE
+};
+
+#endif /* CLIENT_SUPPORT or SERVER_SUPPORT */
+#ifdef CLIENT_SUPPORT
+
+/*
+ * If LEN is 0, then send_to_server() computes string's length itself.
+ *
+ * Therefore, pass the real length when transmitting data that might
+ * contain 0's.
+ */
+void
+send_to_server (str, len)
+ char *str;
+ size_t len;
+{
+ if (len == 0)
+ len = strlen (str);
+
+#ifdef NO_SOCKET_TO_FD
+ if (use_socket_style)
+ {
+ int just_wrtn = 0;
+ size_t wrtn = 0;
+
+#ifdef VMS
+ /* send() blocks under VMS */
+ if (send (server_sock, str + wrtn, len - wrtn, 0) < 0)
+ error (1, errno, "writing to server socket");
+#else /* VMS */
+ while (wrtn < len)
+ {
+ just_wrtn = send (server_sock, str + wrtn, len - wrtn, 0);
+
+ if (just_wrtn == -1)
+ error (1, errno, "writing to server socket");
+
+ wrtn += just_wrtn;
+ if (wrtn == len)
+ break;
+ }
+#endif /* VMS */
+ }
+ else
+#endif /* NO_SOCKET_TO_FD */
+ {
+ size_t wrtn = 0;
+
+ while (wrtn < len)
+ {
+ wrtn += fwrite (str + wrtn, 1, len - wrtn, to_server);
+
+ if (wrtn == len)
+ break;
+
+ if (ferror (to_server))
+ error (1, errno, "writing to server");
+ if (feof (to_server))
+ error (1, 0, "premature end-of-file on server");
+ }
+ }
+
+ if (to_server_logfile)
+ if (fwrite (str, 1, len, to_server_logfile) < len)
+ error (0, errno, "writing to to-server logfile");
+}
+
+/* Read up to LEN bytes from the server. Returns actual number of bytes
+ read. Gives a fatal error on EOF or error. */
+static size_t
+try_read_from_server (buf, len)
+ char *buf;
+ size_t len;
+{
+ int nread;
+
+#ifdef NO_SOCKET_TO_FD
+ if (use_socket_style)
+ {
+ nread = recv (server_sock, buf, len, 0);
+ if (nread == -1)
+ error (1, errno, "reading from server");
+ }
+ else
+#endif
+ {
+ nread = fread (buf, 1, len, from_server);
+ if (ferror (from_server))
+ error (1, errno, "reading from server");
+ if (feof (from_server))
+ error (1, 0,
+ "end of file from server (consult above messages if any)");
+ }
+
+ /* Log, if that's what we're doing. */
+ if (from_server_logfile != NULL && nread > 0)
+ {
+ size_t towrite = nread;
+ if (fwrite (buf, 1, towrite, from_server_logfile) < towrite)
+ error (0, errno, "writing to from-server logfile");
+ }
+
+ return nread;
+}
+
+/*
+ * Read LEN bytes from the server or die trying.
+ */
+void
+read_from_server (buf, len)
+ char *buf;
+ size_t len;
+{
+ size_t red = 0;
+ while (red < len)
+ {
+ red += try_read_from_server (buf + red, len - red);
+ if (red == len)
+ break;
+ }
+}
+
+/*
+ * Get some server responses and process them. Returns nonzero for
+ * error, 0 for success. */
+int
+get_server_responses ()
+{
+ struct response *rs;
+ do
+ {
+ char *cmd;
+ int len;
+
+ len = read_line (&cmd, 0);
+ for (rs = responses; rs->name != NULL; ++rs)
+ if (strncmp (cmd, rs->name, strlen (rs->name)) == 0)
+ {
+ int cmdlen = strlen (rs->name);
+ if (cmd[cmdlen] == '\0')
+ ;
+ else if (cmd[cmdlen] == ' ')
+ ++cmdlen;
+ else
+ /*
+ * The first len characters match, but it's a different
+ * response. e.g. the response is "oklahoma" but we
+ * matched "ok".
+ */
+ continue;
+ (*rs->func) (cmd + cmdlen, len - cmdlen);
+ break;
+ }
+ if (rs->name == NULL)
+ /* It's OK to print just to the first '\0'. */
+ error (0, 0,
+ "warning: unrecognized response `%s' from cvs server",
+ cmd);
+ free (cmd);
+ } while (rs->type == response_type_normal);
+ return rs->type == response_type_error ? 1 : 0;
+}
+
+/* Get the responses and then close the connection. */
+int server_fd = -1;
+
+/*
+ * Flag var; we'll set it in start_server() and not one of its
+ * callees, such as start_rsh_server(). This means that there might
+ * be a small window between the starting of the server and the
+ * setting of this var, but all the code in that window shouldn't care
+ * because it's busy checking return values to see if the server got
+ * started successfully anyway.
+ */
+int server_started = 0;
+
+int
+get_responses_and_close ()
+{
+ int errs = get_server_responses ();
+
+ do_deferred_progs ();
+
+ if (client_prune_dirs)
+ process_prune_candidates ();
+
+#ifdef NO_SOCKET_TO_FD
+ if (use_socket_style)
+ {
+ if (shutdown (server_sock, 2) < 0)
+ error (1, errno, "shutting down server socket");
+ }
+ else
+#endif /* NO_SOCKET_TO_FD */
+ {
+#if defined(HAVE_KERBEROS) || defined(USE_DIRECT_TCP) || defined(AUTH_CLIENT_SUPPORT)
+ if (server_fd != -1)
+ {
+ if (shutdown (server_fd, 1) < 0)
+ error (1, errno, "shutting down connection to %s", server_host);
+ /*
+ * This test will always be true because we dup the descriptor
+ */
+ if (fileno (from_server) != fileno (to_server))
+ {
+ if (fclose (to_server) != 0)
+ error (1, errno,
+ "closing down connection to %s",
+ server_host);
+ }
+ }
+ else
+#endif /* HAVE_KERBEROS || USE_DIRECT_TCP || AUTH_CLIENT_SUPPORT */
+
+#ifdef SHUTDOWN_SERVER
+ SHUTDOWN_SERVER (fileno (to_server));
+#else /* ! SHUTDOWN_SERVER */
+ {
+
+#ifdef START_RSH_WITH_POPEN_RW
+ if (pclose (to_server) == EOF)
+#else /* ! START_RSH_WITH_POPEN_RW */
+ if (fclose (to_server) == EOF)
+#endif /* START_RSH_WITH_POPEN_RW */
+ {
+ error (1, errno, "closing connection to %s", server_host);
+ }
+ }
+
+ if (getc (from_server) != EOF)
+ error (0, 0, "dying gasps from %s unexpected", server_host);
+ else if (ferror (from_server))
+ error (0, errno, "reading from %s", server_host);
+
+ fclose (from_server);
+#endif /* SHUTDOWN_SERVER */
+ }
+
+#if ! RSH_NOT_TRANSPARENT
+ if (rsh_pid != -1
+ && waitpid (rsh_pid, (int *) 0, 0) == -1)
+ error (1, errno, "waiting for process %d", rsh_pid);
+#endif /* ! RSH_NOT_TRANSPARENT */
+
+ server_started = 0;
+
+ return errs;
+}
+
+#ifndef RSH_NOT_TRANSPARENT
+static void start_rsh_server PROTO((int *, int *));
+#endif /* RSH_NOT_TRANSPARENT */
+
+int
+supported_request (name)
+ char *name;
+{
+ struct request *rq;
+
+ for (rq = requests; rq->name; rq++)
+ if (!strcmp (rq->name, name))
+ return rq->status == rq_supported;
+ error (1, 0, "internal error: testing support for unknown option?");
+ /* NOTREACHED */
+ return 0;
+}
+
+
+#ifdef AUTH_CLIENT_SUPPORT
+void
+init_sockaddr (name, hostname, port)
+ struct sockaddr_in *name;
+ const char *hostname;
+ unsigned short int port;
+{
+ struct hostent *hostinfo;
+
+ memset (name, 0, sizeof (*name));
+ name->sin_family = AF_INET;
+ name->sin_port = htons (port);
+ hostinfo = gethostbyname (hostname);
+ if (hostinfo == NULL)
+ {
+ fprintf (stderr, "Unknown host %s.\n", hostname);
+ exit (EXIT_FAILURE);
+ }
+ name->sin_addr = *(struct in_addr *) hostinfo->h_addr;
+}
+
+
+int
+auth_server_port_number ()
+{
+ return CVS_AUTH_PORT;
+}
+
+
+/*
+ * Connect to the authenticating server.
+ *
+ * If VERIFY_ONLY is non-zero, then just verify that the password is
+ * correct and then shutdown the connection. In this case, the return
+ * values is 1 if the password was correct, 0 if not.
+ *
+ * If VERIFY_ONLY is 0, then really connect to the server. In this
+ * case the return value is 1 on succees, but is probably ignored. If
+ * fail to connect, then die with error.
+ */
+int
+connect_to_pserver (tofdp, fromfdp, verify_only)
+ int *tofdp, *fromfdp;
+ int verify_only;
+{
+ int sock;
+#ifndef NO_SOCKET_TO_FD
+ int tofd, fromfd;
+#endif
+ int port_number;
+ struct sockaddr_in client_sai;
+
+ /* Does nothing if already called before now. */
+ parse_cvsroot ();
+
+ sock = socket (AF_INET, SOCK_STREAM, 0);
+ if (sock == -1)
+ {
+ fprintf (stderr, "socket() failed\n");
+ exit (EXIT_FAILURE);
+ }
+ port_number = auth_server_port_number ();
+ init_sockaddr (&client_sai, server_host, port_number);
+ if (connect (sock, (struct sockaddr *) &client_sai, sizeof (client_sai))
+ < 0)
+ error (1, errno, "connect to %s:%d failed", server_host,
+ CVS_AUTH_PORT);
+
+ /* Run the authorization mini-protocol before anything else. */
+ {
+ int i;
+ char ch, read_buf[PATH_MAX];
+ char *begin = NULL;
+ char *repository = server_cvsroot;
+ char *username = server_user;
+ char *password = NULL;
+ char *end = NULL;
+
+ if (verify_only)
+ {
+ begin = "BEGIN VERIFICATION REQUEST\n";
+ end = "END VERIFICATION REQUEST\n";
+ }
+ else
+ {
+ begin = "BEGIN AUTH REQUEST\n";
+ end = "END AUTH REQUEST\n";
+ }
+
+ /* Get the password, probably from ~/.cvspass. */
+ password = get_cvs_password (server_user, server_host, server_cvsroot);
+
+ /* Announce that we're starting the authorization protocol. */
+ send (sock, begin, strlen (begin), 0);
+
+ /* Send the data the server needs. */
+ send (sock, repository, strlen (repository), 0);
+ send (sock, "\n", 1, 0);
+ send (sock, username, strlen (username), 0);
+ send (sock, "\n", 1, 0);
+ send (sock, password, strlen (password), 0);
+ send (sock, "\n", 1, 0);
+
+ /* Announce that we're ending the authorization protocol. */
+ send (sock, end, strlen (end), 0);
+
+ /* Paranoia. */
+ memset (password, 0, strlen (password));
+
+ /* Get ACK or NACK from the server.
+ *
+ * We could avoid this careful read-char loop by having the ACK
+ * and NACK cookies be of the same length, so we'd simply read
+ * that length and see what we got. But then there'd be Yet
+ * Another Protocol Requirement floating around, and someday
+ * someone would make a change that breaks it and spend a hellish
+ * day tracking it down. Therefore, we use "\n" to mark off the
+ * end of both ACK and NACK, and we loop, reading until "\n".
+ */
+ ch = 0;
+ memset (read_buf, 0, PATH_MAX);
+ for (i = 0; (i < (PATH_MAX - 1)) && (ch != '\n'); i++)
+ {
+ if (recv (sock, &ch, 1, 0) < 0)
+ error (1, errno, "recv() from server %s", server_host);
+
+ read_buf[i] = ch;
+ }
+
+ if (strcmp (read_buf, "I HATE YOU\n") == 0)
+ {
+ /* Authorization not granted. */
+ if (shutdown (sock, 2) < 0)
+ {
+ error (0, 0,
+ "authorization failed: server %s rejected access",
+ server_host);
+ error (1, errno,
+ "shutdown() failed (server %s)", server_host);
+ }
+
+ if (verify_only)
+ return 0;
+ else
+ error (1, 0,
+ "authorization failed: server %s rejected access",
+ server_host);
+ }
+ else if (strcmp (read_buf, "I LOVE YOU\n") != 0)
+ {
+ /* Unrecognized response from server. */
+ if (shutdown (sock, 2) < 0)
+ {
+ error (0, 0,
+ "unrecognized auth response from %s: %s",
+ server_host, read_buf);
+ error (1, errno, "shutdown() failed, server %s", server_host);
+ }
+ error (1, 0,
+ "unrecognized auth response from %s: %s",
+ server_host, read_buf);
+ }
+ }
+
+ if (verify_only)
+ {
+ if (shutdown (sock, 2) < 0)
+ error (0, errno, "shutdown() failed, server %s", server_host);
+ return 1;
+ }
+ else
+ {
+#ifdef NO_SOCKET_TO_FD
+ use_socket_style = 1;
+ server_sock = sock;
+ /* Try to break mistaken callers: */
+ *tofdp = 0;
+ *fromfdp = 0;
+#else /* ! NO_SOCKET_TO_FD */
+ server_fd = sock;
+ close_on_exec (server_fd);
+ tofd = fromfd = sock;
+ /* Hand them back to the caller. */
+ *tofdp = tofd;
+ *fromfdp = fromfd;
+#endif /* NO_SOCKET_TO_FD */
+ }
+
+ return 1;
+}
+#endif /* AUTH_CLIENT_SUPPORT */
+
+
+#if HAVE_KERBEROS || USE_DIRECT_TCP
+
+/*
+ * FIXME: this function has not been changed to deal with
+ * NO_SOCKET_TO_FD (i.e., systems on which sockets cannot be converted
+ * to file descriptors. The first person to try building a kerberos
+ * client on such a system (OS/2, Windows 95, and maybe others) will
+ * have to make take care of this.
+ */
+void
+start_tcp_server (tofdp, fromfdp)
+ int *tofdp, *fromfdp;
+{
+ int tofd, fromfd;
+
+ struct hostent *hp;
+ char *hname;
+ const char *portenv;
+ int port;
+ struct sockaddr_in sin;
+ int s;
+
+
+#if HAVE_KERBEROS
+ KTEXT_ST ticket;
+ const char *realm;
+#endif /* HAVE_KERBEROS */
+
+ int status;
+
+ /*
+ * We look up the host to give a better error message if it
+ * does not exist. However, we then pass server_host to
+ * krb_sendauth, rather than the canonical name, because
+ * krb_sendauth is going to do its own canonicalization anyhow
+ * and that lets us not worry about the static storage used by
+ * gethostbyname.
+ */
+ hp = gethostbyname (server_host);
+ if (hp == NULL)
+ error (1, 0, "%s: unknown host", server_host);
+ hname = xmalloc (strlen (hp->h_name) + 1);
+ strcpy (hname, hp->h_name);
+
+#if HAVE_KERBEROS
+ realm = krb_realmofhost (hname);
+#endif /* HAVE_KERBEROS */
+
+ /* Get CVS_CLIENT_PORT or look up cvs/tcp with CVS_PORT as default */
+ portenv = getenv ("CVS_CLIENT_PORT");
+ if (portenv != NULL)
+ {
+ port = atoi (portenv);
+ if (port <= 0)
+ goto try_rsh_no_message;
+ if (trace)
+ fprintf(stderr, "Using TCP port %d to contact server.\n", port);
+ port = htons (port);
+ }
+ else
+ {
+ struct servent *sp;
+
+ sp = getservbyname ("cvs", "tcp");
+ if (sp == NULL)
+ port = htons (CVS_PORT);
+ else
+ port = sp->s_port;
+ }
+
+ s = socket (AF_INET, SOCK_STREAM, 0);
+ if (s < 0)
+ error (1, errno, "socket");
+
+ memset (&sin, 0, sizeof sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = INADDR_ANY;
+ sin.sin_port = 0;
+
+ if (bind (s, (struct sockaddr *) &sin, sizeof sin) < 0)
+ error (1, errno, "bind");
+
+ memcpy (&sin.sin_addr, hp->h_addr, hp->h_length);
+ sin.sin_port = port;
+
+ tofd = -1;
+ if (connect (s, (struct sockaddr *) &sin, sizeof sin) < 0)
+ {
+ error (0, errno, "connect");
+ close (s);
+ }
+ else
+ {
+#ifdef HAVE_KERBEROS
+ struct sockaddr_in laddr;
+ int laddrlen;
+ MSG_DAT msg_data;
+ CREDENTIALS cred;
+ Key_schedule sched;
+
+ laddrlen = sizeof (laddr);
+ if (getsockname (s, (struct sockaddr *) &laddr, &laddrlen) < 0)
+ error (1, errno, "getsockname");
+
+ /* We don't care about the checksum, and pass it as zero. */
+ status = krb_sendauth (KOPT_DO_MUTUAL, s, &ticket, "rcmd",
+ hname, realm, (unsigned long) 0, &msg_data,
+ &cred, sched, &laddr, &sin, "KCVSV1.0");
+ if (status != KSUCCESS)
+ {
+ error (0, 0, "kerberos: %s", krb_get_err_text(status));
+ close (s);
+ }
+ else
+ {
+#endif /* HAVE_KERBEROS */
+
+ server_fd = s;
+ close_on_exec (server_fd);
+ tofd = fromfd = s;
+
+#ifdef HAVE_KERBEROS
+ }
+#endif /* HAVE_KERBEROS */
+ }
+
+ if (tofd == -1)
+ {
+ /* FIXME: Falling back like this is slow and we should probably
+ just make it a fatal error (so that people use the right
+ environment variables or, when we get around to implementing
+ the right ones, access methods). */
+ error (0, 0, "trying to start server using rsh");
+ try_rsh_no_message:
+ server_fd = -1;
+#if ! RSH_NOT_TRANSPARENT
+ start_rsh_server (&tofd, &fromfd);
+#else /* RSH_NOT_TRANSPARENT */
+#if defined (START_SERVER)
+ START_SERVER (&tofd, &fromfd, getcaller (),
+ server_user, server_host, server_cvsroot);
+#endif /* defined (START_SERVER) */
+#endif /* ! RSH_NOT_TRANSPARENT */
+ }
+ free (hname);
+
+ /* Give caller the values it wants. */
+ *tofdp = tofd;
+ *fromfdp = fromfd;
+}
+
+#endif /* HAVE_KERBEROS || USE_DIRECT_TCP */
+
+static int send_variable_proc PROTO ((Node *, void *));
+
+static int
+send_variable_proc (node, closure)
+ Node *node;
+ void *closure;
+{
+ send_to_server ("Set ", 0);
+ send_to_server (node->key, 0);
+ send_to_server ("=", 1);
+ send_to_server (node->data, 0);
+ send_to_server ("\012", 1);
+ return 0;
+}
+
+/* Contact the server. */
+void
+start_server ()
+{
+ int tofd, fromfd;
+ char *log = getenv ("CVS_CLIENT_LOG");
+
+ /* Note that generally speaking we do *not* fall back to a different
+ way of connecting if the first one does not work. This is slow
+ (*really* slow on a 14.4kbps link); the clean way to have a CVS
+ which supports several ways of connecting is with access methods. */
+
+ /* Init these to NULL. They will be set later if logging is on. */
+ from_server_logfile = (FILE *) NULL;
+ to_server_logfile = (FILE *) NULL;
+
+#ifdef AUTH_CLIENT_SUPPORT
+ if (use_authenticating_server)
+ {
+ /* Toss the return value. It will die with error if anything
+ goes wrong anyway. */
+ connect_to_pserver (&tofd, &fromfd, 0);
+ }
+ else
+#endif /* AUTH_CLIENT_SUPPORT */
+ {
+#if HAVE_KERBEROS || USE_DIRECT_TCP
+ start_tcp_server (&tofd, &fromfd);
+#else
+
+# if ! RSH_NOT_TRANSPARENT
+ start_rsh_server (&tofd, &fromfd);
+# else
+
+# if defined(START_SERVER)
+ START_SERVER (&tofd, &fromfd, getcaller (),
+ server_user, server_host, server_cvsroot);
+# endif
+# endif
+#endif
+ }
+
+#if defined(VMS) && defined(NO_SOCKET_TO_FD)
+ /* Avoid mixing sockets with stdio */
+ use_socket_style = 1;
+ server_sock = tofd;
+#endif /* VMS && NO_SOCKET_TO_FD */
+
+ /* "Hi, I'm Darlene and I'll be your server tonight..." */
+ server_started = 1;
+
+ /* Set up logfiles, if any. */
+ if (log)
+ {
+ int len = strlen (log);
+ char *buf = xmalloc (len + 5);
+ char *p;
+
+ strcpy (buf, log);
+ p = buf + len;
+
+ strcpy (p, ".in");
+ to_server_logfile = open_file (buf, "w");
+ if (to_server_logfile == NULL)
+ error (0, errno, "opening to-server logfile %s", buf);
+
+ strcpy (p, ".out");
+ from_server_logfile = open_file (buf, "w");
+ if (from_server_logfile == NULL)
+ error (0, errno, "opening from-server logfile %s", buf);
+
+ free (buf);
+ }
+
+#ifdef NO_SOCKET_TO_FD
+ if (! use_socket_style)
+#endif /* NO_SOCKET_TO_FD */
+ {
+ /* todo: some OS's don't need these calls... */
+ close_on_exec (tofd);
+ close_on_exec (fromfd);
+
+ /* SCO 3 and AIX have a nasty bug in the I/O libraries which precludes
+ fdopening the same file descriptor twice, so dup it if it is the
+ same. */
+ if (tofd == fromfd)
+ {
+ fromfd = dup (tofd);
+ if (fromfd < 0)
+ error (1, errno, "cannot dup net connection");
+ }
+
+ /* These will use binary mode on systems which have it. */
+ to_server = fdopen (tofd, FOPEN_BINARY_WRITE);
+ if (to_server == NULL)
+ error (1, errno, "cannot fdopen %d for write", tofd);
+ from_server = fdopen (fromfd, FOPEN_BINARY_READ);
+ if (from_server == NULL)
+ error (1, errno, "cannot fdopen %d for read", fromfd);
+ }
+
+ /* Clear static variables. */
+ if (toplevel_repos != NULL)
+ free (toplevel_repos);
+ toplevel_repos = NULL;
+ if (last_dir_name != NULL)
+ free (last_dir_name);
+ last_dir_name = NULL;
+ if (last_repos != NULL)
+ free (last_repos);
+ last_repos = NULL;
+ if (last_update_dir != NULL)
+ free (last_update_dir);
+ last_update_dir = NULL;
+ stored_checksum_valid = 0;
+ stored_mode_valid = 0;
+
+ if (strcmp (command_name, "init") != 0)
+ {
+ send_to_server ("Root ", 0);
+ send_to_server (server_cvsroot, 0);
+ send_to_server ("\012", 1);
+ }
+
+ {
+ struct response *rs;
+
+ send_to_server ("Valid-responses", 0);
+
+ for (rs = responses; rs->name != NULL; ++rs)
+ {
+ send_to_server (" ", 0);
+ send_to_server (rs->name, 0);
+ }
+ send_to_server ("\012", 1);
+ }
+ send_to_server ("valid-requests\012", 0);
+
+ if (get_server_responses ())
+ exit (EXIT_FAILURE);
+
+ /*
+ * Now handle global options.
+ *
+ * -H, -f, -d, -e should be handled OK locally.
+ *
+ * -b we ignore (treating it as a server installation issue).
+ * FIXME: should be an error message.
+ *
+ * -v we print local version info; FIXME: Add a protocol request to get
+ * the version from the server so we can print that too.
+ *
+ * -l -t -r -w -q -n and -Q need to go to the server.
+ */
+
+ {
+ int have_global = supported_request ("Global_option");
+
+ if (noexec)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -n\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -n option.");
+ }
+ if (quiet)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -q\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -q option.");
+ }
+ if (really_quiet)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -Q\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -Q option.");
+ }
+ if (!cvswrite)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -r\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -r option.");
+ }
+ if (trace)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -t\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -t option.");
+ }
+ if (logoff)
+ {
+ if (have_global)
+ {
+ send_to_server ("Global_option -l\012", 0);
+ }
+ else
+ error (1, 0,
+ "This server does not support the global -l option.");
+ }
+ }
+ if (gzip_level)
+ {
+ if (supported_request ("gzip-file-contents"))
+ {
+ char gzip_level_buf[5];
+ send_to_server ("gzip-file-contents ", 0);
+ sprintf (gzip_level_buf, "%d", gzip_level);
+ send_to_server (gzip_level_buf, 0);
+
+ send_to_server ("\012", 1);
+ }
+ else
+ {
+ fprintf (stderr, "server doesn't support gzip-file-contents\n");
+ gzip_level = 0;
+ }
+ }
+
+#ifdef FILENAMES_CASE_INSENSITIVE
+ if (supported_request ("Case"))
+ send_to_server ("Case\012", 0);
+#endif
+
+ /* If "Set" is not supported, just silently fail to send the variables.
+ Users with an old server should get a useful error message when it
+ fails to recognize the ${=foo} syntax. This way if someone uses
+ several servers, some of which are new and some old, they can still
+ set user variables in their .cvsrc without trouble. */
+ if (supported_request ("Set"))
+ walklist (variable_list, send_variable_proc, NULL);
+}
+
+#ifndef RSH_NOT_TRANSPARENT
+/* Contact the server by starting it with rsh. */
+
+/* Right now, we have two different definitions for this function,
+ depending on whether we start the rsh server using popenRW or not.
+ This isn't ideal, and the best thing would probably be to change
+ the OS/2 port to be more like the regular Unix client (i.e., by
+ implementing piped_child)... but I'm doing something else at the
+ moment, and wish to make only one change at a time. -Karl */
+
+#ifdef START_RSH_WITH_POPEN_RW
+
+/* This is actually a crock -- it's OS/2-specific, for no one else
+ uses it. If I get time, I want to make piped_child and all the
+ other stuff in os2/run.c work right. In the meantime, this gets us
+ up and running, and that's most important. */
+
+static void
+start_rsh_server (tofdp, fromfdp)
+ int *tofdp, *fromfdp;
+{
+ int pipes[2];
+
+ /* If you're working through firewalls, you can set the
+ CVS_RSH environment variable to a script which uses rsh to
+ invoke another rsh on a proxy machine. */
+ char *cvs_rsh = getenv ("CVS_RSH");
+ char *cvs_server = getenv ("CVS_SERVER");
+ char command[PATH_MAX];
+ int i = 0;
+ /* This needs to fit "rsh", "-b", "-l", "USER", "host",
+ "cmd (w/ args)", and NULL. We leave some room to grow. */
+ char *rsh_argv[10];
+
+ if (!cvs_rsh)
+ cvs_rsh = "rsh";
+ if (!cvs_server)
+ cvs_server = "cvs";
+
+ /* If you are running a very old (Nov 3, 1994, before 1.5)
+ * version of the server, you need to make sure that your .bashrc
+ * on the server machine does not set CVSROOT to something
+ * containing a colon (or better yet, upgrade the server). */
+
+ /* The command line starts out with rsh. */
+ rsh_argv[i++] = cvs_rsh;
+
+#ifdef RSH_NEEDS_BINARY_FLAG
+ /* "-b" for binary, under OS/2. */
+ rsh_argv[i++] = "-b";
+#endif /* RSH_NEEDS_BINARY_FLAG */
+
+ /* Then we strcat more things on the end one by one. */
+ if (server_user != NULL)
+ {
+ rsh_argv[i++] = "-l";
+ rsh_argv[i++] = server_user;
+ }
+
+ rsh_argv[i++] = server_host;
+ rsh_argv[i++] = cvs_server;
+ rsh_argv[i++] = "server";
+
+ /* Mark the end of the arg list. */
+ rsh_argv[i] = (char *) NULL;
+
+ if (trace)
+ {
+ fprintf (stderr, " -> Starting server: ");
+ fprintf (stderr, "%s", command);
+ putc ('\n', stderr);
+ }
+
+ /* Do the deed. */
+ rsh_pid = popenRW (rsh_argv, pipes);
+ if (rsh_pid < 0)
+ error (1, errno, "cannot start server via rsh");
+
+ /* Give caller the file descriptors. */
+ *tofdp = pipes[0];
+ *fromfdp = pipes[1];
+}
+
+#else /* ! START_RSH_WITH_POPEN_RW */
+
+static void
+start_rsh_server (tofdp, fromfdp)
+ int *tofdp;
+ int *fromfdp;
+{
+ /* If you're working through firewalls, you can set the
+ CVS_RSH environment variable to a script which uses rsh to
+ invoke another rsh on a proxy machine. */
+ char *cvs_rsh = getenv ("CVS_RSH");
+ char *cvs_server = getenv ("CVS_SERVER");
+ char *command;
+
+ if (!cvs_rsh)
+ cvs_rsh = "rsh";
+ if (!cvs_server)
+ cvs_server = "cvs";
+
+ /* Pass the command to rsh as a single string. This shouldn't
+ affect most rsh servers at all, and will pacify some buggy
+ versions of rsh that grab switches out of the middle of the
+ command (they're calling the GNU getopt routines incorrectly). */
+ command = xmalloc (strlen (cvs_server)
+ + strlen (server_cvsroot)
+ + 50);
+
+ /* If you are running a very old (Nov 3, 1994, before 1.5)
+ * version of the server, you need to make sure that your .bashrc
+ * on the server machine does not set CVSROOT to something
+ * containing a colon (or better yet, upgrade the server). */
+ sprintf (command, "%s server", cvs_server);
+
+ {
+ char *argv[10];
+ char **p = argv;
+
+ *p++ = cvs_rsh;
+ *p++ = server_host;
+
+ /* If the login names differ between client and server
+ * pass it on to rsh.
+ */
+ if (server_user != NULL)
+ {
+ *p++ = "-l";
+ *p++ = server_user;
+ }
+
+ *p++ = command;
+ *p++ = NULL;
+
+ if (trace)
+ {
+ int i;
+
+ fprintf (stderr, " -> Starting server: ");
+ for (i = 0; argv[i]; i++)
+ fprintf (stderr, "%s ", argv[i]);
+ putc ('\n', stderr);
+ }
+ rsh_pid = piped_child (argv, tofdp, fromfdp);
+
+ if (rsh_pid < 0)
+ error (1, errno, "cannot start server via rsh");
+ }
+}
+
+#endif /* START_RSH_WITH_POPEN_RW */
+#endif /* ! RSH_NOT_TRANSPARENT */
+
+
+
+/* Send an argument STRING. */
+void
+send_arg (string)
+ char *string;
+{
+ char buf[1];
+ char *p = string;
+
+ send_to_server ("Argument ", 0);
+
+ while (*p)
+ {
+ if (*p == '\n')
+ {
+ send_to_server ("\012Argumentx ", 0);
+ }
+ else
+ {
+ buf[0] = *p;
+ send_to_server (buf, 1);
+ }
+ ++p;
+ }
+ send_to_server ("\012", 1);
+}
+
+static void send_modified PROTO ((char *, char *, Vers_TS *));
+
+static void
+send_modified (file, short_pathname, vers)
+ char *file;
+ char *short_pathname;
+ Vers_TS *vers;
+{
+ /* File was modified, send it. */
+ struct stat sb;
+ int fd;
+ char *buf;
+ char *mode_string;
+ int bufsize;
+ int bin;
+
+ /* Don't think we can assume fstat exists. */
+ if (stat (file, &sb) < 0)
+ error (1, errno, "reading %s", short_pathname);
+
+ mode_string = mode_to_string (sb.st_mode);
+
+ /* Beware: on systems using CRLF line termination conventions,
+ the read and write functions will convert CRLF to LF, so the
+ number of characters read is not the same as sb.st_size. Text
+ files should always be transmitted using the LF convention, so
+ we don't want to disable this conversion. */
+ bufsize = sb.st_size;
+ buf = xmalloc (bufsize);
+
+ /* Is the file marked as containing binary data by the "-kb" flag?
+ If so, make sure to open it in binary mode: */
+
+ if (vers && vers->options)
+ bin = !(strcmp (vers->options, "-kb"));
+ else
+ bin = 0;
+
+ fd = open (file, O_RDONLY | (bin ? OPEN_BINARY : 0));
+
+ if (fd < 0)
+ error (1, errno, "reading %s", short_pathname);
+
+ if (gzip_level && sb.st_size > 100)
+ {
+ int nread, newsize = 0, gzip_status;
+ pid_t gzip_pid;
+ char *bufp = buf;
+ int readsize = 8192;
+#ifdef LINES_CRLF_TERMINATED
+ char tempfile[L_tmpnam];
+ int converting;
+#endif /* LINES_CRLF_TERMINATED */
+
+#ifdef LINES_CRLF_TERMINATED
+ /* Assume everything in a "cvs import" is text. */
+ if (vers == NULL)
+ converting = 1;
+ else
+ /* Otherwise, we convert things unless they're binary. */
+ converting = (! bin);
+
+ if (converting)
+ {
+ /* gzip reads and writes files without munging CRLF
+ sequences, as it should, but files should be
+ transmitted in LF form. Convert CRLF to LF before
+ gzipping, on systems where this is necessary.
+
+ If Windows NT supported fork, we could do this by
+ pushing another filter on in front of gzip. But it
+ doesn't. I'd have to write a trivial little program to
+ do the conversion and have CVS spawn it off. But
+ little executables like that always get lost.
+
+ Alternatively, this cruft could go away if we switched
+ to a gzip library instead of a subprocess; then we
+ could tell gzip to open the file with CRLF translation
+ enabled. */
+ if (close (fd) < 0)
+ error (0, errno, "warning: can't close %s", short_pathname);
+
+ tmpnam (tempfile);
+ convert_file (file, O_RDONLY,
+ tempfile,
+ O_WRONLY | O_CREAT | O_TRUNC | OPEN_BINARY);
+
+ /* This OPEN_BINARY doesn't make any difference, I think, because
+ gzip will deal with the inherited handle as it pleases. But I
+ do remember something obscure in the manuals about propagating
+ the translation mode to created processes via environment
+ variables, ick. */
+ fd = open (tempfile, O_RDONLY | OPEN_BINARY);
+ if (fd < 0)
+ error (1, errno, "reading %s", short_pathname);
+ }
+#endif /* LINES_CRLF_TERMINATED */
+
+ fd = filter_through_gzip (fd, 1, gzip_level, &gzip_pid);
+
+ /* FIXME: is there any reason to go through all this realloc'ing
+ when we could just be writing the data to the network as we read
+ it from gzip? */
+ while (1)
+ {
+ if ((bufp - buf) + readsize >= bufsize)
+ {
+ /*
+ * We need to expand the buffer if gzip ends up expanding
+ * the file.
+ */
+ newsize = bufp - buf;
+ while (newsize + readsize >= bufsize)
+ bufsize *= 2;
+ buf = xrealloc (buf, bufsize);
+ bufp = buf + newsize;
+ }
+ nread = read (fd, bufp, readsize);
+ if (nread < 0)
+ error (1, errno, "reading from gzip pipe");
+ else if (nread == 0)
+ /* eof */
+ break;
+ bufp += nread;
+ }
+ newsize = bufp - buf;
+ if (close (fd) < 0)
+ error (0, errno, "warning: can't close %s", short_pathname);
+
+ if (waitpid (gzip_pid, &gzip_status, 0) != gzip_pid)
+ error (1, errno, "waiting for gzip proc %ld", (long) gzip_pid);
+ else if (gzip_status != 0)
+ error (1, errno, "gzip exited %d", gzip_status);
+
+#if LINES_CRLF_TERMINATED
+ if (converting)
+ {
+ if (unlink (tempfile) < 0)
+ error (0, errno,
+ "warning: can't remove temp file %s", tempfile);
+ }
+#endif /* LINES_CRLF_TERMINATED */
+
+ {
+ char tmp[80];
+
+ send_to_server ("Modified ", 0);
+ send_to_server (file, 0);
+ send_to_server ("\012", 1);
+ send_to_server (mode_string, 0);
+ send_to_server ("\012z", 2);
+ sprintf (tmp, "%lu\n", (unsigned long) newsize);
+ send_to_server (tmp, 0);
+
+ send_to_server (buf, newsize);
+ }
+ }
+ else
+ {
+ int newsize;
+
+ {
+ char *bufp = buf;
+ int len;
+
+ /* FIXME: This is gross. It assumes that we might read
+ less than st_size bytes (true on NT), but not more.
+ Instead of this we should just be reading a block of
+ data (e.g. 8192 bytes), writing it to the network, and
+ so on until EOF. */
+ while ((len = read (fd, bufp, (buf + sb.st_size) - bufp)) > 0)
+ bufp += len;
+
+ if (len < 0)
+ error (1, errno, "reading %s", short_pathname);
+
+ newsize = bufp - buf;
+ }
+ if (close (fd) < 0)
+ error (0, errno, "warning: can't close %s", short_pathname);
+
+ {
+ char tmp[80];
+
+ send_to_server ("Modified ", 0);
+ send_to_server (file, 0);
+ send_to_server ("\012", 1);
+ send_to_server (mode_string, 0);
+ send_to_server ("\012", 1);
+ sprintf (tmp, "%lu\012", (unsigned long) newsize);
+ send_to_server (tmp, 0);
+ }
+
+ /*
+ * Note that this only ends with a newline if the file ended with
+ * one.
+ */
+ if (newsize > 0)
+ send_to_server (buf, newsize);
+ }
+ free (buf);
+ free (mode_string);
+}
+
+static int send_fileproc PROTO ((struct file_info *finfo));
+
+/* Deal with one file. */
+static int
+send_fileproc (finfo)
+ struct file_info *finfo;
+{
+ Vers_TS *vers;
+
+ send_a_repository ("", finfo->repository, finfo->update_dir);
+
+ vers = Version_TS ((char *)NULL, (char *)NULL, (char *)NULL,
+ (char *)NULL,
+ finfo->file, 0, 0, finfo->entries, (RCSNode *)NULL);
+
+ if (vers->vn_user != NULL)
+ {
+ char *tmp;
+
+ tmp = xmalloc (strlen (finfo->file) + strlen (vers->vn_user)
+ + strlen (vers->options) + 200);
+ sprintf (tmp, "Entry /%s/%s/%s%s/%s/",
+ finfo->file, vers->vn_user,
+ vers->ts_conflict == NULL ? "" : "+",
+ (vers->ts_conflict == NULL ? ""
+ : (vers->ts_user != NULL &&
+ strcmp (vers->ts_conflict, vers->ts_user) == 0
+ ? "="
+ : "modified")),
+ vers->options);
+
+ /* The Entries request. */
+ /* Not sure about whether this deals with -k and stuff right. */
+ send_to_server (tmp, 0);
+ free (tmp);
+ if (vers->entdata != NULL && vers->entdata->tag)
+ {
+ send_to_server ("T", 0);
+ send_to_server (vers->entdata->tag, 0);
+ }
+ else if (vers->entdata != NULL && vers->entdata->date)
+ {
+ send_to_server ("D", 0);
+ send_to_server (vers->entdata->date, 0);
+ }
+ send_to_server ("\012", 1);
+ }
+
+ if (vers->ts_user == NULL)
+ {
+ /*
+ * Do we want to print "file was lost" like normal CVS?
+ * Would it always be appropriate?
+ */
+ /* File no longer exists. */
+ if (!use_unchanged)
+ {
+ /* if the server is old, use the old request... */
+ send_to_server ("Lost ", 0);
+ send_to_server (finfo->file, 0);
+ send_to_server ("\012", 1);
+ /*
+ * Otherwise, don't do anything for missing files,
+ * they just happen.
+ */
+ }
+ }
+ else if (vers->ts_rcs == NULL
+ || strcmp (vers->ts_user, vers->ts_rcs) != 0)
+ {
+ send_modified (finfo->file, finfo->fullname, vers);
+ }
+ else
+ {
+ /* Only use this request if the server supports it... */
+ if (use_unchanged)
+ {
+ send_to_server ("Unchanged ", 0);
+ send_to_server (finfo->file, 0);
+ send_to_server ("\012", 1);
+ }
+ }
+
+ /* if this directory has an ignore list, add this file to it */
+ if (ignlist)
+ {
+ Node *p;
+
+ p = getnode ();
+ p->type = FILES;
+ p->key = xstrdup (finfo->file);
+ (void) addnode (ignlist, p);
+ }
+
+ freevers_ts (&vers);
+ return 0;
+}
+
+static void send_ignproc PROTO ((char *, char *));
+
+static void
+send_ignproc (file, dir)
+ char *file;
+ char *dir;
+{
+ if (ign_inhibit_server || !supported_request ("Questionable"))
+ {
+ if (dir[0] != '\0')
+ (void) printf ("? %s/%s\n", dir, file);
+ else
+ (void) printf ("? %s\n", file);
+ }
+ else
+ {
+ send_to_server ("Questionable ", 0);
+ send_to_server (file, 0);
+ send_to_server ("\012", 1);
+ }
+}
+
+static int send_filesdoneproc PROTO ((int, char *, char *));
+
+static int
+send_filesdoneproc (err, repository, update_dir)
+ int err;
+ char *repository;
+ char *update_dir;
+{
+ /* if this directory has an ignore list, process it then free it */
+ if (ignlist)
+ {
+ ignore_files (ignlist, update_dir, send_ignproc);
+ dellist (&ignlist);
+ }
+
+ return (err);
+}
+
+static Dtype send_dirent_proc PROTO ((char *, char *, char *));
+
+/*
+ * send_dirent_proc () is called back by the recursion processor before a
+ * sub-directory is processed for update.
+ * A return code of 0 indicates the directory should be
+ * processed by the recursion code. A return of non-zero indicates the
+ * recursion code should skip this directory.
+ *
+ */
+static Dtype
+send_dirent_proc (dir, repository, update_dir)
+ char *dir;
+ char *repository;
+ char *update_dir;
+{
+ int dir_exists;
+ char *cvsadm_repos_name;
+
+ /*
+ * If the directory does not exist yet (e.g. "cvs update -d
+ * foo"), no need to send any files from it.
+ */
+ dir_exists = isdir (dir);
+
+ if (ignore_directory (update_dir))
+ {
+ /* print the warm fuzzy message */
+ if (!quiet)
+ error (0, 0, "Ignoring %s", update_dir);
+ return (R_SKIP_ALL);
+ }
+
+ /* initialize the ignore list for this directory */
+ ignlist = getlist ();
+
+ /*
+ * If there is an empty directory (e.g. we are doing `cvs add' on a
+ * newly-created directory), the server still needs to know about it.
+ */
+
+ cvsadm_repos_name = xmalloc (strlen (dir) + sizeof (CVSADM_REP) + 80);
+ sprintf (cvsadm_repos_name, "%s/%s", dir, CVSADM_REP);
+ if (dir_exists && isreadable (cvsadm_repos_name))
+ {
+ /*
+ * Get the repository from a CVS/Repository file whenever possible.
+ * The repository variable is wrong if the names in the local
+ * directory don't match the names in the repository.
+ */
+ char *repos = Name_Repository (dir, update_dir);
+ send_a_repository (dir, repos, update_dir);
+ free (repos);
+ }
+ else
+ send_a_repository (dir, repository, update_dir);
+ free (cvsadm_repos_name);
+
+ return (dir_exists ? R_PROCESS : R_SKIP_ALL);
+}
+
+/*
+ * Send each option in a string to the server, one by one.
+ * This assumes that the options are single characters. For
+ * more complex parsing, do it yourself.
+ */
+
+void
+send_option_string (string)
+ char *string;
+{
+ char *p;
+ char it[3];
+
+ for (p = string; p[0]; p++) {
+ if (p[0] == ' ')
+ continue;
+ if (p[0] == '-')
+ continue;
+ it[0] = '-';
+ it[1] = p[0];
+ it[2] = '\0';
+ send_arg (it);
+ }
+}
+
+
+/* Send the names of all the argument files to the server. */
+
+void
+send_file_names (argc, argv, flags)
+ int argc;
+ char **argv;
+ unsigned int flags;
+{
+ int i;
+ char *p;
+ char *q;
+ int level;
+ int max_level;
+
+ /* The fact that we do this here as well as start_recursion is a bit
+ of a performance hit. Perhaps worth cleaning up someday. */
+ if (flags & SEND_EXPAND_WILD)
+ expand_wild (argc, argv, &argc, &argv);
+
+ /* Send Max-dotdot if needed. */
+ max_level = 0;
+ for (i = 0; i < argc; ++i)
+ {
+ p = argv[i];
+ level = 0;
+ do
+ {
+ q = strchr (p, '/');
+ if (q != NULL)
+ ++q;
+ if (p[0] == '.' && p[1] == '.' && (p[2] == '\0' || p[2] == '/'))
+ {
+ --level;
+ if (-level > max_level)
+ max_level = -level;
+ }
+ else if (p[0] == '.' && (p[1] == '\0' || p[1] == '/'))
+ ;
+ else
+ ++level;
+ p = q;
+ } while (p != NULL);
+ }
+ if (max_level > 0)
+ {
+ if (supported_request ("Max-dotdot"))
+ {
+ char buf[10];
+ sprintf (buf, "%d", max_level);
+
+ send_to_server ("Max-dotdot ", 0);
+ send_to_server (buf, 0);
+ send_to_server ("\012", 1);
+ }
+ else
+ /*
+ * "leading .." is not strictly correct, as this also includes
+ * cases like "foo/../..". But trying to explain that in the
+ * error message would probably just confuse users.
+ */
+ error (1, 0,
+ "leading .. not supported by old (pre-Max-dotdot) servers");
+ }
+
+ for (i = 0; i < argc; ++i)
+ {
+ char buf[1];
+ char *p = argv[i];
+
+ send_to_server ("Argument ", 0);
+
+ while (*p)
+ {
+ if (*p == '\n')
+ {
+ send_to_server ("\012Argumentx ", 0);
+ }
+ else if (ISDIRSEP (*p))
+ {
+ buf[0] = '/';
+ send_to_server (buf, 1);
+ }
+ else
+ {
+ buf[0] = *p;
+ send_to_server (buf, 1);
+ }
+ ++p;
+ }
+ send_to_server ("\012", 1);
+ }
+
+ if (flags & SEND_EXPAND_WILD)
+ {
+ int i;
+ for (i = 0; i < argc; ++i)
+ free (argv[i]);
+ free (argv);
+ }
+}
+
+
+/*
+ * Send Repository, Modified and Entry. argc and argv contain only
+ * the files to operate on (or empty for everything), not options.
+ * local is nonzero if we should not recurse (-l option). Also sends
+ * Argument lines for argc and argv, so should be called after options
+ * are sent.
+ */
+void
+send_files (argc, argv, local, aflag)
+ int argc;
+ char **argv;
+ int local;
+ int aflag;
+{
+ int err;
+
+ /*
+ * aflag controls whether the tag/date is copied into the vers_ts.
+ * But we don't actually use it, so I don't think it matters what we pass
+ * for aflag here.
+ */
+ err = start_recursion
+ (send_fileproc, send_filesdoneproc,
+ send_dirent_proc, (DIRLEAVEPROC)NULL,
+ argc, argv, local, W_LOCAL, aflag, 0, (char *)NULL, 0, 0);
+ if (err)
+ exit (EXIT_FAILURE);
+ if (toplevel_repos == NULL)
+ /*
+ * This happens if we are not processing any files,
+ * or for checkouts in directories without any existing stuff
+ * checked out. The following assignment is correct for the
+ * latter case; I don't think toplevel_repos matters for the
+ * former.
+ */
+ toplevel_repos = xstrdup (server_cvsroot);
+ send_repository ("", toplevel_repos, ".");
+}
+
+void
+client_import_setup (repository)
+ char *repository;
+{
+ if (toplevel_repos == NULL) /* should always be true */
+ send_a_repository ("", repository, "");
+}
+
+/*
+ * Process the argument import file.
+ */
+int
+client_process_import_file (message, vfile, vtag, targc, targv, repository)
+ char *message;
+ char *vfile;
+ char *vtag;
+ int targc;
+ char *targv[];
+ char *repository;
+{
+ char *short_pathname;
+ int first_time;
+
+ /* FIXME: I think this is always false now that we call
+ client_import_setup at the start. */
+
+ first_time = toplevel_repos == NULL;
+
+ if (first_time)
+ send_a_repository ("", repository, "");
+
+ if (strncmp (repository, toplevel_repos, strlen (toplevel_repos)) != 0)
+ error (1, 0,
+ "internal error: pathname `%s' doesn't specify file in `%s'",
+ repository, toplevel_repos);
+ short_pathname = repository + strlen (toplevel_repos) + 1;
+
+ if (!first_time)
+ {
+ send_a_repository ("", repository, short_pathname);
+ }
+ send_modified (vfile, short_pathname, NULL);
+ return 0;
+}
+
+void
+client_import_done ()
+{
+ if (toplevel_repos == NULL)
+ /*
+ * This happens if we are not processing any files,
+ * or for checkouts in directories without any existing stuff
+ * checked out. The following assignment is correct for the
+ * latter case; I don't think toplevel_repos matters for the
+ * former.
+ */
+ /* FIXME: "can't happen" now that we call client_import_setup
+ at the beginning. */
+ toplevel_repos = xstrdup (server_cvsroot);
+ send_repository ("", toplevel_repos, ".");
+}
+
+static void
+notified_a_file (data, ent_list, short_pathname, filename)
+ char *data;
+ List *ent_list;
+ char *short_pathname;
+ char *filename;
+{
+ FILE *fp;
+ FILE *newf;
+ size_t line_len = 8192;
+ char *line = xmalloc (line_len);
+ char *cp;
+ int nread;
+ int nwritten;
+ char *p;
+
+ fp = open_file (CVSADM_NOTIFY, "r");
+ if (getline (&line, &line_len, fp) < 0)
+ {
+ error (0, errno, "cannot read %s", CVSADM_NOTIFY);
+ goto error_exit;
+ }
+ cp = strchr (line, '\t');
+ if (cp == NULL)
+ {
+ error (0, 0, "malformed %s file", CVSADM_NOTIFY);
+ goto error_exit;
+ }
+ *cp = '\0';
+ if (strcmp (filename, line + 1) != 0)
+ {
+ error (0, 0, "protocol error: notified %s, expected %s", filename,
+ line + 1);
+ }
+
+ if (getline (&line, &line_len, fp) < 0)
+ {
+ if (feof (fp))
+ {
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", CVSADM_NOTIFY);
+ if (unlink (CVSADM_NOTIFY) < 0)
+ error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
+ return;
+ }
+ else
+ {
+ error (0, errno, "cannot read %s", CVSADM_NOTIFY);
+ goto error_exit;
+ }
+ }
+ newf = open_file (CVSADM_NOTIFYTMP, "w");
+ if (fputs (line, newf) < 0)
+ {
+ error (0, errno, "cannot write %s", CVSADM_NOTIFYTMP);
+ goto error2;
+ }
+ while ((nread = fread (line, 1, line_len, fp)) > 0)
+ {
+ p = line;
+ while ((nwritten = fwrite (p, 1, nread, newf)) > 0)
+ {
+ nread -= nwritten;
+ p += nwritten;
+ }
+ if (ferror (newf))
+ {
+ error (0, errno, "cannot write %s", CVSADM_NOTIFYTMP);
+ goto error2;
+ }
+ }
+ if (ferror (fp))
+ {
+ error (0, errno, "cannot read %s", CVSADM_NOTIFY);
+ goto error2;
+ }
+ if (fclose (newf) < 0)
+ {
+ error (0, errno, "cannot close %s", CVSADM_NOTIFYTMP);
+ goto error_exit;
+ }
+ if (fclose (fp) < 0)
+ {
+ error (0, errno, "cannot close %s", CVSADM_NOTIFY);
+ return;
+ }
+
+ {
+ /* In this case, we want rename_file() to ignore noexec. */
+ int saved_noexec = noexec;
+ noexec = 0;
+ rename_file (CVSADM_NOTIFYTMP, CVSADM_NOTIFY);
+ noexec = saved_noexec;
+ }
+
+ return;
+ error2:
+ (void) fclose (newf);
+ error_exit:
+ (void) fclose (fp);
+}
+
+static void
+handle_notified (args, len)
+ char *args;
+ int len;
+{
+ call_in_directory (args, notified_a_file, NULL);
+}
+
+void
+client_notify (repository, update_dir, filename, notif_type, val)
+ char *repository;
+ char *update_dir;
+ char *filename;
+ int notif_type;
+ char *val;
+{
+ char buf[2];
+
+ send_a_repository ("", repository, update_dir);
+ send_to_server ("Notify ", 0);
+ send_to_server (filename, 0);
+ send_to_server ("\012", 1);
+ buf[0] = notif_type;
+ buf[1] = '\0';
+ send_to_server (buf, 1);
+ send_to_server ("\t", 1);
+ send_to_server (val, 0);
+}
+
+/*
+ * Send an option with an argument, dealing correctly with newlines in
+ * the argument. If ARG is NULL, forget the whole thing.
+ */
+void
+option_with_arg (option, arg)
+ char *option;
+ char *arg;
+{
+ if (arg == NULL)
+ return;
+
+ send_to_server ("Argument ", 0);
+ send_to_server (option, 0);
+ send_to_server ("\012", 1);
+
+ send_arg (arg);
+}
+
+/*
+ * Send a date to the server. This will passed a string which is the
+ * result of Make_Date, and looks like YY.MM.DD.HH.MM.SS, where all
+ * the letters are single digits. The time will be GMT. getdate on
+ * the server can't parse that, so we turn it back into something
+ * which it can parse.
+ */
+
+void
+client_senddate (date)
+ const char *date;
+{
+ int year, month, day, hour, minute, second;
+ char buf[100];
+
+ if (sscanf (date, DATEFORM, &year, &month, &day, &hour, &minute, &second)
+ != 6)
+ {
+ error (1, 0, "diff_client_senddate: sscanf failed on date");
+ }
+
+#ifndef HAVE_RCS5
+ /* We need to fix the timezone in this case; see Make_Date. */
+ abort ();
+#endif /* HAVE_RCS5 */
+
+ sprintf (buf, "%d/%d/%d %d:%d:%d GMT", month, day, year,
+ hour, minute, second);
+ option_with_arg ("-D", buf);
+}
+
+int
+client_commit (argc, argv)
+ int argc;
+ char **argv;
+{
+ parse_cvsroot ();
+
+ return commit (argc, argv);
+}
+
+int
+client_update (argc, argv)
+ int argc;
+ char **argv;
+{
+ parse_cvsroot ();
+
+ return update (argc, argv);
+}
+
+int
+client_checkout (argc, argv)
+ int argc;
+ char **argv;
+{
+ parse_cvsroot ();
+
+ return checkout (argc, argv);
+}
+
+int
+client_diff (argc, argv)
+ int argc;
+ char **argv;
+{
+ parse_cvsroot ();
+
+ return diff (argc, argv); /* Call real code */
+}
+
+int
+client_status (argc, argv)
+ int argc;
+ char **argv;
+{
+ parse_cvsroot ();
+ return status (argc, argv);
+}
+
+int
+client_log (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return cvslog (argc, argv); /* Call real code */
+}
+
+int
+client_add (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return add (argc, argv); /* Call real code */
+}
+
+int
+client_remove (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return cvsremove (argc, argv); /* Call real code */
+}
+
+int
+client_rdiff (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return patch (argc, argv); /* Call real code */
+}
+
+int
+client_tag (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return tag (argc, argv); /* Call real code */
+}
+
+int
+client_rtag (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return rtag (argc, argv); /* Call real code */
+}
+
+int
+client_import (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return import (argc, argv); /* Call real code */
+}
+
+int
+client_admin (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return admin (argc, argv); /* Call real code */
+}
+
+int
+client_export (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return checkout (argc, argv); /* Call real code */
+}
+
+int
+client_history (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return history (argc, argv); /* Call real code */
+}
+
+int
+client_release (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return release (argc, argv); /* Call real code */
+}
+
+int
+client_watch (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return watch (argc, argv); /* Call real code */
+}
+
+int
+client_watchers (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return watchers (argc, argv); /* Call real code */
+}
+
+int
+client_editors (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return editors (argc, argv); /* Call real code */
+}
+
+int
+client_edit (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return edit (argc, argv); /* Call real code */
+}
+
+int
+client_unedit (argc, argv)
+ int argc;
+ char **argv;
+{
+
+ parse_cvsroot ();
+
+ return unedit (argc, argv); /* Call real code */
+}
+
+void
+send_init_command ()
+{
+ /* This is here because we need the server_cvsroot variable. */
+ send_to_server ("init ", 0);
+ send_to_server (server_cvsroot, 0);
+ send_to_server ("\012", 0);
+}
+
+int
+client_init (argc, argv)
+ int argc;
+ char **argv;
+{
+ parse_cvsroot ();
+
+ return init (argc, argv); /* Call real code */
+}
+
+int
+client_annotate (argc, argv)
+ int argc;
+ char **argv;
+{
+ parse_cvsroot ();
+
+ return annotate (argc, argv); /* Call real code */
+}
+#endif /* CLIENT_SUPPORT */
diff --git a/contrib/cvs/src/client.h b/contrib/cvs/src/client.h
new file mode 100644
index 0000000..b238c32
--- /dev/null
+++ b/contrib/cvs/src/client.h
@@ -0,0 +1,191 @@
+/* Interface between the client and the rest of CVS. */
+
+/* Stuff shared with the server. */
+extern char *mode_to_string PROTO((mode_t));
+extern int change_mode PROTO((char *, char *));
+
+extern int gzip_level;
+extern int filter_through_gzip PROTO((int, int, int, pid_t *));
+extern int filter_through_gunzip PROTO((int, int, pid_t *));
+
+#ifdef CLIENT_SUPPORT
+/*
+ * Functions to perform CVS commands via the protocol. argc and argv
+ * are the arguments and the return value is the exit status (zero success
+ * nonzero failure).
+ */
+extern int client_commit PROTO((int argc, char **argv));
+extern int client_update PROTO((int argc, char **argv));
+extern int client_checkout PROTO((int argc, char **argv));
+extern int client_diff PROTO((int argc, char **argv));
+extern int client_log PROTO((int argc, char **argv));
+extern int client_add PROTO((int argc, char **argv));
+extern int client_remove PROTO((int argc, char **argv));
+extern int client_status PROTO((int argc, char **argv));
+extern int client_rdiff PROTO((int argc, char **argv));
+extern int client_tag PROTO((int argc, char **argv));
+extern int client_rtag PROTO((int argc, char **argv));
+extern int client_import PROTO((int argc, char **argv));
+extern int client_admin PROTO((int argc, char **argv));
+extern int client_export PROTO((int argc, char **argv));
+extern int client_history PROTO((int argc, char **argv));
+extern int client_release PROTO((int argc, char **argv));
+extern int client_watch PROTO((int argc, char **argv));
+extern int client_watchers PROTO((int argc, char **argv));
+extern int client_editors PROTO((int argc, char **argv));
+extern int client_edit PROTO((int argc, char **argv));
+extern int client_unedit PROTO((int argc, char **argv));
+extern int client_init PROTO ((int argc, char **argv));
+extern int client_annotate PROTO ((int argc, char **argv));
+
+/*
+ * Flag variable for seeing whether common code is running as a client
+ * or to do a local operation.
+ */
+extern int client_active;
+
+/*
+ * Flag variable for seeing whether the server has been started yet.
+ * As of this writing, only edit.c:notify_check() uses it.
+ */
+extern int server_started;
+
+/* Is the -P option to checkout or update specified? */
+extern int client_prune_dirs;
+
+#ifdef AUTH_CLIENT_SUPPORT
+extern int use_authenticating_server;
+int connect_to_pserver PROTO((int *tofdp, int* fromfdp, int verify_only));
+# ifndef CVS_AUTH_PORT
+# define CVS_AUTH_PORT 2401
+# endif /* CVS_AUTH_PORT */
+#endif /* AUTH_CLIENT_SUPPORT */
+
+#ifdef AUTH_SERVER_SUPPORT
+extern void authenticate_connection PROTO ((void));
+#endif
+
+/* Talking to the server. */
+void send_to_server PROTO((char *str, size_t len));
+void read_from_server PROTO((char *buf, size_t len));
+
+/* Internal functions that handle client communication to server, etc. */
+int supported_request PROTO ((char *));
+void option_with_arg PROTO((char *option, char *arg));
+
+/* Get the responses and then close the connection. */
+extern int get_responses_and_close PROTO((void));
+
+extern int get_server_responses PROTO((void));
+
+/* Start up the connection to the server on the other end. */
+void
+start_server PROTO((void));
+
+/* Send the names of all the argument files to the server. */
+void
+send_file_names PROTO((int argc, char **argv, unsigned int flags));
+
+/* Flags for send_file_names. */
+/* Expand wild cards? */
+#define SEND_EXPAND_WILD 1
+
+/*
+ * Send Repository, Modified and Entry. argc and argv contain only
+ * the files to operate on (or empty for everything), not options.
+ * local is nonzero if we should not recurse (-l option).
+ */
+void
+send_files PROTO((int argc, char **argv, int local, int aflag));
+
+/*
+ * Like send_files but never send "Unchanged"--just send the contents of the
+ * file in that case. This is used to fix it if you import a directory which
+ * happens to have CVS directories (yes it is obscure but the testsuite tests
+ * it).
+ */
+void
+send_files_contents PROTO((int argc, char **argv, int local, int aflag));
+
+/* Send an argument to the remote server. */
+void
+send_arg PROTO((char *string));
+
+/* Send a string of single-char options to the remote server, one by one. */
+void
+send_option_string PROTO((char *string));
+
+#endif /* CLIENT_SUPPORT */
+
+/*
+ * This structure is used to catalog the responses the client is
+ * prepared to see from the server.
+ */
+
+struct response
+{
+ /* Name of the response. */
+ char *name;
+
+#ifdef CLIENT_SUPPORT
+ /*
+ * Function to carry out the response. ARGS is the text of the
+ * command after name and, if present, a single space, have been
+ * stripped off. The function can scribble into ARGS if it wants.
+ */
+ void (*func) PROTO((char *args, int len));
+
+ /*
+ * ok and error are special; they indicate we are at the end of the
+ * responses, and error indicates we should exit with nonzero
+ * exitstatus.
+ */
+ enum {response_type_normal, response_type_ok, response_type_error} type;
+#endif
+
+ /* Used by the server to indicate whether response is supported by
+ the client, as set by the Valid-responses request. */
+ enum {
+ /*
+ * Failure to implement this response can imply a fatal
+ * error. This should be set only for responses which were in the
+ * original version of the protocol; it should not be set for new
+ * responses.
+ */
+ rs_essential,
+
+ /* Some clients might not understand this response. */
+ rs_optional,
+
+ /*
+ * Set by the server to one of the following based on what this
+ * client actually supports.
+ */
+ rs_supported,
+ rs_not_supported
+ } status;
+};
+
+/* Table of responses ending in an entry with a NULL name. */
+
+extern struct response responses[];
+
+#ifdef CLIENT_SUPPORT
+
+extern void client_senddate PROTO((const char *date));
+extern void client_expand_modules PROTO((int argc, char **argv, int local));
+extern void client_send_expansions PROTO((int local));
+extern void client_nonexpanded_setup PROTO((void));
+
+extern void send_init_command PROTO ((void));
+
+extern char **failed_patches;
+extern int failed_patches_count;
+extern char toplevel_wd[];
+extern void client_import_setup PROTO((char *repository));
+extern int client_process_import_file
+ PROTO((char *message, char *vfile, char *vtag,
+ int targc, char *targv[], char *repository));
+extern void client_import_done PROTO((void));
+extern void client_notify PROTO((char *, char *, char *, int, char *));
+#endif /* CLIENT_SUPPORT */
diff --git a/contrib/cvs/src/commit.c b/contrib/cvs/src/commit.c
new file mode 100644
index 0000000..ac81790
--- /dev/null
+++ b/contrib/cvs/src/commit.c
@@ -0,0 +1,1824 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Commit Files
+ *
+ * "commit" commits the present version to the RCS repository, AFTER
+ * having done a test on conflicts.
+ *
+ * The call is: cvs commit [options] files...
+ *
+ */
+
+#include "cvs.h"
+#include "getline.h"
+#include "edit.h"
+#include "fileattr.h"
+
+static Dtype check_direntproc PROTO((char *dir, char *repos, char *update_dir));
+static int check_fileproc PROTO((struct file_info *finfo));
+static int check_filesdoneproc PROTO((int err, char *repos, char *update_dir));
+static int checkaddfile PROTO((char *file, char *repository, char *tag,
+ char *options, RCSNode **rcsnode));
+static Dtype commit_direntproc PROTO((char *dir, char *repos, char *update_dir));
+static int commit_dirleaveproc PROTO((char *dir, int err, char *update_dir));
+static int commit_fileproc PROTO((struct file_info *finfo));
+static int commit_filesdoneproc PROTO((int err, char *repository, char *update_dir));
+static int finaladd PROTO((char *file, char *revision, char *tag,
+ char *options, char *update_dir,
+ char *repository, List *entries));
+static int findmaxrev PROTO((Node * p, void *closure));
+static int lock_RCS PROTO((char *user, char *rcs, char *rev, char *repository));
+static int lockrcsfile PROTO((char *file, char *repository, char *rev));
+static int precommit_list_proc PROTO((Node * p, void *closure));
+static int precommit_proc PROTO((char *repository, char *filter));
+static int remove_file PROTO((char *file, char *repository, char *tag,
+ char *message, List *entries, RCSNode *rcsnode));
+static void fix_rcs_modes PROTO((char *rcs, char *user));
+static void fixaddfile PROTO((char *file, char *repository));
+static void fixbranch PROTO((char *file, char *repository, char *branch));
+static void unlockrcs PROTO((char *file, char *repository));
+static void ci_delproc PROTO((Node *p));
+static void masterlist_delproc PROTO((Node *p));
+static void locate_rcs PROTO((char *file, char *repository, char *rcs));
+
+struct commit_info
+{
+ Ctype status; /* as returned from Classify_File() */
+ char *rev; /* a numeric rev, if we know it */
+ char *tag; /* any sticky tag, or -r option */
+ char *options; /* Any sticky -k option */
+};
+struct master_lists
+{
+ List *ulist; /* list for Update_Logfile */
+ List *cilist; /* list with commit_info structs */
+};
+
+static int force_ci = 0;
+static int got_message;
+static int run_module_prog = 1;
+static int aflag;
+static char *tag;
+static char *write_dirtag;
+static char *logfile;
+static List *mulist;
+static char *message;
+static time_t last_register_time;
+
+
+static const char *const commit_usage[] =
+{
+ "Usage: %s %s [-nRlf] [-m msg | -F logfile] [-r rev] files...\n",
+ "\t-n\tDo not run the module program (if any).\n",
+ "\t-R\tProcess directories recursively.\n",
+ "\t-l\tLocal directory only (not recursive).\n",
+ "\t-f\tForce the file to be committed; disables recursion.\n",
+ "\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",
+ NULL
+};
+
+#ifdef CLIENT_SUPPORT
+struct find_data {
+ List *ulist;
+ int argc;
+ char **argv;
+};
+
+/* Pass as a static until we get around to fixing start_recursion to
+ pass along a void * where we can stash it. */
+struct find_data *find_data_static;
+
+static int find_fileproc PROTO ((struct file_info *finfo));
+
+/* Machinery to find out what is modified, added, and removed. It is
+ possible this should be broken out into a new client_classify function;
+ merging it with classify_file is almost sure to be a mess, though,
+ because classify_file has all kinds of repository processing. */
+static int
+find_fileproc (finfo)
+ struct file_info *finfo;
+{
+ Vers_TS *vers;
+ enum classify_type status;
+ Node *node;
+ struct find_data *args = find_data_static;
+
+ vers = Version_TS ((char *)NULL, (char *)NULL, (char *)NULL,
+ (char *)NULL,
+ finfo->file, 0, 0, finfo->entries, (RCSNode *)NULL);
+ if (vers->ts_user == NULL
+ && vers->vn_user != NULL
+ && vers->vn_user[0] == '-')
+ status = T_REMOVED;
+ else if (vers->vn_user == NULL)
+ {
+ 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);
+ return 1;
+ }
+ else if (vers->ts_user != NULL
+ && vers->vn_user != NULL
+ && vers->vn_user[0] == '0')
+ status = T_ADDED;
+ else if (vers->ts_user != NULL
+ && vers->ts_rcs != NULL
+ && strcmp (vers->ts_user, vers->ts_rcs) != 0)
+ status = T_MODIFIED;
+ else
+ {
+ /* This covers unmodified files, as well as a variety of other
+ cases. FIXME: we probably should be printing a message and
+ returning 1 for many of those cases (but I'm not sure
+ exactly which ones). */
+ return 0;
+ }
+
+ node = getnode ();
+ node->key = xstrdup (finfo->fullname);
+
+ node->type = UPDATE;
+ node->delproc = update_delproc;
+ node->data = (char *) status;
+ (void)addnode (args->ulist, node);
+
+ ++args->argc;
+
+ return 0;
+}
+
+static int copy_ulist PROTO ((Node *, void *));
+
+static int
+copy_ulist (node, data)
+ Node *node;
+ void *data;
+{
+ struct find_data *args = (struct find_data *)data;
+ args->argv[args->argc++] = node->key;
+ return 0;
+}
+#endif /* CLIENT_SUPPORT */
+
+int
+commit (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int err = 0;
+ int local = 0;
+
+ if (argc == -1)
+ usage (commit_usage);
+
+#ifdef CVS_BADROOT
+ /*
+ * 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.
+ */
+ if (geteuid () == (uid_t) 0)
+ {
+ struct passwd *pw;
+
+ if ((pw = (struct passwd *) getpwnam (getcaller ())) == NULL)
+ error (1, 0, "you are unknown to this system");
+ if (pw->pw_uid == (uid_t) 0)
+ error (1, 0, "cannot commit files as 'root'");
+ }
+#endif /* CVS_BADROOT */
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "nlRm:fF:r:")) != -1)
+ {
+ switch (c)
+ {
+ case 'n':
+ run_module_prog = 0;
+ break;
+ case 'm':
+#ifdef FORCE_USE_EDITOR
+ use_editor = TRUE;
+#else
+ use_editor = FALSE;
+#endif
+ if (message)
+ {
+ free (message);
+ message = NULL;
+ }
+
+ message = xstrdup(optarg);
+ break;
+ case 'r':
+ if (tag)
+ free (tag);
+ tag = xstrdup (optarg);
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'f':
+ force_ci = 1;
+ local = 1; /* also disable recursion */
+ break;
+ case 'F':
+#ifdef FORCE_USE_EDITOR
+ use_editor = TRUE;
+#else
+ use_editor = FALSE;
+#endif
+ logfile = optarg;
+ break;
+ case '?':
+ default:
+ usage (commit_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* numeric specified revision means we ignore sticky tags... */
+ if (tag && isdigit (*tag))
+ {
+ aflag = 1;
+ /* strip trailing dots */
+ while (tag[strlen (tag) - 1] == '.')
+ tag[strlen (tag) - 1] = '\0';
+ }
+
+ /* some checks related to the "-F logfile" option */
+ if (logfile)
+ {
+ int n, logfd;
+ struct stat statbuf;
+
+ if (message)
+ error (1, 0, "cannot specify both a message and a log file");
+
+ /* FIXME: Why is this binary? Needs more investigation. */
+ if ((logfd = open (logfile, O_RDONLY | OPEN_BINARY)) < 0)
+ error (1, errno, "cannot open log file %s", logfile);
+
+ if (fstat(logfd, &statbuf) < 0)
+ error (1, errno, "cannot find size of log file %s", logfile);
+
+ message = xmalloc (statbuf.st_size + 1);
+
+ /* FIXME: Should keep reading until EOF, rather than assuming the
+ first read gets the whole thing. */
+ if ((n = read (logfd, message, statbuf.st_size + 1)) < 0)
+ error (1, errno, "cannot read log message from %s", logfile);
+
+ (void) close (logfd);
+ message[n] = '\0';
+ }
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ struct find_data find_args;
+
+ ign_setup ();
+
+ /* Note that we don't do ignore file processing here, and we
+ don't call ignore_files. This means that we won't print "?
+ foo" for stray files. Sounds OK, the doc only promises
+ that update does that. */
+ find_args.ulist = getlist ();
+ find_args.argc = 0;
+ find_data_static = &find_args;
+ err = start_recursion (find_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ argc, argv, local, W_LOCAL, 0, 0,
+ (char *)NULL, 0, 0);
+ if (err)
+ error (1, 0, "correct above errors first!");
+
+ if (find_args.argc == 0)
+ return 0;
+
+ /* Now we keep track of which files we actually are going to
+ operate on, and only work with those files in the future.
+ This saves time--we don't want to search the file system
+ of the working directory twice. */
+ find_args.argv = (char **) xmalloc (find_args.argc * sizeof (char **));
+ find_args.argc = 0;
+ walklist (find_args.ulist, copy_ulist, &find_args);
+
+ /*
+ * Do this before calling do_editor; don't ask for a log
+ * message if we can't talk to the server. But do it after we
+ * have made the checks that we can locally (to more quickly
+ * catch syntax errors, the case where no files are modified,
+ * added or removed, etc.). */
+ start_server ();
+
+ /*
+ * We do this once, not once for each directory as in normal CVS.
+ * The protocol is designed this way. This is a feature.
+ */
+ if (use_editor)
+ do_editor (".", &message, (char *)NULL, find_args.ulist);
+
+ /* We always send some sort of message, even if empty. */
+ option_with_arg ("-m", message);
+
+ if (local)
+ send_arg("-l");
+ if (force_ci)
+ send_arg("-f");
+ if (!run_module_prog)
+ send_arg("-n");
+ option_with_arg ("-r", tag);
+
+ /* Sending only the names of the files which were modified, added,
+ or removed means that the server will only do an up-to-date
+ check on those files. This is different from local CVS and
+ previous versions of client/server CVS, but it probably is a Good
+ Thing, or at least Not Such A Bad Thing. */
+ send_file_names (find_args.argc, find_args.argv, 0);
+ send_files (find_args.argc, find_args.argv, local, 0);
+
+ send_to_server ("ci\012", 0);
+ return get_responses_and_close ();
+ }
+#endif
+
+ if (tag != NULL)
+ tag_check_valid (tag, argc, argv, local, aflag, "");
+
+ /* XXX - this is not the perfect check for this */
+ if (argc <= 0)
+ write_dirtag = tag;
+
+ wrap_setup ();
+
+ lock_tree_for_write (argc, argv, local, aflag);
+
+ /*
+ * Set up the master update list
+ */
+ mulist = getlist ();
+
+ /*
+ * Run the recursion processor to verify the files are all up-to-date
+ */
+ err = start_recursion (check_fileproc, check_filesdoneproc,
+ check_direntproc, (DIRLEAVEPROC) NULL, argc,
+ argv, local, W_LOCAL, aflag, 0, (char *) NULL, 1,
+ 0);
+ if (err)
+ {
+ lock_tree_cleanup ();
+ error (1, 0, "correct above errors first!");
+ }
+
+ /*
+ * Run the recursion processor to commit the files
+ */
+ if (noexec == 0)
+ err = start_recursion (commit_fileproc, commit_filesdoneproc,
+ commit_direntproc, commit_dirleaveproc,
+ argc, argv, local, W_LOCAL, aflag, 0,
+ (char *) NULL, 1, 0);
+
+ /*
+ * Unlock all the dirs and clean up
+ */
+ lock_tree_cleanup ();
+ dellist (&mulist);
+
+ if (last_register_time)
+ {
+ time_t now;
+
+ (void) time (&now);
+ if (now == last_register_time)
+ {
+ sleep (1); /* to avoid time-stamp races */
+ }
+ }
+
+ return (err);
+}
+
+/*
+ * Check to see if a file is ok to commit and make sure all files are
+ * up-to-date
+ */
+/* ARGSUSED */
+static int
+check_fileproc (finfo)
+ struct file_info *finfo;
+{
+ Ctype status;
+ char *xdir;
+ Node *p;
+ List *ulist, *cilist;
+ Vers_TS *vers;
+ struct commit_info *ci;
+ int save_noexec, save_quiet, save_really_quiet;
+
+ save_noexec = noexec;
+ save_quiet = quiet;
+ save_really_quiet = really_quiet;
+ noexec = quiet = really_quiet = 1;
+
+ /* handle specified numeric revision specially */
+ if (tag && isdigit (*tag))
+ {
+ /* If the tag is for the trunk, make sure we're at the head */
+ if (numdots (tag) < 2)
+ {
+ status = Classify_File (finfo->file, (char *) NULL, (char *) NULL,
+ (char *) NULL, 1, aflag, finfo->repository,
+ finfo->entries, finfo->rcs, &vers, finfo->update_dir, 0);
+ if (status == T_UPTODATE || status == T_MODIFIED ||
+ status == T_ADDED)
+ {
+ Ctype xstatus;
+
+ freevers_ts (&vers);
+ xstatus = Classify_File (finfo->file, tag, (char *) NULL,
+ (char *) NULL, 1, aflag, finfo->repository,
+ finfo->entries, finfo->rcs, &vers, finfo->update_dir,
+ 0);
+ if (xstatus == T_REMOVE_ENTRY)
+ status = T_MODIFIED;
+ else if (status == T_MODIFIED && xstatus == T_CONFLICT)
+ status = T_MODIFIED;
+ else
+ status = xstatus;
+ }
+ }
+ else
+ {
+ char *xtag, *cp;
+
+ /*
+ * The revision is off the main trunk; make sure we're
+ * up-to-date with the head of the specified branch.
+ */
+ xtag = xstrdup (tag);
+ if ((numdots (xtag) & 1) != 0)
+ {
+ cp = strrchr (xtag, '.');
+ *cp = '\0';
+ }
+ status = Classify_File (finfo->file, xtag, (char *) NULL,
+ (char *) NULL, 1, aflag, finfo->repository,
+ finfo->entries, finfo->rcs, &vers, finfo->update_dir, 0);
+ if ((status == T_REMOVE_ENTRY || status == T_CONFLICT)
+ && (cp = strrchr (xtag, '.')) != NULL)
+ {
+ /* pluck one more dot off the revision */
+ *cp = '\0';
+ freevers_ts (&vers);
+ status = Classify_File (finfo->file, xtag, (char *) NULL,
+ (char *) NULL, 1, aflag, finfo->repository,
+ finfo->entries, finfo->rcs, &vers, finfo->update_dir,
+ 0);
+ if (status == T_UPTODATE || status == T_REMOVE_ENTRY)
+ status = T_MODIFIED;
+ }
+ /* now, muck with vers to make the tag correct */
+ free (vers->tag);
+ vers->tag = xstrdup (tag);
+ free (xtag);
+ }
+ }
+ else
+ status = Classify_File (finfo->file, tag, (char *) NULL, (char *) NULL,
+ 1, 0, finfo->repository, finfo->entries, finfo->rcs, &vers,
+ finfo->update_dir, 0);
+ noexec = save_noexec;
+ quiet = save_quiet;
+ really_quiet = save_really_quiet;
+
+ /*
+ * If the force-commit option is enabled, and the file in question
+ * appears to be up-to-date, just make it look modified so that
+ * it will be committed.
+ */
+ if (force_ci && status == T_UPTODATE)
+ status = T_MODIFIED;
+
+ switch (status)
+ {
+ case T_CHECKOUT:
+#ifdef SERVER_SUPPORT
+ case T_PATCH:
+#endif
+ case T_NEEDS_MERGE:
+ case T_CONFLICT:
+ case T_REMOVE_ENTRY:
+ error (0, 0, "Up-to-date check failed for `%s'", finfo->fullname);
+ freevers_ts (&vers);
+ return (1);
+ case T_MODIFIED:
+ case T_ADDED:
+ case T_REMOVED:
+ /*
+ * some quick sanity checks; if no numeric -r option specified:
+ * - can't have a sticky date
+ * - can't have a sticky tag that is not a branch
+ * Also,
+ * - if status is T_REMOVED, can't have a numeric tag
+ * - if status is T_ADDED, rcs file must not exist
+ * - if status is T_ADDED, can't have a non-trunk numeric rev
+ * - if status is T_MODIFIED and a Conflict marker exists, don't
+ * allow the commit if timestamp is identical or if we find
+ * an RCS_MERGE_PAT in the file.
+ */
+ if (!tag || !isdigit (*tag))
+ {
+ if (vers->date)
+ {
+ error (0, 0,
+ "cannot commit with sticky date for file `%s'",
+ finfo->fullname);
+ freevers_ts (&vers);
+ return (1);
+ }
+ if (status == T_MODIFIED && vers->tag &&
+ !RCS_isbranch (finfo->rcs, vers->tag))
+ {
+ error (0, 0,
+ "sticky tag `%s' for file `%s' is not a branch",
+ vers->tag, finfo->fullname);
+ freevers_ts (&vers);
+ return (1);
+ }
+ }
+ if (status == T_MODIFIED && !force_ci && vers->ts_conflict)
+ {
+ char *filestamp;
+ int retcode;
+
+ /*
+ * We found a "conflict" marker.
+ *
+ * If the timestamp on the file is the same as the
+ * timestamp stored in the Entries file, we block the commit.
+ */
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ retcode = vers->ts_conflict[0] != '=';
+ else {
+ filestamp = time_stamp (finfo->file);
+ retcode = strcmp (vers->ts_conflict, filestamp);
+ free (filestamp);
+ }
+#else
+ filestamp = time_stamp (finfo->file);
+ retcode = strcmp (vers->ts_conflict, filestamp);
+ free (filestamp);
+#endif
+ if (retcode == 0)
+ {
+ error (0, 0,
+ "file `%s' had a conflict and has not been modified",
+ finfo->fullname);
+ freevers_ts (&vers);
+ return (1);
+ }
+
+ /*
+ * If the timestamps differ, look for Conflict indicators
+ * in the file to see if we should block the commit anyway
+ */
+ run_setup ("%s", GREP);
+ run_arg (RCS_MERGE_PAT);
+ run_arg (finfo->file);
+ retcode = run_exec (RUN_TTY, DEVNULL, RUN_TTY, RUN_REALLY);
+
+ if (retcode == -1)
+ {
+ error (1, errno,
+ "fork failed while examining conflict in `%s'",
+ finfo->fullname);
+ }
+ else if (retcode == 0)
+ {
+ error (0, 0,
+ "file `%s' still contains conflict indicators",
+ finfo->fullname);
+ freevers_ts (&vers);
+ return (1);
+ }
+ }
+
+ if (status == T_REMOVED && vers->tag && isdigit (*vers->tag))
+ {
+ error (0, 0,
+ "cannot remove file `%s' which has a numeric sticky tag of `%s'",
+ finfo->fullname, vers->tag);
+ freevers_ts (&vers);
+ return (1);
+ }
+ if (status == T_ADDED)
+ {
+ char rcs[PATH_MAX];
+
+ /* Don't look in the attic; if it exists there we will
+ move it back out in checkaddfile. */
+ sprintf(rcs, "%s/%s%s", finfo->repository, finfo->file, RCSEXT);
+ if (isreadable (rcs))
+ {
+ error (0, 0,
+ "cannot add file `%s' when RCS file `%s' already exists",
+ finfo->fullname, rcs);
+ freevers_ts (&vers);
+ return (1);
+ }
+ if (vers->tag && isdigit (*vers->tag) &&
+ numdots (vers->tag) > 1)
+ {
+ error (0, 0,
+ "cannot add file `%s' with revision `%s'; must be on trunk",
+ finfo->fullname, vers->tag);
+ freevers_ts (&vers);
+ return (1);
+ }
+ }
+
+ /* done with consistency checks; now, to get on with the commit */
+ if (finfo->update_dir[0] == '\0')
+ xdir = ".";
+ else
+ xdir = finfo->update_dir;
+ if ((p = findnode (mulist, xdir)) != NULL)
+ {
+ ulist = ((struct master_lists *) p->data)->ulist;
+ cilist = ((struct master_lists *) p->data)->cilist;
+ }
+ else
+ {
+ struct master_lists *ml;
+
+ ulist = getlist ();
+ cilist = getlist ();
+ p = getnode ();
+ p->key = xstrdup (xdir);
+ p->type = UPDATE;
+ ml = (struct master_lists *)
+ xmalloc (sizeof (struct master_lists));
+ ml->ulist = ulist;
+ ml->cilist = cilist;
+ p->data = (char *) ml;
+ p->delproc = masterlist_delproc;
+ (void) addnode (mulist, p);
+ }
+
+ /* first do ulist, then cilist */
+ p = getnode ();
+ p->key = xstrdup (finfo->file);
+ p->type = UPDATE;
+ p->delproc = update_delproc;
+ p->data = (char *) status;
+ (void) addnode (ulist, p);
+
+ p = getnode ();
+ p->key = xstrdup (finfo->file);
+ p->type = UPDATE;
+ p->delproc = ci_delproc;
+ ci = (struct commit_info *) xmalloc (sizeof (struct commit_info));
+ ci->status = status;
+ if (vers->tag)
+ if (isdigit (*vers->tag))
+ ci->rev = xstrdup (vers->tag);
+ else
+ ci->rev = RCS_whatbranch (finfo->rcs, vers->tag);
+ else
+ ci->rev = (char *) NULL;
+ ci->tag = xstrdup (vers->tag);
+ ci->options = xstrdup(vers->options);
+ p->data = (char *) ci;
+ (void) addnode (cilist, p);
+ break;
+ case T_UNKNOWN:
+ error (0, 0, "nothing known about `%s'", finfo->fullname);
+ freevers_ts (&vers);
+ return (1);
+ case T_UPTODATE:
+ break;
+ default:
+ error (0, 0, "CVS internal error: unknown status %d", status);
+ break;
+ }
+
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Print warm fuzzies while examining the dirs
+ */
+/* ARGSUSED */
+static Dtype
+check_direntproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "Examining %s", update_dir);
+
+ return (R_PROCESS);
+}
+
+/*
+ * Walklist proc to run pre-commit checks
+ */
+static int
+precommit_list_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ if (p->data == (char *) T_ADDED || p->data == (char *) T_MODIFIED ||
+ p->data == (char *) T_REMOVED)
+ {
+ run_arg (p->key);
+ }
+ return (0);
+}
+
+/*
+ * Callback proc for pre-commit checking
+ */
+static List *ulist;
+static int
+precommit_proc (repository, filter)
+ char *repository;
+ char *filter;
+{
+ /* see if the filter is there, only if it's a full path */
+ if (isabsolute (filter))
+ {
+ char *s, *cp;
+
+ s = xstrdup (filter);
+ for (cp = s; *cp; cp++)
+ if (isspace (*cp))
+ {
+ *cp = '\0';
+ break;
+ }
+ if (!isfile (s))
+ {
+ error (0, errno, "cannot find pre-commit filter `%s'", s);
+ free (s);
+ return (1); /* so it fails! */
+ }
+ free (s);
+ }
+
+ run_setup ("%s %s", filter, repository);
+ (void) walklist (ulist, precommit_list_proc, NULL);
+ return (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY));
+}
+
+/*
+ * Run the pre-commit checks for the dir
+ */
+/* ARGSUSED */
+static int
+check_filesdoneproc (err, repos, update_dir)
+ int err;
+ char *repos;
+ char *update_dir;
+{
+ int n;
+ Node *p;
+
+ /* find the update list for this dir */
+ p = findnode (mulist, update_dir);
+ if (p != NULL)
+ ulist = ((struct master_lists *) p->data)->ulist;
+ else
+ ulist = (List *) NULL;
+
+ /* skip the checks if there's nothing to do */
+ if (ulist == NULL || ulist->list->next == ulist->list)
+ return (err);
+
+ /* run any pre-commit checks */
+ if ((n = Parse_Info (CVSROOTADM_COMMITINFO, repos, precommit_proc, 1)) > 0)
+ {
+ error (0, 0, "Pre-commit check failed");
+ err += n;
+ }
+
+ return (err);
+}
+
+/*
+ * Do the work of committing a file
+ */
+static int maxrev;
+static char sbranch[PATH_MAX];
+
+/* ARGSUSED */
+static int
+commit_fileproc (finfo)
+ struct file_info *finfo;
+{
+ Node *p;
+ int err = 0;
+ List *ulist, *cilist;
+ struct commit_info *ci;
+ char rcs[PATH_MAX];
+
+ if (finfo->update_dir[0] == '\0')
+ p = findnode (mulist, ".");
+ else
+ p = findnode (mulist, finfo->update_dir);
+
+ /*
+ * if p is null, there were file type command line args which were
+ * all up-to-date so nothing really needs to be done
+ */
+ if (p == NULL)
+ return (0);
+ ulist = ((struct master_lists *) p->data)->ulist;
+ cilist = ((struct master_lists *) p->data)->cilist;
+
+ /*
+ * At this point, we should have the commit message unless we were called
+ * with files as args from the command line. In that latter case, we
+ * need to get the commit message ourselves
+ */
+ if (use_editor && !got_message)
+ {
+ got_message = 1;
+ do_editor (finfo->update_dir, &message, finfo->repository, ulist);
+ }
+
+ p = findnode (cilist, finfo->file);
+ if (p == NULL)
+ return (0);
+
+ ci = (struct commit_info *) p->data;
+ if (ci->status == T_MODIFIED)
+ {
+ if (lockrcsfile (finfo->file, finfo->repository, ci->rev) != 0)
+ {
+ unlockrcs (finfo->file, finfo->repository);
+ err = 1;
+ goto out;
+ }
+ }
+ else if (ci->status == T_ADDED)
+ {
+ if (checkaddfile (finfo->file, finfo->repository, ci->tag, ci->options,
+ &finfo->rcs) != 0)
+ {
+ fixaddfile (finfo->file, finfo->repository);
+ err = 1;
+ goto out;
+ }
+
+ /* adding files with a tag, now means adding them on a branch.
+ Since the branch test was done in check_fileproc for
+ modified files, we need to stub it in again here. */
+
+ if (ci->tag) {
+ locate_rcs (finfo->file, finfo->repository, rcs);
+ ci->rev = RCS_whatbranch (finfo->rcs, ci->tag);
+ err = Checkin ('A', finfo->file, finfo->update_dir, finfo->repository, rcs, ci->rev,
+ ci->tag, ci->options, message, finfo->entries);
+ if (err != 0)
+ {
+ unlockrcs (finfo->file, finfo->repository);
+ fixbranch (finfo->file, finfo->repository, sbranch);
+ }
+
+ (void) time (&last_register_time);
+
+ ci->status = T_UPTODATE;
+ }
+ }
+
+ /*
+ * Add the file for real
+ */
+ if (ci->status == T_ADDED)
+ {
+ char *xrev = (char *) NULL;
+
+ if (ci->rev == NULL)
+ {
+ /* find the max major rev number in this directory */
+ maxrev = 0;
+ (void) walklist (finfo->entries, findmaxrev, NULL);
+ if (maxrev == 0)
+ maxrev = 1;
+ xrev = xmalloc (20);
+ (void) sprintf (xrev, "%d", maxrev);
+ }
+
+ /* XXX - an added file with symbolic -r should add tag as well */
+ err = finaladd (finfo->file, ci->rev ? ci->rev : xrev, ci->tag, ci->options,
+ finfo->update_dir, finfo->repository, finfo->entries);
+ if (xrev)
+ free (xrev);
+ }
+ else if (ci->status == T_MODIFIED)
+ {
+ locate_rcs (finfo->file, finfo->repository, rcs);
+ err = Checkin ('M', finfo->file, finfo->update_dir, finfo->repository,
+ rcs, ci->rev, ci->tag,
+ ci->options, message, finfo->entries);
+
+ (void) time (&last_register_time);
+
+ if (err != 0)
+ {
+ unlockrcs (finfo->file, finfo->repository);
+ fixbranch (finfo->file, finfo->repository, sbranch);
+ }
+ }
+ else if (ci->status == T_REMOVED)
+ {
+ err = remove_file (finfo->file, finfo->repository, ci->tag, message,
+ finfo->entries, finfo->rcs);
+#ifdef SERVER_SUPPORT
+ if (server_active) {
+ server_scratch_entry_only ();
+ server_updated (finfo->file, finfo->update_dir, finfo->repository,
+ /* Doesn't matter, it won't get checked. */
+ SERVER_UPDATED, (struct stat *) NULL,
+ (unsigned char *) NULL);
+ }
+#endif
+ }
+
+ /* Clearly this is right for T_MODIFIED. I haven't thought so much
+ about T_ADDED or T_REMOVED. */
+ notify_do ('C', finfo->file, getcaller (), NULL, NULL, finfo->repository);
+
+out:
+ if (err != 0)
+ {
+ /* on failure, remove the file from ulist */
+ p = findnode (ulist, finfo->file);
+ if (p)
+ delnode (p);
+ }
+
+ return (err);
+}
+
+/*
+ * Log the commit and clean up the update list
+ */
+/* ARGSUSED */
+static int
+commit_filesdoneproc (err, repository, update_dir)
+ int err;
+ char *repository;
+ char *update_dir;
+{
+ char *xtag = (char *) NULL;
+ Node *p;
+ List *ulist;
+
+ p = findnode (mulist, update_dir);
+ if (p == NULL)
+ return (err);
+
+ ulist = ((struct master_lists *) p->data)->ulist;
+
+ got_message = 0;
+
+ /* see if we need to specify a per-directory or -r option tag */
+ if (tag == NULL)
+ ParseTag (&xtag, (char **) NULL);
+
+ Update_Logfile (repository, message, tag ? tag : xtag, (FILE *) 0, ulist);
+ if (xtag)
+ free (xtag);
+
+ /* Build the administrative files if necessary. */
+ {
+ char *p;
+
+ if (strncmp (CVSroot, repository, strlen (CVSroot)) != 0)
+ error (0, 0, "internal error: repository doesn't begin with root");
+ p = repository + strlen (CVSroot);
+ if (*p == '/')
+ ++p;
+ if (strcmp ("CVSROOT", p) == 0)
+ {
+ /* "Database" might a little bit grandiose and/or vague,
+ but "checked-out copies of administrative files, unless
+ in the case of modules and you are using ndbm in which
+ case modules.{pag,dir,db}" is verbose and excessively
+ focused on how the database is implemented. */
+
+ cvs_output (program_name, 0);
+ cvs_output (" ", 1);
+ cvs_output (command_name, 0);
+ cvs_output (": Rebuilding administrative file database\n", 0);
+ mkmodules (repository);
+ }
+ }
+
+ if (err == 0 && run_module_prog)
+ {
+ FILE *fp;
+
+ if ((fp = fopen (CVSADM_CIPROG, "r")) != NULL)
+ {
+ char *line;
+ int line_length;
+ size_t line_chars_allocated;
+ char *repository;
+
+ line = NULL;
+ line_chars_allocated = 0;
+ line_length = getline (&line, &line_chars_allocated, fp);
+ if (line_length > 0)
+ {
+ /* Remove any trailing newline. */
+ if (line[line_length - 1] == '\n')
+ line[--line_length] = '\0';
+ repository = Name_Repository ((char *) NULL, update_dir);
+ run_setup ("%s %s", line, repository);
+ (void) printf ("%s %s: Executing '", program_name,
+ command_name);
+ run_print (stdout);
+ (void) printf ("'\n");
+ (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+ free (repository);
+ }
+ else
+ {
+ if (ferror (fp))
+ error (0, errno, "warning: error reading %s",
+ CVSADM_CIPROG);
+ }
+ if (line != NULL)
+ free (line);
+ if (fclose (fp) < 0)
+ error (0, errno, "warning: cannot close %s", CVSADM_CIPROG);
+ }
+ else
+ {
+ if (! existence_error (errno))
+ error (0, errno, "warning: cannot open %s", CVSADM_CIPROG);
+ }
+ }
+
+ return (err);
+}
+
+/*
+ * Get the log message for a dir and print a warm fuzzy
+ */
+/* ARGSUSED */
+static Dtype
+commit_direntproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ Node *p;
+ List *ulist;
+ char *real_repos;
+
+ /* find the update list for this dir */
+ p = findnode (mulist, update_dir);
+ if (p != NULL)
+ ulist = ((struct master_lists *) p->data)->ulist;
+ else
+ ulist = (List *) NULL;
+
+ /* skip the files as an optimization */
+ 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 */
+ if (use_editor)
+ {
+ got_message = 1;
+ real_repos = Name_Repository (dir, update_dir);
+ do_editor (update_dir, &message, real_repos, ulist);
+ free (real_repos);
+ }
+ return (R_PROCESS);
+}
+
+/*
+ * Process the post-commit proc if necessary
+ */
+/* ARGSUSED */
+static int
+commit_dirleaveproc (dir, err, update_dir)
+ char *dir;
+ int err;
+ char *update_dir;
+{
+ /* update the per-directory tag info */
+ if (err == 0 && write_dirtag != NULL)
+ {
+ WriteTag ((char *) NULL, write_dirtag, (char *) NULL);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_sticky (update_dir, Name_Repository (dir, update_dir),
+ write_dirtag, (char *) NULL);
+#endif
+ }
+
+ return (err);
+}
+
+/*
+ * find the maximum major rev number in an entries file
+ */
+static int
+findmaxrev (p, closure)
+ Node *p;
+ void *closure;
+{
+ char *cp;
+ int thisrev;
+ Entnode *entdata;
+
+ entdata = (Entnode *) p->data;
+ cp = strchr (entdata->version, '.');
+ if (cp != NULL)
+ *cp = '\0';
+ thisrev = atoi (entdata->version);
+ if (cp != NULL)
+ *cp = '.';
+ if (thisrev > maxrev)
+ maxrev = thisrev;
+ return (0);
+}
+
+/*
+ * Actually remove a file by moving it to the attic
+ * XXX - if removing a ,v file that is a relative symbolic link to
+ * another ,v file, we probably should add a ".." component to the
+ * link to keep it relative after we move it into the attic.
+ */
+static int
+remove_file (file, repository, tag, message, entries, rcsnode)
+ char *file;
+ char *repository;
+ char *tag;
+ char *message;
+ List *entries;
+ RCSNode *rcsnode;
+{
+ mode_t omask;
+ int retcode;
+ char rcs[PATH_MAX];
+ char *tmp;
+
+ int branch;
+ int lockflag;
+ char *corev;
+ char *rev;
+ char *prev_rev;
+
+ corev = NULL;
+ rev = NULL;
+ prev_rev = NULL;
+
+ retcode = 0;
+
+ locate_rcs (file, repository, rcs);
+
+ branch = 0;
+ if (tag && !(branch = RCS_isbranch (rcsnode, tag)))
+ {
+ /* a symbolic tag is specified; just remove the tag from the file */
+ if ((retcode = RCS_deltag (rcs, tag, 1)) != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to remove tag `%s' from `%s'", tag, rcs);
+ return (1);
+ }
+ Scratch_Entry (entries, file);
+ return (0);
+ }
+
+ /* we are removing the file from either the head or a branch */
+ /* commit a new, dead revision. */
+
+ /* Print message indicating that file is going to be removed. */
+ (void) printf ("Removing %s;\n", file);
+
+ rev = NULL;
+ lockflag = RCS_FLAGS_LOCK;
+ if (branch)
+ {
+ char *branchname;
+
+ rev = RCS_whatbranch (rcsnode, tag);
+ if (rev == NULL)
+ {
+ error (0, 0, "cannot find branch \"%s\".", tag);
+ return (1);
+ }
+
+ if (rcsnode == NULL)
+ {
+ error (0, 0, "boy, I'm confused.");
+ return (1);
+ }
+ branchname = RCS_getbranch (rcsnode, rev, 1);
+ if (branchname == NULL)
+ {
+ /* no revision exists on this branch. use the previous
+ revision but do not lock. */
+ corev = RCS_gettag (rcsnode, tag, 1, 0);
+ prev_rev = xstrdup(rev);
+ lockflag = 0;
+ } else
+ {
+ corev = xstrdup (rev);
+ prev_rev = xstrdup(branchname);
+ free (branchname);
+ }
+
+ } else /* Not a branch */
+ {
+
+ /* Get current head revision of file. */
+ if (rcsnode == NULL)
+ {
+ error (0, 0, "could not find parsed rcsfile %s", file);
+ return (1);
+ }
+ prev_rev = RCS_head (rcsnode);
+ }
+
+ /* if removing without a tag or a branch, then make sure the default
+ branch is the trunk. */
+ if (!tag && !branch)
+ {
+ if (RCS_setbranch (rcs, NULL) != 0)
+ {
+ error (0, 0, "cannot change branch to default for %s",
+ rcs);
+ return (1);
+ }
+ }
+
+#ifdef SERVER_SUPPORT
+ if (server_active) {
+ /* If this is the server, there will be a file sitting in the
+ temp directory which is the kludgy way in which server.c
+ tells time_stamp that the file is no longer around. Remove
+ it so we can create temp files with that name (ignore errors). */
+ unlink_file (file);
+ }
+#endif
+
+ /* check something out. Generally this is the head. If we have a
+ particular rev, then name it. except when creating a branch,
+ lock the rev we're checking out. */
+ retcode = RCS_checkout (rcs, "", rev ? corev : NULL, NULL, RUN_TTY,
+ lockflag, 1);
+ if (retcode != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to check out `%s'", rcs);
+ return (1);
+ }
+
+ if (corev != NULL)
+ free (corev);
+
+ retcode = RCS_checkin (rcs, NULL, message, rev, RCS_FLAGS_DEAD, 1);
+ if (retcode != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to commit dead revision for `%s'", rcs);
+ return (1);
+ }
+
+ if (rev != NULL)
+ free (rev);
+
+ if (!branch)
+ {
+ /* this was the head; really move it into the Attic */
+ tmp = xmalloc(strlen(repository) +
+ sizeof('/') +
+ sizeof(CVSATTIC) +
+ sizeof('/') +
+ strlen(file) +
+ sizeof(RCSEXT) + 1);
+ (void) sprintf (tmp, "%s/%s", repository, CVSATTIC);
+ omask = umask (cvsumask);
+ (void) CVS_MKDIR (tmp, 0777);
+ (void) umask (omask);
+ (void) sprintf (tmp, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
+
+ if (strcmp (rcs, tmp) != 0
+ && rename (rcs, tmp) == -1
+ && (isreadable (rcs) || !isreadable (tmp)))
+ {
+ free(tmp);
+ return (1);
+ }
+ free(tmp);
+ }
+
+ /* Print message that file was removed. */
+ (void) printf ("%s <-- %s\n", rcs, file);
+ (void) printf ("new revision: delete; ");
+ (void) printf ("previous revision: %s\n", prev_rev);
+ (void) printf ("done\n");
+ free(prev_rev);
+
+ Scratch_Entry (entries, file);
+ return (0);
+}
+
+/*
+ * Do the actual checkin for added files
+ */
+static int
+finaladd (file, rev, tag, options, update_dir, repository, entries)
+ char *file;
+ char *rev;
+ char *tag;
+ char *options;
+ char *update_dir;
+ char *repository;
+ List *entries;
+{
+ int ret;
+ char tmp[PATH_MAX];
+ char rcs[PATH_MAX];
+
+ locate_rcs (file, repository, rcs);
+ ret = Checkin ('A', file, update_dir, repository, rcs, rev, tag, options,
+ message, entries);
+ if (ret == 0)
+ {
+ (void) sprintf (tmp, "%s/%s%s", CVSADM, file, CVSEXT_LOG);
+ (void) unlink_file (tmp);
+ }
+ else
+ fixaddfile (file, repository);
+
+ (void) time (&last_register_time);
+
+ return (ret);
+}
+
+/*
+ * Unlock an rcs file
+ */
+static void
+unlockrcs (file, repository)
+ char *file;
+ char *repository;
+{
+ char rcs[PATH_MAX];
+ int retcode = 0;
+
+ locate_rcs (file, repository, rcs);
+
+ if ((retcode = RCS_unlock (rcs, NULL, 0)) != 0)
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "could not unlock %s", rcs);
+}
+
+/*
+ * remove a partially added file. if we can parse it, leave it alone.
+ */
+static void
+fixaddfile (file, repository)
+ char *file;
+ char *repository;
+{
+ RCSNode *rcsfile;
+ char rcs[PATH_MAX];
+ int save_really_quiet;
+
+ locate_rcs (file, repository, rcs);
+ save_really_quiet = really_quiet;
+ really_quiet = 1;
+ if ((rcsfile = RCS_parsercsfile (rcs)) == NULL)
+ (void) unlink_file (rcs);
+ else
+ freercsnode (&rcsfile);
+ really_quiet = save_really_quiet;
+}
+
+/*
+ * put the branch back on an rcs file
+ */
+static void
+fixbranch (file, repository, branch)
+ char *file;
+ char *repository;
+ char *branch;
+{
+ char rcs[PATH_MAX];
+ int retcode = 0;
+
+ if (branch != NULL && branch[0] != '\0')
+ {
+ locate_rcs (file, repository, rcs);
+ 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);
+ }
+}
+
+/*
+ * do the initial part of a file add for the named file. if adding
+ * with a tag, put the file in the Attic and point the symbolic tag
+ * at the committed revision.
+ */
+
+static int
+checkaddfile (file, repository, tag, options, rcsnode)
+ char *file;
+ char *repository;
+ char *tag;
+ char *options;
+ RCSNode **rcsnode;
+{
+ char rcs[PATH_MAX];
+ char fname[PATH_MAX];
+ mode_t omask;
+ int retcode = 0;
+ int newfile = 0;
+
+ if (tag)
+ {
+ (void) sprintf(rcs, "%s/%s", repository, CVSATTIC);
+ omask = umask (cvsumask);
+ if (CVS_MKDIR (rcs, 0777) != 0 && errno != EEXIST)
+ error (1, errno, "cannot make directory `%s'", rcs);;
+ (void) umask (omask);
+ (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
+ }
+ else
+ locate_rcs (file, repository, rcs);
+
+ if (isreadable(rcs))
+ {
+ /* file has existed in the past. Prepare to resurrect. */
+ char oldfile[PATH_MAX];
+ char *rev;
+ RCSNode *rcsfile;
+
+ if (tag == NULL)
+ {
+ /* we are adding on the trunk, so move the file out of the
+ Attic. */
+ strcpy (oldfile, rcs);
+ sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
+
+ if (strcmp (oldfile, rcs) == 0
+ || rename (oldfile, rcs) != 0
+ || isreadable (oldfile)
+ || !isreadable (rcs))
+ {
+ error (0, 0, "failed to move `%s' out of the attic.",
+ file);
+ return (1);
+ }
+ }
+
+ if ((rcsfile = *rcsnode) == NULL)
+ {
+ error (0, 0, "could not find parsed rcsfile %s", file);
+ return (1);
+ }
+
+ rev = RCS_getversion (rcsfile, tag, NULL, 1, 0);
+ /* and lock it */
+ if (lock_RCS (file, rcs, rev, repository)) {
+ error (0, 0, "cannot lock `%s'.", rcs);
+ free (rev);
+ return (1);
+ }
+
+ free (rev);
+ } else {
+ /* this is the first time we have ever seen this file; create
+ an rcs file. */
+ run_setup ("%s%s -x,v/ -i", Rcsbin, RCS);
+
+ (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);
+
+ /* 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)
+ {
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "could not create %s", rcs);
+ return (1);
+ }
+ newfile = 1;
+ }
+
+ /* when adding a file for the first time, and using a tag, we need
+ to create a dead revision on the trunk. */
+ if (tag && newfile)
+ {
+ char *tmp;
+
+ /* move the new file out of the way. */
+ (void) sprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, file);
+ rename_file (file, fname);
+ copy_file (DEVNULL, file);
+
+ tmp = xmalloc (strlen (file) + strlen (tag) + 80);
+ /* commit a dead revision. */
+ (void) sprintf (tmp, "file %s was initially added on branch %s.",
+ file, tag);
+ retcode = RCS_checkin (rcs, NULL, tmp, NULL,
+ RCS_FLAGS_DEAD | RCS_FLAGS_QUIET, 0);
+ free (tmp);
+ if (retcode != 0)
+ {
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "could not create initial dead revision %s", rcs);
+ return (1);
+ }
+
+ /* put the new file back where it was */
+ rename_file (fname, file);
+
+ /* and lock it once again. */
+ if (lock_RCS (file, rcs, NULL, repository)) {
+ error (0, 0, "cannot lock `%s'.", rcs);
+ return (1);
+ }
+ }
+
+ if (tag != NULL)
+ {
+ /* when adding with a tag, we need to stub a branch, if it
+ doesn't already exist. */
+ RCSNode *rcsfile;
+
+ rcsfile = RCS_parse (file, repository);
+ if (rcsfile == NULL)
+ {
+ error (0, 0, "could not read %s", rcs);
+ return (1);
+ }
+
+ if (!RCS_nodeisbranch (rcsfile, tag)) {
+ /* branch does not exist. Stub it. */
+ char *head;
+ char *magicrev;
+
+ head = RCS_getversion (rcsfile, NULL, NULL, 0, 0);
+ magicrev = RCS_magicrev (rcsfile, head);
+ if ((retcode = RCS_settag(rcs, tag, magicrev)) != 0)
+ {
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "could not stub branch %s for %s", tag, rcs);
+ return (1);
+ }
+
+ freercsnode (&rcsfile);
+
+ /* reparse the file, then add it to our list. */
+ rcsfile = RCS_parse (file, repository);
+ if (rcsfile == NULL)
+ {
+ error (0, 0, "could not reparse %s", rcs);
+ return (1);
+ }
+
+ free (head);
+ free (magicrev);
+ }
+ else
+ {
+ /* lock the branch. (stubbed branches need not be locked.) */
+ if (lock_RCS (file, rcs, NULL, repository)) {
+ error (0, 0, "cannot lock `%s'.", rcs);
+ return (1);
+ }
+ }
+
+ if (rcsnode)
+ freercsnode(rcsnode);
+ *rcsnode = rcsfile;
+ }
+
+ fileattr_newfile (file);
+
+ fix_rcs_modes (rcs, file);
+ return (0);
+}
+
+/*
+ * Lock the rcs file ``file''
+ */
+static int
+lockrcsfile (file, repository, rev)
+ char *file;
+ char *repository;
+ char *rev;
+{
+ char rcs[PATH_MAX];
+
+ locate_rcs (file, repository, rcs);
+ if (lock_RCS (file, rcs, rev, repository) != 0)
+ return (1);
+ else
+ return (0);
+}
+
+/*
+ * Attempt to place a lock on the RCS file; returns 0 if it could and 1 if it
+ * couldn't. If the RCS file currently has a branch as the head, we must
+ * move the head back to the trunk before locking the file, and be sure to
+ * put the branch back as the head if there are any errors.
+ */
+static int
+lock_RCS (user, rcs, rev, repository)
+ char *user;
+ char *rcs;
+ char *rev;
+ char *repository;
+{
+ RCSNode *rcsfile;
+ char *branch = NULL;
+ int err = 0;
+
+ /*
+ * For a specified, numeric revision of the form "1" or "1.1", (or when
+ * no revision is specified ""), definitely move the branch to the trunk
+ * before locking the RCS file.
+ *
+ * The assumption is that if there is more than one revision on the trunk,
+ * the head points to the trunk, not a branch... and as such, it's not
+ * necessary to move the head in this case.
+ */
+ if (rev == NULL || (rev && isdigit (*rev) && numdots (rev) < 2))
+ {
+ if ((rcsfile = RCS_parsercsfile (rcs)) == NULL)
+ {
+ /* invalid rcs file? */
+ err = 1;
+ }
+ else
+ {
+ /* rcsfile is valid */
+ branch = xstrdup (rcsfile->branch);
+ freercsnode (&rcsfile);
+ if (branch != NULL)
+ {
+ if (RCS_setbranch (rcs, NULL) != 0)
+ {
+ error (0, 0, "cannot change branch to default for %s",
+ rcs);
+ if (branch)
+ free (branch);
+ return (1);
+ }
+ }
+ err = RCS_lock(rcs, NULL, 0);
+ }
+ }
+ else
+ {
+ (void) RCS_lock(rcs, rev, 1);
+ }
+
+ if (err == 0)
+ {
+ if (branch)
+ {
+ (void) strcpy (sbranch, branch);
+ free (branch);
+ }
+ else
+ sbranch[0] = '\0';
+ return (0);
+ }
+
+ /* try to restore the branch if we can on error */
+ if (branch != NULL)
+ fixbranch (user, repository, branch);
+
+ if (branch)
+ free (branch);
+ return (1);
+}
+
+/*
+ * Called when "add"ing files to the RCS respository, as it is necessary to
+ * preserve the file modes in the same fashion that RCS does. This would be
+ * automatic except that we are placing the RCS ,v file very far away from
+ * the user file, and I can't seem to convince RCS of the location of the
+ * user file. So we munge it here, after the ,v file has been successfully
+ * initialized with "rcs -i".
+ */
+static void
+fix_rcs_modes (rcs, user)
+ char *rcs;
+ char *user;
+{
+ struct stat sb;
+
+ if (stat (user, &sb) != -1)
+ (void) chmod (rcs, (int) sb.st_mode & ~0222);
+}
+
+/*
+ * free an UPDATE node's data (really nothing to do)
+ */
+void
+update_delproc (p)
+ Node *p;
+{
+ p->data = (char *) NULL;
+}
+
+/*
+ * Free the commit_info structure in p.
+ */
+static void
+ci_delproc (p)
+ Node *p;
+{
+ struct commit_info *ci;
+
+ ci = (struct commit_info *) p->data;
+ if (ci->rev)
+ free (ci->rev);
+ if (ci->tag)
+ free (ci->tag);
+ if (ci->options)
+ free (ci->options);
+ free (ci);
+}
+
+/*
+ * Free the commit_info structure in p.
+ */
+static void
+masterlist_delproc (p)
+ Node *p;
+{
+ struct master_lists *ml;
+
+ ml = (struct master_lists *) p->data;
+ dellist (&ml->ulist);
+ dellist (&ml->cilist);
+ free (ml);
+}
+
+/*
+ * Find an RCS file in the repository.
+ */
+static void
+locate_rcs (file, repository, rcs)
+ char *file;
+ char *repository;
+ char *rcs;
+{
+ (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
+ if (!isreadable (rcs))
+ {
+ (void) sprintf (rcs, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT);
+ if (!isreadable (rcs))
+ (void) sprintf (rcs, "%s/%s%s", repository, file, RCSEXT);
+ }
+}
diff --git a/contrib/cvs/src/create_adm.c b/contrib/cvs/src/create_adm.c
new file mode 100644
index 0000000..fd7fd4d
--- /dev/null
+++ b/contrib/cvs/src/create_adm.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Create Administration.
+ *
+ * Creates a CVS administration directory based on the argument repository; the
+ * "Entries" file is prefilled from the "initrecord" argument.
+ */
+
+#include "cvs.h"
+
+/* update_dir includes dir as its last component. */
+
+void
+Create_Admin (dir, update_dir, repository, tag, date)
+ char *dir;
+ char *update_dir;
+ char *repository;
+ char *tag;
+ char *date;
+{
+ FILE *fout;
+ char *cp;
+ char tmp[PATH_MAX];
+
+#ifdef SERVER_SUPPORT
+ if (trace)
+ {
+ char wd[PATH_MAX];
+ getwd (wd);
+ fprintf (stderr, "%c-> Create_Admin (%s, %s, %s, %s, %s) in %s\n",
+ (server_active) ? 'S' : ' ',
+ dir, update_dir, repository, tag ? tag : "",
+ date ? date : "", wd);
+ }
+#endif
+
+ if (noexec)
+ return;
+
+ if (dir != NULL)
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM);
+ else
+ (void) strcpy (tmp, CVSADM);
+ if (isfile (tmp))
+ error (1, 0, "there is a version in %s already", update_dir);
+
+ make_directory (tmp);
+
+ /* record the current cvs root for later use */
+
+ Create_Root (dir, CVSroot);
+ if (dir != NULL)
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_REP);
+ else
+ (void) strcpy (tmp, CVSADM_REP);
+ fout = fopen (tmp, "w+");
+ if (fout == NULL)
+ {
+ if (update_dir[0] == '\0')
+ error (1, errno, "cannot open %s", tmp);
+ else
+ error (1, errno, "cannot open %s/%s", update_dir, CVSADM_REP);
+ }
+ cp = repository;
+ strip_path (cp);
+
+#ifdef RELATIVE_REPOS
+ /*
+ * If the Repository file is to hold a relative path, try to strip off
+ * the leading CVSroot argument.
+ */
+ if (CVSroot != NULL)
+ {
+ char path[PATH_MAX];
+
+ (void) sprintf (path, "%s/", CVSroot);
+ if (strncmp (repository, path, strlen (path)) == 0)
+ cp = repository + strlen (path);
+ }
+#endif
+
+ if (fprintf (fout, "%s\n", cp) < 0)
+ {
+ if (update_dir[0] == '\0')
+ error (1, errno, "write to %s failed", tmp);
+ else
+ error (1, errno, "write to %s/%s failed", update_dir, CVSADM_REP);
+ }
+ if (fclose (fout) == EOF)
+ {
+ if (update_dir[0] == '\0')
+ error (1, errno, "cannot close %s", tmp);
+ else
+ error (1, errno, "cannot close %s/%s", update_dir, CVSADM_REP);
+ }
+
+ /* now, do the Entries file */
+ if (dir != NULL)
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENT);
+ else
+ (void) strcpy (tmp, CVSADM_ENT);
+ fout = fopen (tmp, "w+");
+ if (fout == NULL)
+ {
+ if (update_dir[0] == '\0')
+ error (1, errno, "cannot open %s", tmp);
+ else
+ error (1, errno, "cannot open %s/%s", update_dir, CVSADM_ENT);
+ }
+ if (fclose (fout) == EOF)
+ {
+ if (update_dir[0] == '\0')
+ error (1, errno, "cannot close %s", tmp);
+ else
+ error (1, errno, "cannot close %s/%s", update_dir, CVSADM_ENT);
+ }
+
+ /* Create a new CVS/Tag file */
+ WriteTag (dir, tag, date);
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ server_set_sticky (update_dir, repository, tag, date);
+ server_template (update_dir, repository);
+ }
+
+ if (trace)
+ {
+ fprintf (stderr, "%c<- Create_Admin\n",
+ (server_active) ? 'S' : ' ');
+ }
+#endif
+
+}
diff --git a/contrib/cvs/src/cvs.h b/contrib/cvs/src/cvs.h
new file mode 100644
index 0000000..8e50f5d
--- /dev/null
+++ b/contrib/cvs/src/cvs.h
@@ -0,0 +1,690 @@
+/* $CVSid: @(#)cvs.h 1.86 94/10/22 $ */
+
+/*
+ * basic information used in all source files
+ *
+ */
+
+
+#include "config.h" /* this is stuff found via autoconf */
+#include "options.h" /* these are some larger questions which
+ can't easily be automatically checked
+ for */
+
+/* Changed from if __STDC__ to ifdef __STDC__ because of Sun's acc compiler */
+
+#ifdef __STDC__
+#define PTR void *
+#else
+#define PTR char *
+#endif
+
+/* Add prototype support. */
+#ifndef PROTO
+#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)
+#define PROTO(ARGS) ARGS
+#else
+#define PROTO(ARGS) ()
+#endif
+#endif
+
+#include <stdio.h>
+
+/* Under OS/2, <stdio.h> doesn't define popen()/pclose(). */
+#ifdef USE_OWN_POPEN
+#include "popen.h"
+#endif
+
+#ifdef STDC_HEADERS
+#include <stdlib.h>
+#else
+extern void exit ();
+extern char *getenv();
+#endif
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_STRING_H
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+#ifdef SERVER_SUPPORT
+/* If the system doesn't provide strerror, it won't be declared in
+ string.h. */
+char *strerror ();
+#endif
+
+#include <fnmatch.h> /* This is supposed to be available on Posix systems */
+
+#include <ctype.h>
+#include <pwd.h>
+#include <signal.h>
+
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#else
+#ifndef errno
+extern int errno;
+#endif /* !errno */
+#endif /* HAVE_ERRNO_H */
+
+#include "system.h"
+
+#include "hash.h"
+#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
+#include "server.h"
+#include "client.h"
+#endif
+
+#ifdef MY_NDBM
+#include "myndbm.h"
+#else
+#include <ndbm.h>
+#endif /* MY_NDBM */
+
+#include "regex.h"
+#include "getopt.h"
+#include "wait.h"
+
+#include "rcs.h"
+
+
+/* XXX - for now this is static */
+#ifndef PATH_MAX
+#ifdef MAXPATHLEN
+#define PATH_MAX MAXPATHLEN+2
+#else
+#define PATH_MAX 1024+2
+#endif
+#endif /* PATH_MAX */
+
+/* just in case this implementation does not define this */
+#ifndef L_tmpnam
+#define L_tmpnam 50
+#endif
+
+
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Definitions for the CVS Administrative directory and the files it contains.
+ * Here as #define's to make changing the names a simple task.
+ */
+
+#ifdef USE_VMS_FILENAMES
+#define CVSADM "CVS"
+#define CVSADM_ENT "CVS/Entries."
+#define CVSADM_ENTBAK "CVS/Entries.Backup"
+#define CVSADM_ENTLOG "CVS/Entries.Log"
+#define CVSADM_ENTSTAT "CVS/Entries.Static"
+#define CVSADM_REP "CVS/Repository."
+#define CVSADM_ROOT "CVS/Root."
+#define CVSADM_CIPROG "CVS/Checkin.prog"
+#define CVSADM_UPROG "CVS/Update.prog"
+#define CVSADM_TAG "CVS/Tag."
+#define CVSADM_NOTIFY "CVS/Notify."
+#define CVSADM_NOTIFYTMP "CVS/Notify.tmp"
+#define CVSADM_BASE "CVS/Base"
+#define CVSADM_TEMPLATE "CVS/Template."
+#else /* USE_VMS_FILENAMES */
+#define CVSADM "CVS"
+#define CVSADM_ENT "CVS/Entries"
+#define CVSADM_ENTBAK "CVS/Entries.Backup"
+#define CVSADM_ENTLOG "CVS/Entries.Log"
+#define CVSADM_ENTSTAT "CVS/Entries.Static"
+#define CVSADM_REP "CVS/Repository"
+#define CVSADM_ROOT "CVS/Root"
+#define CVSADM_CIPROG "CVS/Checkin.prog"
+#define CVSADM_UPROG "CVS/Update.prog"
+#define CVSADM_TAG "CVS/Tag"
+#define CVSADM_NOTIFY "CVS/Notify"
+#define CVSADM_NOTIFYTMP "CVS/Notify.tmp"
+/* A directory in which we store base versions of files we currently are
+ editing with "cvs edit". */
+#define CVSADM_BASE "CVS/Base"
+/* File which contains the template for use in log messages. */
+#define CVSADM_TEMPLATE "CVS/Template"
+#endif /* USE_VMS_FILENAMES */
+
+/* This is the special directory which we use to store various extra
+ per-directory information in the repository. It must be the same as
+ CVSADM to avoid creating a new reserved directory name which users cannot
+ use, but is a separate #define because if anyone changes it (which I don't
+ recommend), one needs to deal with old, unconverted, repositories.
+
+ See fileattr.h for details about file attributes, the only thing stored
+ in CVSREP currently. */
+#define CVSREP "CVS"
+
+/*
+ * Definitions for the CVSROOT Administrative directory and the files it
+ * contains. This directory is created as a sub-directory of the $CVSROOT
+ * environment variable, and holds global administration information for the
+ * entire source repository beginning at $CVSROOT.
+ */
+#define CVSROOTADM "CVSROOT"
+#define CVSROOTADM_MODULES "modules"
+#define CVSROOTADM_LOGINFO "loginfo"
+#define CVSROOTADM_RCSINFO "rcsinfo"
+#define CVSROOTADM_COMMITINFO "commitinfo"
+#define CVSROOTADM_TAGINFO "taginfo"
+#define CVSROOTADM_EDITINFO "editinfo"
+#define CVSROOTADM_HISTORY "history"
+#define CVSROOTADM_VALTAGS "val-tags"
+#define CVSROOTADM_IGNORE "cvsignore"
+#define CVSROOTADM_CHECKOUTLIST "checkoutlist"
+#define CVSROOTADM_WRAPPER "cvswrappers"
+#define CVSROOTADM_NOTIFY "notify"
+#define CVSROOTADM_USERS "users"
+
+#define CVSNULLREPOS "Emptydir" /* an empty directory */
+
+/* Other CVS file names */
+
+/* Files go in the attic if the head main branch revision is dead,
+ otherwise they go in the regular repository directories. The whole
+ concept of having an attic is sort of a relic from before death
+ support but on the other hand, it probably does help the speed of
+ some operations (such as main branch checkouts and updates). */
+#define CVSATTIC "Attic"
+
+#define CVSLCK "#cvs.lock"
+#define CVSRFL "#cvs.rfl"
+#define CVSWFL "#cvs.wfl"
+#define CVSRFLPAT "#cvs.rfl.*" /* wildcard expr to match read locks */
+#define CVSEXT_LOG ",t"
+#define CVSPREFIX ",,"
+#define CVSDOTIGNORE ".cvsignore"
+#define CVSDOTWRAPPER ".cvswrappers"
+
+/* miscellaneous CVS defines */
+#define CVSEDITPREFIX "CVS: "
+#define CVSLCKAGE (60*60) /* 1-hour old lock files cleaned up */
+#define CVSLCKSLEEP 30 /* wait 30 seconds before retrying */
+#define CVSBRANCH "1.1.1" /* RCS branch used for vendor srcs */
+
+#ifdef USE_VMS_FILENAMES
+#define BAKPREFIX "_$"
+#define DEVNULL "NLA0:"
+#else /* USE_VMS_FILENAMES */
+#define BAKPREFIX ".#" /* when rcsmerge'ing */
+#ifndef DEVNULL
+#define DEVNULL "/dev/null"
+#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
+ * out This mimics the behaviour of RCS.
+ */
+#define TAG_HEAD "HEAD"
+#define TAG_BASE "BASE"
+
+/* Environment variable used by CVS */
+#define CVSREAD_ENV "CVSREAD" /* make files read-only */
+#define CVSREAD_DFLT FALSE /* writable files by default */
+
+#define RCSBIN_ENV "RCSBIN" /* RCS binary directory */
+/* #define RCSBIN_DFLT Set by config.h */
+
+#define EDITOR1_ENV "CVSEDITOR" /* which editor to use */
+#define EDITOR2_ENV "VISUAL" /* which editor to use */
+#define EDITOR3_ENV "EDITOR" /* which editor to use */
+/* #define EDITOR_DFLT Set by config.h */
+
+#define CVSROOT_ENV "CVSROOT" /* source directory root */
+#define CVSROOT_DFLT NULL /* No dflt; must set for checkout */
+
+#define IGNORE_ENV "CVSIGNORE" /* More files to ignore */
+#define WRAPPER_ENV "CVSWRAPPERS" /* name of the wrapper file */
+
+#define CVSUMASK_ENV "CVSUMASK" /* Effective umask for repository */
+/* #define CVSUMASK_DFLT Set by config.h */
+
+/*
+ * If the beginning of the Repository matches the following string, strip it
+ * so that the output to the logfile does not contain a full pathname.
+ *
+ * If the CVSROOT environment variable is set, it overrides this define.
+ */
+#define REPOS_STRIP "/master/"
+
+/*
+ * The maximum number of files per each CVS directory. This is mainly for
+ * sizing arrays statically rather than dynamically. 3000 seems plenty for
+ * now.
+ */
+#define MAXFILEPERDIR 3000
+#define MAXLINELEN 5000 /* max input line from a file */
+#define MAXPROGLEN 30000 /* max program length to system() */
+#define MAXLISTLEN 40000 /* For [A-Z]list holders */
+#define MAXDATELEN 50 /* max length for a date */
+
+/* structure of a entry record */
+struct entnode
+{
+ char *user;
+ char *version;
+ char *timestamp;
+ char *options;
+ char *tag;
+ char *date;
+ char *conflict;
+};
+typedef struct entnode Entnode;
+
+/* The type of request that is being done in do_module() */
+enum mtype
+{
+ CHECKOUT, TAG, PATCH, EXPORT
+};
+
+/*
+ * 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
+ * Update_Logfile in commit, import, and add.
+ */
+enum classify_type
+{
+ T_UNKNOWN = 1, /* no old-style analog existed */
+ T_CONFLICT, /* C (conflict) list */
+ T_NEEDS_MERGE, /* G (needs merging) list */
+ T_MODIFIED, /* M (needs checked in) list */
+ T_CHECKOUT, /* O (needs checkout) list */
+ T_ADDED, /* A (added file) list */
+ T_REMOVED, /* R (removed file) list */
+ T_REMOVE_ENTRY, /* W (removed entry) list */
+ T_UPTODATE, /* File is up-to-date */
+#ifdef SERVER_SUPPORT
+ T_PATCH, /* P Like C, but can patch */
+#endif
+ T_TITLE /* title for node type */
+};
+typedef enum classify_type Ctype;
+
+/*
+ * a struct vers_ts contains all the information about a file including the
+ * user and rcs file names, and the version checked out and the head.
+ *
+ * this is usually obtained from a call to Version_TS which takes a tag argument
+ * for the RCS file if desired
+ */
+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. */
+ char *vn_user;
+
+ /* Numeric revision number corresponding to ->vn_tag (->vn_tag
+ will often be symbolic). */
+ char *vn_rcs;
+ /* If ->tag corresponds to a tag which really exists in this file,
+ this is just a copy of ->tag. If not, this is either NULL or
+ the head revision. (Or something like that, see RCS_getversion
+ and friends). */
+ char *vn_tag;
+
+ /* This is the timestamp from stating the file in the working directory.
+ It is NULL if there is no file in the working directory. */
+ char *ts_user;
+ /* Timestamp from CVS/Entries. For the server, ts_user and ts_rcs
+ are computed in a slightly different way, but the fact remains that
+ if they are equal the file in the working directory is unmodified
+ and if they differ it is modified. */
+ char *ts_rcs;
+
+ /* Options from CVS/Entries (keyword expansion). */
+ char *options;
+
+ /* If non-NULL, there was a conflict (or merely a merge? See merge_file)
+ and the time stamp in this field is the time stamp of the working
+ directory file which was created with the conflict markers in it.
+ This is from CVS/Entries. */
+ char *ts_conflict;
+
+ /* Tag specified on the command line, or if none, tag stored in
+ CVS/Entries. */
+ char *tag;
+ /* Date specified on the command line, or if none, date stored in
+ CVS/Entries. */
+ char *date;
+
+ /* Pointer to entries file node */
+ Entnode *entdata;
+
+ /* Pointer to parsed src file info */
+ RCSNode *srcfile;
+};
+typedef struct vers_ts Vers_TS;
+
+/*
+ * structure used for list-private storage by Entries_Open() and
+ * Version_TS().
+ */
+struct stickydirtag
+{
+ int aflag;
+ char *tag;
+ char *date;
+ char *options;
+};
+
+/* Flags for find_{names,dirs} routines */
+#define W_LOCAL 0x01 /* look for files locally */
+#define W_REPOS 0x02 /* look for files in the repository */
+#define W_ATTIC 0x04 /* look for files in the attic */
+
+/* Flags for return values of direnter procs for the recursion processor */
+enum direnter_type
+{
+ R_PROCESS = 1, /* process files and maybe dirs */
+ R_SKIP_FILES, /* don't process files in this dir */
+ R_SKIP_DIRS, /* don't process sub-dirs */
+ R_SKIP_ALL /* don't process files or dirs */
+};
+typedef enum direnter_type Dtype;
+
+extern char *program_name, *program_path, *command_name;
+extern char *Rcsbin, *Editor, *CVSroot;
+extern char *CVSADM_Root;
+extern int cvsadmin_root;
+extern char *CurDir;
+extern int really_quiet, quiet;
+extern int use_editor;
+extern int cvswrite;
+extern mode_t cvsumask;
+
+extern int trace; /* Show all commands */
+extern int noexec; /* Don't modify disk anywhere */
+extern int logoff; /* Don't write history entry */
+
+extern char hostname[];
+
+/* Externs that are included directly in the CVS sources */
+
+int RCS_settag PROTO((const char *, const char *, const char *));
+int RCS_deltag PROTO((const char *, const char *, int));
+int RCS_setbranch PROTO((const char *, const char *));
+int RCS_lock PROTO((const char *, const char *, int));
+int RCS_unlock PROTO((const char *, const char *, int));
+int RCS_merge PROTO((const char *, const char *, const char *, const char *));
+int RCS_checkout PROTO ((char *rcsfile, char *workfile, char *tag,
+ char *options,
+ char *sout, int flags, int noerr));
+/* Flags used by RCS_* functions. See the description of the individual
+ functions for which flags mean what for each function. */
+#define RCS_FLAGS_LOCK 1
+#define RCS_FLAGS_FORCE 2
+#define RCS_FLAGS_DEAD 4
+#define RCS_FLAGS_QUIET 8
+#define RCS_FLAGS_MODTIME 16
+int RCS_checkin PROTO ((char *rcsfile, char *workfile, char *message,
+ char *rev, int flags, int noerr));
+
+
+
+#include "error.h"
+
+DBM *open_module PROTO((void));
+FILE *open_file PROTO((const char *, const char *));
+List *Find_Directories PROTO((char *repository, int which));
+void Entries_Close PROTO((List *entries));
+List *Entries_Open PROTO((int aflag));
+char *Make_Date PROTO((char *rawdate));
+char *Name_Repository PROTO((char *dir, char *update_dir));
+char *Name_Root PROTO((char *dir, char *update_dir));
+void Create_Root PROTO((char *dir, char *rootdir));
+int same_directories PROTO((char *dir1, char *dir2));
+char *Short_Repository PROTO((char *repository));
+char *gca PROTO((char *rev1, char *rev2));
+char *getcaller PROTO((void));
+char *time_stamp PROTO((char *file));
+char *xmalloc PROTO((size_t bytes));
+void *xrealloc PROTO((void *ptr, size_t bytes));
+char *xstrdup PROTO((const char *str));
+void strip_trailing_newlines PROTO((char *str));
+int No_Difference PROTO((char *file, Vers_TS * vers, List * entries,
+ char *repository, char *update_dir));
+typedef int (*CALLPROC) PROTO((char *repository, char *value));
+int Parse_Info PROTO((char *infofile, char *repository, CALLPROC callproc, int all));
+int Reader_Lock PROTO((char *xrepository));
+typedef RETSIGTYPE (*SIGCLEANUPPROC) PROTO(());
+int SIG_register PROTO((int sig, SIGCLEANUPPROC sigcleanup));
+int Writer_Lock PROTO((List * list));
+int isdir PROTO((const char *file));
+int isfile PROTO((const char *file));
+int islink PROTO((const char *file));
+int isreadable PROTO((const char *file));
+int iswritable PROTO((const char *file));
+int isaccessible PROTO((const char *file, const int mode));
+int isabsolute PROTO((const char *filename));
+char *last_component PROTO((char *path));
+char *get_homedir PROTO ((void));
+
+int numdots PROTO((const char *s));
+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));
+int update PROTO((int argc, char *argv[]));
+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));
+
+void Lock_Cleanup PROTO((void));
+
+/* Writelock an entire subtree, well the part specified by ARGC, ARGV, LOCAL,
+ and AFLAG, anyway. */
+void lock_tree_for_write PROTO ((int argc, char **argv, int local, int aflag));
+
+/* Remove locks set by lock_tree_for_write. Currently removes readlocks
+ too. */
+void lock_tree_cleanup PROTO ((void));
+
+void ParseTag PROTO((char **tagp, char **datep));
+void Scratch_Entry PROTO((List * list, char *fname));
+void WriteTag PROTO((char *dir, char *tag, char *date));
+void cat_module PROTO((int status));
+void check_entries PROTO((char *dir));
+void close_module PROTO((DBM * db));
+void copy_file PROTO((const char *from, const char *to));
+void (*error_set_cleanup PROTO((void (*) (void)))) PROTO ((void));
+void fperror PROTO((FILE * fp, int status, int errnum, char *message,...));
+void free_names PROTO((int *pargc, char *argv[]));
+void freevers_ts PROTO((Vers_TS ** versp));
+
+extern int ign_name PROTO ((char *name));
+void ign_add PROTO((char *ign, int hold));
+void ign_add_file PROTO((char *file, int hold));
+void ign_setup PROTO((void));
+void ign_dir_add PROTO((char *name));
+int ignore_directory PROTO((char *name));
+typedef void (*Ignore_proc) PROTO ((char *, char *));
+extern void ignore_files PROTO ((List *, char *, Ignore_proc));
+extern int ign_inhibit_server;
+extern int ign_case;
+
+#include "update.h"
+
+void line2argv PROTO((int *pargc, char *argv[], char *line));
+void make_directories PROTO((const char *name));
+void make_directory PROTO((const char *name));
+void rename_file PROTO((const char *from, const char *to));
+/* Expand wildcards in each element of (ARGC,ARGV). This is according to the
+ files which exist in the current directory, and accordingly to OS-specific
+ conventions regarding wildcard syntax. It might be desirable to change the
+ former in the future (e.g. "cvs status *.h" including files which don't exist
+ in the working directory). The result is placed in *PARGC and *PARGV;
+ the *PARGV array itself and all the strings it contains are newly
+ malloc'd. It is OK to call it with PARGC == &ARGC or PARGV == &ARGV. */
+extern void expand_wild PROTO ((int argc, char **argv,
+ int *pargc, char ***pargv));
+
+void strip_path PROTO((char *path));
+void strip_trailing_slashes PROTO((char *path));
+void update_delproc PROTO((Node * p));
+void usage PROTO((const char *const *cpp));
+void xchmod PROTO((char *fname, int writable));
+char *xgetwd PROTO((void));
+int Checkin PROTO((int type, char *file, char *update_dir,
+ char *repository, char *rcs, char *rev,
+ char *tag, char *options, char *message, List *entries));
+Ctype Classify_File PROTO((char *file, char *tag, char *date, char *options,
+ int force_tag_match, int aflag, char *repository,
+ List *entries, RCSNode *rcsnode, Vers_TS **versp,
+ char *update_dir, int pipeout));
+List *Find_Names PROTO((char *repository, int which, int aflag,
+ List ** optentries));
+void Register PROTO((List * list, char *fname, char *vn, char *ts,
+ char *options, char *tag, char *date, char *ts_conflict));
+void Update_Logfile PROTO((char *repository, char *xmessage, char *xrevision,
+ FILE * xlogfp, List * xchanges));
+Vers_TS *Version_TS PROTO((char *repository, char *options, char *tag,
+ char *date, char *user, int force_tag_match,
+ int set_time, List * entries, RCSNode * rcs));
+void do_editor PROTO((char *dir, char **messagep,
+ char *repository, List * changes));
+
+typedef int (*CALLBACKPROC) PROTO((int *pargc, char *argv[], char *where,
+ char *mwhere, char *mfile, int horten, int local_specified,
+ char *omodule, char *msg));
+
+/* This is the structure that the recursion processor passes to the
+ fileproc to tell it about a particular file. */
+struct file_info
+{
+ /* Name of the file, without any directory component. */
+ char *file;
+
+ /* Name of the directory we are in, relative to the directory in
+ which this command was issued. We have cd'd to this directory
+ (either in the working directory or in the repository, depending
+ on which sort of recursion we are doing). If we are in the directory
+ in which the command was issued, this is "". */
+ char *update_dir;
+
+ /* update_dir and file put together, with a slash between them as
+ necessary. This is the proper way to refer to the file in user
+ messages. */
+ char *fullname;
+
+ /* Name of the directory corresponding to the repository which contains
+ this file. */
+ char *repository;
+
+ /* The pre-parsed entries for this directory. */
+ List *entries;
+
+ RCSNode *rcs;
+};
+
+typedef int (*FILEPROC) PROTO((struct file_info *finfo));
+typedef int (*FILESDONEPROC) PROTO((int err, char *repository, char *update_dir));
+typedef Dtype (*DIRENTPROC) PROTO((char *dir, char *repos, char *update_dir));
+typedef int (*DIRLEAVEPROC) PROTO((char *dir, int err, char *update_dir));
+
+extern int mkmodules PROTO ((char *dir));
+extern int init PROTO ((int argc, char **argv));
+
+int do_module PROTO((DBM * db, char *mname, enum mtype m_type, char *msg,
+ CALLBACKPROC callback_proc, char *where, int shorten,
+ int local_specified, int run_module_prog, char *extra_arg));
+int do_recursion PROTO((FILEPROC xfileproc, FILESDONEPROC xfilesdoneproc,
+ DIRENTPROC xdirentproc, DIRLEAVEPROC xdirleaveproc,
+ Dtype xflags, int xwhich, int xaflag, int xreadlock,
+ int xdosrcs));
+void history_write PROTO((int type, char *update_dir, char *revs, char *name,
+ char *repository));
+int start_recursion PROTO((FILEPROC fileproc, FILESDONEPROC filesdoneproc,
+ DIRENTPROC direntproc, DIRLEAVEPROC dirleaveproc,
+ int argc, char *argv[], int local, int which,
+ int aflag, int readlock, char *update_preload,
+ int dosrcs, int wd_is_repos));
+void SIG_beginCrSect PROTO((void));
+void SIG_endCrSect PROTO((void));
+void read_cvsrc PROTO((int *argc, char ***argv, char *cmdname));
+
+char *make_message_rcslegal PROTO((char *message));
+
+/* flags for run_exec(), the fast system() for CVS */
+#define RUN_NORMAL 0x0000 /* no special behaviour */
+#define RUN_COMBINED 0x0001 /* stdout is duped to stderr */
+#define RUN_REALLY 0x0002 /* do the exec, even if noexec is on */
+#define RUN_STDOUT_APPEND 0x0004 /* append to stdout, don't truncate */
+#define RUN_STDERR_APPEND 0x0008 /* append to stderr, don't truncate */
+#define RUN_SIGIGNORE 0x0010 /* ignore interrupts for command */
+#define RUN_TTY (char *)0 /* for the benefit of lint */
+
+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));
+
+/* other similar-minded stuff from run.c. */
+FILE *run_popen PROTO((const char *, const char *));
+int piped_child PROTO((char **, int *, int *));
+void close_on_exec PROTO((int));
+int filter_stream_through_program PROTO((int, int, char **, pid_t *));
+
+pid_t waitpid PROTO((pid_t, int *, int));
+
+/* Wrappers. */
+
+typedef enum { WRAP_MERGE, WRAP_COPY } WrapMergeMethod;
+typedef enum { WRAP_TOCVS, WRAP_FROMCVS, WRAP_CONFLICT } WrapMergeHas;
+
+void wrap_setup PROTO((void));
+int wrap_name_has PROTO((const char *name,WrapMergeHas has));
+char *wrap_tocvs_process_file PROTO((const char *fileName));
+int wrap_merge_is_copy PROTO((const char *fileName));
+char *wrap_fromcvs_process_file PROTO((const char *fileName));
+void wrap_add_file PROTO((const char *file,int temp));
+void wrap_add PROTO((char *line,int temp));
+
+/* Pathname expansion */
+char *expand_path PROTO((char *name, char *file, int line));
+
+/* User variables. */
+extern List *variable_list;
+
+extern void variable_set PROTO ((char *nameval));
+
+int watch PROTO ((int argc, char **argv));
+int edit PROTO ((int argc, char **argv));
+int unedit PROTO ((int argc, char **argv));
+int editors PROTO ((int argc, char **argv));
+int watchers PROTO ((int argc, char **argv));
+extern int annotate PROTO ((int argc, char **argv));
+
+#if defined(AUTH_CLIENT_SUPPORT) || defined(AUTH_SERVER_SUPPORT)
+char *scramble PROTO ((char *str));
+char *descramble PROTO ((char *str));
+#endif /* AUTH_CLIENT_SUPPORT || AUTH_SERVER_SUPPORT */
+
+extern void tag_check_valid PROTO ((char *, int, char **, int, int, char *));
+
+extern void cvs_output PROTO ((char *, size_t));
+extern void cvs_outerr PROTO ((char *, size_t));
diff --git a/contrib/cvs/src/cvsbug.sh b/contrib/cvs/src/cvsbug.sh
new file mode 100755
index 0000000..ab26cfc
--- /dev/null
+++ b/contrib/cvs/src/cvsbug.sh
@@ -0,0 +1,528 @@
+#! /bin/sh
+# Submit a problem report to a GNATS site.
+# Copyright (C) 1993 Free Software Foundation, Inc.
+# Contributed by Brendan Kehoe (brendan@cygnus.com), based on a
+# version written by Heinz G. Seidl (hgs@ide.com).
+#
+# This file is part of GNU GNATS.
+# Modified by Berliner for CVS.
+#
+#ident "@(#)cvs/src:$Name: $:$Id: cvsbug.sh,v 1.10 1995/11/15 00:18:00 woods Exp $"
+#
+# GNU GNATS is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# GNU GNATS is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU GNATS; see the file COPYING. If not, write to
+# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# The version of this send-pr.
+VERSION=3.2
+
+# The submitter-id for your site.
+SUBMITTER=net
+
+## # Where the GNATS directory lives, if at all.
+## [ -z "$GNATS_ROOT" ] &&
+## GNATS_ROOT=/usr/local/lib/gnats/gnats-db
+
+# The default mail address for PR submissions.
+GNATS_ADDR=bug-cvs@prep.ai.mit.edu
+
+## # Where the gnats category tree lives.
+## DATADIR=/usr/local/lib
+
+## # If we've been moved around, try using GCC_EXEC_PREFIX.
+## [ ! -d $DATADIR/gnats -a -d "$GCC_EXEC_PREFIX" ] && DATADIR=${GCC_EXEC_PREFIX}..
+
+# The default release for this host.
+DEFAULT_RELEASE="xVERSIONx"
+
+# The default organization.
+DEFAULT_ORGANIZATION="net"
+
+## # The default site to look for.
+## GNATS_SITE=unknown
+
+## # Newer config information?
+## [ -f ${GNATS_ROOT}/gnats-adm/config ] && . ${GNATS_ROOT}/gnats-adm/config
+
+# What mailer to use. This must come after the config file, since it is
+# host-dependent.
+if [ -f /usr/sbin/sendmail ]; then
+ MAIL_AGENT="/usr/sbin/sendmail -oi -t"
+else
+ MAIL_AGENT="/usr/lib/sendmail -oi -t"
+fi
+MAILER=`echo $MAIL_AGENT | sed -e 's, .*,,'`
+if [ ! -f "$MAILER" ] ; then
+ echo "$COMMAND: Cannot file mail program \"$MAILER\"."
+ echo "$COMMAND: Please fix the MAIL_AGENT entry in the $COMMAND file."
+ exit 1
+fi
+
+if test "`echo -n foo`" = foo ; then
+ ECHON=bsd
+elif test "`echo 'foo\c'`" = foo ; then
+ ECHON=sysv
+else
+ ECHON=none
+fi
+
+if [ $ECHON = bsd ] ; then
+ ECHON1="echo -n"
+ ECHON2=
+elif [ $ECHON = sysv ] ; then
+ ECHON1=echo
+ ECHON2='\c'
+else
+ ECHON1=echo
+ ECHON2=
+fi
+
+#
+
+[ -z "$TMPDIR" ] && TMPDIR=/tmp
+
+TEMP=$TMPDIR/p$$
+BAD=$TMPDIR/pbad$$
+REF=$TMPDIR/pf$$
+
+if [ -z "$LOGNAME" -a -n "$USER" ]; then
+ LOGNAME=$USER
+fi
+
+FROM="$LOGNAME"
+REPLY_TO="$LOGNAME"
+
+# Find out the name of the originator of this PR.
+if [ -n "$NAME" ]; then
+ ORIGINATOR="$NAME"
+elif [ -f $HOME/.fullname ]; then
+ ORIGINATOR="`sed -e '1q' $HOME/.fullname`"
+elif [ -f /bin/domainname ]; then
+ if [ "`/bin/domainname`" != "" -a -f /usr/bin/ypcat ]; then
+ # Must use temp file due to incompatibilities in quoting behavior
+ # and to protect shell metacharacters in the expansion of $LOGNAME
+ /usr/bin/ypcat passwd 2>/dev/null | cat - /etc/passwd | grep "^$LOGNAME:" |
+ cut -f5 -d':' | sed -e 's/,.*//' > $TEMP
+ ORIGINATOR="`cat $TEMP`"
+ rm -f $TEMP
+ fi
+fi
+
+if [ "$ORIGINATOR" = "" ]; then
+ grep "^$LOGNAME:" /etc/passwd | cut -f5 -d':' | sed -e 's/,.*//' > $TEMP
+ ORIGINATOR="`cat $TEMP`"
+ rm -f $TEMP
+fi
+
+if [ -n "$ORGANIZATION" ]; then
+ if [ -f "$ORGANIZATION" ]; then
+ ORGANIZATION="`cat $ORGANIZATION`"
+ fi
+else
+ if [ -n "$DEFAULT_ORGANIZATION" ]; then
+ ORGANIZATION="$DEFAULT_ORGANIZATION"
+ elif [ -f $HOME/.organization ]; then
+ ORGANIZATION="`cat $HOME/.organization`"
+ elif [ -f $HOME/.signature ]; then
+ ORGANIZATION="`cat $HOME/.signature`"
+ fi
+fi
+
+# If they don't have a preferred editor set, then use
+if [ -z "$VISUAL" ]; then
+ if [ -z "$EDITOR" ]; then
+ EDIT=vi
+ else
+ EDIT="$EDITOR"
+ fi
+else
+ EDIT="$VISUAL"
+fi
+
+# Find out some information.
+SYSTEM=`( [ -f /bin/uname ] && /bin/uname -a ) || \
+ ( [ -f /usr/bin/uname ] && /usr/bin/uname -a ) || echo ""`
+ARCH=`[ -f /bin/arch ] && /bin/arch`
+MACHINE=`[ -f /bin/machine ] && /bin/machine`
+
+COMMAND=`echo $0 | sed -e 's,.*/,,'`
+## USAGE="Usage: $COMMAND [-PVL] [-t address] [-f filename] [--request-id]
+USAGE="Usage: $COMMAND [-PVL]
+[--version]"
+REMOVE=
+BATCH=
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -r) ;; # Ignore for backward compat.
+## -t | --to) if [ $# -eq 1 ]; then echo "$USAGE"; exit 1; fi
+## shift ; GNATS_ADDR="$1"
+## EXPLICIT_GNATS_ADDR=true
+## ;;
+## -f | --file) if [ $# -eq 1 ]; then echo "$USAGE"; exit 1; fi
+## shift ; IN_FILE="$1"
+## if [ "$IN_FILE" != "-" -a ! -r "$IN_FILE" ]; then
+## echo "$COMMAND: cannot read $IN_FILE"
+## exit 1
+## fi
+## ;;
+ -b | --batch) BATCH=true ;;
+ -p | -P | --print) PRINT=true ;;
+ -L | --list) FORMAT=norm ;;
+ -l | -CL | --lisp) FORMAT=lisp ;;
+## --request-id) REQUEST_ID=true ;;
+ -h | --help) echo "$USAGE"; exit 0 ;;
+ -V | --version) echo "$VERSION"; exit 0 ;;
+ -*) echo "$USAGE" ; exit 1 ;;
+ *) echo "$USAGE" ; exit 1
+## if [ -z "$USER_GNATS_SITE" ]; then
+## if [ ! -r "$DATADIR/gnats/$1" ]; then
+## echo "$COMMAND: the GNATS site $1 does not have a categories list."
+## exit 1
+## else
+## # The site name is the alias they'll have to have created.
+## USER_GNATS_SITE=$1
+## fi
+## else
+## echo "$USAGE" ; exit 1
+## fi
+ ;;
+ esac
+ shift
+done
+
+if [ -n "$USER_GNATS_SITE" ]; then
+ GNATS_SITE=$USER_GNATS_SITE
+ GNATS_ADDR=$USER_GNATS_SITE-gnats
+fi
+
+if [ "$SUBMITTER" = "unknown" -a -z "$REQUEST_ID" -a -z "$IN_FILE" ]; then
+ cat << '__EOF__'
+It seems that send-pr is not installed with your unique submitter-id.
+You need to run
+
+ install-sid YOUR-SID
+
+where YOUR-SID is the identification code you received with `send-pr'.
+`send-pr' will automatically insert this value into the template field
+`>Submitter-Id'. If you've downloaded `send-pr' from the Net, use `net'
+for this value. If you do not know your id, run `send-pr --request-id' to
+get one from your support site.
+__EOF__
+ exit 1
+fi
+
+## if [ -r "$DATADIR/gnats/$GNATS_SITE" ]; then
+## CATEGORIES=`grep -v '^#' $DATADIR/gnats/$GNATS_SITE | sort`
+## else
+## echo "$COMMAND: could not read $DATADIR/gnats/$GNATS_SITE for categories list."
+## exit 1
+## fi
+CATEGORIES="contrib cvs doc pcl-cvs portability"
+
+if [ -z "$CATEGORIES" ]; then
+ echo "$COMMAND: the categories list for $GNATS_SITE was empty!"
+ exit 1
+fi
+
+case "$FORMAT" in
+ lisp) echo "$CATEGORIES" | \
+ awk 'BEGIN {printf "( "} {printf "(\"%s\") ",$0} END {printf ")\n"}'
+ exit 0
+ ;;
+ norm) l=`echo "$CATEGORIES" | \
+ awk 'BEGIN {max = 0; } { if (length($0) > max) { max = length($0); } }
+ END {print max + 1;}'`
+ c=`expr 70 / $l`
+ if [ $c -eq 0 ]; then c=1; fi
+ echo "$CATEGORIES" | \
+ awk 'BEGIN {print "Known categories:"; i = 0 }
+ { printf ("%-'$l'.'$l's", $0); if ((++i % '$c') == 0) { print "" } }
+ END { print ""; }'
+ exit 0
+ ;;
+esac
+
+ORIGINATOR_C='<name of the PR author (one line)>'
+ORGANIZATION_C='<organization of PR author (multiple lines)>'
+CONFIDENTIAL_C='<[ yes | no ] (one line)>'
+SYNOPSIS_C='<synopsis of the problem (one line)>'
+SEVERITY_C='<[ non-critical | serious | critical ] (one line)>'
+PRIORITY_C='<[ low | medium | high ] (one line)>'
+CATEGORY_C='<name of the product (one line)>'
+CLASS_C='<[ sw-bug | doc-bug | change-request | support ] (one line)>'
+RELEASE_C='<release number or tag (one line)>'
+ENVIRONMENT_C='<machine, os, target, libraries (multiple lines)>'
+DESCRIPTION_C='<precise description of the problem (multiple lines)>'
+HOW_TO_REPEAT_C='<code/input/activities to reproduce the problem (multiple lines)>'
+FIX_C='<how to correct or work around the problem, if known (multiple lines)>'
+
+# Catch some signals. ($xs kludge needed by Sun /bin/sh)
+xs=0
+trap 'rm -f $REF $TEMP; exit $xs' 0
+trap 'echo "$COMMAND: Aborting ..."; rm -f $REF $TEMP; xs=1; exit' 1 2 3 13 15
+
+# If they told us to use a specific file, then do so.
+if [ -n "$IN_FILE" ]; then
+ if [ "$IN_FILE" = "-" ]; then
+ # The PR is coming from the standard input.
+ if [ -n "$EXPLICIT_GNATS_ADDR" ]; then
+ sed -e "s;^[Tt][Oo]:.*;To: $GNATS_ADDR;" > $TEMP
+ else
+ cat > $TEMP
+ fi
+ else
+ # Use the file they named.
+ if [ -n "$EXPLICIT_GNATS_ADDR" ]; then
+ sed -e "s;^[Tt][Oo]:.*;To: $GNATS_ADDR;" $IN_FILE > $TEMP
+ else
+ cat $IN_FILE > $TEMP
+ fi
+ fi
+else
+
+ if [ -n "$PR_FORM" -a -z "$PRINT_INTERN" ]; then
+ # If their PR_FORM points to a bogus entry, then bail.
+ if [ ! -f "$PR_FORM" -o ! -r "$PR_FORM" -o ! -s "$PR_FORM" ]; then
+ echo "$COMMAND: can't seem to read your template file (\`$PR_FORM'), ignoring PR_FORM"
+ sleep 1
+ PRINT_INTERN=bad_prform
+ fi
+ fi
+
+ if [ -n "$PR_FORM" -a -z "$PRINT_INTERN" ]; then
+ cp $PR_FORM $TEMP ||
+ ( echo "$COMMAND: could not copy $PR_FORM" ; xs=1; exit )
+ else
+ for file in $TEMP $REF ; do
+ cat > $file << '__EOF__'
+SEND-PR: -*- send-pr -*-
+SEND-PR: Lines starting with `SEND-PR' will be removed automatically, as
+SEND-PR: will all comments (text enclosed in `<' and `>').
+SEND-PR:
+SEND-PR: Choose from the following categories:
+SEND-PR:
+__EOF__
+
+ # Format the categories so they fit onto lines.
+ l=`echo "$CATEGORIES" | \
+ awk 'BEGIN {max = 0; } { if (length($0) > max) { max = length($0); } }
+ END {print max + 1;}'`
+ c=`expr 61 / $l`
+ if [ $c -eq 0 ]; then c=1; fi
+ echo "$CATEGORIES" | \
+ awk 'BEGIN {printf "SEND-PR: "; i = 0 }
+ { printf ("%-'$l'.'$l's", $0);
+ if ((++i % '$c') == 0) { printf "\nSEND-PR: " } }
+ END { printf "\nSEND-PR:\n"; }' >> $file
+
+ cat >> $file << __EOF__
+To: $GNATS_ADDR
+Subject:
+From: $FROM
+Reply-To: $REPLY_TO
+X-send-pr-version: $VERSION
+
+
+>Submitter-Id: $SUBMITTER
+>Originator: $ORIGINATOR
+>Organization:
+${ORGANIZATION-$ORGANIZATION_C}
+>Confidential: $CONFIDENTIAL_C
+>Synopsis: $SYNOPSIS_C
+>Severity: $SEVERITY_C
+>Priority: $PRIORITY_C
+>Category: $CATEGORY_C
+>Class: $CLASS_C
+>Release: ${DEFAULT_RELEASE-$RELEASE_C}
+>Environment:
+ $ENVIRONMENT_C
+`[ -n "$SYSTEM" ] && echo System: $SYSTEM`
+`[ -n "$ARCH" ] && echo Architecture: $ARCH`
+`[ -n "$MACHINE" ] && echo Machine: $MACHINE`
+>Description:
+ $DESCRIPTION_C
+>How-To-Repeat:
+ $HOW_TO_REPEAT_C
+>Fix:
+ $FIX_C
+__EOF__
+ done
+ fi
+
+ if [ "$PRINT" = true -o "$PRINT_INTERN" = true ]; then
+ cat $TEMP
+ xs=0; exit
+ fi
+
+ chmod u+w $TEMP
+ if [ -z "$REQUEST_ID" ]; then
+ eval $EDIT $TEMP
+ else
+ ed -s $TEMP << '__EOF__'
+/^Subject/s/^Subject:.*/Subject: request for a customer id/
+/^>Category/s/^>Category:.*/>Category: send-pr/
+w
+q
+__EOF__
+ fi
+
+ if cmp -s $REF $TEMP ; then
+ echo "$COMMAND: problem report not filled out, therefore not sent"
+ xs=1; exit
+ fi
+fi
+
+#
+# Check the enumeration fields
+
+# This is a "sed-subroutine" with one keyword parameter
+# (with workaround for Sun sed bug)
+#
+SED_CMD='
+/$PATTERN/{
+s|||
+s|<.*>||
+s|^[ ]*||
+s|[ ]*$||
+p
+q
+}'
+
+
+while [ -z "$REQUEST_ID" ]; do
+ CNT=0
+
+ # 1) Confidential
+ #
+ PATTERN=">Confidential:"
+ CONFIDENTIAL=`eval sed -n -e "\"$SED_CMD\"" $TEMP`
+ case "$CONFIDENTIAL" in
+ ""|yes|no) CNT=`expr $CNT + 1` ;;
+ *) echo "$COMMAND: \`$CONFIDENTIAL' is not a valid value for \`Confidential'." ;;
+ esac
+ #
+ # 2) Severity
+ #
+ PATTERN=">Severity:"
+ SEVERITY=`eval sed -n -e "\"$SED_CMD\"" $TEMP`
+ case "$SEVERITY" in
+ ""|non-critical|serious|critical) CNT=`expr $CNT + 1` ;;
+ *) echo "$COMMAND: \`$SEVERITY' is not a valid value for \`Severity'."
+ esac
+ #
+ # 3) Priority
+ #
+ PATTERN=">Priority:"
+ PRIORITY=`eval sed -n -e "\"$SED_CMD\"" $TEMP`
+ case "$PRIORITY" in
+ ""|low|medium|high) CNT=`expr $CNT + 1` ;;
+ *) echo "$COMMAND: \`$PRIORITY' is not a valid value for \`Priority'."
+ esac
+ #
+ # 4) Category
+ #
+ PATTERN=">Category:"
+ CATEGORY=`eval sed -n -e "\"$SED_CMD\"" $TEMP`
+ FOUND=
+ for C in $CATEGORIES
+ do
+ if [ "$C" = "$CATEGORY" ]; then FOUND=true ; break ; fi
+ done
+ if [ -n "$FOUND" ]; then
+ CNT=`expr $CNT + 1`
+ else
+ if [ -z "$CATEGORY" ]; then
+ echo "$COMMAND: you must include a Category: field in your report."
+ else
+ echo "$COMMAND: \`$CATEGORY' is not a known category."
+ fi
+ fi
+ #
+ # 5) Class
+ #
+ PATTERN=">Class:"
+ CLASS=`eval sed -n -e "\"$SED_CMD\"" $TEMP`
+ case "$CLASS" in
+ ""|sw-bug|doc-bug|change-request|support) CNT=`expr $CNT + 1` ;;
+ *) echo "$COMMAND: \`$CLASS' is not a valid value for \`Class'."
+ esac
+
+ [ $CNT -lt 5 -a -z "$BATCH" ] &&
+ echo "Errors were found with the problem report."
+
+ while true; do
+ if [ -z "$BATCH" ]; then
+ $ECHON1 "a)bort, e)dit or s)end? $ECHON2"
+ read input
+ else
+ if [ $CNT -eq 5 ]; then
+ input=s
+ else
+ input=a
+ fi
+ fi
+ case "$input" in
+ a*)
+ if [ -z "$BATCH" ]; then
+ echo "$COMMAND: the problem report remains in $BAD and is not sent."
+ mv $TEMP $BAD
+ else
+ echo "$COMMAND: the problem report is not sent."
+ fi
+ xs=1; exit
+ ;;
+ e*)
+ eval $EDIT $TEMP
+ continue 2
+ ;;
+ s*)
+ break 2
+ ;;
+ esac
+ done
+done
+#
+# Remove comments and send the problem report
+# (we have to use patterns, where the comment contains regex chars)
+#
+# /^>Originator:/s;$ORIGINATOR;;
+sed -e "
+/^SEND-PR:/d
+/^>Organization:/,/^>[A-Za-z-]*:/s;$ORGANIZATION_C;;
+/^>Confidential:/s;<.*>;;
+/^>Synopsis:/s;$SYNOPSIS_C;;
+/^>Severity:/s;<.*>;;
+/^>Priority:/s;<.*>;;
+/^>Category:/s;$CATEGORY_C;;
+/^>Class:/s;<.*>;;
+/^>Release:/,/^>[A-Za-z-]*:/s;$RELEASE_C;;
+/^>Environment:/,/^>[A-Za-z-]*:/s;$ENVIRONMENT_C;;
+/^>Description:/,/^>[A-Za-z-]*:/s;$DESCRIPTION_C;;
+/^>How-To-Repeat:/,/^>[A-Za-z-]*:/s;$HOW_TO_REPEAT_C;;
+/^>Fix:/,/^>[A-Za-z-]*:/s;$FIX_C;;
+" $TEMP > $REF
+
+if $MAIL_AGENT < $REF; then
+ echo "$COMMAND: problem report sent"
+ xs=0; exit
+else
+ echo "$COMMAND: mysterious mail failure."
+ if [ -z "$BATCH" ]; then
+ echo "$COMMAND: the problem report remains in $BAD and is not sent."
+ mv $REF $BAD
+ else
+ echo "$COMMAND: the problem report is not sent."
+ fi
+ xs=1; exit
+fi
diff --git a/contrib/cvs/src/cvsrc.c b/contrib/cvs/src/cvsrc.c
new file mode 100644
index 0000000..140ce1c
--- /dev/null
+++ b/contrib/cvs/src/cvsrc.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 1993 david d zuhn
+ *
+ * written by david d `zoo' zuhn while at Cygnus Support
+ *
+ * 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.
+ *
+ */
+
+
+#include "cvs.h"
+#include "getline.h"
+
+/* this file is to be found in the user's home directory */
+
+#ifndef CVSRC_FILENAME
+#define CVSRC_FILENAME ".cvsrc"
+#endif
+char cvsrc[] = CVSRC_FILENAME;
+
+#define GROW 10
+
+extern char *strtok ();
+
+/* Read cvsrc, processing options matching CMDNAME ("cvs" for global
+ options, and update *ARGC and *ARGV accordingly. */
+
+void
+read_cvsrc (argc, argv, cmdname)
+ int *argc;
+ char ***argv;
+ char *cmdname;
+{
+ char *homedir;
+ char *homeinit;
+ FILE *cvsrcfile;
+
+ char *line;
+ int line_length;
+ size_t line_chars_allocated;
+
+ char *optstart;
+
+ int command_len;
+ int found = 0;
+
+ int i;
+
+ int new_argc;
+ int max_new_argv;
+ char **new_argv;
+
+ /* don't do anything if argc is -1, since that implies "help" mode */
+ if (*argc == -1)
+ return;
+
+ /* setup the new options list */
+
+ new_argc = 1;
+ max_new_argv = (*argc) + GROW;
+ new_argv = (char **) xmalloc (max_new_argv * sizeof (char*));
+ new_argv[0] = xstrdup ((*argv)[0]);
+
+ /* determine filename for ~/.cvsrc */
+
+ homedir = get_homedir ();
+ if (!homedir)
+ return;
+
+ homeinit = (char *) xmalloc (strlen (homedir) + strlen (cvsrc) + 10);
+ strcpy (homeinit, homedir);
+ strcat (homeinit, "/");
+ strcat (homeinit, cvsrc);
+
+ /* if it can't be read, there's no point to continuing */
+
+ if (!isreadable (homeinit))
+ {
+ free (homeinit);
+ return;
+ }
+
+ /* now scan the file until we find the line for the command in question */
+
+ line = NULL;
+ line_chars_allocated = 0;
+ command_len = strlen (cmdname);
+ cvsrcfile = open_file (homeinit, "r");
+ while ((line_length = getline (&line, &line_chars_allocated, cvsrcfile))
+ >= 0)
+ {
+ /* skip over comment lines */
+ if (line[0] == '#')
+ continue;
+
+ /* stop if we match the current command */
+ if (!strncmp (line, cmdname, command_len)
+ && isspace (*(line + command_len)))
+ {
+ found = 1;
+ break;
+ }
+ }
+
+ fclose (cvsrcfile);
+
+ if (found)
+ {
+ /* skip over command in the options line */
+ optstart = strtok (line + command_len, "\t \n");
+
+ do
+ {
+ new_argv [new_argc] = xstrdup (optstart);
+ new_argv [new_argc+1] = NULL;
+ new_argc += 1;
+
+ if (new_argc >= max_new_argv)
+ {
+ char **tmp_argv;
+ max_new_argv += GROW;
+ tmp_argv = (char **) xmalloc (max_new_argv * sizeof (char*));
+ for (i = 0; i <= new_argc; i++)
+ tmp_argv[i] = new_argv[i];
+ free(new_argv);
+ new_argv = tmp_argv;
+ }
+
+ }
+ while ((optstart = strtok (NULL, "\t \n")) != NULL);
+ }
+
+ if (line != NULL)
+ free (line);
+
+ /* now copy the remaining arguments */
+
+ for (i=1; i < *argc; i++)
+ {
+ new_argv [new_argc] = (*argv)[i];
+ new_argc += 1;
+ }
+
+ *argc = new_argc;
+ *argv = new_argv;
+
+ free (homeinit);
+ return;
+}
diff --git a/contrib/cvs/src/diff.c b/contrib/cvs/src/diff.c
new file mode 100644
index 0000000..7520cec
--- /dev/null
+++ b/contrib/cvs/src/diff.c
@@ -0,0 +1,623 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Difference
+ *
+ * Run diff against versions in the repository. Options that are specified are
+ * passed on directly to "rcsdiff".
+ *
+ * Without any file arguments, runs diff against all the currently modified
+ * files.
+ */
+
+#include "cvs.h"
+
+static Dtype diff_dirproc PROTO((char *dir, char *pos_repos, char *update_dir));
+static int diff_filesdoneproc PROTO((int err, char *repos, char *update_dir));
+static int diff_dirleaveproc PROTO((char *dir, int err, char *update_dir));
+static int diff_file_nodiff PROTO((char *file, char *repository, List *entries,
+ RCSNode *rcs, Vers_TS *vers));
+static int diff_fileproc PROTO((struct file_info *finfo));
+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;
+
+#ifdef SERVER_SUPPORT
+/* Revision of the user file, if it is unchanged from something in the
+ repository and we want to use that fact. */
+static char *user_file_rev;
+#endif
+
+static char *options;
+static char opts[PATH_MAX];
+static int diff_errors;
+static int empty_files = 0;
+
+static const char *const diff_usage[] =
+{
+ "Usage: %s %s [-lN] [rcsdiff-options]\n",
+#ifdef CVS_DIFFDATE
+ " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
+#else
+ " [-r rev1 [-r rev2]] [files...] \n",
+#endif
+ "\t-l\tLocal directory only, not recursive\n",
+ "\t-D d1\tDiff revision for date against working file.\n",
+ "\t-D d2\tDiff rev1/date1 against date2.\n",
+ "\t-N\tinclude diffs for added and removed files.\n",
+ "\t-r rev1\tDiff revision for rev1 against working file.\n",
+ "\t-r rev2\tDiff rev1/date1 against rev2.\n",
+ NULL
+};
+
+int
+diff (argc, argv)
+ int argc;
+ char **argv;
+{
+ char tmp[50];
+ int c, err = 0;
+ int local = 0;
+ int which;
+
+ if (argc == -1)
+ usage (diff_usage);
+
+ /*
+ * 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
+ * non-recursive/recursive diff.
+ */
+#ifdef SERVER_SUPPORT
+ /* Need to be able to do this command more than once (according to
+ the protocol spec, even if the current client doesn't use it). */
+ opts[0] = '\0';
+#endif
+ optind = 1;
+ while ((c = getopt (argc, argv,
+ "abcdefhilnpqtuw0123456789BHNQRTC:D:F:I:L:V:k:r:")) != -1)
+ {
+ switch (c)
+ {
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'h': case 'i': case 'n': case 'p': case 't': 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 'Q':
+ (void) sprintf (tmp, " -%c", (char) c);
+ (void) strcat (opts, tmp);
+ if (c == 'Q')
+ {
+ quiet = 1;
+ really_quiet = 1;
+ c = 'q';
+ }
+ break;
+ case 'C': case 'F': case 'I': case 'L': case 'V':
+#ifndef CVS_DIFFDATE
+ case 'D':
+#endif
+ (void) sprintf (tmp, " -%c%s", (char) c, optarg);
+ (void) strcat (opts, tmp);
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case 'k':
+ if (options)
+ free (options);
+ options = RCS_check_kflag (optarg);
+ break;
+ case 'r':
+ if (diff_rev2 != NULL || diff_date2 != NULL)
+ error (1, 0,
+ "no more than two revisions/dates can be specified");
+ if (diff_rev1 != NULL || diff_date1 != NULL)
+ diff_rev2 = optarg;
+ else
+ diff_rev1 = optarg;
+ break;
+#ifdef CVS_DIFFDATE
+ case 'D':
+ if (diff_rev2 != NULL || diff_date2 != NULL)
+ error (1, 0,
+ "no more than two revisions/dates can be specified");
+ if (diff_rev1 != NULL || diff_date1 != NULL)
+ diff_date2 = Make_Date (optarg);
+ else
+ diff_date1 = Make_Date (optarg);
+ break;
+#endif
+ case 'N':
+ empty_files = 1;
+ break;
+ case '?':
+ default:
+ usage (diff_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* make sure options is non-null */
+ if (!options)
+ options = xstrdup ("");
+
+#ifdef CLIENT_SUPPORT
+ if (client_active) {
+ /* We're the client side. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ if (local)
+ send_arg("-l");
+ if (empty_files)
+ send_arg("-N");
+ send_option_string (opts);
+ if (diff_rev1)
+ option_with_arg ("-r", diff_rev1);
+ if (diff_date1)
+ client_senddate (diff_date1);
+ if (diff_rev2)
+ option_with_arg ("-r", diff_rev2);
+ if (diff_date2)
+ client_senddate (diff_date2);
+
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+#if 0
+ /* FIXME: We shouldn't have to send current files to diff two
+ revs, but it doesn't work yet and I haven't debugged it.
+ So send the files -- it's slower but it works.
+ gnu@cygnus.com Apr94 */
+ /* Send the current files unless diffing two revs from the archive */
+ if (diff_rev2 == NULL && diff_date2 == NULL)
+#endif
+ send_files (argc, argv, local, 0);
+
+ send_to_server ("diff\012", 0);
+ err = get_responses_and_close ();
+ free (options);
+ return (err);
+ }
+#endif
+
+ if (diff_rev1 != NULL)
+ tag_check_valid (diff_rev1, argc, argv, local, 0, "");
+ if (diff_rev2 != NULL)
+ tag_check_valid (diff_rev2, argc, argv, local, 0, "");
+
+ which = W_LOCAL;
+ if (diff_rev2 != NULL || diff_date2 != NULL)
+ which |= W_REPOS | W_ATTIC;
+
+ wrap_setup ();
+
+ /* start the recursion processor */
+ err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
+ diff_dirleaveproc, argc, argv, local,
+ which, 0, 1, (char *) NULL, 1, 0);
+
+ /* clean up */
+ free (options);
+ return (err);
+}
+
+/*
+ * Do a file diff
+ */
+/* ARGSUSED */
+static int
+diff_fileproc (finfo)
+ struct file_info *finfo;
+{
+ int status, err = 2; /* 2 == trouble, like rcsdiff */
+ Vers_TS *vers;
+ enum {
+ DIFF_ERROR,
+ DIFF_ADDED,
+ DIFF_REMOVED,
+ DIFF_NEITHER
+ } empty_file = DIFF_NEITHER;
+ char tmp[L_tmpnam+1];
+ char *tocvsPath;
+ char fname[PATH_MAX];
+
+#ifdef SERVER_SUPPORT
+ user_file_rev = 0;
+#endif
+ vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL,
+ finfo->file, 1, 0, finfo->entries, finfo->rcs);
+
+ if (diff_rev2 != NULL || diff_date2 != NULL)
+ {
+ /* Skip all the following checks regarding the user file; we're
+ not using it. */
+ }
+ else if (vers->vn_user == NULL)
+ {
+ error (0, 0, "I know nothing about %s", finfo->fullname);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+ }
+ else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
+ {
+ if (empty_files)
+ empty_file = DIFF_ADDED;
+ else
+ {
+ error (0, 0, "%s is a new entry, no comparison available",
+ finfo->fullname);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+ }
+ }
+ else if (vers->vn_user[0] == '-')
+ {
+ if (empty_files)
+ empty_file = DIFF_REMOVED;
+ else
+ {
+ error (0, 0, "%s was removed, no comparison available",
+ finfo->fullname);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+ }
+ }
+ else
+ {
+ if (vers->vn_rcs == NULL && vers->srcfile == NULL)
+ {
+ error (0, 0, "cannot find revision control file for %s",
+ finfo->fullname);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+ }
+ else
+ {
+ if (vers->ts_user == NULL)
+ {
+ error (0, 0, "cannot find %s", finfo->fullname);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+ }
+#ifdef SERVER_SUPPORT
+ else if (!strcmp (vers->ts_user, vers->ts_rcs))
+ {
+ /* The user file matches some revision in the repository
+ Diff against the repository (for remote CVS, we might not
+ have a copy of the user file around). */
+ user_file_rev = vers->vn_user;
+ }
+#endif
+ }
+ }
+
+ if (empty_file == DIFF_NEITHER && diff_file_nodiff (finfo->file, finfo->repository, finfo->entries, finfo->rcs, vers))
+ {
+ freevers_ts (&vers);
+ return (0);
+ }
+
+ /* FIXME: Check whether use_rev1 and use_rev2 are dead and deal
+ accordingly. */
+
+ /* Output an "Index:" line for patch to use */
+ (void) fflush (stdout);
+ (void) printf ("Index: %s\n", finfo->fullname);
+ (void) fflush (stdout);
+
+ tocvsPath = wrap_tocvs_process_file(finfo->file);
+ if (tocvsPath)
+ {
+ /* Backup the current version of the file to CVS/,,filename */
+ sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file);
+ if (unlink_file_dir (fname) < 0)
+ if (! existence_error (errno))
+ error (1, errno, "cannot remove %s", finfo->file);
+ rename_file (finfo->file, fname);
+ /* Copy the wrapped file to the current directory then go to work */
+ copy_file (tocvsPath, finfo->file);
+ }
+
+ if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
+ {
+ /* 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);
+
+ if (empty_file == DIFF_ADDED)
+ {
+ run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, finfo->file);
+ }
+ else
+ {
+ int retcode;
+
+ /*
+ * FIXME: Should be setting use_rev1 using the logic in
+ * diff_file_nodiff, and using that revision. This code
+ * is broken for "cvs diff -N -r foo".
+ */
+ retcode = RCS_checkout (vers->srcfile->path, NULL, vers->vn_rcs,
+ *options ? options : vers->options, tmpnam (tmp),
+ 0, 0);
+ if (retcode == -1)
+ {
+ (void) unlink (tmp);
+ error (1, errno, "fork failed during checkout of %s",
+ vers->srcfile->path);
+ }
+ /* FIXME: what if retcode > 0? */
+
+ run_setup ("%s %s %s %s", DIFF, opts, tmp, DEVNULL);
+ }
+ }
+ 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);
+ }
+
+ switch ((status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
+ RUN_REALLY|RUN_COMBINED)))
+ {
+ case -1: /* fork failed */
+ error (1, errno, "fork failed during rcsdiff of %s",
+ vers->srcfile->path);
+ case 0: /* everything ok */
+ err = 0;
+ break;
+ default: /* other error */
+ err = status;
+ break;
+ }
+
+ if (tocvsPath)
+ {
+ if (unlink_file_dir (finfo->file) < 0)
+ if (! existence_error (errno))
+ error (1, errno, "cannot remove %s", finfo->file);
+
+ rename_file (fname,finfo->file);
+ if (unlink_file (tocvsPath) < 0)
+ error (1, errno, "cannot remove %s", finfo->file);
+ }
+
+ if (empty_file == DIFF_REMOVED)
+ (void) unlink (tmp);
+
+ (void) fflush (stdout);
+ freevers_ts (&vers);
+ diff_mark_errors (err);
+ return (err);
+}
+
+/*
+ * Remember the exit status for each file.
+ */
+static void
+diff_mark_errors (err)
+ int err;
+{
+ if (err > diff_errors)
+ diff_errors = err;
+}
+
+/*
+ * Print a warm fuzzy message when we enter a dir
+ *
+ * Don't try to diff directories that don't exist! -- DW
+ */
+/* ARGSUSED */
+static Dtype
+diff_dirproc (dir, pos_repos, update_dir)
+ char *dir;
+ char *pos_repos;
+ char *update_dir;
+{
+ /* 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 (!quiet)
+ error (0, 0, "Diffing %s", update_dir);
+ return (R_PROCESS);
+}
+
+/*
+ * Concoct the proper exit status - done with files
+ */
+/* ARGSUSED */
+static int
+diff_filesdoneproc (err, repos, update_dir)
+ int err;
+ char *repos;
+ char *update_dir;
+{
+ return (diff_errors);
+}
+
+/*
+ * Concoct the proper exit status - leaving directories
+ */
+/* ARGSUSED */
+static int
+diff_dirleaveproc (dir, err, update_dir)
+ char *dir;
+ int err;
+ char *update_dir;
+{
+ return (diff_errors);
+}
+
+/*
+ * verify that a file is different 0=same 1=different
+ */
+static int
+diff_file_nodiff (file, repository, entries, rcs, vers)
+ char *file;
+ char *repository;
+ List *entries;
+ RCSNode *rcs;
+ Vers_TS *vers;
+{
+ Vers_TS *xvers;
+ char tmp[L_tmpnam+1];
+ int retcode;
+
+ /* free up any old use_rev* variables and reset 'em */
+ if (use_rev1)
+ free (use_rev1);
+ if (use_rev2)
+ free (use_rev2);
+ use_rev1 = use_rev2 = (char *) NULL;
+
+ if (diff_rev1 || diff_date1)
+ {
+ /* special handling for TAG_HEAD */
+ if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
+ use_rev1 = xstrdup (vers->vn_rcs);
+ else
+ {
+ xvers = Version_TS (repository, (char *) NULL, diff_rev1,
+ diff_date1, file, 1, 0, entries, rcs);
+ if (xvers->vn_rcs == NULL)
+ {
+ /* Don't gripe if it doesn't exist, just ignore! */
+ if (! isfile (file))
+ /* null statement */ ;
+ else if (diff_rev1)
+ error (0, 0, "tag %s is not in file %s", diff_rev1, file);
+ else
+ error (0, 0, "no revision for date %s in file %s",
+ diff_date1, file);
+
+ freevers_ts (&xvers);
+ return (1);
+ }
+ use_rev1 = xstrdup (xvers->vn_rcs);
+ freevers_ts (&xvers);
+ }
+ }
+ if (diff_rev2 || diff_date2)
+ {
+ /* special handling for TAG_HEAD */
+ if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
+ use_rev2 = xstrdup (vers->vn_rcs);
+ else
+ {
+ xvers = Version_TS (repository, (char *) NULL, diff_rev2,
+ diff_date2, file, 1, 0, entries, rcs);
+ if (xvers->vn_rcs == NULL)
+ {
+ /* Don't gripe if it doesn't exist, just ignore! */
+ if (! isfile (file))
+ /* null statement */ ;
+ else if (diff_rev1)
+ error (0, 0, "tag %s is not in file %s", diff_rev2, file);
+ else
+ error (0, 0, "no revision for date %s in file %s",
+ diff_date2, file);
+
+ freevers_ts (&xvers);
+ return (1);
+ }
+ use_rev2 = xstrdup (xvers->vn_rcs);
+ freevers_ts (&xvers);
+ }
+
+ /* now, see if we really need to do the diff */
+ if (use_rev1 && use_rev2) {
+ return (strcmp (use_rev1, use_rev2) == 0);
+ } else {
+ error(0, 0, "No HEAD revision for file %s", file);
+ return (1);
+ }
+ }
+#ifdef SERVER_SUPPORT
+ if (user_file_rev)
+ {
+ /* drop user_file_rev into first unused use_rev */
+ if (!use_rev1)
+ use_rev1 = xstrdup (user_file_rev);
+ else if (!use_rev2)
+ use_rev2 = xstrdup (user_file_rev);
+ /* and if not, it wasn't needed anyhow */
+ user_file_rev = 0;
+ }
+
+ /* now, see if we really need to do the diff */
+ if (use_rev1 && use_rev2)
+ {
+ return (strcmp (use_rev1, use_rev2) == 0);
+ }
+#endif /* SERVER_SUPPORT */
+ if (use_rev1 == NULL || strcmp (use_rev1, vers->vn_user) == 0)
+ {
+ if (strcmp (vers->ts_rcs, vers->ts_user) == 0 &&
+ (!(*options) || strcmp (options, vers->options) == 0))
+ {
+ return (1);
+ }
+ if (use_rev1 == NULL)
+ use_rev1 = xstrdup (vers->vn_user);
+ }
+
+ /*
+ * with 0 or 1 -r option specified, run a quick diff to see if we
+ * should bother with it at all.
+ */
+ retcode = RCS_checkout (vers->srcfile->path, NULL, use_rev1,
+ *options ? options : vers->options, tmpnam (tmp), 0, 0);
+ switch (retcode)
+ {
+ case 0: /* everything ok */
+ if (xcmp (file, tmp) == 0)
+ {
+ (void) unlink (tmp);
+ return (1);
+ }
+ break;
+ case -1: /* fork failed */
+ (void) unlink (tmp);
+ error (1, errno, "fork failed during checkout of %s",
+ vers->srcfile->path);
+ default:
+ break;
+ }
+ (void) unlink (tmp);
+ return (0);
+}
diff --git a/contrib/cvs/src/edit.c b/contrib/cvs/src/edit.c
new file mode 100644
index 0000000..0473a03
--- /dev/null
+++ b/contrib/cvs/src/edit.c
@@ -0,0 +1,1020 @@
+/* Implementation for "cvs edit", "cvs watch on", and related commands
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "cvs.h"
+#include "getline.h"
+#include "watch.h"
+#include "edit.h"
+#include "fileattr.h"
+
+static int watch_onoff PROTO ((int, char **));
+
+static int setting_default;
+static int turning_on;
+
+static int setting_tedit;
+static int setting_tunedit;
+static int setting_tcommit;
+
+static int onoff_fileproc PROTO ((struct file_info *finfo));
+
+static int
+onoff_fileproc (finfo)
+ struct file_info *finfo;
+{
+ fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL);
+ return 0;
+}
+
+static int onoff_filesdoneproc PROTO ((int, char *, char *));
+
+static int
+onoff_filesdoneproc (err, repository, update_dir)
+ int err;
+ char *repository;
+ char *update_dir;
+{
+ if (setting_default)
+ fileattr_set (NULL, "_watched", turning_on ? "" : NULL);
+ return err;
+}
+
+static int
+watch_onoff (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int local = 0;
+ int err;
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "l")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case '?':
+ default:
+ usage (watch_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ start_server ();
+
+ ign_setup ();
+
+ if (local)
+ send_arg ("-l");
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* FIXME: We shouldn't have to send current files, but I'm not sure
+ whether it works. So send the files --
+ it's slower but it works. */
+ send_files (argc, argv, local, 0);
+ send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0);
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ setting_default = (argc <= 0);
+
+ lock_tree_for_write (argc, argv, local, 0);
+
+ err = start_recursion (onoff_fileproc, onoff_filesdoneproc,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0, 0);
+
+ lock_tree_cleanup ();
+ return err;
+}
+
+int
+watch_on (argc, argv)
+ int argc;
+ char **argv;
+{
+ turning_on = 1;
+ return watch_onoff (argc, argv);
+}
+
+int
+watch_off (argc, argv)
+ int argc;
+ char **argv;
+{
+ turning_on = 0;
+ return watch_onoff (argc, argv);
+}
+
+static int dummy_fileproc PROTO ((struct file_info *finfo));
+
+static int
+dummy_fileproc (finfo)
+ struct file_info *finfo;
+{
+ /* This is a pretty hideous hack, but the gist of it is that recurse.c
+ won't call notify_check unless there is a fileproc, so we can't just
+ pass NULL for fileproc. */
+ return 0;
+}
+
+static int ncheck_fileproc PROTO ((struct file_info *finfo));
+
+/* Check for and process notifications. Local only. I think that doing
+ this as a fileproc is the only way to catch all the
+ cases (e.g. foo/bar.c), even though that means checking over and over
+ for the same CVSADM_NOTIFY file which we removed the first time we
+ processed the directory. */
+
+static int
+ncheck_fileproc (finfo)
+ struct file_info *finfo;
+{
+ int notif_type;
+ char *filename;
+ char *val;
+ char *cp;
+ char *watches;
+
+ FILE *fp;
+ char *line = NULL;
+ size_t line_len = 0;
+
+ /* We send notifications even if noexec. I'm not sure which behavior
+ is most sensible. */
+
+ fp = fopen (CVSADM_NOTIFY, "r");
+ if (fp == NULL)
+ {
+ if (!existence_error (errno))
+ error (0, errno, "cannot open %s", CVSADM_NOTIFY);
+ return 0;
+ }
+
+ while (getline (&line, &line_len, fp) > 0)
+ {
+ notif_type = line[0];
+ if (notif_type == '\0')
+ continue;
+ filename = line + 1;
+ cp = strchr (filename, '\t');
+ if (cp == NULL)
+ continue;
+ *cp++ = '\0';
+ val = cp;
+ cp = strchr (val, '\t');
+ if (cp == NULL)
+ continue;
+ *cp++ = '+';
+ cp = strchr (cp, '\t');
+ if (cp == NULL)
+ continue;
+ *cp++ = '+';
+ cp = strchr (cp, '\t');
+ if (cp == NULL)
+ continue;
+ *cp++ = '\0';
+ watches = cp;
+ cp = strchr (cp, '\n');
+ if (cp == NULL)
+ continue;
+ *cp = '\0';
+
+ notify_do (notif_type, filename, getcaller (), val, watches,
+ finfo->repository);
+ }
+ free (line);
+
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", CVSADM_NOTIFY);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", CVSADM_NOTIFY);
+
+ if (unlink (CVSADM_NOTIFY) < 0)
+ error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
+
+ return 0;
+}
+
+static int send_notifications PROTO ((int, char **, int));
+
+/* Look through the CVSADM_NOTIFY file and process each item there
+ accordingly. */
+static int
+send_notifications (argc, argv, local)
+ int argc;
+ char **argv;
+ int local;
+{
+ int err = 0;
+
+#ifdef CLIENT_SUPPORT
+ /* OK, we've done everything which needs to happen on the client side.
+ Now we can try to contact the server; if we fail, then the
+ notifications stay in CVSADM_NOTIFY to be sent next time. */
+ if (client_active)
+ {
+ if (strcmp (command_name, "release") != 0)
+ {
+ start_server ();
+ ign_setup ();
+ }
+
+ err += start_recursion (dummy_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0, 0);
+
+ send_to_server ("noop\012", 0);
+ if (strcmp (command_name, "release") == 0)
+ err += get_server_responses ();
+ else
+ err += get_responses_and_close ();
+ }
+ else
+#endif
+ {
+ /* Local. */
+
+ lock_tree_for_write (argc, argv, local, 0);
+ err += start_recursion (ncheck_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0, 0);
+ lock_tree_cleanup ();
+ }
+ return err;
+}
+
+static int edit_fileproc PROTO ((struct file_info *finfo));
+
+static int
+edit_fileproc (finfo)
+ struct file_info *finfo;
+{
+ FILE *fp;
+ time_t now;
+ char *ascnow;
+ char *basefilename;
+
+ if (noexec)
+ return 0;
+
+ fp = open_file (CVSADM_NOTIFY, "a");
+
+ (void) time (&now);
+ ascnow = asctime (gmtime (&now));
+ ascnow[24] = '\0';
+ fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file,
+ ascnow, hostname, CurDir);
+ if (setting_tedit)
+ fprintf (fp, "E");
+ if (setting_tunedit)
+ fprintf (fp, "U");
+ if (setting_tcommit)
+ fprintf (fp, "C");
+ fprintf (fp, "\n");
+
+ if (fclose (fp) < 0)
+ {
+ if (finfo->update_dir[0] == '\0')
+ error (0, errno, "cannot close %s", CVSADM_NOTIFY);
+ else
+ error (0, errno, "cannot close %s/%s", finfo->update_dir,
+ CVSADM_NOTIFY);
+ }
+
+ xchmod (finfo->file, 1);
+
+ /* Now stash the file away in CVSADM so that unedit can revert even if
+ it can't communicate with the server. We stash away a writable
+ copy so that if the user removes the working file, then restores it
+ with "cvs update" (which clears _editors but does not update
+ CVSADM_BASE), then a future "cvs edit" can still win. */
+ /* Could save a system call by only calling mkdir if trying to create
+ the output file fails. But copy_file isn't set up to facilitate
+ that. */
+ if (CVS_MKDIR (CVSADM_BASE, 0777) < 0)
+ {
+ if (errno != EEXIST
+#ifdef EACCESS
+ /* OS/2; see longer comment in client.c. */
+ && errno != EACCESS
+#endif
+ )
+ error (1, errno, "cannot mkdir %s", CVSADM_BASE);
+ }
+ basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
+ strcpy (basefilename, CVSADM_BASE);
+ strcat (basefilename, "/");
+ strcat (basefilename, finfo->file);
+ copy_file (finfo->file, basefilename);
+ free (basefilename);
+
+ return 0;
+}
+
+static const char *const edit_usage[] =
+{
+ "Usage: %s %s [-l] [files...]\n",
+ "-l: Local directory only, not recursive\n",
+ "-a: Specify what actions for temporary watch, one of\n",
+ " edit,unedit,commit.all,none\n",
+ NULL
+};
+
+int
+edit (argc, argv)
+ int argc;
+ char **argv;
+{
+ int local = 0;
+ int c;
+ int err;
+ int a_omitted;
+
+ if (argc == -1)
+ usage (edit_usage);
+
+ a_omitted = 1;
+ setting_tedit = 0;
+ setting_tunedit = 0;
+ setting_tcommit = 0;
+ optind = 1;
+ while ((c = getopt (argc, argv, "la:")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case 'a':
+ a_omitted = 0;
+ if (strcmp (optarg, "edit") == 0)
+ setting_tedit = 1;
+ else if (strcmp (optarg, "unedit") == 0)
+ setting_tunedit = 1;
+ else if (strcmp (optarg, "commit") == 0)
+ setting_tcommit = 1;
+ else if (strcmp (optarg, "all") == 0)
+ {
+ setting_tedit = 1;
+ setting_tunedit = 1;
+ setting_tcommit = 1;
+ }
+ else if (strcmp (optarg, "none") == 0)
+ {
+ setting_tedit = 0;
+ setting_tunedit = 0;
+ setting_tcommit = 0;
+ }
+ else
+ usage (edit_usage);
+ break;
+ case '?':
+ default:
+ usage (edit_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (a_omitted)
+ {
+ setting_tedit = 1;
+ setting_tunedit = 1;
+ setting_tcommit = 1;
+ }
+
+ /* No need to readlock since we aren't doing anything to the
+ repository. */
+ err = start_recursion (edit_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0, 0);
+
+ err += send_notifications (argc, argv, local);
+
+ return err;
+}
+
+static int unedit_fileproc PROTO ((struct file_info *finfo));
+
+static int
+unedit_fileproc (finfo)
+ struct file_info *finfo;
+{
+ FILE *fp;
+ time_t now;
+ char *ascnow;
+ char *basefilename;
+
+ if (noexec)
+ return 0;
+
+ basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
+ strcpy (basefilename, CVSADM_BASE);
+ strcat (basefilename, "/");
+ strcat (basefilename, finfo->file);
+ if (!isfile (basefilename))
+ {
+ /* This file apparently was never cvs edit'd (e.g. we are uneditting
+ a directory where only some of the files were cvs edit'd. */
+ free (basefilename);
+ return 0;
+ }
+
+ if (xcmp (finfo->file, basefilename) != 0)
+ {
+ printf ("%s has been modified; revert changes? ", finfo->fullname);
+ if (!yesno ())
+ {
+ /* "no". */
+ free (basefilename);
+ return 0;
+ }
+ }
+ rename_file (basefilename, finfo->file);
+ free (basefilename);
+
+ fp = open_file (CVSADM_NOTIFY, "a");
+
+ (void) time (&now);
+ ascnow = asctime (gmtime (&now));
+ ascnow[24] = '\0';
+ fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", finfo->file,
+ ascnow, hostname, CurDir);
+
+ if (fclose (fp) < 0)
+ {
+ if (finfo->update_dir[0] == '\0')
+ error (0, errno, "cannot close %s", CVSADM_NOTIFY);
+ else
+ error (0, errno, "cannot close %s/%s", finfo->update_dir,
+ CVSADM_NOTIFY);
+ }
+
+ xchmod (finfo->file, 0);
+ return 0;
+}
+
+int
+unedit (argc, argv)
+ int argc;
+ char **argv;
+{
+ int local = 0;
+ int c;
+ int err;
+
+ if (argc == -1)
+ usage (edit_usage);
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "l")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case '?':
+ default:
+ usage (edit_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* No need to readlock since we aren't doing anything to the
+ repository. */
+ err = start_recursion (unedit_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 0, 0);
+
+ err += send_notifications (argc, argv, local);
+
+ return err;
+}
+
+void
+mark_up_to_date (file)
+ char *file;
+{
+ char *base;
+
+ /* The file is up to date, so we better get rid of an out of
+ date file in CVSADM_BASE. */
+ base = xmalloc (strlen (file) + 80);
+ strcpy (base, CVSADM_BASE);
+ strcat (base, "/");
+ strcat (base, file);
+ if (unlink_file (base) < 0 && ! existence_error (errno))
+ error (0, errno, "cannot remove %s", file);
+ free (base);
+}
+
+
+void
+editor_set (filename, editor, val)
+ char *filename;
+ char *editor;
+ char *val;
+{
+ char *edlist;
+ char *newlist;
+
+ edlist = fileattr_get0 (filename, "_editors");
+ newlist = fileattr_modify (edlist, editor, val, '>', ',');
+ if (edlist != NULL)
+ free (edlist);
+ /* If the attributes is unchanged, don't rewrite the attribute file. */
+ if (!((edlist == NULL && newlist == NULL)
+ || (edlist != NULL
+ && newlist != NULL
+ && strcmp (edlist, newlist) == 0)))
+ fileattr_set (filename, "_editors", newlist);
+ if (newlist != NULL)
+ free (newlist);
+}
+
+struct notify_proc_args {
+ /* What kind of notification, "edit", "tedit", etc. */
+ char *type;
+ /* User who is running the command which causes notification. */
+ char *who;
+ /* User to be notified. */
+ char *notifyee;
+ /* File. */
+ char *file;
+};
+
+/* Pass as a static until we get around to fixing Parse_Info to pass along
+ a void * where we can stash it. */
+static struct notify_proc_args *notify_args;
+
+static int notify_proc PROTO ((char *repository, char *filter));
+
+static int
+notify_proc (repository, filter)
+ char *repository;
+ char *filter;
+{
+ FILE *pipefp;
+ char *prog;
+ char *expanded_prog;
+ char *p;
+ char *q;
+ char *srepos;
+ struct notify_proc_args *args = notify_args;
+
+ srepos = Short_Repository (repository);
+ prog = xmalloc (strlen (filter) + strlen (args->notifyee) + 1);
+ /* Copy FILTER to PROG, replacing the first occurrence of %s with
+ the notifyee. We only allocated enough memory for one %s, and I doubt
+ there is a need for more. */
+ for (p = filter, q = prog; *p != '\0'; ++p)
+ {
+ if (p[0] == '%')
+ {
+ if (p[1] == 's')
+ {
+ strcpy (q, args->notifyee);
+ q += strlen (q);
+ strcpy (q, p + 2);
+ q += strlen (q);
+ break;
+ }
+ else
+ continue;
+ }
+ *q++ = *p;
+ }
+ *q = '\0';
+
+ /* FIXME: why are we calling expand_proc? Didn't we already
+ expand it in Parse_Info, before passing it to notify_proc? */
+ expanded_prog = expand_path (prog, "notify", 0);
+ if (!expanded_prog)
+ {
+ free (prog);
+ return 1;
+ }
+
+ pipefp = run_popen (expanded_prog, "w");
+ if (pipefp == NULL)
+ {
+ error (0, errno, "cannot write entry to notify filter: %s", prog);
+ free (prog);
+ free (expanded_prog);
+ return 1;
+ }
+
+ fprintf (pipefp, "%s %s\n---\n", srepos, args->file);
+ fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository);
+ fprintf (pipefp, "By %s\n", args->who);
+
+ /* Lots more potentially useful information we could add here; see
+ logfile_write for inspiration. */
+
+ free (prog);
+ free (expanded_prog);
+ return (pclose (pipefp));
+}
+
+void
+notify_do (type, filename, who, val, watches, repository)
+ int type;
+ char *filename;
+ char *who;
+ char *val;
+ char *watches;
+ char *repository;
+{
+ static struct addremove_args blank;
+ struct addremove_args args;
+ char *watchers;
+ char *p;
+ char *endp;
+ char *nextp;
+
+ /* Initialize fields to 0, NULL, or 0.0. */
+ args = blank;
+ switch (type)
+ {
+ case 'E':
+ editor_set (filename, who, val);
+ break;
+ case 'U':
+ case 'C':
+ editor_set (filename, who, NULL);
+ break;
+ default:
+ return;
+ }
+
+ watchers = fileattr_get0 (filename, "_watchers");
+ p = watchers;
+ while (p != NULL)
+ {
+ char *q;
+ char *endq;
+ char *nextq;
+ char *notif;
+
+ endp = strchr (p, '>');
+ if (endp == NULL)
+ break;
+ nextp = strchr (p, ',');
+
+ if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0)
+ {
+ /* Don't notify user of their own changes. Would perhaps
+ be better to check whether it is the same working
+ directory, not the same user, but that is hairy. */
+ p = nextp == NULL ? nextp : nextp + 1;
+ continue;
+ }
+
+ /* Now we point q at a string which looks like
+ "edit+unedit+commit,"... and walk down it. */
+ q = endp + 1;
+ notif = NULL;
+ while (q != NULL)
+ {
+ endq = strchr (q, '+');
+ if (endq == NULL || (nextp != NULL && endq > nextp))
+ {
+ if (nextp == NULL)
+ endq = q + strlen (q);
+ else
+ endq = nextp;
+ nextq = NULL;
+ }
+ else
+ nextq = endq + 1;
+
+ /* If there is a temporary and a regular watch, send a single
+ notification, for the regular watch. */
+ if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0)
+ {
+ notif = "edit";
+ }
+ else if (type == 'U'
+ && endq - q == 6 && strncmp ("unedit", q, 6) == 0)
+ {
+ notif = "unedit";
+ }
+ else if (type == 'C'
+ && endq - q == 6 && strncmp ("commit", q, 6) == 0)
+ {
+ notif = "commit";
+ }
+ else if (type == 'E'
+ && endq - q == 5 && strncmp ("tedit", q, 5) == 0)
+ {
+ if (notif == NULL)
+ notif = "temporary edit";
+ }
+ else if (type == 'U'
+ && endq - q == 7 && strncmp ("tunedit", q, 7) == 0)
+ {
+ if (notif == NULL)
+ notif = "temporary unedit";
+ }
+ else if (type == 'C'
+ && endq - q == 7 && strncmp ("tcommit", q, 7) == 0)
+ {
+ if (notif == NULL)
+ notif = "temporary commit";
+ }
+ q = nextq;
+ }
+ if (nextp != NULL)
+ ++nextp;
+
+ if (notif != NULL)
+ {
+ struct notify_proc_args args;
+ size_t len = endp - p;
+ FILE *fp;
+ char *usersname;
+ char *line = NULL;
+ size_t line_len = 0;
+
+ args.notifyee = NULL;
+ usersname = xmalloc (strlen (CVSroot)
+ + sizeof CVSROOTADM
+ + sizeof CVSROOTADM_USERS
+ + 20);
+ strcpy (usersname, CVSroot);
+ strcat (usersname, "/");
+ strcat (usersname, CVSROOTADM);
+ strcat (usersname, "/");
+ strcat (usersname, CVSROOTADM_USERS);
+ fp = fopen (usersname, "r");
+ if (fp == NULL && !existence_error (errno))
+ error (0, errno, "cannot read %s", usersname);
+ if (fp != NULL)
+ {
+ while (getline (&line, &line_len, fp) >= 0)
+ {
+ if (strncmp (line, p, len) == 0
+ && line[len] == ':')
+ {
+ char *cp;
+ args.notifyee = xstrdup (line + len + 1);
+ cp = strchr (args.notifyee, ':');
+ if (cp != NULL)
+ *cp = '\0';
+ break;
+ }
+ }
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", usersname);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", usersname);
+ }
+ free (usersname);
+ free (line);
+
+ if (args.notifyee == NULL)
+ {
+ args.notifyee = xmalloc (endp - p + 1);
+ strncpy (args.notifyee, p, endp - p);
+ args.notifyee[endp - p] = '\0';
+ }
+
+ notify_args = &args;
+ args.type = notif;
+ args.who = who;
+ args.file = filename;
+
+ (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, 1);
+ free (args.notifyee);
+ }
+
+ p = nextp;
+ }
+ if (watchers != NULL)
+ free (watchers);
+
+ switch (type)
+ {
+ case 'E':
+ if (*watches == 'E')
+ {
+ args.add_tedit = 1;
+ ++watches;
+ }
+ if (*watches == 'U')
+ {
+ args.add_tunedit = 1;
+ ++watches;
+ }
+ if (*watches == 'C')
+ {
+ args.add_tcommit = 1;
+ }
+ watch_modify_watchers (filename, &args);
+ break;
+ case 'U':
+ case 'C':
+ args.remove_temp = 1;
+ watch_modify_watchers (filename, &args);
+ break;
+ }
+}
+
+#ifdef CLIENT_SUPPORT
+/* Check and send notifications. This is only for the client. */
+void
+notify_check (repository, update_dir)
+ char *repository;
+ char *update_dir;
+{
+ FILE *fp;
+ char *line = NULL;
+ size_t line_len = 0;
+
+ if (! server_started)
+ /* We are in the midst of a command which is not to talk to
+ the server (e.g. the first phase of a cvs edit). Just chill
+ out, we'll catch the notifications on the flip side. */
+ return;
+
+ /* We send notifications even if noexec. I'm not sure which behavior
+ is most sensible. */
+
+ fp = fopen (CVSADM_NOTIFY, "r");
+ if (fp == NULL)
+ {
+ if (!existence_error (errno))
+ error (0, errno, "cannot open %s", CVSADM_NOTIFY);
+ return;
+ }
+ while (getline (&line, &line_len, fp) > 0)
+ {
+ int notif_type;
+ char *filename;
+ char *val;
+ char *cp;
+
+ notif_type = line[0];
+ if (notif_type == '\0')
+ continue;
+ filename = line + 1;
+ cp = strchr (filename, '\t');
+ if (cp == NULL)
+ continue;
+ *cp++ = '\0';
+ val = cp;
+
+ client_notify (repository, update_dir, filename, notif_type, val);
+ }
+
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", CVSADM_NOTIFY);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", CVSADM_NOTIFY);
+
+ /* Leave the CVSADM_NOTIFY file there, until the server tells us it
+ has dealt with it. */
+}
+#endif /* CLIENT_SUPPORT */
+
+
+static const char *const editors_usage[] =
+{
+ "Usage: %s %s [files...]\n",
+ NULL
+};
+
+static int editors_fileproc PROTO ((struct file_info *finfo));
+
+static int
+editors_fileproc (finfo)
+ struct file_info *finfo;
+{
+ char *them;
+ char *p;
+
+ them = fileattr_get0 (finfo->file, "_editors");
+ if (them == NULL)
+ return 0;
+
+ fputs (finfo->fullname, stdout);
+
+ p = them;
+ while (1)
+ {
+ putc ('\t', stdout);
+ while (*p != '>' && *p != '\0')
+ putc (*p++, stdout);
+ if (*p == '\0')
+ {
+ /* Only happens if attribute is misformed. */
+ putc ('\n', stdout);
+ break;
+ }
+ ++p;
+ putc ('\t', stdout);
+ while (1)
+ {
+ while (*p != '+' && *p != ',' && *p != '\0')
+ putc (*p++, stdout);
+ if (*p == '\0')
+ {
+ putc ('\n', stdout);
+ goto out;
+ }
+ if (*p == ',')
+ {
+ ++p;
+ break;
+ }
+ ++p;
+ putc ('\t', stdout);
+ }
+ putc ('\n', stdout);
+ }
+ out:;
+ return 0;
+}
+
+int
+editors (argc, argv)
+ int argc;
+ char **argv;
+{
+ int local = 0;
+ int c;
+
+ if (argc == -1)
+ usage (editors_usage);
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "l")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case '?':
+ default:
+ usage (editors_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ start_server ();
+ ign_setup ();
+
+ if (local)
+ send_arg ("-l");
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* FIXME: We shouldn't have to send current files, but I'm not sure
+ whether it works. So send the files --
+ it's slower but it works. */
+ send_files (argc, argv, local, 0);
+ send_to_server ("editors\012", 0);
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ return start_recursion (editors_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ argc, argv, local, W_LOCAL, 0, 1, (char *)NULL,
+ 0, 0);
+}
diff --git a/contrib/cvs/src/edit.h b/contrib/cvs/src/edit.h
new file mode 100644
index 0000000..0a823ad
--- /dev/null
+++ b/contrib/cvs/src/edit.h
@@ -0,0 +1,42 @@
+/* Interface to "cvs edit", "cvs watch on", and related features
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+extern int watch_on PROTO ((int argc, char **argv));
+extern int watch_off PROTO ((int argc, char **argv));
+
+#ifdef CLIENT_SUPPORT
+/* Check to see if any notifications are sitting around in need of being
+ sent. These are the notifications stored in CVSADM_NOTIFY (edit,unedit);
+ commit calls notify_do directly. */
+extern void notify_check PROTO ((char *repository, char *update_dir));
+#endif /* CLIENT_SUPPORT */
+
+/* Issue a notification for file FILENAME. TYPE is 'E' for edit, 'U'
+ for unedit, and 'C' for commit. WHO is the user currently running.
+ For TYPE 'E', VAL is the time+host+directory data which goes in
+ _editors, and WATCHES is zero or more of E,U,C, in that order, to specify
+ what kinds of temporary watches to set. */
+extern void notify_do PROTO ((int type, char *filename, char *who,
+ char *val, char *watches, char *repository));
+
+/* Set attributes to reflect the fact that EDITOR is editing FILENAME.
+ VAL is time+host+directory, or NULL if we are to say that EDITOR is
+ *not* editing FILENAME. */
+extern void editor_set PROTO ((char *filename, char *editor, char *val));
+
+/* Take note of the fact that FILE is up to date (this munges CVS/Base;
+ processing of CVS/Entries is done separately). */
+extern void mark_up_to_date PROTO ((char *file));
diff --git a/contrib/cvs/src/entries.c b/contrib/cvs/src/entries.c
new file mode 100644
index 0000000..350f7f8
--- /dev/null
+++ b/contrib/cvs/src/entries.c
@@ -0,0 +1,548 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Entries file to Files file
+ *
+ * Creates the file Files containing the names that comprise the project, from
+ * the Entries file.
+ */
+
+#include "cvs.h"
+#include "getline.h"
+
+static Node *AddEntryNode PROTO((List * list, Entnode *entnode));
+
+static Entnode *fgetentent PROTO((FILE *));
+static int fputentent PROTO((FILE *, Entnode *));
+
+static FILE *entfile;
+static char *entfilename; /* for error messages */
+
+/*
+ * Construct an Entnode
+ */
+Entnode *
+Entnode_Create(user, vn, ts, options, tag, date, ts_conflict)
+ const char *user;
+ const char *vn;
+ const char *ts;
+ const char *options;
+ const char *tag;
+ const char *date;
+ const char *ts_conflict;
+{
+ Entnode *ent;
+
+ /* Note that timestamp and options must be non-NULL */
+ ent = (Entnode *) xmalloc (sizeof (Entnode));
+ ent->user = xstrdup (user);
+ ent->version = xstrdup (vn);
+ ent->timestamp = xstrdup (ts ? ts : "");
+ ent->options = xstrdup (options ? options : "");
+ ent->tag = xstrdup (tag);
+ ent->date = xstrdup (date);
+ ent->conflict = xstrdup (ts_conflict);
+
+ return ent;
+}
+
+/*
+ * Destruct an Entnode
+ */
+void
+Entnode_Destroy (ent)
+ Entnode *ent;
+{
+ free (ent->user);
+ free (ent->version);
+ free (ent->timestamp);
+ free (ent->options);
+ if (ent->tag)
+ free (ent->tag);
+ if (ent->date)
+ free (ent->date);
+ if (ent->conflict)
+ free (ent->conflict);
+ free (ent);
+}
+
+/*
+ * Write out the line associated with a node of an entries file
+ */
+static int write_ent_proc PROTO ((Node *, void *));
+static int
+write_ent_proc (node, closure)
+ Node *node;
+ void *closure;
+{
+ if (fputentent(entfile, (Entnode *) node->data))
+ error (1, errno, "cannot write %s", entfilename);
+
+ return (0);
+}
+
+/*
+ * write out the current entries file given a list, making a backup copy
+ * first of course
+ */
+static void
+write_entries (list)
+ List *list;
+{
+ /* open the new one and walk the list writing entries */
+ entfilename = CVSADM_ENTBAK;
+ entfile = open_file (entfilename, "w+");
+ (void) walklist (list, write_ent_proc, NULL);
+ if (fclose (entfile) == EOF)
+ error (1, errno, "error closing %s", entfilename);
+
+ /* now, atomically (on systems that support it) rename it */
+ rename_file (entfilename, CVSADM_ENT);
+
+ /* now, remove the log file */
+ unlink_file (CVSADM_ENTLOG);
+}
+
+/*
+ * Removes the argument file from the Entries file if necessary.
+ */
+void
+Scratch_Entry (list, fname)
+ List *list;
+ char *fname;
+{
+ Node *node;
+
+ if (trace)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> Scratch_Entry(%s)\n",
+ (server_active) ? 'S' : ' ', fname);
+#else
+ (void) fprintf (stderr, "-> Scratch_Entry(%s)\n", fname);
+#endif
+
+ /* hashlookup to see if it is there */
+ if ((node = findnode_fn (list, fname)) != NULL)
+ {
+ delnode (node); /* delete the node */
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_scratch (fname);
+#endif
+ if (!noexec)
+ write_entries (list); /* re-write the file */
+ }
+}
+
+/*
+ * Enters the given file name/version/time-stamp into the Entries file,
+ * removing the old entry first, if necessary.
+ */
+void
+Register (list, fname, vn, ts, options, tag, date, ts_conflict)
+ List *list;
+ char *fname;
+ char *vn;
+ char *ts;
+ char *options;
+ char *tag;
+ char *date;
+ char *ts_conflict;
+{
+ Entnode *entnode;
+ Node *node;
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ server_register (fname, vn, ts, options, tag, date, ts_conflict);
+ }
+#endif
+
+ if (trace)
+ {
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> Register(%s, %s, %s%s%s, %s, %s %s)\n",
+ (server_active) ? 'S' : ' ',
+ fname, vn, ts ? ts : "",
+ ts_conflict ? "+" : "", ts_conflict ? ts_conflict : "",
+ options, tag ? tag : "", date ? date : "");
+#else
+ (void) fprintf (stderr, "-> Register(%s, %s, %s%s%s, %s, %s %s)\n",
+ fname, vn, ts ? ts : "",
+ ts_conflict ? "+" : "", ts_conflict ? ts_conflict : "",
+ options, tag ? tag : "", date ? date : "");
+#endif
+ }
+
+ entnode = Entnode_Create(fname, vn, ts, options, tag, date, ts_conflict);
+ node = AddEntryNode (list, entnode);
+
+ if (!noexec)
+ {
+ entfile = open_file (CVSADM_ENTLOG, "a");
+
+ write_ent_proc (node, NULL);
+
+ if (fclose (entfile) == EOF)
+ error (1, errno, "error closing %s", CVSADM_ENTLOG);
+ }
+}
+
+/*
+ * Node delete procedure for list-private sticky dir tag/date info
+ */
+static void
+freesdt (p)
+ Node *p;
+{
+ struct stickydirtag *sdtp;
+
+ sdtp = (struct stickydirtag *) p->data;
+ if (sdtp->tag)
+ free (sdtp->tag);
+ if (sdtp->date)
+ free (sdtp->date);
+ if (sdtp->options)
+ free (sdtp->options);
+ free ((char *) sdtp);
+}
+
+static Entnode *
+fgetentent(fpin)
+ FILE *fpin;
+{
+ Entnode *ent;
+ char *line;
+ size_t line_chars_allocated;
+ register char *cp;
+ char *user, *vn, *ts, *options;
+ char *tag_or_date, *tag, *date, *ts_conflict;
+
+ line = NULL;
+ line_chars_allocated = 0;
+
+ ent = NULL;
+ while (getline (&line, &line_chars_allocated, fpin) > 0)
+ {
+ if (line[0] != '/')
+ continue;
+
+ user = line + 1;
+ if ((cp = strchr (user, '/')) == NULL)
+ continue;
+ *cp++ = '\0';
+ vn = cp;
+ if ((cp = strchr (vn, '/')) == NULL)
+ continue;
+ *cp++ = '\0';
+ ts = cp;
+ if ((cp = strchr (ts, '/')) == NULL)
+ continue;
+ *cp++ = '\0';
+ options = cp;
+ if ((cp = strchr (options, '/')) == NULL)
+ continue;
+ *cp++ = '\0';
+ tag_or_date = cp;
+ if ((cp = strchr (tag_or_date, '\n')) == NULL)
+ continue;
+ *cp = '\0';
+ tag = (char *) NULL;
+ date = (char *) NULL;
+ if (*tag_or_date == 'T')
+ tag = tag_or_date + 1;
+ else if (*tag_or_date == 'D')
+ date = tag_or_date + 1;
+
+ if ((ts_conflict = strchr (ts, '+')))
+ *ts_conflict++ = '\0';
+
+ /*
+ * XXX - Convert timestamp from old format to new format.
+ *
+ * If the timestamp doesn't match the file's current
+ * mtime, we'd have to generate a string that doesn't
+ * match anyways, so cheat and base it on the existing
+ * string; it doesn't have to match the same mod time.
+ *
+ * For an unmodified file, write the correct timestamp.
+ */
+ {
+ struct stat sb;
+ if (strlen (ts) > 30 && stat (user, &sb) == 0)
+ {
+ char *c = ctime (&sb.st_mtime);
+
+ if (!strncmp (ts + 25, c, 24))
+ ts = time_stamp (user);
+ else
+ {
+ ts += 24;
+ ts[0] = '*';
+ }
+ }
+ }
+
+ ent = Entnode_Create(user, vn, ts, options, tag, date, ts_conflict);
+ break;
+ }
+
+ free (line);
+ return ent;
+}
+
+static int
+fputentent(fp, p)
+ FILE *fp;
+ Entnode *p;
+{
+ if (fprintf (fp, "/%s/%s/%s", p->user, p->version, p->timestamp) < 0)
+ return 1;
+ if (p->conflict)
+ {
+ if (fprintf (fp, "+%s", p->conflict) < 0)
+ return 1;
+ }
+ if (fprintf (fp, "/%s/", p->options) < 0)
+ return 1;
+
+ if (p->tag)
+ {
+ if (fprintf (fp, "T%s\n", p->tag) < 0)
+ return 1;
+ }
+ else if (p->date)
+ {
+ if (fprintf (fp, "D%s\n", p->date) < 0)
+ return 1;
+ }
+ else
+ {
+ if (fprintf (fp, "\n") < 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Read the entries file into a list, hashing on the file name.
+ */
+List *
+Entries_Open (aflag)
+ int aflag;
+{
+ List *entries;
+ Entnode *ent;
+ char *dirtag, *dirdate;
+ int do_rewrite = 0;
+ FILE *fpin;
+
+ /* get a fresh list... */
+ entries = getlist ();
+
+ /*
+ * Parse the CVS/Tag file, to get any default tag/date settings. Use
+ * list-private storage to tuck them away for Version_TS().
+ */
+ ParseTag (&dirtag, &dirdate);
+ if (aflag || dirtag || dirdate)
+ {
+ struct stickydirtag *sdtp;
+
+ sdtp = (struct stickydirtag *) xmalloc (sizeof (*sdtp));
+ memset ((char *) sdtp, 0, sizeof (*sdtp));
+ sdtp->aflag = aflag;
+ sdtp->tag = xstrdup (dirtag);
+ sdtp->date = xstrdup (dirdate);
+
+ /* feed it into the list-private area */
+ entries->list->data = (char *) sdtp;
+ entries->list->delproc = freesdt;
+ }
+
+ fpin = fopen (CVSADM_ENT, "r");
+ if (fpin == NULL)
+ error (0, errno, "cannot open %s for reading", CVSADM_ENT);
+ else
+ {
+ while ((ent = fgetentent (fpin)) != NULL)
+ {
+ (void) AddEntryNode (entries, ent);
+ }
+
+ fclose (fpin);
+ }
+
+ fpin = fopen (CVSADM_ENTLOG, "r");
+ if (fpin != NULL)
+ {
+ while ((ent = fgetentent (fpin)) != NULL)
+ {
+ (void) AddEntryNode (entries, ent);
+ }
+ do_rewrite = 1;
+ fclose (fpin);
+ }
+
+ if (do_rewrite && !noexec)
+ write_entries (entries);
+
+ /* clean up and return */
+ if (dirtag)
+ free (dirtag);
+ if (dirdate)
+ free (dirdate);
+ return (entries);
+}
+
+void
+Entries_Close(list)
+ List *list;
+{
+ if (list)
+ {
+ if (!noexec)
+ {
+ if (isfile (CVSADM_ENTLOG))
+ write_entries (list);
+ }
+ dellist(&list);
+ }
+}
+
+
+/*
+ * Free up the memory associated with the data section of an ENTRIES type
+ * node
+ */
+static void
+Entries_delproc (node)
+ Node *node;
+{
+ Entnode *p;
+
+ p = (Entnode *) node->data;
+ Entnode_Destroy(p);
+}
+
+/*
+ * Get an Entries file list node, initialize it, and add it to the specified
+ * list
+ */
+static Node *
+AddEntryNode (list, entdata)
+ List *list;
+ Entnode *entdata;
+{
+ Node *p;
+
+ /* was it already there? */
+ if ((p = findnode_fn (list, entdata->user)) != NULL)
+ {
+ /* take it out */
+ delnode (p);
+ }
+
+ /* get a node and fill in the regular stuff */
+ p = getnode ();
+ p->type = ENTRIES;
+ p->delproc = Entries_delproc;
+
+ /* this one gets a key of the name for hashing */
+ /* FIXME This results in duplicated data --- the hash package shouldn't
+ assume that the key is dynamically allocated. The user's free proc
+ should be responsible for freeing the key. */
+ p->key = xstrdup (entdata->user);
+ p->data = (char *) entdata;
+
+ /* put the node into the list */
+ addnode (list, p);
+ return (p);
+}
+
+/*
+ * Write out/Clear the CVS/Tag file.
+ */
+void
+WriteTag (dir, tag, date)
+ char *dir;
+ char *tag;
+ char *date;
+{
+ FILE *fout;
+ char tmp[PATH_MAX];
+
+ if (noexec)
+ return;
+
+ if (dir == NULL)
+ (void) strcpy (tmp, CVSADM_TAG);
+ else
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_TAG);
+
+ if (tag || date)
+ {
+ fout = open_file (tmp, "w+");
+ if (tag)
+ {
+ if (fprintf (fout, "T%s\n", tag) < 0)
+ error (1, errno, "write to %s failed", tmp);
+ }
+ else
+ {
+ if (fprintf (fout, "D%s\n", date) < 0)
+ error (1, errno, "write to %s failed", tmp);
+ }
+ if (fclose (fout) == EOF)
+ error (1, errno, "cannot close %s", tmp);
+ }
+ else
+ if (unlink_file (tmp) < 0 && ! existence_error (errno))
+ error (1, errno, "cannot remove %s", tmp);
+}
+
+/*
+ * Parse the CVS/Tag file for the current directory.
+ */
+void
+ParseTag (tagp, datep)
+ char **tagp;
+ char **datep;
+{
+ FILE *fp;
+
+ if (tagp)
+ *tagp = (char *) NULL;
+ if (datep)
+ *datep = (char *) NULL;
+ fp = fopen (CVSADM_TAG, "r");
+ if (fp)
+ {
+ char *line;
+ int line_length;
+ size_t line_chars_allocated;
+
+ line = NULL;
+ line_chars_allocated = 0;
+
+ if ((line_length = getline (&line, &line_chars_allocated, fp)) > 0)
+ {
+ /* Remove any trailing newline. */
+ if (line[line_length - 1] == '\n')
+ line[--line_length] = '\0';
+ if (*line == 'T' && tagp)
+ *tagp = xstrdup (line + 1);
+ else if (*line == 'D' && datep)
+ *datep = xstrdup (line + 1);
+ }
+ (void) fclose (fp);
+ free (line);
+ }
+}
diff --git a/contrib/cvs/src/error.c b/contrib/cvs/src/error.c
new file mode 100644
index 0000000..8a10cc7
--- /dev/null
+++ b/contrib/cvs/src/error.c
@@ -0,0 +1,256 @@
+/* error.c -- error handler for noninteractive utilities
+ Copyright (C) 1990-1992 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* David MacKenzie */
+/* Brian Berliner added support for CVS */
+
+#include "cvs.h"
+
+#include <stdio.h>
+
+/* If non-zero, error will use the CVS protocol to stdout to report error
+ messages. This will only be set in the CVS server parent process;
+ most other code is run via do_cvs_command, which forks off a child
+ process and packages up its stderr in the protocol. */
+int error_use_protocol;
+
+#ifdef HAVE_VPRINTF
+
+#if __STDC__
+#include <stdarg.h>
+#define VA_START(args, lastarg) va_start(args, lastarg)
+#else /* ! __STDC__ */
+#include <varargs.h>
+#define VA_START(args, lastarg) va_start(args)
+#endif /* __STDC__ */
+
+#else /* ! HAVE_VPRINTF */
+
+#ifdef HAVE_DOPRNT
+#define va_alist args
+#define va_dcl int args;
+#else /* ! HAVE_DOPRNT */
+#define va_alist a1, a2, a3, a4, a5, a6, a7, a8
+#define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8;
+#endif /* HAVE_DOPRNT */
+
+#endif /* HAVE_VPRINTF */
+
+#if STDC_HEADERS
+#include <stdlib.h>
+#include <string.h>
+#else /* ! STDC_HEADERS */
+#if __STDC__
+void exit(int status);
+#else /* ! __STDC__ */
+void exit ();
+#endif /* __STDC__ */
+#endif /* STDC_HEADERS */
+
+extern char *strerror ();
+
+extern int vasprintf ();
+
+typedef void (*fn_returning_void) PROTO((void));
+
+/* Function to call before exiting. */
+static fn_returning_void cleanup_fn;
+
+fn_returning_void
+error_set_cleanup (arg)
+ fn_returning_void arg;
+{
+ fn_returning_void retval = cleanup_fn;
+ cleanup_fn = arg;
+ return retval;
+}
+
+/* Print the program name and error message MESSAGE, which is a printf-style
+ format string with optional args.
+ If ERRNUM is nonzero, print its corresponding system error message.
+ Exit with status EXIT_FAILURE if STATUS is nonzero. */
+/* VARARGS */
+void
+#if defined (HAVE_VPRINTF) && __STDC__
+error (int status, int errnum, const char *message, ...)
+#else
+error (status, errnum, message, va_alist)
+ int status;
+ int errnum;
+ const char *message;
+ va_dcl
+#endif
+{
+ FILE *out = stderr;
+#ifdef HAVE_VPRINTF
+ va_list args;
+#endif
+
+ if (error_use_protocol)
+ {
+ out = stdout;
+ printf ("E ");
+ }
+
+#ifdef HAVE_VPRINTF
+ {
+ char *mess = NULL;
+ char *entire;
+ size_t len;
+
+ VA_START (args, message);
+ vasprintf (&mess, message, args);
+ va_end (args);
+
+ if (mess == NULL)
+ {
+ entire = NULL;
+ status = 1;
+ }
+ else
+ {
+ len = strlen (mess) + strlen (program_name) + 80;
+ if (command_name != NULL)
+ len += strlen (command_name);
+ if (errnum != 0)
+ len += strlen (strerror (errnum));
+ entire = malloc (len);
+ if (entire == NULL)
+ {
+ free (mess);
+ status = 1;
+ }
+ else
+ {
+ strcpy (entire, program_name);
+ if (command_name != NULL && command_name[0] != '\0')
+ {
+ strcat (entire, " ");
+ if (status != 0)
+ strcat (entire, "[");
+ strcat (entire, command_name);
+ if (status != 0)
+ strcat (entire, " aborted]");
+ }
+ strcat (entire, ": ");
+ strcat (entire, mess);
+ if (errnum != 0)
+ {
+ strcat (entire, ": ");
+ strcat (entire, strerror (errnum));
+ }
+ strcat (entire, "\n");
+ free (mess);
+ }
+ }
+ if (error_use_protocol)
+ fputs (entire ? entire : "out of memory", out);
+ else
+ cvs_outerr (entire ? entire : "out of memory", 0);
+ if (entire != NULL)
+ free (entire);
+ }
+
+#else /* No HAVE_VPRINTF */
+ /* I think that all relevant systems have vprintf these days. But
+ just in case, I'm leaving this code here. */
+
+ if (command_name && *command_name)
+ {
+ if (status)
+ fprintf (out, "%s [%s aborted]: ", program_name, command_name);
+ else
+ fprintf (out, "%s %s: ", program_name, command_name);
+ }
+ else
+ fprintf (out, "%s: ", program_name);
+
+#ifdef HAVE_VPRINTF
+ VA_START (args, message);
+ vfprintf (out, message, args);
+ va_end (args);
+#else
+#ifdef HAVE_DOPRNT
+ _doprnt (message, &args, out);
+#else
+ fprintf (out, message, a1, a2, a3, a4, a5, a6, a7, a8);
+#endif
+#endif
+ if (errnum)
+ fprintf (out, ": %s", strerror (errnum));
+ putc ('\n', out);
+
+#endif /* No HAVE_VPRINTF */
+
+ /* In the error_use_protocol case, this probably does something useful.
+ In most other cases, I suspect it is a noop (either stderr is line
+ buffered or we haven't written anything to stderr) or unnecessary
+ (if stderr is not line buffered, maybe there is a reason....). */
+ fflush (out);
+
+ if (status)
+ {
+ if (cleanup_fn)
+ (*cleanup_fn) ();
+ exit (EXIT_FAILURE);
+ }
+}
+
+/* Print the program name and error message MESSAGE, which is a printf-style
+ format string with optional args to the file specified by FP.
+ If ERRNUM is nonzero, print its corresponding system error message.
+ Exit with status EXIT_FAILURE if STATUS is nonzero. */
+/* VARARGS */
+void
+#if defined (HAVE_VPRINTF) && __STDC__
+fperror (FILE *fp, int status, int errnum, char *message, ...)
+#else
+fperror (fp, status, errnum, message, va_alist)
+ FILE *fp;
+ int status;
+ int errnum;
+ char *message;
+ va_dcl
+#endif
+{
+#ifdef HAVE_VPRINTF
+ va_list args;
+#endif
+
+ fprintf (fp, "%s: ", program_name);
+#ifdef HAVE_VPRINTF
+ VA_START (args, message);
+ vfprintf (fp, message, args);
+ va_end (args);
+#else
+#ifdef HAVE_DOPRNT
+ _doprnt (message, &args, fp);
+#else
+ fprintf (fp, message, a1, a2, a3, a4, a5, a6, a7, a8);
+#endif
+#endif
+ if (errnum)
+ fprintf (fp, ": %s", strerror (errnum));
+ putc ('\n', fp);
+ fflush (fp);
+ if (status)
+ {
+ if (cleanup_fn)
+ (*cleanup_fn) ();
+ exit (EXIT_FAILURE);
+ }
+}
diff --git a/contrib/cvs/src/error.h b/contrib/cvs/src/error.h
new file mode 100644
index 0000000..7d4f535
--- /dev/null
+++ b/contrib/cvs/src/error.h
@@ -0,0 +1,47 @@
+/* error.h -- declaration for error-reporting function
+ Copyright (C) 1995 Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifndef _error_h_
+#define _error_h_
+
+#ifndef __attribute__
+/* This feature is available in gcc versions 2.5 and later. */
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) || __STRICT_ANSI__
+# define __attribute__(Spec) /* empty */
+# endif
+/* The __-protected variants of `format' and `printf' attributes
+ are accepted by gcc versions 2.6.4 (effectively 2.7) and later. */
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
+# define __format__ format
+# define __printf__ printf
+# endif
+#endif
+
+#if __STDC__
+void error (int, int, const char *, ...) \
+ __attribute__ ((__format__ (__printf__, 3, 4)));
+#else
+void error ();
+#endif
+
+/* If non-zero, error will use the CVS protocol to report error
+ messages. This will only be set in the CVS server parent process;
+ most other code is run via do_cvs_command, which forks off a child
+ process and packages up its stderr in the protocol. */
+extern int error_use_protocol;
+
+#endif /* _error_h_ */
diff --git a/contrib/cvs/src/expand_path.c b/contrib/cvs/src/expand_path.c
new file mode 100644
index 0000000..9898051
--- /dev/null
+++ b/contrib/cvs/src/expand_path.c
@@ -0,0 +1,241 @@
+/* expand_path.c -- expand environmental variables in passed in string
+ *
+ * The main routine is expand_path(), it is the routine that handles
+ * the '~' character in four forms:
+ * ~name
+ * ~name/
+ * ~/
+ * ~
+ * and handles environment variables contained within the pathname
+ * which are defined by:
+ * ${var_name} (var_name is the name of the environ variable)
+ * $var_name (var_name ends w/ non-alphanumeric char other than '_')
+ */
+
+#include "cvs.h"
+#include <sys/types.h>
+
+static char *expand_variable PROTO((char *env, char *file, int line));
+
+
+/* User variables. */
+
+List *variable_list = NULL;
+
+static void variable_delproc PROTO ((Node *));
+
+static void
+variable_delproc (node)
+ Node *node;
+{
+ free (node->data);
+}
+
+/* Currently used by -s option; we might want a way to set user
+ variables in a file in the $CVSROOT/CVSROOT directory too. */
+
+void
+variable_set (nameval)
+ char *nameval;
+{
+ char *p;
+ char *name;
+ Node *node;
+
+ p = nameval;
+ while (isalnum (*p) || *p == '_')
+ ++p;
+ if (*p != '=')
+ error (1, 0, "illegal character in user variable name in %s", nameval);
+ if (p == nameval)
+ error (1, 0, "empty user variable name in %s", nameval);
+ name = xmalloc (p - nameval + 1);
+ strncpy (name, nameval, p - nameval);
+ name[p - nameval] = '\0';
+ /* Make p point to the value. */
+ ++p;
+ if (strchr (p, '\012') != NULL)
+ error (1, 0, "linefeed in user variable value in %s", nameval);
+
+ if (variable_list == NULL)
+ variable_list = getlist ();
+
+ node = findnode (variable_list, name);
+ if (node == NULL)
+ {
+ node = getnode ();
+ node->type = VARIABLE;
+ node->delproc = variable_delproc;
+ node->key = name;
+ node->data = xstrdup (p);
+ (void) addnode (variable_list, node);
+ }
+ else
+ {
+ /* Replace the old value. For example, this means that -s
+ options on the command line override ones from .cvsrc. */
+ free (node->data);
+ node->data = xstrdup (p);
+ free (name);
+ }
+}
+
+/* This routine will expand the pathname to account for ~ and $
+ characters as described above. If an error occurs, an error
+ message is printed via error() and NULL is returned. FILE and
+ LINE are the filename and linenumber to include in the error
+ message. */
+char *
+expand_path (name, file, line)
+ char *name;
+ char *file;
+ int line;
+{
+ char *s;
+ char *d;
+ /* FIXME: arbitrary limit. */
+ char mybuf[PATH_MAX];
+ char buf[PATH_MAX];
+ char *result;
+ s = name;
+ d = mybuf;
+ while ((*d++ = *s))
+ if (*s++ == '$')
+ {
+ char *p = d;
+ char *e;
+ int flag = (*s == '{');
+
+ for (; (*d++ = *s); s++)
+ if (flag
+ ? *s =='}'
+ : isalnum (*s) == 0 && *s != '_')
+ break;
+ *--d = 0;
+ e = expand_variable (&p[flag], file, line);
+
+ if (e)
+ {
+ for (d = &p[-1]; (*d++ = *e++);)
+ ;
+ --d;
+ if (flag && *s)
+ s++;
+ }
+ else
+ /* expand_variable has already printed an error message. */
+ return NULL;
+ }
+ *d = 0;
+ s = mybuf;
+ d = buf;
+ /* If you don't want ~username ~/ to be expanded simply remove
+ * This entire if statement including the else portion
+ */
+ if (*s++ == '~')
+ {
+ char *t;
+ char *p=s;
+ if (*s=='/' || *s==0)
+ t = get_homedir ();
+ else
+ {
+ struct passwd *ps;
+ for (; *p!='/' && *p; p++)
+ ;
+ *p = 0;
+ ps = getpwnam (s);
+ if (ps == 0)
+ {
+ if (line != 0)
+ error (0, 0, "%s:%d: no such user %s",
+ file, line, s);
+ else
+ error (0, 0, "%s: no such user %s", file, s);
+ return NULL;
+ }
+ t = ps->pw_dir;
+ }
+ while ((*d++ = *t++))
+ ;
+ --d;
+ if (*p == 0)
+ *p = '/'; /* always add / */
+ s=p;
+ }
+ else
+ --s;
+ /* Kill up to here */
+ while ((*d++ = *s++))
+ ;
+ *d=0;
+ result = xmalloc (sizeof(char) * strlen(buf)+1);
+ strcpy (result, buf);
+ return result;
+}
+
+static char *
+expand_variable (name, file, line)
+ char *name;
+ char *file;
+ int line;
+{
+ if (strcmp (name, CVSROOT_ENV) == 0)
+ return CVSroot;
+ else if (strcmp (name, RCSBIN_ENV) == 0)
+ return Rcsbin;
+ else if (strcmp (name, EDITOR1_ENV) == 0)
+ return Editor;
+ else if (strcmp (name, EDITOR2_ENV) == 0)
+ return Editor;
+ else if (strcmp (name, EDITOR3_ENV) == 0)
+ return Editor;
+ else if (strcmp (name, "USER") == 0)
+ return getcaller ();
+ else if (isalpha (name[0]))
+ {
+ /* These names are reserved for future versions of CVS,
+ so that is why it is an error. */
+ if (line != 0)
+ error (0, 0, "%s:%d: no such internal variable $%s",
+ file, line, name);
+ else
+ error (0, 0, "%s: no such internal variable $%s",
+ file, name);
+ return NULL;
+ }
+ else if (name[0] == '=')
+ {
+ Node *node;
+ /* Crazy syntax for a user variable. But we want
+ *something* that lets the user name a user variable
+ anything he wants, without interference from
+ (existing or future) internal variables. */
+ node = findnode (variable_list, name + 1);
+ if (node == NULL)
+ {
+ if (line != 0)
+ error (0, 0, "%s:%d: no such user variable ${%s}",
+ file, line, name);
+ else
+ error (0, 0, "%s: no such user variable ${%s}",
+ file, name);
+ return NULL;
+ }
+ return node->data;
+ }
+ else
+ {
+ /* It is an unrecognized character. We return an error to
+ reserve these for future versions of CVS; it is plausible
+ that various crazy syntaxes might be invented for inserting
+ information about revisions, branches, etc. */
+ if (line != 0)
+ error (0, 0, "%s:%d: unrecognized varaible syntax %s",
+ file, line, name);
+ else
+ error (0, 0, "%s: unrecognized varaible syntax %s",
+ file, name);
+ return NULL;
+ }
+}
diff --git a/contrib/cvs/src/fileattr.c b/contrib/cvs/src/fileattr.c
new file mode 100644
index 0000000..827c69c
--- /dev/null
+++ b/contrib/cvs/src/fileattr.c
@@ -0,0 +1,517 @@
+/* Implementation for file attribute munging features.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "cvs.h"
+#include "getline.h"
+#include "fileattr.h"
+#include <assert.h>
+
+static void fileattr_read PROTO ((void));
+static int writeattr_proc PROTO ((Node *, void *));
+
+/* Where to look for CVSREP_FILEATTR. */
+static char *fileattr_stored_repos;
+
+/* The in-memory attributes. */
+static List *attrlist;
+static char *fileattr_default_attrs;
+/* We have already tried to read attributes and failed in this directory
+ (for example, there is no CVSREP_FILEATTR file). */
+static int attr_read_attempted;
+
+/* Have the in-memory attributes been modified since we read them? */
+static int attrs_modified;
+
+/* Note that if noone calls fileattr_get, this is very cheap. No stat(),
+ no open(), no nothing. */
+void
+fileattr_startdir (repos)
+ char *repos;
+{
+ assert (fileattr_stored_repos == NULL);
+ fileattr_stored_repos = xstrdup (repos);
+ assert (attrlist == NULL);
+ attr_read_attempted = 0;
+}
+
+static void
+fileattr_delproc (node)
+ Node *node;
+{
+ assert (node->data != NULL);
+ free (node->data);
+ node->data = NULL;
+}
+
+/* Read all the attributes for the current directory into memory. */
+static void
+fileattr_read ()
+{
+ char *fname;
+ FILE *fp;
+ char *line = NULL;
+ size_t line_len = 0;
+
+ /* If there are no attributes, don't waste time repeatedly looking
+ for the CVSREP_FILEATTR file. */
+ if (attr_read_attempted)
+ return;
+
+ /* If NULL was passed to fileattr_startdir, then it isn't kosher to look
+ at attributes. */
+ assert (fileattr_stored_repos != NULL);
+
+ fname = xmalloc (strlen (fileattr_stored_repos)
+ + 1
+ + sizeof (CVSREP_FILEATTR)
+ + 1);
+
+ strcpy (fname, fileattr_stored_repos);
+ strcat (fname, "/");
+ strcat (fname, CVSREP_FILEATTR);
+
+ attr_read_attempted = 1;
+ fp = fopen (fname, FOPEN_BINARY_READ);
+ if (fp == NULL)
+ {
+ if (!existence_error (errno))
+ error (0, errno, "cannot read %s", fname);
+ free (fname);
+ return;
+ }
+ attrlist = getlist ();
+ while (1) {
+ int nread;
+ nread = getline (&line, &line_len, fp);
+ if (nread < 0)
+ break;
+ /* Remove trailing newline. */
+ line[nread - 1] = '\0';
+ if (line[0] == 'F')
+ {
+ char *p;
+ Node *newnode;
+
+ p = strchr (line, '\t');
+ *p++ = '\0';
+ newnode = getnode ();
+ newnode->type = FILEATTR;
+ newnode->delproc = fileattr_delproc;
+ newnode->key = xstrdup (line + 1);
+ newnode->data = xstrdup (p);
+ addnode (attrlist, newnode);
+ }
+ else if (line[0] == 'D')
+ {
+ char *p;
+ /* Currently nothing to skip here, but for future expansion,
+ ignore anything located here. */
+ p = strchr (line, '\t');
+ ++p;
+ fileattr_default_attrs = xstrdup (p);
+ }
+ /* else just ignore the line, for future expansion. */
+ }
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", fname);
+ if (line != NULL)
+ free (line);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", fname);
+ attrs_modified = 0;
+ free (fname);
+}
+
+char *
+fileattr_get (filename, attrname)
+ char *filename;
+ char *attrname;
+{
+ Node *node;
+ size_t attrname_len = strlen (attrname);
+ char *p;
+
+ if (attrlist == NULL)
+ fileattr_read ();
+ if (attrlist == NULL)
+ /* Either nothing has any attributes, or fileattr_read already printed
+ an error message. */
+ return NULL;
+
+ if (filename == NULL)
+ p = fileattr_default_attrs;
+ else
+ {
+ node = findnode (attrlist, filename);
+ if (node == NULL)
+ /* A file not mentioned has no attributes. */
+ return NULL;
+ p = node->data;
+ }
+ while (p)
+ {
+ if (strncmp (attrname, p, attrname_len) == 0
+ && p[attrname_len] == '=')
+ {
+ /* Found it. */
+ return p + attrname_len + 1;
+ }
+ p = strchr (p, ';');
+ if (p == NULL)
+ break;
+ ++p;
+ }
+ /* The file doesn't have this attribute. */
+ return NULL;
+}
+
+char *
+fileattr_get0 (filename, attrname)
+ char *filename;
+ char *attrname;
+{
+ char *cp;
+ char *cpend;
+ char *retval;
+
+ cp = fileattr_get (filename, attrname);
+ if (cp == NULL)
+ return NULL;
+ cpend = strchr (cp, ';');
+ if (cpend == NULL)
+ cpend = cp + strlen (cp);
+ retval = xmalloc (cpend - cp + 1);
+ strncpy (retval, cp, cpend - cp);
+ retval[cpend - cp] = '\0';
+ return retval;
+}
+
+char *
+fileattr_modify (list, attrname, attrval, namevalsep, entsep)
+ char *list;
+ char *attrname;
+ char *attrval;
+ int namevalsep;
+ int entsep;
+{
+ char *retval;
+ char *rp;
+ size_t attrname_len = strlen (attrname);
+
+ /* Portion of list before the attribute to be replaced. */
+ char *pre;
+ char *preend;
+ /* Portion of list after the attribute to be replaced. */
+ char *post;
+
+ char *p;
+ char *p2;
+
+ p = list;
+ pre = list;
+ preend = NULL;
+ /* post is NULL unless set otherwise. */
+ post = NULL;
+ p2 = NULL;
+ if (list != NULL)
+ {
+ while (1) {
+ p2 = strchr (p, entsep);
+ if (p2 == NULL)
+ {
+ p2 = p + strlen (p);
+ if (preend == NULL)
+ preend = p2;
+ }
+ else
+ ++p2;
+ if (strncmp (attrname, p, attrname_len) == 0
+ && p[attrname_len] == namevalsep)
+ {
+ /* Found it. */
+ preend = p;
+ if (preend > list)
+ /* Don't include the preceding entsep. */
+ --preend;
+
+ post = p2;
+ }
+ if (p2[0] == '\0')
+ break;
+ p = p2;
+ }
+ }
+ if (post == NULL)
+ post = p2;
+
+ if (preend == pre && attrval == NULL && post == p2)
+ return NULL;
+
+ retval = xmalloc ((preend - pre)
+ + 1
+ + (attrval == NULL ? 0 : (attrname_len + 1
+ + strlen (attrval)))
+ + 1
+ + (p2 - post)
+ + 1);
+ if (preend != pre)
+ {
+ strncpy (retval, pre, preend - pre);
+ rp = retval + (preend - pre);
+ if (attrval != NULL)
+ *rp++ = entsep;
+ *rp = '\0';
+ }
+ else
+ retval[0] = '\0';
+ if (attrval != NULL)
+ {
+ strcat (retval, attrname);
+ rp = retval + strlen (retval);
+ *rp++ = namevalsep;
+ strcpy (rp, attrval);
+ }
+ if (post != p2)
+ {
+ rp = retval + strlen (retval);
+ if (preend != pre || attrval != NULL)
+ *rp++ = entsep;
+ strncpy (rp, post, p2 - post);
+ rp += p2 - post;
+ *rp = '\0';
+ }
+ return retval;
+}
+
+void
+fileattr_set (filename, attrname, attrval)
+ char *filename;
+ char *attrname;
+ char *attrval;
+{
+ Node *node;
+ char *p;
+
+ attrs_modified = 1;
+
+ if (filename == NULL)
+ {
+ p = fileattr_modify (fileattr_default_attrs, attrname, attrval,
+ '=', ';');
+ if (fileattr_default_attrs != NULL)
+ free (fileattr_default_attrs);
+ fileattr_default_attrs = p;
+ return;
+ }
+ if (attrlist == NULL)
+ fileattr_read ();
+ if (attrlist == NULL)
+ {
+ /* Not sure this is a graceful way to handle things
+ in the case where fileattr_read was unable to read the file. */
+ /* No attributes existed previously. */
+ attrlist = getlist ();
+ }
+
+ node = findnode (attrlist, filename);
+ if (node == NULL)
+ {
+ if (attrval == NULL)
+ /* Attempt to remove an attribute which wasn't there. */
+ return;
+
+ /* First attribute for this file. */
+ node = getnode ();
+ node->type = FILEATTR;
+ node->delproc = fileattr_delproc;
+ node->key = xstrdup (filename);
+ node->data = xmalloc (strlen (attrname) + 1 + strlen (attrval) + 1);
+ strcpy (node->data, attrname);
+ strcat (node->data, "=");
+ strcat (node->data, attrval);
+ addnode (attrlist, node);
+ }
+
+ p = fileattr_modify (node->data, attrname, attrval, '=', ';');
+ free (node->data);
+ node->data = NULL;
+ if (p == NULL)
+ delnode (node);
+ else
+ node->data = p;
+}
+
+void
+fileattr_newfile (filename)
+ char *filename;
+{
+ Node *node;
+
+ if (attrlist == NULL)
+ fileattr_read ();
+
+ if (fileattr_default_attrs == NULL)
+ return;
+
+ if (attrlist == NULL)
+ {
+ /* Not sure this is a graceful way to handle things
+ in the case where fileattr_read was unable to read the file. */
+ /* No attributes existed previously. */
+ attrlist = getlist ();
+ }
+
+ node = getnode ();
+ node->type = FILEATTR;
+ node->delproc = fileattr_delproc;
+ node->key = xstrdup (filename);
+ node->data = xstrdup (fileattr_default_attrs);
+ addnode (attrlist, node);
+ attrs_modified = 1;
+}
+
+static int
+writeattr_proc (node, data)
+ Node *node;
+ void *data;
+{
+ FILE *fp = (FILE *)data;
+ fputs ("F", fp);
+ fputs (node->key, fp);
+ fputs ("\t", fp);
+ fputs (node->data, fp);
+ fputs ("\012", fp);
+ return 0;
+}
+
+void
+fileattr_write ()
+{
+ FILE *fp;
+ char *fname;
+ mode_t omask;
+
+ if (!attrs_modified)
+ return;
+
+ if (noexec)
+ return;
+
+ /* If NULL was passed to fileattr_startdir, then it isn't kosher to set
+ attributes. */
+ assert (fileattr_stored_repos != NULL);
+
+ fname = xmalloc (strlen (fileattr_stored_repos)
+ + 1
+ + sizeof (CVSREP_FILEATTR)
+ + 1);
+
+ strcpy (fname, fileattr_stored_repos);
+ strcat (fname, "/");
+ strcat (fname, CVSREP_FILEATTR);
+
+ if (list_isempty (attrlist) && fileattr_default_attrs == NULL)
+ {
+ /* There are no attributes. */
+ if (unlink_file (fname) < 0)
+ {
+ if (!existence_error (errno))
+ {
+ error (0, errno, "cannot remove %s", fname);
+ }
+ }
+
+ /* Now remove CVSREP directory, if empty. The main reason we bother
+ is that CVS 1.6 and earlier will choke if a CVSREP directory
+ exists, so provide the user a graceful way to remove it. */
+ strcpy (fname, fileattr_stored_repos);
+ strcat (fname, "/");
+ strcat (fname, CVSREP);
+ if (rmdir (fname) < 0)
+ {
+ if (errno != ENOTEMPTY
+
+ /* Don't know why we would be here if there is no CVSREP
+ directory, but it seemed to be happening anyway, so
+ check for it. */
+ && !existence_error (errno))
+ error (0, errno, "cannot remove %s", fname);
+ }
+
+ free (fname);
+ return;
+ }
+
+ omask = umask (cvsumask);
+ fp = fopen (fname, FOPEN_BINARY_WRITE);
+ if (fp == NULL)
+ {
+ if (existence_error (errno))
+ {
+ /* Maybe the CVSREP directory doesn't exist. Try creating it. */
+ char *repname;
+
+ repname = xmalloc (strlen (fileattr_stored_repos)
+ + 1
+ + sizeof (CVSREP)
+ + 1);
+ strcpy (repname, fileattr_stored_repos);
+ strcat (repname, "/");
+ strcat (repname, CVSREP);
+
+ if (CVS_MKDIR (repname, 0777) < 0 && errno != EEXIST)
+ {
+ error (0, errno, "cannot make directory %s", repname);
+ (void) umask (omask);
+ free (repname);
+ return;
+ }
+ free (repname);
+
+ fp = fopen (fname, FOPEN_BINARY_WRITE);
+ }
+ if (fp == NULL)
+ {
+ error (0, errno, "cannot write %s", fname);
+ (void) umask (omask);
+ return;
+ }
+ }
+ (void) umask (omask);
+ walklist (attrlist, writeattr_proc, fp);
+ if (fileattr_default_attrs != NULL)
+ {
+ fputs ("D\t", fp);
+ fputs (fileattr_default_attrs, fp);
+ fputs ("\012", fp);
+ }
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", fname);
+ attrs_modified = 0;
+ free (fname);
+}
+
+void
+fileattr_free ()
+{
+ dellist (&attrlist);
+ if (fileattr_stored_repos != NULL)
+ free (fileattr_stored_repos);
+ fileattr_stored_repos = NULL;
+ if (fileattr_default_attrs != NULL)
+ free (fileattr_default_attrs);
+ fileattr_default_attrs = NULL;
+}
diff --git a/contrib/cvs/src/fileattr.h b/contrib/cvs/src/fileattr.h
new file mode 100644
index 0000000..c24c035
--- /dev/null
+++ b/contrib/cvs/src/fileattr.h
@@ -0,0 +1,125 @@
+/* Declarations for file attribute munging features.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#ifndef FILEATTR_H
+
+/* File containing per-file attributes. Format is a series of entries:
+
+ ENT-TYPE FILENAME <tab> ATTRNAME = ATTRVAL
+ {; ATTRNAME = ATTRVAL} <linefeed>
+
+ ENT-TYPE is 'F' for a file, in which case the entry specifies the
+ attributes for that file.
+
+ ENT-TYPE is 'D', and FILENAME empty, to specify default attributes
+ to be used for newly added files.
+
+ There is currently no way of quoting tabs or linefeeds in the
+ filename, '=' in ATTRNAME, ';' in ATTRVAL, etc. I'm not sure
+ whether I think we need one. Note: the current implementation also
+ doesn't handle '\0' in any of the fields.
+
+ By convention, ATTRNAME starting with '_' is for an attribute given
+ special meaning by CVS; other ATTRNAMEs are for user-defined attributes
+ (or will be, once we add commands to manipulate user-defined attributes).
+
+ Builtin attributes:
+
+ _watched: Present means the file is watched and should be checked out
+ read-only.
+
+ _watchers: Users with watches for this file. Value is
+ WATCHER > TYPE { , WATCHER > TYPE }
+ where WATCHER is a username, and TYPE is edit,unedit,commit separated by
+ + (or nothing if none; there is no "none" or "all" keyword).
+
+ _editors: Users editing this file. Value is
+ EDITOR > VAL { , EDITOR > VAL }
+ where EDITOR is a username, and VAL is TIME+HOSTNAME+PATHNAME, where
+ TIME is when the "cvs edit" command happened,
+ and HOSTNAME and PATHNAME are for the working directory. */
+
+#define CVSREP_FILEATTR "CVS/fileattr"
+
+/* Prepare for a new directory with repository REPOS. If REPOS is NULL,
+ then prepare for a "non-directory"; the caller can call fileattr_write
+ and fileattr_free, but must not call fileattr_get or fileattr_set. */
+extern void fileattr_startdir PROTO ((char *repos));
+
+/* Get the attribute ATTRNAME for file FILENAME. The return value
+ points into memory managed by the fileattr_* routines, should not
+ be altered by the caller, and is only good until the next call to
+ fileattr_clear or fileattr_set. It points to the value, terminated
+ by '\0' or ';'. Return NULL if said file lacks said attribute.
+ If FILENAME is NULL, return default attributes (attributes for
+ files created in the future). */
+extern char *fileattr_get PROTO ((char *filename, char *attrname));
+
+/* Like fileattr_get, but return a pointer to a newly malloc'd string
+ terminated by '\0' (or NULL if said file lacks said attribute). */
+extern char *fileattr_get0 PROTO ((char *filename, char *attrname));
+
+/* This is just a string manipulation function; it does not manipulate
+ file attributes as such.
+
+ LIST is in the format
+
+ ATTRNAME NAMEVALSEP ATTRVAL {ENTSEP ATTRNAME NAMEVALSEP ATTRVAL}
+
+ And we want to put in an attribute with name NAME and value VAL,
+ replacing the already-present attribute with name NAME if there is
+ one. Or if VAL is NULL remove attribute NAME. Return a new
+ malloc'd list; don't muck with the one passed in. If we are removing
+ the last attribute return NULL. LIST can be NULL to mean that we
+ started out without any attributes.
+
+ Examples:
+
+ fileattr_modify ("abc=def", "xxx", "val", '=', ';')) => "abc=def;xxx=val"
+ fileattr_modify ("abc=def", "abc", "val", '=', ';')) => "abc=val"
+ fileattr_modify ("abc=v1;def=v2", "abc", "val", '=', ';'))
+ => "abc=val;def=v2"
+ fileattr_modify ("abc=v1;def=v2", "def", "val", '=', ';'))
+ => "abc=v1;def=val"
+ fileattr_modify ("abc=v1;def=v2", "xxx", "val"))
+ => "abc=v1;def=v2;xxx=val"
+ fileattr_modify ("abc=v1;def=v2;ghi=v3", "def", "val", '=', ';'))
+ => "abc=v1;def=val;ghi=v3"
+*/
+
+extern char *fileattr_modify PROTO ((char *list, char *attrname,
+ char *attrval, int namevalsep,
+ int entsep));
+
+/* Set attribute ATTRNAME for file FILENAME to ATTRVAL. If ATTRVAL is NULL,
+ the attribute is removed. Changes are not written to disk until the
+ next call to fileattr_write. If FILENAME is NULL, set attributes for
+ files created in the future. If ATTRVAL is NULL, remove that attribute. */
+extern void fileattr_set PROTO ((char *filename, char *attrname,
+ char *attrval));
+
+/* Set the attributes for file FILENAME in whatever manner is appropriate
+ for a newly created file. */
+extern void fileattr_newfile PROTO ((char *filename));
+
+/* Write out all modified attributes. */
+extern void fileattr_write PROTO ((void));
+
+/* Free all memory allocated by fileattr_*. */
+extern void fileattr_free PROTO ((void));
+
+#define FILEATTR_H 1
+#endif /* fileattr.h */
diff --git a/contrib/cvs/src/filesubr.c b/contrib/cvs/src/filesubr.c
new file mode 100644
index 0000000..086da83
--- /dev/null
+++ b/contrib/cvs/src/filesubr.c
@@ -0,0 +1,662 @@
+/* filesubr.c --- subroutines for dealing with files
+ Jim Blandy <jimb@cyclic.com>
+
+ This file is part of GNU CVS.
+
+ GNU CVS is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2, or (at your option) any
+ later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+/* These functions were moved out of subr.c because they need different
+ definitions under operating systems (like, say, Windows NT) with different
+ file system semantics. */
+
+#include "cvs.h"
+
+/*
+ * I don't know of a convenient way to test this at configure time, or else
+ * I'd certainly do it there.
+ */
+#if defined(NeXT)
+#define LOSING_TMPNAM_FUNCTION
+#endif
+
+static int deep_remove_dir PROTO((const char *path));
+
+/*
+ * Copies "from" to "to".
+ */
+void
+copy_file (from, to)
+ const char *from;
+ const char *to;
+{
+ struct stat sb;
+ struct utimbuf t;
+ int fdin, fdout;
+
+ if (trace)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> copy(%s,%s)\n",
+ (server_active) ? 'S' : ' ', from, to);
+#else
+ (void) fprintf (stderr, "-> copy(%s,%s)\n", from, to);
+#endif
+ if (noexec)
+ return;
+
+ if ((fdin = open (from, O_RDONLY)) < 0)
+ error (1, errno, "cannot open %s for copying", from);
+ if (fstat (fdin, &sb) < 0)
+ error (1, errno, "cannot fstat %s", from);
+ if ((fdout = creat (to, (int) sb.st_mode & 07777)) < 0)
+ error (1, errno, "cannot create %s for copying", to);
+ if (sb.st_size > 0)
+ {
+ char buf[BUFSIZ];
+ int n;
+
+ for (;;)
+ {
+ n = read (fdin, buf, sizeof(buf));
+ if (n == -1)
+ {
+#ifdef EINTR
+ if (errno == EINTR)
+ continue;
+#endif
+ error (1, errno, "cannot read file %s for copying", from);
+ }
+ else if (n == 0)
+ break;
+
+ if (write(fdout, buf, n) != n) {
+ error (1, errno, "cannot write file %s for copying", to);
+ }
+ }
+
+#ifdef HAVE_FSYNC
+ if (fsync (fdout))
+ error (1, errno, "cannot fsync file %s after copying", to);
+#endif
+ }
+
+ if (close (fdin) < 0)
+ error (0, errno, "cannot close %s", from);
+ if (close (fdout) < 0)
+ error (1, errno, "cannot close %s", to);
+
+ /* now, set the times for the copied file to match those of the original */
+ memset ((char *) &t, 0, sizeof (t));
+ t.actime = sb.st_atime;
+ t.modtime = sb.st_mtime;
+ (void) utime (to, &t);
+}
+
+/* FIXME-krp: these functions would benefit from caching the char * &
+ stat buf. */
+
+/*
+ * Returns non-zero if the argument file is a directory, or is a symbolic
+ * link which points to a directory.
+ */
+int
+isdir (file)
+ const char *file;
+{
+ struct stat sb;
+
+ if (stat (file, &sb) < 0)
+ return (0);
+ return (S_ISDIR (sb.st_mode));
+}
+
+/*
+ * Returns non-zero if the argument file is a symbolic link.
+ */
+int
+islink (file)
+ const char *file;
+{
+#ifdef S_ISLNK
+ struct stat sb;
+
+ if (lstat (file, &sb) < 0)
+ return (0);
+ return (S_ISLNK (sb.st_mode));
+#else
+ return (0);
+#endif
+}
+
+/*
+ * Returns non-zero if the argument file exists.
+ */
+int
+isfile (file)
+ const char *file;
+{
+ return isaccessible(file, F_OK);
+}
+
+/*
+ * Returns non-zero if the argument file is readable.
+ */
+int
+isreadable (file)
+ const char *file;
+{
+ return isaccessible(file, R_OK);
+}
+
+/*
+ * Returns non-zero if the argument file is writable.
+ */
+int
+iswritable (file)
+ const char *file;
+{
+ return isaccessible(file, W_OK);
+}
+
+/*
+ * Returns non-zero if the argument file is accessable according to
+ * mode. If compiled with SETXID_SUPPORT also works if cvs has setxid
+ * bits set.
+ */
+int
+isaccessible (file, mode)
+ const char *file;
+ const int mode;
+{
+#ifdef SETXID_SUPPORT
+ struct stat sb;
+ int umask = 0;
+ int gmask = 0;
+ int omask = 0;
+ int uid;
+
+ if (stat(file, &sb) == -1)
+ return 0;
+ if (mode == F_OK)
+ return 1;
+
+ uid = geteuid();
+ if (uid == 0) /* superuser */
+ {
+ if (mode & X_OK)
+ return sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH);
+ else
+ return 1;
+ }
+
+ if (mode & R_OK)
+ {
+ umask |= S_IRUSR;
+ gmask |= S_IRGRP;
+ omask |= S_IROTH;
+ }
+ if (mode & W_OK)
+ {
+ umask |= S_IWUSR;
+ gmask |= S_IWGRP;
+ omask |= S_IWOTH;
+ }
+ if (mode & X_OK)
+ {
+ umask |= S_IXUSR;
+ gmask |= S_IXGRP;
+ omask |= S_IXOTH;
+ }
+
+ if (sb.st_uid == uid)
+ return (sb.st_mode & umask) == umask;
+ else if (sb.st_gid == getegid())
+ return (sb.st_mode & gmask) == gmask;
+ else
+ return (sb.st_mode & omask) == omask;
+#else
+ return access(file, mode) == 0;
+#endif
+}
+
+/*
+ * Open a file and die if it fails
+ */
+FILE *
+open_file (name, mode)
+ const char *name;
+ const char *mode;
+{
+ FILE *fp;
+
+ if ((fp = fopen (name, mode)) == NULL)
+ error (1, errno, "cannot open %s", name);
+ return (fp);
+}
+
+/*
+ * Make a directory and die if it fails
+ */
+void
+make_directory (name)
+ const char *name;
+{
+ struct stat sb;
+
+ if (stat (name, &sb) == 0 && (!S_ISDIR (sb.st_mode)))
+ error (0, 0, "%s already exists but is not a directory", name);
+ if (!noexec && mkdir (name, 0777) < 0)
+ error (1, errno, "cannot make directory %s", name);
+}
+
+/*
+ * Make a path to the argument directory, printing a message if something
+ * goes wrong.
+ */
+void
+make_directories (name)
+ const char *name;
+{
+ char *cp;
+
+ if (noexec)
+ return;
+
+ if (mkdir (name, 0777) == 0 || errno == EEXIST)
+ return;
+ if (! existence_error (errno))
+ {
+ error (0, errno, "cannot make path to %s", name);
+ return;
+ }
+ if ((cp = strrchr (name, '/')) == NULL)
+ return;
+ *cp = '\0';
+ make_directories (name);
+ *cp++ = '/';
+ if (*cp == '\0')
+ return;
+ (void) mkdir (name, 0777);
+}
+
+/*
+ * Change the mode of a file, either adding write permissions, or removing
+ * all write permissions. Either change honors the current umask setting.
+ */
+void
+xchmod (fname, writable)
+ char *fname;
+ int writable;
+{
+ struct stat sb;
+ mode_t mode, oumask;
+
+ if (stat (fname, &sb) < 0)
+ {
+ if (!noexec)
+ error (0, errno, "cannot stat %s", fname);
+ return;
+ }
+ oumask = umask (0);
+ (void) umask (oumask);
+ if (writable)
+ {
+ mode = sb.st_mode | (~oumask
+ & (((sb.st_mode & S_IRUSR) ? S_IWUSR : 0)
+ | ((sb.st_mode & S_IRGRP) ? S_IWGRP : 0)
+ | ((sb.st_mode & S_IROTH) ? S_IWOTH : 0)));
+ }
+ else
+ {
+ mode = sb.st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH) & ~oumask;
+ }
+
+ if (trace)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> chmod(%s,%o)\n",
+ (server_active) ? 'S' : ' ', fname,
+ (unsigned int) mode);
+#else
+ (void) fprintf (stderr, "-> chmod(%s,%o)\n", fname,
+ (unsigned int) mode);
+#endif
+ if (noexec)
+ return;
+
+ if (chmod (fname, mode) < 0)
+ error (0, errno, "cannot change mode of file %s", fname);
+}
+
+/*
+ * Rename a file and die if it fails
+ */
+void
+rename_file (from, to)
+ const char *from;
+ const char *to;
+{
+ if (trace)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> rename(%s,%s)\n",
+ (server_active) ? 'S' : ' ', from, to);
+#else
+ (void) fprintf (stderr, "-> rename(%s,%s)\n", from, to);
+#endif
+ if (noexec)
+ return;
+
+ if (rename (from, to) < 0)
+ error (1, errno, "cannot rename file %s to %s", from, to);
+}
+
+/*
+ * link a file, if possible. Warning: the Windows NT version of this
+ * function just copies the file, so only use this function in ways
+ * that can deal with either a link or a copy.
+ */
+int
+link_file (from, to)
+ const char *from;
+ const char *to;
+{
+ if (trace)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> link(%s,%s)\n",
+ (server_active) ? 'S' : ' ', from, to);
+#else
+ (void) fprintf (stderr, "-> link(%s,%s)\n", from, to);
+#endif
+ if (noexec)
+ return (0);
+
+ return (link (from, to));
+}
+
+/*
+ * unlink a file, if possible.
+ */
+int
+unlink_file (f)
+ const char *f;
+{
+ if (trace)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> unlink(%s)\n",
+ (server_active) ? 'S' : ' ', f);
+#else
+ (void) fprintf (stderr, "-> unlink(%s)\n", f);
+#endif
+ if (noexec)
+ return (0);
+
+ return (unlink (f));
+}
+
+/*
+ * Unlink a file or dir, if possible. If it is a directory do a deep
+ * removal of all of the files in the directory. Return -1 on error
+ * (in which case errno is set).
+ */
+int
+unlink_file_dir (f)
+ const char *f;
+{
+ if (trace)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> unlink_file_dir(%s)\n",
+ (server_active) ? 'S' : ' ', f);
+#else
+ (void) fprintf (stderr, "-> unlink_file_dir(%s)\n", f);
+#endif
+ if (noexec)
+ return (0);
+
+ /* For at least some unices, if root tries to unlink() a directory,
+ instead of doing something rational like returning EISDIR,
+ the system will gleefully go ahead and corrupt the filesystem.
+ So we first call isdir() to see if it is OK to call unlink(). This
+ doesn't quite work--if someone creates a directory between the
+ call to isdir() and the call to unlink(), we'll still corrupt
+ the filesystem. Where is the Unix Haters Handbook when you need
+ it? */
+ if (isdir(f))
+ return deep_remove_dir(f);
+ else
+ {
+ if (unlink (f) != 0)
+ return -1;
+ }
+ /* We were able to remove the file from the disk */
+ return 0;
+}
+
+/* Remove a directory and everything it contains. Returns 0 for
+ * success, -1 for failure (in which case errno is set).
+ */
+
+static int
+deep_remove_dir (path)
+ const char *path;
+{
+ DIR *dirp;
+ struct dirent *dp;
+ char buf[PATH_MAX];
+
+ if (rmdir (path) != 0 && (errno == ENOTEMPTY || errno == EEXIST))
+ {
+ if ((dirp = opendir (path)) == NULL)
+ /* If unable to open the directory return
+ * an error
+ */
+ return -1;
+
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (strcmp (dp->d_name, ".") == 0 ||
+ strcmp (dp->d_name, "..") == 0)
+ continue;
+
+ sprintf (buf, "%s/%s", path, dp->d_name);
+
+ /* See comment in unlink_file_dir explanation of why we use
+ isdir instead of just calling unlink and checking the
+ status. */
+ if (isdir(buf))
+ {
+ if (deep_remove_dir(buf))
+ {
+ closedir(dirp);
+ return -1;
+ }
+ }
+ else
+ {
+ if (unlink (buf) != 0)
+ {
+ closedir(dirp);
+ return -1;
+ }
+ }
+ }
+ closedir (dirp);
+ return rmdir (path);
+ }
+
+ /* Was able to remove the directory return 0 */
+ return 0;
+}
+
+/* Read NCHARS bytes from descriptor FD into BUF.
+ Return the number of characters successfully read.
+ The number returned is always NCHARS unless end-of-file or error. */
+static size_t
+block_read (fd, buf, nchars)
+ int fd;
+ char *buf;
+ size_t nchars;
+{
+ char *bp = buf;
+ size_t nread;
+
+ do
+ {
+ nread = read (fd, bp, nchars);
+ if (nread == (size_t)-1)
+ {
+#ifdef EINTR
+ if (errno == EINTR)
+ continue;
+#endif
+ return (size_t)-1;
+ }
+
+ if (nread == 0)
+ break;
+
+ bp += nread;
+ nchars -= nread;
+ } while (nchars != 0);
+
+ return bp - buf;
+}
+
+
+/*
+ * Compare "file1" to "file2". Return non-zero if they don't compare exactly.
+ */
+int
+xcmp (file1, file2)
+ const char *file1;
+ const char *file2;
+{
+ char *buf1, *buf2;
+ struct stat sb1, sb2;
+ int fd1, fd2;
+ int ret;
+
+ if ((fd1 = open (file1, O_RDONLY)) < 0)
+ error (1, errno, "cannot open file %s for comparing", file1);
+ if ((fd2 = open (file2, O_RDONLY)) < 0)
+ error (1, errno, "cannot open file %s for comparing", file2);
+ if (fstat (fd1, &sb1) < 0)
+ error (1, errno, "cannot fstat %s", file1);
+ if (fstat (fd2, &sb2) < 0)
+ error (1, errno, "cannot fstat %s", file2);
+
+ /* A generic file compare routine might compare st_dev & st_ino here
+ to see if the two files being compared are actually the same file.
+ But that won't happen in CVS, so we won't bother. */
+
+ if (sb1.st_size != sb2.st_size)
+ ret = 1;
+ else if (sb1.st_size == 0)
+ ret = 0;
+ else
+ {
+ /* FIXME: compute the optimal buffer size by computing the least
+ common multiple of the files st_blocks field */
+ size_t buf_size = 8 * 1024;
+ size_t read1;
+ size_t read2;
+
+ buf1 = xmalloc (buf_size);
+ buf2 = xmalloc (buf_size);
+
+ do
+ {
+ read1 = block_read (fd1, buf1, buf_size);
+ if (read1 == (size_t)-1)
+ error (1, errno, "cannot read file %s for comparing", file1);
+
+ read2 = block_read (fd2, buf2, buf_size);
+ if (read2 == (size_t)-1)
+ error (1, errno, "cannot read file %s for comparing", file2);
+
+ /* assert (read1 == read2); */
+
+ ret = memcmp(buf1, buf2, read1);
+ } while (ret == 0 && read1 == buf_size);
+
+ free (buf1);
+ free (buf2);
+ }
+
+ (void) close (fd1);
+ (void) close (fd2);
+ return (ret);
+}
+
+#ifdef LOSING_TMPNAM_FUNCTION
+char *tmpnam(char *s)
+{
+ static char value[L_tmpnam+1];
+
+ if (s){
+ strcpy(s,"/tmp/cvsXXXXXX");
+ mktemp(s);
+ return s;
+ }else{
+ strcpy(value,"/tmp/cvsXXXXXX");
+ mktemp(s);
+ return value;
+ }
+}
+#endif
+
+/* Return non-zero iff FILENAME is absolute.
+ Trivial under Unix, but more complicated under other systems. */
+int
+isabsolute (filename)
+ const char *filename;
+{
+ return filename[0] == '/';
+}
+
+
+/* Return a pointer into PATH's last component. */
+char *
+last_component (path)
+ char *path;
+{
+ char *last = strrchr (path, '/');
+
+ if (last)
+ return last + 1;
+ else
+ return path;
+}
+
+/* Return the home directory. Returns a pointer to storage
+ managed by this function or its callees (currently getenv). */
+char *
+get_homedir ()
+{
+ return getenv ("HOME");
+}
+
+/* See cvs.h for description. On unix this does nothing, because the
+ shell expands the wildcards. */
+void
+expand_wild (argc, argv, pargc, pargv)
+ int argc;
+ char **argv;
+ int *pargc;
+ char ***pargv;
+{
+ int i;
+ *pargc = argc;
+ *pargv = (char **) xmalloc (argc * sizeof (char *));
+ for (i = 0; i < argc; ++i)
+ (*pargv)[i] = xstrdup (argv[i]);
+}
diff --git a/contrib/cvs/src/find_names.c b/contrib/cvs/src/find_names.c
new file mode 100644
index 0000000..4885437
--- /dev/null
+++ b/contrib/cvs/src/find_names.c
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Find Names
+ *
+ * Finds all the pertinent file names, both from the administration and from the
+ * repository
+ *
+ * Find Dirs
+ *
+ * Finds all pertinent sub-directories of the checked out instantiation and the
+ * repository (and optionally the attic)
+ */
+
+#include "cvs.h"
+
+static int find_dirs PROTO((char *dir, List * list, int checkadm));
+static int find_rcs PROTO((char *dir, List * list));
+
+static List *filelist;
+
+/*
+ * add the key from entry on entries list to the files list
+ */
+static int add_entries_proc PROTO((Node *, void *));
+static int
+add_entries_proc (node, closure)
+ Node *node;
+ void *closure;
+{
+ Node *fnode;
+
+ fnode = getnode ();
+ fnode->type = FILES;
+ fnode->key = xstrdup (node->key);
+ if (addnode (filelist, fnode) != 0)
+ freenode (fnode);
+ return (0);
+}
+
+/*
+ * compare two files list node (for sort)
+ */
+static int fsortcmp PROTO ((const Node *, const Node *));
+static int
+fsortcmp (p, q)
+ const Node *p;
+ const Node *q;
+{
+ return (strcmp (p->key, q->key));
+}
+
+List *
+Find_Names (repository, which, aflag, optentries)
+ char *repository;
+ int which;
+ int aflag;
+ List **optentries;
+{
+ List *entries;
+ List *files;
+ char dir[PATH_MAX];
+
+ /* make a list for the files */
+ files = filelist = getlist ();
+
+ /* look at entries (if necessary) */
+ if (which & W_LOCAL)
+ {
+ /* parse the entries file (if it exists) */
+ entries = Entries_Open (aflag);
+ if (entries != NULL)
+ {
+ /* walk the entries file adding elements to the files list */
+ (void) walklist (entries, add_entries_proc, NULL);
+
+ /* if our caller wanted the entries list, return it; else free it */
+ if (optentries != NULL)
+ *optentries = entries;
+ else
+ Entries_Close (entries);
+ }
+ }
+
+ if ((which & W_REPOS) && repository && !isreadable (CVSADM_ENTSTAT))
+ {
+ /* search the repository */
+ if (find_rcs (repository, files) != 0)
+ error (1, errno, "cannot open directory %s", repository);
+
+ /* search the attic too */
+ if (which & W_ATTIC)
+ {
+ (void) sprintf (dir, "%s/%s", repository, CVSATTIC);
+ (void) find_rcs (dir, files);
+ }
+ }
+
+ /* sort the list into alphabetical order and return it */
+ sortlist (files, fsortcmp);
+ return (files);
+}
+
+/*
+ * create a list of directories to traverse from the current directory
+ */
+List *
+Find_Directories (repository, which)
+ char *repository;
+ int which;
+{
+ List *dirlist;
+
+ /* make a list for the directories */
+ dirlist = getlist ();
+
+ /* find the local ones */
+ if (which & W_LOCAL)
+ {
+ /* look only for CVS controlled sub-directories */
+ if (find_dirs (".", dirlist, 1) != 0)
+ error (1, errno, "cannot open current directory");
+ }
+
+ /* look for sub-dirs in the repository */
+ if ((which & W_REPOS) && repository)
+ {
+ /* search the repository */
+ if (find_dirs (repository, dirlist, 0) != 0)
+ error (1, errno, "cannot open directory %s", repository);
+
+#ifdef ATTIC_DIR_SUPPORT /* XXX - FIXME */
+ /* search the attic too */
+ if (which & W_ATTIC)
+ {
+ char dir[PATH_MAX];
+
+ (void) sprintf (dir, "%s/%s", repository, CVSATTIC);
+ (void) find_dirs (dir, dirlist, 0);
+ }
+#endif
+ }
+
+ /* sort the list into alphabetical order and return it */
+ sortlist (dirlist, fsortcmp);
+ return (dirlist);
+}
+
+/*
+ * Finds all the ,v files in the argument directory, and adds them to the
+ * files list. Returns 0 for success and non-zero if the argument directory
+ * cannot be opened.
+ */
+static int
+find_rcs (dir, list)
+ char *dir;
+ List *list;
+{
+ Node *p;
+ struct dirent *dp;
+ DIR *dirp;
+
+ /* set up to read the dir */
+ if ((dirp = opendir (dir)) == NULL)
+ return (1);
+
+ /* read the dir, grabbing the ,v files */
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (fnmatch (RCSPAT, dp->d_name, 0) == 0)
+ {
+ char *comma;
+
+ comma = strrchr (dp->d_name, ','); /* strip the ,v */
+ *comma = '\0';
+ p = getnode ();
+ p->type = FILES;
+ p->key = xstrdup (dp->d_name);
+ if (addnode (list, p) != 0)
+ freenode (p);
+ }
+ }
+ (void) closedir (dirp);
+ return (0);
+}
+
+/*
+ * Finds all the subdirectories of the argument dir and adds them to the
+ * specified list. Sub-directories without a CVS administration directory
+ * are optionally ignored Returns 0 for success or 1 on error.
+ */
+static int
+find_dirs (dir, list, checkadm)
+ char *dir;
+ List *list;
+ int checkadm;
+{
+ Node *p;
+ char tmp[PATH_MAX];
+ struct dirent *dp;
+ DIR *dirp;
+
+ /* set up to read the dir */
+ if ((dirp = opendir (dir)) == NULL)
+ return (1);
+
+ /* read the dir, grabbing sub-dirs */
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (strcmp (dp->d_name, ".") == 0 ||
+ strcmp (dp->d_name, "..") == 0 ||
+ strcmp (dp->d_name, CVSATTIC) == 0 ||
+ strcmp (dp->d_name, CVSLCK) == 0 ||
+ strcmp (dp->d_name, CVSREP) == 0)
+ continue;
+
+#ifdef DT_DIR
+ if (dp->d_type != DT_DIR)
+ {
+ if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_LNK)
+ continue;
+#endif
+ /* don't bother stating ,v files */
+ if (fnmatch (RCSPAT, dp->d_name, 0) == 0)
+ continue;
+
+ sprintf (tmp, "%s/%s", dir, dp->d_name);
+ if (!isdir (tmp))
+ continue;
+
+#ifdef DT_DIR
+ }
+#endif
+
+ /* check for administration directories (if needed) */
+ if (checkadm)
+ {
+ /* blow off symbolic links to dirs in local dir */
+#ifdef DT_DIR
+ if (dp->d_type != DT_DIR)
+ {
+ /* we're either unknown or a symlink at this point */
+ if (dp->d_type == DT_LNK)
+ continue;
+#endif
+ if (islink (tmp))
+ continue;
+#ifdef DT_DIR
+ }
+#endif
+
+ /* check for new style */
+ (void) sprintf (tmp, "%s/%s/%s", dir, dp->d_name, CVSADM);
+ if (!isdir (tmp))
+ continue;
+ }
+
+ /* put it in the list */
+ p = getnode ();
+ p->type = DIRS;
+ p->key = xstrdup (dp->d_name);
+ if (addnode (list, p) != 0)
+ freenode (p);
+ }
+ (void) closedir (dirp);
+ return (0);
+}
diff --git a/contrib/cvs/src/hash.c b/contrib/cvs/src/hash.c
new file mode 100644
index 0000000..2197db0
--- /dev/null
+++ b/contrib/cvs/src/hash.c
@@ -0,0 +1,442 @@
+/*
+ * 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.
+ *
+ * Polk's hash list manager. So cool.
+ */
+
+#include "cvs.h"
+#include <assert.h>
+
+/* global caches */
+static List *listcache = NULL;
+static Node *nodecache = NULL;
+
+static void freenode_mem PROTO((Node * p));
+
+/* hash function */
+static int
+hashp (key)
+ const char *key;
+{
+ unsigned int h = 0;
+ unsigned int g;
+
+ assert(key != NULL);
+
+ while (*key != 0)
+ {
+ unsigned int c = *key++;
+ /* The FOLD_FN_CHAR is so that findnode_fn works. */
+ h = (h << 4) + FOLD_FN_CHAR (c);
+ if ((g = h & 0xf0000000) != 0)
+ h = (h ^ (g >> 24)) ^ g;
+ }
+
+ return (h % HASHSIZE);
+}
+
+/*
+ * create a new list (or get an old one from the cache)
+ */
+List *
+getlist ()
+{
+ int i;
+ List *list;
+ Node *node;
+
+ if (listcache != NULL)
+ {
+ /* get a list from the cache and clear it */
+ list = listcache;
+ listcache = listcache->next;
+ list->next = (List *) NULL;
+ for (i = 0; i < HASHSIZE; i++)
+ list->hasharray[i] = (Node *) NULL;
+ }
+ else
+ {
+ /* make a new list from scratch */
+ list = (List *) xmalloc (sizeof (List));
+ memset ((char *) list, 0, sizeof (List));
+ node = getnode ();
+ list->list = node;
+ node->type = HEADER;
+ node->next = node->prev = node;
+ }
+ return (list);
+}
+
+/*
+ * free up a list
+ */
+void
+dellist (listp)
+ List **listp;
+{
+ int i;
+ Node *p;
+
+ if (*listp == (List *) NULL)
+ return;
+
+ p = (*listp)->list;
+
+ /* free each node in the list (except header) */
+ while (p->next != p)
+ delnode (p->next);
+
+ /* free any list-private data, without freeing the actual header */
+ freenode_mem (p);
+
+ /* free up the header nodes for hash lists (if any) */
+ for (i = 0; i < HASHSIZE; i++)
+ {
+ if ((p = (*listp)->hasharray[i]) != (Node *) NULL)
+ {
+ /* put the nodes into the cache */
+ p->type = UNKNOWN;
+ p->next = nodecache;
+ nodecache = p;
+ }
+ }
+
+ /* put it on the cache */
+ (*listp)->next = listcache;
+ listcache = *listp;
+ *listp = (List *) NULL;
+}
+
+/*
+ * get a new list node
+ */
+Node *
+getnode ()
+{
+ Node *p;
+
+ if (nodecache != (Node *) NULL)
+ {
+ /* get one from the cache */
+ p = nodecache;
+ nodecache = p->next;
+ }
+ else
+ {
+ /* make a new one */
+ p = (Node *) xmalloc (sizeof (Node));
+ }
+
+ /* always make it clean */
+ memset ((char *) p, 0, sizeof (Node));
+ p->type = UNKNOWN;
+
+ return (p);
+}
+
+/*
+ * remove a node from it's list (maybe hash list too) and free it
+ */
+void
+delnode (p)
+ Node *p;
+{
+ if (p == (Node *) NULL)
+ return;
+
+ /* take it out of the list */
+ p->next->prev = p->prev;
+ p->prev->next = p->next;
+
+ /* if it was hashed, remove it from there too */
+ if (p->hashnext != (Node *) NULL)
+ {
+ p->hashnext->hashprev = p->hashprev;
+ p->hashprev->hashnext = p->hashnext;
+ }
+
+ /* free up the storage */
+ freenode (p);
+}
+
+/*
+ * free up the storage associated with a node
+ */
+static void
+freenode_mem (p)
+ Node *p;
+{
+ if (p->delproc != (void (*) ()) NULL)
+ p->delproc (p); /* call the specified delproc */
+ else
+ {
+ if (p->data != NULL) /* otherwise free() it if necessary */
+ free (p->data);
+ }
+ if (p->key != NULL) /* free the key if necessary */
+ free (p->key);
+
+ /* to be safe, re-initialize these */
+ p->key = p->data = (char *) NULL;
+ p->delproc = (void (*) ()) NULL;
+}
+
+/*
+ * free up the storage associated with a node and recycle it
+ */
+void
+freenode (p)
+ Node *p;
+{
+ /* first free the memory */
+ freenode_mem (p);
+
+ /* then put it in the cache */
+ p->type = UNKNOWN;
+ p->next = nodecache;
+ nodecache = p;
+}
+
+/*
+ * insert item p at end of list "list" (maybe hash it too) if hashing and it
+ * already exists, return -1 and don't actually put it in the list
+ *
+ * return 0 on success
+ */
+int
+addnode (list, p)
+ List *list;
+ Node *p;
+{
+ int hashval;
+ Node *q;
+
+ if (p->key != NULL) /* hash it too? */
+ {
+ hashval = hashp (p->key);
+ if (list->hasharray[hashval] == NULL) /* make a header for list? */
+ {
+ q = getnode ();
+ q->type = HEADER;
+ list->hasharray[hashval] = q->hashnext = q->hashprev = q;
+ }
+
+ /* put it into the hash list if it's not already there */
+ for (q = list->hasharray[hashval]->hashnext;
+ q != list->hasharray[hashval]; q = q->hashnext)
+ {
+ if (strcmp (p->key, q->key) == 0)
+ return (-1);
+ }
+ q = list->hasharray[hashval];
+ p->hashprev = q->hashprev;
+ p->hashnext = q;
+ p->hashprev->hashnext = p;
+ q->hashprev = p;
+ }
+
+ /* put it into the regular list */
+ p->prev = list->list->prev;
+ p->next = list->list;
+ list->list->prev->next = p;
+ list->list->prev = p;
+
+ return (0);
+}
+
+/* Look up an entry in hash list table and return a pointer to the
+ node. Return NULL if not found. Abort with a fatal error for
+ errors. */
+Node *
+findnode (list, key)
+ List *list;
+ const char *key;
+{
+ Node *head, *p;
+
+ /* This probably should be "assert (list != NULL)" (or if not we
+ should document the current behavior), but only if we check all
+ the callers to see if any are relying on this behavior. */
+ if ((list == (List *) NULL))
+ return ((Node *) NULL);
+
+ assert (key != NULL);
+
+ head = list->hasharray[hashp (key)];
+ if (head == (Node *) NULL)
+ /* Not found. */
+ return ((Node *) NULL);
+
+ for (p = head->hashnext; p != head; p = p->hashnext)
+ if (strcmp (p->key, key) == 0)
+ return (p);
+ return ((Node *) NULL);
+}
+
+/*
+ * Like findnode, but for a filename.
+ */
+Node *
+findnode_fn (list, key)
+ List *list;
+ const char *key;
+{
+ Node *head, *p;
+
+ /* This probably should be "assert (list != NULL)" (or if not we
+ should document the current behavior), but only if we check all
+ the callers to see if any are relying on this behavior. */
+ if (list == (List *) NULL)
+ return ((Node *) NULL);
+
+ assert (key != NULL);
+
+ head = list->hasharray[hashp (key)];
+ if (head == (Node *) NULL)
+ return ((Node *) NULL);
+
+ for (p = head->hashnext; p != head; p = p->hashnext)
+ if (fncmp (p->key, key) == 0)
+ return (p);
+ return ((Node *) NULL);
+}
+
+/*
+ * walk a list with a specific proc
+ */
+int
+walklist (list, proc, closure)
+ List *list;
+ int (*proc) PROTO ((Node *, void *));
+ void *closure;
+{
+ Node *head, *p;
+ int err = 0;
+
+ if (list == NULL)
+ return (0);
+
+ head = list->list;
+ for (p = head->next; p != head; p = p->next)
+ err += proc (p, closure);
+ return (err);
+}
+
+int
+list_isempty (list)
+ List *list;
+{
+ return list == NULL || list->list->next == list->list;
+}
+
+/*
+ * sort the elements of a list (in place)
+ */
+void
+sortlist (list, comp)
+ List *list;
+ int (*comp) PROTO ((const Node *, const Node *));
+{
+ Node *head, *remain, *p, *q;
+
+ /* save the old first element of the list */
+ head = list->list;
+ remain = head->next;
+
+ /* make the header node into a null list of it's own */
+ head->next = head->prev = head;
+
+ /* while there are nodes remaining, do insert sort */
+ while (remain != head)
+ {
+ /* take one from the list */
+ p = remain;
+ remain = remain->next;
+
+ /* traverse the sorted list looking for the place to insert it */
+ for (q = head->next; q != head; q = q->next)
+ {
+ if (comp (p, q) < 0)
+ {
+ /* p comes before q */
+ p->next = q;
+ p->prev = q->prev;
+ p->prev->next = p;
+ q->prev = p;
+ break;
+ }
+ }
+ if (q == head)
+ {
+ /* it belongs at the end of the list */
+ p->next = head;
+ p->prev = head->prev;
+ p->prev->next = p;
+ head->prev = p;
+ }
+ }
+}
+
+/* Debugging functions. Quite useful to call from within gdb. */
+
+char *
+nodetypestring (type)
+ Ntype type;
+{
+ switch (type) {
+ case UNKNOWN: return("UNKNOWN");
+ case HEADER: return("HEADER");
+ case ENTRIES: return("ENTRIES");
+ case FILES: return("FILES");
+ case LIST: return("LIST");
+ case RCSNODE: return("RCSNODE");
+ case RCSVERS: return("RCSVERS");
+ case DIRS: return("DIRS");
+ case UPDATE: return("UPDATE");
+ case LOCK: return("LOCK");
+ case NDBMNODE: return("NDBMNODE");
+ case FILEATTR: return("FILEATTR");
+ case VARIABLE: return("VARIABLE");
+ }
+
+ return("<trash>");
+}
+
+static int printnode PROTO ((Node *, void *));
+static int
+printnode (node, closure)
+ Node *node;
+ void *closure;
+{
+ if (node == NULL)
+ {
+ (void) printf("NULL node.\n");
+ return(0);
+ }
+
+ (void) printf("Node at 0x%p: type = %s, key = 0x%p = \"%s\", data = 0x%p, next = 0x%p, prev = 0x%p\n",
+ node, nodetypestring(node->type), node->key, node->key, node->data, node->next, node->prev);
+
+ return(0);
+}
+
+void
+printlist (list)
+ List *list;
+{
+ if (list == NULL)
+ {
+ (void) printf("NULL list.\n");
+ return;
+ }
+
+ (void) printf("List at 0x%p: list = 0x%p, HASHSIZE = %d, next = 0x%p\n",
+ list, list->list, HASHSIZE, list->next);
+
+ (void) walklist(list, printnode, NULL);
+
+ return;
+}
diff --git a/contrib/cvs/src/hash.h b/contrib/cvs/src/hash.h
new file mode 100644
index 0000000..dd83665
--- /dev/null
+++ b/contrib/cvs/src/hash.h
@@ -0,0 +1,58 @@
+/* $CVSid: @(#)hash.h 1.23 94/10/07 $ */
+
+/*
+ * 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.
+ */
+
+/*
+ * The number of buckets for the hash table contained in each list. This
+ * should probably be prime.
+ */
+#define HASHSIZE 151
+
+/*
+ * Types of nodes
+ */
+enum ntype
+{
+ UNKNOWN, HEADER, ENTRIES, FILES, LIST, RCSNODE,
+ RCSVERS, DIRS, UPDATE, LOCK, NDBMNODE, FILEATTR,
+ VARIABLE
+};
+typedef enum ntype Ntype;
+
+struct node
+{
+ Ntype type;
+ struct node *next;
+ struct node *prev;
+ struct node *hashnext;
+ struct node *hashprev;
+ char *key;
+ char *data;
+ void (*delproc) ();
+};
+typedef struct node Node;
+
+struct list
+{
+ Node *list;
+ Node *hasharray[HASHSIZE];
+ struct list *next;
+};
+typedef struct list List;
+
+List *getlist PROTO((void));
+Node *findnode PROTO((List * list, const char *key));
+Node *findnode_fn PROTO((List * list, const char *key));
+Node *getnode PROTO((void));
+int addnode PROTO((List * list, Node * p));
+int walklist PROTO((List * list, int (*)(Node *n, void *closure), void *closure));
+int list_isempty PROTO ((List *list));
+void dellist PROTO((List ** listp));
+void delnode PROTO((Node * p));
+void freenode PROTO((Node * p));
+void sortlist PROTO((List * list, int (*)(const Node *, const Node *)));
diff --git a/contrib/cvs/src/history.c b/contrib/cvs/src/history.c
new file mode 100644
index 0000000..81c71ff
--- /dev/null
+++ b/contrib/cvs/src/history.c
@@ -0,0 +1,1484 @@
+/*
+ *
+ * You may distribute under the terms of the GNU General Public License
+ * as specified in the README file that comes with the CVS 1.0 kit.
+ *
+ * **************** History of Users and Module ****************
+ *
+ * LOGGING: Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY".
+ *
+ * On For each Tag, Add, Checkout, Commit, Update or Release command,
+ * one line of text is written to a History log.
+ *
+ * X date | user | CurDir | special | rev(s) | argument '\n'
+ *
+ * where: [The spaces in the example line above are not in the history file.]
+ *
+ * X is a single character showing the type of event:
+ * T "Tag" cmd.
+ * O "Checkout" cmd.
+ * F "Release" cmd.
+ * W "Update" cmd - No User file, Remove from Entries file.
+ * U "Update" cmd - File was checked out over User file.
+ * G "Update" cmd - File was merged successfully.
+ * C "Update" cmd - File was merged and shows overlaps.
+ * M "Commit" cmd - "Modified" file.
+ * A "Commit" cmd - "Added" file.
+ * R "Commit" cmd - "Removed" file.
+ *
+ * date is a fixed length 8-char hex representation of a Unix time_t.
+ * [Starting here, variable fields are delimited by '|' chars.]
+ *
+ * user is the username of the person who typed the command.
+ *
+ * CurDir The directory where the action occurred. This should be the
+ * absolute path of the directory which is at the same level as
+ * the "Repository" field (for W,U,G,C & M,A,R).
+ *
+ * Repository For record types [W,U,G,C,M,A,R] this field holds the
+ * repository read from the administrative data where the
+ * command was typed.
+ * T "A" --> New Tag, "D" --> Delete Tag
+ * Otherwise it is the Tag or Date to modify.
+ * O,F A "" (null field)
+ *
+ * rev(s) Revision number or tag.
+ * T The Tag to apply.
+ * O The Tag or Date, if specified, else "" (null field).
+ * F "" (null field)
+ * W The Tag or Date, if specified, else "" (null field).
+ * U The Revision checked out over the User file.
+ * G,C The Revision(s) involved in merge.
+ * M,A,R RCS Revision affected.
+ *
+ * argument The module (for [TOUF]) or file (for [WUGCMAR]) affected.
+ *
+ *
+ *** Report categories: "User" and "Since" modifiers apply to all reports.
+ * [For "sort" ordering see the "sort_order" routine.]
+ *
+ * Extract list of record types
+ *
+ * -e, -x [TOFWUGCMAR]
+ *
+ * Extracted records are simply printed, No analysis is performed.
+ * All "field" modifiers apply. -e chooses all types.
+ *
+ * Checked 'O'ut modules
+ *
+ * -o, -w
+ * Checked out modules. 'F' and 'O' records are examined and if
+ * the last record for a repository/file is an 'O', a line is
+ * printed. "-w" forces the "working dir" to be used in the
+ * comparison instead of the repository.
+ *
+ * Committed (Modified) files
+ *
+ * -c, -l, -w
+ * All 'M'odified, 'A'dded and 'R'emoved records are examined.
+ * "Field" modifiers apply. -l forces a sort by file within user
+ * and shows only the last modifier. -w works as in Checkout.
+ *
+ * Warning: Be careful with what you infer from the output of
+ * "cvs hi -c -l". It means the last time *you*
+ * changed the file, not the list of files for which
+ * you were the last changer!!!
+ *
+ * Module history for named modules.
+ * -m module, -l
+ *
+ * This is special. If one or more modules are specified, the
+ * module names are remembered and the files making up the
+ * modules are remembered. Only records matching exactly those
+ * files and repositories are shown. Sorting by "module", then
+ * filename, is implied. If -l ("last modified") is specified,
+ * then "update" records (types WUCG), tag and release records
+ * are ignored and the last (by date) "modified" record.
+ *
+ * TAG history
+ *
+ * -T All Tag records are displayed.
+ *
+ *** Modifiers.
+ *
+ * Since ... [All records contain a timestamp, so any report
+ * category can be limited by date.]
+ *
+ * -D date - The "date" is parsed into a Unix "time_t" and
+ * records with an earlier time stamp are ignored.
+ * -r rev/tag - A "rev" begins with a digit. A "tag" does not. If
+ * you use this option, every file is searched for the
+ * indicated rev/tag.
+ * -t tag - The "tag" is searched for in the history file and no
+ * record is displayed before the tag is found. An
+ * error is printed if the tag is never found.
+ * -b string - Records are printed only back to the last reference
+ * to the string in the "module", "file" or
+ * "repository" fields.
+ *
+ * Field Selections [Simple comparisons on existing fields. All field
+ * selections are repeatable.]
+ *
+ * -a - All users.
+ * -u user - If no user is given and '-a' is not given, only
+ * records for the user typing the command are shown.
+ * ==> If -a or -u is not specified, just use "self".
+ *
+ * -f filematch - Only records in which the "file" field contains the
+ * string "filematch" are considered.
+ *
+ * -p repository - Only records in which the "repository" string is a
+ * prefix of the "repos" field are considered.
+ *
+ * -m modulename - Only records which contain "modulename" in the
+ * "module" field are considered.
+ *
+ *
+ * EXAMPLES: ("cvs history", "cvs his" or "cvs hi")
+ *
+ *** Checked out files for username. (default self, e.g. "dgg")
+ * cvs hi [equivalent to: "cvs hi -o -u dgg"]
+ * cvs hi -u user [equivalent to: "cvs hi -o -u user"]
+ * cvs hi -o [equivalent to: "cvs hi -o -u dgg"]
+ *
+ *** Committed (modified) files from the beginning of the file.
+ * cvs hi -c [-u user]
+ *
+ *** Committed (modified) files since Midnight, January 1, 1990:
+ * cvs hi -c -D 'Jan 1 1990' [-u user]
+ *
+ *** Committed (modified) files since tag "TAG" was stored in the history file:
+ * cvs hi -c -t TAG [-u user]
+ *
+ *** Committed (modified) files since tag "TAG" was placed on the files:
+ * cvs hi -c -r TAG [-u user]
+ *
+ *** Who last committed file/repository X?
+ * cvs hi -c -l -[fp] X
+ *
+ *** Modified files since tag/date/file/repos?
+ * cvs hi -c {-r TAG | -D Date | -b string}
+ *
+ *** Tag history
+ * cvs hi -T
+ *
+ *** History of file/repository/module X.
+ * cvs hi -[fpn] X
+ *
+ *** History of user "user".
+ * cvs hi -e -u user
+ *
+ *** Dump (eXtract) specified record types
+ * cvs hi -x [TOFWUGCMAR]
+ *
+ *
+ * FUTURE: J[Join], I[Import] (Not currently implemented.)
+ *
+ */
+
+#include "cvs.h"
+
+static struct hrec
+{
+ char *type; /* Type of record (In history record) */
+ char *user; /* Username (In history record) */
+ char *dir; /* "Compressed" Working dir (In history record) */
+ char *repos; /* (Tag is special.) Repository (In history record) */
+ char *rev; /* Revision affected (In history record) */
+ char *file; /* Filename (In history record) */
+ char *end; /* Ptr into repository to copy at end of workdir */
+ char *mod; /* The module within which the file is contained */
+ time_t date; /* Calculated from date stored in record */
+ int idx; /* Index of record, for "stable" sort. */
+} *hrec_head;
+
+
+static char *fill_hrec PROTO((char *line, struct hrec * hr));
+static int accept_hrec PROTO((struct hrec * hr, struct hrec * lr));
+static int select_hrec PROTO((struct hrec * hr));
+static int sort_order PROTO((const PTR l, const PTR r));
+static int within PROTO((char *find, char *string));
+static time_t date_and_time PROTO((char *date_str));
+static void expand_modules PROTO((void));
+static void read_hrecs PROTO((char *fname));
+static void report_hrecs PROTO((void));
+static void save_file PROTO((char *dir, char *name, char *module));
+static void save_module PROTO((char *module));
+static void save_user PROTO((char *name));
+
+#define ALL_REC_TYPES "TOFWUCGMAR"
+#define USER_INCREMENT 2
+#define FILE_INCREMENT 128
+#define MODULE_INCREMENT 5
+#define HREC_INCREMENT 128
+
+static short report_count;
+
+static short extract;
+static short v_checkout;
+static short modified;
+static short tag_report;
+static short module_report;
+static short working;
+static short last_entry;
+static short all_users;
+
+static short user_sort;
+static short repos_sort;
+static short file_sort;
+static short module_sort;
+
+#ifdef HAVE_RCS5
+static short tz_local;
+static time_t tz_seconds_east_of_GMT;
+static char *tz_name = "+0000";
+#else
+static char tz_name[] = "LT";
+#endif
+
+static time_t since_date;
+static char since_rev[20]; /* Maxrev ~= 99.99.99.999 */
+static char since_tag[64];
+static struct hrec *last_since_tag;
+static char backto[128];
+static struct hrec *last_backto;
+static char rec_types[20];
+
+static int hrec_count;
+static int hrec_max;
+
+static char **user_list; /* Ptr to array of ptrs to user names */
+static int user_max; /* Number of elements allocated */
+static int user_count; /* Number of elements used */
+
+static struct file_list_str
+{
+ char *l_file;
+ char *l_module;
+} *file_list; /* Ptr to array file name structs */
+static int file_max; /* Number of elements allocated */
+static int file_count; /* Number of elements used */
+
+static char **mod_list; /* Ptr to array of ptrs to module names */
+static int mod_max; /* Number of elements allocated */
+static int mod_count; /* Number of elements used */
+
+static char *histfile; /* Ptr to the history file name */
+
+static const char *const history_usg[] =
+{
+ "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n",
+ " Reports:\n",
+ " -T Produce report on all TAGs\n",
+ " -c Committed (Modified) files\n",
+ " -o Checked out modules\n",
+ " -m <module> Look for specified module (repeatable)\n",
+ " -x [TOFWUCGMAR] Extract by record type\n",
+ " Flags:\n",
+ " -a All users (Default is self)\n",
+ " -e Everything (same as -x, but all record types)\n",
+ " -l Last modified (committed or modified report)\n",
+ " -w Working directory must match\n",
+ " Options:\n",
+ " -D <date> Since date (Many formats)\n",
+ " -b <str> Back to record with str in module/file/repos field\n",
+ " -f <file> Specified file (same as command line) (repeatable)\n",
+ " -n <modulename> In module (repeatable)\n",
+ " -p <repos> In repository (repeatable)\n",
+ " -r <rev/tag> Since rev or tag (looks inside RCS files!)\n",
+ " -t <tag> Since tag record placed in history file (by anyone).\n",
+ " -u <user> For user name (repeatable)\n",
+ " -z <tz> Output for time zone <tz> (e.g. -z -0700)\n",
+ NULL};
+
+/* Sort routine for qsort:
+ - If a user is selected at all, sort it first. User-within-file is useless.
+ - If a module was selected explicitly, sort next on module.
+ - Then sort by file. "File" is "repository/file" unless "working" is set,
+ then it is "workdir/file". (Revision order should always track date.)
+ - Always sort timestamp last.
+*/
+static int
+sort_order (l, r)
+ const PTR l;
+ const PTR r;
+{
+ int i;
+ const struct hrec *left = (const struct hrec *) l;
+ const struct hrec *right = (const struct hrec *) r;
+
+ if (user_sort) /* If Sort by username, compare users */
+ {
+ if ((i = strcmp (left->user, right->user)) != 0)
+ return (i);
+ }
+ if (module_sort) /* If sort by modules, compare module names */
+ {
+ if (left->mod && right->mod)
+ if ((i = strcmp (left->mod, right->mod)) != 0)
+ return (i);
+ }
+ if (repos_sort) /* If sort by repository, compare them. */
+ {
+ if ((i = strcmp (left->repos, right->repos)) != 0)
+ return (i);
+ }
+ if (file_sort) /* If sort by filename, compare files, NOT dirs. */
+ {
+ if ((i = strcmp (left->file, right->file)) != 0)
+ return (i);
+
+ if (working)
+ {
+ if ((i = strcmp (left->dir, right->dir)) != 0)
+ return (i);
+
+ if ((i = strcmp (left->end, right->end)) != 0)
+ return (i);
+ }
+ }
+
+ /*
+ * By default, sort by date, time
+ * XXX: This fails after 2030 when date slides into sign bit
+ */
+ if ((i = ((long) (left->date) - (long) (right->date))) != 0)
+ return (i);
+
+ /* For matching dates, keep the sort stable by using record index */
+ return (left->idx - right->idx);
+}
+
+static time_t
+date_and_time (date_str)
+ char *date_str;
+{
+ time_t t;
+
+ t = get_date (date_str, (struct timeb *) NULL);
+ if (t == (time_t) - 1)
+ error (1, 0, "Can't parse date/time: %s", date_str);
+ return (t);
+}
+
+int
+history (argc, argv)
+ int argc;
+ char **argv;
+{
+ int i, c;
+ char fname[PATH_MAX];
+
+ if (argc == -1)
+ usage (history_usg);
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "Tacelow?D:b:f:m:n:p:r:t:u:x:X:z:")) != -1)
+ {
+ switch (c)
+ {
+ case 'T': /* Tag list */
+ report_count++;
+ tag_report++;
+ break;
+ case 'a': /* For all usernames */
+ all_users++;
+ break;
+ case 'c':
+ report_count++;
+ modified = 1;
+ break;
+ case 'e':
+ report_count++;
+ extract++;
+ (void) strcpy (rec_types, ALL_REC_TYPES);
+ break;
+ case 'l': /* Find Last file record */
+ last_entry = 1;
+ break;
+ case 'o':
+ report_count++;
+ v_checkout = 1;
+ break;
+ case 'w': /* Match Working Dir (CurDir) fields */
+ working = 1;
+ break;
+ case 'X': /* Undocumented debugging flag */
+ histfile = optarg;
+ break;
+ case 'D': /* Since specified date */
+ if (*since_rev || *since_tag || *backto)
+ {
+ error (0, 0, "date overriding rev/tag/backto");
+ *since_rev = *since_tag = *backto = '\0';
+ }
+ since_date = date_and_time (optarg);
+ break;
+ case 'b': /* Since specified file/Repos */
+ if (since_date || *since_rev || *since_tag)
+ {
+ error (0, 0, "backto overriding date/rev/tag");
+ *since_rev = *since_tag = '\0';
+ since_date = 0;
+ }
+ if (strlen (optarg) >= sizeof (backto))
+ {
+ error (0, 0, "backto truncated to %d bytes",
+ sizeof (backto) - 1);
+ optarg[sizeof (backto) - 1] = '\0';
+ }
+ (void) strcpy (backto, optarg);
+ break;
+ case 'f': /* For specified file */
+ save_file ("", optarg, (char *) NULL);
+ break;
+ case 'm': /* Full module report */
+ report_count++;
+ module_report++;
+ case 'n': /* Look for specified module */
+ save_module (optarg);
+ break;
+ case 'p': /* For specified directory */
+ save_file (optarg, "", (char *) NULL);
+ break;
+ case 'r': /* Since specified Tag/Rev */
+ if (since_date || *since_tag || *backto)
+ {
+ error (0, 0, "rev overriding date/tag/backto");
+ *since_tag = *backto = '\0';
+ since_date = 0;
+ }
+ (void) strcpy (since_rev, optarg);
+ break;
+ case 't': /* Since specified Tag/Rev */
+ if (since_date || *since_rev || *backto)
+ {
+ error (0, 0, "tag overriding date/marker/file/repos");
+ *since_rev = *backto = '\0';
+ since_date = 0;
+ }
+ (void) strcpy (since_tag, optarg); /* tag */
+ break;
+ case 'u': /* For specified username */
+ save_user (optarg);
+ break;
+ case 'x':
+ report_count++;
+ extract++;
+ {
+ char *cp;
+
+ for (cp = optarg; *cp; cp++)
+ if (!strchr (ALL_REC_TYPES, *cp))
+ error (1, 0, "%c is not a valid report type", *cp);
+ }
+ (void) strcpy (rec_types, optarg);
+ break;
+ case 'z':
+#ifndef HAVE_RCS5
+ error (0, 0, "-z not supported with RCS 4");
+#else
+ tz_local =
+ (optarg[0] == 'l' || optarg[0] == 'L')
+ && (optarg[1] == 't' || optarg[1] == 'T')
+ && !optarg[2];
+ if (tz_local)
+ tz_name = optarg;
+ else
+ {
+ /*
+ * Convert a known time with the given timezone to time_t.
+ * Use the epoch + 23 hours, so timezones east of GMT work.
+ */
+ static char f[] = "1/1/1970 23:00 %s";
+ char *buf = xmalloc (sizeof (f) - 2 + strlen (optarg));
+ time_t t;
+ sprintf (buf, f, optarg);
+ t = get_date (buf, (struct timeb *) NULL);
+ free (buf);
+ if (t == (time_t) -1)
+ error (0, 0, "%s is not a known time zone", optarg);
+ else
+ {
+ /*
+ * Convert to seconds east of GMT, removing the
+ * 23-hour offset mentioned above.
+ */
+ tz_seconds_east_of_GMT = (time_t)23 * 60 * 60 - t;
+ tz_name = optarg;
+ }
+ }
+#endif
+ break;
+ case '?':
+ default:
+ usage (history_usg);
+ break;
+ }
+ }
+ c = optind; /* Save the handled option count */
+
+ /* ================ Now analyze the arguments a bit */
+ if (!report_count)
+ v_checkout++;
+ else if (report_count > 1)
+ error (1, 0, "Only one report type allowed from: \"-Tcomx\".");
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ struct file_list_str *f1;
+ char **mod;
+
+ /* We're the client side. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ if (tag_report)
+ send_arg("-T");
+ if (all_users)
+ send_arg("-a");
+ if (modified)
+ send_arg("-c");
+ if (last_entry)
+ send_arg("-l");
+ if (v_checkout)
+ send_arg("-o");
+ if (working)
+ send_arg("-w");
+ if (histfile)
+ send_arg("-X");
+ if (since_date)
+ option_with_arg ("-D", asctime (gmtime (&since_date)));
+ if (backto[0] != '\0')
+ option_with_arg ("-b", backto);
+ for (f1 = file_list; f1 < &file_list[file_count]; ++f1)
+ {
+ if (f1->l_file[0] == '*')
+ option_with_arg ("-p", f1->l_file + 1);
+ else
+ option_with_arg ("-f", f1->l_file);
+ }
+ if (module_report)
+ send_arg("-m");
+ for (mod = mod_list; mod < &mod_list[mod_count]; ++mod)
+ option_with_arg ("-n", *mod);
+ if (since_rev != NULL)
+ option_with_arg ("-r", since_rev);
+ if (since_tag != NULL)
+ option_with_arg ("-t", since_tag);
+ for (mod = user_list; mod < &user_list[user_count]; ++mod)
+ option_with_arg ("-u", *mod);
+ if (extract)
+ option_with_arg ("-x", rec_types);
+ option_with_arg ("-z", tz_name);
+
+ send_to_server ("history\012", 0);
+ return get_responses_and_close ();
+ }
+#endif
+
+ if (all_users)
+ save_user ("");
+
+ if (mod_list)
+ expand_modules ();
+
+ if (tag_report)
+ {
+ if (!strchr (rec_types, 'T'))
+ (void) strcat (rec_types, "T");
+ }
+ else if (extract)
+ {
+ if (user_list)
+ user_sort++;
+ }
+ else if (modified)
+ {
+ (void) strcpy (rec_types, "MAR");
+ /*
+ * If the user has not specified a date oriented flag ("Since"), sort
+ * by Repository/file before date. Default is "just" date.
+ */
+ if (!since_date && !*since_rev && !*since_tag && !*backto)
+ {
+ repos_sort++;
+ file_sort++;
+ /*
+ * If we are not looking for last_modified and the user specified
+ * one or more users to look at, sort by user before filename.
+ */
+ if (!last_entry && user_list)
+ user_sort++;
+ }
+ }
+ else if (module_report)
+ {
+ (void) strcpy (rec_types, last_entry ? "OMAR" : ALL_REC_TYPES);
+ module_sort++;
+ repos_sort++;
+ file_sort++;
+ working = 0; /* User's workdir doesn't count here */
+ }
+ else
+ /* Must be "checkout" or default */
+ {
+ (void) strcpy (rec_types, "OF");
+ /* See comments in "modified" above */
+ if (!last_entry && user_list)
+ user_sort++;
+ if (!since_date && !*since_rev && !*since_tag && !*backto)
+ file_sort++;
+ }
+
+ /* If no users were specified, use self (-a saves a universal ("") user) */
+ if (!user_list)
+ save_user (getcaller ());
+
+ /* If we're looking back to a Tag value, must consider "Tag" records */
+ if (*since_tag && !strchr (rec_types, 'T'))
+ (void) strcat (rec_types, "T");
+
+ argc -= c;
+ argv += c;
+ for (i = 0; i < argc; i++)
+ save_file ("", argv[i], (char *) NULL);
+
+ if (histfile)
+ (void) strcpy (fname, histfile);
+ else
+ (void) sprintf (fname, "%s/%s/%s", CVSroot,
+ CVSROOTADM, CVSROOTADM_HISTORY);
+
+ read_hrecs (fname);
+ qsort ((PTR) hrec_head, hrec_count, sizeof (struct hrec), sort_order);
+ report_hrecs ();
+
+ return (0);
+}
+
+void
+history_write (type, update_dir, revs, name, repository)
+ int type;
+ char *update_dir;
+ char *revs;
+ char *name;
+ char *repository;
+{
+ char fname[PATH_MAX], workdir[PATH_MAX], homedir[PATH_MAX];
+ char *username = getcaller ();
+ int fd;
+ char *line;
+ char *slash = "", *cp, *cp2, *repos;
+ int i;
+ static char *tilde = "";
+ static char *PrCurDir = NULL;
+
+ if (logoff) /* History is turned off by cmd line switch */
+ return;
+ (void) sprintf (fname, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_HISTORY);
+
+ /* turn off history logging if the history file does not exist */
+ if (!isfile (fname))
+ {
+ logoff = 1;
+ return;
+ }
+
+ if (trace)
+#ifdef SERVER_SUPPORT
+ fprintf (stderr, "%c-> fopen(%s,a)\n",
+ (server_active) ? 'S' : ' ', fname);
+#else
+ fprintf (stderr, "-> fopen(%s,a)\n", fname);
+#endif
+ if (noexec)
+ return;
+ fd = open (fname, O_WRONLY | O_APPEND | O_CREAT | OPEN_BINARY, 0666);
+ if (fd < 0)
+ error (1, errno, "cannot open history file: %s", fname);
+
+ repos = Short_Repository (repository);
+
+ if (!PrCurDir)
+ {
+ char *pwdir;
+
+ pwdir = get_homedir ();
+ PrCurDir = CurDir;
+ if (pwdir != NULL)
+ {
+ /* Assumes neither CurDir nor pwdir ends in '/' */
+ i = strlen (pwdir);
+ if (!strncmp (CurDir, pwdir, i))
+ {
+ PrCurDir += i; /* Point to '/' separator */
+ tilde = "~";
+ }
+ else
+ {
+ /* Try harder to find a "homedir" */
+ if (!getwd (workdir))
+ error (1, errno, "can't getwd in history");
+ if (chdir (pwdir) < 0)
+ error (1, errno, "can't chdir(%s)", pwdir);
+ if (!getwd (homedir))
+ error (1, errno, "can't getwd in %s", pwdir);
+ (void) chdir (workdir);
+
+ i = strlen (homedir);
+ if (!strncmp (CurDir, homedir, i))
+ {
+ PrCurDir += i; /* Point to '/' separator */
+ tilde = "~";
+ }
+ }
+ }
+ }
+
+ if (type == 'T')
+ {
+ repos = update_dir;
+ update_dir = "";
+ }
+ else if (update_dir && *update_dir)
+ slash = "/";
+ else
+ update_dir = "";
+
+ (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir);
+
+ /*
+ * "workdir" is the directory where the file "name" is. ("^~" == $HOME)
+ * "repos" is the Repository, relative to $CVSROOT where the RCS file is.
+ *
+ * "$workdir/$name" is the working file name.
+ * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository.
+ *
+ * First, note that the history format was intended to save space, not
+ * to be human readable.
+ *
+ * The working file directory ("workdir") and the Repository ("repos")
+ * usually end with the same one or more directory elements. To avoid
+ * duplication (and save space), the "workdir" field ends with
+ * an integer offset into the "repos" field. This offset indicates the
+ * beginning of the "tail" of "repos", after which all characters are
+ * duplicates.
+ *
+ * In other words, if the "workdir" field has a '*' (a very stupid thing
+ * to put in a filename) in it, then every thing following the last '*'
+ * is a hex offset into "repos" of the first character from "repos" to
+ * append to "workdir" to finish the pathname.
+ *
+ * It might be easier to look at an example:
+ *
+ * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
+ *
+ * Indicates that the workdir is really "~/work/cvs/examples", saving
+ * 10 characters, where "~/work*d" would save 6 characters and mean that
+ * the workdir is really "~/work/examples". It will mean more on
+ * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term
+ *
+ * "workdir" is always an absolute pathname (~/xxx is an absolute path)
+ * "repos" is always a relative pathname. So we can assume that we will
+ * never run into the top of "workdir" -- there will always be a '/' or
+ * a '~' at the head of "workdir" that is not matched by anything in
+ * "repos". On the other hand, we *can* run off the top of "repos".
+ *
+ * Only "compress" if we save characters.
+ */
+
+ if (!repos)
+ repos = "";
+
+ cp = workdir + strlen (workdir) - 1;
+ cp2 = repos + strlen (repos) - 1;
+ for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--)
+ i++;
+
+ if (i > 2)
+ {
+ i = strlen (repos) - i;
+ (void) sprintf ((cp + 1), "*%x", i);
+ }
+
+ if (!revs)
+ revs = "";
+ line = xmalloc (strlen (username) + strlen (workdir) + strlen (repos)
+ + strlen (revs) + strlen (name) + 100);
+ sprintf (line, "%c%08lx|%s|%s|%s|%s|%s\n",
+ type, (long) time ((time_t *) NULL),
+ username, workdir, repos, revs, name);
+
+ /* Lessen some race conditions on non-Posix-compliant hosts. */
+ if (lseek (fd, (off_t) 0, SEEK_END) == -1)
+ error (1, errno, "cannot seek to end of history file: %s", fname);
+
+ if (write (fd, line, strlen (line)) < 0)
+ error (1, errno, "cannot write to history file: %s", fname);
+ free (line);
+ if (close (fd) != 0)
+ error (1, errno, "cannot close history file: %s", fname);
+}
+
+/*
+ * save_user() adds a user name to the user list to select. Zero-length
+ * username ("") matches any user.
+ */
+static void
+save_user (name)
+ char *name;
+{
+ if (user_count == user_max)
+ {
+ user_max += USER_INCREMENT;
+ user_list = (char **) xrealloc ((char *) user_list,
+ (int) user_max * sizeof (char *));
+ }
+ user_list[user_count++] = xstrdup (name);
+}
+
+/*
+ * save_file() adds file name and associated module to the file list to select.
+ *
+ * If "dir" is null, store a file name as is.
+ * If "name" is null, store a directory name with a '*' on the front.
+ * Else, store concatenated "dir/name".
+ *
+ * Later, in the "select" stage:
+ * - if it starts with '*', it is prefix-matched against the repository.
+ * - if it has a '/' in it, it is matched against the repository/file.
+ * - else it is matched against the file name.
+ */
+static void
+save_file (dir, name, module)
+ char *dir;
+ char *name;
+ char *module;
+{
+ char *cp;
+ struct file_list_str *fl;
+
+ if (file_count == file_max)
+ {
+ file_max += FILE_INCREMENT;
+ file_list = (struct file_list_str *) xrealloc ((char *) file_list,
+ file_max * sizeof (*fl));
+ }
+ fl = &file_list[file_count++];
+ fl->l_file = cp = xmalloc (strlen (dir) + strlen (name) + 2);
+ fl->l_module = module;
+
+ if (dir && *dir)
+ {
+ if (name && *name)
+ {
+ (void) strcpy (cp, dir);
+ (void) strcat (cp, "/");
+ (void) strcat (cp, name);
+ }
+ else
+ {
+ *cp++ = '*';
+ (void) strcpy (cp, dir);
+ }
+ }
+ else
+ {
+ if (name && *name)
+ {
+ (void) strcpy (cp, name);
+ }
+ else
+ {
+ error (0, 0, "save_file: null dir and file name");
+ }
+ }
+}
+
+static void
+save_module (module)
+ char *module;
+{
+ if (mod_count == mod_max)
+ {
+ mod_max += MODULE_INCREMENT;
+ mod_list = (char **) xrealloc ((char *) mod_list,
+ mod_max * sizeof (char *));
+ }
+ mod_list[mod_count++] = xstrdup (module);
+}
+
+static void
+expand_modules ()
+{
+}
+
+/* fill_hrec
+ *
+ * Take a ptr to 7-part history line, ending with a newline, for example:
+ *
+ * M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
+ *
+ * Split it into 7 parts and drop the parts into a "struct hrec".
+ * Return a pointer to the character following the newline.
+ */
+
+#define NEXT_BAR(here) do { while (isspace(*line)) line++; hr->here = line; while ((c = *line++) && c != '|') ; if (!c) return(rtn); *(line - 1) = '\0'; } while (0)
+
+static char *
+fill_hrec (line, hr)
+ char *line;
+ struct hrec *hr;
+{
+ char *cp, *rtn;
+ int c;
+ int off;
+ static int idx = 0;
+ unsigned long date;
+
+ memset ((char *) hr, 0, sizeof (*hr));
+ while (isspace (*line))
+ line++;
+ if (!(rtn = strchr (line, '\n')))
+ return ("");
+ *rtn++ = '\0';
+
+ hr->type = line++;
+ (void) sscanf (line, "%lx", &date);
+ hr->date = date;
+ while (*line && strchr ("0123456789abcdefABCDEF", *line))
+ line++;
+ if (*line == '\0')
+ return (rtn);
+
+ line++;
+ NEXT_BAR (user);
+ NEXT_BAR (dir);
+ if ((cp = strrchr (hr->dir, '*')) != NULL)
+ {
+ *cp++ = '\0';
+ (void) sscanf (cp, "%x", &off);
+ hr->end = line + off;
+ }
+ else
+ hr->end = line - 1; /* A handy pointer to '\0' */
+ NEXT_BAR (repos);
+ NEXT_BAR (rev);
+ hr->idx = idx++;
+ if (strchr ("FOT", *(hr->type)))
+ hr->mod = line;
+
+ NEXT_BAR (file); /* This returns ptr to next line or final '\0' */
+ return (rtn); /* If it falls through, go on to next record */
+}
+
+/* read_hrecs's job is to read the history file and fill in all the "hrec"
+ * (history record) array elements with the ones we need to print.
+ *
+ * Logic:
+ * - Read the whole history file into a single buffer.
+ * - Walk through the buffer, parsing lines out of the buffer.
+ * 1. Split line into pointer and integer fields in the "next" hrec.
+ * 2. Apply tests to the hrec to see if it is wanted.
+ * 3. If it *is* wanted, bump the hrec pointer down by one.
+ */
+static void
+read_hrecs (fname)
+ char *fname;
+{
+ char *cp, *cp2;
+ int i, fd;
+ struct hrec *hr;
+ struct stat st_buf;
+
+ if ((fd = open (fname, O_RDONLY | OPEN_BINARY)) < 0)
+ error (1, errno, "cannot open history file: %s", fname);
+
+ if (fstat (fd, &st_buf) < 0)
+ error (1, errno, "can't stat history file");
+
+ /* Exactly enough space for lines data */
+ if (!(i = st_buf.st_size))
+ error (1, 0, "history file is empty");
+ cp = xmalloc (i + 2);
+
+ if (read (fd, cp, i) != i)
+ error (1, errno, "cannot read log file");
+ (void) close (fd);
+
+ if (*(cp + i - 1) != '\n')
+ {
+ *(cp + i) = '\n'; /* Make sure last line ends in '\n' */
+ i++;
+ }
+ *(cp + i) = '\0';
+ for (cp2 = cp; cp2 - cp < i; cp2++)
+ {
+ if (*cp2 != '\n' && !isprint (*cp2))
+ *cp2 = ' ';
+ }
+
+ hrec_max = HREC_INCREMENT;
+ hrec_head = (struct hrec *) xmalloc (hrec_max * sizeof (struct hrec));
+
+ while (*cp)
+ {
+ if (hrec_count == hrec_max)
+ {
+ struct hrec *old_head = hrec_head;
+
+ hrec_max += HREC_INCREMENT;
+ hrec_head = (struct hrec *) xrealloc ((char *) hrec_head,
+ hrec_max * sizeof (struct hrec));
+ if (hrec_head != old_head)
+ {
+ if (last_since_tag)
+ last_since_tag = hrec_head + (last_since_tag - old_head);
+ if (last_backto)
+ last_backto = hrec_head + (last_backto - old_head);
+ }
+ }
+
+ hr = hrec_head + hrec_count;
+ cp = fill_hrec (cp, hr); /* cp == next line or '\0' at end of buffer */
+
+ if (select_hrec (hr))
+ hrec_count++;
+ }
+
+ /* Special selection problem: If "since_tag" is set, we have saved every
+ * record from the 1st occurrence of "since_tag", when we want to save
+ * records since the *last* occurrence of "since_tag". So what we have
+ * to do is bump hrec_head forward and reduce hrec_count accordingly.
+ */
+ if (last_since_tag)
+ {
+ hrec_count -= (last_since_tag - hrec_head);
+ hrec_head = last_since_tag;
+ }
+
+ /* Much the same thing is necessary for the "backto" option. */
+ if (last_backto)
+ {
+ hrec_count -= (last_backto - hrec_head);
+ hrec_head = last_backto;
+ }
+}
+
+/* Utility program for determining whether "find" is inside "string" */
+static int
+within (find, string)
+ char *find, *string;
+{
+ int c, len;
+
+ if (!find || !string)
+ return (0);
+
+ c = *find++;
+ len = strlen (find);
+
+ while (*string)
+ {
+ if (!(string = strchr (string, c)))
+ return (0);
+ string++;
+ if (!strncmp (find, string, len))
+ return (1);
+ }
+ return (0);
+}
+
+/* The purpose of "select_hrec" is to apply the selection criteria based on
+ * the command arguments and defaults and return a flag indicating whether
+ * this record should be remembered for printing.
+ */
+static int
+select_hrec (hr)
+ struct hrec *hr;
+{
+ char **cpp, *cp, *cp2;
+ struct file_list_str *fl;
+ int count;
+
+ /* "Since" checking: The argument parser guarantees that only one of the
+ * following four choices is set:
+ *
+ * 1. If "since_date" is set, it contains a Unix time_t specified on the
+ * command line. hr->date fields earlier than "since_date" are ignored.
+ * 2. If "since_rev" is set, it contains either an RCS "dotted" revision
+ * number (which is of limited use) or a symbolic TAG. Each RCS file
+ * is examined and the date on the specified revision (or the revision
+ * corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is
+ * compared against hr->date as in 1. above.
+ * 3. If "since_tag" is set, matching tag records are saved. The field
+ * "last_since_tag" is set to the last one of these. Since we don't
+ * know where the last one will be, all records are saved from the
+ * first occurrence of the TAG. Later, at the end of "select_hrec"
+ * records before the last occurrence of "since_tag" are skipped.
+ * 4. If "backto" is set, all records with a module name or file name
+ * matching "backto" are saved. In addition, all records with a
+ * repository field with a *prefix* matching "backto" are saved.
+ * The field "last_backto" is set to the last one of these. As in
+ * 3. above, "select_hrec" adjusts to include the last one later on.
+ */
+ if (since_date)
+ {
+ if (hr->date < since_date)
+ return (0);
+ }
+ else if (*since_rev)
+ {
+ Vers_TS *vers;
+ time_t t;
+
+ vers = Version_TS (hr->repos, (char *) NULL, since_rev, (char *) NULL,
+ hr->file, 1, 0, (List *) NULL, (RCSNode *) NULL);
+ if (vers->vn_rcs)
+ {
+ if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, (char *) 0, 0))
+ != (time_t) 0)
+ {
+ if (hr->date < t)
+ {
+ freevers_ts (&vers);
+ return (0);
+ }
+ }
+ }
+ freevers_ts (&vers);
+ }
+ else if (*since_tag)
+ {
+ if (*(hr->type) == 'T')
+ {
+ /*
+ * A 'T'ag record, the "rev" field holds the tag to be set,
+ * while the "repos" field holds "D"elete, "A"dd or a rev.
+ */
+ if (within (since_tag, hr->rev))
+ {
+ last_since_tag = hr;
+ return (1);
+ }
+ else
+ return (0);
+ }
+ if (!last_since_tag)
+ return (0);
+ }
+ else if (*backto)
+ {
+ if (within (backto, hr->file) || within (backto, hr->mod) ||
+ within (backto, hr->repos))
+ last_backto = hr;
+ else
+ return (0);
+ }
+
+ /* User checking:
+ *
+ * Run down "user_list", match username ("" matches anything)
+ * If "" is not there and actual username is not there, return failure.
+ */
+ if (user_list && hr->user)
+ {
+ for (cpp = user_list, count = user_count; count; cpp++, count--)
+ {
+ if (!**cpp)
+ break; /* null user == accept */
+ if (!strcmp (hr->user, *cpp)) /* found listed user */
+ break;
+ }
+ if (!count)
+ return (0); /* Not this user */
+ }
+
+ /* Record type checking:
+ *
+ * 1. If Record type is not in rec_types field, skip it.
+ * 2. If mod_list is null, keep everything. Otherwise keep only modules
+ * on mod_list.
+ * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list". If
+ * file_list is null, keep everything. Otherwise, keep only files on
+ * file_list, matched appropriately.
+ */
+ if (!strchr (rec_types, *(hr->type)))
+ return (0);
+ if (!strchr ("TFO", *(hr->type))) /* Don't bother with "file" if "TFO" */
+ {
+ if (file_list) /* If file_list is null, accept all */
+ {
+ for (fl = file_list, count = file_count; count; fl++, count--)
+ {
+ /* 1. If file_list entry starts with '*', skip the '*' and
+ * compare it against the repository in the hrec.
+ * 2. If file_list entry has a '/' in it, compare it against
+ * the concatenation of the repository and file from hrec.
+ * 3. Else compare the file_list entry against the hrec file.
+ */
+ char cmpfile[PATH_MAX];
+
+ if (*(cp = fl->l_file) == '*')
+ {
+ cp++;
+ /* if argument to -p is a prefix of repository */
+ if (!strncmp (cp, hr->repos, strlen (cp)))
+ {
+ hr->mod = fl->l_module;
+ break;
+ }
+ }
+ else
+ {
+ if (strchr (cp, '/'))
+ {
+ (void) sprintf (cp2 = cmpfile, "%s/%s",
+ hr->repos, hr->file);
+ }
+ else
+ {
+ cp2 = hr->file;
+ }
+
+ /* if requested file is found within {repos}/file fields */
+ if (within (cp, cp2))
+ {
+ hr->mod = fl->l_module;
+ break;
+ }
+ }
+ }
+ if (!count)
+ return (0); /* String specified and no match */
+ }
+ }
+ if (mod_list)
+ {
+ for (cpp = mod_list, count = mod_count; count; cpp++, count--)
+ {
+ if (hr->mod && !strcmp (hr->mod, *cpp)) /* found module */
+ break;
+ }
+ if (!count)
+ return (0); /* Module specified & this record is not one of them. */
+ }
+
+ return (1); /* Select this record unless rejected above. */
+}
+
+/* The "sort_order" routine (when handed to qsort) has arranged for the
+ * hrecs files to be in the right order for the report.
+ *
+ * Most of the "selections" are done in the select_hrec routine, but some
+ * selections are more easily done after the qsort by "accept_hrec".
+ */
+static void
+report_hrecs ()
+{
+ struct hrec *hr, *lr;
+ struct tm *tm;
+ int i, count, ty;
+ char *cp;
+ int user_len, file_len, rev_len, mod_len, repos_len;
+
+ if (*since_tag && !last_since_tag)
+ {
+ (void) printf ("No tag found: %s\n", since_tag);
+ return;
+ }
+ else if (*backto && !last_backto)
+ {
+ (void) printf ("No module, file or repository with: %s\n", backto);
+ return;
+ }
+ else if (hrec_count < 1)
+ {
+ (void) printf ("No records selected.\n");
+ return;
+ }
+
+ user_len = file_len = rev_len = mod_len = repos_len = 0;
+
+ /* Run through lists and find maximum field widths */
+ hr = lr = hrec_head;
+ hr++;
+ for (count = hrec_count; count--; lr = hr, hr++)
+ {
+ char repos[PATH_MAX];
+
+ if (!count)
+ hr = NULL;
+ if (!accept_hrec (lr, hr))
+ continue;
+
+ ty = *(lr->type);
+ (void) strcpy (repos, lr->repos);
+ if ((cp = strrchr (repos, '/')) != NULL)
+ {
+ if (lr->mod && !strcmp (++cp, lr->mod))
+ {
+ (void) strcpy (cp, "*");
+ }
+ }
+ if ((i = strlen (lr->user)) > user_len)
+ user_len = i;
+ if ((i = strlen (lr->file)) > file_len)
+ file_len = i;
+ if (ty != 'T' && (i = strlen (repos)) > repos_len)
+ repos_len = i;
+ if (ty != 'T' && (i = strlen (lr->rev)) > rev_len)
+ rev_len = i;
+ if (lr->mod && (i = strlen (lr->mod)) > mod_len)
+ mod_len = i;
+ }
+
+ /* Walk through hrec array setting "lr" (Last Record) to each element.
+ * "hr" points to the record following "lr" -- It is NULL in the last
+ * pass.
+ *
+ * There are two sections in the loop below:
+ * 1. Based on the report type (e.g. extract, checkout, tag, etc.),
+ * decide whether the record should be printed.
+ * 2. Based on the record type, format and print the data.
+ */
+ for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++)
+ {
+ char workdir[PATH_MAX], repos[PATH_MAX];
+
+ if (!hrec_count)
+ hr = NULL;
+ if (!accept_hrec (lr, hr))
+ continue;
+
+ ty = *(lr->type);
+#ifdef HAVE_RCS5
+ if (!tz_local)
+ {
+ time_t t = lr->date + tz_seconds_east_of_GMT;
+ tm = gmtime (&t);
+ }
+ else
+#endif
+ tm = localtime (&(lr->date));
+ (void) printf ("%c %02d/%02d %02d:%02d %s %-*s", ty, tm->tm_mon + 1,
+ tm->tm_mday, tm->tm_hour, tm->tm_min, tz_name,
+ user_len, lr->user);
+
+ (void) sprintf (workdir, "%s%s", lr->dir, lr->end);
+ if ((cp = strrchr (workdir, '/')) != NULL)
+ {
+ if (lr->mod && !strcmp (++cp, lr->mod))
+ {
+ (void) strcpy (cp, "*");
+ }
+ }
+ (void) strcpy (repos, lr->repos);
+ if ((cp = strrchr (repos, '/')) != NULL)
+ {
+ if (lr->mod && !strcmp (++cp, lr->mod))
+ {
+ (void) strcpy (cp, "*");
+ }
+ }
+
+ switch (ty)
+ {
+ case 'T':
+ /* 'T'ag records: repository is a "tag type", rev is the tag */
+ (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev,
+ repos);
+ if (working)
+ (void) printf (" {%s}", workdir);
+ break;
+ case 'F':
+ case 'O':
+ if (lr->rev && *(lr->rev))
+ (void) printf (" [%s]", lr->rev);
+ (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod,
+ mod_len + 1 - strlen (lr->mod), "=", workdir);
+ break;
+ case 'W':
+ case 'U':
+ case 'C':
+ case 'G':
+ case 'M':
+ case 'A':
+ case 'R':
+ (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev,
+ file_len, lr->file, repos_len, repos,
+ lr->mod ? lr->mod : "", workdir);
+ break;
+ default:
+ (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty);
+ break;
+ }
+ (void) putchar ('\n');
+ }
+}
+
+static int
+accept_hrec (lr, hr)
+ struct hrec *hr, *lr;
+{
+ int ty;
+
+ ty = *(lr->type);
+
+ if (last_since_tag && ty == 'T')
+ return (1);
+
+ if (v_checkout)
+ {
+ if (ty != 'O')
+ return (0); /* Only interested in 'O' records */
+
+ /* We want to identify all the states that cause the next record
+ * ("hr") to be different from the current one ("lr") and only
+ * print a line at the allowed boundaries.
+ */
+
+ if (!hr || /* The last record */
+ strcmp (hr->user, lr->user) || /* User has changed */
+ strcmp (hr->mod, lr->mod) ||/* Module has changed */
+ (working && /* If must match "workdir" */
+ (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
+ strcmp (hr->end, lr->end)))) /* the 2nd parts differ */
+
+ return (1);
+ }
+ else if (modified)
+ {
+ if (!last_entry || /* Don't want only last rec */
+ !hr || /* Last entry is a "last entry" */
+ strcmp (hr->repos, lr->repos) || /* Repository has changed */
+ strcmp (hr->file, lr->file))/* File has changed */
+ return (1);
+
+ if (working)
+ { /* If must match "workdir" */
+ if (strcmp (hr->dir, lr->dir) || /* and the 1st parts or */
+ strcmp (hr->end, lr->end)) /* the 2nd parts differ */
+ return (1);
+ }
+ }
+ else if (module_report)
+ {
+ if (!last_entry || /* Don't want only last rec */
+ !hr || /* Last entry is a "last entry" */
+ strcmp (hr->mod, lr->mod) ||/* Module has changed */
+ strcmp (hr->repos, lr->repos) || /* Repository has changed */
+ strcmp (hr->file, lr->file))/* File has changed */
+ return (1);
+ }
+ else
+ {
+ /* "extract" and "tag_report" always print selected records. */
+ return (1);
+ }
+
+ return (0);
+}
diff --git a/contrib/cvs/src/ignore.c b/contrib/cvs/src/ignore.c
new file mode 100644
index 0000000..25c2269
--- /dev/null
+++ b/contrib/cvs/src/ignore.c
@@ -0,0 +1,405 @@
+/*
+ * .cvsignore file support contributed by David G. Grubbs <dgg@odi.com>
+ */
+
+#include "cvs.h"
+
+/*
+ * Ignore file section.
+ *
+ * "!" may be included any time to reset the list (i.e. ignore nothing);
+ * "*" may be specified to ignore everything. It stays as the first
+ * element forever, unless a "!" clears it out.
+ */
+
+static char **ign_list; /* List of files to ignore in update
+ * and import */
+static char **s_ign_list = NULL;
+static int ign_count; /* Number of active entries */
+static int s_ign_count = 0;
+static int ign_size; /* This many slots available (plus
+ * one for a NULL) */
+static int ign_hold; /* Index where first "temporary" item
+ * is held */
+
+const char *ign_default = ". .. core RCSLOG tags TAGS RCS SCCS .make.state\
+ .nse_depinfo #* .#* cvslog.* ,* CVS CVS.adm .del-* *.a *.olb *.o *.obj\
+ *.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$";
+
+#define IGN_GROW 16 /* grow the list by 16 elements at a
+ * time */
+
+/* Nonzero if we have encountered an -I ! directive, which means one should
+ no longer ask the server about what is in CVSROOTADM_IGNORE. */
+int ign_inhibit_server;
+
+/*
+ * To the "ignore list", add the hard-coded default ignored wildcards above,
+ * the wildcards found in $CVSROOT/CVSROOT/cvsignore, the wildcards found in
+ * ~/.cvsignore and the wildcards found in the CVSIGNORE environment
+ * variable.
+ */
+void
+ign_setup ()
+{
+ struct passwd *pw;
+ char file[PATH_MAX];
+ char *tmp;
+
+ ign_inhibit_server = 0;
+
+ /* Start with default list and special case */
+ tmp = xstrdup (ign_default);
+ ign_add (tmp, 0);
+ free (tmp);
+
+#ifdef CLIENT_SUPPORT
+ /* The client handles another way, by (after it does its own ignore file
+ processing, and only if !ign_inhibit_server), letting the server
+ know about the files and letting it decide whether to ignore
+ them based on CVSROOOTADM_IGNORE. */
+ if (!client_active)
+#endif
+ {
+ /* Then add entries found in repository, if it exists */
+ (void) sprintf (file, "%s/%s/%s", CVSroot, CVSROOTADM,
+ CVSROOTADM_IGNORE);
+ ign_add_file (file, 0);
+ }
+
+ /* Then add entries found in home dir, (if user has one) and file exists */
+ if ((pw = (struct passwd *) getpwuid (getuid ())) && pw->pw_dir)
+ {
+ (void) sprintf (file, "%s/%s", pw->pw_dir, CVSDOTIGNORE);
+ ign_add_file (file, 0);
+ }
+
+ /* Then add entries found in CVSIGNORE environment variable. */
+ ign_add (getenv (IGNORE_ENV), 0);
+
+ /* Later, add ignore entries found in -I arguments */
+}
+
+/*
+ * Open a file and read lines, feeding each line to a line parser. Arrange
+ * for keeping a temporary list of wildcards at the end, if the "hold"
+ * argument is set.
+ */
+void
+ign_add_file (file, hold)
+ char *file;
+ int hold;
+{
+ FILE *fp;
+ char line[1024];
+
+ /* restore the saved list (if any) */
+ if (s_ign_list != NULL)
+ {
+ int i;
+
+ for (i = 0; i < s_ign_count; i++)
+ ign_list[i] = s_ign_list[i];
+ ign_count = s_ign_count;
+ ign_list[ign_count] = NULL;
+
+ s_ign_count = 0;
+ free (s_ign_list);
+ s_ign_list = NULL;
+ }
+
+ /* is this a temporary ignore file? */
+ if (hold)
+ {
+ /* re-set if we had already done a temporary file */
+ if (ign_hold)
+ {
+ int i;
+
+ for (i = ign_hold; i < ign_count; i++)
+ free (ign_list[i]);
+ ign_count = ign_hold;
+ ign_list[ign_count] = NULL;
+ }
+ else
+ {
+ ign_hold = ign_count;
+ }
+ }
+
+ /* load the file */
+ fp = fopen (file, "r");
+ if (fp == NULL)
+ {
+ if (! existence_error (errno))
+ error (0, errno, "cannot open %s", file);
+ return;
+ }
+ while (fgets (line, sizeof (line), fp))
+ ign_add (line, hold);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", file);
+}
+
+/* Parse a line of space-separated wildcards and add them to the list. */
+void
+ign_add (ign, hold)
+ char *ign;
+ int hold;
+{
+ if (!ign || !*ign)
+ return;
+
+ for (; *ign; ign++)
+ {
+ char *mark;
+ char save;
+
+ /* ignore whitespace before the token */
+ if (isspace (*ign))
+ continue;
+
+ /*
+ * if we find a single character !, we must re-set the ignore list
+ * (saving it if necessary). We also catch * as a special case in a
+ * global ignore file as an optimization
+ */
+ if ((!*(ign+1) || isspace (*(ign+1))) && (*ign == '!' || *ign == '*'))
+ {
+ if (!hold)
+ {
+ /* permanently reset the ignore list */
+ int i;
+
+ for (i = 0; i < ign_count; i++)
+ free (ign_list[i]);
+ ign_count = 0;
+ ign_list[0] = NULL;
+
+ /* if we are doing a '!', continue; otherwise add the '*' */
+ if (*ign == '!')
+ {
+ ign_inhibit_server = 1;
+ continue;
+ }
+ }
+ else if (*ign == '!')
+ {
+ /* temporarily reset the ignore list */
+ int i;
+
+ if (ign_hold)
+ {
+ for (i = ign_hold; i < ign_count; i++)
+ free (ign_list[i]);
+ ign_hold = 0;
+ }
+ s_ign_list = (char **) xmalloc (ign_count * sizeof (char *));
+ for (i = 0; i < ign_count; i++)
+ s_ign_list[i] = ign_list[i];
+ s_ign_count = ign_count;
+ ign_count = 0;
+ ign_list[0] = NULL;
+ continue;
+ }
+ }
+
+ /* If we have used up all the space, add some more */
+ if (ign_count >= ign_size)
+ {
+ ign_size += IGN_GROW;
+ ign_list = (char **) xrealloc ((char *) ign_list,
+ (ign_size + 1) * sizeof (char *));
+ }
+
+ /* find the end of this token */
+ for (mark = ign; *mark && !isspace (*mark); mark++)
+ /* do nothing */ ;
+
+ save = *mark;
+ *mark = '\0';
+
+ ign_list[ign_count++] = xstrdup (ign);
+ ign_list[ign_count] = NULL;
+
+ *mark = save;
+ if (save)
+ ign = mark;
+ else
+ ign = mark - 1;
+ }
+}
+
+/* Set to 1 if ignore file patterns should be matched in a case-insensitive
+ fashion. */
+int ign_case;
+
+/* Return 1 if the given filename should be ignored by update or import. */
+int
+ign_name (name)
+ char *name;
+{
+ char **cpp = ign_list;
+
+ if (cpp == NULL)
+ return (0);
+
+ if (ign_case)
+ {
+ /* We do a case-insensitive match by calling fnmatch on copies of
+ the pattern and the name which have been converted to
+ lowercase. */
+ char *name_lower;
+ char *pat_lower;
+ char *p;
+
+ name_lower = xstrdup (name);
+ for (p = name_lower; *p != '\0'; ++p)
+ *p = tolower (*p);
+ while (*cpp)
+ {
+ pat_lower = xstrdup (*cpp++);
+ for (p = pat_lower; *p != '\0'; ++p)
+ *p = tolower (*p);
+ if (fnmatch (pat_lower, name_lower, 0) == 0)
+ goto matched;
+ free (pat_lower);
+ }
+ free (name_lower);
+ return 0;
+ matched:
+ free (name_lower);
+ free (pat_lower);
+ return 1;
+ }
+ else
+ {
+ while (*cpp)
+ if (fnmatch (*cpp++, name, 0) == 0)
+ return 1;
+ return 0;
+ }
+}
+
+/* FIXME: This list of dirs to ignore stuff seems not to be used. */
+
+static char **dir_ign_list = NULL;
+static int dir_ign_max = 0;
+static int dir_ign_current = 0;
+
+/* add a directory to list of dirs to ignore */
+void ign_dir_add (name)
+ char *name;
+{
+ /* make sure we've got the space for the entry */
+ if (dir_ign_current <= dir_ign_max)
+ {
+ dir_ign_max += IGN_GROW;
+ dir_ign_list = (char **) xrealloc ((char *) dir_ign_list, (dir_ign_max+1) * sizeof(char*));
+ }
+
+ dir_ign_list[dir_ign_current] = name;
+
+ dir_ign_current += 1 ;
+}
+
+
+/* this function returns 1 (true) if the given directory name is part of
+ * the list of directories to ignore
+ */
+
+int ignore_directory (name)
+ char *name;
+{
+ int i;
+
+ if (!dir_ign_list)
+ return 0;
+
+ i = dir_ign_current;
+ while (i--)
+ {
+ if (strncmp(name, dir_ign_list[i], strlen(dir_ign_list[i])) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Process the current directory, looking for files not in ILIST and not on
+ * the global ignore list for this directory. If we find one, call PROC
+ * passing it the name of the file and the update dir.
+ */
+void
+ignore_files (ilist, update_dir, proc)
+ List *ilist;
+ char *update_dir;
+ Ignore_proc proc;
+{
+ DIR *dirp;
+ struct dirent *dp;
+ struct stat sb;
+ char *file;
+ char *xdir;
+
+ /* we get called with update_dir set to "." sometimes... strip it */
+ if (strcmp (update_dir, ".") == 0)
+ xdir = "";
+ else
+ xdir = update_dir;
+
+ dirp = opendir (".");
+ if (dirp == NULL)
+ return;
+
+ ign_add_file (CVSDOTIGNORE, 1);
+ wrap_add_file (CVSDOTWRAPPER, 1);
+
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ file = dp->d_name;
+ if (strcmp (file, ".") == 0 || strcmp (file, "..") == 0)
+ continue;
+ if (findnode_fn (ilist, file) != NULL)
+ continue;
+
+ if (
+#ifdef DT_DIR
+ dp->d_type != DT_UNKNOWN ||
+#endif
+ lstat(file, &sb) != -1)
+ {
+
+ if (
+#ifdef DT_DIR
+ dp->d_type == DT_DIR || dp->d_type == DT_UNKNOWN &&
+#endif
+ S_ISDIR(sb.st_mode))
+ {
+ char temp[PATH_MAX];
+
+ (void) sprintf (temp, "%s/%s", file, CVSADM);
+ if (isdir (temp))
+ continue;
+ }
+#ifdef S_ISLNK
+ else if (
+#ifdef DT_DIR
+ dp->d_type == DT_LNK || dp->d_type == DT_UNKNOWN &&
+#endif
+ S_ISLNK(sb.st_mode))
+ {
+ continue;
+ }
+#endif
+ }
+
+ /* We could be ignoring FIFOs and other files which are neither
+ regular files nor directories here. */
+ if (ign_name (file))
+ continue;
+ (*proc) (file, xdir);
+ }
+ (void) closedir (dirp);
+}
diff --git a/contrib/cvs/src/import.c b/contrib/cvs/src/import.c
new file mode 100644
index 0000000..98c1635
--- /dev/null
+++ b/contrib/cvs/src/import.c
@@ -0,0 +1,1207 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * "import" checks in the vendor release located in the current directory into
+ * the CVS source repository. The CVS vendor branch support is utilized.
+ *
+ * At least three arguments are expected to follow the options:
+ * repository Where the source belongs relative to the CVSROOT
+ * VendorTag Vendor's major tag
+ * VendorReleTag Tag for this particular release
+ *
+ * Additional arguments specify more Vendor Release Tags.
+ */
+
+#include "cvs.h"
+#include "savecwd.h"
+
+#define FILE_HOLDER ".#cvsxxx"
+
+static char *get_comment PROTO((char *user));
+static int add_rcs_file PROTO((char *message, char *rcs, char *user, char *vtag,
+ int targc, char *targv[]));
+static int expand_at_signs PROTO((char *buf, off_t size, FILE *fp));
+static int add_rev PROTO((char *message, char *rcs, char *vfile, char *vers));
+static int add_tags PROTO((char *rcs, char *vfile, char *vtag, int targc,
+ char *targv[]));
+static int import_descend PROTO((char *message, char *vtag, int targc, char *targv[]));
+static int import_descend_dir PROTO((char *message, char *dir, char *vtag,
+ int targc, char *targv[]));
+static int process_import_file PROTO((char *message, char *vfile, char *vtag,
+ int targc, char *targv[]));
+static int update_rcs_file PROTO((char *message, char *vfile, char *vtag, int targc,
+ char *targv[], int inattic));
+static void add_log PROTO((int ch, char *fname));
+
+static int repos_len;
+static char vhead[50];
+static char vbranch[50];
+static FILE *logfp;
+static char repository[PATH_MAX];
+static int conflicts;
+static int use_file_modtime;
+static char *keyword_opt = NULL;
+
+static const char *const import_usage[] =
+{
+ "Usage: %s %s [-d] [-k subst] [-I ign] [-m msg] [-b branch]\n",
+ " [-W spec] repository vendor-tag release-tags...\n",
+ "\t-d\tUse the file's modification time as the time of import.\n",
+ "\t-k sub\tSet default RCS keyword substitution mode.\n",
+ "\t-I ign\tMore files to ignore (! to reset).\n",
+ "\t-b bra\tVendor branch id.\n",
+ "\t-m msg\tLog message.\n",
+ "\t-W spec\tWrappers specification line.\n",
+ NULL
+};
+
+int
+import (argc, argv)
+ int argc;
+ char **argv;
+{
+ char *message = NULL;
+ char tmpfile[L_tmpnam+1];
+ char *cp;
+ int i, c, msglen, err;
+ List *ulist;
+ Node *p;
+
+ if (argc == -1)
+ usage (import_usage);
+
+ ign_setup ();
+ wrap_setup ();
+
+ (void) strcpy (vbranch, CVSBRANCH);
+ optind = 1;
+ while ((c = getopt (argc, argv, "Qqdb:m:I:k:W:")) != -1)
+ {
+ switch (c)
+ {
+ case 'Q':
+ case 'q':
+#ifdef SERVER_SUPPORT
+ /* The CVS 1.5 client sends these options (in addition to
+ Global_option requests), so we must ignore them. */
+ if (!server_active)
+#endif
+ error (1, 0,
+ "-q or -Q must be specified before \"%s\"",
+ command_name);
+ break;
+ case 'd':
+ use_file_modtime = 1;
+ break;
+ case 'b':
+ (void) strcpy (vbranch, optarg);
+ break;
+ case 'm':
+#ifdef FORCE_USE_EDITOR
+ use_editor = TRUE;
+#else
+ use_editor = FALSE;
+#endif
+ message = xstrdup(optarg);
+ break;
+ case 'I':
+ ign_add (optarg, 0);
+ break;
+ case 'k':
+ /* RCS_check_kflag returns strings of the form -kxx. We
+ only use it for validation, so we can free the value
+ as soon as it is returned. */
+ free (RCS_check_kflag(optarg));
+ keyword_opt = optarg;
+ break;
+ case 'W':
+ wrap_add (optarg, 0);
+ break;
+ case '?':
+ default:
+ usage (import_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc < 3)
+ usage (import_usage);
+
+ for (i = 1; i < argc; i++) /* check the tags for validity */
+ RCS_check_tag (argv[i]);
+
+ /* XXX - this should be a module, not just a pathname */
+ if (! isabsolute (argv[0]))
+ {
+ if (CVSroot == NULL)
+ {
+ error (0, 0, "missing CVSROOT environment variable\n");
+ error (1, 0, "Set it or specify the '-d' option to %s.",
+ program_name);
+ }
+ (void) sprintf (repository, "%s/%s", CVSroot, argv[0]);
+ repos_len = strlen (CVSroot);
+ }
+ else
+ {
+ (void) strcpy (repository, argv[0]);
+ repos_len = 0;
+ }
+
+ /*
+ * Consistency checks on the specified vendor branch. It must be
+ * composed of only numbers and dots ('.'). Also, for now we only
+ * support branching to a single level, so the specified vendor branch
+ * must only have two dots in it (like "1.1.1").
+ */
+ for (cp = vbranch; *cp != '\0'; cp++)
+ if (!isdigit (*cp) && *cp != '.')
+ error (1, 0, "%s is not a numeric branch", vbranch);
+ if (numdots (vbranch) != 2)
+ error (1, 0, "Only branches with two dots are supported: %s", vbranch);
+ (void) strcpy (vhead, vbranch);
+ cp = strrchr (vhead, '.');
+ *cp = '\0';
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ /* Do this now; don't ask for a log message if we can't talk to the
+ server. But if there is a syntax error in the options, give
+ an error message without connecting. */
+ start_server ();
+ }
+#endif
+
+ if (use_editor)
+ {
+ do_editor ((char *) NULL, &message, repository,
+ (List *) NULL);
+ }
+
+ msglen = message == NULL ? 0 : strlen (message);
+ if (msglen == 0 || message[msglen - 1] != '\n')
+ {
+ char *nm = xmalloc (msglen + 2);
+ if (message != NULL)
+ {
+ (void) strcpy (nm, message);
+ free (message);
+ }
+ (void) strcat (nm + msglen, "\n");
+ message = nm;
+ }
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ int err;
+
+ if (use_file_modtime)
+ send_arg("-d");
+
+ if (vbranch[0] != '\0')
+ option_with_arg ("-b", vbranch);
+ if (message)
+ option_with_arg ("-m", message);
+ if (keyword_opt != NULL)
+ option_with_arg ("-k", keyword_opt);
+ /* The only ignore processing which takes place on the server side
+ is the CVSROOT/cvsignore file. But if the user specified -I !,
+ the documented behavior is to not process said file. */
+ if (ign_inhibit_server)
+ {
+ send_arg ("-I");
+ send_arg ("!");
+ }
+
+ {
+ int i;
+ for (i = 0; i < argc; ++i)
+ send_arg (argv[i]);
+ }
+
+ logfp = stdin;
+ client_import_setup (repository);
+ err = import_descend (message, argv[1], argc - 2, argv + 2);
+ client_import_done ();
+ send_to_server ("import\012", 0);
+ err += get_responses_and_close ();
+ return err;
+ }
+#endif
+
+ /*
+ * Make all newly created directories writable. Should really use a more
+ * sophisticated security mechanism here.
+ */
+ (void) umask (cvsumask);
+ make_directories (repository);
+
+ /* Create the logfile that will be logged upon completion */
+ if ((logfp = fopen (tmpnam (tmpfile), "w+")) == NULL)
+ error (1, errno, "cannot create temporary file `%s'", tmpfile);
+ (void) unlink (tmpfile); /* to be sure it goes away */
+ (void) fprintf (logfp, "\nVendor Tag:\t%s\n", argv[1]);
+ (void) fprintf (logfp, "Release Tags:\t");
+ for (i = 2; i < argc; i++)
+ (void) fprintf (logfp, "%s\n\t\t", argv[i]);
+ (void) fprintf (logfp, "\n");
+
+ /* Just Do It. */
+ err = import_descend (message, argv[1], argc - 2, argv + 2);
+ if (conflicts)
+ {
+ if (!really_quiet)
+ {
+ char buf[80];
+ sprintf (buf, "\n%d conflicts created by this import.\n",
+ conflicts);
+ cvs_output (buf, 0);
+ cvs_output ("Use the following command to help the merge:\n\n",
+ 0);
+ cvs_output ("\t", 1);
+ cvs_output (program_name, 0);
+ cvs_output (" checkout -j", 0);
+ cvs_output (argv[1], 0);
+ cvs_output (":yesterday -j", 0);
+ cvs_output (argv[1], 0);
+ cvs_output (" ", 1);
+ cvs_output (argv[0], 0);
+ cvs_output ("\n\n", 0);
+ }
+
+ (void) fprintf (logfp, "\n%d conflicts created by this import.\n",
+ conflicts);
+ (void) fprintf (logfp,
+ "Use the following command to help the merge:\n\n");
+ (void) fprintf (logfp, "\t%s checkout -j%s:yesterday -j%s %s\n\n",
+ program_name, argv[1], argv[1], argv[0]);
+ }
+ else
+ {
+ if (!really_quiet)
+ cvs_output ("\nNo conflicts created by this import\n\n", 0);
+ (void) fprintf (logfp, "\nNo conflicts created by this import\n\n");
+ }
+
+ /*
+ * Write out the logfile and clean up.
+ */
+ ulist = getlist ();
+ p = getnode ();
+ p->type = UPDATE;
+ p->delproc = update_delproc;
+ p->key = xstrdup ("- Imported sources");
+ p->data = (char *) T_TITLE;
+ (void) addnode (ulist, p);
+ Update_Logfile (repository, message, vbranch, logfp, ulist);
+ dellist (&ulist);
+ (void) fclose (logfp);
+
+ /* Make sure the temporary file goes away, even on systems that don't let
+ you delete a file that's in use. */
+ unlink (tmpfile);
+
+ if (message)
+ free (message);
+
+ return (err);
+}
+
+/*
+ * process all the files in ".", then descend into other directories.
+ */
+static int
+import_descend (message, vtag, targc, targv)
+ char *message;
+ char *vtag;
+ int targc;
+ char *targv[];
+{
+ DIR *dirp;
+ struct dirent *dp;
+ int err = 0;
+ List *dirlist = NULL;
+
+ /* first, load up any per-directory ignore lists */
+ ign_add_file (CVSDOTIGNORE, 1);
+ wrap_add_file (CVSDOTWRAPPER, 1);
+
+ if ((dirp = opendir (".")) == NULL)
+ {
+ err++;
+ }
+ else
+ {
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (strcmp (dp->d_name, ".") == 0 || strcmp (dp->d_name, "..") == 0)
+ continue;
+#ifdef SERVER_SUPPORT
+ /* CVS directories are created in the temp directory by
+ server.c because it doesn't special-case import. So
+ don't print a message about them, regardless of -I!. */
+ if (server_active && strcmp (dp->d_name, CVSADM) == 0)
+ continue;
+#endif
+ if (ign_name (dp->d_name))
+ {
+ add_log ('I', dp->d_name);
+ continue;
+ }
+
+ if (
+#ifdef DT_DIR
+ (dp->d_type == DT_DIR
+ || (dp->d_type == DT_UNKNOWN && isdir (dp->d_name)))
+#else
+ isdir (dp->d_name)
+#endif
+ && !wrap_name_has (dp->d_name, WRAP_TOCVS)
+ )
+ {
+ Node *n;
+
+ if (dirlist == NULL)
+ dirlist = getlist();
+
+ n = getnode();
+ n->key = xstrdup (dp->d_name);
+ addnode(dirlist, n);
+ }
+ else if (
+#ifdef DT_DIR
+ dp->d_type == DT_LNK || dp->d_type == DT_UNKNOWN &&
+#endif
+ islink (dp->d_name))
+ {
+ add_log ('L', dp->d_name);
+ err++;
+ }
+ else
+ {
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ err += client_process_import_file (message, dp->d_name,
+ vtag, targc, targv,
+ repository);
+ else
+#endif
+ err += process_import_file (message, dp->d_name,
+ vtag, targc, targv);
+ }
+ }
+ (void) closedir (dirp);
+ }
+
+ if (dirlist != NULL)
+ {
+ Node *head, *p;
+
+ head = dirlist->list;
+ for (p = head->next; p != head; p = p->next)
+ {
+ err += import_descend_dir (message, p->key, vtag, targc, targv);
+ }
+
+ dellist(&dirlist);
+ }
+
+ return (err);
+}
+
+/*
+ * Process the argument import file.
+ */
+static int
+process_import_file (message, vfile, vtag, targc, targv)
+ char *message;
+ char *vfile;
+ char *vtag;
+ int targc;
+ char *targv[];
+{
+ char attic_name[PATH_MAX];
+ char rcs[PATH_MAX];
+ int inattic = 0;
+
+ (void) sprintf (rcs, "%s/%s%s", repository, vfile, RCSEXT);
+ if (!isfile (rcs))
+ {
+ (void) sprintf (attic_name, "%s/%s/%s%s", repository, CVSATTIC,
+ vfile, RCSEXT);
+ if (!isfile (attic_name))
+ {
+
+ /*
+ * A new import source file; it doesn't exist as a ,v within the
+ * repository nor in the Attic -- create it anew.
+ */
+ add_log ('N', vfile);
+ return (add_rcs_file (message, rcs, vfile, vtag, targc, targv));
+ }
+ inattic = 1;
+ }
+
+ /*
+ * an rcs file exists. have to do things the official, slow, way.
+ */
+ return (update_rcs_file (message, vfile, vtag, targc, targv, inattic));
+}
+
+/*
+ * The RCS file exists; update it by adding the new import file to the
+ * (possibly already existing) vendor branch.
+ */
+static int
+update_rcs_file (message, vfile, vtag, targc, targv, inattic)
+ char *message;
+ char *vfile;
+ char *vtag;
+ int targc;
+ char *targv[];
+ int inattic;
+{
+ Vers_TS *vers;
+ int letter;
+ int ierrno;
+ char *tmpdir;
+ char *tocvsPath;
+
+ vers = Version_TS (repository, (char *) NULL, vbranch, (char *) NULL, vfile,
+ 1, 0, (List *) NULL, (RCSNode *) NULL);
+ if (vers->vn_rcs != NULL
+ && !RCS_isdead(vers->srcfile, vers->vn_rcs))
+ {
+ char xtmpfile[PATH_MAX];
+ int different;
+ int retcode = 0;
+
+ tmpdir = getenv ("TMPDIR");
+ if (tmpdir == NULL || tmpdir[0] == '\0')
+ tmpdir = "/tmp";
+
+ (void) sprintf (xtmpfile, "%s/cvs-imp%ld", tmpdir, (long) getpid());
+
+ /*
+ * The rcs file does have a revision on the vendor branch. Compare
+ * this revision with the import file; if they match exactly, there
+ * is no need to install the new import file as a new revision to the
+ * branch. Just tag the revision with the new import tags.
+ *
+ * This is to try to cut down the number of "C" conflict messages for
+ * locally modified import source files.
+ */
+ /* Why is RCS_FLAGS_FORCE here? I wouldn't think that it would have any
+ effect in conjunction with passing NULL for workfile (i.e. to stdout). */
+ retcode = RCS_checkout (vers->srcfile->path, NULL, vers->vn_rcs,
+#ifdef HAVE_RCS5
+ "-ko",
+#else
+ NULL,
+#endif
+ xtmpfile, RCS_FLAGS_FORCE, 0);
+ if (retcode != 0)
+ {
+ ierrno = errno;
+ fperror (logfp, 0, retcode == -1 ? ierrno : 0,
+ "ERROR: cannot co revision %s of file %s", vers->vn_rcs,
+ vers->srcfile->path);
+ error (0, retcode == -1 ? ierrno : 0,
+ "ERROR: cannot co revision %s of file %s", vers->vn_rcs,
+ vers->srcfile->path);
+ (void) unlink_file (xtmpfile);
+ return (1);
+ }
+
+ tocvsPath = wrap_tocvs_process_file (vfile);
+ different = xcmp (xtmpfile, vfile);
+ if (tocvsPath)
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ (void) unlink_file (xtmpfile);
+ if (!different)
+ {
+ int retval = 0;
+
+ /*
+ * The two files are identical. Just update the tags, print the
+ * "U", signifying that the file has changed, but needs no
+ * attention, and we're done.
+ */
+ if (add_tags (vers->srcfile->path, vfile, vtag, targc, targv))
+ retval = 1;
+ add_log ('U', vfile);
+ freevers_ts (&vers);
+ return (retval);
+ }
+ }
+
+ /* We may have failed to parse the RCS file; check just in case */
+ if (vers->srcfile == NULL ||
+ add_rev (message, vers->srcfile->path, vfile, vers->vn_rcs) ||
+ add_tags (vers->srcfile->path, vfile, vtag, targc, targv))
+ {
+ freevers_ts (&vers);
+ return (1);
+ }
+
+ if (vers->srcfile->branch == NULL || inattic ||
+ strcmp (vers->srcfile->branch, vbranch) != 0)
+ {
+ conflicts++;
+ letter = 'C';
+ }
+ else
+ letter = 'U';
+ add_log (letter, vfile);
+
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Add the revision to the vendor branch
+ */
+static int
+add_rev (message, rcs, vfile, vers)
+ char *message;
+ char *rcs;
+ char *vfile;
+ char *vers;
+{
+ int locked, status, ierrno;
+ char *tocvsPath;
+
+ if (noexec)
+ return (0);
+
+ locked = 0;
+ if (vers != NULL)
+ {
+ /* Before RCS_lock existed, we were directing stdout, as well as
+ stderr, from the RCS command, to DEVNULL. I wouldn't guess that
+ was necessary, but I don't know for sure. */
+ if (RCS_lock (rcs, vbranch, 1) != 0)
+ {
+ error (0, errno, "fork failed");
+ return (1);
+ }
+ locked = 1;
+ }
+ tocvsPath = wrap_tocvs_process_file (vfile);
+ if (tocvsPath == NULL)
+ {
+ /* We play with hard links rather than passing -u to ci to avoid
+ expanding RCS keywords (see test 106.5 in sanity.sh). */
+ if (link_file (vfile, FILE_HOLDER) < 0)
+ {
+ if (errno == EEXIST)
+ {
+ (void) unlink_file (FILE_HOLDER);
+ (void) link_file (vfile, FILE_HOLDER);
+ }
+ else
+ {
+ ierrno = errno;
+ fperror (logfp, 0, ierrno,
+ "ERROR: cannot create link to %s", vfile);
+ error (0, ierrno, "ERROR: cannot create link to %s", vfile);
+ return (1);
+ }
+ }
+ }
+
+ status = RCS_checkin (rcs, tocvsPath == NULL ? vfile : tocvsPath,
+ message, vbranch,
+ (RCS_FLAGS_QUIET
+ | (use_file_modtime ? RCS_FLAGS_MODTIME : 0)),
+ 0);
+ ierrno = errno;
+
+ if (tocvsPath == NULL)
+ rename_file (FILE_HOLDER, vfile);
+ else
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ if (status)
+ {
+ if (!noexec)
+ {
+ fperror (logfp, 0, status == -1 ? ierrno : 0, "ERROR: Check-in of %s failed", rcs);
+ error (0, status == -1 ? ierrno : 0, "ERROR: Check-in of %s failed", rcs);
+ }
+ if (locked)
+ {
+ (void) RCS_unlock(rcs, vbranch, 0);
+ }
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Add the vendor branch tag and all the specified import release tags to the
+ * RCS file. The vendor branch tag goes on the branch root (1.1.1) while the
+ * vendor release tags go on the newly added leaf of the branch (1.1.1.1,
+ * 1.1.1.2, ...).
+ */
+static int
+add_tags (rcs, vfile, vtag, targc, targv)
+ char *rcs;
+ char *vfile;
+ char *vtag;
+ int targc;
+ char *targv[];
+{
+ int i, ierrno;
+ Vers_TS *vers;
+ int retcode = 0;
+
+ if (noexec)
+ return (0);
+
+ if ((retcode = RCS_settag(rcs, vtag, vbranch)) != 0)
+ {
+ ierrno = errno;
+ fperror (logfp, 0, retcode == -1 ? ierrno : 0,
+ "ERROR: Failed to set tag %s in %s", vtag, rcs);
+ error (0, retcode == -1 ? ierrno : 0,
+ "ERROR: Failed to set tag %s in %s", vtag, rcs);
+ return (1);
+ }
+ vers = Version_TS (repository, (char *) NULL, vtag, (char *) NULL, vfile,
+ 1, 0, (List *) NULL, (RCSNode *) NULL);
+ for (i = 0; i < targc; i++)
+ {
+ if ((retcode = RCS_settag (rcs, targv[i], vers->vn_rcs)) != 0)
+ {
+ ierrno = errno;
+ fperror (logfp, 0, retcode == -1 ? ierrno : 0,
+ "WARNING: Couldn't add tag %s to %s", targv[i], rcs);
+ error (0, retcode == -1 ? ierrno : 0,
+ "WARNING: Couldn't add tag %s to %s", targv[i], rcs);
+ }
+ }
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Stolen from rcs/src/rcsfnms.c, and adapted/extended.
+ */
+struct compair
+{
+ char *suffix, *comlead;
+};
+
+static const struct compair comtable[] =
+{
+
+/*
+ * comtable pairs each filename suffix with a comment leader. The comment
+ * leader is placed before each line generated by the $Log keyword. This
+ * table is used to guess the proper comment leader from the working file's
+ * suffix during initial ci (see InitAdmin()). Comment leaders are needed for
+ * languages without multiline comments; for others they are optional.
+ */
+ {"a", "-- "}, /* Ada */
+ {"ada", "-- "},
+ {"adb", "-- "},
+ {"asm", ";; "}, /* assembler (MS-DOS) */
+ {"ads", "-- "}, /* Ada */
+ {"bas", "' "}, /* Visual Basic code */
+ {"bat", ":: "}, /* batch (MS-DOS) */
+ {"body", "-- "}, /* Ada */
+ {"c", " * "}, /* C */
+ {"c++", "// "}, /* C++ in all its infinite guises */
+ {"cc", "// "},
+ {"cpp", "// "},
+ {"cxx", "// "},
+ {"m", "// "}, /* Objective-C */
+ {"cl", ";;; "}, /* Common Lisp */
+ {"cmd", ":: "}, /* command (OS/2) */
+ {"cmf", "c "}, /* CM Fortran */
+ {"cs", " * "}, /* C* */
+ {"csh", "# "}, /* shell */
+ {"dlg", " * "}, /* MS Windows dialog file */
+ {"e", "# "}, /* efl */
+ {"epsf", "% "}, /* encapsulated postscript */
+ {"epsi", "% "}, /* encapsulated postscript */
+ {"el", "; "}, /* Emacs Lisp */
+ {"f", "c "}, /* Fortran */
+ {"for", "c "},
+ {"frm", "' "}, /* Visual Basic form */
+ {"h", " * "}, /* C-header */
+ {"hh", "// "}, /* C++ header */
+ {"hpp", "// "},
+ {"hxx", "// "},
+ {"in", "# "}, /* for Makefile.in */
+ {"l", " * "}, /* lex (conflict between lex and
+ * franzlisp) */
+ {"mac", ";; "}, /* macro (DEC-10, MS-DOS, PDP-11,
+ * VMS, etc) */
+ {"mak", "# "}, /* makefile, e.g. Visual C++ */
+ {"me", ".\\\" "}, /* me-macros t/nroff */
+ {"ml", "; "}, /* mocklisp */
+ {"mm", ".\\\" "}, /* mm-macros t/nroff */
+ {"ms", ".\\\" "}, /* ms-macros t/nroff */
+ {"man", ".\\\" "}, /* man-macros t/nroff */
+ {"1", ".\\\" "}, /* feeble attempt at man pages... */
+ {"2", ".\\\" "},
+ {"3", ".\\\" "},
+ {"4", ".\\\" "},
+ {"5", ".\\\" "},
+ {"6", ".\\\" "},
+ {"7", ".\\\" "},
+ {"8", ".\\\" "},
+ {"9", ".\\\" "},
+ {"p", " * "}, /* pascal */
+ {"pas", " * "},
+ {"pl", "# "}, /* perl (conflict with Prolog) */
+ {"ps", "% "}, /* postscript */
+ {"psw", "% "}, /* postscript wrap */
+ {"pswm", "% "}, /* postscript wrap */
+ {"r", "# "}, /* ratfor */
+ {"rc", " * "}, /* Microsoft Windows resource file */
+ {"red", "% "}, /* psl/rlisp */
+#ifdef sparc
+ {"s", "! "}, /* assembler */
+#endif
+#ifdef mc68000
+ {"s", "| "}, /* assembler */
+#endif
+#ifdef pdp11
+ {"s", "/ "}, /* assembler */
+#endif
+#ifdef vax
+ {"s", "# "}, /* assembler */
+#endif
+#ifdef __ksr__
+ {"s", "# "}, /* assembler */
+ {"S", "# "}, /* Macro assembler */
+#endif
+ {"sh", "# "}, /* shell */
+ {"sl", "% "}, /* psl */
+ {"spec", "-- "}, /* Ada */
+ {"tex", "% "}, /* tex */
+ {"y", " * "}, /* yacc */
+ {"ye", " * "}, /* yacc-efl */
+ {"yr", " * "}, /* yacc-ratfor */
+ {"", "# "}, /* default for empty suffix */
+ {NULL, "# "} /* default for unknown suffix; */
+/* must always be last */
+};
+
+static char *
+get_comment (user)
+ char *user;
+{
+ char *cp, *suffix;
+ char suffix_path[PATH_MAX];
+ int i;
+
+ cp = strrchr (user, '.');
+ if (cp != NULL)
+ {
+ cp++;
+
+ /*
+ * Convert to lower-case, since we are not concerned about the
+ * case-ness of the suffix.
+ */
+ (void) strcpy (suffix_path, cp);
+ for (cp = suffix_path; *cp; cp++)
+ if (isupper (*cp))
+ *cp = tolower (*cp);
+ suffix = suffix_path;
+ }
+ else
+ suffix = ""; /* will use the default */
+ for (i = 0;; i++)
+ {
+ if (comtable[i].suffix == NULL) /* default */
+ return (comtable[i].comlead);
+ if (strcmp (suffix, comtable[i].suffix) == 0)
+ return (comtable[i].comlead);
+ }
+}
+
+static int
+add_rcs_file (message, rcs, user, vtag, targc, targv)
+ char *message;
+ char *rcs;
+ char *user;
+ char *vtag;
+ int targc;
+ char *targv[];
+{
+ FILE *fprcs, *fpuser;
+ struct stat sb;
+ struct tm *ftm;
+ time_t now;
+ char altdate1[50];
+#ifndef HAVE_RCS5
+ char altdate2[50];
+#endif
+ char *author;
+ int i, ierrno, err = 0;
+ mode_t mode;
+ char *tocvsPath;
+ char *userfile;
+
+ if (noexec)
+ return (0);
+
+ /* FIXME? We always import files as text files (note that means
+ that files get stored with straight linefeeds). There isn't an
+ obvious, clean, way to let people specify which files are binary.
+ Maybe based on the file name.... */
+ tocvsPath = wrap_tocvs_process_file (user);
+ userfile = (tocvsPath == NULL ? user : tocvsPath);
+ fpuser = fopen (userfile, "r");
+ if (fpuser == NULL)
+ {
+ /* not fatal, continue import */
+ fperror (logfp, 0, errno, "ERROR: cannot read file %s", userfile);
+ error (0, errno, "ERROR: cannot read file %s", userfile);
+ goto read_error;
+ }
+ fprcs = fopen (rcs, "w+b");
+ if (fprcs == NULL)
+ {
+ ierrno = errno;
+ goto write_error_noclose;
+ }
+
+ /*
+ * putadmin()
+ */
+ if (fprintf (fprcs, "head %s;\012", vhead) < 0 ||
+ fprintf (fprcs, "branch %s;\012", vbranch) < 0 ||
+ fprintf (fprcs, "access ;\012") < 0 ||
+ fprintf (fprcs, "symbols ") < 0)
+ {
+ goto write_error;
+ }
+
+ for (i = targc - 1; i >= 0; i--) /* RCS writes the symbols backwards */
+ if (fprintf (fprcs, "%s:%s.1 ", targv[i], vbranch) < 0)
+ goto write_error;
+
+ if (fprintf (fprcs, "%s:%s;\012", vtag, vbranch) < 0 ||
+ fprintf (fprcs, "locks ; strict;\012") < 0 ||
+ /* XXX - make sure @@ processing works in the RCS file */
+ fprintf (fprcs, "comment @%s@;\012", get_comment (user)) < 0)
+ {
+ goto write_error;
+ }
+
+ if (keyword_opt != NULL)
+ if (fprintf (fprcs, "expand @%s@;\012", keyword_opt) < 0)
+ {
+ goto write_error;
+ }
+
+ if (fprintf (fprcs, "\012") < 0)
+ goto write_error;
+
+ /*
+ * puttree()
+ */
+ 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", vhead) < 0 ||
+ fprintf (fprcs, "date %s; author %s; state Exp;\012",
+ altdate1, author) < 0 ||
+ fprintf (fprcs, "branches %s.1;\012", vbranch) < 0 ||
+ fprintf (fprcs, "next ;\012") < 0 ||
+ fprintf (fprcs, "\012%s.1\012", 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 ||
+ /*
+ * putdesc()
+ */
+ fprintf (fprcs, "\012desc\012") < 0 ||
+ fprintf (fprcs, "@@\012\012\012") < 0 ||
+ /*
+ * putdelta()
+ */
+ fprintf (fprcs, "\012%s\012", vhead) < 0 ||
+ fprintf (fprcs, "log\012") < 0 ||
+ fprintf (fprcs, "@Initial revision\012@\012") < 0 ||
+ fprintf (fprcs, "text\012@") < 0)
+ {
+ goto write_error;
+ }
+
+ /* Now copy over the contents of the file, expanding at signs. */
+ {
+ unsigned char buf[8192];
+ unsigned int len;
+
+ while (1)
+ {
+ len = fread (buf, 1, sizeof buf, fpuser);
+ if (len == 0)
+ {
+ if (ferror (fpuser))
+ error (1, errno, "cannot read file %s for copying", user);
+ break;
+ }
+ if (expand_at_signs (buf, len, fprcs) < 0)
+ goto write_error;
+ }
+ }
+ if (fprintf (fprcs, "@\012\012") < 0 ||
+ fprintf (fprcs, "\012%s.1\012", vbranch) < 0 ||
+ fprintf (fprcs, "log\012@") < 0 ||
+ expand_at_signs (message, (off_t) strlen (message), fprcs) < 0 ||
+ fprintf (fprcs, "@\012text\012") < 0 ||
+ fprintf (fprcs, "@@\012") < 0)
+ {
+ goto write_error;
+ }
+ if (fclose (fprcs) == EOF)
+ {
+ ierrno = errno;
+ goto write_error_noclose;
+ }
+ (void) fclose (fpuser);
+
+ /*
+ * Fix the modes on the RCS files. The user modes of the original
+ * user file are propagated to the group and other modes as allowed
+ * by the repository umask, except that all write permissions are
+ * turned off.
+ */
+ mode = (sb.st_mode |
+ (sb.st_mode & S_IRWXU) >> 3 |
+ (sb.st_mode & S_IRWXU) >> 6) &
+ ~cvsumask &
+ ~(S_IWRITE | S_IWGRP | S_IWOTH);
+ if (chmod (rcs, mode) < 0)
+ {
+ ierrno = errno;
+ fperror (logfp, 0, ierrno,
+ "WARNING: cannot change mode of file %s", rcs);
+ error (0, ierrno, "WARNING: cannot change mode of file %s", rcs);
+ err++;
+ }
+ if (tocvsPath)
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+ return (err);
+
+write_error:
+ ierrno = errno;
+ (void) fclose (fprcs);
+write_error_noclose:
+ (void) fclose (fpuser);
+ fperror (logfp, 0, ierrno, "ERROR: cannot write file %s", rcs);
+ error (0, ierrno, "ERROR: cannot write file %s", rcs);
+ if (ierrno == ENOSPC)
+ {
+ (void) unlink (rcs);
+ fperror (logfp, 0, 0, "ERROR: out of space - aborting");
+ error (1, 0, "ERROR: out of space - aborting");
+ }
+read_error:
+ if (tocvsPath)
+ if (unlink_file_dir (tocvsPath) < 0)
+ error (0, errno, "cannot remove %s", tocvsPath);
+
+ return (err + 1);
+}
+
+/*
+ * Write SIZE bytes at BUF to FP, expanding @ signs into double @
+ * signs. If an error occurs, return a negative value and set errno
+ * to indicate the error. If not, return a nonnegative value.
+ */
+static int
+expand_at_signs (buf, size, fp)
+ char *buf;
+ off_t size;
+ FILE *fp;
+{
+ char *cp, *end;
+
+ errno = 0;
+ for (cp = buf, end = buf + size; cp < end; cp++)
+ {
+ if (*cp == '@')
+ {
+ if (putc ('@', fp) == EOF && errno != 0)
+ return EOF;
+ }
+ if (putc (*cp, fp) == EOF && errno != 0)
+ return (EOF);
+ }
+ return (1);
+}
+
+/*
+ * Write an update message to (potentially) the screen and the log file.
+ */
+static void
+add_log (ch, fname)
+ int ch;
+ char *fname;
+{
+ if (!really_quiet) /* write to terminal */
+ {
+ char buf[2];
+ buf[0] = ch;
+ buf[1] = ' ';
+ cvs_output (buf, 2);
+ if (repos_len)
+ {
+ cvs_output (repository + repos_len + 1, 0);
+ cvs_output ("/", 1);
+ }
+ else if (repository[0] != '\0')
+ {
+ cvs_output (repository, 0);
+ cvs_output ("/", 1);
+ }
+ cvs_output (fname, 0);
+ cvs_output ("\n", 1);
+ }
+
+ if (repos_len) /* write to logfile */
+ (void) fprintf (logfp, "%c %s/%s\n", ch,
+ repository + repos_len + 1, fname);
+ else if (repository[0])
+ (void) fprintf (logfp, "%c %s/%s\n", ch, repository, fname);
+ else
+ (void) fprintf (logfp, "%c %s\n", ch, fname);
+}
+
+/*
+ * This is the recursive function that walks the argument directory looking
+ * for sub-directories that have CVS administration files in them and updates
+ * them recursively.
+ *
+ * Note that we do not follow symbolic links here, which is a feature!
+ */
+static int
+import_descend_dir (message, dir, vtag, targc, targv)
+ char *message;
+ char *dir;
+ char *vtag;
+ int targc;
+ char *targv[];
+{
+ struct saved_cwd cwd;
+ char *cp;
+ int ierrno, err;
+
+ if (islink (dir))
+ return (0);
+ if (save_cwd (&cwd))
+ {
+ fperror (logfp, 0, 0, "ERROR: cannot get working directory");
+ return (1);
+ }
+ if (repository[0] == '\0')
+ (void) strcpy (repository, dir);
+ else
+ {
+ (void) strcat (repository, "/");
+ (void) strcat (repository, dir);
+ }
+#ifdef CLIENT_SUPPORT
+ if (!quiet && !client_active)
+#else
+ if (!quiet)
+#endif
+ error (0, 0, "Importing %s", repository);
+
+ if (chdir (dir) < 0)
+ {
+ ierrno = errno;
+ fperror (logfp, 0, ierrno, "ERROR: cannot chdir to %s", repository);
+ error (0, ierrno, "ERROR: cannot chdir to %s", repository);
+ err = 1;
+ goto out;
+ }
+#ifdef CLIENT_SUPPORT
+ if (!client_active && !isdir (repository))
+#else
+ if (!isdir (repository))
+#endif
+ {
+ char rcs[PATH_MAX];
+
+ (void) sprintf (rcs, "%s%s", repository, RCSEXT);
+ if (isfile (repository) || isfile(rcs))
+ {
+ fperror (logfp, 0, 0, "ERROR: %s is a file, should be a directory!",
+ repository);
+ error (0, 0, "ERROR: %s is a file, should be a directory!",
+ repository);
+ err = 1;
+ goto out;
+ }
+ if (noexec == 0 && CVS_MKDIR (repository, 0777) < 0)
+ {
+ ierrno = errno;
+ fperror (logfp, 0, ierrno,
+ "ERROR: cannot mkdir %s -- not added", repository);
+ error (0, ierrno,
+ "ERROR: cannot mkdir %s -- not added", repository);
+ err = 1;
+ goto out;
+ }
+ }
+ err = import_descend (message, vtag, targc, targv);
+ out:
+ if ((cp = strrchr (repository, '/')) != NULL)
+ *cp = '\0';
+ else
+ repository[0] = '\0';
+ if (restore_cwd (&cwd, NULL))
+ exit (EXIT_FAILURE);
+ free_cwd (&cwd);
+ return (err);
+}
diff --git a/contrib/cvs/src/lock.c b/contrib/cvs/src/lock.c
new file mode 100644
index 0000000..7e35aed
--- /dev/null
+++ b/contrib/cvs/src/lock.c
@@ -0,0 +1,639 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Set Lock
+ *
+ * Lock file support for CVS.
+ */
+
+#include "cvs.h"
+
+static int readers_exist PROTO((char *repository));
+static int set_lock PROTO((char *repository, int will_wait));
+static void clear_lock PROTO((void));
+static void set_lockers_name PROTO((struct stat *statp));
+static int set_writelock_proc PROTO((Node * p, void *closure));
+static int unlock_proc PROTO((Node * p, void *closure));
+static int write_lock PROTO((char *repository));
+static void lock_simple_remove PROTO((char *repository));
+static void lock_wait PROTO((char *repository));
+static int Check_Owner PROTO((char *lockdir));
+
+static char lockers_name[20];
+static char *repository;
+static char readlock[PATH_MAX], writelock[PATH_MAX], masterlock[PATH_MAX];
+static int cleanup_lckdir;
+static List *locklist;
+
+#define L_OK 0 /* success */
+#define L_ERROR 1 /* error condition */
+#define L_LOCKED 2 /* lock owned by someone else */
+
+/*
+ * Clean up all outstanding locks
+ */
+void
+Lock_Cleanup ()
+{
+ /* clean up simple locks (if any) */
+ if (repository != NULL)
+ {
+ lock_simple_remove (repository);
+ repository = (char *) NULL;
+ }
+
+ /* clean up multiple locks (if any) */
+ if (locklist != (List *) NULL)
+ {
+ (void) walklist (locklist, unlock_proc, NULL);
+ locklist = (List *) NULL;
+ }
+}
+
+/*
+ * walklist proc for removing a list of locks
+ */
+static int
+unlock_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ lock_simple_remove (p->key);
+ return (0);
+}
+
+/*
+ * Remove the lock files (without complaining if they are not there),
+ */
+static void
+lock_simple_remove (repository)
+ char *repository;
+{
+ char tmp[PATH_MAX];
+
+ if (readlock[0] != '\0')
+ {
+ (void) sprintf (tmp, "%s/%s", repository, readlock);
+ if (unlink (tmp) < 0 && ! existence_error (errno))
+ error (0, errno, "failed to remove lock %s", tmp);
+ }
+
+ if (writelock[0] != '\0')
+ {
+ (void) sprintf (tmp, "%s/%s", repository, writelock);
+ if (unlink (tmp) < 0 && ! existence_error (errno))
+ error (0, errno, "failed to remove lock %s", tmp);
+ }
+
+ /*
+ * Only remove the lock directory if it is ours, note that this does
+ * lead to the limitation that one user ID should not be committing
+ * files into the same Repository directory at the same time. Oh well.
+ */
+ if (writelock[0] != '\0' || (readlock[0] != '\0' && cleanup_lckdir))
+ {
+ (void) sprintf (tmp, "%s/%s", repository, CVSLCK);
+ if (Check_Owner(tmp))
+ {
+#ifdef AFSCVS
+ char rmuidlock[PATH_MAX];
+ sprintf(rmuidlock, "rm -f %s/uidlock%d", tmp, geteuid() );
+ system(rmuidlock);
+#endif
+ (void) rmdir (tmp);
+ }
+ }
+ cleanup_lckdir = 0;
+}
+
+/*
+ * Check the owner of a lock. Returns 1 if we own it, 0 otherwise.
+ */
+static int
+Check_Owner(lockdir)
+ char *lockdir;
+{
+ struct stat sb;
+
+#ifdef AFSCVS
+ /* In the Andrew File System (AFS), user ids from stat don't match
+ those from geteuid(). The AFSCVS code can deal with either AFS or
+ non-AFS repositories; the non-AFSCVS code is faster. */
+ char uidlock[PATH_MAX];
+
+ /* Check if the uidlock is in the lock directory */
+ sprintf(uidlock, "%s/uidlock%d", lockdir, geteuid() );
+ if( stat(uidlock, &sb) != -1)
+ return 1; /* The file exists, therefore we own the lock */
+ else
+ return 0; /* The file didn't exist or some other error.
+ * Assume that we don't own it.
+ */
+#else
+ if (stat (lockdir, &sb) != -1 && sb.st_uid == geteuid ())
+ return 1;
+ else
+ return 0;
+#endif
+} /* end Check_Owner() */
+
+
+/*
+ * Create a lock file for readers
+ */
+int
+Reader_Lock (xrepository)
+ char *xrepository;
+{
+ int err = 0;
+ FILE *fp;
+ char tmp[PATH_MAX];
+
+ if (noexec)
+ return (0);
+
+ /* we only do one directory at a time for read locks! */
+ if (repository != NULL)
+ {
+ error (0, 0, "Reader_Lock called while read locks set - Help!");
+ return (1);
+ }
+
+ if (readlock[0] == '\0')
+ (void) sprintf (readlock,
+#ifdef HAVE_LONG_FILE_NAMES
+ "%s.%s.%ld", CVSRFL, hostname,
+#else
+ "%s.%ld", CVSRFL,
+#endif
+ (long) getpid ());
+
+ /* remember what we're locking (for lock_cleanup) */
+ repository = xrepository;
+
+ /* get the lock dir for our own */
+ if (set_lock (xrepository, 1) != L_OK)
+ {
+ error (0, 0, "failed to obtain dir lock in repository `%s'",
+ xrepository);
+ readlock[0] = '\0';
+ return (1);
+ }
+
+ /* write a read-lock */
+ (void) sprintf (tmp, "%s/%s", xrepository, readlock);
+ if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF)
+ {
+ error (0, errno, "cannot create read lock in repository `%s'",
+ xrepository);
+ readlock[0] = '\0';
+ err = 1;
+ }
+
+ /* free the lock dir */
+ clear_lock();
+
+ return (err);
+}
+
+/*
+ * Lock a list of directories for writing
+ */
+static char *lock_error_repos;
+static int lock_error;
+int
+Writer_Lock (list)
+ List *list;
+{
+ if (noexec)
+ return (0);
+
+ /* We only know how to do one list at a time */
+ if (locklist != (List *) NULL)
+ {
+ error (0, 0, "Writer_Lock called while write locks set - Help!");
+ return (1);
+ }
+
+ for (;;)
+ {
+ /* try to lock everything on the list */
+ lock_error = L_OK; /* init for set_writelock_proc */
+ lock_error_repos = (char *) NULL; /* init for set_writelock_proc */
+ locklist = list; /* init for Lock_Cleanup */
+ (void) strcpy (lockers_name, "unknown");
+
+ (void) walklist (list, set_writelock_proc, NULL);
+
+ switch (lock_error)
+ {
+ case L_ERROR: /* Real Error */
+ Lock_Cleanup (); /* clean up any locks we set */
+ error (0, 0, "lock failed - giving up");
+ return (1);
+
+ case L_LOCKED: /* Someone already had a lock */
+ Lock_Cleanup (); /* clean up any locks we set */
+ lock_wait (lock_error_repos); /* sleep a while and try again */
+ continue;
+
+ case L_OK: /* we got the locks set */
+ return (0);
+
+ default:
+ error (0, 0, "unknown lock status %d in Writer_Lock",
+ lock_error);
+ return (1);
+ }
+ }
+}
+
+/*
+ * walklist proc for setting write locks
+ */
+static int
+set_writelock_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ /* if some lock was not OK, just skip this one */
+ if (lock_error != L_OK)
+ return (0);
+
+ /* apply the write lock */
+ lock_error_repos = p->key;
+ lock_error = write_lock (p->key);
+ return (0);
+}
+
+/*
+ * Create a lock file for writers returns L_OK if lock set ok, L_LOCKED if
+ * lock held by someone else or L_ERROR if an error occurred
+ */
+static int
+write_lock (repository)
+ char *repository;
+{
+ int status;
+ FILE *fp;
+ char tmp[PATH_MAX];
+
+ if (writelock[0] == '\0')
+ (void) sprintf (writelock,
+#ifdef HAVE_LONG_FILE_NAMES
+ "%s.%s.%ld", CVSWFL, hostname,
+#else
+ "%s.%ld", CVSWFL,
+#endif
+ (long) getpid());
+
+ /* make sure the lock dir is ours (not necessarily unique to us!) */
+ status = set_lock (repository, 0);
+ if (status == L_OK)
+ {
+ /* we now own a writer - make sure there are no readers */
+ if (readers_exist (repository))
+ {
+ /* clean up the lock dir if we created it */
+ if (status == L_OK)
+ {
+ clear_lock();
+ }
+
+ /* indicate we failed due to read locks instead of error */
+ return (L_LOCKED);
+ }
+
+ /* write the write-lock file */
+ (void) sprintf (tmp, "%s/%s", repository, writelock);
+ if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF)
+ {
+ int xerrno = errno;
+
+ if (unlink (tmp) < 0 && ! existence_error (errno))
+ error (0, errno, "failed to remove lock %s", tmp);
+
+ /* free the lock dir if we created it */
+ if (status == L_OK)
+ {
+ clear_lock();
+ }
+
+ /* return the error */
+ error (0, xerrno, "cannot create write lock in repository `%s'",
+ repository);
+ return (L_ERROR);
+ }
+ return (L_OK);
+ }
+ else
+ return (status);
+}
+
+/*
+ * readers_exist() returns 0 if there are no reader lock files remaining in
+ * the repository; else 1 is returned, to indicate that the caller should
+ * sleep a while and try again.
+ */
+static int
+readers_exist (repository)
+ char *repository;
+{
+ char *line;
+ DIR *dirp;
+ struct dirent *dp;
+ struct stat sb;
+ int ret = 0;
+
+#ifdef CVS_FUDGELOCKS
+again:
+#endif
+
+ if ((dirp = opendir (repository)) == NULL)
+ error (1, 0, "cannot open directory %s", repository);
+
+ errno = 0;
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (fnmatch (CVSRFLPAT, dp->d_name, 0) == 0)
+ {
+#ifdef CVS_FUDGELOCKS
+ time_t now;
+ (void) time (&now);
+#endif
+
+ line = xmalloc (strlen (repository) + strlen (dp->d_name) + 5);
+ (void) sprintf (line, "%s/%s", repository, dp->d_name);
+ if (stat (line, &sb) != -1)
+ {
+#ifdef CVS_FUDGELOCKS
+ /*
+ * If the create time of the file is more than CVSLCKAGE
+ * seconds ago, try to clean-up the lock file, and if
+ * successful, re-open the directory and try again.
+ */
+ if (now >= (sb.st_ctime + CVSLCKAGE) && unlink (line) != -1)
+ {
+ (void) closedir (dirp);
+ free (line);
+ goto again;
+ }
+#endif
+ set_lockers_name (&sb);
+ }
+ else
+ {
+ /* If the file doesn't exist, it just means that it disappeared
+ between the time we did the readdir and the time we did
+ the stat. */
+ if (!existence_error (errno))
+ error (0, errno, "cannot stat %s", line);
+ }
+ errno = 0;
+ free (line);
+
+ ret = 1;
+ break;
+ }
+ errno = 0;
+ }
+ if (errno != 0)
+ error (0, errno, "error reading directory %s", repository);
+
+ closedir (dirp);
+ return (ret);
+}
+
+/*
+ * Set the static variable lockers_name appropriately, based on the stat
+ * structure passed in.
+ */
+static void
+set_lockers_name (statp)
+ struct stat *statp;
+{
+ struct passwd *pw;
+
+ if ((pw = (struct passwd *) getpwuid (statp->st_uid)) !=
+ (struct passwd *) NULL)
+ {
+ (void) strcpy (lockers_name, pw->pw_name);
+ }
+ else
+ (void) sprintf (lockers_name, "uid%lu", (unsigned long) statp->st_uid);
+}
+
+/*
+ * Persistently tries to make the directory "lckdir",, which serves as a
+ * lock. If the create time on the directory is greater than CVSLCKAGE
+ * seconds old, just try to remove the directory.
+ */
+static int
+set_lock (repository, will_wait)
+ char *repository;
+ int will_wait;
+{
+ struct stat sb;
+ mode_t omask;
+#ifdef CVS_FUDGELOCKS
+ time_t now;
+#endif
+
+ (void) sprintf (masterlock, "%s/%s", repository, CVSLCK);
+
+ /*
+ * Note that it is up to the callers of set_lock() to arrange for signal
+ * handlers that do the appropriate things, like remove the lock
+ * directory before they exit.
+ */
+ cleanup_lckdir = 0;
+ for (;;)
+ {
+ int status = -1;
+ omask = umask (cvsumask);
+ SIG_beginCrSect ();
+ if (CVS_MKDIR (masterlock, 0777) == 0)
+ {
+#ifdef AFSCVS
+ char uidlock[PATH_MAX];
+ FILE *fp;
+
+ sprintf(uidlock, "%s/uidlock%d", masterlock, geteuid() );
+ if ((fp = fopen(uidlock, "w+")) == NULL)
+ {
+ /* We failed to create the uidlock,
+ so rm masterlock and leave */
+ rmdir(masterlock);
+ SIG_endCrSect ();
+ status = L_ERROR;
+ goto out;
+ }
+
+ /* We successfully created the uid lock, so close the file */
+ fclose(fp);
+#endif
+ cleanup_lckdir = 1;
+ SIG_endCrSect ();
+ status = L_OK;
+ goto out;
+ }
+ SIG_endCrSect ();
+ out:
+ (void) umask (omask);
+ if (status != -1)
+ return status;
+
+ if (errno != EEXIST)
+ {
+ error (0, errno,
+ "failed to create lock directory in repository `%s'",
+ repository);
+ return (L_ERROR);
+ }
+
+ /*
+ * stat the dir - if it is non-existent, re-try the loop since
+ * someone probably just removed it (thus releasing the lock)
+ */
+ if (stat (masterlock, &sb) < 0)
+ {
+ if (existence_error (errno))
+ continue;
+
+ error (0, errno, "couldn't stat lock directory `%s'", masterlock);
+ return (L_ERROR);
+ }
+
+#ifdef CVS_FUDGELOCKS
+ /*
+ * If the create time of the directory is more than CVSLCKAGE seconds
+ * ago, try to clean-up the lock directory, and if successful, just
+ * quietly retry to make it.
+ */
+ (void) time (&now);
+ if (now >= (sb.st_ctime + CVSLCKAGE))
+ {
+#ifdef AFSCVS
+ /* Remove the uidlock first */
+ char rmuidlock[PATH_MAX];
+ sprintf(rmuidlock, "rm -f %s/uidlock%d", masterlock, geteuid() );
+ system(rmuidlock);
+#endif
+ if (rmdir (masterlock) >= 0)
+ continue;
+ }
+#endif
+
+ /* set the lockers name */
+ set_lockers_name (&sb);
+
+ /* if he wasn't willing to wait, return an error */
+ if (!will_wait)
+ return (L_LOCKED);
+ lock_wait (repository);
+ }
+}
+
+/*
+ * Clear master lock. We don't have to recompute the lock name since
+ * clear_lock is never called except after a successful set_lock().
+ */
+static void
+clear_lock()
+{
+#ifdef AFSCVS
+ /* Remove the uidlock first */
+ char rmuidlock[PATH_MAX];
+ sprintf(rmuidlock, "rm -f %s/uidlock%d", masterlock, geteuid() );
+ system(rmuidlock);
+#endif
+ if (rmdir (masterlock) < 0)
+ error (0, errno, "failed to remove lock dir `%s'", masterlock);
+ cleanup_lckdir = 0;
+}
+
+/*
+ * Print out a message that the lock is still held, then sleep a while.
+ */
+static void
+lock_wait (repos)
+ char *repos;
+{
+ time_t now;
+
+ (void) time (&now);
+ error (0, 0, "[%8.8s] waiting for %s's lock in %s", ctime (&now) + 11,
+ lockers_name, repos);
+ (void) sleep (CVSLCKSLEEP);
+}
+
+static int lock_filesdoneproc PROTO ((int err, char *repository,
+ char *update_dir));
+static int fsortcmp PROTO((const Node * p, const Node * q));
+
+static List *lock_tree_list;
+
+/*
+ * Create a list of repositories to lock
+ */
+/* ARGSUSED */
+static int
+lock_filesdoneproc (err, repository, update_dir)
+ int err;
+ char *repository;
+ char *update_dir;
+{
+ Node *p;
+
+ p = getnode ();
+ p->type = LOCK;
+ p->key = xstrdup (repository);
+ /* FIXME-KRP: this error condition should not simply be passed by. */
+ if (p->key == NULL || addnode (lock_tree_list, p) != 0)
+ freenode (p);
+ return (err);
+}
+
+/*
+ * compare two lock list nodes (for sort)
+ */
+static int
+fsortcmp (p, q)
+ const Node *p;
+ const Node *q;
+{
+ return (strcmp (p->key, q->key));
+}
+
+void
+lock_tree_for_write (argc, argv, local, aflag)
+ int argc;
+ char **argv;
+ int local;
+ int aflag;
+{
+ int err;
+ /*
+ * Run the recursion processor to find all the dirs to lock and lock all
+ * the dirs
+ */
+ lock_tree_list = getlist ();
+ err = start_recursion ((FILEPROC) NULL, lock_filesdoneproc,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, argc,
+ argv, local, W_LOCAL, aflag, 0, (char *) NULL, 0,
+ 0);
+ sortlist (lock_tree_list, fsortcmp);
+ if (Writer_Lock (lock_tree_list) != 0)
+ error (1, 0, "lock failed - giving up");
+}
+
+void
+lock_tree_cleanup ()
+{
+ Lock_Cleanup ();
+ dellist (&lock_tree_list);
+}
diff --git a/contrib/cvs/src/log.c b/contrib/cvs/src/log.c
new file mode 100644
index 0000000..f167d92
--- /dev/null
+++ b/contrib/cvs/src/log.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Print Log Information
+ *
+ * This line exists solely to test some pcl-cvs/ChangeLog stuff. You
+ * can delete it, if indeed it's still here when you read it. -Karl
+ *
+ * Prints the RCS "log" (rlog) information for the specified files. With no
+ * argument, prints the log information for all the files in the directory
+ * (recursive by default).
+ */
+
+#include "cvs.h"
+
+static Dtype log_dirproc PROTO((char *dir, char *repository, char *update_dir));
+static int log_fileproc PROTO((struct file_info *finfo));
+
+static const char *const log_usage[] =
+{
+ "Usage: %s %s [-l] [rlog-options] [files...]\n",
+ "\t-l\tLocal directory only, no recursion.\n",
+ NULL
+};
+
+static int ac;
+static char **av;
+
+int
+cvslog (argc, argv)
+ int argc;
+ char **argv;
+{
+ int i;
+ int err = 0;
+ int local = 0;
+
+ if (argc == -1)
+ usage (log_usage);
+
+ /*
+ * All 'log' command options except -l are passed directly on to 'rlog'
+ */
+ for (i = 1; i < argc && argv[i][0] == '-'; i++)
+ if (argv[i][1] == 'l')
+ local = 1;
+
+ wrap_setup ();
+
+#ifdef CLIENT_SUPPORT
+ if (client_active) {
+ /* We're the local client. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ for (i = 1; i < argc && argv[i][0] == '-'; i++)
+ send_arg (argv[i]);
+
+ send_file_names (argc - i, argv + i, SEND_EXPAND_WILD);
+/* FIXME: We shouldn't have to send current files to get log entries, but it
+ doesn't work yet and I haven't debugged it. So send the files --
+ it's slower but it works. gnu@cygnus.com Apr94 */
+ send_files (argc - i, argv + i, local, 0);
+
+ send_to_server ("log\012", 0);
+ err = get_responses_and_close ();
+ return err;
+ }
+
+ ac = argc;
+ av = argv;
+#endif
+
+ err = start_recursion (log_fileproc, (FILESDONEPROC) NULL, log_dirproc,
+ (DIRLEAVEPROC) NULL, argc - i, argv + i, local,
+ W_LOCAL | W_REPOS | W_ATTIC, 0, 1,
+ (char *) NULL, 1, 0);
+ return (err);
+}
+
+
+/*
+ * Do an rlog on a file
+ */
+/* ARGSUSED */
+static int
+log_fileproc (finfo)
+ struct file_info *finfo;
+{
+ Node *p;
+ RCSNode *rcsfile;
+ int retcode = 0;
+
+ if ((rcsfile = finfo->rcs) == NULL)
+ {
+ /* no rcs file. What *do* we know about this file? */
+ p = findnode (finfo->entries, finfo->file);
+ if (p != NULL)
+ {
+ Entnode *e;
+
+ e = (Entnode *) p->data;
+ if (e->version[0] == '0' || e->version[1] == '\0')
+ {
+ if (!really_quiet)
+ error (0, 0, "%s has been added, but not committed",
+ finfo->file);
+ return(0);
+ }
+ }
+
+ if (!really_quiet)
+ error (0, 0, "nothing known about %s", finfo->file);
+
+ return (1);
+ }
+
+ run_setup ("%s%s -x,v/", Rcsbin, RCS_RLOG);
+ {
+ int i;
+ for (i = 1; i < ac && av[i][0] == '-'; i++)
+ if (av[i][1] != 'l')
+ run_arg (av[i]);
+ }
+ run_arg (rcsfile->path);
+
+ if (*finfo->update_dir)
+ {
+ char *workfile = xmalloc (strlen (finfo->update_dir) + strlen (finfo->file) + 2);
+ sprintf (workfile, "%s/%s", finfo->update_dir, finfo->file);
+ run_arg (workfile);
+ free (workfile);
+ }
+
+ if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_REALLY)) == -1)
+ {
+ error (1, errno, "fork failed for rlog on %s", finfo->file);
+ }
+ return (retcode);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+log_dirproc (dir, repository, update_dir)
+ char *dir;
+ char *repository;
+ char *update_dir;
+{
+ if (!isdir (dir))
+ return (R_SKIP_ALL);
+
+ if (!quiet)
+ error (0, 0, "Logging %s", update_dir);
+ return (R_PROCESS);
+}
diff --git a/contrib/cvs/src/login.c b/contrib/cvs/src/login.c
new file mode 100644
index 0000000..fc3a178
--- /dev/null
+++ b/contrib/cvs/src/login.c
@@ -0,0 +1,392 @@
+/*
+ * Copyright (c) 1995, Cyclic Software, Bloomington, IN, USA
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with CVS.
+ *
+ * Allow user to log in for an authenticating server.
+ */
+
+#include "cvs.h"
+#include "getline.h"
+
+#ifdef AUTH_CLIENT_SUPPORT /* This covers the rest of the file. */
+
+extern char *getpass ();
+
+#ifndef CVS_PASSWORD_FILE
+#define CVS_PASSWORD_FILE ".cvspass"
+#endif
+
+/* If non-NULL, get_cvs_password() will just return this. */
+static char *cvs_password = NULL;
+
+/* The return value will need to be freed. */
+char *
+construct_cvspass_filename ()
+{
+ char *homedir;
+ char *passfile;
+
+ /* Environment should override file. */
+ if ((passfile = getenv ("CVS_PASSFILE")) != NULL)
+ return xstrdup (passfile);
+
+ /* Construct absolute pathname to user's password file. */
+ /* todo: does this work under OS/2 ? */
+ homedir = get_homedir ();
+ if (! homedir)
+ {
+ error (1, errno, "could not find out home directory");
+ return (char *) NULL;
+ }
+
+ passfile =
+ (char *) xmalloc (strlen (homedir) + strlen (CVS_PASSWORD_FILE) + 3);
+ strcpy (passfile, homedir);
+ strcat (passfile, "/");
+ strcat (passfile, CVS_PASSWORD_FILE);
+
+ /* Safety first and last, Scouts. */
+ if (isfile (passfile))
+ /* xchmod() is too polite. */
+ chmod (passfile, 0600);
+
+ return passfile;
+}
+
+
+/* Prompt for a password, and store it in the file "CVS/.cvspass".
+ *
+ * Because the user might be accessing multiple repositories, with
+ * different passwords for each one, the format of ~/.cvspass is:
+ *
+ * user@host:/path Acleartext_password
+ * user@host:/path Acleartext_password
+ * ...
+ *
+ * Of course, the "user@" might be left off -- it's just based on the
+ * value of CVSroot.
+ *
+ * The "A" before "cleartext_password" is a literal capital A. It's a
+ * version number indicating which form of scrambling we're doing on
+ * the password -- someday we might provide something more secure than
+ * the trivial encoding we do now, and when that day comes, it would
+ * be nice to remain backward-compatible.
+ *
+ * Like .netrc, the file's permissions are the only thing preventing
+ * it from being read by others. Unlike .netrc, we will not be
+ * fascist about it, at most issuing a warning, and never refusing to
+ * work.
+ */
+int
+login (argc, argv)
+ int argc;
+ char **argv;
+{
+ char *passfile;
+ FILE *fp;
+ char *typed_password, *found_password;
+ char *linebuf = (char *) NULL;
+ size_t linebuf_len;
+ int root_len, already_entered = 0;
+
+ /* Make this a "fully-qualified" CVSroot if necessary. */
+ if (! strchr (CVSroot, '@'))
+ {
+ /* We need to prepend "user@host:". */
+ char *tmp;
+
+ printf ("Repository \"%s\" not fully-qualified.\n", CVSroot);
+ printf ("Please enter \"user@host:/path\": ");
+ fflush (stdout);
+ getline (&linebuf, &linebuf_len, stdin);
+
+ tmp = xmalloc (strlen (linebuf) + 1);
+
+ /* Give it some permanent storage. */
+ strcpy (tmp, linebuf);
+ tmp[strlen (linebuf) - 1] = '\0';
+ CVSroot = tmp;
+
+ /* Reset. */
+ free (linebuf);
+ linebuf = (char *) NULL;
+ }
+
+ if (CVSroot[0] != ':')
+ {
+ /* Then we need to prepend ":pserver:". */
+ char *tmp;
+
+ tmp = xmalloc (strlen (":pserver:") + strlen (CVSroot) + 1);
+ strcpy (tmp, ":pserver:");
+ strcat (tmp, CVSroot);
+ CVSroot = tmp;
+ }
+
+ /* Check to make sure it's fully-qualified before going on.
+ * Fully qualified in this context means it has both a user and a
+ * host:repos portion.
+ */
+ {
+ char *r;
+
+ /* After confirming that CVSroot is non-NULL, we skip past the
+ initial ":pserver:" to test the rest of it. */
+
+ if (! CVSroot)
+ error (1, 0, "CVSroot is NULL");
+ else if (! strchr ((r = (CVSroot + strlen (":pserver:"))), '@'))
+ goto not_fqrn;
+ else if (! strchr (r, ':'))
+ goto not_fqrn;
+
+ if (0) /* Lovely. */
+ {
+ not_fqrn:
+ error (0, 0, "CVSroot not fully-qualified: %s", CVSroot);
+ error (1, 0, "should be format user@host:/path/to/repository");
+ }
+ }
+
+ /* CVSroot is now fully qualified and has ":pserver:" prepended.
+ We'll print out most of it so user knows exactly what is being
+ dealt with here. */
+ {
+ char *s;
+ s = strchr (CVSroot, ':');
+ s++;
+ s = strchr (s, ':');
+ s++;
+
+ if (s == NULL)
+ error (1, 0, "NULL CVSroot");
+
+ printf ("(Logging in to %s)\n", s);
+ fflush (stdout);
+ }
+
+ passfile = construct_cvspass_filename ();
+ typed_password = getpass ("CVS password: ");
+ typed_password = scramble (typed_password);
+
+ /* Force get_cvs_password() to use this one (when the client
+ * confirms the new password with the server), instead of consulting
+ * the file. We make a new copy because cvs_password will get
+ * zeroed by connect_to_server().
+ */
+ cvs_password = xstrdup (typed_password);
+
+ if (connect_to_pserver (NULL, NULL, 1) == 0)
+ {
+ /* The password is wrong, according to the server. */
+ error (1, 0, "incorrect password");
+ }
+
+ /* IF we have a password for this "[user@]host:/path" already
+ * THEN
+ * IF it's the same as the password we read from the prompt
+ * THEN
+ * do nothing
+ * ELSE
+ * replace the old password with the new one
+ * ELSE
+ * append new entry to the end of the file.
+ */
+
+ root_len = strlen (CVSroot);
+
+ /* Yes, the method below reads the user's password file twice. It's
+ inefficient, but we're not talking about a gig of data here. */
+
+ fp = fopen (passfile, "r");
+ /* FIXME: should be printing a message if fp == NULL and not
+ existence_error (errno). */
+ if (fp != NULL)
+ {
+ /* Check each line to see if we have this entry already. */
+ while (getline (&linebuf, &linebuf_len, fp) >= 0)
+ {
+ if (strncmp (CVSroot, linebuf, root_len) == 0)
+ {
+ already_entered = 1;
+ break;
+ }
+ else
+ {
+ free (linebuf);
+ linebuf = (char *) NULL;
+ }
+ }
+ fclose (fp);
+ }
+
+ if (already_entered)
+ {
+ /* This user/host has a password in the file already. */
+
+ strtok (linebuf, " ");
+ found_password = strtok (NULL, "\n");
+ if (strcmp (found_password, typed_password))
+ {
+ /* typed_password and found_password don't match, so we'll
+ * have to update passfile. We replace the old password
+ * with the new one by writing a tmp file whose contents are
+ * exactly the same as passfile except that this one entry
+ * gets typed_password instead of found_password. Then we
+ * rename the tmp file on top of passfile.
+ */
+ char *tmp_name;
+ FILE *tmp_fp;
+
+ tmp_name = tmpnam (NULL);
+ if ((tmp_fp = fopen (tmp_name, "w")) == NULL)
+ {
+ error (1, errno, "unable to open temp file %s", tmp_name);
+ return 1;
+ }
+ chmod (tmp_name, 0600);
+
+ fp = fopen (passfile, "r");
+ if (fp == NULL)
+ {
+ error (1, errno, "unable to open %s", passfile);
+ return 1;
+ }
+ /* I'm not paranoid, they really ARE out to get me: */
+ chmod (passfile, 0600);
+
+ free (linebuf);
+ linebuf = (char *) NULL;
+ while (getline (&linebuf, &linebuf_len, fp) >= 0)
+ {
+ if (strncmp (CVSroot, linebuf, root_len))
+ fprintf (tmp_fp, "%s", linebuf);
+ else
+ fprintf (tmp_fp, "%s %s\n", CVSroot, typed_password);
+
+ free (linebuf);
+ linebuf = (char *) NULL;
+ }
+ fclose (tmp_fp);
+ fclose (fp);
+ rename_file (tmp_name, passfile);
+ chmod (passfile, 0600);
+ }
+ }
+ else
+ {
+ if ((fp = fopen (passfile, "a")) == NULL)
+ {
+ error (1, errno, "could not open %s", passfile);
+ free (passfile);
+ return 1;
+ }
+
+ fprintf (fp, "%s %s\n", CVSroot, typed_password);
+ fclose (fp);
+ }
+
+ /* Utter, total, raving paranoia, I know. */
+ chmod (passfile, 0600);
+ memset (typed_password, 0, strlen (typed_password));
+ free (typed_password);
+
+ free (passfile);
+ free (cvs_password);
+ cvs_password = NULL;
+ return 0;
+}
+
+/* todo: "cvs logout" could erase an entry from the file.
+ * But to what purpose?
+ */
+
+/* Returns the _scrambled_ password. The server must descramble
+ before hashing and comparing. */
+char *
+get_cvs_password ()
+{
+ int found_it = 0;
+ int root_len;
+ char *password;
+ char *linebuf = (char *) NULL;
+ size_t linebuf_len;
+ FILE *fp;
+ char *passfile;
+
+ /* If someone (i.e., login()) is calling connect_to_pserver() out of
+ context, then assume they have supplied the correct, scrambled
+ password. */
+ if (cvs_password)
+ return cvs_password;
+
+ /* Environment should override file. */
+ if ((password = getenv ("CVS_PASSWORD")) != NULL)
+ {
+ char *p;
+ p = xstrdup (password);
+ /* If we got it from the environment, then it wasn't properly
+ scrambled. Since unscrambling is done on the server side, we
+ need to transmit it scrambled. */
+ p = scramble (p);
+ return p;
+ }
+
+ /* Else get it from the file. */
+ passfile = construct_cvspass_filename ();
+ fp = fopen (passfile, "r");
+ if (fp == NULL)
+ {
+ error (0, errno, "could not open %s", passfile);
+ free (passfile);
+ error (1, 0, "use \"cvs login\" to log in first");
+ }
+
+ root_len = strlen (CVSroot);
+
+ /* Check each line to see if we have this entry already. */
+ while (getline (&linebuf, &linebuf_len, fp) >= 0)
+ {
+ if (strncmp (CVSroot, linebuf, root_len) == 0)
+ {
+ /* This is it! So break out and deal with linebuf. */
+ found_it = 1;
+ break;
+ }
+ else
+ {
+ free (linebuf);
+ linebuf = (char *) NULL;
+ }
+ }
+
+ if (found_it)
+ {
+ /* linebuf now contains the line with the password. */
+ char *tmp;
+
+ strtok (linebuf, " ");
+ password = strtok (NULL, "\n");
+
+ /* Give it permanent storage. */
+ tmp = xmalloc (strlen (password) + 1);
+ strcpy (tmp, password);
+ tmp[strlen (password)] = '\0';
+ memset (password, 0, strlen (password));
+ free (linebuf);
+ return tmp;
+ }
+ else
+ {
+ error (0, 0, "cannot find password");
+ error (0, 0, "use \"cvs login\" to log in first");
+ error (1, 0, "or set the CVS_PASSWORD environment variable");
+ }
+ /* NOTREACHED */
+ return NULL;
+}
+
+#endif /* AUTH_CLIENT_SUPPORT from beginning of file. */
+
diff --git a/contrib/cvs/src/logmsg.c b/contrib/cvs/src/logmsg.c
new file mode 100644
index 0000000..370ceab
--- /dev/null
+++ b/contrib/cvs/src/logmsg.c
@@ -0,0 +1,521 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ */
+
+#include "cvs.h"
+#include "getline.h"
+
+static int find_type PROTO((Node * p, void *closure));
+static int fmt_proc PROTO((Node * p, void *closure));
+static int logfile_write PROTO((char *repository, char *filter, char *title,
+ char *message, char *revision, FILE * logfp,
+ List * changes));
+static int rcsinfo_proc PROTO((char *repository, char *template));
+static int title_proc PROTO((Node * p, void *closure));
+static int update_logfile_proc PROTO((char *repository, char *filter));
+static void setup_tmpfile PROTO((FILE * xfp, char *xprefix, List * changes));
+static int editinfo_proc PROTO((char *repository, char *template));
+
+static FILE *fp;
+static char *str_list;
+static char *editinfo_editor;
+static Ctype type;
+
+/*
+ * Puts a standard header on the output which is either being prepared for an
+ * editor session, or being sent to a logfile program. The modified, added,
+ * and removed files are included (if any) and formatted to look pretty. */
+static char *prefix;
+static int col;
+static void
+setup_tmpfile (xfp, xprefix, changes)
+ FILE *xfp;
+ char *xprefix;
+ List *changes;
+{
+ /* set up statics */
+ fp = xfp;
+ prefix = xprefix;
+
+ type = T_MODIFIED;
+ if (walklist (changes, find_type, NULL) != 0)
+ {
+ (void) fprintf (fp, "%sModified Files:\n", prefix);
+ (void) fprintf (fp, "%s\t", prefix);
+ col = 8;
+ (void) walklist (changes, fmt_proc, NULL);
+ (void) fprintf (fp, "\n");
+ }
+ type = T_ADDED;
+ if (walklist (changes, find_type, NULL) != 0)
+ {
+ (void) fprintf (fp, "%sAdded Files:\n", prefix);
+ (void) fprintf (fp, "%s\t", prefix);
+ col = 8;
+ (void) walklist (changes, fmt_proc, NULL);
+ (void) fprintf (fp, "\n");
+ }
+ type = T_REMOVED;
+ if (walklist (changes, find_type, NULL) != 0)
+ {
+ (void) fprintf (fp, "%sRemoved Files:\n", prefix);
+ (void) fprintf (fp, "%s\t", prefix);
+ col = 8;
+ (void) walklist (changes, fmt_proc, NULL);
+ (void) fprintf (fp, "\n");
+ }
+}
+
+/*
+ * Looks for nodes of a specified type and returns 1 if found
+ */
+static int
+find_type (p, closure)
+ Node *p;
+ void *closure;
+{
+ if (p->data == (char *) type)
+ return (1);
+ else
+ return (0);
+}
+
+/*
+ * Breaks the files list into reasonable sized lines to avoid line wrap...
+ * all in the name of pretty output. It only works on nodes whose types
+ * match the one we're looking for
+ */
+static int
+fmt_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ if (p->data == (char *) type)
+ {
+ if ((col + (int) strlen (p->key)) > 70)
+ {
+ (void) fprintf (fp, "\n%s\t", prefix);
+ col = 8;
+ }
+ (void) fprintf (fp, "%s ", p->key);
+ col += strlen (p->key) + 1;
+ }
+ return (0);
+}
+
+/*
+ * Builds a temporary file using setup_tmpfile() and invokes the user's
+ * editor on the file. The header garbage in the resultant file is then
+ * stripped and the log message is stored in the "message" argument.
+ *
+ * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it
+ * is NULL, use the CVSADM_TEMPLATE file instead.
+ */
+void
+do_editor (dir, messagep, repository, changes)
+ char *dir;
+ char **messagep;
+ char *repository;
+ List *changes;
+{
+ static int reuse_log_message = 0;
+ char *line;
+ int line_length;
+ size_t line_chars_allocated;
+ char fname[L_tmpnam+1];
+ struct stat pre_stbuf, post_stbuf;
+ int retcode = 0;
+ char *p;
+
+ if (noexec || reuse_log_message)
+ return;
+
+ /* Abort creation of temp file if no editor is defined */
+ if (strcmp (Editor, "") == 0 && !editinfo_editor)
+ error(1, 0, "no editor defined, must use -e or -m");
+
+ /* Create a temporary file */
+ (void) tmpnam (fname);
+ again:
+ if ((fp = fopen (fname, "w+")) == NULL)
+ error (1, 0, "cannot create temporary file %s", fname);
+
+ if (*messagep)
+ {
+ (void) fprintf (fp, "%s", *messagep);
+
+ if ((*messagep)[strlen (*messagep) - 1] != '\n')
+ (void) fprintf (fp, "\n");
+ }
+ else
+ (void) fprintf (fp, "\n");
+
+ if (repository != NULL)
+ /* tack templates on if necessary */
+ (void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc, 1);
+ else
+ {
+ FILE *tfp;
+ char buf[1024];
+ char *p;
+ size_t n;
+ size_t nwrite;
+
+ /* Why "b"? */
+ tfp = fopen (CVSADM_TEMPLATE, "rb");
+ if (tfp == NULL)
+ {
+ if (!existence_error (errno))
+ error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
+ }
+ else
+ {
+ while (!feof (tfp))
+ {
+ n = fread (buf, 1, sizeof buf, tfp);
+ nwrite = n;
+ p = buf;
+ while (nwrite > 0)
+ {
+ n = fwrite (p, 1, nwrite, fp);
+ nwrite -= n;
+ p += n;
+ }
+ if (ferror (tfp))
+ error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
+ }
+ if (fclose (tfp) < 0)
+ error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
+ }
+ }
+
+ (void) fprintf (fp,
+ "%s----------------------------------------------------------------------\n",
+ CVSEDITPREFIX);
+ (void) fprintf (fp,
+ "%sEnter Log. Lines beginning with `%s' are removed automatically\n%s\n",
+ CVSEDITPREFIX, CVSEDITPREFIX, CVSEDITPREFIX);
+ if (dir != NULL && *dir)
+ (void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
+ dir, CVSEDITPREFIX);
+ if (changes != NULL)
+ setup_tmpfile (fp, CVSEDITPREFIX, changes);
+ (void) fprintf (fp,
+ "%s----------------------------------------------------------------------\n",
+ CVSEDITPREFIX);
+
+ /* finish off the temp file */
+ if (fclose (fp) == EOF)
+ error (1, errno, "%s", fname);
+ if (stat (fname, &pre_stbuf) == -1)
+ pre_stbuf.st_mtime = 0;
+
+ if (editinfo_editor)
+ free (editinfo_editor);
+ editinfo_editor = (char *) NULL;
+ if (repository != NULL)
+ (void) Parse_Info (CVSROOTADM_EDITINFO, repository, editinfo_proc, 0);
+
+ /* run the editor */
+ run_setup ("%s", editinfo_editor ? editinfo_editor : Editor);
+ run_arg (fname);
+ if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
+ RUN_NORMAL | RUN_SIGIGNORE)) != 0)
+ error (editinfo_editor ? 1 : 0, retcode == -1 ? errno : 0,
+ editinfo_editor ? "Logfile verification failed" :
+ "warning: editor session failed");
+
+ /* put the entire message back into the *messagep variable */
+
+ fp = open_file (fname, "r");
+
+ if (*messagep)
+ free (*messagep);
+
+ if (stat (fname, &post_stbuf) != 0)
+ error (1, errno, "cannot find size of temp file %s", fname);
+
+ if (post_stbuf.st_size == 0)
+ *messagep = NULL;
+ else
+ {
+ /* On NT, we might read less than st_size bytes, but we won't
+ read more. So this works. */
+ *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
+ *messagep[0] = '\0';
+ }
+
+ line = NULL;
+ line_chars_allocated = 0;
+
+ if (*messagep)
+ {
+ p = *messagep;
+ while (1)
+ {
+ line_length = getline (&line, &line_chars_allocated, fp);
+ if (line_length == -1)
+ {
+ if (ferror (fp))
+ error (0, errno, "warning: cannot read %s", fname);
+ break;
+ }
+ if (strncmp (line, CVSEDITPREFIX, sizeof (CVSEDITPREFIX) - 1) == 0)
+ continue;
+ (void) strcpy (p, line);
+ p += line_length;
+ }
+ }
+ if (fclose (fp) < 0)
+ error (0, errno, "warning: cannot close %s", fname);
+
+ if (pre_stbuf.st_mtime == post_stbuf.st_mtime ||
+ *messagep == NULL ||
+ strcmp (*messagep, "\n") == 0)
+ {
+ for (;;)
+ {
+ (void) printf ("\nLog message unchanged or not specified\n");
+ (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n");
+ (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')
+ break;
+ if (*line == 'a' || *line == 'A')
+ error (1, 0, "aborted by user");
+ if (*line == 'e' || *line == 'E')
+ goto again;
+ if (*line == '!')
+ {
+ reuse_log_message = 1;
+ break;
+ }
+ (void) printf ("Unknown input\n");
+ }
+ }
+ if (line)
+ free (line);
+ if (unlink_file (fname) < 0)
+ error (0, errno, "warning: cannot remove temp file %s", fname);
+}
+
+/*
+ * callback proc for Parse_Info for rcsinfo templates this routine basically
+ * copies the matching template onto the end of the tempfile we are setting
+ * up
+ */
+/* ARGSUSED */
+static int
+rcsinfo_proc (repository, template)
+ char *repository;
+ char *template;
+{
+ static char *last_template;
+ FILE *tfp;
+
+ /* nothing to do if the last one included is the same as this one */
+ if (last_template && strcmp (last_template, template) == 0)
+ return (0);
+ if (last_template)
+ free (last_template);
+ last_template = xstrdup (template);
+
+ if ((tfp = fopen (template, "r")) != NULL)
+ {
+ char *line = NULL;
+ size_t line_chars_allocated = 0;
+
+ while (getline (&line, &line_chars_allocated, tfp) >= 0)
+ (void) fputs (line, fp);
+ if (ferror (tfp))
+ error (0, errno, "warning: cannot read %s", template);
+ if (fclose (tfp) < 0)
+ error (0, errno, "warning: cannot close %s", template);
+ if (line)
+ free (line);
+ return (0);
+ }
+ else
+ {
+ error (0, errno, "Couldn't open rcsinfo template file %s", template);
+ return (1);
+ }
+}
+
+/*
+ * Uses setup_tmpfile() to pass the updated message on directly to any
+ * logfile programs that have a regular expression match for the checked in
+ * directory in the source repository. The log information is fed into the
+ * specified program as standard input.
+ */
+static char *title;
+static FILE *logfp;
+static char *message;
+static char *revision;
+static List *changes;
+
+void
+Update_Logfile (repository, xmessage, xrevision, xlogfp, xchanges)
+ char *repository;
+ char *xmessage;
+ char *xrevision;
+ FILE *xlogfp;
+ List *xchanges;
+{
+ char *srepos;
+
+ /* nothing to do if the list is empty */
+ if (xchanges == NULL || xchanges->list->next == xchanges->list)
+ return;
+
+ /* set up static vars for update_logfile_proc */
+ message = xmessage;
+ revision = xrevision;
+ logfp = xlogfp;
+ changes = xchanges;
+
+ /* figure out a good title string */
+ srepos = Short_Repository (repository);
+
+ /* allocate a chunk of memory to hold the title string */
+ if (!str_list)
+ str_list = xmalloc (MAXLISTLEN);
+ str_list[0] = '\0';
+
+ type = T_TITLE;
+ (void) walklist (changes, title_proc, NULL);
+ type = T_ADDED;
+ (void) walklist (changes, title_proc, NULL);
+ type = T_MODIFIED;
+ (void) walklist (changes, title_proc, NULL);
+ type = T_REMOVED;
+ (void) walklist (changes, title_proc, NULL);
+ title = xmalloc (strlen (srepos) + strlen (str_list) + 1 + 2); /* for 's */
+ (void) sprintf (title, "'%s%s'", srepos, str_list);
+
+ /* to be nice, free up this chunk of memory */
+ free (str_list);
+ str_list = (char *) NULL;
+
+ /* call Parse_Info to do the actual logfile updates */
+ (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc, 1);
+
+ /* clean up */
+ free (title);
+}
+
+/*
+ * callback proc to actually do the logfile write from Update_Logfile
+ */
+static int
+update_logfile_proc (repository, filter)
+ char *repository;
+ char *filter;
+{
+ return (logfile_write (repository, filter, title, message, revision,
+ logfp, changes));
+}
+
+/*
+ * concatenate each name onto str_list
+ */
+static int
+title_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ if (p->data == (char *) type)
+ {
+ (void) strcat (str_list, " ");
+ (void) strcat (str_list, p->key);
+ }
+ return (0);
+}
+
+/*
+ * Since some systems don't define this...
+ */
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 256
+#endif
+
+/*
+ * Writes some stuff to the logfile "filter" and returns the status of the
+ * filter program.
+ */
+static int
+logfile_write (repository, filter, title, message, revision, logfp, changes)
+ char *repository;
+ char *filter;
+ char *title;
+ char *message;
+ char *revision;
+ FILE *logfp;
+ List *changes;
+{
+ char cwd[PATH_MAX];
+ FILE *pipefp;
+ char *prog = xmalloc (MAXPROGLEN);
+ char *cp;
+ int c;
+ int pipestatus;
+
+ /*
+ * Only 1 %s argument is supported in the filter
+ */
+ (void) sprintf (prog, filter, title);
+ if ((pipefp = run_popen (prog, "w")) == NULL)
+ {
+ if (!noexec)
+ error (0, 0, "cannot write entry to log filter: %s", prog);
+ free (prog);
+ return (1);
+ }
+ (void) fprintf (pipefp, "Update of %s\n", repository);
+ (void) fprintf (pipefp, "In directory %s:%s\n\n", hostname,
+ ((cp = getwd (cwd)) != NULL) ? cp : cwd);
+ if (revision && *revision)
+ (void) fprintf (pipefp, "Revision/Branch: %s\n\n", revision);
+ setup_tmpfile (pipefp, "", changes);
+ (void) fprintf (pipefp, "Log Message:\n%s\n", message);
+ if (logfp != (FILE *) 0)
+ {
+ (void) fprintf (pipefp, "Status:\n");
+ rewind (logfp);
+ while ((c = getc (logfp)) != EOF)
+ (void) putc ((char) c, pipefp);
+ }
+ free (prog);
+ pipestatus = pclose (pipefp);
+ return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
+}
+
+/*
+ * We choose to use the *last* match within the editinfo file for this
+ * repository. This allows us to have a global editinfo program for the
+ * root of some hierarchy, for example, and different ones within different
+ * sub-directories of the root (like a special checker for changes made to
+ * the "src" directory versus changes made to the "doc" or "test"
+ * directories.
+ */
+/* ARGSUSED */
+static int
+editinfo_proc(repository, editor)
+ char *repository;
+ char *editor;
+{
+ /* nothing to do if the last match is the same as this one */
+ if (editinfo_editor && strcmp (editinfo_editor, editor) == 0)
+ return (0);
+ if (editinfo_editor)
+ free (editinfo_editor);
+
+ editinfo_editor = xstrdup (editor);
+ return (0);
+}
diff --git a/contrib/cvs/src/main.c b/contrib/cvs/src/main.c
new file mode 100644
index 0000000..daa0230
--- /dev/null
+++ b/contrib/cvs/src/main.c
@@ -0,0 +1,814 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * This is the main C driver for the CVS system.
+ *
+ * Credit to Dick Grune, Vrije Universiteit, Amsterdam, for writing
+ * the shell-script CVS system that this is based on.
+ *
+ * Usage:
+ * cvs [options] command [options] [files/modules...]
+ *
+ * Where "command" is composed of:
+ * admin RCS command
+ * checkout Check out a module/dir/file
+ * export Like checkout, but used for exporting sources
+ * update Brings work tree in sync with repository
+ * commit Checks files into the repository
+ * diff Runs diffs between revisions
+ * log Prints "rlog" information for files
+ * login Record user, host, repos, password
+ * add Adds an entry to the repository
+ * remove Removes an entry from the repository
+ * status Status info on the revisions
+ * rdiff "patch" format diff listing between releases
+ * tag Add/delete a symbolic tag to the RCS file
+ * rtag Add/delete a symbolic tag to the RCS file
+ * import Import sources into CVS, using vendor branches
+ * release Indicate that Module is no longer in use.
+ * history Display history of Users and Modules.
+ */
+
+#include "cvs.h"
+
+#ifdef HAVE_WINSOCK_H
+#include <winsock.h>
+#else
+extern int gethostname ();
+#endif
+
+#if HAVE_KERBEROS
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <krb.h>
+#ifndef HAVE_KRB_GET_ERR_TEXT
+#define krb_get_err_text(status) krb_err_txt[status]
+#endif
+#endif
+
+char *program_name;
+char *program_path;
+/*
+ * Initialize comamnd_name to "cvs" so that the first call to
+ * read_cvsrc tries to find global cvs options.
+ */
+char *command_name = "";
+
+/*
+ * Since some systems don't define this...
+ */
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 256
+#endif
+
+char hostname[MAXHOSTNAMELEN];
+
+#ifdef AUTH_CLIENT_SUPPORT
+int use_authenticating_server = FALSE;
+#endif /* AUTH_CLIENT_SUPPORT */
+int use_editor = TRUE;
+int use_cvsrc = TRUE;
+int cvswrite = !CVSREAD_DFLT;
+int really_quiet = FALSE;
+int quiet = FALSE;
+int trace = FALSE;
+int noexec = FALSE;
+int logoff = FALSE;
+mode_t cvsumask = UMASK_DFLT;
+
+char *CurDir;
+
+/*
+ * Defaults, for the environment variables that are not set
+ */
+char *Rcsbin = RCSBIN_DFLT;
+char *Editor = EDITOR_DFLT;
+char *CVSroot = CVSROOT_DFLT;
+/*
+ * The path found in CVS/Root must match $CVSROOT and/or 'cvs -d root'
+ */
+char *CVSADM_Root = CVSROOT_DFLT;
+
+int add PROTO((int argc, char **argv));
+int admin PROTO((int argc, char **argv));
+int checkout PROTO((int argc, char **argv));
+int commit PROTO((int argc, char **argv));
+int diff PROTO((int argc, char **argv));
+int history PROTO((int argc, char **argv));
+int import PROTO((int argc, char **argv));
+int cvslog PROTO((int argc, char **argv));
+#ifdef AUTH_CLIENT_SUPPORT
+int login PROTO((int argc, char **argv));
+#endif /* AUTH_CLIENT_SUPPORT */
+int patch PROTO((int argc, char **argv));
+int release PROTO((int argc, char **argv));
+int cvsremove PROTO((int argc, char **argv));
+int rtag PROTO((int argc, char **argv));
+int status PROTO((int argc, char **argv));
+int tag PROTO((int argc, char **argv));
+int update PROTO((int argc, char **argv));
+
+const struct cmd
+{
+ char *fullname; /* Full name of the function (e.g. "commit") */
+ char *nick1; /* alternate name (e.g. "ci") */
+ char *nick2; /* another alternate names (e.g. "ci") */
+ int (*func) (); /* Function takes (argc, argv) arguments. */
+#ifdef CLIENT_SUPPORT
+ int (*client_func) (); /* Function to do it via the protocol. */
+#endif
+} cmds[] =
+
+{
+#ifdef CLIENT_SUPPORT
+#define CMD_ENTRY(n1, n2, n3, f1, f2) { n1, n2, n3, f1, f2 }
+#else
+#define CMD_ENTRY(n1, n2, n3, f1, f2) { n1, n2, n3, f1 }
+#endif
+
+ CMD_ENTRY("add", "ad", "new", add, client_add),
+ CMD_ENTRY("admin", "adm", "rcs", admin, client_admin),
+ CMD_ENTRY("annotate", NULL, NULL, annotate, client_annotate),
+ CMD_ENTRY("checkout", "co", "get", checkout, client_checkout),
+ CMD_ENTRY("commit", "ci", "com", commit, client_commit),
+ CMD_ENTRY("diff", "di", "dif", diff, client_diff),
+ CMD_ENTRY("edit", "edit", "edit", edit, client_edit),
+ CMD_ENTRY("editors", "editors","editors",editors, client_editors),
+ CMD_ENTRY("export", "exp", "ex", checkout, client_export),
+ CMD_ENTRY("history", "hi", "his", history, client_history),
+ CMD_ENTRY("import", "im", "imp", import, client_import),
+ CMD_ENTRY("init", NULL, NULL, init, client_init),
+ CMD_ENTRY("log", "lo", "rlog", cvslog, client_log),
+#ifdef AUTH_CLIENT_SUPPORT
+ CMD_ENTRY("login", "logon", "lgn", login, login),
+#endif /* AUTH_CLIENT_SUPPORT */
+ CMD_ENTRY("rdiff", "patch", "pa", patch, client_rdiff),
+ CMD_ENTRY("release", "re", "rel", release, client_release),
+ CMD_ENTRY("remove", "rm", "delete", cvsremove, client_remove),
+ CMD_ENTRY("status", "st", "stat", status, client_status),
+ CMD_ENTRY("rtag", "rt", "rfreeze", rtag, client_rtag),
+ CMD_ENTRY("tag", "ta", "freeze", tag, client_tag),
+ CMD_ENTRY("unedit", "unedit","unedit", unedit, client_unedit),
+ CMD_ENTRY("update", "up", "upd", update, client_update),
+ CMD_ENTRY("watch", "watch", "watch", watch, client_watch),
+ CMD_ENTRY("watchers", "watchers","watchers",watchers,client_watchers),
+#ifdef SERVER_SUPPORT
+ /*
+ * The client_func is also server because we might have picked up a
+ * CVSROOT environment variable containing a colon. The client will send
+ * the real root later.
+ */
+ CMD_ENTRY("server", "server", "server", server, server),
+#endif
+ CMD_ENTRY(NULL, NULL, NULL, NULL, NULL),
+
+#undef CMD_ENTRY
+};
+
+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",
+ " -t Show trace of program execution -- Try with -n\n",
+ " -v CVS version and copyright\n",
+ " -b bindir Find RCS programs in 'bindir'\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 'gzip -#' for net traffic if possible.\n",
+#endif
+ " -s VAR=VAL Set CVS user variable.\n",
+ "\n",
+ " and where 'command' is: add, admin, etc. (use the --help-commands\n",
+ " option for a list of commands)\n",
+ NULL,
+};
+
+static const char *const cmd_usage[] =
+{
+ "CVS commands are:\n",
+ " add Adds a new file/directory to the repository\n",
+ " admin Administration front end for rcs\n",
+ " annotate Show revision where each line was modified\n",
+ " checkout Checkout sources for editing\n",
+ " commit Checks files into the repository\n",
+ " diff Runs diffs between revisions\n",
+ " edit Get ready to edit a watched file\n",
+ " editors See who is editing a watched file\n",
+ " history Shows status of files and users\n",
+ " import Import sources into CVS, using vendor branches\n",
+ " export Export sources from CVS, similar to checkout\n",
+ " log Prints out 'rlog' information for files\n",
+#ifdef AUTH_CLIENT_SUPPORT
+ " login Prompt for password for authenticating server.\n",
+#endif /* AUTH_CLIENT_SUPPORT */
+ " rdiff 'patch' format diffs between releases\n",
+ " release Indicate that a Module is no longer in use\n",
+ " remove Removes an entry from the repository\n",
+ " status Status info on the revisions\n",
+ " tag Add a symbolic tag to checked out version of RCS file\n",
+ " unedit Undo an edit command\n",
+ " rtag Add a symbolic tag to the RCS file\n",
+ " update Brings work tree in sync with repository\n",
+ " watch Set watches\n",
+ " watchers See who is watching a file\n",
+ NULL,
+};
+
+static RETSIGTYPE
+main_cleanup ()
+{
+ exit (EXIT_FAILURE);
+}
+
+static void
+error_cleanup PROTO((void))
+{
+ Lock_Cleanup();
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_cleanup (0);
+#endif
+}
+
+int
+main (argc, argv)
+ int argc;
+ char **argv;
+{
+ extern char *version_string;
+ extern char *config_string;
+ char *cp, *end;
+ const struct cmd *cm;
+ int c, err = 0;
+ static int help = FALSE;
+ static int version_flag = FALSE;
+ static int help_commands = FALSE;
+ int rcsbin_update_env, cvs_update_env = 0;
+ static struct option long_options[] =
+ {
+ {"help", 0, &help, TRUE},
+ {"version", 0, &version_flag, TRUE},
+ {"help-commands", 0, &help_commands, TRUE},
+ {0, 0, 0, 0}
+ };
+ /* `getopt_long' stores the option index here, but right now we
+ don't use it. */
+ int option_index = 0;
+
+ error_set_cleanup (error_cleanup);
+
+/* The socket subsystems on NT and OS2 must be initialized before use */
+#ifdef INITIALIZE_SOCKET_SUBSYSTEM
+ INITIALIZE_SOCKET_SUBSYSTEM();
+#endif /* INITIALIZE_SOCKET_SUBSYSTEM */
+
+ /*
+ * Just save the last component of the path for error messages
+ */
+ program_path = xstrdup (argv[0]);
+ program_name = last_component (argv[0]);
+
+ CurDir = xmalloc (PATH_MAX);
+#ifndef SERVER_SUPPORT
+ if (!getwd (CurDir))
+ error (1, 0, "cannot get working directory: %s", CurDir);
+#endif
+
+ /*
+ * Query the environment variables up-front, so that
+ * they can be overridden by command line arguments
+ */
+ rcsbin_update_env = *Rcsbin; /* RCSBIN_DFLT must be set */
+ cvs_update_env = 0;
+ if ((cp = getenv (RCSBIN_ENV)) != NULL)
+ {
+ Rcsbin = cp;
+ rcsbin_update_env = 0; /* it's already there */
+ }
+ if ((cp = getenv (EDITOR1_ENV)) != NULL)
+ Editor = cp;
+ else if ((cp = getenv (EDITOR2_ENV)) != NULL)
+ Editor = cp;
+ else if ((cp = getenv (EDITOR3_ENV)) != NULL)
+ Editor = cp;
+ if ((cp = getenv (CVSROOT_ENV)) != NULL)
+ {
+ CVSroot = cp;
+ cvs_update_env = 0; /* it's already there */
+ }
+ if (getenv (CVSREAD_ENV) != NULL)
+ cvswrite = FALSE;
+ if ((cp = getenv (CVSUMASK_ENV)) != NULL)
+ {
+ /* FIXME: Should be accepting symbolic as well as numeric mask. */
+ cvsumask = strtol (cp, &end, 8) & 0777;
+ if (*end != '\0')
+ error (1, errno, "invalid umask value in %s (%s)",
+ CVSUMASK_ENV, cp);
+ }
+
+ /* This has the effect of setting getopt's ordering to REQUIRE_ORDER,
+ which is what we need to distinguish between global options and
+ command options. FIXME: It would appear to be possible to do this
+ much less kludgily by passing "+" as the first character to the
+ option string we pass to getopt_long. */
+ optind = 1;
+
+
+ /* We have to parse the options twice because else there is no
+ chance to avoid reading the global options from ".cvsrc". Set
+ opterr to 0 for avoiding error messages about invalid options.
+ */
+ opterr = 0;
+
+ while ((c = getopt_long
+ (argc, argv, "f", NULL, NULL))
+ != EOF)
+ {
+ if (c == 'f')
+ use_cvsrc = FALSE;
+ }
+
+ /*
+ * Scan cvsrc file for global options.
+ */
+ if (use_cvsrc)
+ read_cvsrc (&argc, &argv, "cvs");
+
+ optind = 1;
+ opterr = 1;
+
+ while ((c = getopt_long
+ (argc, argv, "Qqrwtnlvb:e:d:Hfz:s:", long_options, &option_index))
+ != EOF)
+ {
+ switch (c)
+ {
+ case 0:
+ /* getopt_long took care of setting the flag. */
+ break;
+ case 'Q':
+ really_quiet = TRUE;
+ /* FALL THROUGH */
+ case 'q':
+ quiet = TRUE;
+ break;
+ case 'r':
+ cvswrite = FALSE;
+ break;
+ case 'w':
+ cvswrite = TRUE;
+ break;
+ case 't':
+ trace = TRUE;
+ break;
+ case 'n':
+ noexec = TRUE;
+ case 'l': /* Fall through */
+ logoff = TRUE;
+ break;
+ case 'v':
+ version_flag = TRUE;
+ break;
+ case 'b':
+ Rcsbin = optarg;
+ rcsbin_update_env = 1; /* need to update environment */
+ break;
+ case 'e':
+ Editor = optarg;
+ break;
+ case 'd':
+ CVSroot = optarg;
+ cvs_update_env = 1; /* need to update environment */
+ break;
+ case 'H':
+ use_cvsrc = FALSE; /* this ensure that cvs -H works */
+ help = TRUE;
+ break;
+ case 'f':
+ use_cvsrc = FALSE;
+ break;
+ case 'z':
+#ifdef CLIENT_SUPPORT
+ gzip_level = atoi (optarg);
+ if (gzip_level <= 0 || gzip_level > 9)
+ error (1, 0,
+ "gzip compression level must be between 1 and 9");
+#endif
+ /* If no CLIENT_SUPPORT, we just silently ignore the gzip
+ level, so that users can have it in their .cvsrc and not
+ cause any trouble. */
+ break;
+ case 's':
+ variable_set (optarg);
+ break;
+ case '?':
+ default:
+ usage (usg);
+ }
+ }
+
+ if (version_flag == TRUE)
+ {
+ (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 ("\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);
+ exit (0);
+ }
+ else if (help_commands)
+ usage (cmd_usage);
+
+ argc -= optind;
+ argv += optind;
+ if (argc < 1)
+ usage (usg);
+
+#ifdef HAVE_KERBEROS
+ /* If we are invoked with a single argument "kserver", then we are
+ running as Kerberos server as root. Do the authentication as
+ the very first thing, to minimize the amount of time we are
+ running as root. */
+ if (strcmp (argv[0], "kserver") == 0)
+ {
+ int status;
+ char instance[INST_SZ];
+ struct sockaddr_in peer;
+ struct sockaddr_in laddr;
+ int len;
+ KTEXT_ST ticket;
+ AUTH_DAT auth;
+ char version[KRB_SENDAUTH_VLEN];
+ Key_schedule sched;
+ char user[ANAME_SZ];
+ struct passwd *pw;
+
+ strcpy (instance, "*");
+ len = sizeof peer;
+ if (getpeername (STDIN_FILENO, (struct sockaddr *) &peer, &len) < 0
+ || getsockname (STDIN_FILENO, (struct sockaddr *) &laddr,
+ &len) < 0)
+ {
+ printf ("E Fatal error, aborting.\n\
+error %s getpeername or getsockname failed\n", strerror (errno));
+ exit (EXIT_FAILURE);
+ }
+
+ status = krb_recvauth (KOPT_DO_MUTUAL, STDIN_FILENO, &ticket, "rcmd",
+ instance, &peer, &laddr, &auth, "", sched,
+ version);
+ if (status != KSUCCESS)
+ {
+ printf ("E Fatal error, aborting.\n\
+error 0 kerberos: %s\n", krb_get_err_text(status));
+ exit (EXIT_FAILURE);
+ }
+
+ /* Get the local name. */
+ status = krb_kntoln (&auth, user);
+ if (status != KSUCCESS)
+ {
+ printf ("E Fatal error, aborting.\n\
+error 0 kerberos: can't get local name: %s\n", krb_get_err_text(status));
+ exit (EXIT_FAILURE);
+ }
+
+ pw = getpwnam (user);
+ if (pw == NULL)
+ {
+ printf ("E Fatal error, aborting.\n\
+error 0 %s: no such user\n", user);
+ exit (EXIT_FAILURE);
+ }
+
+ initgroups (pw->pw_name, pw->pw_gid);
+ setgid (pw->pw_gid);
+ setuid (pw->pw_uid);
+ /* Inhibit access by randoms. Don't want people randomly
+ changing our temporary tree before we check things in. */
+ umask (077);
+
+#if HAVE_PUTENV
+ /* Set LOGNAME and USER in the environment, in case they are
+ already set to something else. */
+ {
+ char *env;
+
+ env = xmalloc (sizeof "LOGNAME=" + strlen (user));
+ (void) sprintf (env, "LOGNAME=%s", user);
+ (void) putenv (env);
+
+ env = xmalloc (sizeof "USER=" + strlen (user));
+ (void) sprintf (env, "USER=%s", user);
+ (void) putenv (env);
+ }
+#endif
+
+ /* Pretend we were invoked as a plain server. */
+ argv[0] = "server";
+ }
+#endif /* HAVE_KERBEROS */
+
+
+#if defined(AUTH_SERVER_SUPPORT) && defined(SERVER_SUPPORT)
+ if (strcmp (argv[0], "pserver") == 0)
+ {
+ /* Gets username and password from client, authenticates, then
+ switches to run as that user and sends an ACK back to the
+ client. */
+ authenticate_connection ();
+
+ /* Pretend we were invoked as a plain server. */
+ argv[0] = "server";
+ }
+#endif /* AUTH_SERVER_SUPPORT && SERVER_SUPPORT */
+
+
+ /*
+ * See if we are able to find a 'better' value for CVSroot in the
+ * CVSADM_ROOT directory.
+ */
+#ifdef SERVER_SUPPORT
+ if (strcmp (argv[0], "server") == 0 && CVSroot == NULL)
+ CVSADM_Root = NULL;
+ else
+ CVSADM_Root = Name_Root((char *) NULL, (char *) NULL);
+#else /* No SERVER_SUPPORT */
+ CVSADM_Root = Name_Root((char *) NULL, (char *) NULL);
+#endif /* No SERVER_SUPPORT */
+ if (CVSADM_Root != NULL)
+ {
+ if (CVSroot == NULL || !cvs_update_env)
+ {
+ CVSroot = CVSADM_Root;
+ cvs_update_env = 1; /* need to update environment */
+ }
+#ifdef CLIENT_SUPPORT
+ else if (!getenv ("CVS_IGNORE_REMOTE_ROOT"))
+#else /* ! CLIENT_SUPPORT */
+ else
+#endif /* CLIENT_SUPPORT */
+ {
+ /*
+ * Now for the hard part, compare the two directories. If they
+ * are not identical, then abort this command.
+ */
+ if ((fncmp (CVSroot, CVSADM_Root) != 0) &&
+ !same_directories(CVSroot, CVSADM_Root))
+ {
+ error (0, 0, "%s value for CVS Root found in %s",
+ CVSADM_Root, CVSADM_ROOT);
+ error (0, 0, "does not match command line -d %s setting",
+ CVSroot);
+ error (1, 0,
+ "you may wish to try the cvs command again without the -d option ");
+ }
+ }
+ }
+
+ /* CVSroot may need fixing up, if an access-method was specified,
+ * but not a user. Later code assumes that if CVSroot contains an
+ * access-method, then it also has a user. We print a warning and
+ * die if we can't guarantee that.
+ */
+ if (CVSroot
+ && *CVSroot
+ && (CVSroot[0] == ':')
+ && (strchr (CVSroot, '@') == NULL))
+ {
+ error (1, 0,
+ "must also give a username if specifying access method");
+ }
+
+ /*
+ * Specifying just the '-H' flag to the sub-command causes a Usage
+ * message to be displayed.
+ */
+ command_name = cp = argv[0];
+ if (help == TRUE || (argc > 1 && strcmp (argv[1], "-H") == 0))
+ argc = -1;
+ else
+ {
+ /*
+ * Check to see if we can write into the history file. If not,
+ * we assume that we can't work in the repository.
+ * BUT, only if the history file exists.
+ */
+#ifdef SERVER_SUPPORT
+ if (strcmp (command_name, "server") != 0 || CVSroot != NULL)
+#endif
+ {
+ char path[PATH_MAX];
+ int save_errno;
+
+ if (!CVSroot || !*CVSroot)
+ error (1, 0, "You don't have a %s environment variable",
+ CVSROOT_ENV);
+ (void) sprintf (path, "%s/%s", CVSroot, CVSROOTADM);
+ if (!isaccessible (path, R_OK | X_OK))
+ {
+ save_errno = errno;
+ /* If this is "cvs init", the root need not exist yet. */
+ if (strcmp (command_name, "init") != 0
+#ifdef CLIENT_SUPPORT
+ /* If we are a remote client, the root need not exist
+ on the client machine (FIXME: we should also skip
+ the check for CVSROOTADM_HISTORY being writable;
+ it shouldn't matter if there is a read-only file
+ which happens to have the same name on the client
+ machine). */
+ && strchr (CVSroot, ':') == NULL)
+#endif
+ {
+ error (0, 0,
+ "Sorry, you don't have sufficient access to %s", CVSroot);
+ error (1, save_errno, "%s", path);
+ }
+ }
+ (void) strcat (path, "/");
+ (void) strcat (path, CVSROOTADM_HISTORY);
+ if (isfile (path) && !isaccessible (path, R_OK | W_OK))
+ {
+ save_errno = errno;
+ error (0, 0,
+ "Sorry, you don't have read/write access to the history file");
+ error (1, save_errno, "%s", path);
+ }
+ }
+ }
+
+#ifdef SERVER_SUPPORT
+ if (strcmp (command_name, "server") == 0)
+ /* This is only used for writing into the history file. Might
+ be nice to have hostname and/or remote path, on the other hand
+ I'm not sure whether it is worth the trouble. */
+ strcpy (CurDir, "<remote>");
+ else if (!getwd (CurDir))
+ error (1, 0, "cannot get working directory: %s", CurDir);
+#endif
+
+#ifdef HAVE_PUTENV
+ /* Now, see if we should update the environment with the Rcsbin value */
+ if (cvs_update_env)
+ {
+ char *env;
+
+ env = xmalloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1);
+ (void) sprintf (env, "%s=%s", CVSROOT_ENV, CVSroot);
+ (void) putenv (env);
+ /* do not free env, as putenv has control of it */
+ }
+ 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 */
+ }
+#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, "/");
+ }
+ }
+
+ for (cm = cmds; cm->fullname; cm++)
+ {
+ if (cm->nick1 && !strcmp (cp, cm->nick1))
+ break;
+ if (cm->nick2 && !strcmp (cp, cm->nick2))
+ break;
+ if (!strcmp (cp, cm->fullname))
+ break;
+ }
+
+ if (!cm->fullname)
+ usage (usg); /* no match */
+ else
+ {
+ command_name = cm->fullname; /* Global pointer for later use */
+
+ /* make sure we clean up on error */
+#ifdef SIGHUP
+ (void) SIG_register (SIGHUP, main_cleanup);
+ (void) SIG_register (SIGHUP, Lock_Cleanup);
+#endif
+#ifdef SIGINT
+ (void) SIG_register (SIGINT, main_cleanup);
+ (void) SIG_register (SIGINT, Lock_Cleanup);
+#endif
+#ifdef SIGQUIT
+ (void) SIG_register (SIGQUIT, main_cleanup);
+ (void) SIG_register (SIGQUIT, Lock_Cleanup);
+#endif
+#ifdef SIGPIPE
+ (void) SIG_register (SIGPIPE, main_cleanup);
+ (void) SIG_register (SIGPIPE, Lock_Cleanup);
+#endif
+#ifdef SIGTERM
+ (void) SIG_register (SIGTERM, main_cleanup);
+ (void) SIG_register (SIGTERM, Lock_Cleanup);
+#endif
+
+ gethostname(hostname, sizeof (hostname));
+
+#ifdef HAVE_SETVBUF
+ /*
+ * Make stdout line buffered, so 'tail -f' can monitor progress.
+ * Patch creates too much output to monitor and it runs slowly.
+ */
+ if (strcmp (cm->fullname, "patch"))
+ (void) setvbuf (stdout, (char *) NULL, _IOLBF, 0);
+#endif
+
+ if (use_cvsrc)
+ read_cvsrc (&argc, &argv, command_name);
+
+#ifdef CLIENT_SUPPORT
+ /* If cvsroot contains a colon, try to do it via the protocol. */
+ {
+ char *p = CVSroot == NULL ? NULL : strchr (CVSroot, ':');
+ if (p)
+ err = (*(cm->client_func)) (argc, argv);
+ else
+ err = (*(cm->func)) (argc, argv);
+ }
+#else /* No CLIENT_SUPPORT */
+ err = (*(cm->func)) (argc, argv);
+
+#endif /* No CLIENT_SUPPORT */
+ }
+ Lock_Cleanup ();
+ if (err)
+ return (EXIT_FAILURE);
+ return 0;
+}
+
+char *
+Make_Date (rawdate)
+ char *rawdate;
+{
+ struct tm *ftm;
+ time_t unixtime;
+ char date[256]; /* XXX bigger than we'll ever need? */
+ char *ret;
+
+ 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
+ (void) sprintf (date, 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);
+ ret = xstrdup (date);
+ return (ret);
+}
+
+void
+usage (cpp)
+ register const char *const *cpp;
+{
+ (void) fprintf (stderr, *cpp++, program_name, command_name);
+ for (; *cpp; cpp++)
+ (void) fprintf (stderr, *cpp);
+ exit (EXIT_FAILURE);
+}
diff --git a/contrib/cvs/src/mkmodules.c b/contrib/cvs/src/mkmodules.c
new file mode 100644
index 0000000..bdd27eb
--- /dev/null
+++ b/contrib/cvs/src/mkmodules.c
@@ -0,0 +1,742 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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 kit. */
+
+#include "cvs.h"
+#include "savecwd.h"
+
+#ifndef DBLKSIZ
+#define DBLKSIZ 4096 /* since GNU ndbm doesn't define it */
+#endif
+
+static int checkout_file PROTO((char *file, char *temp));
+static void make_tempfile PROTO((char *temp));
+static void rename_rcsfile PROTO((char *temp, char *real));
+
+#ifndef MY_NDBM
+static void rename_dbmfile PROTO((char *temp));
+static void write_dbmfile PROTO((char *temp));
+#endif /* !MY_NDBM */
+
+/* Structure which describes an administrative file. */
+struct admin_file {
+ /* Name of the file, within the CVSROOT directory. */
+ char *filename;
+
+ /* This is a one line description of what the file is for. It is not
+ currently used, although one wonders whether it should be, somehow.
+ If NULL, then don't process this file in mkmodules (FIXME: a bit of
+ a kludge; probably should replace this with a flags field). */
+ char *errormsg;
+
+ /* Contents which the file should have in a new repository. To avoid
+ problems with brain-dead compilers which choke on long string constants,
+ this is a pointer to an array of char * terminated by NULL--each of
+ the strings is concatenated. */
+ const char * const *contents;
+};
+
+static const char *const loginfo_contents[] = {
+ "# The \"loginfo\" file is used to control where \"cvs commit\" log information\n",
+ "# is sent. The first entry on a line is a regular expression which is tested\n",
+ "# against the directory that the change is being made to, relative to the\n",
+ "# $CVSROOT. For the first match that is found, then the remainder of the\n",
+ "# line is a filter program that should expect log information on its standard\n",
+ "# input.\n",
+ "#\n",
+ "# If the repository name does not match any of the regular expressions in the\n",
+ "# first field of this file, the \"DEFAULT\" line is used, if it is specified.\n",
+ "#\n",
+ "# If the name \"ALL\" appears as a regular expression it is always used\n",
+ "# in addition to the first matching regex or \"DEFAULT\".\n",
+ "#\n",
+ "# The filter program may use one and only one \"%s\" modifier (ala printf). If\n",
+ "# such a \"%s\" is specified in the filter program, a brief title is included\n",
+ "# (as one argument, enclosed in single quotes) showing the relative directory\n",
+ "# name and listing the modified file names.\n",
+ "#\n",
+ "# For example:\n",
+ "#DEFAULT (echo \"\"; who am i; date; cat) >> $CVSROOT/CVSROOT/commitlog\n",
+ NULL
+};
+
+static const char *const rcsinfo_contents[] = {
+ "# The \"rcsinfo\" file is used to control templates with which the editor\n",
+ "# is invoked on commit and import.\n",
+ "#\n",
+ "# The first entry on a line is a regular expression which is tested\n",
+ "# against the directory that the change is being made to, relative to the\n",
+ "# $CVSROOT. For the first match that is found, then the remainder of the\n",
+ "# line is the name of the file that contains the template.\n",
+ "#\n",
+ "# If the repository name does not match any of the regular expressions in this\n",
+ "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+ "#\n",
+ "# If the name \"ALL\" appears as a regular expression it is always used\n",
+ "# in addition to the first matching regex or \"DEFAULT\".\n",
+ NULL
+};
+
+static const char *const editinfo_contents[] = {
+ "# The \"editinfo\" file is used to allow verification of logging\n",
+ "# information. It works best when a template (as specified in the\n",
+ "# rcsinfo file) is provided for the logging procedure. Given a\n",
+ "# template with locations for, a bug-id number, a list of people who\n",
+ "# reviewed the code before it can be checked in, and an external\n",
+ "# process to catalog the differences that were code reviewed, the\n",
+ "# following test can be applied to the code:\n",
+ "#\n",
+ "# Making sure that the entered bug-id number is correct.\n",
+ "# Validating that the code that was reviewed is indeed the code being\n",
+ "# checked in (using the bug-id number or a seperate review\n",
+ "# number to identify this particular code set.).\n",
+ "#\n",
+ "# If any of the above test failed, then the commit would be aborted.\n",
+ "#\n",
+ "# Actions such as mailing a copy of the report to each reviewer are\n",
+ "# better handled by an entry in the loginfo file.\n",
+ "#\n",
+ "# One thing that should be noted is the the ALL keyword is not\n",
+ "# supported. There can be only one entry that matches a given\n",
+ "# repository.\n",
+ NULL
+};
+
+static const char *const commitinfo_contents[] = {
+ "# The \"commitinfo\" file is used to control pre-commit checks.\n",
+ "# The filter on the right is invoked with the repository and a list \n",
+ "# of files to check. A non-zero exit of the filter program will \n",
+ "# cause the commit to be aborted.\n",
+ "#\n",
+ "# The first entry on a line is a regular expression which is tested\n",
+ "# against the directory that the change is being committed to, relative\n",
+ "# to the $CVSROOT. For the first match that is found, then the remainder\n",
+ "# of the line is the name of the filter to run.\n",
+ "#\n",
+ "# If the repository name does not match any of the regular expressions in this\n",
+ "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+ "#\n",
+ "# If the name \"ALL\" appears as a regular expression it is always used\n",
+ "# in addition to the first matching regex or \"DEFAULT\".\n",
+ NULL
+};
+
+static const char *const taginfo_contents[] = {
+ "# The \"taginfo\" file is used to control pre-tag checks.\n",
+ "# The filter on the right is invoked with the following arguments:\n",
+ "#\n",
+ "# $1 -- tagname\n",
+ "# $2 -- operation \"add\" for tag, \"mov\" for tag -F, and \"del\" for tag -d\n",
+ "# $3 -- repository\n",
+ "# $4-> file revision [file revision ...]\n",
+ "#\n",
+ "# A non-zero exit of the filter program will cause the tag to be aborted.\n",
+ "#\n",
+ "# The first entry on a line is a regular expression which is tested\n",
+ "# against the directory that the change is being committed to, relative\n",
+ "# to the $CVSROOT. For the first match that is found, then the remainder\n",
+ "# of the line is the name of the filter to run.\n",
+ "#\n",
+ "# If the repository name does not match any of the regular expressions in this\n",
+ "# file, the \"DEFAULT\" line is used, if it is specified.\n",
+ "#\n",
+ "# If the name \"ALL\" appears as a regular expression it is always used\n",
+ "# in addition to the first matching regex or \"DEFAULT\".\n",
+ NULL
+};
+
+static const char *const checkoutlist_contents[] = {
+ "# The \"checkoutlist\" file is used to support additional version controlled\n",
+ "# administrative files in $CVSROOT/CVSROOT, such as template files.\n",
+ "#\n",
+ "# The first entry on a line is a filename which will be checked out from\n",
+ "# the corresponding RCS file in the $CVSROOT/CVSROOT directory.\n",
+ "# The remainder of the line is an error message to use if the file cannot\n",
+ "# be checked out.\n",
+ "#\n",
+ "# File format:\n",
+ "#\n",
+ "# [<whitespace>]<filename><whitespace><error message><end-of-line>\n",
+ "#\n",
+ "# comment lines begin with '#'\n",
+ NULL
+};
+
+static const char *const cvswrappers_contents[] = {
+ "# This file describes wrappers and other binary files to CVS.\n",
+ "#\n",
+ "# Wrappers are the concept where directories of files are to be\n",
+ "# treated as a single file. The intended use is to wrap up a wrapper\n",
+ "# into a single tar such that the tar archive can be treated as a\n",
+ "# single binary file in CVS.\n",
+ "#\n",
+ "# To solve the problem effectively, it was also necessary to be able to\n",
+ "# prevent rcsmerge from merging these files.\n",
+ "#\n",
+ "# Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)\n",
+ "#\n",
+ "# wildcard [option value][option value]...\n",
+ "#\n",
+ "# where option is one of\n",
+ "# -f from cvs filter value: path to filter\n",
+ "# -t to cvs filter value: path to filter\n",
+ "# -m update methodology value: MERGE or COPY\n",
+ "#\n",
+ "# and value is a single-quote delimited value.\n",
+ "#\n",
+ "# For example:\n",
+ NULL
+};
+
+static const char *const notify_contents[] = {
+ "# The \"notify\" file controls where notifications from watches set by\n",
+ "# \"cvs watch add\" or \"cvs edit\" are sent. The first entry on a line is\n",
+ "# a regular expression which is tested against the directory that the\n",
+ "# change is being made to, relative to the $CVSROOT. If it matches,\n",
+ "# then the remainder of the line is a filter program that should contain\n",
+ "# one occurrence of %s for the user to notify, and information on its\n",
+ "# standard input.\n",
+ "#\n",
+ "# \"ALL\" or \"DEFAULT\" can be used in place of the regular expression.\n",
+ "#\n",
+ "# For example:\n",
+ "#ALL mail %s -s \"CVS notification\"\n",
+ NULL
+};
+
+static const char *const modules_contents[] = {
+ "# Three different line formats are valid:\n",
+ "# key -a aliases...\n",
+ "# key [options] directory\n",
+ "# key [options] directory files...\n",
+ "#\n",
+ "# Where \"options\" are composed of:\n",
+ "# -i prog Run \"prog\" on \"cvs commit\" from top-level of module.\n",
+ "# -o prog Run \"prog\" on \"cvs checkout\" of module.\n",
+ "# -e prog Run \"prog\" on \"cvs export\" of module.\n",
+ "# -t prog Run \"prog\" on \"cvs rtag\" of module.\n",
+ "# -u prog Run \"prog\" on \"cvs update\" of module.\n",
+ "# -d dir Place module in directory \"dir\" instead of module name.\n",
+ "# -l Top-level directory only -- do not recurse.\n",
+ "#\n",
+ "# And \"directory\" is a path to a directory relative to $CVSROOT.\n",
+ "#\n",
+ "# The \"-a\" option specifies an alias. An alias is interpreted as if\n",
+ "# everything on the right of the \"-a\" had been typed on the command line.\n",
+ "#\n",
+ "# You can encode a module within a module by using the special '&'\n",
+ "# character to interpose another module into the current module. This\n",
+ "# can be useful for creating a module that consists of many directories\n",
+ "# spread out over the entire source repository.\n",
+ NULL
+};
+
+static const struct admin_file filelist[] = {
+ {CVSROOTADM_LOGINFO,
+ "no logging of 'cvs commit' messages is done without a %s file",
+ &loginfo_contents[0]},
+ {CVSROOTADM_RCSINFO,
+ "a %s file can be used to configure 'cvs commit' templates",
+ rcsinfo_contents},
+ {CVSROOTADM_EDITINFO,
+ "a %s file can be used to validate log messages",
+ editinfo_contents},
+ {CVSROOTADM_COMMITINFO,
+ "a %s file can be used to configure 'cvs commit' checking",
+ commitinfo_contents},
+ {CVSROOTADM_TAGINFO,
+ "a %s file can be used to configure 'cvs tag' checking",
+ taginfo_contents},
+ {CVSROOTADM_IGNORE,
+ "a %s file can be used to specify files to ignore",
+ NULL},
+ {CVSROOTADM_CHECKOUTLIST,
+ "a %s file can specify extra CVSROOT files to auto-checkout",
+ checkoutlist_contents},
+ {CVSROOTADM_WRAPPER,
+ "a %s file can be used to specify files to treat as wrappers",
+ cvswrappers_contents},
+ {CVSROOTADM_NOTIFY,
+ "a %s file can be used to specify where notifications go",
+ notify_contents},
+ {CVSROOTADM_MODULES,
+ /* modules is special-cased in mkmodules. */
+ NULL,
+ modules_contents},
+ {NULL, NULL}
+};
+
+/* Rebuild the checked out administrative files in directory DIR. */
+int
+mkmodules (dir)
+ char *dir;
+{
+ struct saved_cwd cwd;
+ /* FIXME: arbitrary limit */
+ char temp[PATH_MAX];
+ char *cp, *last, *fname;
+#ifdef MY_NDBM
+ DBM *db;
+#endif
+ FILE *fp;
+ /* FIXME: arbitrary limit */
+ char line[512];
+ const struct admin_file *fileptr;
+
+ if (save_cwd (&cwd))
+ exit (EXIT_FAILURE);
+
+ if (chdir (dir) < 0)
+ error (1, errno, "cannot chdir to %s", dir);
+
+ /*
+ * First, do the work necessary to update the "modules" database.
+ */
+ make_tempfile (temp);
+ switch (checkout_file (CVSROOTADM_MODULES, temp))
+ {
+
+ case 0: /* everything ok */
+#ifdef MY_NDBM
+ /* open it, to generate any duplicate errors */
+ if ((db = dbm_open (temp, O_RDONLY, 0666)) != NULL)
+ dbm_close (db);
+#else
+ write_dbmfile (temp);
+ rename_dbmfile (temp);
+#endif
+ rename_rcsfile (temp, CVSROOTADM_MODULES);
+ break;
+
+ case -1: /* fork failed */
+ (void) unlink_file (temp);
+ exit (EXIT_FAILURE);
+ /* NOTREACHED */
+
+ default:
+ error (0, 0,
+ "'cvs checkout' is less functional without a %s file",
+ CVSROOTADM_MODULES);
+ break;
+ } /* switch on checkout_file() */
+
+ (void) unlink_file (temp);
+
+ /* Checkout the files that need it in CVSROOT dir */
+ for (fileptr = filelist; fileptr && fileptr->filename; fileptr++) {
+ if (fileptr->errormsg == NULL)
+ continue;
+ make_tempfile (temp);
+ if (checkout_file (fileptr->filename, temp) == 0)
+ rename_rcsfile (temp, fileptr->filename);
+#if 0
+ /*
+ * If there was some problem other than the file not existing,
+ * checkout_file already printed a real error message. If the
+ * file does not exist, it is harmless--it probably just means
+ * that the repository was created with an old version of CVS
+ * which didn't have so many files in CVSROOT.
+ */
+ else if (fileptr->errormsg)
+ error (0, 0, fileptr->errormsg, fileptr->filename);
+#endif
+ (void) unlink_file (temp);
+ }
+
+ /* Use 'fopen' instead of 'open_file' because we want to ignore error */
+ fp = fopen (CVSROOTADM_CHECKOUTLIST, "r");
+ if (fp)
+ {
+ /*
+ * File format:
+ * [<whitespace>]<filename><whitespace><error message><end-of-line>
+ *
+ * comment lines begin with '#'
+ */
+ while (fgets (line, sizeof (line), fp) != NULL)
+ {
+ /* skip lines starting with # */
+ if (line[0] == '#')
+ continue;
+
+ if ((last = strrchr (line, '\n')) != NULL)
+ *last = '\0'; /* strip the newline */
+
+ /* Skip leading white space. */
+ for (fname = line; *fname && isspace(*fname); fname++)
+ ;
+
+ /* Find end of filename. */
+ for (cp = fname; *cp && !isspace(*cp); cp++)
+ ;
+ *cp = '\0';
+
+ make_tempfile (temp);
+ if (checkout_file (fname, temp) == 0)
+ {
+ rename_rcsfile (temp, fname);
+ }
+ else
+ {
+ for (cp++; cp < last && *last && isspace(*last); cp++)
+ ;
+ if (cp < last && *cp)
+ error (0, 0, cp, fname);
+ }
+ }
+ (void) fclose (fp);
+ }
+
+ if (restore_cwd (&cwd, NULL))
+ exit (EXIT_FAILURE);
+ free_cwd (&cwd);
+
+ return (0);
+}
+
+/*
+ * Yeah, I know, there are NFS race conditions here.
+ */
+static void
+make_tempfile (temp)
+ char *temp;
+{
+ static int seed = 0;
+ int fd;
+
+ if (seed == 0)
+ seed = getpid ();
+ while (1)
+ {
+ (void) sprintf (temp, "%s%d", BAKPREFIX, seed++);
+ if ((fd = open (temp, O_CREAT|O_EXCL|O_RDWR, 0666)) != -1)
+ break;
+ if (errno != EEXIST)
+ error (1, errno, "cannot create temporary file %s", temp);
+ }
+ if (close(fd) < 0)
+ error(1, errno, "cannot close temporary file %s", temp);
+}
+
+static int
+checkout_file (file, temp)
+ char *file;
+ char *temp;
+{
+ char rcs[PATH_MAX];
+ int retcode = 0;
+
+ (void) sprintf (rcs, "%s%s", file, RCSEXT);
+ if (!isfile (rcs))
+ return (1);
+ run_setup ("%s%s -x,v/ -q -p", Rcsbin, RCS_CO);
+ run_arg (rcs);
+ if ((retcode = run_exec (RUN_TTY, temp, RUN_TTY, RUN_NORMAL)) != 0)
+ {
+ error (0, retcode == -1 ? errno : 0, "failed to check out %s file", file);
+ }
+ return (retcode);
+}
+
+#ifndef MY_NDBM
+
+static void
+write_dbmfile (temp)
+ char *temp;
+{
+ char line[DBLKSIZ], value[DBLKSIZ];
+ FILE *fp;
+ DBM *db;
+ char *cp, *vp;
+ datum key, val;
+ int len, cont, err = 0;
+
+ fp = open_file (temp, "r");
+ if ((db = dbm_open (temp, O_RDWR | O_CREAT | O_TRUNC, 0666)) == NULL)
+ error (1, errno, "cannot open dbm file %s for creation", temp);
+ for (cont = 0; fgets (line, sizeof (line), fp) != NULL;)
+ {
+ if ((cp = strrchr (line, '\n')) != NULL)
+ *cp = '\0'; /* strip the newline */
+
+ /*
+ * Add the line to the value, at the end if this is a continuation
+ * line; otherwise at the beginning, but only after any trailing
+ * backslash is removed.
+ */
+ vp = value;
+ if (cont)
+ vp += strlen (value);
+
+ /*
+ * See if the line we read is a continuation line, and strip the
+ * backslash if so.
+ */
+ len = strlen (line);
+ if (len > 0)
+ cp = &line[len - 1];
+ else
+ cp = line;
+ if (*cp == '\\')
+ {
+ cont = 1;
+ *cp = '\0';
+ }
+ else
+ {
+ cont = 0;
+ }
+ (void) strcpy (vp, line);
+ if (value[0] == '#')
+ continue; /* comment line */
+ vp = value;
+ while (*vp && isspace (*vp))
+ vp++;
+ if (*vp == '\0')
+ continue; /* empty line */
+
+ /*
+ * If this was not a continuation line, add the entry to the database
+ */
+ if (!cont)
+ {
+ key.dptr = vp;
+ while (*vp && !isspace (*vp))
+ vp++;
+ key.dsize = vp - key.dptr;
+ *vp++ = '\0'; /* NULL terminate the key */
+ while (*vp && isspace (*vp))
+ vp++; /* skip whitespace to value */
+ if (*vp == '\0')
+ {
+ error (0, 0, "warning: NULL value for key `%s'", key.dptr);
+ continue;
+ }
+ val.dptr = vp;
+ val.dsize = strlen (vp);
+ if (dbm_store (db, key, val, DBM_INSERT) == 1)
+ {
+ error (0, 0, "duplicate key found for `%s'", key.dptr);
+ err++;
+ }
+ }
+ }
+ dbm_close (db);
+ (void) fclose (fp);
+ if (err)
+ {
+ char dotdir[50], dotpag[50], dotdb[50];
+
+ (void) sprintf (dotdir, "%s.dir", temp);
+ (void) sprintf (dotpag, "%s.pag", temp);
+ (void) sprintf (dotdb, "%s.db", temp);
+ (void) unlink_file (dotdir);
+ (void) unlink_file (dotpag);
+ (void) unlink_file (dotdb);
+ error (1, 0, "DBM creation failed; correct above errors");
+ }
+}
+
+static void
+rename_dbmfile (temp)
+ char *temp;
+{
+ char newdir[50], newpag[50], newdb[50];
+ char dotdir[50], dotpag[50], dotdb[50];
+ char bakdir[50], bakpag[50], bakdb[50];
+
+ (void) sprintf (dotdir, "%s.dir", CVSROOTADM_MODULES);
+ (void) sprintf (dotpag, "%s.pag", CVSROOTADM_MODULES);
+ (void) sprintf (dotdb, "%s.db", CVSROOTADM_MODULES);
+ (void) sprintf (bakdir, "%s%s.dir", BAKPREFIX, CVSROOTADM_MODULES);
+ (void) sprintf (bakpag, "%s%s.pag", BAKPREFIX, CVSROOTADM_MODULES);
+ (void) sprintf (bakdb, "%s%s.db", BAKPREFIX, CVSROOTADM_MODULES);
+ (void) sprintf (newdir, "%s.dir", temp);
+ (void) sprintf (newpag, "%s.pag", temp);
+ (void) sprintf (newdb, "%s.db", temp);
+
+ (void) chmod (newdir, 0666);
+ (void) chmod (newpag, 0666);
+ (void) chmod (newdb, 0666);
+
+ /* don't mess with me */
+ SIG_beginCrSect ();
+
+ (void) unlink_file (bakdir); /* rm .#modules.dir .#modules.pag */
+ (void) unlink_file (bakpag);
+ (void) unlink_file (bakdb);
+ (void) rename (dotdir, bakdir); /* mv modules.dir .#modules.dir */
+ (void) rename (dotpag, bakpag); /* mv modules.pag .#modules.pag */
+ (void) rename (dotdb, bakdb); /* mv modules.db .#modules.db */
+ (void) rename (newdir, dotdir); /* mv "temp".dir modules.dir */
+ (void) rename (newpag, dotpag); /* mv "temp".pag modules.pag */
+ (void) rename (newdb, dotdb); /* mv "temp".db modules.db */
+
+ /* OK -- make my day */
+ SIG_endCrSect ();
+}
+
+#endif /* !MY_NDBM */
+
+static void
+rename_rcsfile (temp, real)
+ char *temp;
+ char *real;
+{
+ char bak[50];
+ struct stat statbuf;
+ char rcs[PATH_MAX];
+
+ /* Set "x" bits if set in original. */
+ (void) sprintf (rcs, "%s%s", real, RCSEXT);
+ statbuf.st_mode = 0; /* in case rcs file doesn't exist, but it should... */
+ (void) stat (rcs, &statbuf);
+
+ if (chmod (temp, 0444 | (statbuf.st_mode & 0111)) < 0)
+ error (0, errno, "warning: cannot chmod %s", temp);
+ (void) sprintf (bak, "%s%s", BAKPREFIX, real);
+ (void) unlink_file (bak); /* rm .#loginfo */
+ (void) rename (real, bak); /* mv loginfo .#loginfo */
+ (void) rename (temp, real); /* mv "temp" loginfo */
+}
+
+const char *const init_usage[] = {
+ "Usage: %s %s\n",
+ NULL
+};
+
+/* Create directory NAME if it does not already exist; fatal error for
+ other errors. FIXME: This should be in filesubr.c or thereabouts,
+ probably. Perhaps it should be further abstracted, though (for example
+ to handle CVSUMASK where appropriate?). */
+static void
+mkdir_if_needed (name)
+ char *name;
+{
+ if (CVS_MKDIR (name, 0777) < 0)
+ {
+ if (errno != EEXIST
+#ifdef EACCESS
+ /* OS/2; see longer comment in client.c. */
+ && errno != EACCESS
+#endif
+ )
+ error (1, errno, "cannot mkdir %s", name);
+ }
+}
+
+int
+init (argc, argv)
+ int argc;
+ char **argv;
+{
+ /* Name of CVSROOT directory. */
+ char *adm;
+ /* Name of this administrative file. */
+ char *info;
+ /* Name of ,v file for this administrative file. */
+ char *info_v;
+
+ const struct admin_file *fileptr;
+
+ umask (cvsumask);
+
+ if (argc > 1)
+ usage (init_usage);
+
+ if (client_active)
+ {
+ start_server ();
+
+ ign_setup ();
+ send_init_command ();
+ return get_responses_and_close ();
+ }
+
+ /* Note: we do *not* create parent directories as needed like the
+ old cvsinit.sh script did. Few utilities do that, and a
+ non-existent parent directory is as likely to be a typo as something
+ which needs to be created. */
+ mkdir_if_needed (CVSroot);
+
+ adm = xmalloc (strlen (CVSroot) + sizeof (CVSROOTADM) + 10);
+ strcpy (adm, CVSroot);
+ strcat (adm, "/");
+ strcat (adm, CVSROOTADM);
+ mkdir_if_needed (adm);
+
+ /* This is needed by the call to "ci" below. */
+ if (chdir (adm) < 0)
+ error (1, errno, "cannot change to directory %s", adm);
+
+ /* 80 is long enough for all the administrative file names, plus
+ "/" and so on. */
+ info = xmalloc (strlen (adm) + 80);
+ info_v = xmalloc (strlen (adm) + 80);
+ for (fileptr = filelist; fileptr && fileptr->filename; ++fileptr)
+ {
+ if (fileptr->contents == NULL)
+ continue;
+ strcpy (info, adm);
+ strcat (info, "/");
+ strcat (info, fileptr->filename);
+ strcpy (info_v, info);
+ strcat (info_v, RCSEXT);
+ if (isfile (info_v))
+ /* We will check out this file in the mkmodules step.
+ Nothing else is required. */
+ ;
+ else
+ {
+ int retcode;
+
+ if (!isfile (info))
+ {
+ FILE *fp;
+ const char * const *p;
+
+ fp = open_file (info, "w");
+ for (p = fileptr->contents; *p != NULL; ++p)
+ if (fputs (*p, fp) < 0)
+ error (1, errno, "cannot write %s", info);
+ if (fclose (fp) < 0)
+ error (1, errno, "cannot close %s", info);
+ }
+ /* Now check the file in. FIXME: we could be using
+ add_rcs_file from import.c which is faster (if it were
+ tweaked slightly). */
+ run_setup ("%s%s -x,v/ -q -u -t-", Rcsbin, RCS_CI);
+ run_args ("-minitial checkin of %s", fileptr->filename);
+ run_arg (fileptr->filename);
+ retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+ if (retcode != 0)
+ error (1, retcode == -1 ? errno : 0,
+ "failed to check in %s", info);
+ }
+ }
+
+ /* Turn on history logging by default. The user can remove the file
+ to disable it. */
+ strcpy (info, adm);
+ strcat (info, "/");
+ strcat (info, CVSROOTADM_HISTORY);
+ if (!isfile (info))
+ {
+ FILE *fp;
+
+ fp = open_file (info, "w");
+ if (fclose (fp) < 0)
+ error (1, errno, "cannot close %s", info);
+ }
+
+ free (info);
+ free (info_v);
+
+ mkmodules (adm);
+
+ free (adm);
+ return 0;
+}
diff --git a/contrib/cvs/src/modules.c b/contrib/cvs/src/modules.c
new file mode 100644
index 0000000..0e07c0b
--- /dev/null
+++ b/contrib/cvs/src/modules.c
@@ -0,0 +1,900 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Modules
+ *
+ * Functions for accessing the modules file.
+ *
+ * The modules file supports basically three formats of lines:
+ * key [options] directory files... [ -x directory [files] ] ...
+ * key [options] directory [ -x directory [files] ] ...
+ * key -a aliases...
+ *
+ * The -a option allows an aliasing step in the parsing of the modules
+ * file. The "aliases" listed on a line following the -a are
+ * processed one-by-one, as if they were specified as arguments on the
+ * command line.
+ */
+
+#include "cvs.h"
+#include "savecwd.h"
+
+
+/* Defines related to the syntax of the modules file. */
+
+/* Options in modules file. Note that it is OK to use GNU getopt features;
+ we already are arranging to make sure we are using the getopt distributed
+ with CVS. */
+#define CVSMODULE_OPTS "+ad:i:lo:e:s:t:u:"
+
+/* Special delimiter. */
+#define CVSMODULE_SPEC '&'
+
+struct sortrec
+{
+ char *modname;
+ char *status;
+ char *rest;
+ char *comment;
+};
+
+static int sort_order PROTO((const PTR l, const PTR r));
+static void save_d PROTO((char *k, int ks, char *d, int ds));
+
+
+/*
+ * Open the modules file, and die if the CVSROOT environment variable
+ * was not set. If the modules file does not exist, that's fine, and
+ * a warning message is displayed and a NULL is returned.
+ */
+DBM *
+open_module ()
+{
+ char mfile[PATH_MAX];
+
+ if (CVSroot == NULL)
+ {
+ (void) fprintf (stderr,
+ "%s: must set the CVSROOT environment variable\n",
+ program_name);
+ error (1, 0, "or specify the '-d' option to %s", program_name);
+ }
+ (void) sprintf (mfile, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_MODULES);
+ return (dbm_open (mfile, O_RDONLY, 0666));
+}
+
+/*
+ * Close the modules file, if the open succeeded, that is
+ */
+void
+close_module (db)
+ DBM *db;
+{
+ if (db != NULL)
+ dbm_close (db);
+}
+
+/*
+ * This is the recursive function that processes a module name.
+ * It calls back the passed routine for each directory of a module
+ * It runs the post checkout or post tag proc from the modules file
+ */
+int
+do_module (db, mname, m_type, msg, callback_proc, where,
+ shorten, local_specified, run_module_prog, extra_arg)
+ DBM *db;
+ char *mname;
+ enum mtype m_type;
+ char *msg;
+ CALLBACKPROC callback_proc;
+ char *where;
+ int shorten;
+ int local_specified;
+ int run_module_prog;
+ char *extra_arg;
+{
+ char *checkin_prog = NULL;
+ char *checkout_prog = NULL;
+ char *export_prog = NULL;
+ char *tag_prog = NULL;
+ char *update_prog = NULL;
+ struct saved_cwd cwd;
+ char *line;
+ int modargc;
+ int xmodargc;
+ char **modargv;
+ char *xmodargv[MAXFILEPERDIR];
+ char *value;
+ char *zvalue;
+ char *mwhere = NULL;
+ char *mfile = NULL;
+ char *spec_opt = NULL;
+ char xvalue[PATH_MAX];
+ int alias = 0;
+ datum key, val;
+ char *cp;
+ int c, err = 0;
+
+#ifdef SERVER_SUPPORT
+ if (trace)
+ {
+ fprintf (stderr, "%s%c-> do_module (%s, %s, %s, %s)\n",
+ error_use_protocol ? "E " : "",
+ (server_active) ? 'S' : ' ',
+ mname, msg, where ? where : "",
+ extra_arg ? extra_arg : "");
+ }
+#endif
+
+ /* if this is a directory to ignore, add it to that list */
+ if (mname[0] == '!' && mname[1] != '\0')
+ {
+ ign_dir_add (mname+1);
+ return(err);
+ }
+
+ /* strip extra stuff from the module name */
+ strip_path (mname);
+
+ /*
+ * Look up the module using the following scheme:
+ * 1) look for mname as a module name
+ * 2) look for mname as a directory
+ * 3) look for mname as a file
+ * 4) take mname up to the first slash and look it up as a module name
+ * (this is for checking out only part of a module)
+ */
+
+ /* look it up as a module name */
+ key.dptr = mname;
+ key.dsize = strlen (key.dptr);
+ if (db != NULL)
+ val = dbm_fetch (db, key);
+ else
+ val.dptr = NULL;
+ if (val.dptr != NULL)
+ {
+ /* null terminate the value XXX - is this space ours? */
+ val.dptr[val.dsize] = '\0';
+
+ /* If the line ends in a comment, strip it off */
+ if ((cp = strchr (val.dptr, '#')) != NULL)
+ {
+ do
+ *cp-- = '\0';
+ while (isspace (*cp));
+ }
+ else
+ {
+ /* Always strip trailing spaces */
+ cp = strchr (val.dptr, '\0');
+ while (cp > val.dptr && isspace(*--cp))
+ *cp = '\0';
+ }
+
+ value = val.dptr;
+ mwhere = xstrdup (mname);
+ goto found;
+ }
+ else
+ {
+ char file[PATH_MAX];
+ char attic_file[PATH_MAX];
+ char *acp;
+
+ /* check to see if mname is a directory or file */
+
+ (void) sprintf (file, "%s/%s", CVSroot, mname);
+ if ((acp = strrchr (mname, '/')) != NULL)
+ {
+ *acp = '\0';
+ (void) sprintf (attic_file, "%s/%s/%s/%s%s", CVSroot, mname,
+ CVSATTIC, acp + 1, RCSEXT);
+ *acp = '/';
+ }
+ else
+ (void) sprintf (attic_file, "%s/%s/%s%s", CVSroot, CVSATTIC,
+ mname, RCSEXT);
+
+ if (isdir (file))
+ {
+ value = mname;
+ goto found;
+ }
+ else
+ {
+ (void) strcat (file, RCSEXT);
+ if (isfile (file) || isfile (attic_file))
+ {
+ /* if mname was a file, we have to split it into "dir file" */
+ if ((cp = strrchr (mname, '/')) != NULL && cp != mname)
+ {
+ char *slashp;
+
+ /* put the ' ' in a copy so we don't mess up the original */
+ value = strcpy (xvalue, mname);
+ slashp = strrchr (value, '/');
+ *slashp = ' ';
+ }
+ else
+ {
+ /*
+ * the only '/' at the beginning or no '/' at all
+ * means the file we are interested in is in CVSROOT
+ * itself so the directory should be '.'
+ */
+ if (cp == mname)
+ {
+ /* drop the leading / if specified */
+ value = strcpy (xvalue, ". ");
+ (void) strcat (xvalue, mname + 1);
+ }
+ else
+ {
+ /* otherwise just copy it */
+ value = strcpy (xvalue, ". ");
+ (void) strcat (xvalue, mname);
+ }
+ }
+ goto found;
+ }
+ }
+ }
+
+ /* look up everything to the first / as a module */
+ if (mname[0] != '/' && (cp = strchr (mname, '/')) != NULL)
+ {
+ /* Make the slash the new end of the string temporarily */
+ *cp = '\0';
+ key.dptr = mname;
+ key.dsize = strlen (key.dptr);
+
+ /* do the lookup */
+ if (db != NULL)
+ val = dbm_fetch (db, key);
+ else
+ val.dptr = NULL;
+
+ /* if we found it, clean up the value and life is good */
+ if (val.dptr != NULL)
+ {
+ char *cp2;
+
+ /* null terminate the value XXX - is this space ours? */
+ val.dptr[val.dsize] = '\0';
+
+ /* If the line ends in a comment, strip it off */
+ if ((cp2 = strchr (val.dptr, '#')) != NULL)
+ {
+ do
+ *cp2-- = '\0';
+ while (isspace (*cp2));
+ }
+ value = val.dptr;
+
+ /* mwhere gets just the module name */
+ mwhere = xstrdup (mname);
+ mfile = cp + 1;
+
+ /* put the / back in mname */
+ *cp = '/';
+
+ goto found;
+ }
+
+ /* put the / back in mname */
+ *cp = '/';
+ }
+
+ /* if we got here, we couldn't find it using our search, so give up */
+ error (0, 0, "cannot find module `%s' - ignored", mname);
+ err++;
+ if (mwhere)
+ free (mwhere);
+ return (err);
+
+
+ /*
+ * At this point, we found what we were looking for in one
+ * of the many different forms.
+ */
+ found:
+
+ /* remember where we start */
+ if (save_cwd (&cwd))
+ exit (EXIT_FAILURE);
+
+ /* copy value to our own string since if we go recursive we'll be
+ really screwed if we do another dbm lookup */
+ zvalue = xstrdup (value);
+ value = zvalue;
+
+ /* search the value for the special delimiter and save for later */
+ if ((cp = strchr (value, CVSMODULE_SPEC)) != NULL)
+ {
+ *cp = '\0'; /* null out the special char */
+ spec_opt = cp + 1; /* save the options for later */
+
+ if (cp != value) /* strip whitespace if necessary */
+ while (isspace (*--cp))
+ *cp = '\0';
+
+ if (cp == value)
+ {
+ /*
+ * we had nothing but special options, so skip arg
+ * parsing and regular stuff entirely
+ *
+ * If there were only special ones though, we must
+ * make the appropriate directory and cd to it
+ */
+ char *dir;
+
+ /* XXX - XXX - MAJOR HACK - DO NOT SHIP - this needs to
+ be !pipeout, but we don't know that here yet */
+ if (!run_module_prog)
+ goto out;
+
+ dir = where ? where : mname;
+ /* XXX - think about making null repositories at each dir here
+ instead of just at the bottom */
+ make_directories (dir);
+ if (chdir (dir) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", dir);
+ spec_opt = NULL;
+ err++;
+ goto out;
+ }
+ if (!isfile (CVSADM))
+ {
+ char nullrepos[PATH_MAX];
+
+ (void) sprintf (nullrepos, "%s/%s/%s", CVSroot,
+ CVSROOTADM, CVSNULLREPOS);
+ if (!isfile (nullrepos))
+ {
+ mode_t omask;
+ omask = umask (cvsumask);
+ (void) CVS_MKDIR (nullrepos, 0777);
+ (void) umask (omask);
+ }
+ if (!isdir (nullrepos))
+ error (1, 0, "there is no repository %s", nullrepos);
+
+ Create_Admin (".", dir,
+ nullrepos, (char *) NULL, (char *) NULL);
+ if (!noexec)
+ {
+ FILE *fp;
+
+ fp = open_file (CVSADM_ENTSTAT, "w+");
+ if (fclose (fp) == EOF)
+ error (1, errno, "cannot close %s", CVSADM_ENTSTAT);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_entstat (dir, nullrepos);
+#endif
+ }
+ }
+ out:
+ goto do_special;
+ }
+ }
+
+ /* don't do special options only part of a module was specified */
+ if (mfile != NULL)
+ spec_opt = NULL;
+
+ /*
+ * value now contains one of the following:
+ * 1) dir
+ * 2) dir file
+ * 3) the value from modules without any special args
+ * [ args ] dir [file] [file] ...
+ * or -a module [ module ] ...
+ */
+
+ /* Put the value on a line with XXX prepended for getopt to eat */
+ line = xmalloc (strlen (value) + 10);
+ (void) sprintf (line, "%s %s", "XXX", value);
+
+ /* turn the line into an argv[] array */
+ line2argv (&xmodargc, xmodargv, line);
+ free (line);
+ modargc = xmodargc;
+ modargv = xmodargv;
+
+ /* parse the args */
+ optind = 1;
+ while ((c = getopt (modargc, modargv, CVSMODULE_OPTS)) != -1)
+ {
+ switch (c)
+ {
+ case 'a':
+ alias = 1;
+ break;
+ case 'd':
+ if (mwhere)
+ free (mwhere);
+ mwhere = xstrdup (optarg);
+ break;
+ case 'i':
+ checkin_prog = optarg;
+ break;
+ case 'l':
+ local_specified = 1;
+ break;
+ case 'o':
+ checkout_prog = optarg;
+ break;
+ case 'e':
+ export_prog = optarg;
+ break;
+ case 't':
+ tag_prog = optarg;
+ break;
+ case 'u':
+ update_prog = optarg;
+ break;
+ case '?':
+ error (0, 0,
+ "modules file has invalid option for key %s value %s",
+ key.dptr, val.dptr);
+ err++;
+ if (mwhere)
+ free (mwhere);
+ free (zvalue);
+ free_cwd (&cwd);
+ return (err);
+ }
+ }
+ modargc -= optind;
+ modargv += optind;
+ if (modargc == 0)
+ {
+ error (0, 0, "modules file missing directory for module %s", mname);
+ if (mwhere)
+ free (mwhere);
+ free (zvalue);
+ free_cwd (&cwd);
+ return (++err);
+ }
+
+ /* if this was an alias, call ourselves recursively for each module */
+ if (alias)
+ {
+ int i;
+
+ for (i = 0; i < modargc; i++)
+ {
+ if (strcmp (mname, modargv[i]) == 0)
+ error (0, 0,
+ "module `%s' in modules file contains infinite loop",
+ mname);
+ else
+ err += do_module (db, modargv[i], m_type, msg, callback_proc,
+ where, shorten, local_specified,
+ run_module_prog, extra_arg);
+ }
+ if (mwhere)
+ free (mwhere);
+ free (zvalue);
+ free_cwd (&cwd);
+ return (err);
+ }
+
+ /* otherwise, process this module */
+ err += callback_proc (&modargc, modargv, where, mwhere, mfile, shorten,
+ local_specified, mname, msg);
+
+#if 0
+ /* FIXME: I've fixed this so that the correct arguments are called,
+ but now this fails because there is code below this point that
+ uses optarg values extracted from the arg vector. */
+ free_names (&xmodargc, xmodargv);
+#endif
+
+ /* if there were special include args, process them now */
+
+ do_special:
+
+ /* blow off special options if -l was specified */
+ if (local_specified)
+ spec_opt = NULL;
+
+ while (spec_opt != NULL)
+ {
+ char *next_opt;
+
+ cp = strchr (spec_opt, CVSMODULE_SPEC);
+ if (cp != NULL)
+ {
+ /* save the beginning of the next arg */
+ next_opt = cp + 1;
+
+ /* strip whitespace off the end */
+ do
+ *cp = '\0';
+ while (isspace (*--cp));
+ }
+ else
+ next_opt = NULL;
+
+ /* strip whitespace from front */
+ while (isspace (*spec_opt))
+ spec_opt++;
+
+ if (*spec_opt == '\0')
+ error (0, 0, "Mal-formed %c option for module %s - ignored",
+ CVSMODULE_SPEC, mname);
+ else
+ err += do_module (db, spec_opt, m_type, msg, callback_proc,
+ (char *) NULL, 0, local_specified,
+ run_module_prog, extra_arg);
+ spec_opt = next_opt;
+ }
+
+ /* write out the checkin/update prog files if necessary */
+#ifdef SERVER_SUPPORT
+ if (err == 0 && !noexec && m_type == CHECKOUT && server_expanding)
+ {
+ if (checkin_prog != NULL)
+ server_prog (where ? where : mname, checkin_prog, PROG_CHECKIN);
+ if (update_prog != NULL)
+ server_prog (where ? where : mname, update_prog, PROG_UPDATE);
+ }
+ else
+#endif
+ if (err == 0 && !noexec && m_type == CHECKOUT && run_module_prog)
+ {
+ FILE *fp;
+
+ if (checkin_prog != NULL)
+ {
+ fp = open_file (CVSADM_CIPROG, "w+");
+ (void) fprintf (fp, "%s\n", checkin_prog);
+ if (fclose (fp) == EOF)
+ error (1, errno, "cannot close %s", CVSADM_CIPROG);
+ }
+ if (update_prog != NULL)
+ {
+ fp = open_file (CVSADM_UPROG, "w+");
+ (void) fprintf (fp, "%s\n", update_prog);
+ if (fclose (fp) == EOF)
+ error (1, errno, "cannot close %s", CVSADM_UPROG);
+ }
+ }
+
+ /* cd back to where we started */
+ if (restore_cwd (&cwd, NULL))
+ exit (EXIT_FAILURE);
+ free_cwd (&cwd);
+
+ /* run checkout or tag prog if appropriate */
+ if (err == 0 && run_module_prog)
+ {
+ if ((m_type == TAG && tag_prog != NULL) ||
+ (m_type == CHECKOUT && checkout_prog != NULL) ||
+ (m_type == EXPORT && export_prog != NULL))
+ {
+ /*
+ * If a relative pathname is specified as the checkout, tag
+ * or export proc, try to tack on the current "where" value.
+ * if we can't find a matching program, just punt and use
+ * whatever is specified in the modules file.
+ */
+ char real_prog[PATH_MAX];
+ char *prog = (m_type == TAG ? tag_prog :
+ (m_type == CHECKOUT ? checkout_prog : export_prog));
+ char *real_where = (where != NULL ? where : mwhere);
+
+ if ((*prog != '/') && (*prog != '.'))
+ {
+ (void) sprintf (real_prog, "%s/%s", real_where, prog);
+ if (isfile (real_prog))
+ prog = real_prog;
+ }
+
+ run_setup ("%s %s", prog, real_where);
+ if (extra_arg)
+ run_arg (extra_arg);
+
+ if (!quiet)
+ {
+ (void) printf ("%s %s: Executing '", program_name,
+ command_name);
+ run_print (stdout);
+ (void) printf ("'\n");
+ }
+ err += run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+ }
+ }
+
+ /* clean up */
+ if (mwhere)
+ free (mwhere);
+ free (zvalue);
+
+ return (err);
+}
+
+/* - Read all the records from the modules database into an array.
+ - Sort the array depending on what format is desired.
+ - Print the array in the format desired.
+
+ Currently, there are only two "desires":
+
+ 1. Sort by module name and format the whole entry including switches,
+ files and the comment field: (Including aliases)
+
+ modulename -s switches, one per line, even if
+ -i it has many switches.
+ Directories and files involved, formatted
+ to cover multiple lines if necessary.
+ # Comment, also formatted to cover multiple
+ # lines if necessary.
+
+ 2. Sort by status field string and print: (*not* including aliases)
+
+ modulename STATUS Directories and files involved, formatted
+ to cover multiple lines if necessary.
+ # Comment, also formatted to cover multiple
+ # lines if necessary.
+*/
+
+static struct sortrec *s_head;
+
+static int s_max = 0; /* Number of elements allocated */
+static int s_count = 0; /* Number of elements used */
+
+static int Status; /* Nonzero if the user is
+ interested in status
+ information as well as
+ module name */
+static char def_status[] = "NONE";
+
+/* Sort routine for qsort:
+ - If we want the "Status" field to be sorted, check it first.
+ - Then compare the "module name" fields. Since they are unique, we don't
+ have to look further.
+*/
+static int
+sort_order (l, r)
+ const PTR l;
+ const PTR r;
+{
+ int i;
+ const struct sortrec *left = (const struct sortrec *) l;
+ const struct sortrec *right = (const struct sortrec *) r;
+
+ if (Status)
+ {
+ /* If Sort by status field, compare them. */
+ if ((i = strcmp (left->status, right->status)) != 0)
+ return (i);
+ }
+ return (strcmp (left->modname, right->modname));
+}
+
+static void
+save_d (k, ks, d, ds)
+ char *k;
+ int ks;
+ char *d;
+ int ds;
+{
+ char *cp, *cp2;
+ struct sortrec *s_rec;
+
+ if (Status && *d == '-' && *(d + 1) == 'a')
+ return; /* We want "cvs co -s" and it is an alias! */
+
+ if (s_count == s_max)
+ {
+ s_max += 64;
+ s_head = (struct sortrec *) xrealloc ((char *) s_head, s_max * sizeof (*s_head));
+ }
+ s_rec = &s_head[s_count];
+ s_rec->modname = cp = xmalloc (ks + 1);
+ (void) strncpy (cp, k, ks);
+ *(cp + ks) = '\0';
+
+ s_rec->rest = cp2 = xmalloc (ds + 1);
+ cp = d;
+ *(cp + ds) = '\0'; /* Assumes an extra byte at end of static dbm buffer */
+
+ while (isspace (*cp))
+ cp++;
+ /* Turn <spaces> into one ' ' -- makes the rest of this routine simpler */
+ while (*cp)
+ {
+ if (isspace (*cp))
+ {
+ *cp2++ = ' ';
+ while (isspace (*cp))
+ cp++;
+ }
+ else
+ *cp2++ = *cp++;
+ }
+ *cp2 = '\0';
+
+ /* Look for the "-s statusvalue" text */
+ if (Status)
+ {
+ s_rec->status = def_status;
+
+ /* Minor kluge, but general enough to maintain */
+ for (cp = s_rec->rest; (cp2 = strchr (cp, '-')) != NULL; cp = ++cp2)
+ {
+ if (*(cp2 + 1) == 's' && *(cp2 + 2) == ' ')
+ {
+ s_rec->status = (cp2 += 3);
+ while (*cp2 != ' ')
+ cp2++;
+ *cp2++ = '\0';
+ cp = cp2;
+ break;
+ }
+ }
+ }
+ else
+ cp = s_rec->rest;
+
+ /* Find comment field, clean up on all three sides & compress blanks */
+ if ((cp2 = cp = strchr (cp, '#')) != NULL)
+ {
+ if (*--cp2 == ' ')
+ *cp2 = '\0';
+ if (*++cp == ' ')
+ cp++;
+ s_rec->comment = cp;
+ }
+ else
+ s_rec->comment = "";
+
+ s_count++;
+}
+
+/* Print out the module database as we know it. If STATUS is
+ non-zero, print out status information for each module. */
+
+void
+cat_module (status)
+ int status;
+{
+ DBM *db;
+ datum key, val;
+ int i, c, wid, argc, cols = 80, indent, fill;
+ int moduleargc;
+ struct sortrec *s_h;
+ char *cp, *cp2, **argv;
+ char *line;
+ char *moduleargv[MAXFILEPERDIR];
+
+#ifdef sun
+#ifdef TIOCGSIZE
+ struct ttysize ts;
+
+ (void) ioctl (0, TIOCGSIZE, &ts);
+ cols = ts.ts_cols;
+#endif
+#else
+#ifdef TIOCGWINSZ
+ struct winsize ws;
+
+ (void) ioctl (0, TIOCGWINSZ, &ws);
+ cols = ws.ws_col;
+#endif
+#endif
+
+ Status = status;
+
+ /* Read the whole modules file into allocated records */
+ if (!(db = open_module ()))
+ error (1, 0, "failed to open the modules file");
+
+ for (key = dbm_firstkey (db); key.dptr != NULL; key = dbm_nextkey (db))
+ {
+ val = dbm_fetch (db, key);
+ if (val.dptr != NULL)
+ save_d (key.dptr, key.dsize, val.dptr, val.dsize);
+ }
+
+ /* Sort the list as requested */
+ qsort ((PTR) s_head, s_count, sizeof (struct sortrec), sort_order);
+
+ /*
+ * Run through the sorted array and format the entries
+ * indent = space for modulename + space for status field
+ */
+ indent = 12 + (status * 12);
+ fill = cols - (indent + 2);
+ for (s_h = s_head, i = 0; i < s_count; i++, s_h++)
+ {
+ /* Print module name (and status, if wanted) */
+ (void) printf ("%-12s", s_h->modname);
+ if (status)
+ {
+ (void) printf (" %-11s", s_h->status);
+ if (s_h->status != def_status)
+ *(s_h->status + strlen (s_h->status)) = ' ';
+ }
+
+ /* Parse module file entry as command line and print options */
+ line = xmalloc (strlen (s_h->modname) + strlen (s_h->rest) + 10);
+ (void) sprintf (line, "%s %s", s_h->modname, s_h->rest);
+ line2argv (&moduleargc, moduleargv, line);
+ free (line);
+ argc = moduleargc;
+ argv = moduleargv;
+
+ optind = 0;
+ wid = 0;
+ while ((c = getopt (argc, argv, CVSMODULE_OPTS)) != -1)
+ {
+ if (!status)
+ {
+ if (c == 'a' || c == 'l')
+ {
+ (void) printf (" -%c", c);
+ wid += 3; /* Could just set it to 3 */
+ }
+ else
+ {
+ if (strlen (optarg) + 4 + wid > (unsigned) fill)
+ {
+ (void) printf ("\n%*s", indent, "");
+ wid = 0;
+ }
+ (void) printf (" -%c %s", c, optarg);
+ wid += strlen (optarg) + 4;
+ }
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Format and Print all the files and directories */
+ for (; argc--; argv++)
+ {
+ if (strlen (*argv) + wid > (unsigned) fill)
+ {
+ (void) printf ("\n%*s", indent, "");
+ wid = 0;
+ }
+ (void) printf (" %s", *argv);
+ wid += strlen (*argv) + 1;
+ }
+ (void) printf ("\n");
+
+ /* Format the comment field -- save_d (), compressed spaces */
+ for (cp2 = cp = s_h->comment; *cp; cp2 = cp)
+ {
+ (void) printf ("%*s # ", indent, "");
+ if (strlen (cp2) < (unsigned) (fill - 2))
+ {
+ (void) printf ("%s\n", cp2);
+ break;
+ }
+ cp += fill - 2;
+ while (*cp != ' ' && cp > cp2)
+ cp--;
+ if (cp == cp2)
+ {
+ (void) printf ("%s\n", cp2);
+ break;
+ }
+
+ *cp++ = '\0';
+ (void) printf ("%s\n", cp2);
+ }
+
+ free_names(&moduleargc, moduleargv);
+ }
+}
diff --git a/contrib/cvs/src/myndbm.c b/contrib/cvs/src/myndbm.c
new file mode 100644
index 0000000..527f7ee
--- /dev/null
+++ b/contrib/cvs/src/myndbm.c
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 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.
+ *
+ * A simple ndbm-emulator for CVS. It parses a text file of the format:
+ *
+ * key value
+ *
+ * at dbm_open time, and loads the entire file into memory. As such, it is
+ * probably only good for fairly small modules files. Ours is about 30K in
+ * size, and this code works fine.
+ */
+
+#include <assert.h>
+#include "cvs.h"
+#include "getline.h"
+
+#ifdef MY_NDBM
+
+static void mydbm_load_file ();
+
+/* ARGSUSED */
+DBM *
+mydbm_open (file, flags, mode)
+ char *file;
+ int flags;
+ int mode;
+{
+ FILE *fp;
+ DBM *db;
+
+ fp = fopen (file, FOPEN_BINARY_READ);
+ if (fp == NULL && !(existence_error (errno) && (flags & O_CREAT)))
+ return ((DBM *) 0);
+
+ db = (DBM *) xmalloc (sizeof (*db));
+ db->dbm_list = getlist ();
+ db->modified = 0;
+ db->name = xstrdup (file);
+
+ if (fp != NULL)
+ {
+ mydbm_load_file (fp, db->dbm_list);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", file);
+ }
+ return (db);
+}
+
+static int write_item PROTO ((Node *, void *));
+
+static int
+write_item (node, data)
+ Node *node;
+ void *data;
+{
+ FILE *fp = (FILE *)data;
+ fputs (node->key, fp);
+ fputs (" ", fp);
+ fputs (node->data, fp);
+ fputs ("\012", fp);
+ return 0;
+}
+
+void
+mydbm_close (db)
+ DBM *db;
+{
+ if (db->modified)
+ {
+ FILE *fp;
+ fp = fopen (db->name, FOPEN_BINARY_WRITE);
+ if (fp == NULL)
+ error (1, errno, "cannot write %s", db->name);
+ walklist (db->dbm_list, write_item, (void *)fp);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", db->name);
+ }
+ free (db->name);
+ dellist (&db->dbm_list);
+ free ((char *) db);
+}
+
+datum
+mydbm_fetch (db, key)
+ DBM *db;
+ datum key;
+{
+ Node *p;
+ char *s;
+ datum val;
+
+ /* make sure it's null-terminated */
+ s = xmalloc (key.dsize + 1);
+ (void) strncpy (s, key.dptr, key.dsize);
+ s[key.dsize] = '\0';
+
+ p = findnode (db->dbm_list, s);
+ if (p)
+ {
+ val.dptr = p->data;
+ val.dsize = strlen (p->data);
+ }
+ else
+ {
+ val.dptr = (char *) NULL;
+ val.dsize = 0;
+ }
+ free (s);
+ return (val);
+}
+
+datum
+mydbm_firstkey (db)
+ DBM *db;
+{
+ Node *head, *p;
+ datum key;
+
+ head = db->dbm_list->list;
+ p = head->next;
+ if (p != head)
+ {
+ key.dptr = p->key;
+ key.dsize = strlen (p->key);
+ }
+ else
+ {
+ key.dptr = (char *) NULL;
+ key.dsize = 0;
+ }
+ db->dbm_next = p->next;
+ return (key);
+}
+
+datum
+mydbm_nextkey (db)
+ DBM *db;
+{
+ Node *head, *p;
+ datum key;
+
+ head = db->dbm_list->list;
+ p = db->dbm_next;
+ if (p != head)
+ {
+ key.dptr = p->key;
+ key.dsize = strlen (p->key);
+ }
+ else
+ {
+ key.dptr = (char *) NULL;
+ key.dsize = 0;
+ }
+ db->dbm_next = p->next;
+ return (key);
+}
+
+/* Note: only updates the in-memory copy, which is written out at
+ mydbm_close time. Note: Also differs from DBM in that on duplication,
+ it gives a warning, rather than either DBM_INSERT or DBM_REPLACE
+ behavior. */
+int
+mydbm_store (db, key, value, flags)
+ DBM *db;
+ datum key;
+ datum value;
+ int flags;
+{
+ Node *node;
+
+ node = getnode ();
+ node->type = NDBMNODE;
+
+ node->key = xmalloc (key.dsize + 1);
+ strncpy (node->key, key.dptr, key.dsize);
+ node->key[key.dsize] = '\0';
+
+ node->data = xmalloc (value.dsize + 1);
+ strncpy (node->data, value.dptr, value.dsize);
+ node->data[value.dsize] = '\0';
+
+ db->modified = 1;
+ if (addnode (db->dbm_list, node) == -1)
+ {
+ error (0, 0, "attempt to insert duplicate key `%s'", node->key);
+ freenode (node);
+ return 0;
+ }
+ return 0;
+}
+
+static void
+mydbm_load_file (fp, list)
+ FILE *fp;
+ List *list;
+{
+ char *line = NULL;
+ size_t line_len;
+ /* FIXME: arbitrary limit. */
+ char value[MAXLINELEN];
+ char *cp, *vp;
+ int len, cont;
+
+ for (cont = 0; getline (&line, &line_len, fp) >= 0;)
+ {
+ if ((cp = strrchr (line, '\012')) != NULL)
+ *cp = '\0'; /* strip the newline */
+ cp = line + strlen (line);
+ if (cp > line && cp[-1] == '\015')
+ /* If the file (e.g. modules) was written on an NT box, it will
+ contain CRLF at the ends of lines. Strip them (we can't do
+ this by opening the file in text mode because we might be
+ running on unix). */
+ cp[-1] = '\0';
+
+ /*
+ * Add the line to the value, at the end if this is a continuation
+ * line; otherwise at the beginning, but only after any trailing
+ * backslash is removed.
+ */
+ vp = value;
+ if (cont)
+ vp += strlen (value);
+
+ /*
+ * See if the line we read is a continuation line, and strip the
+ * backslash if so.
+ */
+ len = strlen (line);
+ if (len > 0)
+ cp = &line[len - 1];
+ else
+ cp = line;
+ if (*cp == '\\')
+ {
+ cont = 1;
+ *cp = '\0';
+ }
+ else
+ {
+ cont = 0;
+ }
+ (void) strcpy (vp, line);
+ if (value[0] == '#')
+ continue; /* comment line */
+ vp = value;
+ while (*vp && isspace (*vp))
+ vp++;
+ if (*vp == '\0')
+ continue; /* empty line */
+
+ /*
+ * If this was not a continuation line, add the entry to the database
+ */
+ if (!cont)
+ {
+ Node *p = getnode ();
+ char *kp;
+
+ kp = vp;
+ while (*vp && !isspace (*vp))
+ vp++;
+ *vp++ = '\0'; /* NULL terminate the key */
+ p->type = NDBMNODE;
+ p->key = xstrdup (kp);
+ while (*vp && isspace (*vp))
+ vp++; /* skip whitespace to value */
+ if (*vp == '\0')
+ {
+ error (0, 0, "warning: NULL value for key `%s'", p->key);
+ freenode (p);
+ continue;
+ }
+ p->data = xstrdup (vp);
+ if (addnode (list, p) == -1)
+ {
+ error (0, 0, "duplicate key found for `%s'", p->key);
+ freenode (p);
+ }
+ }
+ }
+ free (line);
+}
+
+#endif /* MY_NDBM */
diff --git a/contrib/cvs/src/myndbm.h b/contrib/cvs/src/myndbm.h
new file mode 100644
index 0000000..0431e15
--- /dev/null
+++ b/contrib/cvs/src/myndbm.h
@@ -0,0 +1,47 @@
+/* $CVSid: @(#)myndbm.h 1.4 94/09/21 $ */
+
+#ifdef MY_NDBM
+
+#define DBLKSIZ 4096
+
+typedef struct
+{
+ List *dbm_list; /* cached database */
+ Node *dbm_next; /* next key to return for nextkey() */
+
+ /* Name of the file to write to if modified is set. malloc'd. */
+ char *name;
+
+ /* Nonzero if the database has been modified and dbm_close needs to
+ write it out to disk. */
+ int modified;
+} DBM;
+
+typedef struct
+{
+ char *dptr;
+ int dsize;
+} datum;
+
+/*
+ * So as not to conflict with other dbm_open, etc., routines that may
+ * be included by someone's libc, all of my emulation routines are prefixed
+ * by "my" and we define the "standard" ones to be "my" ones here.
+ */
+#define dbm_open mydbm_open
+#define dbm_close mydbm_close
+#define dbm_fetch mydbm_fetch
+#define dbm_firstkey mydbm_firstkey
+#define dbm_nextkey mydbm_nextkey
+#define dbm_store mydbm_store
+#define DBM_INSERT 0
+#define DBM_REPLACE 1
+
+DBM *mydbm_open PROTO((char *file, int flags, int mode));
+void mydbm_close PROTO((DBM * db));
+datum mydbm_fetch PROTO((DBM * db, datum key));
+datum mydbm_firstkey PROTO((DBM * db));
+datum mydbm_nextkey PROTO((DBM * db));
+extern int mydbm_store PROTO ((DBM *, datum, datum, int));
+
+#endif /* MY_NDBM */
diff --git a/contrib/cvs/src/no_diff.c b/contrib/cvs/src/no_diff.c
new file mode 100644
index 0000000..a0d00f5
--- /dev/null
+++ b/contrib/cvs/src/no_diff.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * No Difference
+ *
+ * The user file looks modified judging from its time stamp; however it needn't
+ * be. No_difference() finds out whether it is or not. If it is not, it
+ * updates the administration.
+ *
+ * returns 0 if no differences are found and non-zero otherwise
+ */
+
+#include "cvs.h"
+
+int
+No_Difference (file, vers, entries, repository, update_dir)
+ char *file;
+ Vers_TS *vers;
+ List *entries;
+ char *repository;
+ char *update_dir;
+{
+ Node *p;
+ char tmp[L_tmpnam+1];
+ int ret;
+ char *ts, *options;
+ int retcode = 0;
+ char *tocvsPath;
+
+ if (!vers->srcfile || !vers->srcfile->path)
+ return (-1); /* different since we couldn't tell */
+
+ if (vers->entdata && vers->entdata->options)
+ options = xstrdup (vers->entdata->options);
+ else
+ options = xstrdup ("");
+
+ retcode = RCS_checkout (vers->srcfile->path, NULL, vers->vn_user, options,
+ tmpnam (tmp), 0, 0);
+ if (retcode == 0)
+ {
+#if 0
+ /* Why would we want to munge the modes? And only if the timestamps
+ are different? And even for commands like "cvs status"???? */
+ if (!iswritable (file)) /* fix the modes as a side effect */
+ xchmod (file, 1);
+#endif
+
+ tocvsPath = wrap_tocvs_process_file (file);
+
+ /* do the byte by byte compare */
+ if (xcmp (tocvsPath == NULL ? file : tocvsPath, tmp) == 0)
+ {
+#if 0
+ /* Why would we want to munge the modes? And only if the
+ timestamps are different? And even for commands like
+ "cvs status"???? */
+ if (cvswrite == FALSE) /* fix the modes as a side effect */
+ xchmod (file, 0);
+#endif
+
+ /* no difference was found, so fix the entries file */
+ ts = time_stamp (file);
+ Register (entries, file,
+ vers->vn_user ? vers->vn_user : vers->vn_rcs, ts,
+ options, vers->tag, vers->date, (char *) 0);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ /* We need to update the entries line on the client side. */
+ server_update_entries
+ (file, update_dir, repository, SERVER_UPDATED);
+ }
+#endif
+ free (ts);
+
+ /* update the entdata pointer in the vers_ts structure */
+ p = findnode (entries, file);
+ vers->entdata = (Entnode *) p->data;
+
+ ret = 0;
+ }
+ else
+ ret = 1; /* files were really different */
+ if (tocvsPath)
+ {
+ /* Need to call unlink myself because the noexec variable
+ * has been set to 1. */
+ if (trace)
+ (void) fprintf (stderr, "%c-> unlink (%s)\n",
+#ifdef SERVER_SUPPORT
+ (server_active) ? 'S' : ' ',
+#else
+ ' ',
+#endif
+ tocvsPath);
+ if (unlink (tocvsPath) < 0)
+ error (0, errno, "could not remove %s", tocvsPath);
+ }
+ }
+ else
+ {
+ if (update_dir[0] == '\0')
+ error (0, retcode == -1 ? errno : 0,
+ "could not check out revision %s of %s",
+ vers->vn_user, file);
+ else
+ error (0, retcode == -1 ? errno : 0,
+ "could not check out revision %s of %s/%s",
+ vers->vn_user, update_dir, file);
+ ret = -1; /* different since we couldn't tell */
+ }
+
+ if (trace)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> unlink2 (%s)\n",
+ (server_active) ? 'S' : ' ', tmp);
+#else
+ (void) fprintf (stderr, "-> unlink (%s)\n", tmp);
+#endif
+ if (unlink (tmp) < 0)
+ error (0, errno, "could not remove %s", tmp);
+ free (options);
+ return (ret);
+}
diff --git a/contrib/cvs/src/options.h.in b/contrib/cvs/src/options.h.in
new file mode 100644
index 0000000..7cb58dc
--- /dev/null
+++ b/contrib/cvs/src/options.h.in
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * This file holds (most of) the configuration tweaks that can be made to
+ * customize CVS for your site. CVS comes configured for a typical SunOS 4.x
+ * environment. The comments for each configurable item are intended to be
+ * self-explanatory. All #defines are tested first to see if an over-riding
+ * option was specified on the "make" command line.
+ *
+ * If special libraries are needed, you will have to edit the Makefile.in file
+ * or the configure script directly. Sorry.
+ */
+
+/*
+ * CVS provides the most features when used in conjunction with the Version-5
+ * release of RCS. Thus, it is the default. This also assumes that GNU diff
+ * Version-1.15 is being used as well -- you will have to configure your RCS
+ * V5 release separately to make this the case. If you do not have RCS V5 and
+ * GNU diff V1.15, comment out this define. You should not try mixing and
+ * matching other combinations of these tools.
+ */
+#ifndef HAVE_RCS5
+#define HAVE_RCS5
+#endif
+
+/*
+ * If, before installing this version of CVS, you were running RCS V4 AND you
+ * are installing this CVS and RCS V5 and GNU diff 1.15 all at the same time,
+ * you should turn on the following define. It only exists to try to do
+ * reasonable things with your existing checked out files when you upgrade to
+ * RCS V5, since the keyword expansion formats have changed with RCS V5.
+ *
+ * If you already have been running with RCS5, or haven't been running with CVS
+ * yet at all, or are sticking with RCS V4 for now, leave the commented out.
+ */
+#ifndef HAD_RCS4
+/* #define HAD_RCS4 */
+#endif
+
+/*
+ * For portability and heterogeneity reasons, CVS is shipped by default using
+ * my own text-file version of the ndbm database library in the src/myndbm.c
+ * file. If you want better performance and are not concerned about
+ * heterogeneous hosts accessing your modules file, turn this option off.
+ */
+#ifndef MY_NDBM
+#define MY_NDBM
+#endif
+
+/*
+ * The "diff" program to execute when creating patch output. This "diff"
+ * must support the "-c" option for context diffing. Specify a full
+ * pathname if your site wants to use a particular diff. Note that unlike
+ * the diff used with RCS, you *must not* supply -a here (doing so will cause
+ * the server to generate patches which patch cannot handle in some cases).
+ *
+ * NOTE: this program is only used for the ``patch'' sub-command (and
+ * for ``update'' if you are using the server). The other commands
+ * use rcsdiff which will use whatever version of diff was specified
+ * when rcsdiff was built on your system.
+ */
+
+#ifndef DIFF
+#define DIFF "diff"
+#endif
+
+/*
+ * The "grep" program to execute when checking to see if a merged file had
+ * any conflicts. This "grep" must support a standard basic
+ * regular expression as an argument. Specify a full pathname if your site
+ * wants to use a particular grep.
+ */
+
+#ifndef GREP
+#define GREP "grep"
+#endif
+
+/*
+ * The "patch" program to run when using the CVS server and accepting
+ * patches across the network. Specify a full pathname if your site
+ * wants to use a particular patch.
+ */
+#ifndef PATCH_PROGRAM
+#define PATCH_PROGRAM "patch"
+#endif
+
+/*
+ * By default, RCS programs are executed with the shell or through execlp(),
+ * so the user's PATH environment variable is searched. If you'd like to
+ * bind all RCS programs to a certain directory (perhaps one not in most
+ * people's PATH) then set the default in RCSBIN_DFLT. Note that setting
+ * this here will cause all RCS programs to be executed from this directory,
+ * unless the user overrides the default with the RCSBIN environment variable
+ * or the "-b" option to CVS.
+ *
+ * If you use the password-authenticating server, then you need to
+ * make sure that the server can find the RCS programs to invoke them.
+ * The authenticating server starts out running as root, and then
+ * switches to run as the appropriate user once authentication is
+ * complete. But no actual shell is ever started by that user, so the
+ * PATH environment variable may not contain the directory with the
+ * RCS binaries, even though if that user logged in normally, PATH
+ * would include the directory.
+ *
+ * One way to solve this problem is to set RCSBIN_DFLT here. An
+ * alternative is to make sure that root has the right directory in
+ * its path already. Another, probably better alternative is to
+ * specify -b in /etc/inetd.conf.
+ *
+ * This define should be either the empty string ("") or a full
+ * pathname to the directory containing all the installed programs
+ * from the RCS distribution.
+ */
+#ifndef RCSBIN_DFLT
+#define RCSBIN_DFLT ""
+#endif
+
+/*
+ * The default editor to use, if one does not specify the "-e" option to cvs,
+ * or does not have an EDITOR environment variable. I set this to just "vi",
+ * and use the shell to find where "vi" actually is. This allows sites with
+ * /usr/bin/vi or /usr/ucb/vi to work equally well (assuming that your PATH
+ * is reasonable).
+ */
+#ifndef EDITOR_DFLT
+#define EDITOR_DFLT "vi"
+#endif
+
+/*
+ * The default umask to use when creating or otherwise setting file or
+ * directory permissions in the repository. Must be a value in the
+ * range of 0 through 0777. For example, a value of 002 allows group
+ * rwx access and world rx access; a value of 007 allows group rwx
+ * access but no world access. This value is overridden by the value
+ * of the CVSUMASK environment variable, which is interpreted as an
+ * octal number.
+ */
+#ifndef UMASK_DFLT
+#define UMASK_DFLT 002
+#endif
+
+/*
+ * The cvs admin command is restricted to the members of the group
+ * CVS_ADMIN_GROUP. If this group does not exist, all users are
+ * allowed to run cvs admin. To disable the cvs admin for all users,
+ * create an empty group CVS_ADMIN_GROUP. To disable access control for
+ * cvs admin, comment out the define below.
+ */
+#ifndef CVS_ADMIN_GROUP
+#define CVS_ADMIN_GROUP "cvsadmin"
+#endif
+
+/*
+ * The Repository file holds the path to the directory within the source
+ * repository that contains the RCS ,v files for each CVS working directory.
+ * This path is either a full-path or a path relative to CVSROOT.
+ *
+ * The only advantage that I can see to having a relative path is that One can
+ * change the physical location of the master source repository, change one's
+ * CVSROOT environment variable, and CVS will work without problems. I
+ * recommend using full-paths.
+ */
+#ifndef RELATIVE_REPOS
+/* #define RELATIVE_REPOS */
+#endif
+
+/*
+ * When committing or importing files, you must enter a log message.
+ * Normally, you can do this either via the -m flag on the command line or an
+ * editor will be started for you. If you like to use logging templates (the
+ * rcsinfo file within the $CVSROOT/CVSROOT directory), you might want to
+ * force people to use the editor even if they specify a message with -m.
+ * Enabling FORCE_USE_EDITOR will cause the -m message to be appended to the
+ * temp file when the editor is started.
+ */
+#ifndef FORCE_USE_EDITOR
+/* #define FORCE_USE_EDITOR */
+#endif
+
+/*
+ * When locking the repository, some sites like to remove locks and assume
+ * the program that created them went away if the lock has existed for a long
+ * time. This used to be the default for previous versions of CVS. CVS now
+ * attempts to be much more robust, so lock files should not be left around
+ * by mistake. The new behaviour will never remove old locks (they must now
+ * be removed by hand). Enabling CVS_FUDGELOCKS will cause CVS to remove
+ * locks that are older than CVSLCKAGE seconds.
+ * Use of this option is NOT recommended.
+ */
+#ifndef CVS_FUDGELOCKS
+/* #define CVS_FUDGELOCKS */
+#endif
+
+/*
+ * When committing a permanent change, CVS and RCS make a log entry of
+ * who committed the change. If you are committing the change logged in
+ * as "root" (not under "su" or other root-priv giving program), CVS/RCS
+ * cannot determine who is actually making the change.
+ *
+ * As such, by default, CVS disallows changes to be committed by users
+ * logged in as "root". You can disable this option by commenting
+ * out the lines below.
+ */
+#ifndef CVS_BADROOT
+#define CVS_BADROOT
+#endif
+
+/*
+ * The "cvs diff" command accepts all the single-character options that GNU
+ * diff (1.15) accepts. Except -D. GNU diff uses -D as a way to put
+ * cpp-style #define's around the output differences. CVS, by default, uses
+ * -D to specify a free-form date (like "cvs diff -D '1 week ago'"). If
+ * you would prefer that the -D option of "cvs diff" work like the GNU diff
+ * option, then comment out this define.
+ */
+#ifndef CVS_DIFFDATE
+#define CVS_DIFFDATE
+#endif
+
+/* Define this to enable the SETXID support. The way to use this is
+ to create a group with no users in it (except perhaps cvs
+ administrators), set the cvs executable to setgid that group, chown
+ all the repository files to that group, and change all directory
+ permissions in the repository to 770. The last person to modify a
+ file will own it, but as long as directory permissions are set
+ right that won't matter. You'll need a system which inherits file
+ groups from the parent directory. I don't know how carefully this
+ has been inspected for security holes. */
+
+#ifndef SETXID_SUPPORT
+/* #define SETXID_SUPPORT */
+#endif
+
+/* Should we build the password-authenticating client? Whether to
+ include the password-authenticating _server_, on the other hand, is
+ set in config.h. */
+#define AUTH_CLIENT_SUPPORT 1
+
+/*
+ * If you are working with a large remote repository and a 'cvs checkout' is
+ * swamping your network and memory, define these to enable flow control.
+ * You will end up with even less guarantees of a consistant checkout,
+ * but that may be better than no checkout at all. The master server process
+ * will monitor how far it is getting behind, if it reaches the high water
+ * mark, it will signal the child process to stop generating data when
+ * convenient (ie: no locks are held, currently at the beginning of a
+ * new directory). Once the buffer has drained sufficiently to reach the
+ * low water mark, it will be signalled to start again.
+ * -- EXPERIMENTAL! -- A better solution may be in the works.
+ * You may override the default hi/low watermarks here too.
+ */
+#ifndef SERVER_FLOWCONTROL
+/* #define SERVER_FLOWCONTROL */
+/* #define SERVER_HI_WATER (2 * 1024 * 1024) */
+/* #define SERVER_LO_WATER (1 * 1024 * 1024) */
+#endif
+
+/* End of CVS configuration section */
+
+/*
+ * Externs that are included in libc, but are used frequently enough to
+ * warrant defining here.
+ */
+#ifndef STDC_HEADERS
+extern void exit ();
+#endif
+
+#ifndef getwd
+extern char *getwd ();
+#endif
+
diff --git a/contrib/cvs/src/parseinfo.c b/contrib/cvs/src/parseinfo.c
new file mode 100644
index 0000000..c567ef8
--- /dev/null
+++ b/contrib/cvs/src/parseinfo.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ */
+
+#include "cvs.h"
+
+/*
+ * Parse the INFOFILE file for the specified REPOSITORY. Invoke CALLPROC for
+ * the first line in the file that matches the REPOSITORY, or if ALL != 0, any lines
+ * matching "ALL", or if no lines match, the last line matching "DEFAULT".
+ *
+ * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure.
+ */
+int
+Parse_Info (infofile, repository, callproc, all)
+ char *infofile;
+ char *repository;
+ CALLPROC callproc;
+ int all;
+{
+ int err = 0;
+ FILE *fp_info;
+ char infopath[PATH_MAX];
+ char line[MAXLINELEN];
+ char *default_value = NULL;
+ char *expanded_value= NULL;
+ int callback_done, line_number;
+ char *cp, *exp, *value, *srepos;
+ const char *regex_err;
+
+ if (CVSroot == NULL)
+ {
+ /* XXX - should be error maybe? */
+ error (0, 0, "CVSROOT variable not set");
+ return (1);
+ }
+
+ /* find the info file and open it */
+ (void) sprintf (infopath, "%s/%s/%s", CVSroot,
+ CVSROOTADM, infofile);
+ if ((fp_info = fopen (infopath, "r")) == NULL)
+ return (0); /* no file -> nothing special done */
+
+ /* strip off the CVSROOT if repository was absolute */
+ srepos = Short_Repository (repository);
+
+ if (trace)
+ (void) fprintf (stderr, "-> ParseInfo(%s, %s, %s)\n",
+ infopath, srepos, all ? "ALL" : "not ALL");
+
+ /* search the info file for lines that match */
+ callback_done = line_number = 0;
+ while (fgets (line, sizeof (line), fp_info) != NULL)
+ {
+ line_number++;
+
+ /* skip lines starting with # */
+ if (line[0] == '#')
+ continue;
+
+ /* skip whitespace at beginning of line */
+ for (cp = line; *cp && isspace (*cp); cp++)
+ ;
+
+ /* if *cp is null, the whole line was blank */
+ if (*cp == '\0')
+ continue;
+
+ /* the regular expression is everything up to the first space */
+ for (exp = cp; *cp && !isspace (*cp); cp++)
+ ;
+ if (*cp != '\0')
+ *cp++ = '\0';
+
+ /* skip whitespace up to the start of the matching value */
+ while (*cp && isspace (*cp))
+ cp++;
+
+ /* no value to match with the regular expression is an error */
+ if (*cp == '\0')
+ {
+ error (0, 0, "syntax error at line %d file %s; ignored",
+ line_number, infofile);
+ continue;
+ }
+ value = cp;
+
+ /* strip the newline off the end of the value */
+ if ((cp = strrchr (value, '\n')) != NULL)
+ *cp = '\0';
+
+ expanded_value = expand_path (value, infofile, line_number);
+ if (!expanded_value)
+ {
+ continue;
+ }
+
+ /*
+ * At this point, exp points to the regular expression, and value
+ * points to the value to call the callback routine with. Evaluate
+ * the regular expression against srepos and callback with the value
+ * if it matches.
+ */
+
+ /* save the default value so we have it later if we need it */
+ if (strcmp (exp, "DEFAULT") == 0)
+ {
+ default_value = xstrdup (expanded_value);
+ continue;
+ }
+
+ /*
+ * For a regular expression of "ALL", do the callback always We may
+ * execute lots of ALL callbacks in addition to *one* regular matching
+ * callback or default
+ */
+ if (strcmp (exp, "ALL") == 0)
+ {
+ if (all)
+ err += callproc (repository, expanded_value);
+ else
+ error(0, 0, "Keyword `ALL' is ignored at line %d in %s file",
+ line_number, infofile);
+ continue;
+ }
+
+ if (callback_done)
+ /* only first matching, plus "ALL"'s */
+ continue;
+
+ /* see if the repository matched this regular expression */
+ if ((regex_err = re_comp (exp)) != NULL)
+ {
+ error (0, 0, "bad regular expression at line %d file %s: %s",
+ line_number, infofile, regex_err);
+ continue;
+ }
+ if (re_exec (srepos) == 0)
+ continue; /* no match */
+
+ /* it did, so do the callback and note that we did one */
+ err += callproc (repository, expanded_value);
+ callback_done = 1;
+ }
+ (void) fclose (fp_info);
+
+ /* if we fell through and didn't callback at all, do the default */
+ if (callback_done == 0 && default_value != NULL)
+ err += callproc (repository, default_value);
+
+ /* free up space if necessary */
+ if (default_value != NULL)
+ free (default_value);
+ if (expanded_value != NULL)
+ free (expanded_value);
+
+ return (err);
+}
diff --git a/contrib/cvs/src/patch.c b/contrib/cvs/src/patch.c
new file mode 100644
index 0000000..39b4e64
--- /dev/null
+++ b/contrib/cvs/src/patch.c
@@ -0,0 +1,604 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Patch
+ *
+ * Create a Larry Wall format "patch" file between a previous release and the
+ * current head of a module, or between two releases. Can specify the
+ * release as either a date or a revision number.
+ */
+
+#include "cvs.h"
+#include "getline.h"
+
+static RETSIGTYPE patch_cleanup PROTO((void));
+static Dtype patch_dirproc PROTO((char *dir, char *repos, char *update_dir));
+static int patch_fileproc PROTO((struct file_info *finfo));
+static int patch_proc PROTO((int *pargc, char **argv, char *xwhere,
+ char *mwhere, char *mfile, int shorten,
+ int local_specified, char *mname, char *msg));
+
+static int force_tag_match = 1;
+static int patch_short = 0;
+static int toptwo_diffs = 0;
+static int local = 0;
+static char *options = NULL;
+static char *rev1 = NULL;
+static int rev1_validated = 1;
+static char *rev2 = NULL;
+static int rev2_validated = 1;
+static char *date1 = NULL;
+static char *date2 = NULL;
+static char tmpfile1[L_tmpnam+1], tmpfile2[L_tmpnam+1], tmpfile3[L_tmpnam+1];
+static int unidiff = 0;
+
+static const char *const patch_usage[] =
+{
+ "Usage: %s %s [-fl] [-c|-u] [-s|-t] [-V %%d]\n",
+ " -r rev|-D date [-r rev2 | -D date2] modules...\n",
+ "\t-f\tForce a head revision match if tag/date not found.\n",
+ "\t-l\tLocal directory only, not recursive\n",
+ "\t-c\tContext diffs (default)\n",
+ "\t-u\tUnidiff format.\n",
+ "\t-s\tShort patch - one liner per file.\n",
+ "\t-t\tTop two diffs - last change made to the file.\n",
+ "\t-D date\tDate.\n",
+ "\t-r rev\tRevision - symbolic or numeric.\n",
+ "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n",
+ NULL
+};
+
+int
+patch (argc, argv)
+ int argc;
+ char **argv;
+{
+ register int i;
+ int c;
+ int err = 0;
+ DBM *db;
+
+ if (argc == -1)
+ usage (patch_usage);
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "V:k:cuftsQqlRD:r:")) != -1)
+ {
+ switch (c)
+ {
+ case 'Q':
+ case 'q':
+#ifdef SERVER_SUPPORT
+ /* The CVS 1.5 client sends these options (in addition to
+ Global_option requests), so we must ignore them. */
+ if (!server_active)
+#endif
+ error (1, 0,
+ "-q or -Q must be specified before \"%s\"",
+ command_name);
+ break;
+ case 'f':
+ force_tag_match = 0;
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 't':
+ toptwo_diffs = 1;
+ break;
+ case 's':
+ patch_short = 1;
+ break;
+ case 'D':
+ if (rev2 != NULL || date2 != NULL)
+ error (1, 0,
+ "no more than two revisions/dates can be specified");
+ if (rev1 != NULL || date1 != NULL)
+ date2 = Make_Date (optarg);
+ else
+ date1 = Make_Date (optarg);
+ break;
+ case 'r':
+ if (rev2 != NULL || date2 != NULL)
+ error (1, 0,
+ "no more than two revisions/dates can be specified");
+ if (rev1 != NULL || date1 != NULL)
+ rev2 = optarg;
+ else
+ rev1 = optarg;
+ break;
+ case 'k':
+ if (options)
+ free (options);
+ options = RCS_check_kflag (optarg);
+ break;
+ case 'V':
+ if (atoi (optarg) <= 0)
+ error (1, 0, "must specify a version number to -V");
+ if (options)
+ free (options);
+ options = xmalloc (strlen (optarg) + 1 + 2); /* for the -V */
+ (void) sprintf (options, "-V%s", optarg);
+ break;
+ case 'u':
+ unidiff = 1; /* Unidiff */
+ break;
+ case 'c': /* Context diff */
+ unidiff = 0;
+ break;
+ case '?':
+ default:
+ usage (patch_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Sanity checks */
+ if (argc < 1)
+ usage (patch_usage);
+
+ if (toptwo_diffs && patch_short)
+ error (1, 0, "-t and -s options are mutually exclusive");
+ if (toptwo_diffs && (date1 != NULL || date2 != NULL ||
+ rev1 != NULL || rev2 != NULL))
+ error (1, 0, "must not specify revisions/dates with -t option!");
+
+ if (!toptwo_diffs && (date1 == NULL && date2 == NULL &&
+ rev1 == NULL && rev2 == NULL))
+ error (1, 0, "must specify at least one revision/date!");
+ if (date1 != NULL && date2 != NULL)
+ if (RCS_datecmp (date1, date2) >= 0)
+ error (1, 0, "second date must come after first date!");
+
+ /* if options is NULL, make it a NULL string */
+ if (options == NULL)
+ options = xstrdup ("");
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ /* We're the client side. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ if (local)
+ send_arg("-l");
+ if (force_tag_match)
+ send_arg("-f");
+ if (toptwo_diffs)
+ send_arg("-t");
+ if (patch_short)
+ send_arg("-s");
+ if (unidiff)
+ send_arg("-u");
+
+ if (rev1)
+ option_with_arg ("-r", rev1);
+ if (date1)
+ client_senddate (date1);
+ if (rev2)
+ option_with_arg ("-r", rev2);
+ if (date2)
+ client_senddate (date2);
+ if (options[0] != '\0')
+ send_arg (options);
+
+ {
+ int i;
+ for (i = 0; i < argc; ++i)
+ send_arg (argv[i]);
+ }
+
+ send_to_server ("rdiff\012", 0);
+ return get_responses_and_close ();
+ }
+#endif
+
+ /* clean up if we get a signal */
+#ifdef SIGHUP
+ (void) SIG_register (SIGHUP, patch_cleanup);
+#endif
+#ifdef SIGINT
+ (void) SIG_register (SIGINT, patch_cleanup);
+#endif
+#ifdef SIGQUIT
+ (void) SIG_register (SIGQUIT, patch_cleanup);
+#endif
+#ifdef SIGPIPE
+ (void) SIG_register (SIGPIPE, patch_cleanup);
+#endif
+#ifdef SIGTERM
+ (void) SIG_register (SIGTERM, patch_cleanup);
+#endif
+
+ db = open_module ();
+ for (i = 0; i < argc; i++)
+ err += do_module (db, argv[i], PATCH, "Patching", patch_proc,
+ (char *) NULL, 0, 0, 0, (char *) NULL);
+ close_module (db);
+ free (options);
+ patch_cleanup ();
+ return (err);
+}
+
+/*
+ * callback proc for doing the real work of patching
+ */
+/* ARGSUSED */
+static char where[PATH_MAX];
+static int
+patch_proc (pargc, argv, xwhere, mwhere, mfile, shorten, local_specified,
+ mname, msg)
+ int *pargc;
+ char **argv;
+ char *xwhere;
+ char *mwhere;
+ char *mfile;
+ int shorten;
+ int local_specified;
+ char *mname;
+ char *msg;
+{
+ int err = 0;
+ int which;
+ char repository[PATH_MAX];
+
+ (void) sprintf (repository, "%s/%s", CVSroot, argv[0]);
+ (void) strcpy (where, argv[0]);
+
+ /* if mfile isn't null, we need to set up to do only part of the module */
+ if (mfile != NULL)
+ {
+ char *cp;
+ char path[PATH_MAX];
+
+ /* if the portion of the module is a path, put the dir part on repos */
+ if ((cp = strrchr (mfile, '/')) != NULL)
+ {
+ *cp = '\0';
+ (void) strcat (repository, "/");
+ (void) strcat (repository, mfile);
+ (void) strcat (where, "/");
+ (void) strcat (where, mfile);
+ mfile = cp + 1;
+ }
+
+ /* take care of the rest */
+ (void) sprintf (path, "%s/%s", repository, mfile);
+ if (isdir (path))
+ {
+ /* directory means repository gets the dir tacked on */
+ (void) strcpy (repository, path);
+ (void) strcat (where, "/");
+ (void) strcat (where, mfile);
+ }
+ else
+ {
+ int i;
+
+ /* a file means muck argv */
+ for (i = 1; i < *pargc; i++)
+ free (argv[i]);
+ argv[1] = xstrdup (mfile);
+ (*pargc) = 2;
+ }
+ }
+
+ /* cd to the starting repository */
+ if (chdir (repository) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", repository);
+ return (1);
+ }
+
+ if (force_tag_match)
+ which = W_REPOS | W_ATTIC;
+ else
+ which = W_REPOS;
+
+ if (rev1 != NULL && !rev1_validated)
+ {
+ tag_check_valid (rev1, *pargc - 1, argv + 1, local, 0, NULL);
+ rev1_validated = 1;
+ }
+ if (rev2 != NULL && !rev2_validated)
+ {
+ tag_check_valid (rev2, *pargc - 1, argv + 1, local, 0, NULL);
+ rev2_validated = 1;
+ }
+
+ /* start the recursion processor */
+ err = start_recursion (patch_fileproc, (FILESDONEPROC) NULL, patch_dirproc,
+ (DIRLEAVEPROC) NULL, *pargc - 1, argv + 1, local,
+ which, 0, 1, where, 1, 1);
+
+ return (err);
+}
+
+/*
+ * Called to examine a particular RCS file, as appropriate with the options
+ * that were set above.
+ */
+/* ARGSUSED */
+static int
+patch_fileproc (finfo)
+ struct file_info *finfo;
+{
+ struct utimbuf t;
+ char *vers_tag, *vers_head;
+ char rcsspace[1][PATH_MAX];
+ char *rcs = rcsspace[0];
+ RCSNode *rcsfile;
+ FILE *fp1, *fp2, *fp3;
+ int ret = 0;
+ int isattic = 0;
+ int retcode = 0;
+ char file1[PATH_MAX], file2[PATH_MAX], strippath[PATH_MAX];
+ char *line1, *line2;
+ size_t line1_chars_allocated;
+ size_t line2_chars_allocated;
+ char *cp1, *cp2;
+ FILE *fp;
+
+ /* find the parsed rcs file */
+ if ((rcsfile = finfo->rcs) == NULL)
+ return (1);
+ if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
+ isattic = 1;
+
+ (void) sprintf (rcs, "%s%s", finfo->file, RCSEXT);
+
+ /* if vers_head is NULL, may have been removed from the release */
+ if (isattic && rev2 == NULL && date2 == NULL)
+ vers_head = NULL;
+ else
+ vers_head = RCS_getversion (rcsfile, rev2, date2, force_tag_match, 0);
+
+ if (toptwo_diffs)
+ {
+ if (vers_head == NULL)
+ return (1);
+
+ if (!date1)
+ date1 = xmalloc (50); /* plenty big :-) */
+ *date1 = '\0';
+ if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == -1)
+ {
+ if (!really_quiet)
+ error (0, 0, "cannot find date in rcs file %s revision %s",
+ rcs, vers_head);
+ return (1);
+ }
+ }
+ vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match, 0);
+
+ if (vers_tag == NULL && (vers_head == NULL || isattic))
+ return (0); /* nothing known about specified revs */
+
+ if (vers_tag && vers_head && strcmp (vers_head, vers_tag) == 0)
+ return (0); /* not changed between releases */
+
+ if (patch_short)
+ {
+ (void) printf ("File %s ", finfo->fullname);
+ if (vers_tag == NULL)
+ (void) printf ("is new; current revision %s\n", vers_head);
+ else if (vers_head == NULL)
+ {
+ (void) printf ("is removed; not included in ");
+ if (rev2 != NULL)
+ (void) printf ("release tag %s", rev2);
+ else if (date2 != NULL)
+ (void) printf ("release date %s", date2);
+ else
+ (void) printf ("current release");
+ (void) printf ("\n");
+ }
+ else
+ (void) printf ("changed from revision %s to %s\n",
+ vers_tag, vers_head);
+ return (0);
+ }
+ if ((fp1 = fopen (tmpnam (tmpfile1), "w+")) != NULL)
+ (void) fclose (fp1);
+ if ((fp2 = fopen (tmpnam (tmpfile2), "w+")) != NULL)
+ (void) fclose (fp2);
+ if ((fp3 = fopen (tmpnam (tmpfile3), "w+")) != NULL)
+ (void) fclose (fp3);
+ if (fp1 == NULL || fp2 == NULL || fp3 == NULL)
+ {
+ error (0, 0, "cannot create temporary files");
+ ret = 1;
+ goto out;
+ }
+ if (vers_tag != NULL)
+ {
+ retcode = RCS_checkout (rcsfile->path, NULL, vers_tag, options, tmpfile1,
+ 0, 0);
+ if (retcode != 0)
+ {
+ if (!really_quiet)
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "co of revision %s in %s failed", vers_tag, rcs);
+ ret = 1;
+ goto out;
+ }
+ memset ((char *) &t, 0, sizeof (t));
+ if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_tag,
+ (char *) 0, 0)) != -1)
+ (void) utime (tmpfile1, &t);
+ }
+ else if (toptwo_diffs)
+ {
+ ret = 1;
+ goto out;
+ }
+ if (vers_head != NULL)
+ {
+ retcode = RCS_checkout (rcsfile->path, NULL, vers_head, options, tmpfile2, 0, 0);
+ if (retcode != 0)
+ {
+ if (!really_quiet)
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0,
+ "co of revision %s in %s failed", vers_head, rcs);
+ ret = 1;
+ goto out;
+ }
+ if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_head,
+ (char *) 0, 0)) != -1)
+ (void) utime (tmpfile2, &t);
+ }
+ run_setup ("%s -%c", DIFF, unidiff ? 'u' : 'c');
+ run_arg (tmpfile1);
+ run_arg (tmpfile2);
+
+ line1 = NULL;
+ line1_chars_allocated = 0;
+ line2 = NULL;
+ line2_chars_allocated = 0;
+
+ switch (run_exec (RUN_TTY, tmpfile3, RUN_TTY, RUN_REALLY))
+ {
+ case -1: /* fork/wait failure */
+ error (1, errno, "fork for diff failed on %s", rcs);
+ break;
+ case 0: /* nothing to do */
+ break;
+ case 1:
+ /*
+ * The two revisions are really different, so read the first two
+ * lines of the diff output file, and munge them to include more
+ * reasonable file names that "patch" will understand.
+ */
+
+ /* Output an "Index:" line for patch to use */
+ (void) fflush (stdout);
+ (void) printf ("Index: %s\n", finfo->fullname);
+ (void) fflush (stdout);
+
+ fp = open_file (tmpfile3, "r");
+ if (getline (&line1, &line1_chars_allocated, fp) < 0 ||
+ getline (&line2, &line2_chars_allocated, fp) < 0)
+ {
+ error (0, errno, "failed to read diff file header %s for %s",
+ tmpfile3, rcs);
+ ret = 1;
+ (void) fclose (fp);
+ goto out;
+ }
+ if (!unidiff)
+ {
+ if (strncmp (line1, "*** ", 4) != 0 ||
+ strncmp (line2, "--- ", 4) != 0 ||
+ (cp1 = strchr (line1, '\t')) == NULL ||
+ (cp2 = strchr (line2, '\t')) == NULL)
+ {
+ error (0, 0, "invalid diff header for %s", rcs);
+ ret = 1;
+ (void) fclose (fp);
+ goto out;
+ }
+ }
+ else
+ {
+ if (strncmp (line1, "--- ", 4) != 0 ||
+ strncmp (line2, "+++ ", 4) != 0 ||
+ (cp1 = strchr (line1, '\t')) == NULL ||
+ (cp2 = strchr (line2, '\t')) == NULL)
+ {
+ error (0, 0, "invalid unidiff header for %s", rcs);
+ ret = 1;
+ (void) fclose (fp);
+ goto out;
+ }
+ }
+ if (CVSroot != NULL)
+ (void) sprintf (strippath, "%s/", CVSroot);
+ else
+ (void) strcpy (strippath, REPOS_STRIP);
+ if (strncmp (rcs, strippath, strlen (strippath)) == 0)
+ rcs += strlen (strippath);
+ if (vers_tag != NULL)
+ {
+ (void) sprintf (file1, "%s:%s", finfo->fullname, vers_tag);
+ }
+ else
+ {
+ (void) strcpy (file1, DEVNULL);
+ }
+ (void) sprintf (file2, "%s:%s", finfo->fullname,
+ vers_head ? vers_head : "removed");
+
+ /* Note that this prints "diff" not DIFF. The format of a diff
+ does not depend on the name of the program which happens to
+ have produced it. */
+ if (unidiff)
+ {
+ (void) printf ("diff -u %s %s\n", file1, file2);
+ (void) printf ("--- %s%s+++ ", file1, cp1);
+ }
+ else
+ {
+ (void) printf ("diff -c %s %s\n", file1, file2);
+ (void) printf ("*** %s%s--- ", file1, cp1);
+ }
+
+ (void) printf ("%s%s", finfo->fullname, cp2);
+ /* spew the rest of the diff out */
+ while (getline (&line1, &line1_chars_allocated, fp) >= 0)
+ (void) fputs (line1, stdout);
+ (void) fclose (fp);
+ break;
+ default:
+ error (0, 0, "diff failed for %s", finfo->fullname);
+ }
+ out:
+ if (line1)
+ free (line1);
+ if (line2)
+ free (line2);
+ /* FIXME: should be checking for errors. */
+ (void) unlink (tmpfile1);
+ (void) unlink (tmpfile2);
+ (void) unlink (tmpfile3);
+ return (ret);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+patch_dirproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "Diffing %s", update_dir);
+ return (R_PROCESS);
+}
+
+/*
+ * Clean up temporary files
+ */
+static RETSIGTYPE
+patch_cleanup ()
+{
+ if (tmpfile1[0] != '\0')
+ (void) unlink_file (tmpfile1);
+ if (tmpfile2[0] != '\0')
+ (void) unlink_file (tmpfile2);
+ if (tmpfile3[0] != '\0')
+ (void) unlink_file (tmpfile3);
+}
diff --git a/contrib/cvs/src/rcs.c b/contrib/cvs/src/rcs.c
new file mode 100644
index 0000000..c68c255
--- /dev/null
+++ b/contrib/cvs/src/rcs.c
@@ -0,0 +1,2262 @@
+/*
+ * 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.
+ *
+ * The routines contained in this file do all the rcs file parsing and
+ * manipulation
+ */
+
+#include <assert.h>
+#include "cvs.h"
+
+static RCSNode *RCS_parsercsfile_i PROTO((FILE * fp, const char *rcsfile));
+static char *RCS_getdatebranch PROTO((RCSNode * rcs, char *date, char *branch));
+static int getrcskey PROTO((FILE * fp, char **keyp, char **valp));
+static int checkmagic_proc PROTO((Node *p, void *closure));
+static void do_branches PROTO((List * list, char *val));
+static void do_symbols PROTO((List * list, char *val));
+static void rcsvers_delproc PROTO((Node * p));
+
+/*
+ * We don't want to use isspace() from the C library because:
+ *
+ * 1. The definition of "whitespace" in RCS files includes ASCII
+ * backspace, but the C locale doesn't.
+ * 2. isspace is an very expensive function call in some implementations
+ * due to the addition of wide character support.
+ */
+static const char spacetab[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, /* 0x00 - 0x0f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x8f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 0xf0 - 0xff */
+};
+
+#define whitespace(c) (spacetab[(unsigned char)c] != 0)
+
+
+/*
+ * Parse an rcsfile given a user file name and a repository
+ */
+RCSNode *
+RCS_parse (file, repos)
+ const char *file;
+ const char *repos;
+{
+ RCSNode *rcs;
+ FILE *fp;
+ char rcsfile[PATH_MAX];
+
+ (void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT);
+ if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) != NULL)
+ {
+ rcs = RCS_parsercsfile_i(fp, rcsfile);
+ if (rcs != NULL)
+ rcs->flags |= VALID;
+
+ fclose (fp);
+ return (rcs);
+ }
+ else if (! existence_error (errno))
+ {
+ error (0, errno, "cannot open %s", rcsfile);
+ return NULL;
+ }
+
+ (void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC, file, RCSEXT);
+ if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) != NULL)
+ {
+ rcs = RCS_parsercsfile_i(fp, rcsfile);
+ if (rcs != NULL)
+ {
+ rcs->flags |= INATTIC;
+ rcs->flags |= VALID;
+ }
+
+ fclose (fp);
+ return (rcs);
+ }
+ else if (! existence_error (errno))
+ {
+ error (0, errno, "cannot open %s", rcsfile);
+ return NULL;
+ }
+
+ return (NULL);
+}
+
+/*
+ * Parse a specific rcsfile.
+ */
+RCSNode *
+RCS_parsercsfile (rcsfile)
+ char *rcsfile;
+{
+ FILE *fp;
+ RCSNode *rcs;
+
+ /* open the rcsfile */
+ if ((fp = fopen (rcsfile, FOPEN_BINARY_READ)) == NULL)
+ {
+ error (0, errno, "Couldn't open rcs file `%s'", rcsfile);
+ return (NULL);
+ }
+
+ rcs = RCS_parsercsfile_i (fp, rcsfile);
+
+ fclose (fp);
+ return (rcs);
+}
+
+
+/*
+ */
+static RCSNode *
+RCS_parsercsfile_i (fp, rcsfile)
+ FILE *fp;
+ const char *rcsfile;
+{
+ RCSNode *rdata;
+ char *key, *value;
+
+ /* make a node */
+ rdata = (RCSNode *) xmalloc (sizeof (RCSNode));
+ memset ((char *) rdata, 0, sizeof (RCSNode));
+ rdata->refcount = 1;
+ rdata->path = xstrdup (rcsfile);
+
+ /* Process HEAD and BRANCH keywords from the RCS header.
+ *
+ * Most cvs operatations on the main branch don't need any more
+ * information. Those that do call XXX to completely parse the
+ * RCS file. */
+
+ if (getrcskey (fp, &key, &value) == -1 || key == NULL)
+ goto l_error;
+ if (strcmp (key, RCSDESC) == 0)
+ goto l_error;
+
+ if (strcmp (RCSHEAD, key) == 0 && value != NULL)
+ rdata->head = xstrdup (value);
+
+ if (getrcskey (fp, &key, &value) == -1 || key == NULL)
+ goto l_error;
+ if (strcmp (key, RCSDESC) == 0)
+ goto l_error;
+
+ if (strcmp (RCSBRANCH, key) == 0 && value != NULL)
+ {
+ char *cp;
+
+ rdata->branch = xstrdup (value);
+ if ((numdots (rdata->branch) & 1) != 0)
+ {
+ /* turn it into a branch if it's a revision */
+ cp = strrchr (rdata->branch, '.');
+ *cp = '\0';
+ }
+ }
+
+ rdata->flags |= PARTIAL;
+ return rdata;
+
+l_error:
+ if (!really_quiet)
+ {
+ if (ferror(fp))
+ {
+ error (1, 0, "error reading `%s'", rcsfile);
+ }
+ else
+ {
+ error (0, 0, "`%s' does not appear to be a valid rcs file",
+ rcsfile);
+ }
+ }
+ freercsnode (&rdata);
+ return (NULL);
+}
+
+
+/* Do the real work of parsing an RCS file.
+
+ On error, die with a fatal error; if it returns at all it was successful.
+
+ If PFP is NULL, close the file when done. Otherwise, leave it open
+ and store the FILE * in *PFP. */
+static void
+RCS_reparsercsfile (rdata, pfp)
+ RCSNode *rdata;
+ FILE **pfp;
+{
+ FILE *fp;
+ char *rcsfile;
+
+ Node *q;
+ RCSVers *vnode;
+ int n;
+ char *cp;
+ char *key, *value;
+
+ assert (rdata != NULL);
+ rcsfile = rdata->path;
+
+ fp = fopen(rcsfile, FOPEN_BINARY_READ);
+ if (fp == NULL)
+ error (1, 0, "unable to reopen `%s'", rcsfile);
+
+ /* make a node */
+ rdata->versions = getlist ();
+
+ /*
+ * process all the special header information, break out when we get to
+ * the first revision delta
+ */
+ for (;;)
+ {
+ /* get the next key/value pair */
+
+ /* if key is NULL here, then the file is missing some headers
+ or we had trouble reading the file. */
+ if (getrcskey (fp, &key, &value) == -1 || key == NULL
+ || strcmp (key, RCSDESC) == 0)
+ {
+ if (ferror(fp))
+ {
+ error (1, 0, "error reading `%s'", rcsfile);
+ }
+ else
+ {
+ error (1, 0, "`%s' does not appear to be a valid rcs file",
+ rcsfile);
+ }
+ }
+
+ if (strcmp (RCSSYMBOLS, key) == 0)
+ {
+ if (value != NULL)
+ {
+ rdata->symbols_data = xstrdup(value);
+ continue;
+ }
+ }
+
+ if (strcmp (RCSEXPAND, key) == 0)
+ {
+ rdata->expand = xstrdup (value);
+ continue;
+ }
+
+ /*
+ * check key for '.''s and digits (probably a rev) if it is a
+ * revision, we are done with the headers and are down to the
+ * revision deltas, so we break out of the loop
+ */
+ for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
+ /* do nothing */ ;
+ if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
+ break;
+
+ /* if we haven't grabbed it yet, we didn't want it */
+ }
+
+ /*
+ * we got out of the loop, so we have the first part of the first
+ * revision delta in our hand key=the revision and value=the date key and
+ * its value
+ */
+ for (;;)
+ {
+ char *valp;
+
+ vnode = (RCSVers *) xmalloc (sizeof (RCSVers));
+ memset (vnode, 0, sizeof (RCSVers));
+
+ /* fill in the version before we forget it */
+ vnode->version = xstrdup (key);
+
+ /* grab the value of the date from value */
+ valp = value + strlen (RCSDATE);/* skip the "date" keyword */
+ while (whitespace (*valp)) /* take space off front of value */
+ valp++;
+
+ vnode->date = xstrdup (valp);
+
+ /* Get author field. */
+ (void) getrcskey (fp, &key, &value);
+ /* FIXME: should be using errno in case of ferror. */
+ if (key == NULL || strcmp (key, "author") != 0)
+ error (1, 0, "\
+unable to parse rcs file; `author' not in the expected place");
+ vnode->author = xstrdup (value);
+
+ /* Get state field. */
+ (void) getrcskey (fp, &key, &value);
+ /* FIXME: should be using errno in case of ferror. */
+ if (key == NULL || strcmp (key, "state") != 0)
+ error (1, 0, "\
+unable to parse rcs file; `state' not in the expected place");
+ if (strcmp (value, "dead") == 0)
+ {
+ vnode->dead = 1;
+ }
+
+ /* fill in the branch list (if any branches exist) */
+ (void) getrcskey (fp, &key, &value);
+ /* FIXME: should be handling various error conditions better. */
+ if (key != NULL && strcmp (key, RCSDESC) == 0)
+ value = NULL;
+ if (value != (char *) NULL)
+ {
+ vnode->branches = getlist ();
+ do_branches (vnode->branches, value);
+ }
+
+ /* fill in the next field if there is a next revision */
+ (void) getrcskey (fp, &key, &value);
+ /* FIXME: should be handling various error conditions better. */
+ if (key != NULL && strcmp (key, RCSDESC) == 0)
+ value = NULL;
+ if (value != (char *) NULL)
+ vnode->next = xstrdup (value);
+
+ /*
+ * at this point, we skip any user defined fields XXX - this is where
+ * we put the symbolic link stuff???
+ */
+ /* FIXME: Does not correctly handle errors, e.g. from stdio. */
+ while ((n = getrcskey (fp, &key, &value)) >= 0)
+ {
+ assert (key != NULL);
+
+ if (strcmp (key, RCSDESC) == 0)
+ {
+ n = -1;
+ break;
+ }
+
+ /* Enable use of repositories created by certain obsolete
+ versions of CVS. This code should remain indefinately;
+ there is no procedure for converting old repositories, and
+ checking for it is harmless. */
+ if (strcmp(key, RCSDEAD) == 0)
+ {
+ vnode->dead = 1;
+ continue;
+ }
+ /* if we have a revision, break and do it */
+ for (cp = key; (isdigit (*cp) || *cp == '.') && *cp != '\0'; cp++)
+ /* do nothing */ ;
+ if (*cp == '\0' && strncmp (RCSDATE, value, strlen (RCSDATE)) == 0)
+ break;
+ }
+
+ /* get the node */
+ q = getnode ();
+ q->type = RCSVERS;
+ q->delproc = rcsvers_delproc;
+ q->data = (char *) vnode;
+ q->key = vnode->version;
+
+ /* add the nodes to the list */
+ if (addnode (rdata->versions, q) != 0)
+ {
+#if 0
+ purify_printf("WARNING: Adding duplicate version: %s (%s)\n",
+ q->key, rcsfile);
+ freenode (q);
+#endif
+ }
+
+ /*
+ * if we left the loop because there were no more keys, we break out
+ * of the revision processing loop
+ */
+ if (n < 0)
+ break;
+ }
+
+ if (pfp == NULL)
+ {
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", rcsfile);
+ }
+ else
+ {
+ *pfp = fp;
+ }
+ rdata->flags &= ~PARTIAL;
+}
+
+/*
+ * freercsnode - free up the info for an RCSNode
+ */
+void
+freercsnode (rnodep)
+ RCSNode **rnodep;
+{
+ if (rnodep == NULL || *rnodep == NULL)
+ return;
+
+ ((*rnodep)->refcount)--;
+ if ((*rnodep)->refcount != 0)
+ {
+ *rnodep = (RCSNode *) NULL;
+ return;
+ }
+ free ((*rnodep)->path);
+ dellist (&(*rnodep)->versions);
+ if ((*rnodep)->symbols != (List *) NULL)
+ dellist (&(*rnodep)->symbols);
+ if ((*rnodep)->symbols_data != (char *) NULL)
+ free ((*rnodep)->symbols_data);
+ if ((*rnodep)->expand != NULL)
+ free ((*rnodep)->expand);
+ if ((*rnodep)->head != (char *) NULL)
+ free ((*rnodep)->head);
+ if ((*rnodep)->branch != (char *) NULL)
+ free ((*rnodep)->branch);
+ free ((char *) *rnodep);
+ *rnodep = (RCSNode *) NULL;
+}
+
+/*
+ * rcsvers_delproc - free up an RCSVers type node
+ */
+static void
+rcsvers_delproc (p)
+ Node *p;
+{
+ RCSVers *rnode;
+
+ rnode = (RCSVers *) p->data;
+
+ if (rnode->branches != (List *) NULL)
+ dellist (&rnode->branches);
+ if (rnode->date != (char *) NULL)
+ free (rnode->date);
+ if (rnode->next != (char *) NULL)
+ free (rnode->next);
+ free ((char *) rnode);
+}
+
+/*
+ * getrcskey - fill in the key and value from the rcs file the algorithm is
+ * as follows
+ *
+ * o skip whitespace o fill in key with everything up to next white
+ * space or semicolon
+ * o if key == "desc" then key and data are NULL and return -1
+ * o if key wasn't terminated by a semicolon, skip white space and fill
+ * in value with everything up to a semicolon
+ * o compress all whitespace down to a single space
+ * o if a word starts with @, do funky rcs processing
+ * o strip whitespace off end of value or set value to NULL if it empty
+ * o return 0 since we found something besides "desc"
+ *
+ * Sets *KEYP and *VALUEP to point to storage managed by the getrcskey
+ * function; the contents are only valid until the next call to getrcskey
+ * or getrcsrev.
+ */
+
+static char *key = NULL;
+static char *value = NULL;
+static size_t keysize = 0;
+static size_t valsize = 0;
+
+#define ALLOCINCR 1024
+
+static int
+getrcskey (fp, keyp, valp)
+ FILE *fp;
+ char **keyp;
+ char **valp;
+{
+ char *cur, *max;
+ int c;
+
+ /* skip leading whitespace */
+ do
+ {
+ c = getc (fp);
+ if (c == EOF)
+ {
+ *keyp = (char *) NULL;
+ *valp = (char *) NULL;
+ return (-1);
+ }
+ } while (whitespace (c));
+
+ /* fill in key */
+ cur = key;
+ max = key + keysize;
+ while (!whitespace (c) && c != ';')
+ {
+ if (cur >= max)
+ {
+ key = xrealloc (key, keysize + ALLOCINCR);
+ cur = key + keysize;
+ keysize += ALLOCINCR;
+ max = key + keysize;
+ }
+ *cur++ = c;
+
+ c = getc (fp);
+ if (c == EOF)
+ {
+ *keyp = (char *) NULL;
+ *valp = (char *) NULL;
+ return (-1);
+ }
+ }
+ if (cur >= max)
+ {
+ key = xrealloc (key, keysize + ALLOCINCR);
+ cur = key + keysize;
+ keysize += ALLOCINCR;
+ max = key + keysize;
+ }
+ *cur = '\0';
+
+ /* skip whitespace between key and val */
+ while (whitespace (c))
+ {
+ c = getc (fp);
+ if (c == EOF)
+ {
+ *keyp = (char *) NULL;
+ *valp = (char *) NULL;
+ return (-1);
+ }
+ }
+
+ /* if we ended key with a semicolon, there is no value */
+ if (c == ';')
+ {
+ *keyp = key;
+ *valp = (char *) NULL;
+ return (0);
+ }
+
+ /* otherwise, there might be a value, so fill it in */
+ cur = value;
+ max = value + valsize;
+
+ /* process the value */
+ for (;;)
+ {
+ /* handle RCS "strings" */
+ if (c == '@')
+ {
+ for (;;)
+ {
+ c = getc (fp);
+ if (c == EOF)
+ {
+ *keyp = (char *) NULL;
+ *valp = (char *) NULL;
+ return (-1);
+ }
+
+ if (c == '@')
+ {
+ c = getc (fp);
+ if (c == EOF)
+ {
+ *keyp = (char *) NULL;
+ *valp = (char *) NULL;
+ return (-1);
+ }
+
+ if (c != '@')
+ break;
+ }
+
+ if (cur >= max)
+ {
+ value = xrealloc (value, valsize + ALLOCINCR);
+ cur = value + valsize;
+ valsize += ALLOCINCR;
+ max = value + valsize;
+ }
+ *cur++ = c;
+ }
+ }
+
+ /* The syntax for some key-value pairs is different; they
+ don't end with a semicolon. */
+ if (strcmp (key, RCSDESC) == 0
+ || strcmp (key, "text") == 0
+ || strcmp (key, "log") == 0)
+ break;
+
+ /* compress whitespace down to a single space */
+ if (whitespace (c))
+ {
+ do {
+ c = getc (fp);
+ if (c == EOF)
+ {
+ *keyp = (char *) NULL;
+ *valp = (char *) NULL;
+ return (-1);
+ }
+ } while (whitespace (c));
+
+ if (cur >= max)
+ {
+ value = xrealloc (value, valsize + ALLOCINCR);
+ cur = value + valsize;
+ valsize += ALLOCINCR;
+ max = value + valsize;
+ }
+ *cur++ = ' ';
+ }
+
+ /* if we got a semi-colon we are done with the entire value */
+ if (c == ';')
+ break;
+
+ if (cur >= max)
+ {
+ value = xrealloc (value, valsize + ALLOCINCR);
+ cur = value + valsize;
+ valsize += ALLOCINCR;
+ max = value + valsize;
+ }
+ *cur++ = c;
+
+ c = getc (fp);
+ if (c == EOF)
+ {
+ *keyp = (char *) NULL;
+ *valp = (char *) NULL;
+ return (-1);
+ }
+ }
+
+ /* terminate the string */
+ if (cur >= max)
+ {
+ value = xrealloc (value, valsize + ALLOCINCR);
+ cur = value + valsize;
+ valsize += ALLOCINCR;
+ max = value + valsize;
+ }
+ *cur = '\0';
+
+ /* if the string is empty, make it null */
+ if (value && *value != '\0')
+ *valp = value;
+ else
+ *valp = NULL;
+ *keyp = key;
+ return (0);
+}
+
+static void getrcsrev PROTO ((FILE *fp, char **revp));
+
+/* Read an RCS revision number from FP. Put a pointer to it in *REVP;
+ it points to space managed by getrcsrev which is only good until
+ the next call to getrcskey or getrcsrev. */
+static void
+getrcsrev (fp, revp)
+ FILE *fp;
+ char **revp;
+{
+ char *cur;
+ char *max;
+ int c;
+
+ do {
+ c = getc (fp);
+ if (c == EOF)
+ /* FIXME: should be including filename in error message. */
+ error (1, errno, "cannot read rcs file");
+ } while (whitespace (c));
+
+ if (!(isdigit (c) || c == '.'))
+ /* FIXME: should be including filename in error message. */
+ error (1, 0, "error reading rcs file; revision number expected");
+
+ cur = key;
+ max = key + keysize;
+ while (isdigit (c) || c == '.')
+ {
+ if (cur >= max)
+ {
+ key = xrealloc (key, keysize + ALLOCINCR);
+ cur = key + keysize;
+ keysize += ALLOCINCR;
+ max = key + keysize;
+ }
+ *cur++ = c;
+
+ c = getc (fp);
+ if (c == EOF)
+ {
+ /* FIXME: should be including filename in error message. */
+ error (1, errno, "cannot read rcs file");
+ }
+ }
+
+ if (cur >= max)
+ {
+ key = xrealloc (key, keysize + ALLOCINCR);
+ cur = key + keysize;
+ keysize += ALLOCINCR;
+ max = key + keysize;
+ }
+ *cur = '\0';
+ *revp = key;
+}
+
+/*
+ * process the symbols list of the rcs file
+ */
+static void
+do_symbols (list, val)
+ List *list;
+ char *val;
+{
+ Node *p;
+ char *cp = val;
+ char *tag, *rev;
+
+ for (;;)
+ {
+ /* skip leading whitespace */
+ while (whitespace (*cp))
+ cp++;
+
+ /* if we got to the end, we are done */
+ if (*cp == '\0')
+ break;
+
+ /* split it up into tag and rev */
+ tag = cp;
+ cp = strchr (cp, ':');
+ *cp++ = '\0';
+ rev = cp;
+ while (!whitespace (*cp) && *cp != '\0')
+ cp++;
+ if (*cp != '\0')
+ *cp++ = '\0';
+
+ /* make a new node and add it to the list */
+ p = getnode ();
+ p->key = xstrdup (tag);
+ p->data = xstrdup (rev);
+ (void) addnode (list, p);
+ }
+}
+
+/*
+ * process the branches list of a revision delta
+ */
+static void
+do_branches (list, val)
+ List *list;
+ char *val;
+{
+ Node *p;
+ char *cp = val;
+ char *branch;
+
+ for (;;)
+ {
+ /* skip leading whitespace */
+ while (whitespace (*cp))
+ cp++;
+
+ /* if we got to the end, we are done */
+ if (*cp == '\0')
+ break;
+
+ /* find the end of this branch */
+ branch = cp;
+ while (!whitespace (*cp) && *cp != '\0')
+ cp++;
+ if (*cp != '\0')
+ *cp++ = '\0';
+
+ /* make a new node and add it to the list */
+ p = getnode ();
+ p->key = xstrdup (branch);
+ (void) addnode (list, p);
+ }
+}
+
+/*
+ * Version Number
+ *
+ * Returns the requested version number of the RCS file, satisfying tags and/or
+ * dates, and walking branches, if necessary.
+ *
+ * The result is returned; null-string if error.
+ */
+char *
+RCS_getversion (rcs, tag, date, force_tag_match, return_both)
+ RCSNode *rcs;
+ char *tag;
+ char *date;
+ int force_tag_match;
+ int return_both;
+{
+ /* make sure we have something to look at... */
+ assert (rcs != NULL);
+
+ if (tag && date)
+ {
+ char *cp, *rev, *tagrev;
+
+ /*
+ * first lookup the tag; if that works, turn the revision into
+ * a branch and lookup the date.
+ */
+ tagrev = RCS_gettag (rcs, tag, force_tag_match, 0);
+ if (tagrev == NULL)
+ return ((char *) NULL);
+
+ if ((cp = strrchr (tagrev, '.')) != NULL)
+ *cp = '\0';
+ rev = RCS_getdatebranch (rcs, date, tagrev);
+ free (tagrev);
+ return (rev);
+ }
+ else if (tag)
+ return (RCS_gettag (rcs, tag, force_tag_match, return_both));
+ else if (date)
+ return (RCS_getdate (rcs, date, force_tag_match));
+ else
+ return (RCS_head (rcs));
+
+}
+
+/*
+ * Find the revision for a specific tag.
+ * If force_tag_match is set, return NULL if an exact match is not
+ * possible otherwise return RCS_head (). We are careful to look for
+ * and handle "magic" revisions specially.
+ *
+ * If the matched tag is a branch tag, find the head of the branch.
+ */
+char *
+RCS_gettag (rcs, symtag, force_tag_match, return_both)
+ RCSNode *rcs;
+ char *symtag;
+ int force_tag_match;
+ int return_both;
+{
+ Node *p;
+ char *tag = symtag;
+
+ /* make sure we have something to look at... */
+ assert (rcs != NULL);
+
+ /* XXX this is probably not necessary, --jtc */
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, NULL);
+
+ /* If tag is "HEAD", special case to get head RCS revision */
+ if (tag && (strcmp (tag, TAG_HEAD) == 0 || *tag == '\0'))
+#if 0 /* This #if 0 is only in the Cygnus code. Why? Death support? */
+ if (force_tag_match && (rcs->flags & VALID) && (rcs->flags & INATTIC))
+ return ((char *) NULL); /* head request for removed file */
+ else
+#endif
+ return (RCS_head (rcs));
+
+ if (!isdigit (tag[0]))
+ {
+ /* If we got a symbolic tag, resolve it to a numeric */
+ if (rcs == NULL)
+ p = NULL;
+ else {
+ p = findnode (RCS_symbols(rcs), tag);
+ }
+ if (p != NULL)
+ {
+ int dots;
+ char *magic, *branch, *cp;
+
+ tag = p->data;
+
+ /*
+ * If this is a magic revision, we turn it into either its
+ * physical branch equivalent (if one exists) or into
+ * its base revision, which we assume exists.
+ */
+ dots = numdots (tag);
+ if (dots > 2 && (dots & 1) != 0)
+ {
+ branch = strrchr (tag, '.');
+ cp = branch++ - 1;
+ while (*cp != '.')
+ cp--;
+
+ /* see if we have .magic-branch. (".0.") */
+ magic = xmalloc (strlen (tag) + 1);
+ (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
+ if (strncmp (magic, cp, strlen (magic)) == 0)
+ {
+ char *xtag;
+
+ /* it's magic. See if the branch exists */
+ *cp = '\0'; /* turn it into a revision */
+ xtag = xstrdup (tag);
+ *cp = '.'; /* and back again */
+ (void) sprintf (magic, "%s.%s", xtag, branch);
+ branch = RCS_getbranch (rcs, magic, 1);
+ free (magic);
+ if (branch != NULL)
+ {
+ free (xtag);
+ return (branch);
+ }
+ return (xtag);
+ }
+ free (magic);
+ }
+ }
+ else
+ {
+ /* The tag wasn't there, so return the head or NULL */
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+ }
+
+ /*
+ * numeric tag processing:
+ * 1) revision number - just return it
+ * 2) branch number - find head of branch
+ */
+
+ /* strip trailing dots */
+ while (tag[strlen (tag) - 1] == '.')
+ tag[strlen (tag) - 1] = '\0';
+
+ if ((numdots (tag) & 1) == 0)
+ {
+ /* we have a branch tag, so we need to walk the branch */
+ return (RCS_getbranch (rcs, tag, force_tag_match));
+ }
+ else
+ {
+ /* we have a revision tag, so make sure it exists */
+ if (rcs == NULL)
+ p = NULL;
+ else
+ p = findnode (rcs->versions, tag);
+ if (p != NULL)
+ {
+ /*
+ * we have found a numeric revision for the revision tag.
+ * To support expanding the RCS keyword Name, return both
+ * the numeric tag and the supplied tag (which might be
+ * symbolic). They are separated with a ':' which is not
+ * a valid tag char. The variable return_both is only set
+ * if this function is called through Version_TS ->
+ * RCS_getversion.
+ */
+ if (return_both)
+ {
+ char *both = xmalloc(strlen(tag) + 2 + strlen(symtag));
+ sprintf(both, "%s:%s", tag, symtag);
+ return both;
+ }
+ else
+ return (xstrdup (tag));
+ }
+ else
+ {
+ /* The revision wasn't there, so return the head or NULL */
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+ }
+}
+
+/*
+ * Return a "magic" revision as a virtual branch off of REV for the RCS file.
+ * A "magic" revision is one which is unique in the RCS file. By unique, I
+ * mean we return a revision which:
+ * - has a branch of 0 (see rcs.h RCS_MAGIC_BRANCH)
+ * - has a revision component which is not an existing branch off REV
+ * - has a revision component which is not an existing magic revision
+ * - is an even-numbered revision, to avoid conflicts with vendor branches
+ * The first point is what makes it "magic".
+ *
+ * As an example, if we pass in 1.37 as REV, we will look for an existing
+ * branch called 1.37.2. If it did not exist, we would look for an
+ * existing symbolic tag with a numeric part equal to 1.37.0.2. If that
+ * didn't exist, then we know that the 1.37.2 branch can be reserved by
+ * creating a symbolic tag with 1.37.0.2 as the numeric part.
+ *
+ * This allows us to fork development with very little overhead -- just a
+ * symbolic tag is used in the RCS file. When a commit is done, a physical
+ * branch is dynamically created to hold the new revision.
+ *
+ * Note: We assume that REV is an RCS revision and not a branch number.
+ */
+static char *check_rev;
+char *
+RCS_magicrev (rcs, rev)
+ RCSNode *rcs;
+ char *rev;
+{
+ int rev_num;
+ char *xrev, *test_branch;
+
+ xrev = xmalloc (strlen (rev) + 14); /* enough for .0.number */
+ check_rev = xrev;
+
+ /* only look at even numbered branches */
+ for (rev_num = 2; ; rev_num += 2)
+ {
+ /* see if the physical branch exists */
+ (void) sprintf (xrev, "%s.%d", rev, rev_num);
+ test_branch = RCS_getbranch (rcs, xrev, 1);
+ if (test_branch != NULL) /* it did, so keep looking */
+ {
+ free (test_branch);
+ continue;
+ }
+
+ /* now, create a "magic" revision */
+ (void) sprintf (xrev, "%s.%d.%d", rev, RCS_MAGIC_BRANCH, rev_num);
+
+ /* walk the symbols list to see if a magic one already exists */
+ if (walklist (RCS_symbols(rcs), checkmagic_proc, NULL) != 0)
+ continue;
+
+ /* we found a free magic branch. Claim it as ours */
+ return (xrev);
+ }
+}
+
+/*
+ * walklist proc to look for a match in the symbols list.
+ * Returns 0 if the symbol does not match, 1 if it does.
+ */
+static int
+checkmagic_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ if (strcmp (check_rev, p->data) == 0)
+ return (1);
+ else
+ return (0);
+}
+
+/*
+ * Given an RCSNode, returns non-zero if the specified revision number
+ * or symbolic tag resolves to a "branch" within the rcs file.
+ *
+ * FIXME: this is the same as RCS_nodeisbranch except for the special
+ * case for handling a null rcsnode.
+ */
+int
+RCS_isbranch (rcs, rev)
+ RCSNode *rcs;
+ const char *rev;
+{
+ /* numeric revisions are easy -- even number of dots is a branch */
+ if (isdigit (*rev))
+ return ((numdots (rev) & 1) == 0);
+
+ /* assume a revision if you can't find the RCS info */
+ if (rcs == NULL)
+ return (0);
+
+ /* now, look for a match in the symbols list */
+ return (RCS_nodeisbranch (rcs, rev));
+}
+
+/*
+ * Given an RCSNode, returns non-zero if the specified revision number
+ * or symbolic tag resolves to a "branch" within the rcs file. We do
+ * take into account any magic branches as well.
+ */
+int
+RCS_nodeisbranch (rcs, rev)
+ RCSNode *rcs;
+ const char *rev;
+{
+ int dots;
+ Node *p;
+
+ /* numeric revisions are easy -- even number of dots is a branch */
+ if (isdigit (*rev))
+ return ((numdots (rev) & 1) == 0);
+
+ p = findnode (RCS_symbols(rcs), rev);
+ if (p == NULL)
+ return (0);
+ dots = numdots (p->data);
+ if ((dots & 1) == 0)
+ return (1);
+
+ /* got a symbolic tag match, but it's not a branch; see if it's magic */
+ if (dots > 2)
+ {
+ char *magic;
+ char *branch = strrchr (p->data, '.');
+ char *cp = branch - 1;
+ while (*cp != '.')
+ cp--;
+
+ /* see if we have .magic-branch. (".0.") */
+ magic = xmalloc (strlen (p->data) + 1);
+ (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
+ if (strncmp (magic, cp, strlen (magic)) == 0)
+ {
+ free (magic);
+ return (1);
+ }
+ free (magic);
+ }
+ return (0);
+}
+
+/*
+ * Returns a pointer to malloc'ed memory which contains the branch
+ * for the specified *symbolic* tag. Magic branches are handled correctly.
+ */
+char *
+RCS_whatbranch (rcs, rev)
+ RCSNode *rcs;
+ const char *rev;
+{
+ Node *p;
+ int dots;
+
+ /* assume no branch if you can't find the RCS info */
+ if (rcs == NULL)
+ return ((char *) NULL);
+
+ /* now, look for a match in the symbols list */
+ p = findnode (RCS_symbols(rcs), rev);
+ if (p == NULL)
+ return ((char *) NULL);
+ dots = numdots (p->data);
+ if ((dots & 1) == 0)
+ return (xstrdup (p->data));
+
+ /* got a symbolic tag match, but it's not a branch; see if it's magic */
+ if (dots > 2)
+ {
+ char *magic;
+ char *branch = strrchr (p->data, '.');
+ char *cp = branch++ - 1;
+ while (*cp != '.')
+ cp--;
+
+ /* see if we have .magic-branch. (".0.") */
+ magic = xmalloc (strlen (p->data) + 1);
+ (void) sprintf (magic, ".%d.", RCS_MAGIC_BRANCH);
+ if (strncmp (magic, cp, strlen (magic)) == 0)
+ {
+ /* yep. it's magic. now, construct the real branch */
+ *cp = '\0'; /* turn it into a revision */
+ (void) sprintf (magic, "%s.%s", p->data, branch);
+ *cp = '.'; /* and turn it back */
+ return (magic);
+ }
+ free (magic);
+ }
+ return ((char *) NULL);
+}
+
+/*
+ * Get the head of the specified branch. If the branch does not exist,
+ * return NULL or RCS_head depending on force_tag_match
+ */
+char *
+RCS_getbranch (rcs, tag, force_tag_match)
+ RCSNode *rcs;
+ char *tag;
+ int force_tag_match;
+{
+ Node *p, *head;
+ RCSVers *vn;
+ char *xtag;
+ char *nextvers;
+ char *cp;
+
+ /* make sure we have something to look at... */
+ assert (rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, NULL);
+
+ /* find out if the tag contains a dot, or is on the trunk */
+ cp = strrchr (tag, '.');
+
+ /* trunk processing is the special case */
+ if (cp == NULL)
+ {
+ xtag = xmalloc (strlen (tag) + 1 + 1); /* +1 for an extra . */
+ (void) strcpy (xtag, tag);
+ (void) strcat (xtag, ".");
+ for (cp = rcs->head; cp != NULL;)
+ {
+ if (strncmp (xtag, cp, strlen (xtag)) == 0)
+ break;
+ p = findnode (rcs->versions, cp);
+ if (p == NULL)
+ {
+ free (xtag);
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+ vn = (RCSVers *) p->data;
+ cp = vn->next;
+ }
+ free (xtag);
+ if (cp == NULL)
+ {
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+ return (xstrdup (cp));
+ }
+
+ /* if it had a `.', terminate the string so we have the base revision */
+ *cp = '\0';
+
+ /* look up the revision this branch is based on */
+ p = findnode (rcs->versions, tag);
+
+ /* put the . back so we have the branch again */
+ *cp = '.';
+
+ if (p == NULL)
+ {
+ /* if the base revision didn't exist, return head or NULL */
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+
+ /* find the first element of the branch we are looking for */
+ vn = (RCSVers *) p->data;
+ if (vn->branches == NULL)
+ return (NULL);
+ xtag = xmalloc (strlen (tag) + 1 + 1); /* 1 for the extra '.' */
+ (void) strcpy (xtag, tag);
+ (void) strcat (xtag, ".");
+ head = vn->branches->list;
+ for (p = head->next; p != head; p = p->next)
+ if (strncmp (p->key, xtag, strlen (xtag)) == 0)
+ break;
+ free (xtag);
+
+ if (p == head)
+ {
+ /* we didn't find a match so return head or NULL */
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+
+ /* now walk the next pointers of the branch */
+ nextvers = p->key;
+ do
+ {
+ p = findnode (rcs->versions, nextvers);
+ if (p == NULL)
+ {
+ /* a link in the chain is missing - return head or NULL */
+ if (force_tag_match)
+ return (NULL);
+ else
+ return (RCS_head (rcs));
+ }
+ vn = (RCSVers *) p->data;
+ nextvers = vn->next;
+ } while (nextvers != NULL);
+
+ /* we have the version in our hand, so go for it */
+ return (xstrdup (vn->version));
+}
+
+/*
+ * Get the head of the RCS file. If branch is set, this is the head of the
+ * branch, otherwise the real head
+ */
+char *
+RCS_head (rcs)
+ RCSNode *rcs;
+{
+ /* make sure we have something to look at... */
+ assert (rcs != NULL);
+
+ /*
+ * NOTE: we call getbranch with force_tag_match set to avoid any
+ * possibility of recursion
+ */
+ if (rcs->branch)
+ return (RCS_getbranch (rcs, rcs->branch, 1));
+ else
+ return (xstrdup (rcs->head));
+}
+
+/*
+ * Get the most recent revision, based on the supplied date, but use some
+ * funky stuff and follow the vendor branch maybe
+ */
+char *
+RCS_getdate (rcs, date, force_tag_match)
+ RCSNode *rcs;
+ char *date;
+ int force_tag_match;
+{
+ char *cur_rev = NULL;
+ char *retval = NULL;
+ Node *p;
+ RCSVers *vers = NULL;
+
+ /* make sure we have something to look at... */
+ assert (rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, NULL);
+
+ /* if the head is on a branch, try the branch first */
+ if (rcs->branch != NULL)
+ retval = RCS_getdatebranch (rcs, date, rcs->branch);
+
+ /* if we found a match, we are done */
+ if (retval != NULL)
+ return (retval);
+
+ /* otherwise if we have a trunk, try it */
+ if (rcs->head)
+ {
+ p = findnode (rcs->versions, rcs->head);
+ while (p != NULL)
+ {
+ /* if the date of this one is before date, take it */
+ vers = (RCSVers *) p->data;
+ if (RCS_datecmp (vers->date, date) <= 0)
+ {
+ cur_rev = vers->version;
+ break;
+ }
+
+ /* if there is a next version, find the node */
+ if (vers->next != NULL)
+ p = findnode (rcs->versions, vers->next);
+ else
+ p = (Node *) NULL;
+ }
+ }
+
+ /*
+ * at this point, either we have the revision we want, or we have the
+ * first revision on the trunk (1.1?) in our hands
+ */
+
+ /* if we found what we're looking for, and it's not 1.1 return it */
+ if (cur_rev != NULL && strcmp (cur_rev, "1.1") != 0)
+ return (xstrdup (cur_rev));
+
+ /* look on the vendor branch */
+ retval = RCS_getdatebranch (rcs, date, CVSBRANCH);
+
+ /*
+ * if we found a match, return it; otherwise, we return the first
+ * revision on the trunk or NULL depending on force_tag_match and the
+ * date of the first rev
+ */
+ if (retval != NULL)
+ return (retval);
+
+ if (!force_tag_match || RCS_datecmp (vers->date, date) <= 0)
+ return (xstrdup (vers->version));
+ else
+ return (NULL);
+}
+
+/*
+ * Look up the last element on a branch that was put in before the specified
+ * date (return the rev or NULL)
+ */
+static char *
+RCS_getdatebranch (rcs, date, branch)
+ RCSNode *rcs;
+ char *date;
+ char *branch;
+{
+ char *cur_rev = NULL;
+ char *cp;
+ char *xbranch, *xrev;
+ Node *p;
+ RCSVers *vers;
+
+ /* look up the first revision on the branch */
+ xrev = xstrdup (branch);
+ cp = strrchr (xrev, '.');
+ if (cp == NULL)
+ {
+ free (xrev);
+ return (NULL);
+ }
+ *cp = '\0'; /* turn it into a revision */
+
+ assert (rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, NULL);
+
+ p = findnode (rcs->versions, xrev);
+ free (xrev);
+ if (p == NULL)
+ return (NULL);
+ vers = (RCSVers *) p->data;
+
+ /* if no branches list, return NULL */
+ if (vers->branches == NULL)
+ return (NULL);
+
+ /* walk the branches list looking for the branch number */
+ xbranch = xmalloc (strlen (branch) + 1 + 1); /* +1 for the extra dot */
+ (void) strcpy (xbranch, branch);
+ (void) strcat (xbranch, ".");
+ for (p = vers->branches->list->next; p != vers->branches->list; p = p->next)
+ if (strncmp (p->key, xbranch, strlen (xbranch)) == 0)
+ break;
+ free (xbranch);
+ if (p == vers->branches->list)
+ return (NULL);
+
+ p = findnode (rcs->versions, p->key);
+
+ /* walk the next pointers until you find the end, or the date is too late */
+ while (p != NULL)
+ {
+ vers = (RCSVers *) p->data;
+ if (RCS_datecmp (vers->date, date) <= 0)
+ cur_rev = vers->version;
+ else
+ break;
+
+ /* if there is a next version, find the node */
+ if (vers->next != NULL)
+ p = findnode (rcs->versions, vers->next);
+ else
+ p = (Node *) NULL;
+ }
+
+ /* if we found something acceptable, return it - otherwise NULL */
+ if (cur_rev != NULL)
+ return (xstrdup (cur_rev));
+ else
+ return (NULL);
+}
+
+/*
+ * Compare two dates in RCS format. Beware the change in format on January 1,
+ * 2000, when years go from 2-digit to full format.
+ */
+int
+RCS_datecmp (date1, date2)
+ char *date1, *date2;
+{
+ int length_diff = strlen (date1) - strlen (date2);
+
+ return (length_diff ? length_diff : strcmp (date1, date2));
+}
+
+/*
+ * Lookup the specified revision in the ,v file and return, in the date
+ * argument, the date specified for the revision *minus one second*, so that
+ * the logically previous revision will be found later.
+ *
+ * Returns zero on failure, RCS revision time as a Unix "time_t" on success.
+ */
+time_t
+RCS_getrevtime (rcs, rev, date, fudge)
+ RCSNode *rcs;
+ char *rev;
+ char *date;
+ int fudge;
+{
+ char tdate[MAXDATELEN];
+ struct tm xtm, *ftm;
+ time_t revdate = 0;
+ Node *p;
+ RCSVers *vers;
+
+ /* make sure we have something to look at... */
+ assert (rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, NULL);
+
+ /* look up the revision */
+ p = findnode (rcs->versions, rev);
+ if (p == NULL)
+ return (-1);
+ vers = (RCSVers *) p->data;
+
+ /* split up the date */
+ ftm = &xtm;
+ (void) sscanf (vers->date, SDATEFORM, &ftm->tm_year, &ftm->tm_mon,
+ &ftm->tm_mday, &ftm->tm_hour, &ftm->tm_min,
+ &ftm->tm_sec);
+
+ /* If the year is from 1900 to 1999, RCS files contain only two
+ digits, and sscanf gives us a year from 0-99. If the year is
+ 2000+, RCS files contain all four digits and we subtract 1900,
+ because the tm_year field should contain years since 1900. */
+
+ if (ftm->tm_year > 1900)
+ ftm->tm_year -= 1900;
+
+ /* put the date in a form getdate can grok */
+#ifdef HAVE_RCS5
+ (void) sprintf (tdate, "%d/%d/%d GMT %d:%d:%d", ftm->tm_mon,
+ ftm->tm_mday, ftm->tm_year, ftm->tm_hour,
+ ftm->tm_min, ftm->tm_sec);
+#else
+ (void) sprintf (tdate, "%d/%d/%d %d:%d:%d", ftm->tm_mon,
+ ftm->tm_mday, ftm->tm_year, ftm->tm_hour,
+ ftm->tm_min, ftm->tm_sec);
+#endif
+
+ /* turn it into seconds since the epoch */
+ revdate = get_date (tdate, (struct timeb *) NULL);
+ if (revdate != (time_t) -1)
+ {
+ revdate -= fudge; /* remove "fudge" seconds */
+ if (date)
+ {
+ /* put an appropriate string into ``date'' if we were given one */
+#ifdef HAVE_RCS5
+ ftm = gmtime (&revdate);
+#else
+ ftm = localtime (&revdate);
+#endif
+ (void) sprintf (date, 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);
+ }
+ }
+ return (revdate);
+}
+
+List *
+RCS_symbols(rcs)
+ RCSNode *rcs;
+{
+ assert(rcs != NULL);
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, NULL);
+
+ if (rcs->symbols_data) {
+ rcs->symbols = getlist ();
+ do_symbols (rcs->symbols, rcs->symbols_data);
+ free(rcs->symbols_data);
+ rcs->symbols_data = NULL;
+ }
+
+ return rcs->symbols;
+}
+
+/*
+ * The argument ARG is the getopt remainder of the -k option specified on the
+ * command line. This function returns malloc'ed space that can be used
+ * directly in calls to RCS V5, with the -k flag munged correctly.
+ */
+char *
+RCS_check_kflag (arg)
+ const char *arg;
+{
+ static const char *const kflags[] =
+ {"kv", "kvl", "k", "v", "o", "b", (char *) NULL};
+ static const char *const keyword_usage[] =
+ {
+ "%s %s: invalid RCS keyword expansion mode\n",
+ "Valid expansion modes include:\n",
+ " -kkv\tGenerate keywords using the default form.\n",
+ " -kkvl\tLike -kkv, except locker's name inserted.\n",
+ " -kk\tGenerate only keyword names in keyword strings.\n",
+ " -kv\tGenerate only keyword values in keyword strings.\n",
+ " -ko\tGenerate the old keyword string (no changes from checked in file).\n",
+ " -kb\tGenerate binary file unmodified (merges not allowed) (RCS 5.7).\n",
+ NULL,
+ };
+ char karg[10];
+ char const *const *cpp = NULL;
+
+#ifndef HAVE_RCS5
+ error (1, 0, "%s %s: your version of RCS does not support the -k option",
+ program_name, command_name);
+#endif
+
+ if (arg)
+ {
+ for (cpp = kflags; *cpp != NULL; cpp++)
+ {
+ if (strcmp (arg, *cpp) == 0)
+ break;
+ }
+ }
+
+ if (arg == NULL || *cpp == NULL)
+ {
+ usage (keyword_usage);
+ }
+
+ (void) sprintf (karg, "-k%s", *cpp);
+ return (xstrdup (karg));
+}
+
+/*
+ * Do some consistency checks on the symbolic tag... These should equate
+ * pretty close to what RCS checks, though I don't know for certain.
+ */
+void
+RCS_check_tag (tag)
+ const char *tag;
+{
+ char *invalid = "$,.:;@"; /* invalid RCS tag characters */
+ const char *cp;
+
+ /*
+ * The first character must be an alphabetic letter. The remaining
+ * characters cannot be non-visible graphic characters, and must not be
+ * in the set of "invalid" RCS identifier characters.
+ */
+ if (isalpha (*tag))
+ {
+ for (cp = tag; *cp; cp++)
+ {
+ if (!isgraph (*cp))
+ error (1, 0, "tag `%s' has non-visible graphic characters",
+ tag);
+ if (strchr (invalid, *cp))
+ error (1, 0, "tag `%s' must not contain the characters `%s'",
+ tag, invalid);
+ }
+ }
+ else
+ error (1, 0, "tag `%s' must start with a letter", tag);
+}
+
+/*
+ * Return true if RCS revision with TAG is a dead revision.
+ */
+int
+RCS_isdead (rcs, tag)
+ RCSNode *rcs;
+ const char *tag;
+{
+ Node *p;
+ RCSVers *version;
+
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, NULL);
+
+ p = findnode (rcs->versions, tag);
+ if (p == NULL)
+ return (0);
+
+ version = (RCSVers *) p->data;
+ return (version->dead);
+}
+
+/* Return the RCS keyword expansion mode. For example "b" for binary.
+ Returns a pointer into storage which is allocated and freed along with
+ the rest of the RCS information; the caller should not modify this
+ storage. Returns NULL if the RCS file does not specify a keyword
+ expansion mode; for all other errors, die with a fatal error. */
+char *
+RCS_getexpand (rcs)
+ RCSNode *rcs;
+{
+ assert (rcs != NULL);
+ if (rcs->flags & PARTIAL)
+ RCS_reparsercsfile (rcs, NULL);
+ return rcs->expand;
+}
+
+/* Stuff related to annotate command. This should perhaps be split
+ into the stuff which knows about the guts of RCS files, and the
+ command parsing type stuff. */
+
+/* Linked list of allocated blocks. Seems kind of silly to
+ reinvent the obstack wheel, and this isn't as nice as obstacks
+ in some ways, but obstacks are pretty baroque. */
+struct allocblock
+{
+ char *text;
+ struct allocblock *next;
+};
+struct allocblock *blocks;
+
+static void *block_alloc PROTO ((size_t));
+
+static void *
+block_alloc (n)
+ size_t n;
+{
+ struct allocblock *blk;
+ blk = (struct allocblock *) xmalloc (sizeof (struct allocblock));
+ blk->text = xmalloc (n);
+ blk->next = blocks;
+ blocks = blk;
+ return blk->text;
+}
+
+static void block_free PROTO ((void));
+
+static void
+block_free ()
+{
+ struct allocblock *p;
+ struct allocblock *q;
+
+ p = blocks;
+ while (p != NULL)
+ {
+ free (p->text);
+ q = p->next;
+ free (p);
+ p = q;
+ }
+ blocks = NULL;
+}
+
+struct line
+{
+ /* Text of this line, terminated by \n or \0. */
+ char *text;
+ /* Version in which it was introduced. */
+ RCSVers *vers;
+ /* Nonzero if this line ends with \n. This will always be true
+ except possibly for the last line. */
+ int has_newline;
+};
+
+struct linevector
+{
+ /* How many lines in use for this linevector? */
+ unsigned int nlines;
+ /* How many lines allocated for this linevector? */
+ unsigned int lines_alloced;
+ /* Pointer to array containing a pointer to each line. */
+ struct line **vector;
+};
+
+static void linevector_init PROTO ((struct linevector *));
+
+/* Initialize *VEC to be a linevector with no lines. */
+static void
+linevector_init (vec)
+ struct linevector *vec;
+{
+ vec->lines_alloced = 10;
+ vec->nlines = 0;
+ vec->vector = (struct line **)
+ xmalloc (vec->lines_alloced * sizeof (*vec->vector));
+}
+
+static void linevector_add PROTO ((struct linevector *vec, char *text,
+ RCSVers *vers, unsigned int pos));
+
+/* Given some text TEXT, add each of its lines to VEC before line POS
+ (where line 0 is the first line). The last line in TEXT may or may
+ not be \n terminated. All \n in TEXT are changed to \0. Set the
+ version for each of the new lines to VERS. */
+static void
+linevector_add (vec, text, vers, pos)
+ struct linevector *vec;
+ char *text;
+ RCSVers *vers;
+ unsigned int pos;
+{
+ unsigned int i;
+ unsigned int nnew;
+ char *p;
+ struct line *lines;
+
+ assert (vec->lines_alloced > 0);
+
+ /* Count the number of lines we will need to add. */
+ nnew = 1;
+ for (p = text; *p != '\0'; ++p)
+ if (*p == '\n' && p[1] != '\0')
+ ++nnew;
+ /* Allocate the struct line's. */
+ lines = block_alloc (nnew * sizeof (struct line));
+
+ /* Expand VEC->VECTOR if needed. */
+ if (vec->nlines + nnew >= vec->lines_alloced)
+ {
+ while (vec->nlines + nnew >= vec->lines_alloced)
+ vec->lines_alloced *= 2;
+ vec->vector = xrealloc (vec->vector,
+ vec->lines_alloced * sizeof (*vec->vector));
+ }
+
+ /* Make room for the new lines in VEC->VECTOR. */
+ for (i = vec->nlines + nnew - 1; i >= pos + nnew; --i)
+ vec->vector[i] = vec->vector[i - nnew];
+
+ if (pos > vec->nlines)
+ error (1, 0, "invalid rcs file: line to add out of range");
+
+ /* Actually add the lines, to LINES and VEC->VECTOR. */
+ i = pos;
+ lines[0].text = text;
+ lines[0].vers = vers;
+ lines[0].has_newline = 0;
+ vec->vector[i++] = &lines[0];
+ for (p = text; *p != '\0'; ++p)
+ if (*p == '\n')
+ {
+ *p = '\0';
+ lines[i - pos - 1].has_newline = 1;
+ if (p[1] == '\0')
+ /* If there are no characters beyond the last newline, we
+ don't consider it another line. */
+ break;
+ lines[i - pos].text = p + 1;
+ lines[i - pos].vers = vers;
+ lines[i - pos].has_newline = 0;
+ vec->vector[i] = &lines[i - pos];
+ ++i;
+ }
+ vec->nlines += nnew;
+}
+
+static void linevector_delete PROTO ((struct linevector *, unsigned int,
+ unsigned int));
+
+/* Remove NLINES lines from VEC at position POS (where line 0 is the
+ first line). */
+static void
+linevector_delete (vec, pos, nlines)
+ struct linevector *vec;
+ unsigned int pos;
+ unsigned int nlines;
+{
+ unsigned int i;
+ unsigned int last;
+
+ last = vec->nlines - nlines;
+ for (i = pos; i < last; ++i)
+ vec->vector[i] = vec->vector[i + nlines];
+ vec->nlines -= nlines;
+}
+
+static void linevector_copy PROTO ((struct linevector *, struct linevector *));
+
+/* Copy FROM to TO, copying the vectors but not the lines pointed to. */
+static void
+linevector_copy (to, from)
+ struct linevector *to;
+ struct linevector *from;
+{
+ if (from->nlines > to->lines_alloced)
+ {
+ while (from->nlines > to->lines_alloced)
+ to->lines_alloced *= 2;
+ to->vector = (struct line **)
+ xrealloc (to->vector, to->lines_alloced * sizeof (*to->vector));
+ }
+ memcpy (to->vector, from->vector,
+ from->nlines * sizeof (*to->vector));
+ to->nlines = from->nlines;
+}
+
+static void linevector_free PROTO ((struct linevector *));
+
+/* Free storage associated with linevector (that is, the vector but
+ not the lines pointed to). */
+static void
+linevector_free (vec)
+ struct linevector *vec;
+{
+ free (vec->vector);
+}
+
+static char *month_printname PROTO ((char *));
+
+/* Given a textual string giving the month (1-12), terminated with any
+ character not recognized by atoi, return the 3 character name to
+ print it with. I do not think it is a good idea to change these
+ strings based on the locale; they are standard abbreviations (for
+ example in rfc822 mail messages) which should be widely understood.
+ Returns a pointer into static readonly storage. */
+static char *
+month_printname (month)
+ char *month;
+{
+ static const char *const months[] =
+ {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+ int mnum;
+
+ mnum = atoi (month);
+ if (mnum < 1 || mnum > 12)
+ return "???";
+ return (char *)months[mnum - 1];
+}
+
+static int annotate_fileproc PROTO ((struct file_info *));
+
+static int
+annotate_fileproc (finfo)
+ struct file_info *finfo;
+{
+ FILE *fp;
+ char *key;
+ char *value;
+ RCSVers *vers;
+ RCSVers *prev_vers;
+ int n;
+ int ishead;
+ Node *node;
+ struct linevector headlines;
+ struct linevector curlines;
+
+ if (finfo->rcs == NULL)
+ return (1);
+
+ /* Distinguish output for various files if we are processing
+ several files. */
+ cvs_outerr ("Annotations for ", 0);
+ cvs_outerr (finfo->fullname, 0);
+ cvs_outerr ("\n***************\n", 0);
+
+ if (!(finfo->rcs->flags & PARTIAL))
+ /* We are leaking memory by calling RCS_reparsefile again. */
+ error (0, 0, "internal warning: non-partial rcs in annotate_fileproc");
+ RCS_reparsercsfile (finfo->rcs, &fp);
+
+ ishead = 1;
+ vers = NULL;
+
+ do {
+ getrcsrev (fp, &key);
+
+ /* Stash the previous version. */
+ prev_vers = vers;
+
+ /* look up the revision */
+ node = findnode (finfo->rcs->versions, key);
+ if (node == NULL)
+ error (1, 0, "mismatch in rcs file %s between deltas and deltatexts",
+ finfo->rcs->path);
+ vers = (RCSVers *) node->data;
+
+ while ((n = getrcskey (fp, &key, &value)) >= 0)
+ {
+ if (strcmp (key, "text") == 0)
+ {
+ if (ishead)
+ {
+ char *p;
+
+ p = block_alloc (strlen (value) + 1);
+ strcpy (p, value);
+
+ linevector_init (&headlines);
+ linevector_init (&curlines);
+ linevector_add (&headlines, p, NULL, 0);
+ linevector_copy (&curlines, &headlines);
+ ishead = 0;
+ }
+ else
+ {
+ char *p;
+ char *q;
+ int op;
+ /* The RCS format throws us for a loop in that the
+ deltafrags (if we define a deltafrag as an
+ add or a delete) need to be applied in reverse
+ order. So we stick them into a linked list. */
+ struct deltafrag {
+ enum {ADD, DELETE} type;
+ unsigned long pos;
+ unsigned long nlines;
+ char *new_lines;
+ struct deltafrag *next;
+ };
+ struct deltafrag *dfhead;
+ struct deltafrag *df;
+
+ dfhead = NULL;
+ for (p = value; p != NULL && *p != '\0'; )
+ {
+ op = *p++;
+ if (op != 'a' && op != 'd')
+ /* Can't just skip over the deltafrag, because
+ the value of op determines the syntax. */
+ error (1, 0, "unrecognized operation '%c' in %s",
+ op, finfo->rcs->path);
+ df = (struct deltafrag *)
+ xmalloc (sizeof (struct deltafrag));
+ df->next = dfhead;
+ dfhead = df;
+ df->pos = strtoul (p, &q, 10);
+
+ if (p == q)
+ error (1, 0, "number expected in %s",
+ finfo->rcs->path);
+ p = q;
+ if (*p++ != ' ')
+ error (1, 0, "space expected in %s",
+ finfo->rcs->path);
+ df->nlines = strtoul (p, &q, 10);
+ if (p == q)
+ error (1, 0, "number expected in %s",
+ finfo->rcs->path);
+ p = q;
+ if (*p++ != '\012')
+ error (1, 0, "linefeed expected in %s",
+ finfo->rcs->path);
+
+ if (op == 'a')
+ {
+ unsigned int i;
+
+ df->type = ADD;
+ i = df->nlines;
+ /* The text we want is the number of lines
+ specified, or until the end of the value,
+ whichever comes first (it will be the former
+ except in the case where we are adding a line
+ which does not end in newline). */
+ for (q = p; i != 0; ++q)
+ if (*q == '\n')
+ --i;
+ else if (*q == '\0')
+ {
+ if (i != 1)
+ error (1, 0, "\
+invalid rcs file %s: premature end of value",
+ finfo->rcs->path);
+ else
+ break;
+ }
+
+ /* Copy the text we are adding into allocated
+ space. */
+ df->new_lines = block_alloc (q - p + 1);
+ strncpy (df->new_lines, p, q - p);
+ df->new_lines[q - p] = '\0';
+
+ p = q;
+ }
+ else
+ {
+ /* Correct for the fact that line numbers in RCS
+ files start with 1. */
+ --df->pos;
+
+ assert (op == 'd');
+ df->type = DELETE;
+ }
+ }
+ for (df = dfhead; df != NULL;)
+ {
+ unsigned int ln;
+
+ switch (df->type)
+ {
+ case ADD:
+ linevector_add (&curlines, df->new_lines,
+ NULL, df->pos);
+ break;
+ case DELETE:
+ if (df->pos > curlines.nlines
+ || df->pos + df->nlines > curlines.nlines)
+ error (1, 0, "\
+invalid rcs file %s (`d' operand out of range)",
+ finfo->rcs->path);
+ for (ln = df->pos; ln < df->pos + df->nlines; ++ln)
+ curlines.vector[ln]->vers = prev_vers;
+ linevector_delete (&curlines, df->pos, df->nlines);
+ break;
+ }
+ df = df->next;
+ free (dfhead);
+ dfhead = df;
+ }
+ }
+ break;
+ }
+ }
+ if (n < 0)
+ goto l_error;
+ } while (vers->next != NULL);
+
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", finfo->rcs->path);
+
+ /* Now print out the data we have just computed. */
+ {
+ unsigned int ln;
+
+ for (ln = 0; ln < headlines.nlines; ++ln)
+ {
+ char buf[80];
+ /* Period which separates year from month in date. */
+ char *ym;
+ /* Period which separates month from day in date. */
+ char *md;
+ RCSVers *prvers;
+
+ prvers = headlines.vector[ln]->vers;
+ if (prvers == NULL)
+ prvers = vers;
+
+ sprintf (buf, "%-12s (%-8.8s ",
+ prvers->version,
+ prvers->author);
+ cvs_output (buf, 0);
+
+ /* Now output the date. */
+ ym = strchr (prvers->date, '.');
+ if (ym == NULL)
+ cvs_output ("??-???-??", 0);
+ else
+ {
+ md = strchr (ym + 1, '.');
+ if (md == NULL)
+ cvs_output ("??", 0);
+ else
+ cvs_output (md + 1, 2);
+
+ cvs_output ("-", 1);
+ cvs_output (month_printname (ym + 1), 0);
+ cvs_output ("-", 1);
+ /* Only output the last two digits of the year. Our output
+ lines are long enough as it is without printing the
+ century. */
+ cvs_output (ym - 2, 2);
+ }
+ cvs_output ("): ", 0);
+ cvs_output (headlines.vector[ln]->text, 0);
+ cvs_output ("\n", 1);
+ }
+ }
+
+ if (!ishead)
+ {
+ linevector_free (&curlines);
+ linevector_free (&headlines);
+ }
+ block_free ();
+ return 0;
+
+ l_error:
+ if (ferror (fp))
+ error (1, errno, "cannot read %s", finfo->rcs->path);
+ else
+ error (1, 0, "%s does not appear to be a valid rcs file",
+ finfo->rcs->path);
+ /* Shut up gcc -Wall. */
+ return 0;
+}
+
+static const char *const annotate_usage[] =
+{
+ "Usage: %s %s [-l] [files...]\n",
+ "\t-l\tLocal directory only, no recursion.\n",
+ NULL
+};
+
+/* Command to show the revision, date, and author where each line of a
+ file was modified. Currently it will only show the trunk, all the
+ way to the head, but it would be useful to enhance it to (a) allow
+ one to specify a revision, and display only as far as that (easy;
+ just have annotate_fileproc set all the ->vers fields to NULL when
+ you hit that revision), and (b) handle branches (not as easy, but
+ doable). The user interface for both (a) and (b) could be a -r
+ option. */
+
+int
+annotate (argc, argv)
+ int argc;
+ char **argv;
+{
+ int local = 0;
+ int c;
+
+ if (argc == -1)
+ usage (annotate_usage);
+
+ optind = 0;
+ while ((c = getopt (argc, argv, "+l")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case '?':
+ default:
+ usage (annotate_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ start_server ();
+ ign_setup ();
+
+ if (local)
+ send_arg ("-l");
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* FIXME: We shouldn't have to send current files, but I'm not sure
+ whether it works. So send the files --
+ it's slower but it works. */
+ send_files (argc, argv, local, 0);
+ send_to_server ("annotate\012", 0);
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ return start_recursion (annotate_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ argc, argv, local, W_LOCAL, 0, 1, (char *)NULL,
+ 1, 0);
+}
diff --git a/contrib/cvs/src/rcs.h b/contrib/cvs/src/rcs.h
new file mode 100644
index 0000000..698a3b1
--- /dev/null
+++ b/contrib/cvs/src/rcs.h
@@ -0,0 +1,104 @@
+/* $CVSid: @(#)rcs.h 1.18 94/09/23 $ */
+
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * RCS source control definitions needed by rcs.c and friends
+ */
+
+#define RCS "rcs"
+#define RCS_CI "ci"
+#define RCS_CO "co"
+#define RCS_RLOG "rlog"
+#define RCS_DIFF "rcsdiff"
+#define RCS_RCSMERGE "rcsmerge"
+#define RCS_MERGE_PAT "^>>>>>>> " /* runs "grep" with this pattern */
+#define RCSEXT ",v"
+#define RCSPAT "*,v"
+#define RCSHEAD "head"
+#define RCSBRANCH "branch"
+#define RCSSYMBOLS "symbols"
+#define RCSDATE "date"
+#define RCSDESC "desc"
+#define RCSEXPAND "expand"
+
+/* Used by the version of death support which resulted from old
+ versions of CVS (e.g. 1.5 if you define DEATH_SUPPORT and not
+ DEATH_STATE). Only a hacked up RCS (used by those old versions of
+ CVS) will put this into RCS files. Considered obsolete. */
+#define RCSDEAD "dead"
+
+#define DATEFORM "%02d.%02d.%02d.%02d.%02d.%02d"
+#define SDATEFORM "%d.%d.%d.%d.%d.%d"
+
+/*
+ * Opaque structure definitions used by RCS specific lookup routines
+ */
+#define VALID 0x1 /* flags field contains valid data */
+#define INATTIC 0x2 /* RCS file is located in the Attic */
+#define PARTIAL 0x4 /* RCS file not completly parsed */
+
+struct rcsnode
+{
+ int refcount;
+ int flags;
+ char *path;
+ char *head;
+ char *branch;
+ char *symbols_data;
+ char *expand;
+ List *symbols;
+ List *versions;
+};
+
+typedef struct rcsnode RCSNode;
+
+struct rcsversnode
+{
+ char *version;
+ char *date;
+ char *author;
+ char *next;
+ int dead;
+ List *branches;
+};
+typedef struct rcsversnode RCSVers;
+
+/*
+ * CVS reserves all even-numbered branches for its own use. "magic" branches
+ * (see rcs.c) are contained as virtual revision numbers (within symbolic
+ * tags only) off the RCS_MAGIC_BRANCH, which is 0. CVS also reserves the
+ * ".1" branch for vendor revisions. So, if you do your own branching, you
+ * should limit your use to odd branch numbers starting at 3.
+ */
+#define RCS_MAGIC_BRANCH 0
+
+/*
+ * exported interfaces
+ */
+RCSNode *RCS_parse PROTO((const char *file, const char *repos));
+RCSNode *RCS_parsercsfile PROTO((char *rcsfile));
+char *RCS_check_kflag PROTO((const char *arg));
+char *RCS_getdate PROTO((RCSNode * rcs, char *date, int force_tag_match));
+char *RCS_gettag PROTO((RCSNode * rcs, char *symtag, int force_tag_match,
+ int return_both));
+char *RCS_getversion PROTO((RCSNode * rcs, char *tag, char *date,
+ int force_tag_match, int return_both));
+char *RCS_magicrev PROTO((RCSNode *rcs, char *rev));
+int RCS_isbranch PROTO((RCSNode *rcs, const char *rev));
+int RCS_nodeisbranch PROTO((RCSNode *rcs, const char *tag));
+char *RCS_whatbranch PROTO((RCSNode *rcs, const char *tag));
+char *RCS_head PROTO((RCSNode * rcs));
+int RCS_datecmp PROTO((char *date1, char *date2));
+time_t RCS_getrevtime PROTO((RCSNode * rcs, char *rev, char *date, int fudge));
+List *RCS_symbols PROTO((RCSNode *rcs));
+void RCS_check_tag PROTO((const char *tag));
+void freercsnode PROTO((RCSNode ** rnodep));
+char *RCS_getbranch PROTO((RCSNode * rcs, char *tag, int force_tag_match));
+
+int RCS_isdead PROTO((RCSNode *, const char *));
+char *RCS_getexpand PROTO ((RCSNode *));
diff --git a/contrib/cvs/src/rcscmds.c b/contrib/cvs/src/rcscmds.c
new file mode 100644
index 0000000..66aea57
--- /dev/null
+++ b/contrib/cvs/src/rcscmds.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * The functions in this file provide an interface for performing
+ * operations directly on RCS files.
+ */
+
+#include "cvs.h"
+#include <assert.h>
+
+/* For RCS file PATH, make symbolic tag TAG point to revision REV.
+ This validates that TAG is OK for a user to use. Return value is
+ -1 for error (and errno is set to indicate the error), positive for
+ error (and an error message has been printed), or zero for success. */
+
+int
+RCS_settag(path, tag, rev)
+ const char *path;
+ const char *tag;
+ const char *rev;
+{
+ if (strcmp (tag, TAG_BASE) == 0
+ || strcmp (tag, TAG_HEAD) == 0)
+ {
+ /* Print the name of the tag might be considered redundant
+ with the caller, which also prints it. Perhaps this helps
+ clarify why the tag name is considered reserved, I don't
+ know. */
+ error (0, 0, "Attempt to add reserved tag name %s", tag);
+ return 1;
+ }
+
+ run_setup ("%s%s -x,v/ -q -N%s:%s", Rcsbin, RCS, tag, rev);
+ run_arg (path);
+ return run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+}
+
+/* NOERR is 1 to suppress errors--FIXME it would
+ be better to avoid the errors or some cleaner solution. */
+int
+RCS_deltag(path, tag, noerr)
+ const char *path;
+ const char *tag;
+ int noerr;
+{
+ run_setup ("%s%s -x,v/ -q -N%s", Rcsbin, RCS, tag);
+ run_arg (path);
+ return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL);
+}
+
+/* set RCS branch to REV */
+int
+RCS_setbranch(path, rev)
+ const char *path;
+ const char *rev;
+{
+ run_setup ("%s%s -x,v/ -q -b%s", Rcsbin, RCS, rev ? rev : "");
+ run_arg (path);
+ return run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+}
+
+/* Lock revision REV. NOERR is 1 to suppress errors--FIXME it would
+ be better to avoid the errors or some cleaner solution. */
+int
+RCS_lock(path, rev, noerr)
+ const char *path;
+ const char *rev;
+ int noerr;
+{
+ run_setup ("%s%s -x,v/ -q -l%s", Rcsbin, RCS, rev ? rev : "");
+ run_arg (path);
+ return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL);
+}
+
+/* Unlock revision REV. NOERR is 1 to suppress errors--FIXME it would
+ be better to avoid the errors or some cleaner solution. */
+int
+RCS_unlock(path, rev, noerr)
+ const char *path;
+ const char *rev;
+ int noerr;
+{
+ run_setup ("%s%s -x,v/ -q -u%s", Rcsbin, RCS, rev ? rev : "");
+ run_arg (path);
+ return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL);
+}
+
+/* Merge revisions REV1 and REV2. */
+int
+RCS_merge(path, options, rev1, rev2)
+ const char *path;
+ const char *options;
+ const char *rev1;
+ const char *rev2;
+{
+ int status;
+
+ /* XXX - Do merge by hand instead of using rcsmerge, due to -k handling */
+
+ run_setup ("%s%s -x,v/ %s -r%s -r%s %s", Rcsbin, RCS_RCSMERGE,
+ options, rev1, rev2, path);
+ status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+#ifndef HAVE_RCS5
+ if (status == 0)
+ {
+ /* Run GREP to see if there appear to be conflicts in the file */
+ run_setup ("%s", GREP);
+ run_arg (RCS_MERGE_PAT);
+ run_arg (path);
+ status = (run_exec (RUN_TTY, DEVNULL, RUN_TTY, RUN_NORMAL) == 0);
+
+ }
+#endif
+ return status;
+}
+
+/* Check out a revision from RCSFILE into WORKFILE, or to standard output
+ if WORKFILE is NULL. If WORKFILE is "", let RCS pick the working file
+ name. TAG is the tag to check out, or NULL if one should check out
+ the head of the default branch. OPTIONS is a string such as
+ -kb or -kkv, for keyword expansion options, or NULL if there are none.
+ If WORKFILE is NULL, run regardless of noexec; if non-NULL, noexec
+ inhibits execution. SOUT is what to do with standard output
+ (typically RUN_TTY). If FLAGS & RCS_FLAGS_LOCK, lock it. If
+ FLAGS & RCS_FLAGS_FORCE, check out even on top of an existing file.
+ If NOERR is nonzero, suppress errors. */
+int
+RCS_checkout (rcsfile, workfile, tag, options, sout, flags, noerr)
+ char *rcsfile;
+ char *workfile;
+ char *tag;
+ char *options;
+ char *sout;
+ int flags;
+ int noerr;
+{
+ run_setup ("%s%s -x,v/ -q %s%s", Rcsbin, RCS_CO,
+ tag ? "-r" : "", tag ? tag : "");
+ if (options != NULL && options[0] != '\0')
+ run_arg (options);
+ if (workfile == NULL)
+ run_arg ("-p");
+ if (flags & RCS_FLAGS_LOCK)
+ run_arg ("-l");
+ if (flags & RCS_FLAGS_FORCE)
+ run_arg ("-f");
+ run_arg (rcsfile);
+ if (workfile != NULL && workfile[0] != '\0')
+ run_arg (workfile);
+ return run_exec (RUN_TTY, sout, noerr ? DEVNULL : RUN_TTY,
+ workfile == NULL ? (RUN_NORMAL | RUN_REALLY) : RUN_NORMAL);
+}
+
+/* Check in to RCSFILE with revision REV (which must be greater than the
+ largest revision) and message MESSAGE (which is checked for legality).
+ If FLAGS & RCS_FLAGS_DEAD, check in a dead revision. If NOERR, do not
+ report errors. If FLAGS & RCS_FLAGS_QUIET suppress errors somewhat more
+ selectively. If FLAGS & RCS_FLAGS_MODTIME, use the working file's
+ modification time for the checkin time. WORKFILE is the working file
+ to check in from, or NULL to use the usual RCS rules for deriving it
+ from the RCSFILE. */
+int
+RCS_checkin (rcsfile, workfile, message, rev, flags, noerr)
+ char *rcsfile;
+ char *workfile;
+ char *message;
+ char *rev;
+ int flags;
+ int noerr;
+{
+ run_setup ("%s%s -x,v/ -f %s%s", Rcsbin, RCS_CI,
+ rev ? "-r" : "", rev ? rev : "");
+ if (flags & RCS_FLAGS_DEAD)
+ run_arg ("-sdead");
+ if (flags & RCS_FLAGS_QUIET)
+ run_arg ("-q");
+ if (flags & RCS_FLAGS_MODTIME)
+ run_arg ("-d");
+ run_args ("-m%s", make_message_rcslegal (message));
+ if (workfile != NULL)
+ run_arg (workfile);
+ run_arg (rcsfile);
+ return run_exec (RUN_TTY, RUN_TTY, noerr ? DEVNULL : RUN_TTY, RUN_NORMAL);
+}
diff --git a/contrib/cvs/src/recurse.c b/contrib/cvs/src/recurse.c
new file mode 100644
index 0000000..ec51a98
--- /dev/null
+++ b/contrib/cvs/src/recurse.c
@@ -0,0 +1,714 @@
+/*
+ * 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.
+ *
+ * General recursion handler
+ *
+ */
+
+#include "cvs.h"
+#include "savecwd.h"
+#include "fileattr.h"
+#include "edit.h"
+
+static int do_dir_proc PROTO((Node * p, void *closure));
+static int do_file_proc PROTO((Node * p, void *closure));
+static void addlist PROTO((List ** listp, char *key));
+static int unroll_files_proc PROTO((Node *p, void *closure));
+static void addfile PROTO((List **listp, char *dir, char *file));
+
+
+/*
+ * Local static versions eliminates the need for globals
+ */
+static FILEPROC fileproc;
+static FILESDONEPROC filesdoneproc;
+static DIRENTPROC direntproc;
+static DIRLEAVEPROC dirleaveproc;
+static int which;
+static Dtype flags;
+static int aflag;
+static int readlock;
+static int dosrcs;
+static char update_dir[PATH_MAX];
+static char *repository = NULL;
+static List *filelist = NULL; /* holds list of files on which to operate */
+static List *dirlist = NULL; /* holds list of directories on which to operate */
+
+struct recursion_frame {
+ FILEPROC fileproc;
+ FILESDONEPROC filesdoneproc;
+ DIRENTPROC direntproc;
+ DIRLEAVEPROC dirleaveproc;
+ Dtype flags;
+ int which;
+ int aflag;
+ int readlock;
+ int dosrcs;
+};
+
+/*
+ * Called to start a recursive command.
+ *
+ * Command line arguments dictate the directories and files on which
+ * we operate. In the special case of no arguments, we default to
+ * ".".
+ *
+ * The general algorithm is as follows.
+ */
+int
+start_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc,
+ argc, argv, local, which, aflag, readlock,
+ update_preload, dosrcs, wd_is_repos)
+ FILEPROC fileproc;
+ FILESDONEPROC filesdoneproc;
+ DIRENTPROC direntproc;
+ DIRLEAVEPROC dirleaveproc;
+ int argc;
+ char **argv;
+ int local;
+ int which;
+ int aflag;
+ int readlock;
+ char *update_preload;
+ int dosrcs;
+ int wd_is_repos; /* Set if caller has already cd'd to the repository */
+{
+ int i, err = 0;
+ Dtype flags;
+ List *files_by_dir = NULL;
+ struct recursion_frame frame;
+
+ expand_wild (argc, argv, &argc, &argv);
+
+ if (update_preload == NULL)
+ update_dir[0] = '\0';
+ else
+ (void) strcpy (update_dir, update_preload);
+
+ if (local)
+ flags = R_SKIP_DIRS;
+ else
+ flags = R_PROCESS;
+
+ /* clean up from any previous calls to start_recursion */
+ if (repository)
+ {
+ free (repository);
+ repository = (char *) NULL;
+ }
+ if (filelist)
+ dellist (&filelist); /* FIXME-krp: no longer correct. */
+/* FIXME-krp: clean up files_by_dir */
+ if (dirlist)
+ dellist (&dirlist);
+
+ if (argc == 0)
+ {
+
+ /*
+ * There were no arguments, so we'll probably just recurse. The
+ * exception to the rule is when we are called from a directory
+ * without any CVS administration files. That has always meant to
+ * process each of the sub-directories, so we pretend like we were
+ * called with the list of sub-dirs of the current dir as args
+ */
+ if ((which & W_LOCAL) && !isdir (CVSADM))
+ dirlist = Find_Directories ((char *) NULL, W_LOCAL);
+ else
+ addlist (&dirlist, ".");
+
+ err += do_recursion (fileproc, filesdoneproc, direntproc,
+ dirleaveproc, flags, which, aflag,
+ readlock, dosrcs);
+ return(err);
+ }
+
+
+ /*
+ * There were arguments, so we have to handle them by hand. To do
+ * that, we set up the filelist and dirlist with the arguments and
+ * call do_recursion. do_recursion recognizes the fact that the
+ * lists are non-null when it starts and doesn't update them.
+ *
+ * explicitly named directories are stored in dirlist.
+ * explicitly named files are stored in filelist.
+ * other possibility is named entities whicha are not currently in
+ * the working directory.
+ */
+
+ for (i = 0; i < argc; i++)
+ {
+ /* if this argument is a directory, then add it to the list of
+ directories. */
+
+ if (!wrap_name_has (argv[i], WRAP_TOCVS) && isdir (argv[i]))
+ addlist (&dirlist, argv[i]);
+ else
+ {
+ /* otherwise, split argument into directory and component names. */
+ char *dir;
+ char *comp;
+ char tmp[PATH_MAX];
+ char *file_to_try;
+
+ /* Now break out argv[i] into directory part (DIR) and file part (COMP).
+ DIR and COMP will each point to a newly malloc'd string. */
+ dir = xstrdup (argv[i]);
+ comp = last_component (dir);
+ if (comp == dir)
+ {
+ /* no dir component. What we have is an implied "./" */
+ dir = xstrdup(".");
+ }
+ else
+ {
+ char *p = comp;
+
+ p[-1] = '\0';
+ comp = xstrdup (p);
+ }
+
+ /* if this argument exists as a file in the current
+ working directory tree, then add it to the files list. */
+
+ if (wd_is_repos)
+ {
+ /* If doing rtag, we've done a chdir to the repository. */
+ sprintf (tmp, "%s%s", argv[i], RCSEXT);
+ file_to_try = tmp;
+ }
+ else
+ file_to_try = argv[i];
+
+ if(isfile(file_to_try))
+ addfile (&files_by_dir, dir, comp);
+ else if (isdir (dir))
+ {
+ if (isdir (CVSADM))
+ {
+ /* otherwise, look for it in the repository. */
+ char *save_update_dir;
+ char *repos;
+
+ /* save & set (aka push) update_dir */
+ save_update_dir = xstrdup (update_dir);
+
+ if (*update_dir != '\0')
+ (void) strcat (update_dir, "/");
+
+ (void) strcat (update_dir, dir);
+
+ /* look for it in the repository. */
+ repos = Name_Repository (dir, update_dir);
+ (void) sprintf (tmp, "%s/%s", repos, comp);
+ free (repos);
+
+ if (!wrap_name_has (comp, WRAP_TOCVS) && isdir(tmp))
+ addlist (&dirlist, argv[i]);
+ else
+ addfile (&files_by_dir, dir, comp);
+
+ (void) sprintf (update_dir, "%s", save_update_dir);
+ free (save_update_dir);
+ }
+ else
+ addfile (&files_by_dir, dir, comp);
+ }
+ else
+ error (1, 0, "no such directory `%s'", dir);
+
+ free (dir);
+ free (comp);
+ }
+ }
+
+ /* At this point we have looped over all named arguments and built
+ a coupla lists. Now we unroll the lists, setting up and
+ calling do_recursion. */
+
+ frame.fileproc = fileproc;
+ frame.filesdoneproc = filesdoneproc;
+ frame.direntproc = direntproc;
+ frame.dirleaveproc = dirleaveproc;
+ frame.flags = flags;
+ frame.which = which;
+ frame.aflag = aflag;
+ frame.readlock = readlock;
+ frame.dosrcs = dosrcs;
+ err += walklist (files_by_dir, unroll_files_proc, (void *) &frame);
+
+ /* then do_recursion on the dirlist. */
+ if (dirlist != NULL)
+ err += do_recursion (frame.fileproc, frame.filesdoneproc,
+ frame.direntproc, frame.dirleaveproc,
+ frame.flags, frame.which, frame.aflag,
+ frame.readlock, frame.dosrcs);
+
+ /* Free the data which expand_wild allocated. */
+ for (i = 0; i < argc; ++i)
+ free (argv[i]);
+ free (argv);
+
+ return (err);
+}
+
+/*
+ * Implement the recursive policies on the local directory. This may be
+ * called directly, or may be called by start_recursion
+ */
+int
+do_recursion (xfileproc, xfilesdoneproc, xdirentproc, xdirleaveproc,
+ xflags, xwhich, xaflag, xreadlock, xdosrcs)
+ FILEPROC xfileproc;
+ FILESDONEPROC xfilesdoneproc;
+ DIRENTPROC xdirentproc;
+ DIRLEAVEPROC xdirleaveproc;
+ Dtype xflags;
+ int xwhich;
+ int xaflag;
+ int xreadlock;
+ int xdosrcs;
+{
+ int err = 0;
+ int dodoneproc = 1;
+ char *srepository;
+ List *entries = NULL;
+
+ /* do nothing if told */
+ if (xflags == R_SKIP_ALL)
+ return (0);
+
+ /* set up the static vars */
+ fileproc = xfileproc;
+ filesdoneproc = xfilesdoneproc;
+ direntproc = xdirentproc;
+ dirleaveproc = xdirleaveproc;
+ flags = xflags;
+ which = xwhich;
+ aflag = xaflag;
+ readlock = noexec ? 0 : xreadlock;
+ dosrcs = xdosrcs;
+
+ /* The fact that locks are not active here is what makes us fail to have
+ the
+
+ If someone commits some changes in one cvs command,
+ then an update by someone else will either get all the
+ changes, or none of them.
+
+ property (see node Concurrency in cvs.texinfo).
+
+ The most straightforward fix would just to readlock the whole
+ tree before starting an update, but that means that if a commit
+ gets blocked on a big update, it might need to wait a *long*
+ time.
+
+ A more adequate fix would be a two-pass design for update,
+ checkout, etc. The first pass would go through the repository,
+ with the whole tree readlocked, noting what versions of each
+ file we want to get. The second pass would release all locks
+ (except perhaps short-term locks on one file at a
+ time--although I think RCS already deals with this) and
+ actually get the files, specifying the particular versions it wants.
+
+ This could be sped up by separating out the data needed for the
+ first pass into a separate file(s)--for example a file
+ attribute for each file whose value contains the head revision
+ for each branch. The structure should be designed so that
+ commit can relatively quickly update the information for a
+ single file or a handful of files (file attributes, as
+ implemented in Jan 96, are probably acceptable; improvements
+ would be possible such as branch attributes which are in
+ separate files for each branch). */
+
+#if defined(SERVER_SUPPORT) && defined(SERVER_FLOWCONTROL)
+ /*
+ * Now would be a good time to check to see if we need to stop
+ * generating data, to give the buffers a chance to drain to the
+ * remote client. We should not have locks active at this point.
+ */
+ if (server_active
+ /* If there are writelocks around, we cannot pause here. */
+ && (readlock || noexec))
+ server_pause_check();
+#endif
+
+ /*
+ * Fill in repository with the current repository
+ */
+ if (which & W_LOCAL)
+ {
+ if (isdir (CVSADM))
+ repository = Name_Repository ((char *) NULL, update_dir);
+ else
+ repository = NULL;
+ }
+ else
+ {
+ repository = xmalloc (PATH_MAX);
+ (void) getwd (repository);
+ }
+ srepository = repository; /* remember what to free */
+
+ fileattr_startdir (repository);
+
+ /*
+ * The filesdoneproc needs to be called for each directory where files
+ * processed, or each directory that is processed by a call where no
+ * directories were passed in. In fact, the only time we don't want to
+ * call back the filesdoneproc is when we are processing directories that
+ * were passed in on the command line (or in the special case of `.' when
+ * we were called with no args
+ */
+ if (dirlist != NULL && filelist == NULL)
+ dodoneproc = 0;
+
+ /*
+ * If filelist or dirlist is already set, we don't look again. Otherwise,
+ * find the files and directories
+ */
+ if (filelist == NULL && dirlist == NULL)
+ {
+ /* both lists were NULL, so start from scratch */
+ if (fileproc != NULL && flags != R_SKIP_FILES)
+ {
+ int lwhich = which;
+
+ /* be sure to look in the attic if we have sticky tags/date */
+ if ((lwhich & W_ATTIC) == 0)
+ if (isreadable (CVSADM_TAG))
+ lwhich |= W_ATTIC;
+
+ /* find the files and fill in entries if appropriate */
+ filelist = Find_Names (repository, lwhich, aflag, &entries);
+ }
+
+ /* find sub-directories if we will recurse */
+ if (flags != R_SKIP_DIRS)
+ dirlist = Find_Directories (repository, which);
+ }
+ else
+ {
+ /* something was passed on the command line */
+ if (filelist != NULL && fileproc != NULL)
+ {
+ /* we will process files, so pre-parse entries */
+ if (which & W_LOCAL)
+ entries = Entries_Open (aflag);
+ }
+ }
+
+ /* process the files (if any) */
+ if (filelist != NULL && fileproc)
+ {
+ struct file_info finfo_struct;
+
+ /* read lock it if necessary */
+ if (readlock && repository && Reader_Lock (repository) != 0)
+ error (1, 0, "read lock failed - giving up");
+
+#ifdef CLIENT_SUPPORT
+ /* For the server, we handle notifications in a completely different
+ place (server_notify). For local, we can't do them here--we don't
+ have writelocks in place, and there is no way to get writelocks
+ here. */
+ if (client_active)
+ notify_check (repository, update_dir);
+#endif /* CLIENT_SUPPORT */
+
+ finfo_struct.repository = repository;
+ finfo_struct.update_dir = update_dir;
+ finfo_struct.entries = entries;
+ /* do_file_proc will fill in finfo_struct.file. */
+
+ /* process the files */
+ err += walklist (filelist, do_file_proc, &finfo_struct);
+
+ /* unlock it */
+ if (readlock)
+ Lock_Cleanup ();
+
+ /* clean up */
+ dellist (&filelist);
+ }
+
+ if (entries)
+ {
+ Entries_Close (entries);
+ entries = NULL;
+ }
+
+ /* call-back files done proc (if any) */
+ if (dodoneproc && filesdoneproc != NULL)
+ err = filesdoneproc (err, repository, update_dir[0] ? update_dir : ".");
+
+ fileattr_write ();
+ fileattr_free ();
+
+ /* process the directories (if necessary) */
+ if (dirlist != NULL)
+ err += walklist (dirlist, do_dir_proc, NULL);
+#ifdef notdef
+ else if (dirleaveproc != NULL)
+ err += dirleaveproc(".", err, ".");
+#endif
+ dellist (&dirlist);
+
+ /* free the saved copy of the pointer if necessary */
+ if (srepository)
+ {
+ free (srepository);
+ repository = (char *) NULL;
+ }
+
+ return (err);
+}
+
+/*
+ * Process each of the files in the list with the callback proc
+ */
+static int
+do_file_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ struct file_info *finfo = (struct file_info *)closure;
+ int ret;
+
+ finfo->file = p->key;
+ finfo->fullname = xmalloc (strlen (finfo->file)
+ + strlen (finfo->update_dir)
+ + 2);
+ finfo->fullname[0] = '\0';
+ if (finfo->update_dir[0] != '\0')
+ {
+ strcat (finfo->fullname, finfo->update_dir);
+ strcat (finfo->fullname, "/");
+ }
+ strcat (finfo->fullname, finfo->file);
+
+ if (dosrcs && repository)
+ finfo->rcs = RCS_parse (finfo->file, repository);
+ else
+ finfo->rcs = (RCSNode *) NULL;
+ ret = fileproc (finfo);
+
+ freercsnode(&finfo->rcs);
+ free (finfo->fullname);
+
+ return (ret);
+}
+
+/*
+ * Process each of the directories in the list (recursing as we go)
+ */
+static int
+do_dir_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ char *dir = p->key;
+ char newrepos[PATH_MAX];
+ List *sdirlist;
+ char *srepository;
+ char *cp;
+ Dtype dir_return = R_PROCESS;
+ int stripped_dot = 0;
+ int err = 0;
+ struct saved_cwd cwd;
+
+ /* set up update_dir - skip dots if not at start */
+ if (strcmp (dir, ".") != 0)
+ {
+ if (update_dir[0] != '\0')
+ {
+ (void) strcat (update_dir, "/");
+ (void) strcat (update_dir, dir);
+ }
+ else
+ (void) strcpy (update_dir, dir);
+
+ /*
+ * Here we need a plausible repository name for the sub-directory. We
+ * create one by concatenating the new directory name onto the
+ * previous repository name. The only case where the name should be
+ * used is in the case where we are creating a new sub-directory for
+ * update -d and in that case the generated name will be correct.
+ */
+ if (repository == NULL)
+ newrepos[0] = '\0';
+ else
+ (void) sprintf (newrepos, "%s/%s", repository, dir);
+ }
+ else
+ {
+ if (update_dir[0] == '\0')
+ (void) strcpy (update_dir, dir);
+
+ if (repository == NULL)
+ newrepos[0] = '\0';
+ else
+ (void) strcpy (newrepos, repository);
+ }
+
+ /* call-back dir entry proc (if any) */
+ if (direntproc != NULL)
+ dir_return = direntproc (dir, newrepos, update_dir);
+
+ /* only process the dir if the return code was 0 */
+ if (dir_return != R_SKIP_ALL)
+ {
+ /* save our current directory and static vars */
+ if (save_cwd (&cwd))
+ exit (EXIT_FAILURE);
+ sdirlist = dirlist;
+ srepository = repository;
+ dirlist = NULL;
+
+ /* cd to the sub-directory */
+ if (chdir (dir) < 0)
+ error (1, errno, "could not chdir to %s", dir);
+
+ /* honor the global SKIP_DIRS (a.k.a. local) */
+ if (flags == R_SKIP_DIRS)
+ dir_return = R_SKIP_DIRS;
+
+ /* remember if the `.' will be stripped for subsequent dirs */
+ if (strcmp (update_dir, ".") == 0)
+ {
+ update_dir[0] = '\0';
+ stripped_dot = 1;
+ }
+
+ /* make the recursive call */
+ err += do_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc,
+ dir_return, which, aflag, readlock, dosrcs);
+
+ /* put the `.' back if necessary */
+ if (stripped_dot)
+ (void) strcpy (update_dir, ".");
+
+ /* call-back dir leave proc (if any) */
+ if (dirleaveproc != NULL)
+ err = dirleaveproc (dir, err, update_dir);
+
+ /* get back to where we started and restore state vars */
+ if (restore_cwd (&cwd, NULL))
+ exit (EXIT_FAILURE);
+ free_cwd (&cwd);
+ dirlist = sdirlist;
+ repository = srepository;
+ }
+
+ /* put back update_dir */
+ cp = last_component (update_dir);
+ if (cp > update_dir)
+ cp[-1] = '\0';
+ else
+ update_dir[0] = '\0';
+
+ return (err);
+}
+
+/*
+ * Add a node to a list allocating the list if necessary.
+ */
+static void
+addlist (listp, key)
+ List **listp;
+ char *key;
+{
+ Node *p;
+
+ if (*listp == NULL)
+ *listp = getlist ();
+ p = getnode ();
+ p->type = FILES;
+ p->key = xstrdup (key);
+ if (addnode (*listp, p) != 0)
+ freenode (p);
+}
+
+static void
+addfile (listp, dir, file)
+ List **listp;
+ char *dir;
+ char *file;
+{
+ Node *n;
+
+ /* add this dir. */
+ addlist (listp, dir);
+
+ n = findnode (*listp, dir);
+ if (n == NULL)
+ {
+ error (1, 0, "can't find recently added dir node `%s' in start_recursion.",
+ dir);
+ }
+
+ n->type = DIRS;
+ addlist ((List **) &n->data, file);
+ return;
+}
+
+static int
+unroll_files_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ Node *n;
+ struct recursion_frame *frame = (struct recursion_frame *) closure;
+ int err = 0;
+ List *save_dirlist;
+ char *save_update_dir = NULL;
+ struct saved_cwd cwd;
+
+ /* if this dir was also an explicitly named argument, then skip
+ it. We'll catch it later when we do dirs. */
+ n = findnode (dirlist, p->key);
+ if (n != NULL)
+ return (0);
+
+ /* otherwise, call dorecusion for this list of files. */
+ filelist = (List *) p->data;
+ save_dirlist = dirlist;
+ dirlist = NULL;
+
+ if (strcmp(p->key, ".") != 0)
+ {
+ if (save_cwd (&cwd))
+ exit (EXIT_FAILURE);
+ if (chdir (p->key) < 0)
+ error (1, errno, "could not chdir to %s", p->key);
+
+ save_update_dir = xstrdup (update_dir);
+
+ if (*update_dir != '\0')
+ (void) strcat (update_dir, "/");
+
+ (void) strcat (update_dir, p->key);
+ }
+
+ err += do_recursion (frame->fileproc, frame->filesdoneproc,
+ frame->direntproc, frame->dirleaveproc,
+ frame->flags, frame->which, frame->aflag,
+ frame->readlock, frame->dosrcs);
+
+ if (save_update_dir != NULL)
+ {
+ (void) strcpy (update_dir, save_update_dir);
+ free (save_update_dir);
+
+ if (restore_cwd (&cwd, NULL))
+ exit (EXIT_FAILURE);
+ free_cwd (&cwd);
+ }
+
+ dirlist = save_dirlist;
+ filelist = NULL;
+ return(err);
+}
diff --git a/contrib/cvs/src/release.c b/contrib/cvs/src/release.c
new file mode 100644
index 0000000..b3ebb2b
--- /dev/null
+++ b/contrib/cvs/src/release.c
@@ -0,0 +1,286 @@
+/*
+ * Release: "cancel" a checkout in the history log.
+ *
+ * - Don't allow release if anything is active - Don't allow release if not
+ * above or inside repository. - Don't allow release if ./CVS/Repository is
+ * not the same as the directory specified in the module database.
+ *
+ * - Enter a line in the history log indicating the "release". - If asked to,
+ * delete the local working directory.
+ */
+
+#include "cvs.h"
+
+static void release_delete PROTO((char *dir));
+
+static const char *const release_usage[] =
+{
+ "Usage: %s %s [-d] modules...\n",
+ "\t-d\tDelete the given directory.\n",
+ NULL
+};
+
+static short delete_flag;
+
+/* FIXME: This implementation is cheezy in quite a few ways:
+
+ 1. The whole "cvs update" junk could be checked locally with a
+ fairly simple start_recursion/classify_file loop--a win for
+ portability, performance, and cleanliness.
+
+ 2. Should be like edit/unedit in terms of working well if disconnected
+ from the network, and then sending a delayed notification.
+
+ 3. Way too many network turnarounds. More than one for each argument.
+ Puh-leeze.
+
+ 4. Oh, and as a purely stylistic nit, break this out into separate
+ functions for client/local and for server. Those #ifdefs are a mess. */
+
+int
+release (argc, argv)
+ int argc;
+ char **argv;
+{
+ FILE *fp;
+ register int i, c;
+ char *repository, *srepos;
+ char line[PATH_MAX], update_cmd[PATH_MAX];
+ char *thisarg;
+ int arg_start_idx;
+ int err = 0;
+
+#ifdef SERVER_SUPPORT
+ if (!server_active)
+ {
+#endif /* SERVER_SUPPORT */
+ if (argc == -1)
+ usage (release_usage);
+ optind = 1;
+ while ((c = getopt (argc, argv, "Qdq")) != -1)
+ {
+ switch (c)
+ {
+ case 'Q':
+ case 'q':
+#ifdef SERVER_SUPPORT
+ /* The CVS 1.5 client sends these options (in addition to
+ Global_option requests), so we must ignore them. */
+ if (!server_active)
+#endif
+ error (1, 0,
+ "-q or -Q must be specified before \"%s\"",
+ command_name);
+ break;
+ case 'd':
+ delete_flag++;
+ break;
+ case '?':
+ default:
+ usage (release_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+#ifdef SERVER_SUPPORT
+ }
+#endif /* SERVER_SUPPORT */
+
+ /* We're going to run "cvs -n -q update" and check its output; if
+ * the output is sufficiently unalarming, then we release with no
+ * questions asked. Else we prompt, then maybe release.
+ */
+ /* Construct the update command. */
+ sprintf (update_cmd, "%s -n -q -d %s update",
+ program_path, CVSroot);
+
+#ifdef CLIENT_SUPPORT
+ /* Start the server; we'll close it after looping. */
+ if (client_active)
+ {
+ start_server ();
+ ign_setup ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ /* If !server_active, we already skipped over argv[0] in the "argc
+ -= optind;" statement above. But if server_active, we need to
+ skip it now. */
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ arg_start_idx = 1;
+ else
+#endif /* SERVER_SUPPORT */
+ arg_start_idx = 0;
+
+ for (i = arg_start_idx; i < argc; i++)
+ {
+ thisarg = argv[i];
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ /* Just log the release -- all the interesting stuff happened
+ * on the client.
+ */
+ history_write ('F', thisarg, "", thisarg, ""); /* F == Free */
+ }
+ else
+ {
+#endif /* SERVER_SUPPORT */
+
+ /*
+ * If we are in a repository, do it. Else if we are in the parent of
+ * a directory with the same name as the module, "cd" into it and
+ * look for a repository there.
+ */
+ if (isdir (thisarg))
+ {
+ if (chdir (thisarg) < 0)
+ {
+ if (!really_quiet)
+ error (0, 0, "can't chdir to: %s", thisarg);
+ continue;
+ }
+ if (!isdir (CVSADM))
+ {
+ if (!really_quiet)
+ error (0, 0, "no repository module: %s", thisarg);
+ continue;
+ }
+ }
+ else
+ {
+ if (!really_quiet)
+ error (0, 0, "no such directory: %s", thisarg);
+ continue;
+ }
+
+ repository = Name_Repository ((char *) NULL, (char *) NULL);
+ srepos = Short_Repository (repository);
+
+ if (!really_quiet)
+ {
+ /* The "release" command piggybacks on "update", which
+ * does the real work of finding out if anything is not
+ * up-to-date with the repository. Then "release" prompts
+ * the user, telling her how many files have been
+ * modified, and asking if she still wants to do the
+ * release.
+ */
+ fp = run_popen (update_cmd, "r");
+ c = 0;
+
+ while (fgets (line, sizeof (line), fp))
+ {
+ if (strchr ("MARCZ", *line))
+ c++;
+ (void) printf (line);
+ }
+
+ /* If the update exited with an error, then we just want to
+ * complain and go on to the next arg. Especially, we do
+ * not want to delete the local copy, since it's obviously
+ * not what the user thinks it is.
+ */
+ if ((pclose (fp)) != 0)
+ {
+ error (0, 0, "unable to release `%s'", thisarg);
+ continue;
+ }
+
+ (void) printf ("You have [%d] altered files in this repository.\n",
+ c);
+ (void) printf ("Are you sure you want to release %smodule `%s': ",
+ delete_flag ? "(and delete) " : "", thisarg);
+ c = !yesno ();
+ if (c) /* "No" */
+ {
+ (void) fprintf (stderr, "** `%s' aborted by user choice.\n",
+ command_name);
+ free (repository);
+ continue;
+ }
+ }
+
+ if (1
+#ifdef SERVER_SUPPORT
+ && !server_active
+#endif
+#ifdef CLIENT_SUPPORT
+ && !(client_active
+ && (!supported_request ("noop")
+ || !supported_request ("Notify")))
+#endif
+ )
+ {
+ /* We are chdir'ed into the directory in question.
+ So don't pass args to unedit. */
+ int argc = 1;
+ char *argv[3];
+ argv[0] = "dummy";
+ argv[1] = NULL;
+ err += unedit (argc, argv);
+ }
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ send_to_server ("Argument ", 0);
+ send_to_server (thisarg, 0);
+ send_to_server ("\012", 1);
+ send_to_server ("release\012", 0);
+ }
+ else
+ {
+#endif /* CLIENT_SUPPORT */
+ history_write ('F', thisarg, "", thisarg, ""); /* F == Free */
+#ifdef CLIENT_SUPPORT
+ } /* else client not active */
+#endif /* CLIENT_SUPPORT */
+
+ free (repository);
+ if (delete_flag) release_delete (thisarg);
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ return get_responses_and_close ();
+ else
+#endif /* CLIENT_SUPPORT */
+ return (0);
+
+#ifdef SERVER_SUPPORT
+ } /* else server not active */
+#endif /* SERVER_SUPPORT */
+ } /* `for' loop */
+ return err;
+}
+
+
+/* We want to "rm -r" the working directory, but let us be a little
+ paranoid. */
+static void
+release_delete (dir)
+ char *dir;
+{
+ struct stat st;
+ ino_t ino;
+
+ (void) stat (".", &st);
+ ino = st.st_ino;
+ (void) chdir ("..");
+ (void) stat (dir, &st);
+ if (ino != st.st_ino)
+ {
+ error (0, 0,
+ "Parent dir on a different disk, delete of %s aborted", dir);
+ return;
+ }
+ /*
+ * XXX - shouldn't this just delete the CVS-controlled files and, perhaps,
+ * the files that would normally be ignored and leave everything else?
+ */
+ if (unlink_file_dir (dir) < 0)
+ error (0, errno, "deletion of directory %s failed", dir);
+}
diff --git a/contrib/cvs/src/remove.c b/contrib/cvs/src/remove.c
new file mode 100644
index 0000000..2911bf4
--- /dev/null
+++ b/contrib/cvs/src/remove.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Remove a File
+ *
+ * Removes entries from the present version. The entries will be removed from
+ * the RCS repository upon the next "commit".
+ *
+ * "remove" accepts no options, only file names that are to be removed. The
+ * file must not exist in the current directory for "remove" to work
+ * correctly.
+ */
+
+#include "cvs.h"
+
+static int remove_fileproc PROTO((struct file_info *finfo));
+static Dtype remove_dirproc PROTO((char *dir, char *repos, char *update_dir));
+
+static int force;
+static int local;
+static int removed_files;
+static int existing_files;
+
+static const char *const remove_usage[] =
+{
+ "Usage: %s %s [-flR] [files...]\n",
+ "\t-f\tDelete the file before removing it.\n",
+ "\t-l\tProcess this directory only (not recursive).\n",
+ "\t-R\tProcess directories recursively.\n",
+ NULL
+};
+
+int
+cvsremove (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c, err;
+
+ if (argc == -1)
+ usage (remove_usage);
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "flR")) != -1)
+ {
+ switch (c)
+ {
+ case 'f':
+ force = 1;
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case '?':
+ default:
+ usage (remove_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ wrap_setup ();
+
+#ifdef CLIENT_SUPPORT
+ if (client_active) {
+ start_server ();
+ ign_setup ();
+ if (local)
+ send_arg("-l");
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ send_files (argc, argv, local, 0);
+ send_to_server ("remove\012", 0);
+ return get_responses_and_close ();
+ }
+#endif
+
+ /* start the recursion processor */
+ err = start_recursion (remove_fileproc, (FILESDONEPROC) NULL,
+ remove_dirproc, (DIRLEAVEPROC) NULL, argc, argv,
+ local, W_LOCAL, 0, 1, (char *) NULL, 1, 0);
+
+ if (removed_files)
+ error (0, 0, "use '%s commit' to remove %s permanently", program_name,
+ (removed_files == 1) ? "this file" : "these files");
+
+ if (existing_files)
+ error (0, 0,
+ ((existing_files == 1) ?
+ "%d file exists; remove it first" :
+ "%d files exist; remove them first"),
+ existing_files);
+
+ return (err);
+}
+
+/*
+ * remove the file, only if it has already been physically removed
+ */
+/* ARGSUSED */
+static int
+remove_fileproc (finfo)
+ struct file_info *finfo;
+{
+ char fname[PATH_MAX];
+ Vers_TS *vers;
+
+ if (force)
+ {
+ if (!noexec)
+ {
+ if (unlink (finfo->file) < 0 && ! existence_error (errno))
+ {
+ error (0, errno, "unable to remove %s", finfo->fullname);
+ }
+ }
+ /* else FIXME should probably act as if the file doesn't exist
+ in doing the following checks. */
+ }
+
+ vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL,
+ finfo->file, 0, 0, finfo->entries, finfo->rcs);
+
+ if (vers->ts_user != NULL)
+ {
+ existing_files++;
+ if (!quiet)
+ error (0, 0, "file `%s' still in working directory",
+ finfo->fullname);
+ }
+ else if (vers->vn_user == NULL)
+ {
+ if (!quiet)
+ error (0, 0, "nothing known about `%s'", finfo->fullname);
+ }
+ else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
+ {
+ /*
+ * It's a file that has been added, but not commited yet. So,
+ * remove the ,t file for it and scratch it from the
+ * entries file. */
+ Scratch_Entry (finfo->entries, finfo->file);
+ (void) sprintf (fname, "%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
+ (void) unlink_file (fname);
+ if (!quiet)
+ error (0, 0, "removed `%s'", finfo->fullname);
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
+#endif
+ }
+ else if (vers->vn_user[0] == '-')
+ {
+ if (!quiet)
+ error (0, 0, "file `%s' already scheduled for removal",
+ finfo->fullname);
+ }
+ else
+ {
+ /* Re-register it with a negative version number. */
+ (void) strcpy (fname, "-");
+ (void) strcat (fname, vers->vn_user);
+ Register (finfo->entries, finfo->file, fname, vers->ts_rcs, vers->options,
+ vers->tag, vers->date, vers->ts_conflict);
+ if (!quiet)
+ error (0, 0, "scheduling `%s' for removal", finfo->fullname);
+ removed_files++;
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
+#endif
+ }
+
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+remove_dirproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "Removing %s", update_dir);
+ return (R_PROCESS);
+}
diff --git a/contrib/cvs/src/repos.c b/contrib/cvs/src/repos.c
new file mode 100644
index 0000000..7beaaba
--- /dev/null
+++ b/contrib/cvs/src/repos.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ */
+
+#include "cvs.h"
+
+/* Determine the name of the RCS repository for directory DIR in the
+ current working directory, or for the current working directory
+ itself if DIR is NULL. Returns the name in a newly-malloc'd
+ string. On error, gives a fatal error and does not return.
+ UPDATE_DIR is the path from where cvs was invoked (for use in error
+ messages), and should contain DIR as its last component.
+ UPDATE_DIR can be NULL to signify the directory in which cvs was
+ invoked. */
+
+char *
+Name_Repository (dir, update_dir)
+ char *dir;
+ char *update_dir;
+{
+ FILE *fpin;
+ char *ret, *xupdate_dir;
+ char repos[PATH_MAX];
+ char path[PATH_MAX];
+ char tmp[PATH_MAX];
+ char cvsadm[PATH_MAX];
+ char *cp;
+
+ if (update_dir && *update_dir)
+ xupdate_dir = update_dir;
+ else
+ xupdate_dir = ".";
+
+ if (dir != NULL)
+ (void) sprintf (cvsadm, "%s/%s", dir, CVSADM);
+ else
+ (void) strcpy (cvsadm, CVSADM);
+
+ /* sanity checks */
+ if (!isdir (cvsadm))
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (1, 0, "there is no version here; do '%s checkout' first",
+ program_name);
+ }
+
+ if (dir != NULL)
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENT);
+ else
+ (void) strcpy (tmp, CVSADM_ENT);
+
+ if (!isreadable (tmp))
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (1, 0, "*PANIC* administration files missing");
+ }
+
+ if (dir != NULL)
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_REP);
+ else
+ (void) strcpy (tmp, CVSADM_REP);
+
+ if (!isreadable (tmp))
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (1, 0, "*PANIC* administration files missing");
+ }
+
+ /*
+ * The assumption here is that the repository is always contained in the
+ * first line of the "Repository" file.
+ */
+ fpin = open_file (tmp, "r");
+
+ if (fgets (repos, PATH_MAX, fpin) == NULL)
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (1, errno, "cannot read %s", CVSADM_REP);
+ }
+ (void) fclose (fpin);
+ if ((cp = strrchr (repos, '\n')) != NULL)
+ *cp = '\0'; /* strip the newline */
+
+ /*
+ * If this is a relative repository pathname, turn it into an absolute
+ * one by tacking on the CVSROOT environment variable. If the CVSROOT
+ * environment variable is not set, die now.
+ */
+ if (strcmp (repos, "..") == 0 || strncmp (repos, "../", 3) == 0)
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, 0, "`..'-relative repositories are not supported.");
+ error (1, 0, "illegal source repository");
+ }
+ if (! isabsolute(repos))
+ {
+ if (CVSroot == NULL)
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, 0, "must set the CVSROOT environment variable\n");
+ error (0, 0, "or specify the '-d' option to %s.", program_name);
+ error (1, 0, "illegal repository setting");
+ }
+ (void) strcpy (path, repos);
+ (void) sprintf (repos, "%s/%s", CVSroot, path);
+ }
+#ifdef CLIENT_SUPPORT
+ if (!client_active && !isdir (repos))
+#else
+ if (!isdir (repos))
+#endif
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (1, 0, "there is no repository %s", repos);
+ }
+
+ /* allocate space to return and fill it in */
+ strip_path (repos);
+ ret = xstrdup (repos);
+ return (ret);
+}
+
+/*
+ * Return a pointer to the repository name relative to CVSROOT from a
+ * possibly fully qualified repository
+ */
+char *
+Short_Repository (repository)
+ char *repository;
+{
+ if (repository == NULL)
+ return (NULL);
+
+ /* If repository matches CVSroot at the beginning, strip off CVSroot */
+ /* And skip leading '/' in rep, in case CVSroot ended with '/'. */
+ if (strncmp (CVSroot, repository, strlen (CVSroot)) == 0)
+ {
+ char *rep = repository + strlen (CVSroot);
+ return (*rep == '/') ? rep+1 : rep;
+ }
+ else
+ return (repository);
+}
diff --git a/contrib/cvs/src/root.c b/contrib/cvs/src/root.c
new file mode 100644
index 0000000..0742cf0
--- /dev/null
+++ b/contrib/cvs/src/root.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 1992, Mark D. Baushke
+ *
+ * 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.
+ *
+ * Name of Root
+ *
+ * Determine the path to the CVSROOT and set "Root" accordingly.
+ * If this looks like of modified clone of Name_Repository() in
+ * repos.c, it is...
+ */
+
+#include "cvs.h"
+
+char *
+Name_Root(dir, update_dir)
+ char *dir;
+ char *update_dir;
+{
+ FILE *fpin;
+ char *ret, *xupdate_dir;
+ char root[PATH_MAX];
+ char tmp[PATH_MAX];
+ char cvsadm[PATH_MAX];
+ char *cp;
+
+ if (update_dir && *update_dir)
+ xupdate_dir = update_dir;
+ else
+ xupdate_dir = ".";
+
+ if (dir != NULL)
+ {
+ (void) sprintf (cvsadm, "%s/%s", dir, CVSADM);
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT);
+ }
+ else
+ {
+ (void) strcpy (cvsadm, CVSADM);
+ (void) strcpy (tmp, CVSADM_ROOT);
+ }
+
+ /*
+ * Do not bother looking for a readable file if there is no cvsadm
+ * directory present.
+ *
+ * It is possible that not all repositories will have a CVS/Root
+ * file. This is ok, but the user will need to specify -d
+ * /path/name or have the environment variable CVSROOT set in
+ * order to continue.
+ */
+ if ((!isdir (cvsadm)) || (!isreadable (tmp)))
+ {
+ if (CVSroot == NULL)
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, 0, "must set the CVSROOT environment variable");
+ error (0, 0, "or specify the '-d' option to %s.", program_name);
+ }
+ return (NULL);
+ }
+
+ /*
+ * The assumption here is that the CVS Root is always contained in the
+ * first line of the "Root" file.
+ */
+ fpin = open_file (tmp, "r");
+
+ if (fgets (root, PATH_MAX, fpin) == NULL)
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, errno, "cannot read %s", CVSADM_ROOT);
+ error (0, 0, "please correct this problem");
+ return (NULL);
+ }
+ (void) fclose (fpin);
+ if ((cp = strrchr (root, '\n')) != NULL)
+ *cp = '\0'; /* strip the newline */
+
+ /*
+ * root now contains a candidate for CVSroot. It must be an
+ * absolute pathname
+ */
+
+#ifdef CLIENT_SUPPORT
+ /* It must specify a server via remote CVS or be an absolute pathname. */
+ if ((strchr (root, ':') == NULL)
+ && ! isabsolute (root))
+#else /* ! CLIENT_SUPPORT */
+ if (root[0] != '/')
+#endif /* CLIENT_SUPPORT */
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, 0,
+ "ignoring %s because it does not contain an absolute pathname.",
+ CVSADM_ROOT);
+ return (NULL);
+ }
+
+#ifdef CLIENT_SUPPORT
+ if ((strchr (root, ':') == NULL) && !isdir (root))
+#else /* ! CLIENT_SUPPORT */
+ if (!isdir (root))
+#endif /* CLIENT_SUPPORT */
+ {
+ error (0, 0, "in directory %s:", xupdate_dir);
+ error (0, 0,
+ "ignoring %s because it specifies a non-existent repository %s",
+ CVSADM_ROOT, root);
+ return (NULL);
+ }
+
+ /* allocate space to return and fill it in */
+ strip_path (root);
+ ret = xstrdup (root);
+ return (ret);
+}
+
+/*
+ * Returns non-zero if the two directories have the same stat values
+ * which indicates that they are really the same directories.
+ */
+int
+same_directories (dir1, dir2)
+ char *dir1;
+ char *dir2;
+{
+ struct stat sb1;
+ struct stat sb2;
+ int ret;
+
+ if (stat (dir1, &sb1) < 0)
+ return (0);
+ if (stat (dir2, &sb2) < 0)
+ return (0);
+
+ ret = 0;
+ if ( (memcmp( &sb1.st_dev, &sb2.st_dev, sizeof(dev_t) ) == 0) &&
+ (memcmp( &sb1.st_ino, &sb2.st_ino, sizeof(ino_t) ) == 0))
+ ret = 1;
+
+ return (ret);
+}
+
+
+/*
+ * Write the CVS/Root file so that the environment variable CVSROOT
+ * and/or the -d option to cvs will be validated or not necessary for
+ * future work.
+ */
+void
+Create_Root (dir, rootdir)
+ char *dir;
+ char *rootdir;
+{
+ FILE *fout;
+ char tmp[PATH_MAX];
+
+ if (noexec)
+ return;
+
+ /* record the current cvs root */
+
+ if (rootdir != NULL)
+ {
+ if (dir != NULL)
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT);
+ else
+ (void) strcpy (tmp, CVSADM_ROOT);
+ fout = open_file (tmp, "w+");
+ if (fprintf (fout, "%s\n", rootdir) < 0)
+ error (1, errno, "write to %s failed", tmp);
+ if (fclose (fout) == EOF)
+ error (1, errno, "cannot close %s", tmp);
+ }
+}
diff --git a/contrib/cvs/src/rtag.c b/contrib/cvs/src/rtag.c
new file mode 100644
index 0000000..8609647
--- /dev/null
+++ b/contrib/cvs/src/rtag.c
@@ -0,0 +1,682 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Rtag
+ *
+ * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
+ * Uses the modules database, if necessary.
+ */
+
+#include "cvs.h"
+
+static int check_fileproc PROTO((struct file_info *finfo));
+static int check_filesdoneproc PROTO((int err, char *repos, char *update_dir));
+static int pretag_proc PROTO((char *repository, char *filter));
+static void masterlist_delproc PROTO((Node *p));
+static void tag_delproc PROTO((Node *p));
+static int pretag_list_proc PROTO((Node *p, void *closure));
+
+static Dtype rtag_dirproc PROTO((char *dir, char *repos, char *update_dir));
+static int rtag_fileproc PROTO((struct file_info *finfo));
+static int rtag_proc PROTO((int *pargc, char **argv, char *xwhere,
+ char *mwhere, char *mfile, int shorten,
+ int local_specified, char *mname, char *msg));
+static int rtag_delete PROTO((RCSNode *rcsfile));
+
+
+struct tag_info
+{
+ Ctype status;
+ char *rev;
+ char *tag;
+ char *options;
+};
+
+struct master_lists
+{
+ List *tlist;
+};
+
+static List *mtlist;
+static List *tlist;
+
+static char *symtag;
+static char *numtag;
+static int numtag_validated = 0;
+static int delete_flag; /* adding a tag by default */
+static int attic_too; /* remove tag from Attic files */
+static int branch_mode; /* make an automagic "branch" tag */
+static char *date;
+static int local; /* recursive by default */
+static int force_tag_match = 1; /* force by default */
+static int force_tag_move; /* don't move existing tags by default */
+
+static const char *const rtag_usage[] =
+{
+ "Usage: %s %s [-aflRnF] [-b] [-d] [-r tag|-D date] tag modules...\n",
+ "\t-a\tClear tag from removed files that would not otherwise be tagged.\n",
+ "\t-f\tForce a head revision match if tag/date not found.\n",
+ "\t-l\tLocal directory only, not recursive\n",
+ "\t-R\tProcess directories recursively.\n",
+ "\t-n\tNo execution of 'tag program'\n",
+ "\t-d\tDelete the given Tag.\n",
+ "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
+ "\t-[rD]\tExisting tag or Date.\n",
+ "\t-F\tMove tag if it already exists\n",
+ NULL
+};
+
+int
+rtag (argc, argv)
+ int argc;
+ char **argv;
+{
+ register int i;
+ int c;
+ DBM *db;
+ int run_module_prog = 1;
+ int err = 0;
+
+ if (argc == -1)
+ usage (rtag_usage);
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "FanfQqlRdbr:D:")) != -1)
+ {
+ switch (c)
+ {
+ case 'a':
+ attic_too = 1;
+ break;
+ case 'n':
+ run_module_prog = 0;
+ break;
+ case 'Q':
+ case 'q':
+#ifdef SERVER_SUPPORT
+ /* The CVS 1.5 client sends these options (in addition to
+ Global_option requests), so we must ignore them. */
+ if (!server_active)
+#endif
+ error (1, 0,
+ "-q or -Q must be specified before \"%s\"",
+ command_name);
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'd':
+ delete_flag = 1;
+ break;
+ case 'f':
+ force_tag_match = 0;
+ break;
+ case 'b':
+ branch_mode = 1;
+ break;
+ case 'r':
+ numtag = optarg;
+ break;
+ case 'D':
+ if (date)
+ free (date);
+ date = Make_Date (optarg);
+ break;
+ case 'F':
+ force_tag_move = 1;
+ break;
+ case '?':
+ default:
+ usage (rtag_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc < 2)
+ usage (rtag_usage);
+ symtag = argv[0];
+ argc--;
+ argv++;
+
+ if (date && numtag)
+ error (1, 0, "-r and -D options are mutually exclusive");
+ if (delete_flag && branch_mode)
+ error (0, 0, "warning: -b ignored with -d options");
+ RCS_check_tag (symtag);
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ /* We're the client side. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ if (local)
+ send_arg("-l");
+ if (delete_flag)
+ send_arg("-d");
+ if (branch_mode)
+ send_arg("-b");
+ if (force_tag_move)
+ send_arg("-F");
+ if (run_module_prog)
+ send_arg("-n");
+ if (attic_too)
+ send_arg("-a");
+
+ if (numtag)
+ option_with_arg ("-r", numtag);
+ if (date)
+ client_senddate (date);
+
+ send_arg (symtag);
+
+ {
+ int i;
+ for (i = 0; i < argc; ++i)
+ send_arg (argv[i]);
+ }
+
+ send_to_server ("rtag\012", 0);
+ return get_responses_and_close ();
+ }
+#endif
+
+ db = open_module ();
+ for (i = 0; i < argc; i++)
+ {
+ /* XXX last arg should be repository, but doesn't make sense here */
+ history_write ('T', (delete_flag ? "D" : (numtag ? numtag :
+ (date ? date : "A"))), symtag, argv[i], "");
+ err += do_module (db, argv[i], TAG, delete_flag ? "Untagging" : "Tagging",
+ rtag_proc, (char *) NULL, 0, 0, run_module_prog,
+ symtag);
+ }
+ close_module (db);
+ return (err);
+}
+
+/*
+ * callback proc for doing the real work of tagging
+ */
+/* ARGSUSED */
+static int
+rtag_proc (pargc, argv, xwhere, mwhere, mfile, shorten, local_specified,
+ mname, msg)
+ int *pargc;
+ char **argv;
+ char *xwhere;
+ char *mwhere;
+ char *mfile;
+ int shorten;
+ int local_specified;
+ char *mname;
+ char *msg;
+{
+ int err = 0;
+ int which;
+ char repository[PATH_MAX];
+ char where[PATH_MAX];
+
+ (void) sprintf (repository, "%s/%s", CVSroot, argv[0]);
+ (void) strcpy (where, argv[0]);
+
+ /* if mfile isn't null, we need to set up to do only part of the module */
+ if (mfile != NULL)
+ {
+ char *cp;
+ char path[PATH_MAX];
+
+ /* if the portion of the module is a path, put the dir part on repos */
+ if ((cp = strrchr (mfile, '/')) != NULL)
+ {
+ *cp = '\0';
+ (void) strcat (repository, "/");
+ (void) strcat (repository, mfile);
+ (void) strcat (where, "/");
+ (void) strcat (where, mfile);
+ mfile = cp + 1;
+ }
+
+ /* take care of the rest */
+ (void) sprintf (path, "%s/%s", repository, mfile);
+ if (isdir (path))
+ {
+ /* directory means repository gets the dir tacked on */
+ (void) strcpy (repository, path);
+ (void) strcat (where, "/");
+ (void) strcat (where, mfile);
+ }
+ else
+ {
+ int i;
+
+ /* a file means muck argv */
+ for (i = 1; i < *pargc; i++)
+ free (argv[i]);
+ argv[1] = xstrdup (mfile);
+ (*pargc) = 2;
+ }
+ }
+
+ /* chdir to the starting directory */
+ if (chdir (repository) < 0)
+ {
+ error (0, errno, "cannot chdir to %s", repository);
+ return (1);
+ }
+
+ if (delete_flag || attic_too || (force_tag_match && numtag))
+ which = W_REPOS | W_ATTIC;
+ else
+ which = W_REPOS;
+
+ if (numtag != NULL && !numtag_validated)
+ {
+ tag_check_valid (numtag, *pargc - 1, argv + 1, local, 0, NULL);
+ numtag_validated = 1;
+ }
+
+ /* check to make sure they are authorized to tag all the
+ specified files in the repository */
+
+ mtlist = getlist();
+ err = start_recursion (check_fileproc, check_filesdoneproc,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ *pargc - 1, argv + 1, local, which, 0, 1,
+ where, 1, 1);
+
+ if (err)
+ {
+ error (1, 0, "correct the above errors first!");
+ }
+
+ /* start the recursion processor */
+ err = start_recursion (rtag_fileproc, (FILESDONEPROC) NULL, rtag_dirproc,
+ (DIRLEAVEPROC) NULL, *pargc - 1, argv + 1, local,
+ which, 0, 1, where, 1, 1);
+
+ dellist(&mtlist);
+
+ return (err);
+}
+
+/* check file that is to be tagged */
+/* All we do here is add it to our list */
+
+static int
+check_fileproc (finfo)
+ struct file_info *finfo;
+{
+ char *xdir;
+ Node *p;
+ Vers_TS *vers;
+
+ if (finfo->update_dir[0] == '\0')
+ xdir = ".";
+ else
+ xdir = finfo->update_dir;
+ if ((p = findnode (mtlist, xdir)) != NULL)
+ {
+ tlist = ((struct master_lists *) p->data)->tlist;
+ }
+ else
+ {
+ struct master_lists *ml;
+
+ tlist = getlist ();
+ p = getnode ();
+ p->key = xstrdup (xdir);
+ p->type = UPDATE;
+ ml = (struct master_lists *)
+ xmalloc (sizeof (struct master_lists));
+ ml->tlist = tlist;
+ p->data = (char *) ml;
+ p->delproc = masterlist_delproc;
+ (void) addnode (mtlist, p);
+ }
+ /* do tlist */
+ p = getnode ();
+ p->key = xstrdup (finfo->file);
+ p->type = UPDATE;
+ p->delproc = tag_delproc;
+ vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL,
+ (char *) NULL, finfo->file, 0, 0, finfo->entries, finfo->rcs);
+ p->data = RCS_getversion(vers->srcfile, numtag, date, force_tag_match, 0);
+ if (p->data != NULL)
+ {
+ int addit = 1;
+ char *oversion;
+
+ oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1, 0);
+ if (oversion == NULL)
+ {
+ if (delete_flag)
+ {
+ addit = 0;
+ }
+ }
+ else if (strcmp(oversion, p->data) == 0)
+ {
+ addit = 0;
+ }
+ else if (!force_tag_move)
+ {
+ addit = 0;
+ }
+ if (oversion != NULL)
+ {
+ free(oversion);
+ }
+ if (!addit)
+ {
+ free(p->data);
+ p->data = NULL;
+ }
+ }
+ freevers_ts (&vers);
+ (void) addnode (tlist, p);
+ return (0);
+}
+
+static int
+check_filesdoneproc(err, repos, update_dir)
+ int err;
+ char *repos;
+ char *update_dir;
+{
+ int n;
+ Node *p;
+
+ p = findnode(mtlist, update_dir);
+ if (p != NULL)
+ {
+ tlist = ((struct master_lists *) p->data)->tlist;
+ }
+ else
+ {
+ tlist = (List *) NULL;
+ }
+ if ((tlist == NULL) || (tlist->list->next == tlist->list))
+ {
+ return (err);
+ }
+ if ((n = Parse_Info(CVSROOTADM_TAGINFO, repos, pretag_proc, 1)) > 0)
+ {
+ error (0, 0, "Pre-tag check failed");
+ err += n;
+ }
+ return (err);
+}
+
+static int
+pretag_proc(repository, filter)
+ char *repository;
+ char *filter;
+{
+ if (filter[0] == '/')
+ {
+ char *s, *cp;
+
+ s = xstrdup(filter);
+ for (cp=s; *cp; cp++)
+ {
+ if (isspace(*cp))
+ {
+ *cp = '\0';
+ break;
+ }
+ }
+ if (!isfile(s))
+ {
+ error (0, errno, "cannot find pre-tag filter '%s'", s);
+ free(s);
+ return (1);
+ }
+ free(s);
+ }
+ run_setup("%s %s %s %s",
+ filter,
+ symtag,
+ delete_flag ? "del" : force_tag_move ? "mov" : "add",
+ repository);
+ walklist(tlist, pretag_list_proc, NULL);
+ return (run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY));
+}
+
+static void
+masterlist_delproc(p)
+ Node *p;
+{
+ struct master_lists *ml;
+
+ ml = (struct master_lists *)p->data;
+ dellist(&ml->tlist);
+ free(ml);
+ return;
+}
+
+static void
+tag_delproc(p)
+ Node *p;
+{
+ if (p->data != NULL)
+ {
+ free(p->data);
+ p->data = NULL;
+ }
+ return;
+}
+
+static int
+pretag_list_proc(p, closure)
+ Node *p;
+ void *closure;
+{
+ if (p->data != NULL)
+ {
+ run_arg(p->key);
+ run_arg(p->data);
+ }
+ return (0);
+}
+
+/*
+ * Called to tag a particular file, as appropriate with the options that were
+ * set above.
+ */
+/* ARGSUSED */
+static int
+rtag_fileproc (finfo)
+ struct file_info *finfo;
+{
+ RCSNode *rcsfile;
+ char *version, *rev;
+ int retcode = 0;
+
+ /* find the parsed RCS data */
+ if ((rcsfile = finfo->rcs) == NULL)
+ return (1);
+
+ /*
+ * For tagging an RCS file which is a symbolic link, you'd best be
+ * running with RCS 5.6, since it knows how to handle symbolic links
+ * correctly without breaking your link!
+ */
+
+ if (delete_flag)
+ return (rtag_delete (rcsfile));
+
+ /*
+ * If we get here, we are adding a tag. But, if -a was specified, we
+ * need to check to see if a -r or -D option was specified. If neither
+ * was specified and the file is in the Attic, remove the tag.
+ */
+ if (attic_too && (!numtag && !date))
+ {
+ if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
+ return (rtag_delete (rcsfile));
+ }
+
+ version = RCS_getversion (rcsfile, numtag, date, force_tag_match, 0);
+ if (version == NULL)
+ {
+ /* If -a specified, clean up any old tags */
+ if (attic_too)
+ (void) rtag_delete (rcsfile);
+
+ if (!quiet && !force_tag_match)
+ {
+ error (0, 0, "cannot find tag `%s' in `%s'",
+ numtag ? numtag : "head", rcsfile->path);
+ return (1);
+ }
+ return (0);
+ }
+ if (numtag && isdigit (*numtag) && strcmp (numtag, version) != 0)
+ {
+
+ /*
+ * We didn't find a match for the numeric tag that was specified, but
+ * that's OK. just pass the numeric tag on to rcs, to be tagged as
+ * specified. Could get here if one tried to tag "1.1.1" and there
+ * was a 1.1.1 branch with some head revision. In this case, we want
+ * the tag to reference "1.1.1" and not the revision at the head of
+ * the branch. Use a symbolic tag for that.
+ */
+ rev = branch_mode ? RCS_magicrev (rcsfile, version) : numtag;
+ retcode = RCS_settag(rcsfile->path, symtag, numtag);
+ }
+ else
+ {
+ char *oversion;
+
+ /*
+ * As an enhancement for the case where a tag is being re-applied to
+ * a large body of a module, make one extra call to RCS_getversion to
+ * see if the tag is already set in the RCS file. If so, check to
+ * see if it needs to be moved. If not, do nothing. This will
+ * likely save a lot of time when simply moving the tag to the
+ * "current" head revisions of a module -- which I have found to be a
+ * typical tagging operation.
+ */
+ rev = branch_mode ? RCS_magicrev (rcsfile, version) : version;
+ oversion = RCS_getversion (rcsfile, symtag, (char *) NULL, 1, 0);
+ if (oversion != NULL)
+ {
+ int isbranch = RCS_isbranch (finfo->rcs, symtag);
+
+ /*
+ * if versions the same and neither old or new are branches don't
+ * have to do anything
+ */
+ if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
+ {
+ free (oversion);
+ free (version);
+ return (0);
+ }
+
+ if (!force_tag_move)
+ {
+ /* we're NOT going to move the tag */
+ (void) printf ("W %s", finfo->fullname);
+
+ (void) printf (" : %s already exists on %s %s",
+ symtag, isbranch ? "branch" : "version",
+ oversion);
+ (void) printf (" : NOT MOVING tag to %s %s\n",
+ branch_mode ? "branch" : "version", rev);
+ free (oversion);
+ free (version);
+ return (0);
+ }
+ free (oversion);
+ }
+ retcode = RCS_settag(rcsfile->path, symtag, rev);
+ }
+
+ if (retcode != 0)
+ {
+ error (1, retcode == -1 ? errno : 0,
+ "failed to set tag `%s' to revision `%s' in `%s'",
+ symtag, rev, rcsfile->path);
+ if (branch_mode)
+ free (rev);
+ free (version);
+ return (1);
+ }
+ if (branch_mode)
+ free (rev);
+ free (version);
+ return (0);
+}
+
+/*
+ * If -d is specified, "force_tag_match" is set, so that this call to
+ * RCS_getversion() will return a NULL version string if the symbolic
+ * tag does not exist in the RCS file.
+ *
+ * If the -r flag was used, numtag is set, and we only delete the
+ * symtag from files that have numtag.
+ *
+ * This is done here because it's MUCH faster than just blindly calling
+ * "rcs" to remove the tag... trust me.
+ */
+static int
+rtag_delete (rcsfile)
+ RCSNode *rcsfile;
+{
+ char *version;
+ int retcode;
+
+ if (numtag)
+ {
+ version = RCS_getversion (rcsfile, numtag, (char *) NULL, 1, 0);
+ if (version == NULL)
+ return (0);
+ free (version);
+ }
+
+ version = RCS_getversion (rcsfile, symtag, (char *) NULL, 1, 0);
+ if (version == NULL)
+ return (0);
+ free (version);
+
+ if ((retcode = RCS_deltag(rcsfile->path, symtag, 1)) != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to remove tag `%s' from `%s'", symtag,
+ rcsfile->path);
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+rtag_dirproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging", update_dir);
+ return (R_PROCESS);
+}
+
+
+
diff --git a/contrib/cvs/src/run.c b/contrib/cvs/src/run.c
new file mode 100644
index 0000000..036821e
--- /dev/null
+++ b/contrib/cvs/src/run.c
@@ -0,0 +1,541 @@
+/* run.c --- routines for executing subprocesses.
+
+ This file is part of GNU CVS.
+
+ GNU CVS is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2, or (at your option) any
+ later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "cvs.h"
+
+#ifdef HAVE_VPRINTF
+#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)
+#include <stdarg.h>
+#define VA_START(args, lastarg) va_start(args, lastarg)
+#else
+#include <varargs.h>
+#define VA_START(args, lastarg) va_start(args)
+#endif
+#else
+#define va_alist a1, a2, a3, a4, a5, a6, a7, a8
+#define va_dcl char *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8;
+#endif
+
+static void run_add_arg PROTO((const char *s));
+static void run_init_prog PROTO((void));
+
+extern char *strtok ();
+
+/*
+ * To exec a program under CVS, first call run_setup() to setup any initial
+ * arguments. The options to run_setup are essentially like printf(). The
+ * arguments will be parsed into whitespace separated words and added to the
+ * global run_argv list.
+ *
+ * Then, optionally call run_arg() for each additional argument that you'd like
+ * to pass to the executed program.
+ *
+ * Finally, call run_exec() to execute the program with the specified arguments.
+ * The execvp() syscall will be used, so that the PATH is searched correctly.
+ * File redirections can be performed in the call to run_exec().
+ */
+static char *run_prog;
+static char **run_argv;
+static int run_argc;
+static int run_argc_allocated;
+
+/* VARARGS */
+#if defined (HAVE_VPRINTF) && (defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__))
+void
+run_setup (const char *fmt,...)
+#else
+void
+run_setup (fmt, va_alist)
+ char *fmt;
+ va_dcl
+#endif
+{
+#ifdef HAVE_VPRINTF
+ va_list args;
+#endif
+ char *cp;
+ int i;
+
+ run_init_prog ();
+
+ /* clean out any malloc'ed values from run_argv */
+ for (i = 0; i < run_argc; i++)
+ {
+ if (run_argv[i])
+ {
+ free (run_argv[i]);
+ run_argv[i] = (char *) 0;
+ }
+ }
+ run_argc = 0;
+
+ /* process the varargs into run_prog */
+#ifdef HAVE_VPRINTF
+ VA_START (args, fmt);
+ (void) vsprintf (run_prog, fmt, args);
+ va_end (args);
+#else
+ (void) sprintf (run_prog, fmt, a1, a2, a3, a4, a5, a6, a7, a8);
+#endif
+
+ /* put each word into run_argv, allocating it as we go */
+ for (cp = strtok (run_prog, " \t"); cp; cp = strtok ((char *) NULL, " \t"))
+ run_add_arg (cp);
+}
+
+void
+run_arg (s)
+ const char *s;
+{
+ run_add_arg (s);
+}
+
+/* VARARGS */
+#if defined (HAVE_VPRINTF) && (defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__))
+void
+run_args (const char *fmt,...)
+#else
+void
+run_args (fmt, va_alist)
+ char *fmt;
+ va_dcl
+#endif
+{
+#ifdef HAVE_VPRINTF
+ va_list args;
+#endif
+
+ run_init_prog ();
+
+ /* process the varargs into run_prog */
+#ifdef HAVE_VPRINTF
+ VA_START (args, fmt);
+ (void) vsprintf (run_prog, fmt, args);
+ va_end (args);
+#else
+ (void) sprintf (run_prog, fmt, a1, a2, a3, a4, a5, a6, a7, a8);
+#endif
+
+ /* and add the (single) argument to the run_argv list */
+ run_add_arg (run_prog);
+}
+
+static void
+run_add_arg (s)
+ const char *s;
+{
+ /* allocate more argv entries if we've run out */
+ if (run_argc >= run_argc_allocated)
+ {
+ run_argc_allocated += 50;
+ run_argv = (char **) xrealloc ((char *) run_argv,
+ run_argc_allocated * sizeof (char **));
+ }
+
+ if (s)
+ run_argv[run_argc++] = xstrdup (s);
+ else
+ run_argv[run_argc] = (char *) 0; /* not post-incremented on purpose! */
+}
+
+static void
+run_init_prog ()
+{
+ /* make sure that run_prog is allocated once */
+ if (run_prog == (char *) 0)
+ run_prog = xmalloc (10 * 1024); /* 10K of args for _setup and _arg */
+}
+
+int
+run_exec (stin, stout, sterr, flags)
+ char *stin;
+ char *stout;
+ char *sterr;
+ int flags;
+{
+ int shin, shout, sherr;
+ int mode_out, mode_err;
+ int status;
+ int rc = -1;
+ int rerrno = 0;
+ int pid, w;
+
+#ifdef POSIX_SIGNALS
+ sigset_t sigset_mask, sigset_omask;
+ struct sigaction act, iact, qact;
+
+#else
+#ifdef BSD_SIGNALS
+ int mask;
+ struct sigvec vec, ivec, qvec;
+
+#else
+ RETSIGTYPE (*istat) (), (*qstat) ();
+#endif
+#endif
+
+ if (trace)
+ {
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> system(", (server_active) ? 'S' : ' ');
+#else
+ (void) fprintf (stderr, "-> system(");
+#endif
+ run_print (stderr);
+ (void) fprintf (stderr, ")\n");
+ }
+ if (noexec && (flags & RUN_REALLY) == 0)
+ return (0);
+
+ /* make sure that we are null terminated, since we didn't calloc */
+ run_add_arg ((char *) 0);
+
+ /* setup default file descriptor numbers */
+ shin = 0;
+ shout = 1;
+ sherr = 2;
+
+ /* set the file modes for stdout and stderr */
+ mode_out = mode_err = O_WRONLY | O_CREAT;
+ mode_out |= ((flags & RUN_STDOUT_APPEND) ? O_APPEND : O_TRUNC);
+ mode_err |= ((flags & RUN_STDERR_APPEND) ? O_APPEND : O_TRUNC);
+
+ if (stin && (shin = open (stin, O_RDONLY)) == -1)
+ {
+ rerrno = errno;
+ error (0, errno, "cannot open %s for reading (prog %s)",
+ stin, run_argv[0]);
+ goto out0;
+ }
+ if (stout && (shout = open (stout, mode_out, 0666)) == -1)
+ {
+ rerrno = errno;
+ error (0, errno, "cannot open %s for writing (prog %s)",
+ stout, run_argv[0]);
+ goto out1;
+ }
+ if (sterr && (flags & RUN_COMBINED) == 0)
+ {
+ if ((sherr = open (sterr, mode_err, 0666)) == -1)
+ {
+ rerrno = errno;
+ error (0, errno, "cannot open %s for writing (prog %s)",
+ sterr, run_argv[0]);
+ goto out2;
+ }
+ }
+
+ /* Make sure we don't flush this twice, once in the subprocess. */
+ fflush (stdout);
+ fflush (stderr);
+
+ /* The output files, if any, are now created. Do the fork and dups */
+#ifdef HAVE_VFORK
+ pid = vfork ();
+#else
+ pid = fork ();
+#endif
+ if (pid == 0)
+ {
+ if (shin != 0)
+ {
+ (void) dup2 (shin, 0);
+ (void) close (shin);
+ }
+ if (shout != 1)
+ {
+ (void) dup2 (shout, 1);
+ (void) close (shout);
+ }
+ if (flags & RUN_COMBINED)
+ (void) dup2 (1, 2);
+ else if (sherr != 2)
+ {
+ (void) dup2 (sherr, 2);
+ (void) close (sherr);
+ }
+
+ /* dup'ing is done. try to run it now */
+ (void) execvp (run_argv[0], run_argv);
+ error (0, errno, "cannot exec %s", run_argv[0]);
+ _exit (127);
+ }
+ else if (pid == -1)
+ {
+ rerrno = errno;
+ goto out;
+ }
+
+ /* the parent. Ignore some signals for now */
+#ifdef POSIX_SIGNALS
+ if (flags & RUN_SIGIGNORE)
+ {
+ act.sa_handler = SIG_IGN;
+ (void) sigemptyset (&act.sa_mask);
+ act.sa_flags = 0;
+ (void) sigaction (SIGINT, &act, &iact);
+ (void) sigaction (SIGQUIT, &act, &qact);
+ }
+ else
+ {
+ (void) sigemptyset (&sigset_mask);
+ (void) sigaddset (&sigset_mask, SIGINT);
+ (void) sigaddset (&sigset_mask, SIGQUIT);
+ (void) sigprocmask (SIG_SETMASK, &sigset_mask, &sigset_omask);
+ }
+#else
+#ifdef BSD_SIGNALS
+ if (flags & RUN_SIGIGNORE)
+ {
+ memset ((char *) &vec, 0, sizeof (vec));
+ vec.sv_handler = SIG_IGN;
+ (void) sigvec (SIGINT, &vec, &ivec);
+ (void) sigvec (SIGQUIT, &vec, &qvec);
+ }
+ else
+ mask = sigblock (sigmask (SIGINT) | sigmask (SIGQUIT));
+#else
+ istat = signal (SIGINT, SIG_IGN);
+ qstat = signal (SIGQUIT, SIG_IGN);
+#endif
+#endif
+
+ /* wait for our process to die and munge return status */
+#ifdef POSIX_SIGNALS
+ while ((w = waitpid (pid, &status, 0)) == -1 && errno == EINTR)
+ ;
+#else
+ while ((w = wait (&status)) != pid)
+ {
+ if (w == -1 && errno != EINTR)
+ break;
+ }
+#endif
+
+ if (w == -1)
+ {
+ rc = -1;
+ rerrno = errno;
+ }
+#ifndef VMS /* status is return status */
+ else if (WIFEXITED (status))
+ rc = WEXITSTATUS (status);
+ else if (WIFSIGNALED (status))
+ {
+ if (WTERMSIG (status) == SIGPIPE)
+ error (1, 0, "broken pipe");
+ rc = 2;
+ }
+ else
+ rc = 1;
+#else /* VMS */
+ rc = WEXITSTATUS (status);
+#endif /* VMS */
+
+ /* restore the signals */
+#ifdef POSIX_SIGNALS
+ if (flags & RUN_SIGIGNORE)
+ {
+ (void) sigaction (SIGINT, &iact, (struct sigaction *) NULL);
+ (void) sigaction (SIGQUIT, &qact, (struct sigaction *) NULL);
+ }
+ else
+ (void) sigprocmask (SIG_SETMASK, &sigset_omask, (sigset_t *) NULL);
+#else
+#ifdef BSD_SIGNALS
+ if (flags & RUN_SIGIGNORE)
+ {
+ (void) sigvec (SIGINT, &ivec, (struct sigvec *) NULL);
+ (void) sigvec (SIGQUIT, &qvec, (struct sigvec *) NULL);
+ }
+ else
+ (void) sigsetmask (mask);
+#else
+ (void) signal (SIGINT, istat);
+ (void) signal (SIGQUIT, qstat);
+#endif
+#endif
+
+ /* cleanup the open file descriptors */
+ out:
+ if (sterr)
+ (void) close (sherr);
+ out2:
+ if (stout)
+ (void) close (shout);
+ out1:
+ if (stin)
+ (void) close (shin);
+
+ out0:
+ if (rerrno)
+ errno = rerrno;
+ return (rc);
+}
+
+void
+run_print (fp)
+ FILE *fp;
+{
+ int i;
+
+ for (i = 0; i < run_argc; i++)
+ {
+ (void) fprintf (fp, "'%s'", run_argv[i]);
+ if (i != run_argc - 1)
+ (void) fprintf (fp, " ");
+ }
+}
+
+FILE *
+run_popen (cmd, mode)
+ const char *cmd;
+ const char *mode;
+{
+ if (trace)
+#ifdef SERVER_SUPPORT
+ (void) fprintf (stderr, "%c-> run_popen(%s,%s)\n",
+ (server_active) ? 'S' : ' ', cmd, mode);
+#else
+ (void) fprintf (stderr, "-> run_popen(%s,%s)\n", cmd, mode);
+#endif
+ if (noexec)
+ return (NULL);
+
+ return (popen (cmd, mode));
+}
+
+extern int evecvp PROTO((char *file, char **argv));
+
+int
+piped_child (command, tofdp, fromfdp)
+ char **command;
+ int *tofdp;
+ int *fromfdp;
+{
+ int pid;
+ int to_child_pipe[2];
+ int from_child_pipe[2];
+
+ if (pipe (to_child_pipe) < 0)
+ error (1, errno, "cannot create pipe");
+ if (pipe (from_child_pipe) < 0)
+ error (1, errno, "cannot create pipe");
+
+ pid = fork ();
+ if (pid < 0)
+ error (1, errno, "cannot fork");
+ if (pid == 0)
+ {
+ if (dup2 (to_child_pipe[0], STDIN_FILENO) < 0)
+ error (1, errno, "cannot dup2");
+ if (close (to_child_pipe[1]) < 0)
+ error (1, errno, "cannot close");
+ if (close (from_child_pipe[0]) < 0)
+ error (1, errno, "cannot close");
+ if (dup2 (from_child_pipe[1], STDOUT_FILENO) < 0)
+ error (1, errno, "cannot dup2");
+
+ execvp (command[0], command);
+ error (1, errno, "cannot exec");
+ }
+ if (close (to_child_pipe[0]) < 0)
+ error (1, errno, "cannot close");
+ if (close (from_child_pipe[1]) < 0)
+ error (1, errno, "cannot close");
+
+ *tofdp = to_child_pipe[1];
+ *fromfdp = from_child_pipe[0];
+ return pid;
+}
+
+
+void
+close_on_exec (fd)
+ int fd;
+{
+#if defined (FD_CLOEXEC) && defined (F_SETFD)
+ if (fcntl (fd, F_SETFD, 1))
+ error (1, errno, "can't set close-on-exec flag on %d", fd);
+#endif
+}
+
+/*
+ * dir = 0 : main proc writes to new proc, which writes to oldfd
+ * dir = 1 : main proc reads from new proc, which reads from oldfd
+ *
+ * Returns: a file descriptor. On failure (i.e., the exec fails),
+ * then filter_stream_through_program() complains and dies.
+ */
+
+int
+filter_stream_through_program (oldfd, dir, prog, pidp)
+ int oldfd, dir;
+ char **prog;
+ pid_t *pidp;
+{
+ int p[2], newfd;
+ pid_t newpid;
+
+ if (pipe (p))
+ error (1, errno, "cannot create pipe");
+ newpid = fork ();
+ if (pidp)
+ *pidp = newpid;
+ switch (newpid)
+ {
+ case -1:
+ error (1, errno, "cannot fork");
+ case 0:
+ /* child */
+ if (dir)
+ {
+ /* write to new pipe */
+ close (p[0]);
+ dup2 (oldfd, 0);
+ dup2 (p[1], 1);
+ }
+ else
+ {
+ /* read from new pipe */
+ close (p[1]);
+ dup2 (p[0], 0);
+ dup2 (oldfd, 1);
+ }
+ /* Should I be blocking some signals here? */
+ execvp (prog[0], prog);
+ error (1, errno, "couldn't exec %s", prog[0]);
+ default:
+ /* parent */
+ close (oldfd);
+ if (dir)
+ {
+ /* read from new pipe */
+ close (p[1]);
+ newfd = p[0];
+ }
+ else
+ {
+ /* write to new pipe */
+ close (p[0]);
+ newfd = p[1];
+ }
+ close_on_exec (newfd);
+ return newfd;
+ }
+}
diff --git a/contrib/cvs/src/sanity.sh b/contrib/cvs/src/sanity.sh
new file mode 100755
index 0000000..4f80f90
--- /dev/null
+++ b/contrib/cvs/src/sanity.sh
@@ -0,0 +1,2869 @@
+#! /bin/sh
+:
+# sanity.sh -- a growing testsuite for cvs.
+#
+# Copyright (C) 1992, 1993 Cygnus Support
+#
+# Original Author: K. Richard Pixley
+
+# usage: sanity.sh [-r] @var{cvs-to-test} @var{tests-to-run}
+# -r means to test remote instead of local cvs.
+# @var{tests-to-run} are the names of the tests to run; if omitted run all
+# tests.
+
+# See TODO list at end of file.
+
+# required to make this script work properly.
+unset CVSREAD
+
+TESTDIR=/tmp/cvs-sanity
+
+# "debugger"
+#set -x
+
+echo 'This test should produce no other output than this line, and a final "OK".'
+
+if test x"$1" = x"-r"; then
+ shift
+ remote=yes
+else
+ remote=no
+fi
+
+# The --keep option will eventually cause all the tests to leave around the
+# contents of the /tmp directory; right now only some implement it. Not
+# useful if you are running more than one test.
+# FIXME: need some real option parsing so this doesn't depend on the order
+# in which they are specified.
+if test x"$1" = x"--keep"; then
+ shift
+ keep=yes
+else
+ keep=no
+fi
+
+# Use full path for CVS executable, so that CVS_SERVER gets set properly
+# for remote.
+case $1 in
+/*)
+ testcvs=$1
+ ;;
+*)
+ testcvs=`pwd`/$1
+ ;;
+esac
+
+shift
+
+# Regexp to match what CVS will call itself in output that it prints.
+# FIXME: we don't properly quote this--if the name contains . we'll
+# just spuriously match a few things; if the name contains other regexp
+# special characters we are probably in big trouble.
+PROG=`basename ${testcvs}`
+
+# FIXME: try things (what things? checkins?) without -m.
+#
+# Some of these tests are written to expect -Q. But testing with
+# -Q is kind of bogus, it is not the way users actually use CVS (usually).
+# So new tests probably should invoke ${testcvs} directly, rather than ${CVS}.
+# and then they've obviously got to do something with the output....
+#
+CVS="${testcvs} -Q"
+
+LOGFILE=`pwd`/check.log
+
+# Save the previous log in case the person running the tests decides
+# they want to look at it. The extension ".plog" is chosen for consistency
+# with dejagnu.
+if test -f check.log; then
+ mv check.log check.plog
+fi
+
+# That we should have to do this is total bogosity, but GNU expr
+# version 1.9.4 uses the emacs definition of "$" instead of the unix
+# (e.g. SunOS 4.1.3 expr) one. Rumor has it this will be fixed in the
+# next release of GNU expr after 1.12 (but we still have to cater to the old
+# ones for some time because they are in many linux distributions).
+ENDANCHOR="$"
+if expr 'abc
+def' : 'abc$' >/dev/null; then
+ ENDANCHOR='\'\'
+fi
+
+# Work around another GNU expr (version 1.10) bug/incompatibility.
+# "." doesn't appear to match a newline (it does with SunOS 4.1.3 expr).
+# Note that the workaround is not a complete equivalent of .* because
+# the first parenthesized expression in the regexp must match something
+# in order for expr to return a successful exit status.
+# Rumor has it this will be fixed in the
+# next release of GNU expr after 1.12 (but we still have to cater to the old
+# ones for some time because they are in many linux distributions).
+DOTSTAR='.*'
+if expr 'abc
+def' : "a${DOTSTAR}f" >/dev/null; then
+ : good, it works
+else
+ DOTSTAR='\(.\|
+\)*'
+fi
+
+# Work around yet another GNU expr (version 1.10) bug/incompatibility.
+# "+" is a special character, yet for unix expr (e.g. SunOS 4.1.3)
+# it is not. I doubt that POSIX allows us to use \+ and assume it means
+# (non-special) +, so here is another workaround
+# Rumor has it this will be fixed in the
+# next release of GNU expr after 1.12 (but we still have to cater to the old
+# ones for some time because they are in many linux distributions).
+PLUS='+'
+if expr 'a +b' : "a ${PLUS}b" >/dev/null; then
+ : good, it works
+else
+ PLUS='\+'
+fi
+
+# Likewise, for ?
+QUESTION='?'
+if expr 'a?b' : "a${QUESTION}b" >/dev/null; then
+ : good, it works
+else
+ QUESTION='\?'
+fi
+
+# Cause NextStep 3.3 users to lose in a more graceful fashion.
+if expr 'abc
+def' : 'abc
+def' >/dev/null; then
+ : good, it works
+else
+ echo 'Running these tests requires an "expr" program that can handle'
+ echo 'multi-line patterns. Make sure that such an expr (GNU, or many but'
+ echo 'not all vendor-supplied versions) is in your path.'
+ exit 1
+fi
+
+# Warn SunOS, SysVr3.2, etc., users that they may be partially losing
+if expr 'a
+b' : 'a
+c' >/dev/null; then
+ echo 'Warning: you are using a version of expr which does not correctly'
+ echo 'match multi-line patterns. Some tests may spuriously pass.'
+ echo 'You may wish to make sure GNU expr is in your path.'
+else
+ : good, it works
+fi
+
+pass ()
+{
+ echo "PASS: $1" >>${LOGFILE}
+}
+
+fail ()
+{
+ echo "FAIL: $1" | tee -a ${LOGFILE}
+ # This way the tester can go and see what remnants were left
+ exit 1
+}
+
+# See dotest and dotest_fail for explanation (this is the parts
+# of the implementation common to the two).
+dotest_internal ()
+{
+ # expr can't distinguish between "zero characters matched" and "no match",
+ # so special-case it.
+ if test -z "$3"; then
+ if test -s ${TESTDIR}/dotest.tmp; then
+ echo "** expected: " >>${LOGFILE}
+ echo "$3" >>${LOGFILE}
+ echo "** got: " >>${LOGFILE}
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ fail "$1"
+ else
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ pass "$1"
+ fi
+ else
+ if expr "`cat ${TESTDIR}/dotest.tmp`" : \
+ "$3"${ENDANCHOR} >/dev/null; then
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ pass "$1"
+ else
+ if test x"$4" != x; then
+ if expr "`cat ${TESTDIR}/dotest.tmp`" : \
+ "$4"${ENDANCHOR} >/dev/null; then
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ pass "$1"
+ else
+ echo "** expected: " >>${LOGFILE}
+ echo "$3" >>${LOGFILE}
+ echo "** or: " >>${LOGFILE}
+ echo "$4" >>${LOGFILE}
+ echo "** got: " >>${LOGFILE}
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ fail "$1"
+ fi
+ else
+ echo "** expected: " >>${LOGFILE}
+ echo "$3" >>${LOGFILE}
+ echo "** got: " >>${LOGFILE}
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ fail "$1"
+ fi
+ fi
+ fi
+}
+
+# Usage:
+# dotest TESTNAME COMMAND OUTPUT [OUTPUT2]
+# TESTNAME is the name used in the log to identify the test.
+# COMMAND is the command to run; for the test to pass, it exits with
+# exitstatus zero.
+# OUTPUT is a regexp which is compared against the output (stdout and
+# stderr combined) from the test. It is anchored to the start and end
+# of the output, so should start or end with ".*" if that is what is desired.
+# Trailing newlines are stripped from the command's actual output before
+# matching against OUTPUT.
+# If OUTPUT2 is specified and the output matches it, then it is also
+# a pass (partial workaround for the fact that some versions of expr
+# lack \|).
+dotest ()
+{
+ if $2 >${TESTDIR}/dotest.tmp 2>&1; then
+ : so far so good
+ else
+ status=$?
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ echo "exit status was $status" >>${LOGFILE}
+ fail "$1"
+ fi
+ dotest_internal "$@"
+}
+
+# Like dotest except exitstatus should be nonzero.
+dotest_fail ()
+{
+ if $2 >${TESTDIR}/dotest.tmp 2>&1; then
+ status=$?
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ echo "exit status was $status" >>${LOGFILE}
+ fail "$1"
+ else
+ : so far so good
+ fi
+ dotest_internal "$@"
+}
+
+# Like dotest except second argument is the required exitstatus.
+dotest_status ()
+{
+ $3 >${TESTDIR}/dotest.tmp 2>&1
+ status=$?
+ if test "$status" = "$2"; then
+ : so far so good
+ else
+ cat ${TESTDIR}/dotest.tmp >>${LOGFILE}
+ echo "exit status was $status; expected $2" >>${LOGFILE}
+ fail "$1"
+ fi
+ dotest_internal "$1" "$3" "$4" "$5"
+}
+
+# clean any old remnants
+rm -rf ${TESTDIR}
+mkdir ${TESTDIR}
+cd ${TESTDIR}
+
+# Avoid picking up any stray .cvsrc, etc., from the user running the tests
+mkdir home
+HOME=${TESTDIR}/home; export HOME
+
+# Remaining arguments are the names of tests to run.
+#
+# The testsuite is broken up into (hopefully manageably-sized)
+# independently runnable tests, so that one can quickly get a result
+# from a cvs or testsuite change, and to facilitate understanding the
+# tests.
+
+if test x"$*" = x; then
+ tests="basica basic1 deep basic2 death branches import new conflicts modules mflag errmsg1 devcom ignore binfiles info"
+else
+ tests="$*"
+fi
+
+# this should die
+if ${CVS} -d `pwd`/cvsroot co cvs-sanity 2>> ${LOGFILE} ; then
+ echo "FAIL: test 1" | tee -a ${LOGFILE}
+ exit 1
+else
+ echo "PASS: test 1" >>${LOGFILE}
+fi
+
+# this should still die
+mkdir cvsroot
+if ${CVS} -d `pwd`/cvsroot co cvs-sanity 2>> ${LOGFILE} ; then
+ echo "FAIL: test 2" | tee -a ${LOGFILE}
+ exit 1
+else
+ echo "PASS: test 2" >>${LOGFILE}
+fi
+
+# this should still die
+mkdir cvsroot/CVSROOT
+if ${CVS} -d `pwd`/cvsroot co cvs-sanity 2>> ${LOGFILE} ; then
+ echo "FAIL: test 3" | tee -a ${LOGFILE}
+ exit 1
+else
+ echo "PASS: test 3" >>${LOGFILE}
+fi
+
+# This one should work, although it should spit a warning.
+mkdir tmp ; cd tmp
+${CVS} -d `pwd`/../cvsroot co CVSROOT 2>> ${LOGFILE}
+cd .. ; rm -rf tmp
+
+# set up a minimal modules file...
+# (now that mkmodules is gone, this doesn't test -i the way it
+# used to. In fact, it looks like a noop to me).
+echo "CVSROOT CVSROOT" > cvsroot/CVSROOT/modules
+# The following line stolen from cvsinit.sh. FIXME: create our
+# repository via cvsinit.sh; that way we test it too.
+(cd cvsroot/CVSROOT; ci -q -u -t/dev/null \
+ -m'initial checkin of modules' modules)
+
+# This one should succeed. No warnings.
+mkdir tmp ; cd tmp
+if ${CVS} -d `pwd`/../cvsroot co CVSROOT ; then
+ echo "PASS: test 4" >>${LOGFILE}
+else
+ echo "FAIL: test 4" | tee -a ${LOGFILE}
+ exit 1
+fi
+
+if echo "yes" | ${CVS} -d `pwd`/../cvsroot release -d CVSROOT ; then
+ echo "PASS: test 4.5" >>${LOGFILE}
+else
+ echo "FAIL: test 4.5" | tee -a ${LOGFILE}
+ exit 1
+fi
+# this had better be empty
+cd ..; rmdir tmp
+dotest_fail 4.75 "test -d tmp" ''
+
+# a simple function to compare directory contents
+#
+# BTW, I don't care any more -- if you don't have a /bin/sh that handles
+# shell functions, well get one.
+#
+# Returns: ISDIFF := true|false
+#
+directory_cmp ()
+{
+ OLDPWD=`pwd`
+ DIR_1=$1
+ DIR_2=$2
+ ISDIFF=false
+
+ cd $DIR_1
+ find . -print | fgrep -v /CVS | sort > /tmp/dc$$d1
+
+ # go back where we were to avoid symlink hell...
+ cd $OLDPWD
+ cd $DIR_2
+ find . -print | fgrep -v /CVS | sort > /tmp/dc$$d2
+
+ if diff /tmp/dc$$d1 /tmp/dc$$d2 >/dev/null 2>&1
+ then
+ :
+ else
+ ISDIFF=true
+ return
+ fi
+ cd $OLDPWD
+ while read a
+ do
+ if [ -f $DIR_1/"$a" ] ; then
+ cmp -s $DIR_1/"$a" $DIR_2/"$a"
+ if [ $? -ne 0 ] ; then
+ ISDIFF=true
+ fi
+ fi
+ done < /tmp/dc$$d1
+### FIXME:
+### rm -f /tmp/dc$$*
+}
+
+# so much for the setup. Let's try something harder.
+
+# Try setting CVSROOT so we don't have to worry about it anymore. (now that
+# we've tested -d cvsroot.)
+CVSROOT_DIRNAME=${TESTDIR}/cvsroot
+CVSROOT=${CVSROOT_DIRNAME} ; export CVSROOT
+if test "x$remote" = xyes; then
+ CVSROOT=`hostname`:${CVSROOT_DIRNAME} ; export CVSROOT
+ # Use rsh so we can test it without having to muck with inetd or anything
+ # like that. Also needed to get CVS_SERVER to work.
+ CVS_CLIENT_PORT=-1; export CVS_CLIENT_PORT
+ CVS_SERVER=${testcvs}; export CVS_SERVER
+fi
+
+# start keeping history
+touch ${CVSROOT_DIRNAME}/CVSROOT/history
+
+### The big loop
+for what in $tests; do
+ case $what in
+ basica)
+ # Similar in spirit to some of the basic1, and basic2
+ # tests, but hopefully a lot faster. Also tests operating on
+ # files two directories down *without* operating on the parent dirs.
+
+ # Using mkdir in the repository is used throughout these
+ # tests to create a top-level directory. I think instead it
+ # should be:
+ # cvs co -l .
+ # mkdir first-dir
+ # cvs add first-dir
+ # but currently that works only for local CVS, not remote.
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ dotest basica-1 "${testcvs} -q co first-dir" ''
+ cd first-dir
+
+ # Test a few operations, to ensure they gracefully do
+ # nothing in an empty directory.
+ dotest basica-1a0 "${testcvs} -q update" ''
+ dotest basica-1a1 "${testcvs} -q diff -c" ''
+ dotest basica-1a2 "${testcvs} -q status" ''
+
+ mkdir sdir
+ dotest basica-2 "${testcvs} add sdir" \
+'Directory /tmp/cvs-sanity/cvsroot/first-dir/sdir added to the repository'
+ cd sdir
+ mkdir ssdir
+ dotest basica-3 "${testcvs} add ssdir" \
+'Directory /tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir added to the repository'
+ cd ssdir
+ echo ssfile >ssfile
+
+ # Trying to commit it without a "cvs add" should be an error.
+ # The "use `cvs add' to create an entry" message is the one
+ # that I consider to be more correct, but local cvs prints the
+ # "nothing known" message and noone has gotten around to fixing it.
+ dotest_fail basica-notadded "${testcvs} -q ci ssfile" \
+"${PROG} [a-z]*: use "'`cvs add'\'' to create an entry for ssfile
+'"${PROG}"' \[[a-z]* aborted\]: correct above errors first!' \
+"${PROG}"' [a-z]*: nothing known about `ssfile'\''
+'"${PROG}"' \[[a-z]* aborted\]: correct above errors first!'
+
+ dotest basica-4 "${testcvs} add ssfile" \
+"${PROG}"' [a-z]*: scheduling file `ssfile'\'' for addition
+'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently'
+ dotest_fail basica-4a "${testcvs} tag tag0 ssfile" \
+"${PROG} [a-z]*: nothing known about ssfile
+${PROG} "'\[[a-z]* aborted\]: correct the above errors first!'
+ cd ../..
+ dotest basica-5 "${testcvs} -q ci -m add-it" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v
+done
+Checking in sdir/ssdir/ssfile;
+/tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v <-- ssfile
+initial revision: 1.1
+done'
+ dotest_fail basica-5a \
+ "${testcvs} -q tag BASE sdir/ssdir/ssfile" \
+"${PROG} [a-z]*: Attempt to add reserved tag name BASE
+${PROG} \[[a-z]* aborted\]: failed to set tag BASE to revision 1.1 in /tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v"
+ dotest basica-5b "${testcvs} -q tag NOT_RESERVED" \
+'T sdir/ssdir/ssfile'
+
+ dotest basica-6 "${testcvs} -q update" ''
+ echo "ssfile line 2" >>sdir/ssdir/ssfile
+ dotest_status basica-6.2 1 "${testcvs} -q diff -c" \
+'Index: sdir/ssdir/ssfile
+===================================================================
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v
+retrieving revision 1\.1
+diff -c -r1\.1 ssfile
+\*\*\* ssfile [0-9/]* [0-9:]* 1\.1
+--- ssfile [0-9/]* [0-9:]*
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+--- 1,2 ----
+ ssfile
+'"${PLUS} ssfile line 2"
+ dotest basica-7 "${testcvs} -q ci -m modify-it" \
+'Checking in sdir/ssdir/ssfile;
+/tmp/cvs-sanity/cvsroot/first-dir/sdir/ssdir/ssfile,v <-- ssfile
+new revision: 1.2; previous revision: 1.1
+done'
+ dotest_fail basica-nonexist "${testcvs} -q ci nonexist" \
+"${PROG}"' [a-z]*: nothing known about `nonexist'\''
+'"${PROG}"' \[[a-z]* aborted\]: correct above errors first!'
+ dotest basica-8 "${testcvs} -q update" ''
+ dotest_fail basica-9 \
+ "${testcvs} -q -d /tmp/cvs-sanity/nonexist update" \
+"${PROG}: .*/tmp/cvs-sanity/cvsroot value for CVS Root found in CVS/Root
+${PROG}"': does not match command line -d /tmp/cvs-sanity/nonexist setting
+'"${PROG}"': you may wish to try the cvs command again without the -d option '
+
+ dotest basica-10 "${testcvs} annotate" \
+'Annotations for sdir/ssdir/ssfile
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+1.1 .[a-z@][a-z@ ]* [0-9a-zA-Z-]*.: ssfile
+1.2 .[a-z@][a-z@ ]* [0-9a-zA-Z-]*.: ssfile line 2'
+ cd ..
+
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ rm -r first-dir
+ ;;
+
+ basic1) # first dive - add a files, first singly, then in a group.
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ # check out an empty directory
+ if ${CVS} co first-dir ; then
+ echo "PASS: test 13a" >>${LOGFILE}
+ else
+ echo "FAIL: test 13a" | tee -a ${LOGFILE}; exit 1
+ fi
+
+ cd first-dir
+ files=first-file
+ for i in a b ; do
+ for j in ${files} ; do
+ echo $j > $j
+ done
+
+ for do in add rm ; do
+ for j in ${do} "commit -m test" ; do
+ # ${do}
+ if ${CVS} $j ${files} >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 14-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 14-${do}-$j" | tee -a ${LOGFILE}; exit 1
+ fi
+
+ # update it.
+ if [ "${do}" = "rm" -a "$j" != "commit -m test" ] || ${CVS} update ${files} ; then
+ echo "PASS: test 15-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 15-${do}-$j" | tee -a ${LOGFILE}; exit 1
+ fi
+
+ # update all.
+ if ${CVS} update ; then
+ echo "PASS: test 16-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 16-${do}-$j" | tee -a ${LOGFILE}; exit 1
+ fi
+
+ # status all.
+ if ${CVS} status >> ${LOGFILE}; then
+ echo "PASS: test 17-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 17-${do}-$j" | tee -a ${LOGFILE}; exit 1
+ fi
+
+ # FIXME: this one doesn't work yet for added files.
+ # log all.
+ if ${CVS} log >> ${LOGFILE}; then
+ echo "PASS: test 18-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 18-${do}-$j" | tee -a ${LOGFILE}
+ fi
+
+ cd ..
+ # update all.
+ if ${CVS} update ; then
+ echo "PASS: test 21-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 21-${do}-$j" | tee -a ${LOGFILE}; exit 1
+ fi
+
+ # log all.
+ # FIXME: doesn't work right for added files.
+ if ${CVS} log first-dir >> ${LOGFILE}; then
+ echo "PASS: test 22-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 22-${do}-$j" | tee -a ${LOGFILE}
+ fi
+
+ # status all.
+ if ${CVS} status first-dir >> ${LOGFILE}; then
+ echo "PASS: test 23-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 23-${do}-$j" | tee -a ${LOGFILE}; exit 1
+ fi
+
+ # update all.
+ if ${CVS} update first-dir ; then
+ echo "PASS: test 24-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 24-${do}-$j" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # update all.
+ if ${CVS} co first-dir ; then
+ echo "PASS: test 27-${do}-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 27-${do}-$j" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd first-dir
+ done # j
+ rm -f ${files}
+ done # do
+
+ files="file2 file3 file4 file5"
+ done
+ if ${CVS} tag first-dive ; then
+ echo "PASS: test 28" >>${LOGFILE}
+ else
+ echo "FAIL: test 28" | tee -a ${LOGFILE} ; exit 1
+ fi
+ cd ..
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ rm -rf first-dir
+ ;;
+
+ deep)
+ # Test the ability to operate on directories nested rather deeply.
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ dotest deep-1 "${testcvs} -q co first-dir" ''
+ cd first-dir
+ for i in dir1 dir2 dir3 dir4 dir5 dir6 dir7 dir8; do
+ mkdir $i
+ dotest deep-2-$i "${testcvs} add $i" \
+'Directory /tmp/cvs-sanity/cvsroot/first-dir/dir1[/dir0-9]* added to the repository'
+ cd $i
+ echo file1 >file1
+ dotest deep-3-$i "${testcvs} add file1" \
+"${PROG}"' [a-z]*: scheduling file `file1'\'' for addition
+'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently'
+ done
+ cd ../../../../../../../../..
+ dotest deep-4 "${testcvs} -q ci -m add-them first-dir" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/file1,v
+done
+Checking in first-dir/dir1/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/file1,v
+done
+Checking in first-dir/dir1/dir2/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/file1,v
+done
+Checking in first-dir/dir1/dir2/dir3/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/file1,v
+done
+Checking in first-dir/dir1/dir2/dir3/dir4/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/file1,v
+done
+Checking in first-dir/dir1/dir2/dir3/dir4/dir5/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/file1,v
+done
+Checking in first-dir/dir1/dir2/dir3/dir4/dir5/dir6/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file1,v
+done
+Checking in first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file1,v
+done
+Checking in first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file1;
+/tmp/cvs-sanity/cvsroot/first-dir/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file1,v <-- file1
+initial revision: 1.1
+done'
+
+ if echo "yes" | ${testcvs} release -d first-dir >>${LOGFILE}; then
+ pass deep-5
+ else
+ fail deep-5
+ fi
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ ;;
+
+ basic2)
+ # Test rtag, import, history, various miscellaneous operations
+
+ # First empty the history file
+ rm ${CVSROOT_DIRNAME}/CVSROOT/history
+ touch ${CVSROOT_DIRNAME}/CVSROOT/history
+
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ dotest basic2-1 "${testcvs} -q co first-dir" ''
+ for i in first-dir dir1 dir2 ; do
+ if [ ! -d $i ] ; then
+ mkdir $i
+ if ${CVS} add $i >> ${LOGFILE}; then
+ echo "PASS: test 29-$i" >>${LOGFILE}
+ else
+ echo "FAIL: test 29-$i" | tee -a ${LOGFILE} ; exit 1
+ fi
+ fi
+
+ cd $i
+
+ for j in file6 file7; do
+ echo $j > $j
+ done
+
+ if ${CVS} add file6 file7 2>> ${LOGFILE}; then
+ echo "PASS: test 30-$i-$j" >>${LOGFILE}
+ else
+ echo "FAIL: test 30-$i-$j" | tee -a ${LOGFILE} ; exit 1
+ fi
+ done
+ cd ../../..
+ if ${CVS} update first-dir ; then
+ echo "PASS: test 31" >>${LOGFILE}
+ else
+ echo "FAIL: test 31" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # fixme: doesn't work right for added files.
+ if ${CVS} log first-dir >> ${LOGFILE}; then
+ echo "PASS: test 32" >>${LOGFILE}
+ else
+ echo "FAIL: test 32" | tee -a ${LOGFILE} # ; exit 1
+ fi
+
+ if ${CVS} status first-dir >> ${LOGFILE}; then
+ echo "PASS: test 33" >>${LOGFILE}
+ else
+ echo "FAIL: test 33" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+# if ${CVS} diff -u first-dir >> ${LOGFILE} || [ $? = 1 ] ; then
+# echo "PASS: test 34" >>${LOGFILE}
+# else
+# echo "FAIL: test 34" | tee -a ${LOGFILE} # ; exit 1
+# fi
+
+ if ${CVS} ci -m "second dive" first-dir >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 35" >>${LOGFILE}
+ else
+ echo "FAIL: test 35" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if ${CVS} tag second-dive first-dir ; then
+ echo "PASS: test 36" >>${LOGFILE}
+ else
+ echo "FAIL: test 36" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # third dive - in bunch o' directories, add bunch o' files,
+ # delete some, change some.
+
+ for i in first-dir dir1 dir2 ; do
+ cd $i
+
+ # modify a file
+ echo file6 >>file6
+
+ # delete a file
+ rm file7
+
+ if ${CVS} rm file7 2>> ${LOGFILE}; then
+ echo "PASS: test 37-$i" >>${LOGFILE}
+ else
+ echo "FAIL: test 37-$i" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # and add a new file
+ echo file14 >file14
+
+ if ${CVS} add file14 2>> ${LOGFILE}; then
+ echo "PASS: test 38-$i" >>${LOGFILE}
+ else
+ echo "FAIL: test 38-$i" | tee -a ${LOGFILE} ; exit 1
+ fi
+ done
+ cd ../../..
+ if ${CVS} update first-dir ; then
+ echo "PASS: test 39" >>${LOGFILE}
+ else
+ echo "FAIL: test 39" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # fixme: doesn't work right for added files
+ if ${CVS} log first-dir >> ${LOGFILE}; then
+ echo "PASS: test 40" >>${LOGFILE}
+ else
+ echo "FAIL: test 40" | tee -a ${LOGFILE} # ; exit 1
+ fi
+
+ if ${CVS} status first-dir >> ${LOGFILE}; then
+ echo "PASS: test 41" >>${LOGFILE}
+ else
+ echo "FAIL: test 41" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+# if ${CVS} diff -u first-dir >> ${LOGFILE} || [ $? = 1 ] ; then
+# echo "PASS: test 42" >>${LOGFILE}
+# else
+# echo "FAIL: test 42" | tee -a ${LOGFILE} # ; exit 1
+# fi
+
+ if ${CVS} ci -m "third dive" first-dir >>${LOGFILE} 2>&1; then
+ echo "PASS: test 43" >>${LOGFILE}
+ else
+ echo "FAIL: test 43" | tee -a ${LOGFILE} ; exit 1
+ fi
+ dotest 43.5 "${testcvs} -q update first-dir" ''
+
+ if ${CVS} tag third-dive first-dir ; then
+ echo "PASS: test 44" >>${LOGFILE}
+ else
+ echo "FAIL: test 44" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if echo "yes" | ${CVS} release -d first-dir ; then
+ echo "PASS: test 45" >>${LOGFILE}
+ else
+ echo "FAIL: test 45" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # end of third dive
+ if [ -d first-dir ] ; then
+ echo "FAIL: test 45.5" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 45.5" >>${LOGFILE}
+ fi
+
+ # now try some rtags
+
+ # rtag HEADS
+ if ${CVS} rtag rtagged-by-head first-dir ; then
+ echo "PASS: test 46" >>${LOGFILE}
+ else
+ echo "FAIL: test 46" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # tag by tag
+ if ${CVS} rtag -r rtagged-by-head rtagged-by-tag first-dir ; then
+ echo "PASS: test 47" >>${LOGFILE}
+ else
+ echo "FAIL: test 47" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # tag by revision
+ if ${CVS} rtag -r1.1 rtagged-by-revision first-dir ; then
+ echo "PASS: test 48" >>${LOGFILE}
+ else
+ echo "FAIL: test 48" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # rdiff by revision
+ if ${CVS} rdiff -r1.1 -rrtagged-by-head first-dir >> ${LOGFILE} || [ $? = 1 ] ; then
+ echo "PASS: test 49" >>${LOGFILE}
+ else
+ echo "FAIL: test 49" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # now export by rtagged-by-head and rtagged-by-tag and compare.
+ rm -rf first-dir
+ if ${CVS} export -r rtagged-by-head first-dir ; then
+ echo "PASS: test 50" >>${LOGFILE}
+ else
+ echo "FAIL: test 50" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ mv first-dir 1dir
+ if ${CVS} export -r rtagged-by-tag first-dir ; then
+ echo "PASS: test 51" >>${LOGFILE}
+ else
+ echo "FAIL: test 51" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ directory_cmp 1dir first-dir
+
+ if $ISDIFF ; then
+ echo "FAIL: test 52" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 52" >>${LOGFILE}
+ fi
+ rm -rf 1dir first-dir
+
+ # checkout by revision vs export by rtagged-by-revision and compare.
+ if ${CVS} export -rrtagged-by-revision -d export-dir first-dir ; then
+ echo "PASS: test 53" >>${LOGFILE}
+ else
+ echo "FAIL: test 53" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if ${CVS} co -r1.1 first-dir ; then
+ echo "PASS: test 54" >>${LOGFILE}
+ else
+ echo "FAIL: test 54" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # directory copies are done in an oblique way in order to avoid a bug in sun's tmp filesystem.
+ mkdir first-dir.cpy ; (cd first-dir ; tar cf - * | (cd ../first-dir.cpy ; tar xf -))
+
+ directory_cmp first-dir export-dir
+
+ if $ISDIFF ; then
+ echo "FAIL: test 55" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 55" >>${LOGFILE}
+ fi
+
+ # interrupt, while we've got a clean 1.1 here, let's import it into another tree.
+ cd export-dir
+ dotest 56 "${testcvs} import -m first-import second-dir first-immigration immigration1 immigration1_0" \
+'N second-dir/file14
+N second-dir/file6
+N second-dir/file7
+'"${PROG}"' [a-z]*: Importing /tmp/cvs-sanity/cvsroot/second-dir/dir1
+N second-dir/dir1/file14
+N second-dir/dir1/file6
+N second-dir/dir1/file7
+'"${PROG}"' [a-z]*: Importing /tmp/cvs-sanity/cvsroot/second-dir/dir1/dir2
+N second-dir/dir1/dir2/file14
+N second-dir/dir1/dir2/file6
+N second-dir/dir1/dir2/file7
+
+No conflicts created by this import'
+
+ cd ..
+
+ if ${CVS} export -r HEAD second-dir ; then
+ echo "PASS: test 57" >>${LOGFILE}
+ else
+ echo "FAIL: test 57" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ directory_cmp first-dir second-dir
+
+ if $ISDIFF ; then
+ echo "FAIL: test 58" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 58" >>${LOGFILE}
+ fi
+
+ rm -rf second-dir
+ rm -rf export-dir first-dir
+ mkdir first-dir
+ (cd first-dir.cpy ; tar cf - * | (cd ../first-dir ; tar xf -))
+
+ # update the top, cancelling sticky tags, retag, update other copy, compare.
+ cd first-dir
+ if ${CVS} update -A -l *file* 2>> ${LOGFILE}; then
+ echo "PASS: test 59" >>${LOGFILE}
+ else
+ echo "FAIL: test 59" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # If we don't delete the tag first, cvs won't retag it.
+ # This would appear to be a feature.
+ if ${CVS} tag -l -d rtagged-by-revision ; then
+ echo "PASS: test 60a" >>${LOGFILE}
+ else
+ echo "FAIL: test 60a" | tee -a ${LOGFILE} ; exit 1
+ fi
+ if ${CVS} tag -l rtagged-by-revision ; then
+ echo "PASS: test 60b" >>${LOGFILE}
+ else
+ echo "FAIL: test 60b" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd ..
+ mv first-dir 1dir
+ mv first-dir.cpy first-dir
+ cd first-dir
+
+ dotest 61 "${testcvs} -q diff -u" ''
+
+ if ${CVS} update ; then
+ echo "PASS: test 62" >>${LOGFILE}
+ else
+ echo "FAIL: test 62" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd ..
+
+ #### FIXME: is this expected to work??? Need to investigate
+ #### and fix or remove the test.
+# directory_cmp 1dir first-dir
+#
+# if $ISDIFF ; then
+# echo "FAIL: test 63" | tee -a ${LOGFILE} # ; exit 1
+# else
+# echo "PASS: test 63" >>${LOGFILE}
+# fi
+ rm -rf 1dir first-dir
+
+ # Test the cvs history command.
+
+ # The reason that there are two patterns rather than using
+ # \(/tmp/cvs-sanity\|<remote>\) is that we are trying to
+ # make this portable. Perhaps at some point we should
+ # ditch that notion and require GNU expr (or dejagnu or....)
+ # since it seems to be so painful.
+
+ # why are there two lines at the end of the local output
+ # which don't exist in the remote output? would seem to be
+ # a CVS bug.
+ dotest basic2-64 "${testcvs} his -e -a" \
+'O [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir =first-dir= /tmp/cvs-sanity/\*
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir/dir1 == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir/dir1 == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir/dir1/dir2 == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir/dir1/dir2 == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir == /tmp/cvs-sanity
+M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir/dir1 == /tmp/cvs-sanity
+M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir/dir1 == /tmp/cvs-sanity
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir/dir1/dir2 == /tmp/cvs-sanity
+M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir/dir1/dir2 == /tmp/cvs-sanity
+F [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* =first-dir= /tmp/cvs-sanity/\*
+T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-head:A\]
+T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-tag:rtagged-by-head\]
+T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-revision:1.1\]
+O [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* \[1.1\] first-dir =first-dir= /tmp/cvs-sanity/\*
+U [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir == /tmp/cvs-sanity/first-dir
+U [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file7 first-dir == /tmp/cvs-sanity/first-dir' \
+'O [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir =first-dir= <remote>/\*
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir/dir1 == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir/dir1 == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file6 first-dir/dir1/dir2 == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file7 first-dir/dir1/dir2 == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir == <remote>
+M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir/dir1 == <remote>
+M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir/dir1 == <remote>
+A [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.1 file14 first-dir/dir1/dir2 == <remote>
+M [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* 1.2 file6 first-dir/dir1/dir2 == <remote>
+F [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* =first-dir= <remote>/\*
+T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-head:A\]
+T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-tag:rtagged-by-head\]
+T [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* first-dir \[rtagged-by-revision:1.1\]
+O [0-9/]* [0-9:]* '"${PLUS}"'0000 [a-z@][a-z@]* \[1.1\] first-dir =first-dir= <remote>/\*'
+
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ rm -rf ${CVSROOT_DIRNAME}/second-dir
+ ;;
+
+ death) # next dive. test death support.
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ if ${CVS} co first-dir ; then
+ echo "PASS: test 65" >>${LOGFILE}
+ else
+ echo "FAIL: test 65" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd first-dir
+
+ # Create a directory with only dead files, to make sure CVS
+ # doesn't get confused by it.
+ mkdir subdir
+ dotest 65a0 "${testcvs} add subdir" \
+'Directory /tmp/cvs-sanity/cvsroot/first-dir/subdir added to the repository'
+ cd subdir
+ echo file in subdir >sfile
+ dotest 65a1 "${testcvs} add sfile" \
+"${PROG}"' [a-z]*: scheduling file `sfile'\'' for addition
+'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently'
+ dotest 65a2 "${testcvs} -q ci -m add-it" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/subdir/sfile,v
+done
+Checking in sfile;
+/tmp/cvs-sanity/cvsroot/first-dir/subdir/sfile,v <-- sfile
+initial revision: 1.1
+done'
+ rm sfile
+ dotest 65a3 "${testcvs} rm sfile" \
+"${PROG}"' [a-z]*: scheduling `sfile'\'' for removal
+'"${PROG}"' [a-z]*: use '\'"${PROG}"' commit'\'' to remove this file permanently'
+ dotest 65a4 "${testcvs} -q ci -m remove-it" \
+'Removing sfile;
+/tmp/cvs-sanity/cvsroot/first-dir/subdir/sfile,v <-- sfile
+new revision: delete; previous revision: 1.1
+done'
+ cd ..
+ dotest 65a5 "${testcvs} -q update -P" ''
+ dotest_fail 65a6 "test -d subdir" ''
+
+ # add a file.
+ touch file1
+ if ${CVS} add file1 2>> ${LOGFILE}; then
+ echo "PASS: test 66" >>${LOGFILE}
+ else
+ echo "FAIL: test 66" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 67" >>${LOGFILE}
+ else
+ echo "FAIL: test 67" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # remove
+ rm file1
+ if ${CVS} rm file1 2>> ${LOGFILE}; then
+ echo "PASS: test 68" >>${LOGFILE}
+ else
+ echo "FAIL: test 68" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >>${LOGFILE} ; then
+ echo "PASS: test 69" >>${LOGFILE}
+ else
+ echo "FAIL: test 69" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ dotest_fail 69a0 "test -f file1" ''
+ # get the old contents of file1 back
+ if ${testcvs} update -p -r 1.1 file1 >file1 2>>${LOGFILE}; then
+ pass 69a1
+ else
+ fail 69a1
+ fi
+ dotest 69a2 "cat file1" ''
+
+ # create second file
+ touch file2
+ if ${CVS} add file1 file2 2>> ${LOGFILE}; then
+ echo "PASS: test 70" >>${LOGFILE}
+ else
+ echo "FAIL: test 70" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 71" >>${LOGFILE}
+ else
+ echo "FAIL: test 71" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # log
+ if ${CVS} log file1 >> ${LOGFILE}; then
+ echo "PASS: test 72" >>${LOGFILE}
+ else
+ echo "FAIL: test 72" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # file4 will be dead at the time of branching and stay dead.
+ echo file4 > file4
+ dotest death-file4-add "${testcvs} add file4" \
+"${PROG}"' [a-z]*: scheduling file `file4'\'' for addition
+'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently'
+ dotest death-file4-ciadd "${testcvs} -q ci -m add file4" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v
+done
+Checking in file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+initial revision: 1.1
+done'
+ rm file4
+ dotest death-file4-rm "${testcvs} remove file4" \
+"${PROG}"' [a-z]*: scheduling `file4'\'' for removal
+'"${PROG}"' [a-z]*: use '\'"${PROG}"' commit'\'' to remove this file permanently'
+ dotest death-file4-cirm "${testcvs} -q ci -m remove file4" \
+'Removing file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+new revision: delete; previous revision: 1.1
+done'
+
+ # branch1
+ if ${CVS} tag -b branch1 ; then
+ echo "PASS: test 73" >>${LOGFILE}
+ else
+ echo "FAIL: test 73" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # and move to the branch.
+ if ${CVS} update -r branch1 ; then
+ echo "PASS: test 74" >>${LOGFILE}
+ else
+ echo "FAIL: test 74" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ dotest_fail death-file4-3 "test -f file4" ''
+
+ # add a file in the branch
+ echo line1 from branch1 >> file3
+ if ${CVS} add file3 2>> ${LOGFILE}; then
+ echo "PASS: test 75" >>${LOGFILE}
+ else
+ echo "FAIL: test 75" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 76" >>${LOGFILE}
+ else
+ echo "FAIL: test 76" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # remove
+ rm file3
+ if ${CVS} rm file3 2>> ${LOGFILE}; then
+ echo "PASS: test 77" >>${LOGFILE}
+ else
+ echo "FAIL: test 77" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >>${LOGFILE} ; then
+ echo "PASS: test 78" >>${LOGFILE}
+ else
+ echo "FAIL: test 78" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # add again
+ echo line1 from branch1 >> file3
+ if ${CVS} add file3 2>> ${LOGFILE}; then
+ echo "PASS: test 79" >>${LOGFILE}
+ else
+ echo "FAIL: test 79" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 80" >>${LOGFILE}
+ else
+ echo "FAIL: test 80" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # change the first file
+ echo line2 from branch1 >> file1
+
+ # commit
+ if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 81" >>${LOGFILE}
+ else
+ echo "FAIL: test 81" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # remove the second
+ rm file2
+ if ${CVS} rm file2 2>> ${LOGFILE}; then
+ echo "PASS: test 82" >>${LOGFILE}
+ else
+ echo "FAIL: test 82" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >>${LOGFILE}; then
+ echo "PASS: test 83" >>${LOGFILE}
+ else
+ echo "FAIL: test 83" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # back to the trunk.
+ if ${CVS} update -A 2>> ${LOGFILE}; then
+ echo "PASS: test 84" >>${LOGFILE}
+ else
+ echo "FAIL: test 84" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ dotest_fail death-file4-4 "test -f file4" ''
+
+ if [ -f file3 ] ; then
+ echo "FAIL: test 85" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 85" >>${LOGFILE}
+ fi
+
+ # join
+ if ${CVS} update -j branch1 >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 86" >>${LOGFILE}
+ else
+ echo "FAIL: test 86" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ dotest_fail death-file4-5 "test -f file4" ''
+
+ if [ -f file3 ] ; then
+ echo "PASS: test 87" >>${LOGFILE}
+ else
+ echo "FAIL: test 87" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # Make sure that we joined the correct change to file1
+ if echo line2 from branch1 | cmp - file1 >/dev/null; then
+ echo 'PASS: test 87a' >>${LOGFILE}
+ else
+ echo 'FAIL: test 87a' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ # update
+ if ${CVS} update ; then
+ echo "PASS: test 88" >>${LOGFILE}
+ else
+ echo "FAIL: test 88" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >>${LOGFILE} 2>&1; then
+ echo "PASS: test 89" >>${LOGFILE}
+ else
+ echo "FAIL: test 89" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # remove first file.
+ rm file1
+ if ${CVS} rm file1 2>> ${LOGFILE}; then
+ echo "PASS: test 90" >>${LOGFILE}
+ else
+ echo "FAIL: test 90" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m test >>${LOGFILE}; then
+ echo "PASS: test 91" >>${LOGFILE}
+ else
+ echo "FAIL: test 91" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if [ -f file1 ] ; then
+ echo "FAIL: test 92" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 92" >>${LOGFILE}
+ fi
+
+ # typo; try to get to the branch and fail
+ dotest_fail 92.1a "${testcvs} update -r brnach1" \
+ "${PROG}"' \[[a-z]* aborted\]: no such tag brnach1'
+ # Make sure we are still on the trunk
+ if test -f file1 ; then
+ echo "FAIL: 92.1b" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: 92.1b" >>${LOGFILE}
+ fi
+ if test -f file2 ; then
+ echo "PASS: 92.1c" >>${LOGFILE}
+ else
+ echo "FAIL: 92.1c" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # back to branch1
+ if ${CVS} update -r branch1 2>> ${LOGFILE}; then
+ echo "PASS: test 93" >>${LOGFILE}
+ else
+ echo "FAIL: test 93" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ dotest_fail death-file4-6 "test -f file4" ''
+
+ if [ -f file1 ] ; then
+ echo "PASS: test 94" >>${LOGFILE}
+ else
+ echo "FAIL: test 94" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # and join
+ if ${CVS} update -j HEAD >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 95" >>${LOGFILE}
+ else
+ echo "FAIL: test 95" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ dotest_fail death-file4-7 "test -f file4" ''
+
+ cd .. ; rm -rf first-dir ${CVSROOT_DIRNAME}/first-dir
+ ;;
+ branches)
+ # More branch tests, including branches off of branches
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ dotest branches-1 "${testcvs} -q co first-dir" ''
+ cd first-dir
+ echo 1:ancest >file1
+ echo 2:ancest >file2
+ echo 3:ancest >file3
+ echo 4:trunk-1 >file4
+ dotest branches-2 "${testcvs} add file1 file2 file3 file4" \
+"${PROG}"' [a-z]*: scheduling file `file1'\'' for addition
+'"${PROG}"' [a-z]*: scheduling file `file2'\'' for addition
+'"${PROG}"' [a-z]*: scheduling file `file3'\'' for addition
+'"${PROG}"' [a-z]*: scheduling file `file4'\'' for addition
+'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add these files permanently'
+ dotest branches-3 "${testcvs} -q ci -m add-it" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file1,v
+done
+Checking in file1;
+/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file2,v
+done
+Checking in file2;
+/tmp/cvs-sanity/cvsroot/first-dir/file2,v <-- file2
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file3,v
+done
+Checking in file3;
+/tmp/cvs-sanity/cvsroot/first-dir/file3,v <-- file3
+initial revision: 1.1
+done
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v
+done
+Checking in file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+initial revision: 1.1
+done'
+ echo 4:trunk-2 >file4
+ dotest branches-3.2 "${testcvs} -q ci -m trunk-before-branch" \
+'Checking in file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+new revision: 1.2; previous revision: 1.1
+done'
+ dotest branches-4 "${testcvs} tag -b br1" "${PROG}"' [a-z]*: Tagging \.
+T file1
+T file2
+T file3
+T file4'
+ dotest branches-5 "${testcvs} update -r br1" \
+"${PROG}"' [a-z]*: Updating \.'
+ echo 1:br1 >file1
+ echo 2:br1 >file2
+ echo 4:br1 >file4
+ dotest branches-6 "${testcvs} -q ci -m modify" \
+'Checking in file1;
+/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1
+new revision: 1.1.2.1; previous revision: 1.1
+done
+Checking in file2;
+/tmp/cvs-sanity/cvsroot/first-dir/file2,v <-- file2
+new revision: 1.1.2.1; previous revision: 1.1
+done
+Checking in file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+new revision: 1.2.2.1; previous revision: 1.2
+done'
+ dotest branches-7 "${testcvs} -q tag -b brbr" 'T file1
+T file2
+T file3
+T file4'
+ dotest branches-8 "${testcvs} -q update -r brbr" ''
+ echo 1:brbr >file1
+ echo 4:brbr >file4
+ dotest branches-9 "${testcvs} -q ci -m modify" \
+'Checking in file1;
+/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1
+new revision: 1.1.2.1.2.1; previous revision: 1.1.2.1
+done
+Checking in file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+new revision: 1.2.2.1.2.1; previous revision: 1.2.2.1
+done'
+ dotest branches-10 "cat file1 file2 file3 file4" '1:brbr
+2:br1
+3:ancest
+4:brbr'
+ dotest branches-11 "${testcvs} -q update -r br1" \
+'[UP] file1
+[UP] file4'
+ dotest branches-12 "cat file1 file2 file3 file4" '1:br1
+2:br1
+3:ancest
+4:br1'
+ echo 4:br1-2 >file4
+ dotest branches-12.2 "${testcvs} -q ci -m change-on-br1" \
+'Checking in file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+new revision: 1.2.2.2; previous revision: 1.2.2.1
+done'
+ dotest branches-13 "${testcvs} -q update -A" '[UP] file1
+[UP] file2
+[UP] file4'
+ dotest branches-14 "cat file1 file2 file3 file4" '1:ancest
+2:ancest
+3:ancest
+4:trunk-2'
+ echo 4:trunk-3 >file4
+ dotest branches-14.2 \
+ "${testcvs} -q ci -m trunk-change-after-branch" \
+'Checking in file4;
+/tmp/cvs-sanity/cvsroot/first-dir/file4,v <-- file4
+new revision: 1.3; previous revision: 1.2
+done'
+ dotest branches-14.3 "${testcvs} log file4" \
+'
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v
+Working file: file4
+head: 1\.3
+branch:
+locks: strict
+access list:
+symbolic names:
+ brbr: 1\.2\.2\.1\.0\.2
+ br1: 1\.2\.0\.2
+keyword substitution: kv
+total revisions: 6; selected revisions: 6
+description:
+----------------------------
+revision 1\.3
+date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1
+trunk-change-after-branch
+----------------------------
+revision 1\.2
+date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1
+branches: 1\.2\.2;
+trunk-before-branch
+----------------------------
+revision 1\.1
+date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp;
+add-it
+----------------------------
+revision 1\.2\.2\.2
+date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1
+change-on-br1
+----------------------------
+revision 1\.2\.2\.1
+date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1
+branches: 1\.2\.2\.1\.2;
+modify
+----------------------------
+revision 1\.2\.2\.1\.2\.1
+date: [0-9/: ]*; author: [a-z@][a-z@]*; state: Exp; lines: '"${PLUS}"'1 -1
+modify
+============================================================================='
+ dotest_status branches-14.4 1 \
+ "${testcvs} diff -c -r 1.1 -r 1.3 file4" \
+'Index: file4
+===================================================================
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v
+retrieving revision 1\.1
+retrieving revision 1\.3
+diff -c -r1\.1 -r1\.3
+\*\*\* file4 [0-9/]* [0-9:]* 1\.1
+--- file4 [0-9/]* [0-9:]* 1\.3
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+! 4:trunk-1
+--- 1 ----
+! 4:trunk-3'
+ dotest_status branches-14.5 1 \
+ "${testcvs} diff -c -r 1.1 -r 1.2.2.1 file4" \
+'Index: file4
+===================================================================
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file4,v
+retrieving revision 1\.1
+retrieving revision 1\.2\.2\.1
+diff -c -r1\.1 -r1\.2\.2\.1
+\*\*\* file4 [0-9/]* [0-9:]* 1\.1
+--- file4 [0-9/]* [0-9:]* 1\.2\.2\.1
+\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
+\*\*\* 1 \*\*\*\*
+! 4:trunk-1
+--- 1 ----
+! 4:br1'
+ dotest branches-15 \
+ "${testcvs} update -j 1.1.2.1 -j 1.1.2.1.2.1 file1" \
+ 'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file1,v
+retrieving revision 1.1.2.1
+retrieving revision 1.1.2.1.2.1
+Merging differences between 1.1.2.1 and 1.1.2.1.2.1 into file1
+rcsmerge: warning: conflicts during merge'
+ dotest branches-16 "cat file1" '<<<<<<< file1
+1:ancest
+=======
+1:brbr
+>>>>>>> 1.1.2.1.2.1'
+ cd ..
+
+ if test "$keep" = yes; then
+ echo Keeping /tmp/cvs-sanity and exiting due to --keep
+ exit 0
+ fi
+
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ rm -r first-dir
+ ;;
+
+ import) # test death after import
+ # import
+ mkdir import-dir ; cd import-dir
+
+ for i in 1 2 3 4 ; do
+ echo imported file"$i" > imported-file"$i"
+ done
+
+ # This directory should be on the default ignore list,
+ # so it shouldn't get imported.
+ mkdir RCS
+ echo ignore.me >RCS/ignore.me
+
+ echo 'import should not expand $''Id$' >>imported-file2
+ cp imported-file2 ../imported-file2-orig.tmp
+
+ if ${CVS} import -m first-import first-dir vendor-branch junk-1_0 ; then
+ echo "PASS: test 96" >>${LOGFILE}
+ else
+ echo "FAIL: test 96" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if cmp ../imported-file2-orig.tmp imported-file2; then
+ pass 96.5
+ else
+ fail 96.5
+ fi
+ cd ..
+
+ # co
+ if ${CVS} co first-dir ; then
+ echo "PASS: test 97" >>${LOGFILE}
+ else
+ echo "FAIL: test 97" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd first-dir
+ for i in 1 2 3 4 ; do
+ if [ -f imported-file"$i" ] ; then
+ echo "PASS: test 98-$i" >>${LOGFILE}
+ else
+ echo "FAIL: test 98-$i" | tee -a ${LOGFILE} ; exit 1
+ fi
+ done
+ if test -d RCS; then
+ echo "FAIL: test 98.5" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 98.5" >>${LOGFILE}
+ fi
+
+ # remove
+ rm imported-file1
+ if ${CVS} rm imported-file1 2>> ${LOGFILE}; then
+ echo "PASS: test 99" >>${LOGFILE}
+ else
+ echo "FAIL: test 99" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # change
+ # this sleep is significant. Otherwise, on some machines, things happen so
+ # fast that the file mod times do not differ.
+ sleep 1
+ echo local-change >> imported-file2
+
+ # commit
+ if ${CVS} ci -m local-changes >> ${LOGFILE} 2>&1; then
+ echo "PASS: test 100" >>${LOGFILE}
+ else
+ echo "FAIL: test 100" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # log
+ if ${CVS} log imported-file1 | grep '1.1.1.2 (dead)' ; then
+ echo "FAIL: test 101" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 101" >>${LOGFILE}
+ fi
+
+ # update into the vendor branch.
+ if ${CVS} update -rvendor-branch ; then
+ echo "PASS: test 102" >>${LOGFILE}
+ else
+ echo "FAIL: test 102" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # remove file4 on the vendor branch
+ rm imported-file4
+
+ if ${CVS} rm imported-file4 2>> ${LOGFILE}; then
+ echo "PASS: test 103" >>${LOGFILE}
+ else
+ echo "FAIL: test 103" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # commit
+ if ${CVS} ci -m vendor-removed imported-file4 >>${LOGFILE}; then
+ echo "PASS: test 104" >>${LOGFILE}
+ else
+ echo "FAIL: test 104" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # update to main line
+ if ${CVS} update -A 2>> ${LOGFILE}; then
+ echo "PASS: test 105" >>${LOGFILE}
+ else
+ echo "FAIL: test 105" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # second import - file4 deliberately unchanged
+ cd ../import-dir
+ for i in 1 2 3 ; do
+ echo rev 2 of file $i >> imported-file"$i"
+ done
+ cp imported-file2 ../imported-file2-orig.tmp
+
+ if ${CVS} import -m second-import first-dir vendor-branch junk-2_0 ; then
+ echo "PASS: test 106" >>${LOGFILE}
+ else
+ echo "FAIL: test 106" | tee -a ${LOGFILE} ; exit 1
+ fi
+ if cmp ../imported-file2-orig.tmp imported-file2; then
+ pass 106.5
+ else
+ fail 106.5
+ fi
+ cd ..
+
+ # co
+ if ${CVS} co first-dir ; then
+ echo "PASS: test 107" >>${LOGFILE}
+ else
+ echo "FAIL: test 107" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd first-dir
+
+ if [ -f imported-file1 ] ; then
+ echo "FAIL: test 108" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 108" >>${LOGFILE}
+ fi
+
+ for i in 2 3 ; do
+ if [ -f imported-file"$i" ] ; then
+ echo "PASS: test 109-$i" >>${LOGFILE}
+ else
+ echo "FAIL: test 109-$i" | tee -a ${LOGFILE} ; exit 1
+ fi
+ done
+
+ # check vendor branch for file4
+ if ${CVS} update -rvendor-branch ; then
+ echo "PASS: test 110" >>${LOGFILE}
+ else
+ echo "FAIL: test 110" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if [ -f imported-file4 ] ; then
+ echo "PASS: test 111" >>${LOGFILE}
+ else
+ echo "FAIL: test 111" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ # update to main line
+ if ${CVS} update -A 2>> ${LOGFILE}; then
+ echo "PASS: test 112" >>${LOGFILE}
+ else
+ echo "FAIL: test 112" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd ..
+
+ if ${CVS} co -jjunk-1_0 -jjunk-2_0 first-dir >>${LOGFILE} 2>&1; then
+ echo "PASS: test 113" >>${LOGFILE}
+ else
+ echo "FAIL: test 113" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd first-dir
+
+ if [ -f imported-file1 ] ; then
+ echo "FAIL: test 114" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 114" >>${LOGFILE}
+ fi
+
+ for i in 2 3 ; do
+ if [ -f imported-file"$i" ] ; then
+ echo "PASS: test 115-$i" >>${LOGFILE}
+ else
+ echo "FAIL: test 115-$i" | tee -a ${LOGFILE} ; exit 1
+ fi
+ done
+
+ if cat imported-file2 | grep '====' >> ${LOGFILE}; then
+ echo "PASS: test 116" >>${LOGFILE}
+ else
+ echo "FAIL: test 116" | tee -a ${LOGFILE} ; exit 1
+ fi
+ cd .. ; rm -rf first-dir ${CVSROOT_DIRNAME}/first-dir
+ rm -rf import-dir
+ ;;
+
+ new) # look for stray "no longer pertinent" messages.
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+
+ if ${CVS} co first-dir ; then
+ echo "PASS: test 117" >>${LOGFILE}
+ else
+ echo "FAIL: test 117" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ cd first-dir
+ touch a
+
+ if ${CVS} add a 2>>${LOGFILE}; then
+ echo "PASS: test 118" >>${LOGFILE}
+ else
+ echo "FAIL: test 118" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if ${CVS} ci -m added >>${LOGFILE} 2>&1; then
+ echo "PASS: test 119" >>${LOGFILE}
+ else
+ echo "FAIL: test 119" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ rm a
+
+ if ${CVS} rm a 2>>${LOGFILE}; then
+ echo "PASS: test 120" >>${LOGFILE}
+ else
+ echo "FAIL: test 120" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if ${CVS} ci -m removed >>${LOGFILE} ; then
+ echo "PASS: test 121" >>${LOGFILE}
+ else
+ echo "FAIL: test 121" | tee -a ${LOGFILE} ; exit 1
+ fi
+
+ if ${CVS} update -A 2>&1 | grep longer ; then
+ echo "FAIL: test 122" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 122" >>${LOGFILE}
+ fi
+
+ if ${CVS} update -rHEAD 2>&1 | grep longer ; then
+ echo "FAIL: test 123" | tee -a ${LOGFILE} ; exit 1
+ else
+ echo "PASS: test 123" >>${LOGFILE}
+ fi
+
+ cd .. ; rm -rf first-dir ; rm -rf ${CVSROOT_DIRNAME}/first-dir
+ ;;
+
+ conflicts)
+ rm -rf first-dir ${CVSROOT_DIRNAME}/first-dir
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+
+ mkdir 1
+ cd 1
+
+ if ${CVS} co first-dir ; then
+ echo 'PASS: test 124' >>${LOGFILE}
+ else
+ echo 'FAIL: test 124' | tee -a ${LOGFILE}
+ fi
+
+ cd first-dir
+ touch a
+
+ if ${CVS} add a 2>>${LOGFILE} ; then
+ echo 'PASS: test 125' >>${LOGFILE}
+ else
+ echo 'FAIL: test 125' | tee -a ${LOGFILE}
+ fi
+
+ if ${CVS} ci -m added >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 126' >>${LOGFILE}
+ else
+ echo 'FAIL: test 126' | tee -a ${LOGFILE}
+ fi
+
+ cd ../..
+ mkdir 2
+ cd 2
+
+ if ${CVS} co first-dir ; then
+ echo 'PASS: test 127' >>${LOGFILE}
+ else
+ echo 'FAIL: test 127' | tee -a ${LOGFILE}
+ fi
+ cd first-dir
+ if test -f a; then
+ echo 'PASS: test 127a' >>${LOGFILE}
+ else
+ echo 'FAIL: test 127a' | tee -a ${LOGFILE}
+ fi
+
+ cd ../../1/first-dir
+ echo add a line >>a
+ mkdir dir1
+ dotest conflicts-127b "${testcvs} add dir1" \
+'Directory /tmp/cvs-sanity/cvsroot/first-dir/dir1 added to the repository'
+ dotest conflicts-128 "${testcvs} -q ci -m changed" \
+'Checking in a;
+/tmp/cvs-sanity/cvsroot/first-dir/a,v <-- a
+new revision: 1.2; previous revision: 1.1
+done'
+ cd ../../2/first-dir
+ echo add a conflicting line >>a
+ dotest_fail conflicts-129 "${testcvs} -q ci -m changed" \
+"${PROG}"' [a-z]*: Up-to-date check failed for `a'\''
+'"${PROG}"' \[[a-z]* aborted\]: correct above errors first!'
+ mkdir dir1
+ mkdir sdir
+ dotest conflicts-130 "${testcvs} -q update" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/a,v
+retrieving revision 1.1
+retrieving revision 1.2
+Merging differences between 1.1 and 1.2 into a
+rcsmerge: warning: conflicts during merge
+'"${PROG}"' [a-z]*: conflicts found in a
+C a
+'"${QUESTION}"' dir1
+'"${QUESTION}"' sdir' \
+''"${QUESTION}"' dir1
+'"${QUESTION}"' sdir
+RCS file: /tmp/cvs-sanity/cvsroot/first-dir/a,v
+retrieving revision 1.1
+retrieving revision 1.2
+Merging differences between 1.1 and 1.2 into a
+rcsmerge: warning: conflicts during merge
+'"${PROG}"' [a-z]*: conflicts found in a
+C a'
+
+ # Try to check in the file with the conflict markers in it.
+ if ${CVS} ci -m try 2>>${LOGFILE}; then
+ echo 'FAIL: test 131' | tee -a ${LOGFILE}
+ else
+ # Should tell us to resolve conflict first
+ echo 'PASS: test 131' >>${LOGFILE}
+ fi
+
+ echo lame attempt at resolving it >>a
+ # Try to check in the file with the conflict markers in it.
+ if ${CVS} ci -m try >>${LOGFILE} 2>&1; then
+ echo 'FAIL: test 132' | tee -a ${LOGFILE}
+ else
+ # Should tell us to resolve conflict first
+ echo 'PASS: test 132' >>${LOGFILE}
+ fi
+
+ echo resolve conflict >a
+ if ${CVS} ci -m resolved >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 133' >>${LOGFILE}
+ else
+ echo 'FAIL: test 133' | tee -a ${LOGFILE}
+ fi
+
+ # Now test that we can add a file in one working directory
+ # and have an update in another get it.
+ cd ../../1/first-dir
+ echo abc >abc
+ if ${testcvs} add abc >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 134' >>${LOGFILE}
+ else
+ echo 'FAIL: test 134' | tee -a ${LOGFILE}
+ fi
+ if ${testcvs} ci -m 'add abc' abc >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 135' >>${LOGFILE}
+ else
+ echo 'FAIL: test 135' | tee -a ${LOGFILE}
+ fi
+ cd ../../2
+ dotest conflicts-136 "${testcvs} -q update" \
+'[UP] first-dir/abc
+'"${QUESTION}"' first-dir/dir1
+'"${QUESTION}"' first-dir/sdir' \
+''"${QUESTION}"' first-dir/dir1
+'"${QUESTION}"' first-dir/sdir
+[UP] first-dir/abc'
+ dotest conflicts-137 'test -f first-dir/abc' ''
+ rmdir first-dir/dir1 first-dir/sdir
+
+ # Now test something similar, but in which the parent directory
+ # (not the directory in question) has the Entries.Static flag
+ # set.
+ cd ../1/first-dir
+ mkdir subdir
+ if ${testcvs} add subdir >>${LOGFILE}; then
+ echo 'PASS: test 138' >>${LOGFILE}
+ else
+ echo 'FAIL: test 138' | tee -a ${LOGFILE}
+ fi
+ cd ../..
+ mkdir 3
+ cd 3
+ if ${testcvs} -q co first-dir/abc first-dir/subdir \
+ >>${LOGFILE}; then
+ echo 'PASS: test 139' >>${LOGFILE}
+ else
+ echo 'FAIL: test 139' | tee -a ${LOGFILE}
+ fi
+ cd ../1/first-dir/subdir
+ echo sss >sss
+ if ${testcvs} add sss >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 140' >>${LOGFILE}
+ else
+ echo 'FAIL: test 140' | tee -a ${LOGFILE}
+ fi
+ if ${testcvs} ci -m adding sss >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 140' >>${LOGFILE}
+ else
+ echo 'FAIL: test 140' | tee -a ${LOGFILE}
+ fi
+ cd ../../../3/first-dir
+ if ${testcvs} -q update >>${LOGFILE}; then
+ echo 'PASS: test 141' >>${LOGFILE}
+ else
+ echo 'FAIL: test 141' | tee -a ${LOGFILE}
+ fi
+ if test -f subdir/sss; then
+ echo 'PASS: test 142' >>${LOGFILE}
+ else
+ echo 'FAIL: test 142' | tee -a ${LOGFILE}
+ fi
+
+ cd ../..
+ rm -rf 1 2 3 ; rm -rf ${CVSROOT_DIRNAME}/first-dir
+ ;;
+ modules)
+ rm -rf first-dir ${CVSROOT_DIRNAME}/first-dir
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+
+ mkdir 1
+ cd 1
+
+ if ${testcvs} -q co first-dir; then
+ echo 'PASS: test 143' >>${LOGFILE}
+ else
+ echo 'FAIL: test 143' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ cd first-dir
+ mkdir subdir
+ ${testcvs} add subdir >>${LOGFILE}
+ cd subdir
+
+ mkdir ssdir
+ ${testcvs} add ssdir >>${LOGFILE}
+
+ touch a b
+
+ if ${testcvs} add a b 2>>${LOGFILE} ; then
+ echo 'PASS: test 144' >>${LOGFILE}
+ else
+ echo 'FAIL: test 144' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 145' >>${LOGFILE}
+ else
+ echo 'FAIL: test 145' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ cd ..
+ if ${testcvs} -q co CVSROOT >>${LOGFILE}; then
+ echo 'PASS: test 146' >>${LOGFILE}
+ else
+ echo 'FAIL: test 146' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ # Here we test that CVS can deal with CVSROOT (whose repository
+ # is at top level) in the same directory as subdir (whose repository
+ # is a subdirectory of first-dir). TODO: Might want to check that
+ # files can actually get updated in this state.
+ if ${testcvs} -q update; then
+ echo 'PASS: test 147' >>${LOGFILE}
+ else
+ echo 'FAIL: test 147' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ echo realmodule first-dir/subdir a >>CVSROOT/modules
+ echo dirmodule first-dir/subdir >>CVSROOT/modules
+ echo namedmodule -d nameddir first-dir/subdir >>CVSROOT/modules
+ echo aliasmodule -a first-dir/subdir/a >>CVSROOT/modules
+ echo aliasnested -a first-dir/subdir/ssdir >>CVSROOT/modules
+
+ # Options must come before arguments. It is possible this should
+ # be relaxed at some point (though the result would be bizarre for
+ # -a); for now test the current behavior.
+ echo bogusalias first-dir/subdir/a -a >>CVSROOT/modules
+ if ${testcvs} ci -m 'add modules' CVSROOT/modules \
+ >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 148' >>${LOGFILE}
+ else
+ echo 'FAIL: test 148' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ cd ..
+ dotest 148a0 "${testcvs} co -c" 'CVSROOT CVSROOT
+aliasmodule -a first-dir/subdir/a
+aliasnested -a first-dir/subdir/ssdir
+bogusalias first-dir/subdir/a -a
+dirmodule first-dir/subdir
+namedmodule -d nameddir first-dir/subdir
+realmodule first-dir/subdir a'
+ # I don't know why aliasmodule isn't printed (I would have thought
+ # that it gets printed without the -a; although I'm not sure that
+ # printing expansions without options is useful).
+ dotest 148a1 "${testcvs} co -s" 'CVSROOT NONE CVSROOT
+bogusalias NONE first-dir/subdir/a -a
+dirmodule NONE first-dir/subdir
+namedmodule NONE first-dir/subdir
+realmodule NONE first-dir/subdir a'
+
+ # Test that real modules check out to realmodule/a, not subdir/a.
+ if ${testcvs} co realmodule >>${LOGFILE}; then
+ echo 'PASS: test 149a1' >>${LOGFILE}
+ else
+ echo 'FAIL: test 149a1' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if test -d realmodule && test -f realmodule/a; then
+ echo 'PASS: test 149a2' >>${LOGFILE}
+ else
+ echo 'FAIL: test 149a2' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if test -f realmodule/b; then
+ echo 'FAIL: test 149a3' | tee -a ${LOGFILE}
+ exit 1
+ else
+ echo 'PASS: test 149a3' >>${LOGFILE}
+ fi
+ if ${testcvs} -q co realmodule; then
+ echo 'PASS: test 149a4' >>${LOGFILE}
+ else
+ echo 'FAIL: test 149a4' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if echo "yes" | ${testcvs} release -d realmodule >>${LOGFILE} ; then
+ echo 'PASS: test 149a5' >>${LOGFILE}
+ else
+ echo 'FAIL: test 149a5' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ # Now test the ability to check out a single file from a directory
+ if ${testcvs} co dirmodule/a >>${LOGFILE}; then
+ echo 'PASS: test 150c' >>${LOGFILE}
+ else
+ echo 'FAIL: test 150c' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if test -d dirmodule && test -f dirmodule/a; then
+ echo 'PASS: test 150d' >>${LOGFILE}
+ else
+ echo 'FAIL: test 150d' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if test -f dirmodule/b; then
+ echo 'FAIL: test 150e' | tee -a ${LOGFILE}
+ exit 1
+ else
+ echo 'PASS: test 150e' >>${LOGFILE}
+ fi
+ if echo "yes" | ${testcvs} release -d dirmodule >>${LOGFILE} ; then
+ echo 'PASS: test 150f' >>${LOGFILE}
+ else
+ echo 'FAIL: test 150f' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ # Now test the ability to correctly reject a non-existent filename.
+ # For maximum studliness we would check that an error message is
+ # being output.
+ if ${testcvs} co dirmodule/nonexist >>${LOGFILE} 2>&1; then
+ # We accept a zero exit status because it is what CVS does
+ # (Dec 95). Probably the exit status should be nonzero,
+ # however.
+ echo 'PASS: test 150g1' >>${LOGFILE}
+ else
+ echo 'PASS: test 150g1' >>${LOGFILE}
+ fi
+ # We tolerate the creation of the dirmodule directory, since that
+ # is what CVS does, not because we view that as preferable to not
+ # creating it.
+ if test -f dirmodule/a || test -f dirmodule/b; then
+ echo 'FAIL: test 150g2' | tee -a ${LOGFILE}
+ exit 1
+ else
+ echo 'PASS: test 150g2' >>${LOGFILE}
+ fi
+ rm -rf dirmodule
+
+ # Now test that a module using -d checks out to the specified
+ # directory.
+ dotest 150h1 "${testcvs} -q co namedmodule" 'U nameddir/a
+U nameddir/b'
+ if test -f nameddir/a && test -f nameddir/b; then
+ pass 150h2
+ else
+ fail 150h2
+ fi
+ echo add line >>nameddir/a
+ dotest 150h3 "${testcvs} -q co namedmodule" 'M nameddir/a'
+ rm nameddir/a
+ dotest 150h4 "${testcvs} -q co namedmodule" 'U nameddir/a'
+ if echo "yes" | ${testcvs} release -d nameddir >>${LOGFILE} ; then
+ pass 150h99
+ else
+ fail 150h99
+ fi
+
+ # Now test that alias modules check out to subdir/a, not
+ # aliasmodule/a.
+ if ${testcvs} co aliasmodule >>${LOGFILE}; then
+ echo 'PASS: test 151' >>${LOGFILE}
+ else
+ echo 'FAIL: test 151' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if test -d aliasmodule; then
+ echo 'FAIL: test 152' | tee -a ${LOGFILE}
+ exit 1
+ else
+ echo 'PASS: test 152' >>${LOGFILE}
+ fi
+ echo abc >>first-dir/subdir/a
+ if (${testcvs} -q co aliasmodule | tee test153.tmp) \
+ >>${LOGFILE}; then
+ echo 'PASS: test 153' >>${LOGFILE}
+ else
+ echo 'FAIL: test 153' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ echo 'M first-dir/subdir/a' >ans153.tmp
+ if cmp test153.tmp ans153.tmp; then
+ echo 'PASS: test 154' >>${LOGFILE}
+ else
+ echo 'FAIL: test 154' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ cd ..
+ rm -rf 1
+
+ mkdir 2
+ cd 2
+ dotest modules-155a0 "${testcvs} co aliasnested" \
+"${PROG} [a-z]*: Updating first-dir/subdir/ssdir"
+ dotest modules-155a1 "test -d first-dir" ''
+ dotest modules-155a2 "test -d first-dir/subdir" ''
+ dotest modules-155a3 "test -d first-dir/subdir/ssdir" ''
+ # Test that nothing extraneous got created.
+ dotest modules-155a4 "ls -1" "first-dir"
+ cd ..
+ rm -rf 2
+
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ ;;
+ mflag)
+ for message in '' ' ' '
+ ' ' test' ; do
+ # Set up
+ mkdir a-dir; cd a-dir
+ # Test handling of -m during import
+ echo testa >>test
+ if ${testcvs} import -m "$message" a-dir A A1 >>${LOGFILE} 2>&1;then
+ echo 'PASS: test 156' >>${LOGFILE}
+ else
+ echo 'FAIL: test 156' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ # Must import twice since the first time uses inline code that
+ # avoids RCS call.
+ echo testb >>test
+ if ${testcvs} import -m "$message" a-dir A A2 >>${LOGFILE} 2>&1;then
+ echo 'PASS: test 157' >>${LOGFILE}
+ else
+ echo 'FAIL: test 157' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ # Test handling of -m during ci
+ cd ..; rm -rf a-dir;
+ if ${testcvs} co a-dir >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 158' >>${LOGFILE}
+ else
+ echo 'FAIL: test 158' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ cd a-dir
+ echo testc >>test
+ if ${testcvs} ci -m "$message" >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 159' >>${LOGFILE}
+ else
+ echo 'FAIL: test 159' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ # Test handling of -m during rm/ci
+ rm test;
+ if ${testcvs} rm test >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 160' >>${LOGFILE}
+ else
+ echo 'FAIL: test 160' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if ${testcvs} ci -m "$message" >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 161' >>${LOGFILE}
+ else
+ echo 'FAIL: test 161' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ # Clean up
+ cd ..; rm -rf a-dir ${CVSROOT_DIRNAME}/a-dir
+ done
+ ;;
+ errmsg1)
+ mkdir ${CVSROOT_DIRNAME}/1dir
+ mkdir 1
+ cd 1
+ if ${testcvs} -q co 1dir; then
+ echo 'PASS: test 162' >>${LOGFILE}
+ else
+ echo 'FAIL: test 162' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ cd 1dir
+ touch foo
+ if ${testcvs} add foo 2>>${LOGFILE}; then
+ echo 'PASS: test 163' >>${LOGFILE}
+ else
+ echo 'FAIL: test 163' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 164' >>${LOGFILE}
+ else
+ echo 'FAIL: test 164' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ cd ../..
+ mkdir 2
+ cd 2
+ if ${testcvs} -q co 1dir >>${LOGFILE}; then
+ echo 'PASS: test 165' >>${LOGFILE}
+ else
+ echo 'FAIL: test 165' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ chmod a-w 1dir
+ cd ../1/1dir
+ rm foo;
+ if ${testcvs} rm foo >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 166' >>${LOGFILE}
+ else
+ echo 'FAIL: test 166' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if ${testcvs} ci -m removed >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 167' >>${LOGFILE}
+ else
+ echo 'FAIL: test 167' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ cd ../../2/1dir
+ # FIXME: should be using dotest and PROG.
+ ${testcvs} -q update 2>../tst167.err
+ CVSBASE=`basename $testcvs` # Get basename of CVS executable.
+ cat <<EOF >../tst167.ans
+$CVSBASE server: warning: foo is not (any longer) pertinent
+$CVSBASE update: unable to remove ./foo: Permission denied
+EOF
+ if cmp ../tst167.ans ../tst167.err >/dev/null ||
+ ( echo "$CVSBASE [update aborted]: cannot rename file foo to CVS/,,foo: Permission denied" | cmp - ../tst167.err >/dev/null )
+ then
+ echo 'PASS: test 168' >>${LOGFILE}
+ else
+ echo 'FAIL: test 168' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ cd ..
+ chmod u+w 1dir
+ cd ..
+ rm -rf 1 2 ${CVSROOT_DIRNAME}/1dir
+ ;;
+
+ devcom)
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ mkdir 1
+ cd 1
+ if ${testcvs} -q co first-dir >>${LOGFILE} ; then
+ echo 'PASS: test 169' >>${LOGFILE}
+ else
+ echo 'FAIL: test 169' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ cd first-dir
+ echo abb >abb
+ if ${testcvs} add abb 2>>${LOGFILE}; then
+ echo 'PASS: test 170' >>${LOGFILE}
+ else
+ echo 'FAIL: test 170' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 171' >>${LOGFILE}
+ else
+ echo 'FAIL: test 171' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ dotest_fail 171a0 "${testcvs} watch" "Usage${DOTSTAR}"
+ if ${testcvs} watch on; then
+ echo 'PASS: test 172' >>${LOGFILE}
+ else
+ echo 'FAIL: test 172' | tee -a ${LOGFILE}
+ fi
+ echo abc >abc
+ if ${testcvs} add abc 2>>${LOGFILE}; then
+ echo 'PASS: test 173' >>${LOGFILE}
+ else
+ echo 'FAIL: test 173' | tee -a ${LOGFILE}
+ fi
+ if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 174' >>${LOGFILE}
+ else
+ echo 'FAIL: test 174' | tee -a ${LOGFILE}
+ fi
+
+ cd ../..
+ mkdir 2
+ cd 2
+
+ if ${testcvs} -q co first-dir >>${LOGFILE}; then
+ echo 'PASS: test 175' >>${LOGFILE}
+ else
+ echo 'FAIL: test 175' | tee -a ${LOGFILE}
+ fi
+ cd first-dir
+ if test -w abb; then
+ echo 'FAIL: test 176' | tee -a ${LOGFILE}
+ else
+ echo 'PASS: test 176' >>${LOGFILE}
+ fi
+ if test -w abc; then
+ echo 'FAIL: test 177' | tee -a ${LOGFILE}
+ else
+ echo 'PASS: test 177' >>${LOGFILE}
+ fi
+
+ if ${testcvs} editors >../ans178.tmp; then
+ echo 'PASS: test 178' >>${LOGFILE}
+ else
+ echo 'FAIL: test 178' | tee -a ${LOGFILE}
+ fi
+ cat ../ans178.tmp >>${LOGFILE}
+ if test -s ../ans178.tmp; then
+ echo 'FAIL: test 178a' | tee -a ${LOGFILE}
+ else
+ echo 'PASS: test 178a' >>${LOGFILE}
+ fi
+
+ if ${testcvs} edit abb; then
+ echo 'PASS: test 179' >>${LOGFILE}
+ else
+ echo 'FAIL: test 179' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ if ${testcvs} editors >../ans180.tmp; then
+ echo 'PASS: test 180' >>${LOGFILE}
+ else
+ echo 'FAIL: test 180' | tee -a ${LOGFILE}
+ exit 1
+ fi
+ cat ../ans180.tmp >>${LOGFILE}
+ if test -s ../ans180.tmp; then
+ echo 'PASS: test 181' >>${LOGFILE}
+ else
+ echo 'FAIL: test 181' | tee -a ${LOGFILE}
+ fi
+
+ echo aaaa >>abb
+ if ${testcvs} ci -m modify abb >>${LOGFILE} 2>&1; then
+ echo 'PASS: test 182' >>${LOGFILE}
+ else
+ echo 'FAIL: test 182' | tee -a ${LOGFILE}
+ fi
+ # Unedit of a file not being edited should be a noop.
+ dotest 182.5 "${testcvs} unedit abb" ''
+
+ if ${testcvs} editors >../ans183.tmp; then
+ echo 'PASS: test 183' >>${LOGFILE}
+ else
+ echo 'FAIL: test 183' | tee -a ${LOGFILE}
+ fi
+ cat ../ans183.tmp >>${LOGFILE}
+ if test -s ../ans183.tmp; then
+ echo 'FAIL: test 184' | tee -a ${LOGFILE}
+ else
+ echo 'PASS: test 184' >>${LOGFILE}
+ fi
+
+ if test -w abb; then
+ echo 'FAIL: test 185' | tee -a ${LOGFILE}
+ else
+ echo 'PASS: test 185' >>${LOGFILE}
+ fi
+
+ if ${testcvs} edit abc; then
+ echo 'PASS: test 186a1' >>${LOGFILE}
+ else
+ echo 'FAIL: test 186a1' | tee -a ${LOGFILE}
+ fi
+ # Unedit of an unmodified file.
+ if ${testcvs} unedit abc; then
+ echo 'PASS: test 186a2' >>${LOGFILE}
+ else
+ echo 'FAIL: test 186a2' | tee -a ${LOGFILE}
+ fi
+ if ${testcvs} edit abc; then
+ echo 'PASS: test 186a3' >>${LOGFILE}
+ else
+ echo 'FAIL: test 186a3' | tee -a ${LOGFILE}
+ fi
+ echo changedabc >abc
+ # Try to unedit a modified file; cvs should ask for confirmation
+ if (echo no | ${testcvs} unedit abc) >>${LOGFILE}; then
+ echo 'PASS: test 186a4' >>${LOGFILE}
+ else
+ echo 'FAIL: test 186a4' | tee -a ${LOGFILE}
+ fi
+ if echo changedabc | cmp - abc; then
+ echo 'PASS: test 186a5' >>${LOGFILE}
+ else
+ echo 'FAIL: test 186a5' | tee -a ${LOGFILE}
+ fi
+ # OK, now confirm the unedit
+ if (echo yes | ${testcvs} unedit abc) >>${LOGFILE}; then
+ echo 'PASS: test 186a6' >>${LOGFILE}
+ else
+ echo 'FAIL: test 186a6' | tee -a ${LOGFILE}
+ fi
+ if echo abc | cmp - abc; then
+ echo 'PASS: test 186a7' >>${LOGFILE}
+ else
+ echo 'FAIL: test 186a7' | tee -a ${LOGFILE}
+ fi
+
+ dotest devcom-a0 "${testcvs} watchers" ''
+ dotest devcom-a1 "${testcvs} watch add" ''
+ dotest devcom-a2 "${testcvs} watchers" \
+'abb [a-z0-9]* edit unedit commit
+abc [a-z0-9]* edit unedit commit'
+ dotest devcom-a3 "${testcvs} watch remove -a unedit abb" ''
+ dotest devcom-a4 "${testcvs} watchers abb" \
+'abb [a-z0-9]* edit commit'
+
+ cd ../..
+ rm -rf 1 2 ${CVSROOT_DIRNAME}/first-dir
+ ;;
+
+ ignore)
+ dotest 187a1 "${testcvs} -q co CVSROOT" 'U CVSROOT/modules'
+ cd CVSROOT
+ echo rootig.c >cvsignore
+ dotest 187a2 "${testcvs} add cvsignore" "${PROG}"' [a-z]*: scheduling file `cvsignore'"'"' for addition
+'"${PROG}"' [a-z]*: use '"'"'cvs commit'"'"' to add this file permanently'
+
+ # As of Jan 96, local CVS prints "Examining ." and remote doesn't.
+ # Accept either.
+ dotest 187a3 " ${testcvs} ci -m added" \
+"${DOTSTAR}"'CS file: /tmp/cvs-sanity/cvsroot/CVSROOT/cvsignore,v
+done
+Checking in cvsignore;
+/tmp/cvs-sanity/cvsroot/CVSROOT/cvsignore,v <-- cvsignore
+initial revision: 1.1
+done
+'"${PROG}"' [a-z]*: Rebuilding administrative file database'
+
+ cd ..
+ if echo "yes" | ${testcvs} release -d CVSROOT >>${LOGFILE} ; then
+ echo 'PASS: test 187a4' >>${LOGFILE}
+ else
+ echo 'FAIL: test 187a4' | tee -a ${LOGFILE}
+ exit 1
+ fi
+
+ # CVS looks at the home dir from getpwuid, not HOME (is that correct
+ # behavior?), so this is hard to test and we won't try.
+ # echo foobar.c >${HOME}/.cvsignore
+ CVSIGNORE=envig.c; export CVSIGNORE
+ mkdir dir-to-import
+ cd dir-to-import
+ touch foobar.c bar.c rootig.c defig.o envig.c optig.c
+ # We really should allow the files to be listed in any order.
+ # But we (kludgily) just list the orders which have been observed.
+ dotest 188a "${testcvs} import -m m -I optig.c first-dir tag1 tag2" \
+ 'N first-dir/foobar.c
+N first-dir/bar.c
+I first-dir/rootig.c
+I first-dir/defig.o
+I first-dir/envig.c
+I first-dir/optig.c
+
+No conflicts created by this import' 'I first-dir/defig.o
+I first-dir/envig.c
+I first-dir/optig.c
+N first-dir/foobar.c
+N first-dir/bar.c
+I first-dir/rootig.c
+
+No conflicts created by this import'
+ dotest 188b "${testcvs} import -m m -I ! second-dir tag3 tag4" \
+ 'N second-dir/foobar.c
+N second-dir/bar.c
+N second-dir/rootig.c
+N second-dir/defig.o
+N second-dir/envig.c
+N second-dir/optig.c
+
+No conflicts created by this import'
+ cd ..
+ rm -rf dir-to-import
+
+ dotest 189a "${testcvs} -q co second-dir" \
+'U second-dir/bar.c
+U second-dir/defig.o
+U second-dir/envig.c
+U second-dir/foobar.c
+U second-dir/optig.c
+U second-dir/rootig.c'
+ rm -rf second-dir
+ dotest 189b "${testcvs} -q co first-dir" 'U first-dir/bar.c
+U first-dir/foobar.c'
+ cd first-dir
+ touch rootig.c defig.o envig.c optig.c notig.c
+ dotest 189c "${testcvs} -q update -I optig.c" "${QUESTION} notig.c"
+ # The fact that CVS requires us to specify -I CVS here strikes me
+ # as a bug.
+ dotest 189d "${testcvs} -q update -I ! -I CVS" "${QUESTION} rootig.c
+${QUESTION} defig.o
+${QUESTION} envig.c
+${QUESTION} optig.c
+${QUESTION} notig.c"
+ cd ..
+ rm -rf first-dir
+
+ rm -rf ${CVSROOT_DIRNAME}/first-dir ${CVSROOT_DIRNAME}/second-dir
+ ;;
+
+ binfiles)
+ # Test cvs's ability to handle binary files.
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ mkdir 1; cd 1
+ dotest binfiles-1 "${testcvs} -q co first-dir" ''
+ awk 'BEGIN { printf "%c%c%c%c%c%c", 2, 10, 137, 0, 13, 10 }' \
+ </dev/null >binfile.dat
+ cat binfile.dat binfile.dat >binfile2.dat
+ cd first-dir
+ cp ../binfile.dat binfile
+ dotest binfiles-2 "${testcvs} add -kb binfile" \
+"${PROG}"' [a-z]*: scheduling file `binfile'\'' for addition
+'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently'
+ dotest binfiles-3 "${testcvs} -q ci -m add-it" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/binfile,v
+done
+Checking in binfile;
+/tmp/cvs-sanity/cvsroot/first-dir/binfile,v <-- binfile
+initial revision: 1.1
+done'
+ cd ../..
+ mkdir 2; cd 2
+ dotest binfiles-4 "${testcvs} -q co first-dir" 'U first-dir/binfile'
+ cd first-dir
+ dotest binfiles-5 "cmp ../../1/binfile.dat binfile" ''
+ # Testing that sticky options is -kb is the closest thing we have
+ # to testing that binary files work right on non-unix machines
+ # (until there is automated testing for such machines, of course).
+ dotest binfiles-5.5 "${testcvs} status binfile" \
+'===================================================================
+File: binfile Status: Up-to-date
+
+ Working revision: 1\.1.*
+ Repository revision: 1\.1 /tmp/cvs-sanity/cvsroot/first-dir/binfile,v
+ Sticky Tag: (none)
+ Sticky Date: (none)
+ Sticky Options: -kb'
+ cp ../../1/binfile2.dat binfile
+ dotest binfiles-6 "${testcvs} -q ci -m modify-it" \
+'Checking in binfile;
+/tmp/cvs-sanity/cvsroot/first-dir/binfile,v <-- binfile
+new revision: 1.2; previous revision: 1.1
+done'
+ cd ../../1/first-dir
+ dotest binfiles-7 "${testcvs} -q update" '[UP] binfile'
+ dotest binfiles-8 "cmp ../binfile2.dat binfile" ''
+
+ # The bugs which these test for are apparently not fixed for remote.
+ if test "$remote" = no; then
+ dotest binfiles-9 "${testcvs} -q update -A" ''
+ dotest binfiles-10 "${testcvs} -q update -kk" '[UP] binfile'
+ dotest binfiles-11 "${testcvs} -q update" ''
+ dotest binfiles-12 "${testcvs} -q update -A" '[UP] binfile'
+ dotest binfiles-13 "${testcvs} -q update -A" ''
+ fi
+
+ cd ../../2/first-dir
+ echo 'this file is $''RCSfile$' >binfile
+ dotest binfiles-14a "${testcvs} -q ci -m modify-it" \
+'Checking in binfile;
+/tmp/cvs-sanity/cvsroot/first-dir/binfile,v <-- binfile
+new revision: 1.3; previous revision: 1.2
+done'
+ dotest binfiles-14b "cat binfile" 'this file is $''RCSfile$'
+ # See binfiles-5.5 for discussion of -kb.
+ dotest binfiles-14c "${testcvs} status binfile" \
+'===================================================================
+File: binfile Status: Up-to-date
+
+ Working revision: 1\.3.*
+ Repository revision: 1\.3 /tmp/cvs-sanity/cvsroot/first-dir/binfile,v
+ Sticky Tag: (none)
+ Sticky Date: (none)
+ Sticky Options: -kb'
+ dotest binfiles-14d "${testcvs} admin -kv binfile" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/binfile,v
+done'
+ # cvs admin doesn't change the checked-out file or its sticky
+ # kopts. There probably should be a way which does (but
+ # what if the file is modified? And do we try to version
+ # control the kopt setting?)
+ dotest binfiles-14e "cat binfile" 'this file is $''RCSfile$'
+ dotest binfiles-14f "${testcvs} status binfile" \
+'===================================================================
+File: binfile Status: Up-to-date
+
+ Working revision: 1\.3.*
+ Repository revision: 1\.3 /tmp/cvs-sanity/cvsroot/first-dir/binfile,v
+ Sticky Tag: (none)
+ Sticky Date: (none)
+ Sticky Options: -kb'
+ dotest binfiles-14g "${testcvs} -q update -A" '[UP] binfile'
+ dotest binfiles-14h "cat binfile" 'this file is binfile,v'
+ dotest binfiles-14i "${testcvs} status binfile" \
+'===================================================================
+File: binfile Status: Up-to-date
+
+ Working revision: 1\.3.*
+ Repository revision: 1\.3 /tmp/cvs-sanity/cvsroot/first-dir/binfile,v
+ Sticky Tag: (none)
+ Sticky Date: (none)
+ Sticky Options: -kv'
+
+ cd ../..
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ rm -r 1 2
+ ;;
+ info)
+ # Test CVS's ability to handle *info files.
+ dotest info-1 "${testcvs} -q co CVSROOT" "[UP] CVSROOT${DOTSTAR}"
+ cd CVSROOT
+ echo "ALL sh -c \"echo x\${=MYENV}\${=OTHER}y\${=ZEE}=\$USER=\$CVSROOT= >>$TESTDIR/testlog; cat >/dev/null\"" > loginfo
+ dotest info-2 "${testcvs} add loginfo" \
+"${PROG}"' [a-z]*: scheduling file `loginfo'"'"' for addition
+'"${PROG}"' [a-z]*: use '"'"'cvs commit'"'"' to add this file permanently'
+ dotest info-3 "${testcvs} -q ci -m new-loginfo" \
+'RCS file: /tmp/cvs-sanity/cvsroot/CVSROOT/loginfo,v
+done
+Checking in loginfo;
+/tmp/cvs-sanity/cvsroot/CVSROOT/loginfo,v <-- loginfo
+initial revision: 1.1
+done
+'"${PROG}"' [a-z]*: Rebuilding administrative file database'
+ cd ..
+ if echo "yes" | ${testcvs} release -d CVSROOT >>${LOGFILE} ; then
+ pass info-4
+ else
+ fail info-4
+ fi
+
+ mkdir ${CVSROOT_DIRNAME}/first-dir
+ dotest info-5 "${testcvs} -q co first-dir" ''
+ cd first-dir
+ touch file1
+ dotest info-6 "${testcvs} add file1" \
+"${PROG}"' [a-z]*: scheduling file `file1'\'' for addition
+'"${PROG}"' [a-z]*: use '\''cvs commit'\'' to add this file permanently'
+ echo "cvs -s OTHER=not-this -s MYENV=env-" >>$HOME/.cvsrc
+ dotest info-6a "${testcvs} -q -s OTHER=value ci -m add-it" \
+'RCS file: /tmp/cvs-sanity/cvsroot/first-dir/file1,v
+done
+Checking in file1;
+/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1
+initial revision: 1.1
+done
+'"${PROG}"' [a-z]*: loginfo:1: no such user variable ${=ZEE}'
+ echo line1 >>file1
+ dotest info-7 "${testcvs} -q -s OTHER=value -s ZEE=z ci -m mod-it" \
+'Checking in file1;
+/tmp/cvs-sanity/cvsroot/first-dir/file1,v <-- file1
+new revision: 1.2; previous revision: 1.1
+done'
+ cd ..
+ if echo "yes" | ${testcvs} release -d first-dir >>${LOGFILE} ; then
+ pass info-8
+ else
+ fail info-8
+ fi
+ dotest info-9 "cat $TESTDIR/testlog" 'xenv-valueyz=[a-z@][a-z@]*=/tmp/cvs-sanity/cvsroot='
+
+ # I think this might be doable with cvs remove, or at least
+ # checking in a version with only comments, but I'm too lazy
+ # at the moment. Blow it away.
+ rm -f ${CVSROOT_DIRNAME}/CVSROOT/loginfo*
+
+ rm -rf ${CVSROOT_DIRNAME}/first-dir
+ ;;
+ *)
+ echo $what is not the name of a test -- ignored
+ ;;
+ esac
+done
+
+echo "OK, all tests completed."
+
+# TODO:
+# * Test `cvs admin'.
+# * Test `cvs update -d foo' (where foo does not exist).
+# * Test `cvs update foo bar' (where foo and bar are both from the same
+# repository). Suppose one is a branch--make sure that both directories
+# get updated with the respective correct thing.
+# * `cvs update ../foo'. Also ../../foo ./../foo foo/../../bar /foo/bar
+# foo/.././../bar foo/../bar etc.
+# * Test all flags in modules file.
+# Test that ciprog gets run both on checkin in that directory, or a
+# higher-level checkin which recurses into it.
+# * Test that $ followed by "Header" followed by $ gets expanded on checkin.
+# * Test operations on a directory that contains other directories but has
+# no files of its own.
+# * -t global option
+# * cvs rm followed by cvs add or vice versa (with no checkin in between).
+# * cvs rm twice (should be a nice error message).
+# * -P option to checkout--(a) refrains from checking out new empty dirs,
+# (b) prunes empty dirs already there.
+# * Test that cvs -d `hostname`:/tmp/cvs-sanity/non/existent co foo
+# gives an appropriate error (e.g.
+# Cannot access /tmp/cvs-sanity/non-existent/CVSROOT
+# No such file or directory).
+# * Test ability to send notifications in response to watches. (currently
+# hard to test because CVS doesn't send notifications if username is the
+# same).
+# * Test that remote edit and/or unedit works when disconnected from
+# server (e.g. set CVS_SERVER to "foobar").
+# End of TODO list.
+
+# Remove the test directory, but first change out of it.
+cd /tmp
+rm -rf ${TESTDIR}
+
+# end of sanity.sh
diff --git a/contrib/cvs/src/scramble.c b/contrib/cvs/src/scramble.c
new file mode 100644
index 0000000..07094a6
--- /dev/null
+++ b/contrib/cvs/src/scramble.c
@@ -0,0 +1,246 @@
+/*
+ * Trivially encode strings to protect them from innocent eyes (i.e.,
+ * inadvertent password compromises, like a network administrator
+ * who's watching packets for legitimate reasons and accidentally sees
+ * the password protocol go by).
+ *
+ * This is NOT secure encryption.
+ *
+ * It would be tempting to encode the password according to username
+ * and repository, so that the same password would encode to a
+ * different string when used with different usernames and/or
+ * repositories. However, then users would not be able to cut and
+ * paste passwords around. They're not supposed to anyway, but we all
+ * know they will, and there's no reason to make it harder for them if
+ * we're not trying to provide real security anyway.
+ */
+
+/* Set this to test as a standalone program. */
+/* #define DIAGNOSTIC */
+
+#ifndef DIAGNOSTIC
+#include "cvs.h"
+#else /* ! DIAGNOSTIC */
+/* cvs.h won't define this for us */
+#define AUTH_CLIENT_SUPPORT
+#define xmalloc malloc
+/* Use "gcc -fwritable-strings". */
+#include <stdio.h>
+#include <stdio.h>
+#include <string.h>
+#endif /* ! DIAGNOSTIC */
+
+#if defined(AUTH_CLIENT_SUPPORT) || defined(AUTH_SERVER_SUPPORT)
+
+/* Map characters to each other randomly and symmetrically, A <--> B.
+ *
+ * We divide the ASCII character set into 3 domains: control chars (0
+ * thru 31), printing chars (32 through 126), and "meta"-chars (127
+ * through 255). The control chars map _to_ themselves, the printing
+ * chars map _among_ themselves, and the meta chars map _among_
+ * themselves. Why is this thus?
+ *
+ * No character in any of these domains maps to a character in another
+ * domain, because I'm not sure what characters are legal in
+ * passwords, or what tools people are likely to use to cut and paste
+ * them. It seems prudent not to introduce control or meta chars,
+ * unless the user introduced them first. And having the control
+ * chars all map to themselves insures that newline and
+ * carriage-return are safely handled.
+ */
+
+static unsigned char
+shifts[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 114, 120,
+53, 79, 96, 109, 72, 108, 70, 64, 76, 67, 116, 74, 68, 87, 111, 52,
+75, 119, 49, 34, 82, 81, 95, 65, 112, 86, 118, 110, 122, 105, 41, 57,
+83, 43, 46, 102, 40, 89, 38, 103, 45, 50, 42, 123, 91, 35, 125, 55,
+54, 66, 124, 126, 59, 47, 92, 71, 115, 78, 88, 107, 106, 56, 36, 121,
+117, 104, 101, 100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48, 58, 113,
+32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85, 223, 225, 216,
+187, 166, 229, 189, 222, 188, 141, 249, 148, 200, 184, 136, 248, 190,
+199, 170, 181, 204, 138, 232, 218, 183, 255, 234, 220, 247, 213, 203,
+226, 193, 174, 172, 228, 252, 217, 201, 131, 230, 197, 211, 145, 238,
+161, 179, 160, 212, 207, 221, 254, 173, 202, 146, 224, 151, 140, 196,
+205, 130, 135, 133, 143, 246, 192, 159, 244, 239, 185, 168, 215, 144,
+139, 165, 180, 157, 147, 186, 214, 176, 227, 231, 219, 169, 175, 156,
+206, 198, 129, 164, 150, 210, 154, 177, 134, 127, 182, 128, 158, 208,
+162, 132, 167, 209, 149, 241, 153, 251, 237, 236, 171, 195, 243, 233,
+253, 240, 194, 250, 191, 155, 142, 137, 245, 235, 163, 242, 178, 152 };
+
+
+/* SCRAMBLE and DESCRAMBLE work like this:
+ *
+ * scramble(STR) returns SCRM, a scrambled copy of STR. SCRM[0] is a
+ * single letter indicating the scrambling method. As of this
+ * writing, the only legal method is 'A', but check the code for more
+ * up-to-date information. The copy will have been allocated with
+ * malloc().
+ *
+ * descramble(SCRM) returns STR, again in its own malloc'd space.
+ * descramble() uses SCRM[0] to determine which method of unscrambling
+ * to use. If it does not recognize the method, it dies with error.
+ */
+
+/* Return a malloc'd, scrambled version of STR. */
+char *
+scramble (str)
+ char *str;
+{
+ int i;
+ char *s;
+
+ /* +2 to hold the 'A' prefix that indicates which version of
+ * scrambling this is (the first, obviously, since we only do one
+ * kind of scrambling so far), and then the '\0' of course.
+ */
+ s = (char *) xmalloc (strlen (str) + 2);
+
+ s[0] = 'A'; /* Scramble (TM) version prefix. */
+ strcpy (s + 1, str);
+
+ for (i = 1; s[i]; i++)
+ s[i] = shifts[(unsigned char)(s[i])];
+
+ return s;
+}
+
+/* Decode the string in place. */
+char *
+descramble (str)
+ char *str;
+{
+ char *s;
+ int i;
+
+ /* For now we can only handle one kind of scrambling. In the future
+ * there may be other kinds, and this `if' will become a `switch'.
+ */
+ if (str[0] != 'A')
+#ifndef DIAGNOSTIC
+ error (1, 0, "descramble: unknown scrambling method");
+#else /* DIAGNOSTIC */
+ {
+ fprintf (stderr, "descramble: unknown scrambling method\n", str);
+ fflush (stderr);
+ exit (EXIT_FAILURE);
+ }
+#endif /* DIAGNOSTIC */
+
+ /* Method `A' is symmetrical, so scramble again to decrypt. */
+ s = scramble (str + 1);
+
+ /* Shift the whole string one char to the left, pushing the unwanted
+ 'A' off the left end. Safe, because s is null-terminated. */
+ for (i = 0; s[i]; i++)
+ s[i] = s[i + 1];
+
+ return s;
+}
+
+#endif /* (AUTH_CLIENT_SUPPORT || AUTH_SERVER_SUPPORT) from top of file */
+
+#ifdef DIAGNOSTIC
+int
+main ()
+{
+ int i;
+ char *e, *m, biggie[256];
+
+ char *cleartexts[5];
+ cleartexts[0] = "first";
+ cleartexts[1] = "the second";
+ cleartexts[2] = "this is the third";
+ cleartexts[3] = "$#% !!\\3";
+ cleartexts[4] = biggie;
+
+ /* Set up the most important test string: */
+ /* Can't have a real ASCII zero in the string, because we want to
+ use printf, so we substitute the character zero. */
+ biggie[0] = '0';
+ /* The rest of the string gets straight ascending ASCII. */
+ for (i = 1; i < 256; i++)
+ biggie[i] = i;
+
+ /* Test all the strings. */
+ for (i = 0; i < 5; i++)
+ {
+ printf ("clear%d: %s\n", i, cleartexts[i]);
+ e = scramble (cleartexts[i]);
+ printf ("scram%d: %s\n", i, e);
+ m = descramble (e);
+ free (e);
+ printf ("clear%d: %s\n\n", i, m);
+ free (m);
+ }
+
+ fflush (stdout);
+ return 0;
+}
+#endif /* DIAGNOSTIC */
+
+/*
+ * ;;; The Emacs Lisp that did the dirty work ;;;
+ * (progn
+ *
+ * ;; Helper func.
+ * (defun random-elt (lst)
+ * (let* ((len (length lst))
+ * (rnd (random len)))
+ * (nth rnd lst)))
+ *
+ * ;; A list of all characters under 127, each appearing once.
+ * (setq non-meta-chars
+ * (let ((i 0)
+ * (l nil))
+ * (while (< i 127)
+ * (setq l (cons i l)
+ * i (1+ i)))
+ * l))
+ *
+ * ;; A list of all characters 127 and above, each appearing once.
+ * (setq meta-chars
+ * (let ((i 127)
+ * (l nil))
+ * (while (< i 256)
+ * (setq l (cons i l)
+ * i (1+ i)))
+ * l))
+ *
+ * ;; A vector that will hold the chars in a random order.
+ * (setq scrambled-chars (make-vector 256 0))
+ *
+ * ;; These characters should map to themselves.
+ * (let ((i 0))
+ * (while (< i 32)
+ * (aset scrambled-chars i i)
+ * (setq non-meta-chars (delete i non-meta-chars)
+ * i (1+ i))))
+ *
+ * ;; Assign random (but unique) values, within the non-meta chars.
+ * (let ((i 32))
+ * (while (< i 127)
+ * (let ((ch (random-elt non-meta-chars)))
+ * (if (= 0 (aref scrambled-chars i))
+ * (progn
+ * (aset scrambled-chars i ch)
+ * (aset scrambled-chars ch i)
+ * (setq non-meta-chars (delete ch non-meta-chars)
+ * non-meta-chars (delete i non-meta-chars))))
+ * (setq i (1+ i)))))
+ *
+ * ;; Assign random (but unique) values, within the non-meta chars.
+ * (let ((i 127))
+ * (while (< i 256)
+ * (let ((ch (random-elt meta-chars)))
+ * (if (= 0 (aref scrambled-chars i))
+ * (progn
+ * (aset scrambled-chars i ch)
+ * (aset scrambled-chars ch i)
+ * (setq meta-chars (delete ch meta-chars)
+ * meta-chars (delete i meta-chars))))
+ * (setq i (1+ i)))))
+ *
+ * ;; Now use the `scrambled-chars' vector to get your C array.
+ * )
+ */
diff --git a/contrib/cvs/src/server.c b/contrib/cvs/src/server.c
new file mode 100644
index 0000000..e92445b
--- /dev/null
+++ b/contrib/cvs/src/server.c
@@ -0,0 +1,4642 @@
+#include <assert.h>
+#include "cvs.h"
+#include "watch.h"
+#include "edit.h"
+#include "fileattr.h"
+
+#ifdef SERVER_SUPPORT
+
+/* for select */
+#include <sys/types.h>
+#ifdef HAVE_SYS_BSDTYPES_H
+#include <sys/bsdtypes.h>
+#endif
+#include <sys/time.h>
+
+#if HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#if HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#ifndef O_NONBLOCK
+#define O_NONBLOCK O_NDELAY
+#endif
+
+#ifdef AUTH_SERVER_SUPPORT
+/* For initgroups(). */
+#if HAVE_INITGROUPS
+#include <grp.h>
+#endif /* HAVE_INITGROUPS */
+#endif /* AUTH_SERVER_SUPPORT */
+
+
+/* Functions which the server calls. */
+int add PROTO((int argc, char **argv));
+int admin PROTO((int argc, char **argv));
+int checkout PROTO((int argc, char **argv));
+int commit PROTO((int argc, char **argv));
+int diff PROTO((int argc, char **argv));
+int history PROTO((int argc, char **argv));
+int import PROTO((int argc, char **argv));
+int cvslog PROTO((int argc, char **argv));
+int patch PROTO((int argc, char **argv));
+int release PROTO((int argc, char **argv));
+int cvsremove PROTO((int argc, char **argv));
+int rtag PROTO((int argc, char **argv));
+int status PROTO((int argc, char **argv));
+int tag PROTO((int argc, char **argv));
+int update PROTO((int argc, char **argv));
+
+
+/*
+ * This is where we stash stuff we are going to use. Format string
+ * which expects a single directory within it, starting with a slash.
+ */
+static char *server_temp_dir;
+
+/* Nonzero if we should keep the temp directory around after we exit. */
+static int dont_delete_temp;
+
+static char no_mem_error;
+#define NO_MEM_ERROR (&no_mem_error)
+
+static void server_write_entries PROTO((void));
+
+/*
+ * Read a line from the stream "instream" without command line editing.
+ *
+ * Action is compatible with "readline", e.g. space for the result is
+ * malloc'd and should be freed by the caller.
+ *
+ * A NULL return means end of file. A return of NO_MEM_ERROR means
+ * that we are out of memory.
+ */
+static char *read_line PROTO((FILE *));
+
+static char *
+read_line (stream)
+ FILE *stream;
+{
+ int c;
+ char *result;
+ int input_index = 0;
+ int result_size = 80;
+
+ fflush (stdout);
+ result = (char *) malloc (result_size);
+ if (result == NULL)
+ return NO_MEM_ERROR;
+
+ while (1)
+ {
+ c = fgetc (stream);
+
+ if (c == EOF)
+ {
+ free (result);
+ return NULL;
+ }
+
+ if (c == '\n')
+ break;
+
+ result[input_index++] = c;
+ while (input_index >= result_size)
+ {
+ result_size *= 2;
+ result = (char *) realloc (result, result_size);
+ if (result == NULL)
+ return NO_MEM_ERROR;
+ }
+ }
+
+ result[input_index++] = '\0';
+ return result;
+}
+
+/*
+ * Make directory DIR, including all intermediate directories if necessary.
+ * Returns 0 for success or errno code.
+ */
+static int mkdir_p PROTO((char *));
+
+static int
+mkdir_p (dir)
+ char *dir;
+{
+ char *p;
+ char *q = malloc (strlen (dir) + 1);
+ int retval;
+
+ if (q == NULL)
+ return ENOMEM;
+
+ /*
+ * Skip over leading slash if present. We won't bother to try to
+ * make '/'.
+ */
+ p = dir + 1;
+ while (1)
+ {
+ while (*p != '/' && *p != '\0')
+ ++p;
+ if (*p == '/')
+ {
+ strncpy (q, dir, p - dir);
+ q[p - dir] = '\0';
+ if (CVS_MKDIR (q, 0777) < 0)
+ {
+ if (errno != EEXIST
+ && (errno != EACCES || !isdir(q)))
+ {
+ retval = errno;
+ goto done;
+ }
+ }
+ ++p;
+ }
+ else
+ {
+ if (CVS_MKDIR (dir, 0777) < 0)
+ retval = errno;
+ else
+ retval = 0;
+ goto done;
+ }
+ }
+ done:
+ free (q);
+ return retval;
+}
+
+/*
+ * Print the error response for error code STATUS. The caller is
+ * reponsible for making sure we get back to the command loop without
+ * any further output occuring.
+ */
+static void
+print_error (status)
+ int status;
+{
+ char *msg;
+ printf ("error ");
+ msg = strerror (status);
+ if (msg)
+ printf ("%s", msg);
+ printf ("\n");
+}
+
+static int pending_error;
+/*
+ * Malloc'd text for pending error. Each line must start with "E ". The
+ * last line should not end with a newline.
+ */
+static char *pending_error_text;
+
+/* If an error is pending, print it and return 1. If not, return 0. */
+static int
+print_pending_error ()
+{
+ if (pending_error_text)
+ {
+ printf ("%s\n", pending_error_text);
+ if (pending_error)
+ print_error (pending_error);
+ else
+ printf ("error \n");
+ pending_error = 0;
+ free (pending_error_text);
+ pending_error_text = NULL;
+ return 1;
+ }
+ else if (pending_error)
+ {
+ print_error (pending_error);
+ pending_error = 0;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+/* Is an error pending? */
+#define error_pending() (pending_error || pending_error_text)
+
+int
+supported_response (name)
+ char *name;
+{
+ struct response *rs;
+
+ for (rs = responses; rs->name != NULL; ++rs)
+ if (strcmp (rs->name, name) == 0)
+ return rs->status == rs_supported;
+ error (1, 0, "internal error: testing support for unknown response?");
+ /* NOTREACHED */
+ return 0;
+}
+
+static void
+serve_valid_responses (arg)
+ char *arg;
+{
+ char *p = arg;
+ char *q;
+ struct response *rs;
+ do
+ {
+ q = strchr (p, ' ');
+ if (q != NULL)
+ *q++ = '\0';
+ for (rs = responses; rs->name != NULL; ++rs)
+ {
+ if (strcmp (rs->name, p) == 0)
+ break;
+ }
+ if (rs->name == NULL)
+ /*
+ * It is a response we have never heard of (and thus never
+ * will want to use). So don't worry about it.
+ */
+ ;
+ else
+ rs->status = rs_supported;
+ p = q;
+ } while (q != NULL);
+ for (rs = responses; rs->name != NULL; ++rs)
+ {
+ if (rs->status == rs_essential)
+ {
+ printf ("E response `%s' not supported by client\nerror \n",
+ rs->name);
+ exit (EXIT_FAILURE);
+ }
+ else if (rs->status == rs_optional)
+ rs->status = rs_not_supported;
+ }
+}
+
+static int use_dir_and_repos = 0;
+
+static void
+serve_root (arg)
+ char *arg;
+{
+ char *env;
+ extern char *CVSroot;
+ char path[PATH_MAX];
+ int save_errno;
+
+ if (error_pending()) return;
+
+ (void) sprintf (path, "%s/%s", arg, CVSROOTADM);
+ if (!isaccessible (path, R_OK | X_OK))
+ {
+ save_errno = errno;
+ pending_error_text = malloc (80 + strlen (path));
+ if (pending_error_text != NULL)
+ sprintf (pending_error_text, "E Cannot access %s", path);
+ pending_error = save_errno;
+ }
+ (void) strcat (path, "/");
+ (void) strcat (path, CVSROOTADM_HISTORY);
+ if (isfile (path) && !isaccessible (path, R_OK | W_OK))
+ {
+ save_errno = errno;
+ pending_error_text = malloc (80 + strlen (path));
+ if (pending_error_text != NULL)
+ sprintf (pending_error_text, "E \
+Sorry, you don't have read/write access to the history file %s", path);
+ pending_error = save_errno;
+ }
+
+ CVSroot = malloc (strlen (arg) + 1);
+ if (CVSroot == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (CVSroot, arg);
+#ifdef HAVE_PUTENV
+ env = malloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1);
+ if (env == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ (void) sprintf (env, "%s=%s", CVSROOT_ENV, arg);
+ (void) putenv (env);
+ /* do not free env, as putenv has control of it */
+#endif
+}
+
+/*
+ * Add as many directories to the temp directory as the client tells us it
+ * will use "..", so we never try to access something outside the temp
+ * directory via "..".
+ */
+static void
+serve_max_dotdot (arg)
+ char *arg;
+{
+ int lim = atoi (arg);
+ int i;
+ char *p;
+
+ if (lim < 0)
+ return;
+ p = malloc (strlen (server_temp_dir) + 2 * lim + 10);
+ if (p == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (p, server_temp_dir);
+ for (i = 0; i < lim; ++i)
+ strcat (p, "/d");
+ free (server_temp_dir);
+ server_temp_dir = p;
+}
+
+static char *dir_name;
+
+static void
+dirswitch (dir, repos)
+ char *dir;
+ char *repos;
+{
+ int status;
+ FILE *f;
+
+ server_write_entries ();
+
+ if (error_pending()) return;
+
+ if (dir_name != NULL)
+ free (dir_name);
+
+ dir_name = malloc (strlen (server_temp_dir) + strlen (dir) + 40);
+ if (dir_name == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+
+ strcpy (dir_name, server_temp_dir);
+ strcat (dir_name, "/");
+ strcat (dir_name, dir);
+
+ status = mkdir_p (dir_name);
+ if (status != 0
+ && status != EEXIST)
+ {
+ pending_error = status;
+ pending_error_text = malloc (80 + strlen(dir_name));
+ sprintf(pending_error_text, "E cannot mkdir %s", dir_name);
+ return;
+ }
+ if (chdir (dir_name) < 0)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(dir_name));
+ sprintf(pending_error_text, "E cannot change to %s", dir_name);
+ return;
+ }
+ /*
+ * This is pretty much like calling Create_Admin, but Create_Admin doesn't
+ * report errors in the right way for us.
+ */
+ if (CVS_MKDIR (CVSADM, 0777) < 0)
+ {
+ if (errno == EEXIST)
+ /* Don't create the files again. */
+ return;
+ pending_error = errno;
+ return;
+ }
+ f = fopen (CVSADM_REP, "w");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ return;
+ }
+ if (fprintf (f, "%s\n", repos) < 0)
+ {
+ pending_error = errno;
+ fclose (f);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ return;
+ }
+ f = fopen (CVSADM_ENT, "w+");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_ENT));
+ sprintf(pending_error_text, "E cannot open %s", CVSADM_ENT);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_ENT));
+ sprintf(pending_error_text, "E cannot close %s", CVSADM_ENT);
+ return;
+ }
+}
+
+static void
+serve_repository (arg)
+ char *arg;
+{
+ dirswitch (arg + 1, arg);
+}
+
+static void
+serve_directory (arg)
+ char *arg;
+{
+ char *repos;
+ use_dir_and_repos = 1;
+ repos = read_line (stdin);
+ if (repos == NULL)
+ {
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text)
+ {
+ if (feof (stdin))
+ sprintf (pending_error_text,
+ "E end of file reading mode for %s", arg);
+ else
+ {
+ sprintf (pending_error_text,
+ "E error reading mode for %s", arg);
+ pending_error = errno;
+ }
+ }
+ else
+ pending_error = ENOMEM;
+ }
+ else if (repos == NO_MEM_ERROR)
+ {
+ pending_error = ENOMEM;
+ }
+ else
+ {
+ dirswitch (arg, repos);
+ free (repos);
+ }
+}
+
+static void
+serve_static_directory (arg)
+ char *arg;
+{
+ FILE *f;
+ f = fopen (CVSADM_ENTSTAT, "w+");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_ENTSTAT));
+ sprintf(pending_error_text, "E cannot open %s", CVSADM_ENTSTAT);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_ENTSTAT));
+ sprintf(pending_error_text, "E cannot close %s", CVSADM_ENTSTAT);
+ return;
+ }
+}
+
+static void
+serve_sticky (arg)
+ char *arg;
+{
+ FILE *f;
+ f = fopen (CVSADM_TAG, "w+");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_TAG));
+ sprintf(pending_error_text, "E cannot open %s", CVSADM_TAG);
+ return;
+ }
+ if (fprintf (f, "%s\n", arg) < 0)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_TAG));
+ sprintf(pending_error_text, "E cannot write to %s", CVSADM_TAG);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_TAG));
+ sprintf(pending_error_text, "E cannot close %s", CVSADM_TAG);
+ return;
+ }
+}
+
+/*
+ * Read SIZE bytes from stdin, write them to FILE.
+ *
+ * Currently this isn't really used for receiving parts of a file --
+ * the file is still sent over in one chunk. But if/when we get
+ * spiffy in-process gzip support working, perhaps the compressed
+ * pieces could be sent over as they're ready, if the network is fast
+ * enough. Or something.
+ */
+static void
+receive_partial_file (size, file)
+ int size;
+ int file;
+{
+ char buf[16*1024], *bufp;
+ int toread, nread, nwrote;
+ while (size > 0)
+ {
+ toread = sizeof (buf);
+ if (toread > size)
+ toread = size;
+
+ nread = fread (buf, 1, toread, stdin);
+ if (nread <= 0)
+ {
+ if (feof (stdin))
+ {
+ pending_error_text = malloc (80);
+ if (pending_error_text)
+ {
+ sprintf (pending_error_text,
+ "E premature end of file from client");
+ pending_error = 0;
+ }
+ else
+ pending_error = ENOMEM;
+ }
+ else if (ferror (stdin))
+ {
+ pending_error_text = malloc (40);
+ if (pending_error_text)
+ sprintf (pending_error_text,
+ "E error reading from client");
+ pending_error = errno;
+ }
+ else
+ {
+ pending_error_text = malloc (40);
+ if (pending_error_text)
+ sprintf (pending_error_text,
+ "E short read from client");
+ pending_error = 0;
+ }
+ return;
+ }
+ size -= nread;
+ bufp = buf;
+ while (nread)
+ {
+ nwrote = write (file, bufp, nread);
+ if (nwrote < 0)
+ {
+ pending_error_text = malloc (40);
+ if (pending_error_text)
+ sprintf (pending_error_text, "E unable to write");
+ pending_error = errno;
+ return;
+ }
+ nread -= nwrote;
+ bufp += nwrote;
+ }
+ }
+}
+
+/* Receive SIZE bytes, write to filename FILE. */
+static void
+receive_file (size, file, gzipped)
+ int size;
+ char *file;
+ int gzipped;
+{
+ int fd;
+ char *arg = file;
+ pid_t gzip_pid = 0;
+ int gzip_status;
+
+ /* Write the file. */
+ fd = open (arg, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (fd < 0)
+ {
+ pending_error_text = malloc (40 + strlen (arg));
+ if (pending_error_text)
+ sprintf (pending_error_text, "E cannot open %s", arg);
+ pending_error = errno;
+ return;
+ }
+
+ /*
+ * FIXME: This doesn't do anything reasonable with gunzip's stderr, which
+ * means that if gunzip writes to stderr, it will cause all manner of
+ * protocol violations.
+ */
+ if (gzipped)
+ fd = filter_through_gunzip (fd, 0, &gzip_pid);
+
+ receive_partial_file (size, fd);
+
+ if (pending_error_text)
+ {
+ char *p = realloc (pending_error_text,
+ strlen (pending_error_text) + strlen (arg) + 30);
+ if (p)
+ {
+ pending_error_text = p;
+ sprintf (p + strlen (p), ", file %s", arg);
+ }
+ /* else original string is supposed to be unchanged */
+ }
+
+ if (close (fd) < 0 && !error_pending ())
+ {
+ pending_error_text = malloc (40 + strlen (arg));
+ if (pending_error_text)
+ sprintf (pending_error_text, "E cannot close %s", arg);
+ pending_error = errno;
+ if (gzip_pid)
+ waitpid (gzip_pid, (int *) 0, 0);
+ return;
+ }
+
+ if (gzip_pid)
+ {
+ if (waitpid (gzip_pid, &gzip_status, 0) != gzip_pid)
+ error (1, errno, "waiting for gunzip process %ld",
+ (long) gzip_pid);
+ else if (gzip_status != 0)
+ error (1, 0, "gunzip exited %d", gzip_status);
+ }
+}
+
+static void
+serve_modified (arg)
+ char *arg;
+{
+ int size;
+ char *size_text;
+ char *mode_text;
+
+ int gzipped = 0;
+
+ if (error_pending ()) return;
+
+ mode_text = read_line (stdin);
+ if (mode_text == NULL)
+ {
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text)
+ {
+ if (feof (stdin))
+ sprintf (pending_error_text,
+ "E end of file reading mode for %s", arg);
+ else
+ {
+ sprintf (pending_error_text,
+ "E error reading mode for %s", arg);
+ pending_error = errno;
+ }
+ }
+ else
+ pending_error = ENOMEM;
+ return;
+ }
+ else if (mode_text == NO_MEM_ERROR)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ size_text = read_line (stdin);
+ if (size_text == NULL)
+ {
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text)
+ {
+ if (feof (stdin))
+ sprintf (pending_error_text,
+ "E end of file reading size for %s", arg);
+ else
+ {
+ sprintf (pending_error_text,
+ "E error reading size for %s", arg);
+ pending_error = errno;
+ }
+ }
+ else
+ pending_error = ENOMEM;
+ return;
+ }
+ else if (size_text == NO_MEM_ERROR)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ if (size_text[0] == 'z')
+ {
+ gzipped = 1;
+ size = atoi (size_text + 1);
+ }
+ else
+ size = atoi (size_text);
+ free (size_text);
+
+ if (size >= 0)
+ {
+ receive_file (size, arg, gzipped);
+ if (error_pending ()) return;
+ }
+
+ {
+ int status = change_mode (arg, mode_text);
+ free (mode_text);
+ if (status)
+ {
+ pending_error_text = malloc (40 + strlen (arg));
+ if (pending_error_text)
+ sprintf (pending_error_text,
+ "E cannot change mode for %s", arg);
+ pending_error = status;
+ return;
+ }
+ }
+}
+
+#endif /* SERVER_SUPPORT */
+
+#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
+
+int use_unchanged = 0;
+
+#endif
+#ifdef SERVER_SUPPORT
+
+static void
+serve_enable_unchanged (arg)
+ char *arg;
+{
+ use_unchanged = 1;
+}
+
+static void
+serve_lost (arg)
+ char *arg;
+{
+ if (use_unchanged)
+ {
+ /* A missing file already indicates it is nonexistent. */
+ return;
+ }
+ else
+ {
+ struct utimbuf ut;
+ int fd = open (arg, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0 || close (fd) < 0)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(arg));
+ sprintf(pending_error_text, "E cannot open %s", arg);
+ return;
+ }
+ /*
+ * Set the times to the beginning of the epoch to tell time_stamp()
+ * that the file was lost.
+ */
+ ut.actime = 0;
+ ut.modtime = 0;
+ if (utime (arg, &ut) < 0)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(arg));
+ sprintf(pending_error_text, "E cannot utime %s", arg);
+ return;
+ }
+ }
+}
+
+struct an_entry {
+ struct an_entry *next;
+ char *entry;
+};
+
+static struct an_entry *entries;
+
+static void
+serve_unchanged (arg)
+ char *arg;
+{
+ if (error_pending ())
+ return;
+ if (!use_unchanged)
+ {
+ /* A missing file already indicates it is unchanged. */
+ return;
+ }
+ else
+ {
+ struct an_entry *p;
+ char *name;
+ char *cp;
+ char *timefield;
+
+ /* Rewrite entries file to have `=' in timestamp field. */
+ for (p = entries; p != NULL; p = p->next)
+ {
+ name = p->entry + 1;
+ cp = strchr (name, '/');
+ if (cp != NULL
+ && strlen (arg) == cp - name
+ && strncmp (arg, name, cp - name) == 0)
+ {
+ timefield = strchr (cp + 1, '/') + 1;
+ if (*timefield != '=')
+ {
+ cp = timefield + strlen (timefield);
+ cp[1] = '\0';
+ while (cp > timefield)
+ {
+ *cp = cp[-1];
+ --cp;
+ }
+ *timefield = '=';
+ }
+ break;
+ }
+ }
+ }
+}
+
+static void
+serve_entry (arg)
+ char *arg;
+{
+ struct an_entry *p;
+ char *cp;
+ if (error_pending()) return;
+ p = (struct an_entry *) malloc (sizeof (struct an_entry));
+ if (p == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ /* Leave space for serve_unchanged to write '=' if it wants. */
+ cp = malloc (strlen (arg) + 2);
+ if (cp == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (cp, arg);
+ p->next = entries;
+ p->entry = cp;
+ entries = p;
+}
+
+static void
+server_write_entries ()
+{
+ FILE *f;
+ struct an_entry *p;
+ struct an_entry *q;
+
+ if (entries == NULL)
+ return;
+
+ f = NULL;
+ /* Note that we free all the entries regardless of errors. */
+ if (!error_pending ())
+ {
+ f = fopen (CVSADM_ENT, "w");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_ENT));
+ sprintf(pending_error_text, "E cannot open %s", CVSADM_ENT);
+ }
+ }
+ for (p = entries; p != NULL;)
+ {
+ if (!error_pending ())
+ {
+ if (fprintf (f, "%s\n", p->entry) < 0)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_ENT));
+ sprintf(pending_error_text, "E cannot write to %s", CVSADM_ENT);
+ }
+ }
+ free (p->entry);
+ q = p->next;
+ free (p);
+ p = q;
+ }
+ entries = NULL;
+ if (f != NULL && fclose (f) == EOF && !error_pending ())
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_ENT));
+ sprintf(pending_error_text, "E cannot close %s", CVSADM_ENT);
+ }
+}
+
+struct notify_note {
+ /* Directory in which this notification happens. malloc'd*/
+ char *dir;
+
+ /* malloc'd. */
+ char *filename;
+
+ /* The following three all in one malloc'd block, pointed to by TYPE.
+ Each '\0' terminated. */
+ /* "E" or "U". */
+ char *type;
+ /* time+host+dir */
+ char *val;
+ char *watches;
+
+ struct notify_note *next;
+};
+
+static struct notify_note *notify_list;
+/* Used while building list, to point to the last node that already exists. */
+static struct notify_note *last_node;
+
+static void serve_notify PROTO ((char *));
+
+static void
+serve_notify (arg)
+ char *arg;
+{
+ struct notify_note *new;
+ char *data;
+
+ if (error_pending ()) return;
+
+ new = (struct notify_note *) malloc (sizeof (struct notify_note));
+ if (new == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ if (dir_name == NULL)
+ goto error;
+ new->dir = malloc (strlen (dir_name) + 1);
+ if (new->dir == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (new->dir, dir_name);
+ new->filename = malloc (strlen (arg) + 1);
+ if (new->filename == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (new->filename, arg);
+
+ data = read_line (stdin);
+ if (data == NULL)
+ {
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text)
+ {
+ if (feof (stdin))
+ sprintf (pending_error_text,
+ "E end of file reading mode for %s", arg);
+ else
+ {
+ sprintf (pending_error_text,
+ "E error reading mode for %s", arg);
+ pending_error = errno;
+ }
+ }
+ else
+ pending_error = ENOMEM;
+ }
+ else if (data == NO_MEM_ERROR)
+ {
+ pending_error = ENOMEM;
+ }
+ else
+ {
+ char *cp;
+
+ new->type = data;
+ if (data[1] != '\t')
+ goto error;
+ data[1] = '\0';
+ cp = data + 2;
+ new->val = cp;
+ cp = strchr (cp, '\t');
+ if (cp == NULL)
+ goto error;
+ *cp++ = '+';
+ cp = strchr (cp, '\t');
+ if (cp == NULL)
+ goto error;
+ *cp++ = '+';
+ cp = strchr (cp, '\t');
+ if (cp == NULL)
+ goto error;
+ *cp++ = '\0';
+ new->watches = cp;
+ /* If there is another tab, ignore everything after it,
+ for future expansion. */
+ cp = strchr (cp, '\t');
+ if (cp != NULL)
+ {
+ *cp = '\0';
+ }
+
+ new->next = NULL;
+
+ if (last_node == NULL)
+ {
+ notify_list = new;
+ }
+ else
+ last_node->next = new;
+ last_node = new;
+ }
+ return;
+ error:
+ pending_error_text = malloc (40);
+ if (pending_error_text)
+ strcpy (pending_error_text,
+ "E Protocol error; misformed Notify request");
+ pending_error = 0;
+ return;
+}
+
+/* Process all the Notify requests that we have stored up. Returns 0
+ if successful, if not prints error message (via error()) and
+ returns negative value. */
+static int
+server_notify ()
+{
+ struct notify_note *p;
+ char *repos;
+ List *list;
+ Node *node;
+ int status;
+
+ while (notify_list != NULL)
+ {
+ if (chdir (notify_list->dir) < 0)
+ {
+ error (0, errno, "cannot change to %s", notify_list->dir);
+ return -1;
+ }
+ repos = Name_Repository (NULL, NULL);
+
+ /* Now writelock. */
+ list = getlist ();
+ node = getnode ();
+ node->type = LOCK;
+ node->key = xstrdup (repos);
+ status = addnode (list, node);
+ assert (status == 0);
+ Writer_Lock (list);
+
+ fileattr_startdir (repos);
+
+ notify_do (*notify_list->type, notify_list->filename, getcaller(),
+ notify_list->val, notify_list->watches, repos);
+
+ printf ("Notified ");
+ if (use_dir_and_repos)
+ {
+ char *dir = notify_list->dir + strlen (server_temp_dir) + 1;
+ if (dir[0] == '\0')
+ fputs (".", stdout);
+ else
+ fputs (dir, stdout);
+ fputs ("/\n", stdout);
+ }
+ fputs (repos, stdout);
+ fputs ("/", stdout);
+ fputs (notify_list->filename, stdout);
+ fputs ("\n", stdout);
+
+ p = notify_list->next;
+ free (notify_list->filename);
+ free (notify_list->dir);
+ free (notify_list->type);
+ free (notify_list);
+ notify_list = p;
+
+ fileattr_write ();
+ fileattr_free ();
+
+ /* Remove the writelock. */
+ Lock_Cleanup ();
+ dellist (&list);
+ }
+ /* do_cvs_command writes to stdout via write(), not stdio, so better
+ flush out the buffer. */
+ fflush (stdout);
+ return 0;
+}
+
+static int argument_count;
+static char **argument_vector;
+static int argument_vector_size;
+
+static void
+serve_argument (arg)
+ char *arg;
+{
+ char *p;
+
+ if (error_pending()) return;
+
+ if (argument_vector_size <= argument_count)
+ {
+ argument_vector_size *= 2;
+ argument_vector =
+ (char **) realloc ((char *)argument_vector,
+ argument_vector_size * sizeof (char *));
+ if (argument_vector == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ }
+ p = malloc (strlen (arg) + 1);
+ if (p == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (p, arg);
+ argument_vector[argument_count++] = p;
+}
+
+static void
+serve_argumentx (arg)
+ char *arg;
+{
+ char *p;
+
+ if (error_pending()) return;
+
+ p = argument_vector[argument_count - 1];
+ p = realloc (p, strlen (p) + 1 + strlen (arg) + 1);
+ if (p == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcat (p, "\n");
+ strcat (p, arg);
+ argument_vector[argument_count - 1] = p;
+}
+
+static void
+serve_global_option (arg)
+ char *arg;
+{
+ if (arg[0] != '-' || arg[1] == '\0' || arg[2] != '\0')
+ {
+ error_return:
+ pending_error_text = malloc (strlen (arg) + 80);
+ sprintf (pending_error_text, "E Protocol error: bad global option %s",
+ arg);
+ return;
+ }
+ switch (arg[1])
+ {
+ case 'n':
+ noexec = 1;
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case 'r':
+ cvswrite = 0;
+ break;
+ case 'Q':
+ really_quiet = 1;
+ break;
+ case 'l':
+ logoff = 1;
+ break;
+ case 't':
+ trace = 1;
+ break;
+ default:
+ goto error_return;
+ }
+}
+
+static void
+serve_set (arg)
+ char *arg;
+{
+ /* FIXME: This sends errors immediately (I think); they should be
+ put into pending_error. */
+ variable_set (arg);
+}
+
+/*
+ * We must read data from a child process and send it across the
+ * network. We do not want to block on writing to the network, so we
+ * store the data from the child process in memory. A BUFFER
+ * structure holds the status of one communication, and uses a linked
+ * list of buffer_data structures to hold data.
+ */
+
+struct buffer
+{
+ /* Data. */
+ struct buffer_data *data;
+
+ /* Last buffer on data chain. */
+ struct buffer_data *last;
+
+ /* File descriptor to write to or read from. */
+ int fd;
+
+ /* Nonzero if this is an output buffer (sanity check). */
+ int output;
+
+ /* Nonzero if the file descriptor is in nonblocking mode. */
+ int nonblocking;
+
+ /* Function to call if we can't allocate memory. */
+ void (*memory_error) PROTO((struct buffer *));
+};
+
+/* Data is stored in lists of these structures. */
+
+struct buffer_data
+{
+ /* Next buffer in linked list. */
+ struct buffer_data *next;
+
+ /*
+ * A pointer into the data area pointed to by the text field. This
+ * is where to find data that has not yet been written out.
+ */
+ char *bufp;
+
+ /* The number of data bytes found at BUFP. */
+ int size;
+
+ /*
+ * Actual buffer. This never changes after the structure is
+ * allocated. The buffer is BUFFER_DATA_SIZE bytes.
+ */
+ char *text;
+};
+
+/* The size we allocate for each buffer_data structure. */
+#define BUFFER_DATA_SIZE (4096)
+
+#ifdef SERVER_FLOWCONTROL
+/* The maximum we'll queue to the remote client before blocking. */
+# ifndef SERVER_HI_WATER
+# define SERVER_HI_WATER (2 * 1024 * 1024)
+# endif /* SERVER_HI_WATER */
+/* When the buffer drops to this, we restart the child */
+# ifndef SERVER_LO_WATER
+# define SERVER_LO_WATER (1 * 1024 * 1024)
+# endif /* SERVER_LO_WATER */
+#endif /* SERVER_FLOWCONTROL */
+
+/* Linked list of available buffer_data structures. */
+static struct buffer_data *free_buffer_data;
+
+static void allocate_buffer_datas PROTO((void));
+static inline struct buffer_data *get_buffer_data PROTO((void));
+static int buf_empty_p PROTO((struct buffer *));
+static void buf_output PROTO((struct buffer *, const char *, int));
+static void buf_output0 PROTO((struct buffer *, const char *));
+static inline void buf_append_char PROTO((struct buffer *, int));
+static int buf_send_output PROTO((struct buffer *));
+static int set_nonblock PROTO((struct buffer *));
+static int set_block PROTO((struct buffer *));
+static int buf_send_counted PROTO((struct buffer *));
+static inline void buf_append_data PROTO((struct buffer *,
+ struct buffer_data *,
+ struct buffer_data *));
+static int buf_read_file PROTO((FILE *, long, struct buffer_data **,
+ struct buffer_data **));
+static int buf_input_data PROTO((struct buffer *, int *));
+static void buf_copy_lines PROTO((struct buffer *, struct buffer *, int));
+static int buf_copy_counted PROTO((struct buffer *, struct buffer *));
+
+#ifdef SERVER_FLOWCONTROL
+static int buf_count_mem PROTO((struct buffer *));
+static int set_nonblock_fd PROTO((int));
+#endif /* SERVER_FLOWCONTROL */
+
+/* Allocate more buffer_data structures. */
+
+static void
+allocate_buffer_datas ()
+{
+ struct buffer_data *alc;
+ char *space;
+ int i;
+
+ /* Allocate buffer_data structures in blocks of 16. */
+#define ALLOC_COUNT (16)
+
+ alc = ((struct buffer_data *)
+ malloc (ALLOC_COUNT * sizeof (struct buffer_data)));
+ space = (char *) valloc (ALLOC_COUNT * BUFFER_DATA_SIZE);
+ if (alc == NULL || space == NULL)
+ return;
+ for (i = 0; i < ALLOC_COUNT; i++, alc++, space += BUFFER_DATA_SIZE)
+ {
+ alc->next = free_buffer_data;
+ free_buffer_data = alc;
+ alc->text = space;
+ }
+}
+
+/* Get a new buffer_data structure. */
+
+static inline struct buffer_data *
+get_buffer_data ()
+{
+ struct buffer_data *ret;
+
+ if (free_buffer_data == NULL)
+ {
+ allocate_buffer_datas ();
+ if (free_buffer_data == NULL)
+ return NULL;
+ }
+
+ ret = free_buffer_data;
+ free_buffer_data = ret->next;
+ return ret;
+}
+
+/* See whether a buffer is empty. */
+
+static int
+buf_empty_p (buf)
+ struct buffer *buf;
+{
+ struct buffer_data *data;
+
+ for (data = buf->data; data != NULL; data = data->next)
+ if (data->size > 0)
+ return 0;
+ return 1;
+}
+
+#ifdef SERVER_FLOWCONTROL
+/*
+ * Count how much data is stored in the buffer..
+ * Note that each buffer is a malloc'ed chunk BUFFER_DATA_SIZE.
+ */
+
+static int
+buf_count_mem (buf)
+ struct buffer *buf;
+{
+ struct buffer_data *data;
+ int mem = 0;
+
+ for (data = buf->data; data != NULL; data = data->next)
+ mem += BUFFER_DATA_SIZE;
+
+ return mem;
+}
+#endif /* SERVER_FLOWCONTROL */
+
+/* Add data DATA of length LEN to BUF. */
+
+static void
+buf_output (buf, data, len)
+ struct buffer *buf;
+ const char *data;
+ int len;
+{
+ if (buf->data != NULL
+ && (((buf->last->text + BUFFER_DATA_SIZE)
+ - (buf->last->bufp + buf->last->size))
+ >= len))
+ {
+ memcpy (buf->last->bufp + buf->last->size, data, len);
+ buf->last->size += len;
+ return;
+ }
+
+ while (1)
+ {
+ struct buffer_data *newdata;
+
+ newdata = get_buffer_data ();
+ if (newdata == NULL)
+ {
+ (*buf->memory_error) (buf);
+ return;
+ }
+
+ if (buf->data == NULL)
+ buf->data = newdata;
+ else
+ buf->last->next = newdata;
+ newdata->next = NULL;
+ buf->last = newdata;
+
+ newdata->bufp = newdata->text;
+
+ if (len <= BUFFER_DATA_SIZE)
+ {
+ newdata->size = len;
+ memcpy (newdata->text, data, len);
+ return;
+ }
+
+ newdata->size = BUFFER_DATA_SIZE;
+ memcpy (newdata->text, data, BUFFER_DATA_SIZE);
+
+ data += BUFFER_DATA_SIZE;
+ len -= BUFFER_DATA_SIZE;
+ }
+
+ /*NOTREACHED*/
+}
+
+/* Add a '\0' terminated string to BUF. */
+
+static void
+buf_output0 (buf, string)
+ struct buffer *buf;
+ const char *string;
+{
+ buf_output (buf, string, strlen (string));
+}
+
+/* Add a single character to BUF. */
+
+static inline void
+buf_append_char (buf, ch)
+ struct buffer *buf;
+ int ch;
+{
+ if (buf->data != NULL
+ && (buf->last->text + BUFFER_DATA_SIZE
+ != buf->last->bufp + buf->last->size))
+ {
+ *(buf->last->bufp + buf->last->size) = ch;
+ ++buf->last->size;
+ }
+ else
+ {
+ char b;
+
+ b = ch;
+ buf_output (buf, &b, 1);
+ }
+}
+
+/*
+ * Send all the output we've been saving up. Returns 0 for success or
+ * errno code. If the buffer has been set to be nonblocking, this
+ * will just write until the write would block.
+ */
+
+static int
+buf_send_output (buf)
+ struct buffer *buf;
+{
+ if (! buf->output)
+ abort ();
+
+ while (buf->data != NULL)
+ {
+ struct buffer_data *data;
+
+ data = buf->data;
+ while (data->size > 0)
+ {
+ int nbytes;
+
+ nbytes = write (buf->fd, data->bufp, data->size);
+ if (nbytes <= 0)
+ {
+ int status;
+
+ if (buf->nonblocking
+ && (nbytes == 0
+#ifdef EWOULDBLOCK
+ || errno == EWOULDBLOCK
+#endif
+ || errno == EAGAIN))
+ {
+ /*
+ * A nonblocking write failed to write any data.
+ * Just return.
+ */
+ return 0;
+ }
+
+ /*
+ * An error, or EOF. Throw away all the data and
+ * return.
+ */
+ if (nbytes == 0)
+ status = EIO;
+ else
+ status = errno;
+
+ buf->last->next = free_buffer_data;
+ free_buffer_data = buf->data;
+ buf->data = NULL;
+ buf->last = NULL;
+
+ return status;
+ }
+
+ data->size -= nbytes;
+ data->bufp += nbytes;
+ }
+
+ buf->data = data->next;
+ data->next = free_buffer_data;
+ free_buffer_data = data;
+ }
+
+ buf->last = NULL;
+
+ return 0;
+}
+
+#ifdef SERVER_FLOWCONTROL
+/*
+ * Set buffer BUF to non-blocking I/O. Returns 0 for success or errno
+ * code.
+ */
+
+static int
+set_nonblock_fd (fd)
+ int fd;
+{
+ int flags;
+
+ flags = fcntl (fd, F_GETFL, 0);
+ if (flags < 0)
+ return errno;
+ if (fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0)
+ return errno;
+ return 0;
+}
+#endif /* SERVER_FLOWCONTROL */
+
+static int
+set_nonblock (buf)
+ struct buffer *buf;
+{
+ int flags;
+
+ if (buf->nonblocking)
+ return 0;
+ flags = fcntl (buf->fd, F_GETFL, 0);
+ if (flags < 0)
+ return errno;
+ if (fcntl (buf->fd, F_SETFL, flags | O_NONBLOCK) < 0)
+ return errno;
+ buf->nonblocking = 1;
+ return 0;
+}
+
+/*
+ * Set buffer BUF to blocking I/O. Returns 0 for success or errno
+ * code.
+ */
+
+static int
+set_block (buf)
+ struct buffer *buf;
+{
+ int flags;
+
+ if (! buf->nonblocking)
+ return 0;
+ flags = fcntl (buf->fd, F_GETFL, 0);
+ if (flags < 0)
+ return errno;
+ if (fcntl (buf->fd, F_SETFL, flags & ~O_NONBLOCK) < 0)
+ return errno;
+ buf->nonblocking = 0;
+ return 0;
+}
+
+/*
+ * Send a character count and some output. Returns errno code or 0 for
+ * success.
+ *
+ * Sending the count in binary is OK since this is only used on a pipe
+ * within the same system.
+ */
+
+static int
+buf_send_counted (buf)
+ struct buffer *buf;
+{
+ int size;
+ struct buffer_data *data;
+
+ if (! buf->output)
+ abort ();
+
+ size = 0;
+ for (data = buf->data; data != NULL; data = data->next)
+ size += data->size;
+
+ data = get_buffer_data ();
+ if (data == NULL)
+ {
+ (*buf->memory_error) (buf);
+ return ENOMEM;
+ }
+
+ data->next = buf->data;
+ buf->data = data;
+ if (buf->last == NULL)
+ buf->last = data;
+
+ data->bufp = data->text;
+ data->size = sizeof (int);
+
+ *((int *) data->text) = size;
+
+ return buf_send_output (buf);
+}
+
+/* Append a list of buffer_data structures to an buffer. */
+
+static inline void
+buf_append_data (buf, data, last)
+ struct buffer *buf;
+ struct buffer_data *data;
+ struct buffer_data *last;
+{
+ if (data != NULL)
+ {
+ if (buf->data == NULL)
+ buf->data = data;
+ else
+ buf->last->next = data;
+ buf->last = last;
+ }
+}
+
+/*
+ * Copy the contents of file F into buffer_data structures. We can't
+ * copy directly into an buffer, because we want to handle failure and
+ * succeess differently. Returns 0 on success, or -2 if out of
+ * memory, or a status code on error. Since the caller happens to
+ * know the size of the file, it is passed in as SIZE. On success,
+ * this function sets *RETP and *LASTP, which may be passed to
+ * buf_append_data.
+ */
+
+static int
+buf_read_file (f, size, retp, lastp)
+ FILE *f;
+ long size;
+ struct buffer_data **retp;
+ struct buffer_data **lastp;
+{
+ int status;
+
+ *retp = NULL;
+ *lastp = NULL;
+
+ while (size > 0)
+ {
+ struct buffer_data *data;
+ int get;
+
+ data = get_buffer_data ();
+ if (data == NULL)
+ {
+ status = -2;
+ goto error_return;
+ }
+
+ if (*retp == NULL)
+ *retp = data;
+ else
+ (*lastp)->next = data;
+ data->next = NULL;
+ *lastp = data;
+
+ data->bufp = data->text;
+ data->size = 0;
+
+ if (size > BUFFER_DATA_SIZE)
+ get = BUFFER_DATA_SIZE;
+ else
+ get = size;
+
+ errno = EIO;
+ if (fread (data->text, get, 1, f) != 1)
+ {
+ status = errno;
+ goto error_return;
+ }
+
+ data->size += get;
+ size -= get;
+ }
+
+ return 0;
+
+ error_return:
+ if (*retp != NULL)
+ {
+ (*lastp)->next = free_buffer_data;
+ free_buffer_data = *retp;
+ }
+ return status;
+}
+
+static int
+buf_read_file_to_eof (f, retp, lastp)
+ FILE *f;
+ struct buffer_data **retp;
+ struct buffer_data **lastp;
+{
+ int status;
+
+ *retp = NULL;
+ *lastp = NULL;
+
+ while (!feof (f))
+ {
+ struct buffer_data *data;
+ int get, nread;
+
+ data = get_buffer_data ();
+ if (data == NULL)
+ {
+ status = -2;
+ goto error_return;
+ }
+
+ if (*retp == NULL)
+ *retp = data;
+ else
+ (*lastp)->next = data;
+ data->next = NULL;
+ *lastp = data;
+
+ data->bufp = data->text;
+ data->size = 0;
+
+ get = BUFFER_DATA_SIZE;
+
+ errno = EIO;
+ nread = fread (data->text, 1, get, f);
+ if (nread == 0 && !feof (f))
+ {
+ status = errno;
+ goto error_return;
+ }
+
+ data->size = nread;
+ }
+
+ return 0;
+
+ error_return:
+ if (*retp != NULL)
+ {
+ (*lastp)->next = free_buffer_data;
+ free_buffer_data = *retp;
+ }
+ return status;
+}
+
+static int
+buf_chain_length (buf)
+ struct buffer_data *buf;
+{
+ int size = 0;
+ while (buf)
+ {
+ size += buf->size;
+ buf = buf->next;
+ }
+ return size;
+}
+
+/*
+ * Read an arbitrary amount of data from a file descriptor into an
+ * input buffer. The file descriptor will be in nonblocking mode, and
+ * we just grab what we can. Return 0 on success, or -1 on end of
+ * file, or -2 if out of memory, or an error code. If COUNTP is not
+ * NULL, *COUNTP is set to the number of bytes read.
+ */
+
+static int
+buf_input_data (buf, countp)
+ struct buffer *buf;
+ int *countp;
+{
+ if (buf->output)
+ abort ();
+
+ if (countp != NULL)
+ *countp = 0;
+
+ while (1)
+ {
+ int get;
+ int nbytes;
+
+ if (buf->data == NULL
+ || (buf->last->bufp + buf->last->size
+ == buf->last->text + BUFFER_DATA_SIZE))
+ {
+ struct buffer_data *data;
+
+ data = get_buffer_data ();
+ if (data == NULL)
+ {
+ (*buf->memory_error) (buf);
+ return -2;
+ }
+
+ if (buf->data == NULL)
+ buf->data = data;
+ else
+ buf->last->next = data;
+ data->next = NULL;
+ buf->last = data;
+
+ data->bufp = data->text;
+ data->size = 0;
+ }
+
+ get = ((buf->last->text + BUFFER_DATA_SIZE)
+ - (buf->last->bufp + buf->last->size));
+ nbytes = read (buf->fd, buf->last->bufp + buf->last->size, get);
+ if (nbytes <= 0)
+ {
+ if (nbytes == 0)
+ {
+ /*
+ * This assumes that we are using POSIX or BSD style
+ * nonblocking I/O. On System V we will get a zero
+ * return if there is no data, even when not at EOF.
+ */
+ return -1;
+ }
+
+ if (errno == EAGAIN
+#ifdef EWOULDBLOCK
+ || errno == EWOULDBLOCK
+#endif
+ )
+ return 0;
+
+ return errno;
+ }
+
+ buf->last->size += nbytes;
+ if (countp != NULL)
+ *countp += nbytes;
+ }
+
+ /*NOTREACHED*/
+}
+
+/*
+ * Copy lines from an input buffer to an output buffer. This copies
+ * all complete lines (characters up to a newline) from INBUF to
+ * OUTBUF. Each line in OUTBUF is preceded by the character COMMAND
+ * and a space.
+ */
+
+static void
+buf_copy_lines (outbuf, inbuf, command)
+ struct buffer *outbuf;
+ struct buffer *inbuf;
+ int command;
+{
+ if (! outbuf->output || inbuf->output)
+ abort ();
+
+ while (1)
+ {
+ struct buffer_data *data;
+ struct buffer_data *nldata;
+ char *nl;
+ int len;
+
+ /* See if there is a newline in INBUF. */
+ nldata = NULL;
+ nl = NULL;
+ for (data = inbuf->data; data != NULL; data = data->next)
+ {
+ nl = memchr (data->bufp, '\n', data->size);
+ if (nl != NULL)
+ {
+ nldata = data;
+ break;
+ }
+ }
+
+ if (nldata == NULL)
+ {
+ /* There are no more lines in INBUF. */
+ return;
+ }
+
+ /* Put in the command. */
+ buf_append_char (outbuf, command);
+ buf_append_char (outbuf, ' ');
+
+ if (inbuf->data != nldata)
+ {
+ /*
+ * Simply move over all the buffers up to the one containing
+ * the newline.
+ */
+ for (data = inbuf->data; data->next != nldata; data = data->next)
+ ;
+ data->next = NULL;
+ buf_append_data (outbuf, inbuf->data, data);
+ inbuf->data = nldata;
+ }
+
+ /*
+ * If the newline is at the very end of the buffer, just move
+ * the buffer onto OUTBUF. Otherwise we must copy the data.
+ */
+ len = nl + 1 - nldata->bufp;
+ if (len == nldata->size)
+ {
+ inbuf->data = nldata->next;
+ if (inbuf->data == NULL)
+ inbuf->last = NULL;
+
+ nldata->next = NULL;
+ buf_append_data (outbuf, nldata, nldata);
+ }
+ else
+ {
+ buf_output (outbuf, nldata->bufp, len);
+ nldata->bufp += len;
+ nldata->size -= len;
+ }
+ }
+}
+
+/*
+ * Copy counted data from one buffer to another. The count is an
+ * integer, host size, host byte order (it is only used across a
+ * pipe). If there is enough data, it should be moved over. If there
+ * is not enough data, it should remain on the original buffer. This
+ * returns the number of bytes it needs to see in order to actually
+ * copy something over.
+ */
+
+static int
+buf_copy_counted (outbuf, inbuf)
+ struct buffer *outbuf;
+ struct buffer *inbuf;
+{
+ if (! outbuf->output || inbuf->output)
+ abort ();
+
+ while (1)
+ {
+ struct buffer_data *data;
+ int need;
+ union
+ {
+ char intbuf[sizeof (int)];
+ int i;
+ } u;
+ char *intp;
+ int count;
+ struct buffer_data *start;
+ int startoff;
+ struct buffer_data *stop;
+ int stopwant;
+
+ /* See if we have enough bytes to figure out the count. */
+ need = sizeof (int);
+ intp = u.intbuf;
+ for (data = inbuf->data; data != NULL; data = data->next)
+ {
+ if (data->size >= need)
+ {
+ memcpy (intp, data->bufp, need);
+ break;
+ }
+ memcpy (intp, data->bufp, data->size);
+ intp += data->size;
+ need -= data->size;
+ }
+ if (data == NULL)
+ {
+ /* We don't have enough bytes to form an integer. */
+ return need;
+ }
+
+ count = u.i;
+ start = data;
+ startoff = need;
+
+ /*
+ * We have an integer in COUNT. We have gotten all the data
+ * from INBUF in all buffers before START, and we have gotten
+ * STARTOFF bytes from START. See if we have enough bytes
+ * remaining in INBUF.
+ */
+ need = count - (start->size - startoff);
+ if (need <= 0)
+ {
+ stop = start;
+ stopwant = count;
+ }
+ else
+ {
+ for (data = start->next; data != NULL; data = data->next)
+ {
+ if (need <= data->size)
+ break;
+ need -= data->size;
+ }
+ if (data == NULL)
+ {
+ /* We don't have enough bytes. */
+ return need;
+ }
+ stop = data;
+ stopwant = need;
+ }
+
+ /*
+ * We have enough bytes. Free any buffers in INBUF before
+ * START, and remove STARTOFF bytes from START, so that we can
+ * forget about STARTOFF.
+ */
+ start->bufp += startoff;
+ start->size -= startoff;
+
+ if (start->size == 0)
+ start = start->next;
+
+ if (stop->size == stopwant)
+ {
+ stop = stop->next;
+ stopwant = 0;
+ }
+
+ while (inbuf->data != start)
+ {
+ data = inbuf->data;
+ inbuf->data = data->next;
+ data->next = free_buffer_data;
+ free_buffer_data = data;
+ }
+
+ /*
+ * We want to copy over the bytes from START through STOP. We
+ * only want STOPWANT bytes from STOP.
+ */
+
+ if (start != stop)
+ {
+ /* Attach the buffers from START through STOP to OUTBUF. */
+ for (data = start; data->next != stop; data = data->next)
+ ;
+ inbuf->data = stop;
+ data->next = NULL;
+ buf_append_data (outbuf, start, data);
+ }
+
+ if (stopwant > 0)
+ {
+ buf_output (outbuf, stop->bufp, stopwant);
+ stop->bufp += stopwant;
+ stop->size -= stopwant;
+ }
+ }
+
+ /*NOTREACHED*/
+}
+
+/* While processing requests, this buffer accumulates data to be sent to
+ the client, and then once we are in do_cvs_command, we use it
+ for all the data to be sent. */
+static struct buffer buf_to_net;
+
+static void serve_questionable PROTO((char *));
+
+static void
+serve_questionable (arg)
+ char *arg;
+{
+ static int initted;
+
+ if (!initted)
+ {
+ /* Pick up ignores from CVSROOTADM_IGNORE, $HOME/.cvsignore on server,
+ and CVSIGNORE on server. */
+ ign_setup ();
+ initted = 1;
+ }
+
+ if (dir_name == NULL)
+ {
+ buf_output0 (&buf_to_net, "E Protocol error: 'Directory' missing");
+ return;
+ }
+
+ if (!ign_name (arg))
+ {
+ char *update_dir;
+
+ buf_output (&buf_to_net, "M ? ", 4);
+ update_dir = dir_name + strlen (server_temp_dir) + 1;
+ if (!(update_dir[0] == '.' && update_dir[1] == '\0'))
+ {
+ buf_output0 (&buf_to_net, update_dir);
+ buf_output (&buf_to_net, "/", 1);
+ }
+ buf_output0 (&buf_to_net, arg);
+ buf_output (&buf_to_net, "\n", 1);
+ }
+}
+
+static void serve_case PROTO ((char *));
+
+static void
+serve_case (arg)
+ char *arg;
+{
+ ign_case = 1;
+}
+
+static struct buffer protocol;
+
+/* This is the output which we are saving up to send to the server, in the
+ child process. We will push it through, via the `protocol' buffer, when
+ we have a complete line. */
+static struct buffer saved_output;
+/* Likewise, but stuff which will go to stderr. */
+static struct buffer saved_outerr;
+
+static void
+protocol_memory_error (buf)
+ struct buffer *buf;
+{
+ error (1, ENOMEM, "Virtual memory exhausted");
+}
+
+/*
+ * Process IDs of the subprocess, or negative if that subprocess
+ * does not exist.
+ */
+static pid_t command_pid;
+
+static void
+outbuf_memory_error (buf)
+ struct buffer *buf;
+{
+ static const char msg[] = "E Fatal server error\n\
+error ENOMEM Virtual memory exhausted.\n";
+ if (command_pid > 0)
+ kill (command_pid, SIGTERM);
+
+ /*
+ * We have arranged things so that printing this now either will
+ * be legal, or the "E fatal error" line will get glommed onto the
+ * end of an existing "E" or "M" response.
+ */
+
+ /* If this gives an error, not much we could do. syslog() it? */
+ write (STDOUT_FILENO, msg, sizeof (msg) - 1);
+ server_cleanup (0);
+ exit (EXIT_FAILURE);
+}
+
+static void
+input_memory_error (buf)
+ struct buffer *buf;
+{
+ outbuf_memory_error (buf);
+}
+
+/* Execute COMMAND in a subprocess with the approriate funky things done. */
+
+static struct fd_set_wrapper { fd_set fds; } command_fds_to_drain;
+static int max_command_fd;
+
+#ifdef SERVER_FLOWCONTROL
+static int flowcontrol_pipe[2];
+#endif /* SERVER_FLOWCONTROL */
+
+static void
+do_cvs_command (command)
+ int (*command) PROTO((int argc, char **argv));
+{
+ /*
+ * The following file descriptors are set to -1 if that file is not
+ * currently open.
+ */
+
+ /* Data on these pipes is a series of '\n'-terminated lines. */
+ int stdout_pipe[2];
+ int stderr_pipe[2];
+
+ /*
+ * Data on this pipe is a series of counted (see buf_send_counted)
+ * packets. Each packet must be processed atomically (i.e. not
+ * interleaved with data from stdout_pipe or stderr_pipe).
+ */
+ int protocol_pipe[2];
+
+ int dev_null_fd = -1;
+
+ int errs;
+
+ command_pid = -1;
+ stdout_pipe[0] = -1;
+ stdout_pipe[1] = -1;
+ stderr_pipe[0] = -1;
+ stderr_pipe[1] = -1;
+ protocol_pipe[0] = -1;
+ protocol_pipe[1] = -1;
+
+ server_write_entries ();
+
+ if (print_pending_error ())
+ goto free_args_and_return;
+
+ (void) server_notify ();
+
+ /*
+ * We use a child process which actually does the operation. This
+ * is so we can intercept its standard output. Even if all of CVS
+ * were written to go to some special routine instead of writing
+ * to stdout or stderr, we would still need to do the same thing
+ * for the RCS commands.
+ */
+
+ if (pipe (stdout_pipe) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ if (pipe (stderr_pipe) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ if (pipe (protocol_pipe) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+#ifdef SERVER_FLOWCONTROL
+ if (pipe (flowcontrol_pipe) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ set_nonblock_fd (flowcontrol_pipe[0]);
+ set_nonblock_fd (flowcontrol_pipe[1]);
+#endif /* SERVER_FLOWCONTROL */
+
+ dev_null_fd = open ("/dev/null", O_RDONLY);
+ if (dev_null_fd < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+
+ /* Don't use vfork; we're not going to exec(). */
+ command_pid = fork ();
+ if (command_pid < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ if (command_pid == 0)
+ {
+ int exitstatus;
+
+ /* Since we're in the child, and the parent is going to take
+ care of packaging up our error messages, we can clear this
+ flag. */
+ error_use_protocol = 0;
+
+ protocol.data = protocol.last = NULL;
+ protocol.fd = protocol_pipe[1];
+ protocol.output = 1;
+ protocol.nonblocking = 0;
+ protocol.memory_error = protocol_memory_error;
+
+ saved_output.data = saved_output.last = NULL;
+ saved_output.fd = -1;
+ saved_output.output = 0;
+ saved_output.nonblocking = 0;
+ saved_output.memory_error = protocol_memory_error;
+ saved_outerr = saved_output;
+
+ if (dup2 (dev_null_fd, STDIN_FILENO) < 0)
+ error (1, errno, "can't set up pipes");
+ if (dup2 (stdout_pipe[1], STDOUT_FILENO) < 0)
+ error (1, errno, "can't set up pipes");
+ if (dup2 (stderr_pipe[1], STDERR_FILENO) < 0)
+ error (1, errno, "can't set up pipes");
+ close (stdout_pipe[0]);
+ close (stderr_pipe[0]);
+ close (protocol_pipe[0]);
+#ifdef SERVER_FLOWCONTROL
+ close (flowcontrol_pipe[1]);
+#endif /* SERVER_FLOWCONTROL */
+
+ /*
+ * Set this in .bashrc if you want to give yourself time to attach
+ * to the subprocess with a debugger.
+ */
+ if (getenv ("CVS_SERVER_SLEEP"))
+ {
+ int secs = atoi (getenv ("CVS_SERVER_SLEEP"));
+ sleep (secs);
+ }
+
+ exitstatus = (*command) (argument_count, argument_vector);
+
+ /*
+ * When we exit, that will close the pipes, giving an EOF to
+ * the parent.
+ */
+ exit (exitstatus);
+ }
+
+ /* OK, sit around getting all the input from the child. */
+ {
+ struct buffer stdoutbuf;
+ struct buffer stderrbuf;
+ struct buffer protocol_inbuf;
+ /* Number of file descriptors to check in select (). */
+ int num_to_check;
+ int count_needed = 0;
+#ifdef SERVER_FLOWCONTROL
+ int have_flowcontrolled = 0;
+#endif /* SERVER_FLOWCONTROL */
+
+ FD_ZERO (&command_fds_to_drain.fds);
+ num_to_check = stdout_pipe[0];
+ FD_SET (stdout_pipe[0], &command_fds_to_drain.fds);
+ if (stderr_pipe[0] > num_to_check)
+ num_to_check = stderr_pipe[0];
+ FD_SET (stderr_pipe[0], &command_fds_to_drain.fds);
+ if (protocol_pipe[0] > num_to_check)
+ num_to_check = protocol_pipe[0];
+ FD_SET (protocol_pipe[0], &command_fds_to_drain.fds);
+ if (STDOUT_FILENO > num_to_check)
+ num_to_check = STDOUT_FILENO;
+ max_command_fd = num_to_check;
+ /*
+ * File descriptors are numbered from 0, so num_to_check needs to
+ * be one larger than the largest descriptor.
+ */
+ ++num_to_check;
+ if (num_to_check > FD_SETSIZE)
+ {
+ printf ("E internal error: FD_SETSIZE not big enough.\nerror \n");
+ goto error_exit;
+ }
+
+ stdoutbuf.data = stdoutbuf.last = NULL;
+ stdoutbuf.fd = stdout_pipe[0];
+ stdoutbuf.output = 0;
+ stdoutbuf.nonblocking = 0;
+ stdoutbuf.memory_error = input_memory_error;
+
+ stderrbuf.data = stderrbuf.last = NULL;
+ stderrbuf.fd = stderr_pipe[0];
+ stderrbuf.output = 0;
+ stderrbuf.nonblocking = 0;
+ stderrbuf.memory_error = input_memory_error;
+
+ protocol_inbuf.data = protocol_inbuf.last = NULL;
+ protocol_inbuf.fd = protocol_pipe[0];
+ protocol_inbuf.output = 0;
+ protocol_inbuf.nonblocking = 0;
+ protocol_inbuf.memory_error = input_memory_error;
+
+ set_nonblock (&buf_to_net);
+ set_nonblock (&stdoutbuf);
+ set_nonblock (&stderrbuf);
+ set_nonblock (&protocol_inbuf);
+
+ if (close (stdout_pipe[1]) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ stdout_pipe[1] = -1;
+
+ if (close (stderr_pipe[1]) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ stderr_pipe[1] = -1;
+
+ if (close (protocol_pipe[1]) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ protocol_pipe[1] = -1;
+
+#ifdef SERVER_FLOWCONTROL
+ if (close (flowcontrol_pipe[0]) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ flowcontrol_pipe[0] = -1;
+#endif /* SERVER_FLOWCONTROL */
+
+ if (close (dev_null_fd) < 0)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ dev_null_fd = -1;
+
+ while (stdout_pipe[0] >= 0
+ || stderr_pipe[0] >= 0
+ || protocol_pipe[0] >= 0)
+ {
+ fd_set readfds;
+ fd_set writefds;
+ int numfds;
+#ifdef SERVER_FLOWCONTROL
+ int bufmemsize;
+
+ /*
+ * See if we are swamping the remote client and filling our VM.
+ * Tell child to hold off if we do.
+ */
+ bufmemsize = buf_count_mem (&buf_to_net);
+ if (!have_flowcontrolled && (bufmemsize > SERVER_HI_WATER))
+ {
+ if (write(flowcontrol_pipe[1], "S", 1) == 1)
+ have_flowcontrolled = 1;
+ }
+ else if (have_flowcontrolled && (bufmemsize < SERVER_LO_WATER))
+ {
+ if (write(flowcontrol_pipe[1], "G", 1) == 1)
+ have_flowcontrolled = 0;
+ }
+#endif /* SERVER_FLOWCONTROL */
+
+ FD_ZERO (&readfds);
+ FD_ZERO (&writefds);
+ if (! buf_empty_p (&buf_to_net))
+ FD_SET (STDOUT_FILENO, &writefds);
+
+ if (stdout_pipe[0] >= 0)
+ {
+ FD_SET (stdout_pipe[0], &readfds);
+ }
+ if (stderr_pipe[0] >= 0)
+ {
+ FD_SET (stderr_pipe[0], &readfds);
+ }
+ if (protocol_pipe[0] >= 0)
+ {
+ FD_SET (protocol_pipe[0], &readfds);
+ }
+
+ do {
+ /* This used to select on exceptions too, but as far
+ as I know there was never any reason to do that and
+ SCO doesn't let you select on exceptions on pipes. */
+ numfds = select (num_to_check, &readfds, &writefds,
+ (fd_set *)0, (struct timeval *)NULL);
+ if (numfds < 0
+ && errno != EINTR)
+ {
+ print_error (errno);
+ goto error_exit;
+ }
+ } while (numfds < 0);
+
+ if (FD_ISSET (STDOUT_FILENO, &writefds))
+ {
+ /* What should we do with errors? syslog() them? */
+ buf_send_output (&buf_to_net);
+ }
+
+ if (stdout_pipe[0] >= 0
+ && (FD_ISSET (stdout_pipe[0], &readfds)))
+ {
+ int status;
+
+ status = buf_input_data (&stdoutbuf, (int *) NULL);
+
+ buf_copy_lines (&buf_to_net, &stdoutbuf, 'M');
+
+ if (status == -1)
+ stdout_pipe[0] = -1;
+ else if (status > 0)
+ {
+ print_error (status);
+ goto error_exit;
+ }
+
+ /* What should we do with errors? syslog() them? */
+ buf_send_output (&buf_to_net);
+ }
+
+ if (stderr_pipe[0] >= 0
+ && (FD_ISSET (stderr_pipe[0], &readfds)))
+ {
+ int status;
+
+ status = buf_input_data (&stderrbuf, (int *) NULL);
+
+ buf_copy_lines (&buf_to_net, &stderrbuf, 'E');
+
+ if (status == -1)
+ stderr_pipe[0] = -1;
+ else if (status > 0)
+ {
+ print_error (status);
+ goto error_exit;
+ }
+
+ /* What should we do with errors? syslog() them? */
+ buf_send_output (&buf_to_net);
+ }
+
+ if (protocol_pipe[0] >= 0
+ && (FD_ISSET (protocol_pipe[0], &readfds)))
+ {
+ int status;
+ int count_read;
+
+ status = buf_input_data (&protocol_inbuf, &count_read);
+
+ /*
+ * We only call buf_copy_counted if we have read
+ * enough bytes to make it worthwhile. This saves us
+ * from continually recounting the amount of data we
+ * have.
+ */
+ count_needed -= count_read;
+ if (count_needed <= 0)
+ count_needed = buf_copy_counted (&buf_to_net,
+ &protocol_inbuf);
+
+ if (status == -1)
+ protocol_pipe[0] = -1;
+ else if (status > 0)
+ {
+ print_error (status);
+ goto error_exit;
+ }
+
+ /* What should we do with errors? syslog() them? */
+ buf_send_output (&buf_to_net);
+ }
+ }
+
+ /*
+ * OK, we've gotten EOF on all the pipes. If there is
+ * anything left on stdoutbuf or stderrbuf (this could only
+ * happen if there was no trailing newline), send it over.
+ */
+ if (! buf_empty_p (&stdoutbuf))
+ {
+ buf_append_char (&stdoutbuf, '\n');
+ buf_copy_lines (&buf_to_net, &stdoutbuf, 'M');
+ }
+ if (! buf_empty_p (&stderrbuf))
+ {
+ buf_append_char (&stderrbuf, '\n');
+ buf_copy_lines (&buf_to_net, &stderrbuf, 'E');
+ }
+ if (! buf_empty_p (&protocol_inbuf))
+ buf_output0 (&buf_to_net,
+ "E Protocol error: uncounted data discarded\n");
+
+ errs = 0;
+
+ while (command_pid > 0)
+ {
+ int status;
+ pid_t waited_pid;
+ waited_pid = waitpid (command_pid, &status, 0);
+ if (waited_pid < 0)
+ {
+ /*
+ * Intentionally ignoring EINTR. Other errors
+ * "can't happen".
+ */
+ continue;
+ }
+
+ if (WIFEXITED (status))
+ errs += WEXITSTATUS (status);
+ else
+ {
+ int sig = WTERMSIG (status);
+ /*
+ * This is really evil, because signals might be numbered
+ * differently on the two systems. We should be using
+ * signal names (either of the "Terminated" or the "SIGTERM"
+ * variety). But cvs doesn't currently use libiberty...we
+ * could roll our own.... FIXME.
+ */
+ printf ("E Terminated with fatal signal %d\n", sig);
+
+ /* Test for a core dump. Is this portable? */
+ if (status & 0x80)
+ {
+ printf ("E Core dumped; preserving %s on server.\n\
+E CVS locks may need cleaning up.\n",
+ server_temp_dir);
+ dont_delete_temp = 1;
+ }
+ ++errs;
+ }
+ if (waited_pid == command_pid)
+ command_pid = -1;
+ }
+
+ /*
+ * OK, we've waited for the child. By now all CVS locks are free
+ * and it's OK to block on the network.
+ */
+ set_block (&buf_to_net);
+ buf_send_output (&buf_to_net);
+ }
+
+ if (errs)
+ /* We will have printed an error message already. */
+ printf ("error \n");
+ else
+ printf ("ok\n");
+ goto free_args_and_return;
+
+ error_exit:
+ if (command_pid > 0)
+ kill (command_pid, SIGTERM);
+
+ while (command_pid > 0)
+ {
+ pid_t waited_pid;
+ waited_pid = waitpid (command_pid, (int *) 0, 0);
+ if (waited_pid < 0 && errno == EINTR)
+ continue;
+ if (waited_pid == command_pid)
+ command_pid = -1;
+ }
+
+ close (dev_null_fd);
+ close (protocol_pipe[0]);
+ close (protocol_pipe[1]);
+ close (stderr_pipe[0]);
+ close (stderr_pipe[1]);
+ close (stdout_pipe[0]);
+ close (stdout_pipe[1]);
+
+ free_args_and_return:
+ /* Now free the arguments. */
+ {
+ /* argument_vector[0] is a dummy argument, we don't mess with it. */
+ char **cp;
+ for (cp = argument_vector + 1;
+ cp < argument_vector + argument_count;
+ ++cp)
+ free (*cp);
+
+ argument_count = 1;
+ }
+ return;
+}
+
+#ifdef SERVER_FLOWCONTROL
+/*
+ * Called by the child at convenient points in the server's execution for
+ * the server child to block.. ie: when it has no locks active.
+ */
+void
+server_pause_check()
+{
+ int paused = 0;
+ char buf[1];
+
+ while (read (flowcontrol_pipe[0], buf, 1) == 1)
+ {
+ if (*buf == 'S') /* Stop */
+ paused = 1;
+ else if (*buf == 'G') /* Go */
+ paused = 0;
+ else
+ return; /* ??? */
+ }
+ while (paused) {
+ int numfds, numtocheck;
+ fd_set fds;
+
+ FD_ZERO (&fds);
+ FD_SET (flowcontrol_pipe[0], &fds);
+ numtocheck = flowcontrol_pipe[0] + 1;
+
+ do {
+ numfds = select (numtocheck, &fds, (fd_set *)0,
+ (fd_set *)0, (struct timeval *)NULL);
+ if (numfds < 0
+ && errno != EINTR)
+ {
+ print_error (errno);
+ return;
+ }
+ } while (numfds < 0);
+
+ if (FD_ISSET (flowcontrol_pipe[0], &fds))
+ {
+ while (read (flowcontrol_pipe[0], buf, 1) == 1)
+ {
+ if (*buf == 'S') /* Stop */
+ paused = 1;
+ else if (*buf == 'G') /* Go */
+ paused = 0;
+ else
+ return; /* ??? */
+ }
+ }
+ }
+}
+#endif /* SERVER_FLOWCONTROL */
+
+static void output_dir PROTO((char *, char *));
+
+static void
+output_dir (update_dir, repository)
+ char *update_dir;
+ char *repository;
+{
+ if (use_dir_and_repos)
+ {
+ if (update_dir[0] == '\0')
+ buf_output0 (&protocol, ".");
+ else
+ buf_output0 (&protocol, update_dir);
+ buf_output0 (&protocol, "/\n");
+ }
+ buf_output0 (&protocol, repository);
+ buf_output0 (&protocol, "/");
+}
+
+/*
+ * Entries line that we are squirreling away to send to the client when
+ * we are ready.
+ */
+static char *entries_line;
+
+/*
+ * File which has been Scratch_File'd, we are squirreling away that fact
+ * to inform the client when we are ready.
+ */
+static char *scratched_file;
+
+/*
+ * The scratched_file will need to be removed as well as having its entry
+ * removed.
+ */
+static int kill_scratched_file;
+
+void
+server_register (name, version, timestamp, options, tag, date, conflict)
+ char *name;
+ char *version;
+ char *timestamp;
+ char *options;
+ char *tag;
+ char *date;
+ char *conflict;
+{
+ int len;
+
+ if (options == NULL)
+ options = "";
+
+ if (trace)
+ {
+ (void) fprintf (stderr,
+ "%c-> server_register(%s, %s, %s, %s, %s, %s, %s)\n",
+ (server_active) ? 'S' : ' ', /* silly */
+ name, version, timestamp, options, tag ? tag : "",
+ date ? date : "", conflict ? conflict : "");
+ }
+
+ if (entries_line != NULL)
+ {
+ /*
+ * If CVS decides to Register it more than once (which happens
+ * on "cvs update foo/foo.c" where foo and foo.c are already
+ * checked out), use the last of the entries lines Register'd.
+ */
+ free (entries_line);
+ }
+
+ /*
+ * I have reports of Scratch_Entry and Register both happening, in
+ * two different cases. Using the last one which happens is almost
+ * surely correct; I haven't tracked down why they both happen (or
+ * even verified that they are for the same file).
+ */
+ if (scratched_file != NULL)
+ {
+ free (scratched_file);
+ scratched_file = NULL;
+ }
+
+ len = (strlen (name) + strlen (version) + strlen (options) + 80);
+ if (tag)
+ len += strlen (tag);
+ if (date)
+ len += strlen (date);
+
+ entries_line = xmalloc (len);
+ sprintf (entries_line, "/%s/%s/", name, version);
+ if (conflict != NULL)
+ {
+ strcat (entries_line, "+=");
+ }
+ strcat (entries_line, "/");
+ strcat (entries_line, options);
+ strcat (entries_line, "/");
+ if (tag != NULL)
+ {
+ strcat (entries_line, "T");
+ strcat (entries_line, tag);
+ }
+ else if (date != NULL)
+ {
+ strcat (entries_line, "D");
+ strcat (entries_line, date);
+ }
+}
+
+void
+server_scratch (fname)
+ char *fname;
+{
+ /*
+ * I have reports of Scratch_Entry and Register both happening, in
+ * two different cases. Using the last one which happens is almost
+ * surely correct; I haven't tracked down why they both happen (or
+ * even verified that they are for the same file).
+ */
+ if (entries_line != NULL)
+ {
+ free (entries_line);
+ entries_line = NULL;
+ }
+
+ if (scratched_file != NULL)
+ {
+ buf_output0 (&protocol,
+ "E CVS server internal error: duplicate Scratch_Entry\n");
+ buf_send_counted (&protocol);
+ return;
+ }
+ scratched_file = xstrdup (fname);
+ kill_scratched_file = 1;
+}
+
+void
+server_scratch_entry_only ()
+{
+ kill_scratched_file = 0;
+}
+
+/* Print a new entries line, from a previous server_register. */
+static void
+new_entries_line ()
+{
+ if (entries_line)
+ {
+ buf_output0 (&protocol, entries_line);
+ buf_output (&protocol, "\n", 1);
+ }
+ else
+ /* Return the error message as the Entries line. */
+ buf_output0 (&protocol,
+ "CVS server internal error: Register missing\n");
+ free (entries_line);
+ entries_line = NULL;
+}
+
+static void
+serve_ci (arg)
+ char *arg;
+{
+ do_cvs_command (commit);
+}
+
+static void
+checked_in_response (file, update_dir, repository)
+ char *file;
+ char *update_dir;
+ char *repository;
+{
+ if (supported_response ("Mode"))
+ {
+ struct stat sb;
+ char *mode_string;
+
+ if (stat (file, &sb) < 0)
+ {
+ /* Not clear to me why the file would fail to exist, but it
+ was happening somewhere in the testsuite. */
+ if (!existence_error (errno))
+ error (0, errno, "cannot stat %s", file);
+ }
+ else
+ {
+ buf_output0 (&protocol, "Mode ");
+ mode_string = mode_to_string (sb.st_mode);
+ buf_output0 (&protocol, mode_string);
+ buf_output0 (&protocol, "\n");
+ free (mode_string);
+ }
+ }
+
+ buf_output0 (&protocol, "Checked-in ");
+ output_dir (update_dir, repository);
+ buf_output0 (&protocol, file);
+ buf_output (&protocol, "\n", 1);
+ new_entries_line ();
+}
+
+void
+server_checked_in (file, update_dir, repository)
+ char *file;
+ char *update_dir;
+ char *repository;
+{
+ if (noexec)
+ return;
+ if (scratched_file != NULL && entries_line == NULL)
+ {
+ /*
+ * This happens if we are now doing a "cvs remove" after a previous
+ * "cvs add" (without a "cvs ci" in between).
+ */
+ buf_output0 (&protocol, "Remove-entry ");
+ output_dir (update_dir, repository);
+ buf_output0 (&protocol, file);
+ buf_output (&protocol, "\n", 1);
+ free (scratched_file);
+ scratched_file = NULL;
+ }
+ else
+ {
+ checked_in_response (file, update_dir, repository);
+ }
+ buf_send_counted (&protocol);
+}
+
+void
+server_update_entries (file, update_dir, repository, updated)
+ char *file;
+ char *update_dir;
+ char *repository;
+ enum server_updated_arg4 updated;
+{
+ if (noexec)
+ return;
+ if (updated == SERVER_UPDATED)
+ checked_in_response (file, update_dir, repository);
+ else
+ {
+ if (!supported_response ("New-entry"))
+ return;
+ buf_output0 (&protocol, "New-entry ");
+ output_dir (update_dir, repository);
+ buf_output0 (&protocol, file);
+ buf_output (&protocol, "\n", 1);
+ new_entries_line ();
+ }
+
+ buf_send_counted (&protocol);
+}
+
+static void
+serve_update (arg)
+ char *arg;
+{
+ do_cvs_command (update);
+}
+
+static void
+serve_diff (arg)
+ char *arg;
+{
+ do_cvs_command (diff);
+}
+
+static void
+serve_log (arg)
+ char *arg;
+{
+ do_cvs_command (cvslog);
+}
+
+static void
+serve_add (arg)
+ char *arg;
+{
+ do_cvs_command (add);
+}
+
+static void
+serve_remove (arg)
+ char *arg;
+{
+ do_cvs_command (cvsremove);
+}
+
+static void
+serve_status (arg)
+ char *arg;
+{
+ do_cvs_command (status);
+}
+
+static void
+serve_rdiff (arg)
+ char *arg;
+{
+ do_cvs_command (patch);
+}
+
+static void
+serve_tag (arg)
+ char *arg;
+{
+ do_cvs_command (tag);
+}
+
+static void
+serve_rtag (arg)
+ char *arg;
+{
+ do_cvs_command (rtag);
+}
+
+static void
+serve_import (arg)
+ char *arg;
+{
+ do_cvs_command (import);
+}
+
+static void
+serve_admin (arg)
+ char *arg;
+{
+ do_cvs_command (admin);
+}
+
+static void
+serve_history (arg)
+ char *arg;
+{
+ do_cvs_command (history);
+}
+
+static void
+serve_release (arg)
+ char *arg;
+{
+ do_cvs_command (release);
+}
+
+static void serve_watch_on PROTO ((char *));
+
+static void
+serve_watch_on (arg)
+ char *arg;
+{
+ do_cvs_command (watch_on);
+}
+
+static void serve_watch_off PROTO ((char *));
+
+static void
+serve_watch_off (arg)
+ char *arg;
+{
+ do_cvs_command (watch_off);
+}
+
+static void serve_watch_add PROTO ((char *));
+
+static void
+serve_watch_add (arg)
+ char *arg;
+{
+ do_cvs_command (watch_add);
+}
+
+static void serve_watch_remove PROTO ((char *));
+
+static void
+serve_watch_remove (arg)
+ char *arg;
+{
+ do_cvs_command (watch_remove);
+}
+
+static void serve_watchers PROTO ((char *));
+
+static void
+serve_watchers (arg)
+ char *arg;
+{
+ do_cvs_command (watchers);
+}
+
+static void serve_editors PROTO ((char *));
+
+static void
+serve_editors (arg)
+ char *arg;
+{
+ do_cvs_command (editors);
+}
+
+static int noop PROTO ((int, char **));
+
+static int
+noop (argc, argv)
+ int argc;
+ char **argv;
+{
+ return 0;
+}
+
+static void serve_noop PROTO ((char *));
+
+static void
+serve_noop (arg)
+ char *arg;
+{
+ do_cvs_command (noop);
+}
+
+static void serve_init PROTO ((char *));
+
+static void
+serve_init (arg)
+ char *arg;
+{
+ CVSroot = malloc (strlen (arg) + 1);
+ if (CVSroot == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ strcpy (CVSroot, arg);
+
+ do_cvs_command (init);
+}
+
+static void serve_annotate PROTO ((char *));
+
+static void
+serve_annotate (arg)
+ char *arg;
+{
+ do_cvs_command (annotate);
+}
+
+static void
+serve_co (arg)
+ char *arg;
+{
+ char *tempdir;
+ int status;
+
+ if (print_pending_error ())
+ return;
+
+ if (!isdir (CVSADM))
+ {
+ /*
+ * The client has not sent a "Repository" line. Check out
+ * into a pristine directory.
+ */
+ tempdir = malloc (strlen (server_temp_dir) + 80);
+ if (tempdir == NULL)
+ {
+ printf ("E Out of memory\n");
+ return;
+ }
+ strcpy (tempdir, server_temp_dir);
+ strcat (tempdir, "/checkout-dir");
+ status = mkdir_p (tempdir);
+ if (status != 0 && status != EEXIST)
+ {
+ printf ("E Cannot create %s\n", tempdir);
+ print_error (errno);
+ free (tempdir);
+ return;
+ }
+
+ if (chdir (tempdir) < 0)
+ {
+ printf ("E Cannot change to directory %s\n", tempdir);
+ print_error (errno);
+ free (tempdir);
+ return;
+ }
+ free (tempdir);
+ }
+ do_cvs_command (checkout);
+}
+
+static void
+serve_export (arg)
+ char *arg;
+{
+ /* Tell checkout() to behave like export not checkout. */
+ command_name = "export";
+ serve_co (arg);
+}
+
+void
+server_copy_file (file, update_dir, repository, newfile)
+ char *file;
+ char *update_dir;
+ char *repository;
+ char *newfile;
+{
+ if (!supported_response ("Copy-file"))
+ return;
+ buf_output0 (&protocol, "Copy-file ");
+ output_dir (update_dir, repository);
+ buf_output0 (&protocol, file);
+ buf_output0 (&protocol, "\n");
+ buf_output0 (&protocol, newfile);
+ buf_output0 (&protocol, "\n");
+}
+
+void
+server_updated (file, update_dir, repository, updated, file_info, checksum)
+ char *file;
+ char *update_dir;
+ char *repository;
+ enum server_updated_arg4 updated;
+ struct stat *file_info;
+ unsigned char *checksum;
+{
+ char *short_pathname;
+
+ if (noexec)
+ return;
+
+ short_pathname = xmalloc (strlen (update_dir) + strlen (file) + 10);
+ if (update_dir[0] == '\0')
+ strcpy (short_pathname, file);
+ else
+ sprintf (short_pathname, "%s/%s", update_dir, file);
+
+ if (entries_line != NULL && scratched_file == NULL)
+ {
+ FILE *f;
+ struct stat sb;
+ struct buffer_data *list, *last;
+ unsigned long size;
+ char size_text[80];
+
+ if (stat (file, &sb) < 0)
+ {
+ if (existence_error (errno))
+ {
+ /*
+ * If we have a sticky tag for a branch on which the
+ * file is dead, and cvs update the directory, it gets
+ * a T_CHECKOUT but no file. So in this case just
+ * forget the whole thing.
+ */
+ free (entries_line);
+ entries_line = NULL;
+ goto done;
+ }
+ error (1, errno, "reading %s", short_pathname);
+ }
+
+ if (checksum != NULL)
+ {
+ static int checksum_supported = -1;
+
+ if (checksum_supported == -1)
+ {
+ checksum_supported = supported_response ("Checksum");
+ }
+
+ if (checksum_supported)
+ {
+ int i;
+ char buf[3];
+
+ buf_output0 (&protocol, "Checksum ");
+ for (i = 0; i < 16; i++)
+ {
+ sprintf (buf, "%02x", (unsigned int) checksum[i]);
+ buf_output0 (&protocol, buf);
+ }
+ buf_append_char (&protocol, '\n');
+ }
+ }
+
+ if (updated == SERVER_UPDATED)
+ buf_output0 (&protocol, "Updated ");
+ else if (updated == SERVER_MERGED)
+ buf_output0 (&protocol, "Merged ");
+ else if (updated == SERVER_PATCHED)
+ buf_output0 (&protocol, "Patched ");
+ else
+ abort ();
+ output_dir (update_dir, repository);
+ buf_output0 (&protocol, file);
+ buf_output (&protocol, "\n", 1);
+
+ new_entries_line ();
+
+ {
+ char *mode_string;
+
+ /* FIXME: When we check out files the umask of the server
+ (set in .bashrc if rsh is in use, or set in main.c in
+ the kerberos case, I think) affects what mode we send,
+ and it shouldn't. */
+ if (file_info != NULL)
+ mode_string = mode_to_string (file_info->st_mode);
+ else
+ mode_string = mode_to_string (sb.st_mode);
+ buf_output0 (&protocol, mode_string);
+ buf_output0 (&protocol, "\n");
+ free (mode_string);
+ }
+
+ list = last = NULL;
+ size = 0;
+ if (sb.st_size > 0)
+ {
+ /* Throughout this section we use binary mode to read the
+ file we are sending. The client handles any line ending
+ translation if necessary. */
+
+ if (gzip_level
+ /*
+ * For really tiny files, the gzip process startup
+ * time will outweigh the compression savings. This
+ * might be computable somehow; using 100 here is just
+ * a first approximation.
+ */
+ && sb.st_size > 100)
+ {
+ int status, fd, gzip_status;
+ pid_t gzip_pid;
+
+ fd = open (file, O_RDONLY | OPEN_BINARY, 0);
+ if (fd < 0)
+ error (1, errno, "reading %s", short_pathname);
+ fd = filter_through_gzip (fd, 1, gzip_level, &gzip_pid);
+ f = fdopen (fd, "rb");
+ status = buf_read_file_to_eof (f, &list, &last);
+ size = buf_chain_length (list);
+ if (status == -2)
+ (*protocol.memory_error) (&protocol);
+ else if (status != 0)
+ error (1, ferror (f) ? errno : 0, "reading %s",
+ short_pathname);
+ if (fclose (f) == EOF)
+ error (1, errno, "reading %s", short_pathname);
+ if (waitpid (gzip_pid, &gzip_status, 0) == -1)
+ error (1, errno, "waiting for gzip process %ld",
+ (long) gzip_pid);
+ else if (gzip_status != 0)
+ error (1, 0, "gzip exited %d", gzip_status);
+ /* Prepending length with "z" is flag for using gzip here. */
+ buf_output0 (&protocol, "z");
+ }
+ else
+ {
+ long status;
+
+ size = sb.st_size;
+ f = fopen (file, "rb");
+ if (f == NULL)
+ error (1, errno, "reading %s", short_pathname);
+ status = buf_read_file (f, sb.st_size, &list, &last);
+ if (status == -2)
+ (*protocol.memory_error) (&protocol);
+ else if (status != 0)
+ error (1, ferror (f) ? errno : 0, "reading %s",
+ short_pathname);
+ if (fclose (f) == EOF)
+ error (1, errno, "reading %s", short_pathname);
+ }
+ }
+
+ sprintf (size_text, "%lu\n", size);
+ buf_output0 (&protocol, size_text);
+
+ buf_append_data (&protocol, list, last);
+ /* Note we only send a newline here if the file ended with one. */
+
+ /*
+ * Avoid using up too much disk space for temporary files.
+ * A file which does not exist indicates that the file is up-to-date,
+ * which is now the case. If this is SERVER_MERGED, the file is
+ * not up-to-date, and we indicate that by leaving the file there.
+ * I'm thinking of cases like "cvs update foo/foo.c foo".
+ */
+ if ((updated == SERVER_UPDATED || updated == SERVER_PATCHED)
+ /* But if we are joining, we'll need the file when we call
+ join_file. */
+ && !joining ())
+ unlink (file);
+ }
+ else if (scratched_file != NULL && entries_line == NULL)
+ {
+ if (strcmp (scratched_file, file) != 0)
+ error (1, 0,
+ "CVS server internal error: `%s' vs. `%s' scratched",
+ scratched_file,
+ file);
+ free (scratched_file);
+ scratched_file = NULL;
+
+ if (kill_scratched_file)
+ buf_output0 (&protocol, "Removed ");
+ else
+ buf_output0 (&protocol, "Remove-entry ");
+ output_dir (update_dir, repository);
+ buf_output0 (&protocol, file);
+ buf_output (&protocol, "\n", 1);
+ }
+ else if (scratched_file == NULL && entries_line == NULL)
+ {
+ /*
+ * This can happen with death support if we were processing
+ * a dead file in a checkout.
+ */
+ }
+ else
+ error (1, 0,
+ "CVS server internal error: Register *and* Scratch_Entry.\n");
+ buf_send_counted (&protocol);
+ done:
+ free (short_pathname);
+}
+
+void
+server_set_entstat (update_dir, repository)
+ char *update_dir;
+ char *repository;
+{
+ static int set_static_supported = -1;
+ if (set_static_supported == -1)
+ set_static_supported = supported_response ("Set-static-directory");
+ if (!set_static_supported) return;
+
+ buf_output0 (&protocol, "Set-static-directory ");
+ output_dir (update_dir, repository);
+ buf_output0 (&protocol, "\n");
+ buf_send_counted (&protocol);
+}
+
+void
+server_clear_entstat (update_dir, repository)
+ char *update_dir;
+ char *repository;
+{
+ static int clear_static_supported = -1;
+ if (clear_static_supported == -1)
+ clear_static_supported = supported_response ("Clear-static-directory");
+ if (!clear_static_supported) return;
+
+ if (noexec)
+ return;
+
+ buf_output0 (&protocol, "Clear-static-directory ");
+ output_dir (update_dir, repository);
+ buf_output0 (&protocol, "\n");
+ buf_send_counted (&protocol);
+}
+
+void
+server_set_sticky (update_dir, repository, tag, date)
+ char *update_dir;
+ char *repository;
+ char *tag;
+ char *date;
+{
+ static int set_sticky_supported = -1;
+ if (set_sticky_supported == -1)
+ set_sticky_supported = supported_response ("Set-sticky");
+ if (!set_sticky_supported) return;
+
+ if (noexec)
+ return;
+
+ if (tag == NULL && date == NULL)
+ {
+ buf_output0 (&protocol, "Clear-sticky ");
+ output_dir (update_dir, repository);
+ buf_output0 (&protocol, "\n");
+ }
+ else
+ {
+ buf_output0 (&protocol, "Set-sticky ");
+ output_dir (update_dir, repository);
+ buf_output0 (&protocol, "\n");
+ if (tag != NULL)
+ {
+ buf_output0 (&protocol, "T");
+ buf_output0 (&protocol, tag);
+ }
+ else
+ {
+ buf_output0 (&protocol, "D");
+ buf_output0 (&protocol, date);
+ }
+ buf_output0 (&protocol, "\n");
+ }
+ buf_send_counted (&protocol);
+}
+
+struct template_proc_data
+{
+ char *update_dir;
+ char *repository;
+};
+
+/* Here as a static until we get around to fixing Parse_Info to pass along
+ a void * for it. */
+static struct template_proc_data *tpd;
+
+static int
+template_proc (repository, template)
+ char *repository;
+ char *template;
+{
+ FILE *fp;
+ char buf[1024];
+ size_t n;
+ struct stat sb;
+ struct template_proc_data *data = tpd;
+
+ if (!supported_response ("Template"))
+ /* Might want to warn the user that the rcsinfo feature won't work. */
+ return 0;
+ buf_output0 (&protocol, "Template ");
+ output_dir (data->update_dir, data->repository);
+ buf_output0 (&protocol, "\n");
+
+ fp = fopen (template, "rb");
+ if (fp == NULL)
+ {
+ error (0, errno, "Couldn't open rcsinfo template file %s", template);
+ return 1;
+ }
+ if (fstat (fileno (fp), &sb) < 0)
+ {
+ error (0, errno, "cannot stat rcsinfo template file %s", template);
+ return 1;
+ }
+ sprintf (buf, "%ld\n", (long) sb.st_size);
+ buf_output0 (&protocol, buf);
+ while (!feof (fp))
+ {
+ n = fread (buf, 1, sizeof buf, fp);
+ buf_output (&protocol, buf, n);
+ if (ferror (fp))
+ {
+ error (0, errno, "cannot read rcsinfo template file %s", template);
+ (void) fclose (fp);
+ return 1;
+ }
+ }
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close rcsinfo template file %s", template);
+ return 0;
+}
+
+void
+server_template (update_dir, repository)
+ char *update_dir;
+ char *repository;
+{
+ struct template_proc_data data;
+ data.update_dir = update_dir;
+ data.repository = repository;
+ tpd = &data;
+ (void) Parse_Info (CVSROOTADM_RCSINFO, repository, template_proc, 1);
+}
+
+static void
+serve_gzip_contents (arg)
+ char *arg;
+{
+ int level;
+ level = atoi (arg);
+ if (level == 0)
+ level = 6;
+ gzip_level = level;
+}
+
+static void
+serve_ignore (arg)
+ char *arg;
+{
+ /*
+ * Just ignore this command. This is used to support the
+ * update-patches command, which is not a real command, but a signal
+ * to the client that update will accept the -u argument.
+ */
+}
+
+static int
+expand_proc (pargc, argv, where, mwhere, mfile, shorten,
+ local_specified, omodule, msg)
+ int *pargc;
+ char **argv;
+ char *where;
+ char *mwhere;
+ char *mfile;
+ int shorten;
+ int local_specified;
+ char *omodule;
+ char *msg;
+{
+ int i;
+ char *dir = argv[0];
+
+ /* If mwhere has been specified, the thing we're expanding is a
+ module -- just return its name so the client will ask for the
+ right thing later. If it is an alias or a real directory,
+ mwhere will not be set, so send out the appropriate
+ expansion. */
+
+ if (mwhere != NULL)
+ {
+ printf ("Module-expansion %s", mwhere);
+ if (mfile != NULL)
+ {
+ printf ("/%s", mfile);
+ }
+ printf ("\n");
+ }
+ else
+ {
+ /* We may not need to do this anymore -- check the definition
+ of aliases before removing */
+ if (*pargc == 1)
+ printf ("Module-expansion %s\n", dir);
+ else
+ for (i = 1; i < *pargc; ++i)
+ printf ("Module-expansion %s/%s\n", dir, argv[i]);
+ }
+ return 0;
+}
+
+static void
+serve_expand_modules (arg)
+ char *arg;
+{
+ int i;
+ int err;
+ DBM *db;
+ err = 0;
+
+ /*
+ * FIXME: error handling is bogus; do_module can write to stdout and/or
+ * stderr and we're not using do_cvs_command.
+ */
+
+ server_expanding = 1;
+ db = open_module ();
+ for (i = 1; i < argument_count; i++)
+ err += do_module (db, argument_vector[i],
+ CHECKOUT, "Updating", expand_proc,
+ NULL, 0, 0, 0,
+ (char *) NULL);
+ close_module (db);
+ server_expanding = 0;
+ {
+ /* argument_vector[0] is a dummy argument, we don't mess with it. */
+ char **cp;
+ for (cp = argument_vector + 1;
+ cp < argument_vector + argument_count;
+ ++cp)
+ free (*cp);
+
+ argument_count = 1;
+ }
+ if (err)
+ /* We will have printed an error message already. */
+ printf ("error \n");
+ else
+ printf ("ok\n");
+}
+
+void
+server_prog (dir, name, which)
+ char *dir;
+ char *name;
+ enum progs which;
+{
+ if (!supported_response ("Set-checkin-prog"))
+ {
+ printf ("E \
+warning: this client does not support -i or -u flags in the modules file.\n");
+ return;
+ }
+ switch (which)
+ {
+ case PROG_CHECKIN:
+ printf ("Set-checkin-prog ");
+ break;
+ case PROG_UPDATE:
+ printf ("Set-update-prog ");
+ break;
+ }
+ printf ("%s\n%s\n", dir, name);
+}
+
+static void
+serve_checkin_prog (arg)
+ char *arg;
+{
+ FILE *f;
+ f = fopen (CVSADM_CIPROG, "w+");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_CIPROG));
+ sprintf(pending_error_text, "E cannot open %s", CVSADM_CIPROG);
+ return;
+ }
+ if (fprintf (f, "%s\n", arg) < 0)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_CIPROG));
+ sprintf(pending_error_text, "E cannot write to %s", CVSADM_CIPROG);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_CIPROG));
+ sprintf(pending_error_text, "E cannot close %s", CVSADM_CIPROG);
+ return;
+ }
+}
+
+static void
+serve_update_prog (arg)
+ char *arg;
+{
+ FILE *f;
+ f = fopen (CVSADM_UPROG, "w+");
+ if (f == NULL)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_UPROG));
+ sprintf(pending_error_text, "E cannot open %s", CVSADM_UPROG);
+ return;
+ }
+ if (fprintf (f, "%s\n", arg) < 0)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_UPROG));
+ sprintf(pending_error_text, "E cannot write to %s", CVSADM_UPROG);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ pending_error = errno;
+ pending_error_text = malloc (80 + strlen(CVSADM_UPROG));
+ sprintf(pending_error_text, "E cannot close %s", CVSADM_UPROG);
+ return;
+ }
+}
+
+static void serve_valid_requests PROTO((char *arg));
+
+#endif /* SERVER_SUPPORT */
+#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
+
+/*
+ * Parts of this table are shared with the client code,
+ * but the client doesn't need to know about the handler
+ * functions.
+ */
+
+struct request requests[] =
+{
+#ifdef SERVER_SUPPORT
+#define REQ_LINE(n, f, s) {n, f, s}
+#else
+#define REQ_LINE(n, f, s) {n, s}
+#endif
+
+ REQ_LINE("Root", serve_root, rq_essential),
+ REQ_LINE("Valid-responses", serve_valid_responses, rq_essential),
+ REQ_LINE("valid-requests", serve_valid_requests, rq_essential),
+ REQ_LINE("Repository", serve_repository, rq_essential),
+ REQ_LINE("Directory", serve_directory, rq_optional),
+ REQ_LINE("Max-dotdot", serve_max_dotdot, rq_optional),
+ REQ_LINE("Static-directory", serve_static_directory, rq_optional),
+ REQ_LINE("Sticky", serve_sticky, rq_optional),
+ 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("Modified", serve_modified, rq_essential),
+ REQ_LINE("Lost", serve_lost, rq_optional),
+ REQ_LINE("UseUnchanged", serve_enable_unchanged, rq_enableme),
+ REQ_LINE("Unchanged", serve_unchanged, rq_optional),
+ REQ_LINE("Notify", serve_notify, rq_optional),
+ REQ_LINE("Questionable", serve_questionable, rq_optional),
+ REQ_LINE("Case", serve_case, rq_optional),
+ REQ_LINE("Argument", serve_argument, rq_essential),
+ REQ_LINE("Argumentx", serve_argumentx, rq_essential),
+ REQ_LINE("Global_option", serve_global_option, rq_optional),
+ REQ_LINE("Set", serve_set, rq_optional),
+ REQ_LINE("expand-modules", serve_expand_modules, rq_optional),
+ REQ_LINE("ci", serve_ci, rq_essential),
+ REQ_LINE("co", serve_co, rq_essential),
+ REQ_LINE("update", serve_update, rq_essential),
+ REQ_LINE("diff", serve_diff, rq_optional),
+ REQ_LINE("log", serve_log, rq_optional),
+ REQ_LINE("add", serve_add, rq_optional),
+ REQ_LINE("remove", serve_remove, rq_optional),
+ REQ_LINE("update-patches", serve_ignore, rq_optional),
+ REQ_LINE("gzip-file-contents", serve_gzip_contents, rq_optional),
+ REQ_LINE("status", serve_status, rq_optional),
+ REQ_LINE("rdiff", serve_rdiff, rq_optional),
+ REQ_LINE("tag", serve_tag, rq_optional),
+ REQ_LINE("rtag", serve_rtag, rq_optional),
+ REQ_LINE("import", serve_import, rq_optional),
+ REQ_LINE("admin", serve_admin, rq_optional),
+ REQ_LINE("export", serve_export, rq_optional),
+ REQ_LINE("history", serve_history, rq_optional),
+ REQ_LINE("release", serve_release, rq_optional),
+ REQ_LINE("watch-on", serve_watch_on, rq_optional),
+ REQ_LINE("watch-off", serve_watch_off, rq_optional),
+ REQ_LINE("watch-add", serve_watch_add, rq_optional),
+ REQ_LINE("watch-remove", serve_watch_remove, rq_optional),
+ REQ_LINE("watchers", serve_watchers, rq_optional),
+ REQ_LINE("editors", serve_editors, rq_optional),
+ REQ_LINE("init", serve_init, rq_optional),
+ REQ_LINE("annotate", serve_annotate, rq_optional),
+ REQ_LINE("noop", serve_noop, rq_optional),
+ REQ_LINE(NULL, NULL, rq_optional)
+
+#undef REQ_LINE
+};
+
+#endif /* SERVER_SUPPORT or CLIENT_SUPPORT */
+#ifdef SERVER_SUPPORT
+
+static void
+serve_valid_requests (arg)
+ char *arg;
+{
+ struct request *rq;
+ if (print_pending_error ())
+ return;
+ printf ("Valid-requests");
+ for (rq = requests; rq->name != NULL; rq++)
+ if (rq->func != NULL)
+ printf (" %s", rq->name);
+ printf ("\nok\n");
+}
+
+#ifdef sun
+/*
+ * Delete temporary files. SIG is the signal making this happen, or
+ * 0 if not called as a result of a signal.
+ */
+static int command_pid_is_dead;
+static void wait_sig (sig)
+ int sig;
+{
+ int status;
+ pid_t r = wait (&status);
+ if (r == command_pid)
+ command_pid_is_dead++;
+}
+#endif
+
+void
+server_cleanup (sig)
+ int sig;
+{
+ /* Do "rm -rf" on the temp directory. */
+ int len;
+ char *cmd;
+ char *temp_dir;
+
+ if (dont_delete_temp)
+ return;
+
+ /* What a bogus kludge. This disgusting code makes all kinds of
+ assumptions about SunOS, and is only for a bug in that system.
+ So only enable it on Suns. */
+#ifdef sun
+ if (command_pid > 0) {
+ /* To avoid crashes on SunOS due to bugs in SunOS tmpfs
+ triggered by the use of rename() in RCS, wait for the
+ subprocess to die. Unfortunately, this means draining output
+ while waiting for it to unblock the signal we sent it. Yuck! */
+ int status;
+ pid_t r;
+
+ signal (SIGCHLD, wait_sig);
+ if (sig)
+ /* Perhaps SIGTERM would be more correct. But the child
+ process will delay the SIGINT delivery until its own
+ children have exited. */
+ kill (command_pid, SIGINT);
+ /* The caller may also have sent a signal to command_pid, so
+ always try waiting. First, though, check and see if it's still
+ there.... */
+ do_waitpid:
+ r = waitpid (command_pid, &status, WNOHANG);
+ if (r == 0)
+ ;
+ else if (r == command_pid)
+ command_pid_is_dead++;
+ else if (r == -1)
+ switch (errno) {
+ case ECHILD:
+ command_pid_is_dead++;
+ break;
+ case EINTR:
+ goto do_waitpid;
+ }
+ else
+ /* waitpid should always return one of the above values */
+ abort ();
+ while (!command_pid_is_dead) {
+ struct timeval timeout;
+ struct fd_set_wrapper readfds;
+ char buf[100];
+ int i;
+
+ /* Use a non-zero timeout to avoid eating up CPU cycles. */
+ timeout.tv_sec = 2;
+ timeout.tv_usec = 0;
+ readfds = command_fds_to_drain;
+ switch (select (max_command_fd + 1, &readfds.fds,
+ (fd_set *)0, (fd_set *)0,
+ &timeout)) {
+ case -1:
+ if (errno != EINTR)
+ abort ();
+ case 0:
+ /* timeout */
+ break;
+ case 1:
+ for (i = 0; i <= max_command_fd; i++)
+ {
+ if (!FD_ISSET (i, &readfds.fds))
+ continue;
+ /* this fd is non-blocking */
+ while (read (i, buf, sizeof (buf)) >= 1)
+ ;
+ }
+ break;
+ default:
+ abort ();
+ }
+ }
+ }
+#endif
+
+ /* This might be set by the user in ~/.bashrc, ~/.cshrc, etc. */
+ temp_dir = getenv ("TMPDIR");
+ if (temp_dir == NULL || temp_dir[0] == '\0')
+ temp_dir = "/tmp";
+ chdir(temp_dir);
+
+ len = strlen (server_temp_dir) + 80;
+ cmd = malloc (len);
+ if (cmd == NULL)
+ {
+ printf ("E Cannot delete %s on server; out of memory\n",
+ server_temp_dir);
+ return;
+ }
+ sprintf (cmd, "rm -rf %s", server_temp_dir);
+ system (cmd);
+ free (cmd);
+}
+
+int server_active = 0;
+int server_expanding = 0;
+
+int
+server (argc, argv)
+ int argc;
+ char **argv;
+{
+ if (argc == -1)
+ {
+ static const char *const msg[] =
+ {
+ "Usage: %s %s\n",
+ " Normally invoked by a cvs client on a remote machine.\n",
+ NULL
+ };
+ usage (msg);
+ }
+ /* Ignore argc and argv. They might be from .cvsrc. */
+
+ /* Since we're in the server parent process, error should use the
+ 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");
+ exit (EXIT_FAILURE);
+ }
+ putenv (env);
+ }
+#endif
+
+ /* OK, now figure out where we stash our temporary files. */
+ {
+ char *p;
+
+ /* This might be set by the user in ~/.bashrc, ~/.cshrc, etc. */
+ char *temp_dir = getenv ("TMPDIR");
+ if (temp_dir == NULL || temp_dir[0] == '\0')
+ temp_dir = "/tmp";
+
+ server_temp_dir = malloc (strlen (temp_dir) + 80);
+ if (server_temp_dir == NULL)
+ {
+ /*
+ * Strictly speaking, we're not supposed to output anything
+ * now. But we're about to exit(), give it a try.
+ */
+ printf ("E Fatal server error, aborting.\n\
+error ENOMEM Virtual memory exhausted.\n");
+ exit (EXIT_FAILURE);
+ }
+ strcpy (server_temp_dir, temp_dir);
+
+ /* Remove a trailing slash from TMPDIR if present. */
+ p = server_temp_dir + strlen (server_temp_dir) - 1;
+ if (*p == '/')
+ *p = '\0';
+
+ /*
+ * I wanted to use cvs-serv/PID, but then you have to worry about
+ * the permissions on the cvs-serv directory being right. So
+ * use cvs-servPID.
+ */
+ strcat (server_temp_dir, "/cvs-serv");
+
+ p = server_temp_dir + strlen (server_temp_dir);
+ sprintf (p, "%ld", (long) getpid ());
+ }
+
+ (void) SIG_register (SIGHUP, server_cleanup);
+ (void) SIG_register (SIGINT, server_cleanup);
+ (void) SIG_register (SIGQUIT, server_cleanup);
+ (void) SIG_register (SIGPIPE, server_cleanup);
+ (void) SIG_register (SIGTERM, server_cleanup);
+
+ /* Now initialize our argument vector (for arguments from the client). */
+
+ /* Small for testing. */
+ argument_vector_size = 1;
+ argument_vector =
+ (char **) malloc (argument_vector_size * sizeof (char *));
+ if (argument_vector == NULL)
+ {
+ /*
+ * Strictly speaking, we're not supposed to output anything
+ * now. But we're about to exit(), give it a try.
+ */
+ printf ("E Fatal server error, aborting.\n\
+error ENOMEM Virtual memory exhausted.\n");
+ exit (EXIT_FAILURE);
+ }
+
+ argument_count = 1;
+ argument_vector[0] = "Dummy argument 0";
+
+ buf_to_net.data = buf_to_net.last = NULL;
+ buf_to_net.fd = STDOUT_FILENO;
+ buf_to_net.output = 1;
+ buf_to_net.nonblocking = 0;
+ buf_to_net.memory_error = outbuf_memory_error;
+
+ server_active = 1;
+
+ while (1)
+ {
+ char *cmd, *orig_cmd;
+ struct request *rq;
+
+ orig_cmd = cmd = read_line (stdin);
+ if (cmd == NULL)
+ break;
+ if (cmd == NO_MEM_ERROR)
+ {
+ printf ("E Fatal server error, aborting.\n\
+error ENOMEM Virtual memory exhausted.\n");
+ break;
+ }
+ for (rq = requests; rq->name != NULL; ++rq)
+ if (strncmp (cmd, rq->name, strlen (rq->name)) == 0)
+ {
+ int len = strlen (rq->name);
+ if (cmd[len] == '\0')
+ cmd += len;
+ else if (cmd[len] == ' ')
+ cmd += len + 1;
+ else
+ /*
+ * The first len characters match, but it's a different
+ * command. e.g. the command is "cooperate" but we matched
+ * "co".
+ */
+ continue;
+ (*rq->func) (cmd);
+ break;
+ }
+ if (rq->name == NULL)
+ {
+ if (!print_pending_error ())
+ printf ("error unrecognized request `%s'\n", cmd);
+ }
+ free (orig_cmd);
+ }
+ server_cleanup (0);
+ return 0;
+}
+
+
+#ifdef AUTH_SERVER_SUPPORT
+
+extern char *crypt PROTO((const char *, const char *));
+
+/* This was test code, which we may need again. */
+#if 0
+ /* If we were invoked this way, then stdin comes from the
+ client and stdout/stderr writes to it. */
+ int c;
+ while ((c = getc (stdin)) != EOF && c != '*')
+ {
+ printf ("%c", toupper (c));
+ fflush (stdout);
+ }
+ exit (0);
+#endif /* 1/0 */
+
+
+/*
+ * 0 means no entry found for this user.
+ * 1 means entry found and password matches.
+ * 2 means entry found, but password does not match.
+ */
+int
+check_repository_password (username, password, repository, host_user_ptr)
+ char *username, *password, *repository, **host_user_ptr;
+{
+ int retval = 0;
+ FILE *fp;
+ char *filename;
+ char *linebuf;
+ int found_it = 0;
+ int namelen;
+
+ filename = xmalloc (strlen (repository)
+ + 1
+ + strlen ("CVSROOT")
+ + 1
+ + strlen ("passwd")
+ + 1);
+
+ strcpy (filename, repository);
+ strcat (filename, "/CVSROOT");
+ strcat (filename, "/passwd");
+
+ fp = fopen (filename, "r");
+ if (fp == NULL)
+ {
+ if (!existence_error (errno))
+ error (0, errno, "cannot open %s", filename);
+ return 0;
+ }
+
+ /* Look for a relevant line -- one with this user's name. */
+ namelen = strlen (username);
+ while (1)
+ {
+ linebuf = read_line(fp);
+ if (linebuf == NULL)
+ {
+ free (linebuf);
+ break;
+ }
+ if (linebuf == NO_MEM_ERROR)
+ {
+ error (0, errno, "out of memory");
+ break;
+ }
+ if ((strncmp (linebuf, username, namelen) == 0)
+ && (linebuf[namelen] == ':'))
+ {
+ found_it = 1;
+ break;
+ }
+ free (linebuf);
+
+ }
+ if (ferror (fp))
+ error (0, errno, "cannot read %s", filename);
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", filename);
+
+ /* If found_it != 0, then linebuf contains the information we need. */
+ if (found_it)
+ {
+ char *found_password;
+
+ strtok (linebuf, ":");
+ found_password = strtok (NULL, ": \n");
+ *host_user_ptr = strtok (NULL, ": \n");
+ if (*host_user_ptr == NULL) *host_user_ptr = username;
+ if (strcmp (found_password, crypt (password, found_password)) == 0)
+ retval = 1;
+ else
+ retval = 2;
+ }
+ else
+ {
+ *host_user_ptr = NULL;
+ retval = 0;
+ }
+
+ free (filename);
+
+ return retval;
+}
+
+
+/* Return a hosting username if password matches, else NULL. */
+char *
+check_password (username, password, repository)
+ char *username, *password, *repository;
+{
+ int rc;
+ char *host_user;
+
+ /* First we see if this user has a password in the CVS-specific
+ password file. If so, that's enough to authenticate with. If
+ not, we'll check /etc/passwd. */
+
+ rc = check_repository_password (username, password, repository,
+ &host_user);
+
+ if (rc == 1)
+ return host_user;
+ else if (rc == 2)
+ return 0;
+ else if (rc == 0)
+ {
+ /* No cvs password found, so try /etc/passwd. */
+
+ struct passwd *pw;
+ char *found_passwd;
+
+ pw = getpwnam (username);
+ if (pw == NULL)
+ {
+ printf ("E Fatal error, aborting.\n\
+error 0 %s: no such user\n", username);
+ exit (EXIT_FAILURE);
+ }
+ found_passwd = pw->pw_passwd;
+
+ if (found_passwd && *found_passwd)
+ return ((! strcmp (found_passwd, crypt (password, found_passwd)))
+ ? username : NULL);
+ else if (password && *password)
+ return username;
+ else
+ return NULL;
+ }
+ else
+ {
+ /* Something strange happened. We don't know what it was, but
+ we certainly won't grant authorization. */
+ return NULL;
+ }
+}
+
+
+/* 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. */
+void
+authenticate_connection ()
+{
+ char tmp[PATH_MAX];
+ char repository[PATH_MAX];
+ char username[PATH_MAX];
+ char password[PATH_MAX];
+ char *host_user;
+ char *descrambled_password;
+ struct passwd *pw;
+ int verify_and_exit = 0;
+
+ /* The Authentication Protocol. Client sends:
+ *
+ * BEGIN AUTH REQUEST\n
+ * <REPOSITORY>\n
+ * <USERNAME>\n
+ * <PASSWORD>\n
+ * END AUTH REQUEST\n
+ *
+ * Server uses above information to authenticate, then sends
+ *
+ * I LOVE YOU\n
+ *
+ * if it grants access, else
+ *
+ * I HATE YOU\n
+ *
+ * if it denies access (and it exits if denying).
+ *
+ * When the client is "cvs login", the user does not desire actual
+ * repository access, but would like to confirm the password with
+ * the server. In this case, the start and stop strings are
+ *
+ * BEGIN VERIFICATION REQUEST\n
+ *
+ * and
+ *
+ * END VERIFICATION REQUEST\n
+ *
+ * On a verification request, the server's responses are the same
+ * (with the obvious semantics), but it exits immediately after
+ * sending the response in both cases.
+ *
+ * Why is the repository sent? Well, note that the actual
+ * client/server protocol can't start up until authentication is
+ * successful. But in order to perform authentication, the server
+ * needs to look up the password in the special CVS passwd file,
+ * before trying /etc/passwd. So the client transmits the
+ * repository as part of the "authentication protocol". The
+ * repository will be redundantly retransmitted later, but that's no
+ * big deal.
+ */
+
+ /* Since we're in the server parent process, error should use the
+ protocol to report error messages. */
+ error_use_protocol = 1;
+
+ /* Make sure the protocol starts off on the right foot... */
+ fgets (tmp, PATH_MAX, stdin);
+ if (strcmp (tmp, "BEGIN VERIFICATION REQUEST\n") == 0)
+ verify_and_exit = 1;
+ else if (strcmp (tmp, "BEGIN AUTH REQUEST\n") != 0)
+ error (1, 0, "bad auth protocol start: %s", tmp);
+
+ /* Get the three important pieces of information in order. */
+ fgets (repository, PATH_MAX, stdin);
+ fgets (username, PATH_MAX, stdin);
+ fgets (password, PATH_MAX, stdin);
+
+ /* Make them pure. */
+ strip_trailing_newlines (repository);
+ strip_trailing_newlines (username);
+ strip_trailing_newlines (password);
+
+ /* ... and make sure the protocol ends on the right foot. */
+ fgets (tmp, PATH_MAX, stdin);
+ if (strcmp (tmp,
+ verify_and_exit ?
+ "END VERIFICATION REQUEST\n" : "END AUTH REQUEST\n")
+ != 0)
+ {
+ error (1, 0, "bad auth protocol end: %s", tmp);
+ }
+
+ /* We need the real cleartext before we hash it. */
+ descrambled_password = descramble (password);
+ host_user = check_password (username, descrambled_password, repository);
+ if (host_user)
+ {
+ printf ("I LOVE YOU\n");
+ fflush (stdout);
+ memset (descrambled_password, 0, strlen (descrambled_password));
+ free (descrambled_password);
+ }
+ else
+ {
+ printf ("I HATE YOU\n");
+ fflush (stdout);
+ memset (descrambled_password, 0, strlen (descrambled_password));
+ free (descrambled_password);
+ exit (EXIT_FAILURE);
+ }
+
+ /* Don't go any farther if we're just responding to "cvs login". */
+ if (verify_and_exit)
+ exit (0);
+
+ /* Switch to run as this user. */
+ pw = getpwnam (host_user);
+ if (pw == NULL)
+ {
+ error (1, 0,
+ "fatal error, aborting.\nerror 0 %s: no such user\n",
+ username);
+ }
+
+#if HAVE_INITGROUPS
+ initgroups (pw->pw_name, pw->pw_gid);
+#endif /* HAVE_INITGROUPS */
+
+ setgid (pw->pw_gid);
+ setuid (pw->pw_uid);
+ /* Inhibit access by randoms. Don't want people randomly
+ changing our temporary tree before we check things in. */
+ umask (077);
+
+#if HAVE_PUTENV
+ /* Set LOGNAME and USER in the environment, in case they are
+ already set to something else. */
+ {
+ char *env;
+
+ env = xmalloc (sizeof "LOGNAME=" + strlen (username));
+ (void) sprintf (env, "LOGNAME=%s", username);
+ (void) putenv (env);
+
+ env = xmalloc (sizeof "USER=" + strlen (username));
+ (void) sprintf (env, "USER=%s", username);
+ (void) putenv (env);
+ }
+#endif /* HAVE_PUTENV */
+}
+
+#endif /* AUTH_SERVER_SUPPORT */
+
+
+#endif /* SERVER_SUPPORT */
+
+/* Output LEN bytes at STR. If LEN is zero, then output up to (not including)
+ the first '\0' byte. Should not be called from the server parent process
+ (yet at least, in the future it might be extended so that works). */
+
+void
+cvs_output (str, len)
+ char *str;
+ size_t len;
+{
+ if (len == 0)
+ len = strlen (str);
+ if (error_use_protocol)
+ /* Eventually we'll probably want to make it so this case works,
+ but for now, callers who want to output something with
+ error_use_protocol in effect can just printf the "M foo"
+ themselves. */
+ abort ();
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ buf_output (&saved_output, str, len);
+ buf_copy_lines (&protocol, &saved_output, 'M');
+ buf_send_counted (&protocol);
+ }
+ else
+#endif
+ {
+ size_t written;
+ size_t to_write = len;
+ char *p = str;
+
+ while (to_write > 0)
+ {
+ written = fwrite (str, 1, to_write, stdout);
+ if (written == 0)
+ break;
+ p += written;
+ to_write -= written;
+ }
+ }
+}
+
+/* Like CVS_OUTPUT but output is for stderr not stdout. */
+
+void
+cvs_outerr (str, len)
+ char *str;
+ size_t len;
+{
+ if (len == 0)
+ len = strlen (str);
+ if (error_use_protocol)
+ /* Eventually we'll probably want to make it so this case works,
+ but for now, callers who want to output something with
+ error_use_protocol in effect can just printf the "E foo"
+ themselves. */
+ abort ();
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ buf_output (&saved_outerr, str, len);
+ buf_copy_lines (&protocol, &saved_outerr, 'E');
+ buf_send_counted (&protocol);
+ }
+ else
+#endif
+ {
+ size_t written;
+ size_t to_write = len;
+ char *p = str;
+
+ while (to_write > 0)
+ {
+ written = fwrite (str, 1, to_write, stderr);
+ if (written == 0)
+ break;
+ p += written;
+ to_write -= written;
+ }
+ }
+}
diff --git a/contrib/cvs/src/server.h b/contrib/cvs/src/server.h
new file mode 100644
index 0000000..30ddb8c
--- /dev/null
+++ b/contrib/cvs/src/server.h
@@ -0,0 +1,138 @@
+/* Interface between the server and the rest of CVS. */
+
+/* Miscellaneous stuff which isn't actually particularly server-specific. */
+#ifndef STDIN_FILENO
+#define STDIN_FILENO 0
+#define STDOUT_FILENO 1
+#define STDERR_FILENO 2
+#endif
+
+#ifdef SERVER_SUPPORT
+
+/*
+ * Nonzero if we are using the server. Used by various places to call
+ * server-specific functions.
+ */
+extern int server_active;
+extern int server_expanding;
+
+/* Server functions exported to the rest of CVS. */
+
+/* Run the server. */
+extern int server PROTO((int argc, char **argv));
+
+/* We have a new Entries line for a file. TAG or DATE can be NULL. */
+extern void server_register
+ PROTO((char *name, char *version, char *timestamp,
+ char *options, char *tag, char *date, char *conflict));
+
+/*
+ * We want to nuke the Entries line for a file, and (unless
+ * server_scratch_entry_only is subsequently called) the file itself.
+ */
+extern void server_scratch PROTO((char *name));
+
+/*
+ * The file which just had server_scratch called on it needs to have only
+ * the Entries line removed, not the file itself.
+ */
+extern void server_scratch_entry_only PROTO((void));
+
+/*
+ * We just successfully checked in FILE (which is just the bare
+ * filename, with no directory). REPOSITORY is the directory for the
+ * repository.
+ */
+extern void server_checked_in
+ PROTO((char *file, char *update_dir, char *repository));
+
+extern void server_copy_file
+ PROTO((char *file, char *update_dir, char *repository, char *newfile));
+
+/*
+ * We just successfully updated FILE (bare filename, no directory).
+ * REPOSITORY is the directory for the repository. This is called
+ * after server_register or server_scratch, in the latter case the
+ * file is to be removed. UPDATED indicates whether the file is now
+ * up to date (SERVER_UPDATED, yes, SERVER_MERGED, no, SERVER_PATCHED,
+ * yes, but file is a diff from user version to repository version).
+ */
+enum server_updated_arg4 {SERVER_UPDATED, SERVER_MERGED, SERVER_PATCHED};
+extern void server_updated
+ PROTO((char *file, char *update_dir, char *repository,
+ enum server_updated_arg4 updated, struct stat *,
+ unsigned char *checksum));
+
+/* Set the Entries.Static flag. */
+extern void server_set_entstat PROTO((char *update_dir, char *repository));
+/* Clear it. */
+extern void server_clear_entstat PROTO((char *update_dir, char *repository));
+
+/* Set or clear a per-directory sticky tag or date. */
+extern void server_set_sticky PROTO((char *update_dir, char *repository,
+ char *tag,
+ char *date));
+/* Send Template response. */
+extern void server_template PROTO ((char *, char *));
+
+extern void server_update_entries
+ PROTO((char *file, char *update_dir, char *repository,
+ enum server_updated_arg4 updated));
+
+enum progs {PROG_CHECKIN, PROG_UPDATE};
+extern void server_prog PROTO((char *, char *, enum progs));
+extern void server_cleanup PROTO((int sig));
+
+#ifdef SERVER_FLOWCONTROL
+/* Pause if it's convenient to avoid memory blowout */
+extern void server_pause_check PROTO((void));
+#endif /* SERVER_FLOWCONTROL */
+
+#endif /* SERVER_SUPPORT */
+
+/* Stuff shared with the client. */
+struct request
+{
+ /* Name of the request. */
+ char *name;
+
+#ifdef SERVER_SUPPORT
+ /*
+ * Function to carry out the request. ARGS is the text of the command
+ * after name and, if present, a single space, have been stripped off.
+ */
+ void (*func) PROTO((char *args));
+#endif
+
+ /* Stuff for use by the client. */
+ enum {
+ /*
+ * Failure to implement this request can imply a fatal
+ * error. This should be set only for commands which were in the
+ * original version of the protocol; it should not be set for new
+ * commands.
+ */
+ rq_essential,
+
+ /* Some servers might lack this request. */
+ rq_optional,
+
+ /*
+ * Set by the client to one of the following based on what this
+ * server actually supports.
+ */
+ rq_supported,
+ rq_not_supported,
+
+ /*
+ * If the server supports this request, and we do too, tell the
+ * server by making the request.
+ */
+ rq_enableme
+ } status;
+};
+
+/* Table of requests ending with an entry with a NULL name. */
+extern struct request requests[];
+
+extern int use_unchanged;
diff --git a/contrib/cvs/src/status.c b/contrib/cvs/src/status.c
new file mode 100644
index 0000000..277da0c
--- /dev/null
+++ b/contrib/cvs/src/status.c
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Status Information
+ */
+
+#include "cvs.h"
+
+static Dtype status_dirproc PROTO((char *dir, char *repos, char *update_dir));
+static int status_fileproc PROTO((struct file_info *finfo));
+static int tag_list_proc PROTO((Node * p, void *closure));
+
+static int local = 0;
+static int long_format = 0;
+static RCSNode *xrcsnode;
+
+static const char *const status_usage[] =
+{
+ "Usage: %s %s [-vlR] [files...]\n",
+ "\t-v\tVerbose format; includes tag information for the file\n",
+ "\t-l\tProcess this directory only (not recursive).\n",
+ "\t-R\tProcess directories recursively.\n",
+ NULL
+};
+
+int
+status (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int err = 0;
+
+ if (argc == -1)
+ usage (status_usage);
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "vlR")) != -1)
+ {
+ switch (c)
+ {
+ case 'v':
+ long_format = 1;
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case '?':
+ default:
+ usage (status_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ wrap_setup ();
+
+#ifdef CLIENT_SUPPORT
+ if (client_active) {
+ start_server ();
+
+ ign_setup ();
+
+ if (long_format)
+ send_arg("-v");
+ if (local)
+ send_arg("-l");
+
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* XXX This should only need to send file info; the file
+ contents themselves will not be examined. */
+ send_files (argc, argv, local, 0);
+
+ send_to_server ("status\012", 0);
+ err = get_responses_and_close ();
+
+ return err;
+ }
+#endif
+
+ /* start the recursion processor */
+ err = start_recursion (status_fileproc, (FILESDONEPROC) NULL, status_dirproc,
+ (DIRLEAVEPROC) NULL, argc, argv, local,
+ W_LOCAL, 0, 1, (char *) NULL, 1, 0);
+
+ return (err);
+}
+
+/*
+ * display the status of a file
+ */
+/* ARGSUSED */
+static int
+status_fileproc (finfo)
+ struct file_info *finfo;
+{
+ Ctype status;
+ char *sstat;
+ Vers_TS *vers;
+
+ status = Classify_File (finfo->file, (char *) NULL, (char *) NULL, (char *) NULL,
+ 1, 0, finfo->repository, finfo->entries, finfo->rcs, &vers,
+ finfo->update_dir, 0);
+ switch (status)
+ {
+ case T_UNKNOWN:
+ sstat = "Unknown";
+ break;
+ case T_CHECKOUT:
+ sstat = "Needs Checkout";
+ break;
+#ifdef SERVER_SUPPORT
+ case T_PATCH:
+ sstat = "Needs Patch";
+ break;
+#endif
+ case T_CONFLICT:
+ sstat = "Unresolved Conflict";
+ break;
+ case T_ADDED:
+ sstat = "Locally Added";
+ break;
+ case T_REMOVED:
+ sstat = "Locally Removed";
+ break;
+ case T_MODIFIED:
+ if (vers->ts_conflict)
+ sstat = "Unresolved Conflict";
+ else
+ sstat = "Locally Modified";
+ break;
+ case T_REMOVE_ENTRY:
+ sstat = "Entry Invalid";
+ break;
+ case T_UPTODATE:
+ sstat = "Up-to-date";
+ break;
+ case T_NEEDS_MERGE:
+ sstat = "Needs Merge";
+ break;
+ default:
+ sstat = "Classify Error";
+ break;
+ }
+
+ (void) printf ("===================================================================\n");
+ if (vers->ts_user == NULL)
+ (void) printf ("File: no file %s\t\tStatus: %s\n\n", finfo->file, sstat);
+ else
+ (void) printf ("File: %-17s\tStatus: %s\n\n", finfo->file, sstat);
+
+ if (vers->vn_user == NULL)
+ (void) printf (" Working revision:\tNo entry for %s\n", finfo->file);
+ else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
+ (void) printf (" Working revision:\tNew file!\n");
+#ifdef SERVER_SUPPORT
+ else if (server_active)
+ (void) printf (" Working revision:\t%s\n", vers->vn_user);
+#endif
+ else
+ (void) printf (" Working revision:\t%s\t%s\n", vers->vn_user,
+ vers->ts_rcs);
+
+ if (vers->vn_rcs == NULL)
+ (void) printf (" Repository revision:\tNo revision control file\n");
+ else
+ (void) printf (" Repository revision:\t%s\t%s\n", vers->vn_rcs,
+ vers->srcfile->path);
+
+ if (vers->entdata)
+ {
+ Entnode *edata;
+
+ edata = vers->entdata;
+ if (edata->tag)
+ {
+ if (vers->vn_rcs == NULL)
+ (void) printf (
+ " Sticky Tag:\t\t%s - MISSING from RCS file!\n",
+ edata->tag);
+ else
+ {
+ if (isdigit (edata->tag[0]))
+ (void) printf (" Sticky Tag:\t\t%s\n", edata->tag);
+ else
+ {
+ char *branch = NULL;
+
+ if (RCS_isbranch (finfo->rcs, edata->tag))
+ branch = RCS_whatbranch(finfo->rcs, edata->tag);
+
+ (void) printf (" Sticky Tag:\t\t%s (%s: %s)\n",
+ edata->tag,
+ branch ? "branch" : "revision",
+ branch ? branch : vers->vn_rcs);
+
+ if (branch)
+ free (branch);
+ }
+ }
+ }
+ else if (!really_quiet)
+ (void) printf (" Sticky Tag:\t\t(none)\n");
+
+ if (edata->date)
+ (void) printf (" Sticky Date:\t\t%s\n", edata->date);
+ else if (!really_quiet)
+ (void) printf (" Sticky Date:\t\t(none)\n");
+
+ if (edata->options && edata->options[0])
+ (void) printf (" Sticky Options:\t%s\n", edata->options);
+ else if (!really_quiet)
+ (void) printf (" Sticky Options:\t(none)\n");
+
+ if (long_format && vers->srcfile)
+ {
+ List *symbols = RCS_symbols(vers->srcfile);
+
+ (void) printf ("\n Existing Tags:\n");
+ if (symbols)
+ {
+ xrcsnode = finfo->rcs;
+ (void) walklist (symbols, tag_list_proc, NULL);
+ }
+ else
+ (void) printf ("\tNo Tags Exist\n");
+ }
+ }
+
+ (void) printf ("\n");
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+status_dirproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "Examining %s", update_dir);
+ return (R_PROCESS);
+}
+
+/*
+ * Print out a tag and its type
+ */
+static int
+tag_list_proc (p, closure)
+ Node *p;
+ void *closure;
+{
+ char *branch = NULL;
+
+ if (RCS_isbranch (xrcsnode, p->key))
+ branch = RCS_whatbranch(xrcsnode, p->key) ;
+
+ (void) printf ("\t%-25.25s\t(%s: %s)\n", p->key,
+ branch ? "branch" : "revision",
+ branch ? branch : p->data);
+
+ if (branch)
+ free (branch);
+
+ return (0);
+}
diff --git a/contrib/cvs/src/subr.c b/contrib/cvs/src/subr.c
new file mode 100644
index 0000000..8ed9177
--- /dev/null
+++ b/contrib/cvs/src/subr.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Various useful functions for the CVS support code.
+ */
+
+#include "cvs.h"
+
+extern char *getlogin ();
+
+/*
+ * malloc some data and die if it fails
+ */
+char *
+xmalloc (bytes)
+ size_t bytes;
+{
+ char *cp;
+
+ /* Parts of CVS try to xmalloc zero bytes and then free it. Some
+ systems have a malloc which returns NULL for zero byte
+ allocations but a free which can't handle NULL, so compensate. */
+ if (bytes == 0)
+ bytes = 1;
+
+ cp = malloc (bytes);
+ if (cp == NULL)
+ error (1, 0, "can not allocate %lu bytes", (unsigned long) bytes);
+ return (cp);
+}
+
+/*
+ * realloc data and die if it fails [I've always wanted to have "realloc" do
+ * a "malloc" if the argument is NULL, but you can't depend on it. Here, I
+ * can *force* it.
+ */
+void *
+xrealloc (ptr, bytes)
+ void *ptr;
+ size_t bytes;
+{
+ char *cp;
+
+ if (!ptr)
+ cp = malloc (bytes);
+ else
+ cp = realloc (ptr, bytes);
+
+ if (cp == NULL)
+ error (1, 0, "can not reallocate %lu bytes", (unsigned long) bytes);
+ return (cp);
+}
+
+/*
+ * Duplicate a string, calling xmalloc to allocate some dynamic space
+ */
+char *
+xstrdup (str)
+ const char *str;
+{
+ char *s;
+
+ if (str == NULL)
+ return ((char *) NULL);
+ s = xmalloc (strlen (str) + 1);
+ (void) strcpy (s, str);
+ return (s);
+}
+
+/* Remove trailing newlines from STRING, destructively. */
+void
+strip_trailing_newlines (str)
+ char *str;
+{
+ int len;
+ len = strlen (str) - 1;
+
+ while (str[len] == '\n')
+ str[len--] = '\0';
+}
+
+/*
+ * Recover the space allocated by Find_Names() and line2argv()
+ */
+void
+free_names (pargc, argv)
+ int *pargc;
+ char **argv;
+{
+ register int i;
+
+ for (i = 0; i < *pargc; i++)
+ { /* only do through *pargc */
+ free (argv[i]);
+ }
+ *pargc = 0; /* and set it to zero when done */
+}
+
+/*
+ * Convert a line into argc/argv components and return the result in the
+ * arguments as passed. Use free_names() to return the memory allocated here
+ * back to the free pool.
+ */
+void
+line2argv (pargc, argv, line)
+ int *pargc;
+ char **argv;
+ char *line;
+{
+ char *cp;
+
+ *pargc = 0;
+ for (cp = strtok (line, " \t"); cp; cp = strtok ((char *) NULL, " \t"))
+ {
+ argv[*pargc] = xstrdup (cp);
+ (*pargc)++;
+ }
+}
+
+/*
+ * Returns the number of dots ('.') found in an RCS revision number
+ */
+int
+numdots (s)
+ const char *s;
+{
+ int dots = 0;
+
+ for (; *s; s++)
+ {
+ if (*s == '.')
+ dots++;
+ }
+ return (dots);
+}
+
+/*
+ * Get the caller's login from his uid. If the real uid is "root" try LOGNAME
+ * USER or getlogin(). If getlogin() and getpwuid() both fail, return
+ * the uid as a string.
+ */
+char *
+getcaller ()
+{
+ static char uidname[20];
+ struct passwd *pw;
+ char *name;
+ uid_t uid;
+
+ uid = getuid ();
+ if (uid == (uid_t) 0)
+ {
+ /* super-user; try getlogin() to distinguish */
+ if (((name = getlogin ()) || (name = getenv("LOGNAME")) ||
+ (name = getenv("USER"))) && *name)
+ return (name);
+ }
+ if ((pw = (struct passwd *) getpwuid (uid)) == NULL)
+ {
+ (void) sprintf (uidname, "uid%lu", (unsigned long) uid);
+ return (uidname);
+ }
+ return (pw->pw_name);
+}
+
+#ifdef lint
+#ifndef __GNUC__
+/* ARGSUSED */
+time_t
+get_date (date, now)
+ char *date;
+ struct timeb *now;
+{
+ time_t foo = 0;
+
+ return (foo);
+}
+#endif
+#endif
+
+/* Given two revisions, find their greatest common ancestor. If the
+ two input revisions exist, then rcs guarantees that the gca will
+ exist. */
+
+char *
+gca (rev1, rev2)
+ char *rev1;
+ char *rev2;
+{
+ int dots;
+ char gca[PATH_MAX];
+ char *p[2];
+ int j[2];
+
+ if (rev1 == NULL || rev2 == NULL)
+ {
+ error (0, 0, "sanity failure in gca");
+ abort();
+ }
+
+ /* walk the strings, reading the common parts. */
+ gca[0] = '\0';
+ p[0] = rev1;
+ p[1] = rev2;
+ do
+ {
+ int i;
+ char c[2];
+ char *s[2];
+
+ for (i = 0; i < 2; ++i)
+ {
+ /* swap out the dot */
+ s[i] = strchr (p[i], '.');
+ if (s[i] != NULL) {
+ c[i] = *s[i];
+ }
+
+ /* read an int */
+ j[i] = atoi (p[i]);
+
+ /* swap back the dot... */
+ if (s[i] != NULL) {
+ *s[i] = c[i];
+ p[i] = s[i] + 1;
+ }
+ else
+ {
+ /* or mark us at the end */
+ p[i] = NULL;
+ }
+
+ }
+
+ /* use the lowest. */
+ (void) sprintf (gca + strlen (gca), "%d.",
+ j[0] < j[1] ? j[0] : j[1]);
+
+ } while (j[0] == j[1]
+ && p[0] != NULL
+ && p[1] != NULL);
+
+ /* back up over that last dot. */
+ gca[strlen(gca) - 1] = '\0';
+
+ /* numbers differ, or we ran out of strings. we're done with the
+ common parts. */
+
+ dots = numdots (gca);
+ if (dots == 0)
+ {
+ /* revisions differ in trunk major number. */
+
+ char *q;
+ char *s;
+
+ s = (j[0] < j[1]) ? p[0] : p[1];
+
+ if (s == NULL)
+ {
+ /* we only got one number. this is strange. */
+ error (0, 0, "bad revisions %s or %s", rev1, rev2);
+ abort();
+ }
+ else
+ {
+ /* we have a minor number. use it. */
+ q = gca + strlen (gca);
+
+ *q++ = '.';
+ for ( ; *s != '.' && *s != '\0'; )
+ *q++ = *s++;
+
+ *q = '\0';
+ }
+ }
+ else if ((dots & 1) == 0)
+ {
+ /* if we have an even number of dots, then we have a branch.
+ remove the last number in order to make it a revision. */
+
+ char *s;
+
+ s = strrchr(gca, '.');
+ *s = '\0';
+ }
+
+ return (xstrdup (gca));
+}
+
+/*
+ * Sanity checks and any required fix-up on message passed to RCS via '-m'.
+ * RCS 5.7 requires that a non-total-whitespace, non-null message be provided
+ * with '-m'. Returns the original argument or a pointer to readonly
+ * static storage.
+ */
+char *
+make_message_rcslegal (message)
+ char *message;
+{
+ if ((message == NULL) || (*message == '\0') || isspace (*message))
+ {
+ char *t;
+
+ if (message)
+ for (t = message; *t; t++)
+ if (!isspace (*t))
+ return message;
+
+ return "*** empty log message ***\n";
+ }
+
+ return message;
+}
diff --git a/contrib/cvs/src/tag.c b/contrib/cvs/src/tag.c
new file mode 100644
index 0000000..2e30009
--- /dev/null
+++ b/contrib/cvs/src/tag.c
@@ -0,0 +1,781 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * Tag
+ *
+ * Add or delete a symbolic name to an RCS file, or a collection of RCS files.
+ * Uses the checked out revision in the current directory.
+ */
+
+#include "cvs.h"
+#include "savecwd.h"
+
+static int check_fileproc PROTO((struct file_info *finfo));
+static int check_filesdoneproc PROTO((int err, char *repos, char *update_dir));
+static int pretag_proc PROTO((char *repository, char *filter));
+static void masterlist_delproc PROTO((Node *p));
+static void tag_delproc PROTO((Node *p));
+static int pretag_list_proc PROTO((Node *p, void *closure));
+
+static Dtype tag_dirproc PROTO((char *dir, char *repos, char *update_dir));
+static int tag_fileproc PROTO((struct file_info *finfo));
+
+static char *numtag;
+static char *date = NULL;
+static char *symtag;
+static int delete_flag; /* adding a tag by default */
+static int branch_mode; /* make an automagic "branch" tag */
+static int local; /* recursive by default */
+static int force_tag_match = 1; /* force tag to match by default */
+static int force_tag_move; /* don't force tag to move by default */
+
+struct tag_info
+{
+ Ctype status;
+ char *rev;
+ char *tag;
+ char *options;
+};
+
+struct master_lists
+{
+ List *tlist;
+};
+
+static List *mtlist;
+static List *tlist;
+
+static const char *const tag_usage[] =
+{
+ "Usage: %s %s [-lRF] [-b] [-d] [-r tag|-D date] tag [files...]\n",
+ "\t-l\tLocal directory only, not recursive.\n",
+ "\t-R\tProcess directories recursively.\n",
+ "\t-d\tDelete the given Tag.\n",
+ "\t-[rD]\tExisting tag or date.\n",
+ "\t-f\tForce a head revision if tag etc not found.\n",
+ "\t-b\tMake the tag a \"branch\" tag, allowing concurrent development.\n",
+ "\t-F\tMove tag if it already exists\n",
+ NULL
+};
+
+int
+tag (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int err = 0;
+
+ if (argc == -1)
+ usage (tag_usage);
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "FQqlRdr:D:bf")) != -1)
+ {
+ switch (c)
+ {
+ case 'Q':
+ case 'q':
+#ifdef SERVER_SUPPORT
+ /* The CVS 1.5 client sends these options (in addition to
+ Global_option requests), so we must ignore them. */
+ if (!server_active)
+#endif
+ error (1, 0,
+ "-q or -Q must be specified before \"%s\"",
+ command_name);
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'd':
+ delete_flag = 1;
+ break;
+ case 'r':
+ numtag = optarg;
+ break;
+ case 'D':
+ if (date)
+ free (date);
+ date = Make_Date (optarg);
+ break;
+ case 'f':
+ force_tag_match = 0;
+ break;
+ case 'b':
+ branch_mode = 1;
+ break;
+ case 'F':
+ force_tag_move = 1;
+ break;
+ case '?':
+ default:
+ usage (tag_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0)
+ usage (tag_usage);
+ symtag = argv[0];
+ argc--;
+ argv++;
+
+ if (date && numtag)
+ error (1, 0, "-r and -D options are mutually exclusive");
+ if (delete_flag && branch_mode)
+ error (0, 0, "warning: -b ignored with -d options");
+ RCS_check_tag (symtag);
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ /* We're the client side. Fire up the remote server. */
+ start_server ();
+
+ ign_setup ();
+
+ if (local)
+ send_arg("-l");
+ if (delete_flag)
+ send_arg("-d");
+ if (branch_mode)
+ send_arg("-b");
+ if (force_tag_move)
+ send_arg("-F");
+
+ if (numtag)
+ option_with_arg ("-r", numtag);
+ if (date)
+ client_senddate (date);
+
+ send_arg (symtag);
+
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* FIXME: We shouldn't have to send current files, but I'm not sure
+ whether it works. So send the files --
+ it's slower but it works. */
+ send_files (argc, argv, local, 0);
+ send_to_server ("tag\012", 0);
+ return get_responses_and_close ();
+ }
+#endif
+
+ if (numtag != NULL)
+ tag_check_valid (numtag, argc, argv, local, 0, "");
+
+ /* check to make sure they are authorized to tag all the
+ specified files in the repository */
+
+ mtlist = getlist();
+ err = start_recursion (check_fileproc, check_filesdoneproc,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ argc, argv, local, W_LOCAL, 0, 1,
+ (char *) NULL, 1, 0);
+
+ if (err)
+ {
+ error (1, 0, "correct the above errors first!");
+ }
+
+ /* start the recursion processor */
+ err = start_recursion (tag_fileproc, (FILESDONEPROC) NULL, tag_dirproc,
+ (DIRLEAVEPROC) NULL, argc, argv, local,
+ W_LOCAL, 0, 1, (char *) NULL, 1, 0);
+ dellist(&mtlist);
+ return (err);
+}
+
+/* check file that is to be tagged */
+/* All we do here is add it to our list */
+
+static int
+check_fileproc (finfo)
+ struct file_info *finfo;
+{
+ char *xdir;
+ Node *p;
+ Vers_TS *vers;
+
+ if (finfo->update_dir[0] == '\0')
+ xdir = ".";
+ else
+ xdir = finfo->update_dir;
+ if ((p = findnode (mtlist, xdir)) != NULL)
+ {
+ tlist = ((struct master_lists *) p->data)->tlist;
+ }
+ else
+ {
+ struct master_lists *ml;
+
+ tlist = getlist ();
+ p = getnode ();
+ p->key = xstrdup (xdir);
+ p->type = UPDATE;
+ ml = (struct master_lists *)
+ xmalloc (sizeof (struct master_lists));
+ ml->tlist = tlist;
+ p->data = (char *) ml;
+ p->delproc = masterlist_delproc;
+ (void) addnode (mtlist, p);
+ }
+ /* do tlist */
+ p = getnode ();
+ p->key = xstrdup (finfo->file);
+ p->type = UPDATE;
+ p->delproc = tag_delproc;
+ vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL,
+ (char *) NULL, finfo->file, 0, 0,
+ finfo->entries, finfo->rcs);
+ if (vers->srcfile == NULL)
+ {
+ if (!really_quiet)
+ error (0, 0, "nothing known about %s", finfo->file);
+ return (1);
+ }
+ p->data = RCS_getversion(vers->srcfile, numtag, date, force_tag_match, 0);
+ if (p->data != NULL)
+ {
+ int addit = 1;
+ char *oversion;
+
+ oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1, 0);
+ if (oversion == NULL)
+ {
+ if (delete_flag)
+ {
+ addit = 0;
+ }
+ }
+ else if (strcmp(oversion, p->data) == 0)
+ {
+ addit = 0;
+ }
+ else if (!force_tag_move)
+ {
+ addit = 0;
+ }
+ if (oversion != NULL)
+ {
+ free(oversion);
+ }
+ if (!addit)
+ {
+ free(p->data);
+ p->data = NULL;
+ }
+ }
+ freevers_ts(&vers);
+ (void) addnode (tlist, p);
+ return (0);
+}
+
+static int
+check_filesdoneproc(err, repos, update_dir)
+ int err;
+ char *repos;
+ char *update_dir;
+{
+ int n;
+ Node *p;
+
+ p = findnode(mtlist, update_dir);
+ if (p != NULL)
+ {
+ tlist = ((struct master_lists *) p->data)->tlist;
+ }
+ else
+ {
+ tlist = (List *) NULL;
+ }
+ if ((tlist == NULL) || (tlist->list->next == tlist->list))
+ {
+ return (err);
+ }
+ if ((n = Parse_Info(CVSROOTADM_TAGINFO, repos, pretag_proc, 1)) > 0)
+ {
+ error (0, 0, "Pre-tag check failed");
+ err += n;
+ }
+ return (err);
+}
+
+static int
+pretag_proc(repository, filter)
+ char *repository;
+ char *filter;
+{
+ if (filter[0] == '/')
+ {
+ char *s, *cp;
+
+ s = xstrdup(filter);
+ for (cp=s; *cp; cp++)
+ {
+ if (isspace(*cp))
+ {
+ *cp = '\0';
+ break;
+ }
+ }
+ if (!isfile(s))
+ {
+ error (0, errno, "cannot find pre-tag filter '%s'", s);
+ free(s);
+ return (1);
+ }
+ free(s);
+ }
+ run_setup("%s %s %s %s",
+ filter,
+ symtag,
+ delete_flag ? "del" : force_tag_move ? "mov" : "add",
+ repository);
+ walklist(tlist, pretag_list_proc, NULL);
+ return (run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY));
+}
+
+static void
+masterlist_delproc(p)
+ Node *p;
+{
+ struct master_lists *ml;
+
+ ml = (struct master_lists *)p->data;
+ dellist(&ml->tlist);
+ free(ml);
+ return;
+}
+
+static void
+tag_delproc(p)
+ Node *p;
+{
+ if (p->data != NULL)
+ {
+ free(p->data);
+ p->data = NULL;
+ }
+ return;
+}
+
+static int
+pretag_list_proc(p, closure)
+ Node *p;
+ void *closure;
+{
+ if (p->data != NULL)
+ {
+ run_arg(p->key);
+ run_arg(p->data);
+ }
+ return (0);
+}
+
+
+/*
+ * Called to tag a particular file (the currently checked out version is
+ * tagged with the specified tag - or the specified tag is deleted).
+ */
+/* ARGSUSED */
+static int
+tag_fileproc (finfo)
+ struct file_info *finfo;
+{
+ char *version, *oversion;
+ char *nversion = NULL;
+ char *rev;
+ Vers_TS *vers;
+ int retcode = 0;
+
+ vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL,
+ finfo->file, 0, 0, finfo->entries, finfo->rcs);
+
+ if ((numtag != NULL) || (date != NULL))
+ {
+ nversion = RCS_getversion(vers->srcfile,
+ numtag,
+ date,
+ force_tag_match, 0);
+ if (nversion == NULL)
+ {
+ freevers_ts (&vers);
+ return (0);
+ }
+ }
+ if (delete_flag)
+ {
+
+ /*
+ * If -d is specified, "force_tag_match" is set, so that this call to
+ * RCS_getversion() will return a NULL version string if the symbolic
+ * tag does not exist in the RCS file.
+ *
+ * This is done here because it's MUCH faster than just blindly calling
+ * "rcs" to remove the tag... trust me.
+ */
+
+ version = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1, 0);
+ if (version == NULL || vers->srcfile == NULL)
+ {
+ freevers_ts (&vers);
+ return (0);
+ }
+ free (version);
+
+ if ((retcode = RCS_deltag(vers->srcfile->path, symtag, 1)) != 0)
+ {
+ if (!quiet)
+ error (0, retcode == -1 ? errno : 0,
+ "failed to remove tag %s from %s", symtag,
+ vers->srcfile->path);
+ freevers_ts (&vers);
+ return (1);
+ }
+
+ /* warm fuzzies */
+ if (!really_quiet)
+ {
+ (void) printf ("D %s\n", finfo->fullname);
+ }
+
+ freevers_ts (&vers);
+ return (0);
+ }
+
+ /*
+ * If we are adding a tag, we need to know which version we have checked
+ * out and we'll tag that version.
+ */
+ if (nversion == NULL)
+ {
+ version = vers->vn_user;
+ }
+ else
+ {
+ version = nversion;
+ }
+ if (version == NULL)
+ {
+ freevers_ts (&vers);
+ return (0);
+ }
+ else if (strcmp (version, "0") == 0)
+ {
+ if (!quiet)
+ error (0, 0, "couldn't tag added but un-commited file `%s'", finfo->file);
+ freevers_ts (&vers);
+ return (0);
+ }
+ else if (version[0] == '-')
+ {
+ if (!quiet)
+ error (0, 0, "skipping removed but un-commited file `%s'", finfo->file);
+ freevers_ts (&vers);
+ return (0);
+ }
+ else if (vers->srcfile == NULL)
+ {
+ if (!quiet)
+ error (0, 0, "cannot find revision control file for `%s'", finfo->file);
+ freevers_ts (&vers);
+ return (0);
+ }
+
+ /*
+ * As an enhancement for the case where a tag is being re-applied to a
+ * large number of files, make one extra call to RCS_getversion to see
+ * if the tag is already set in the RCS file. If so, check to see if it
+ * needs to be moved. If not, do nothing. This will likely save a lot of
+ * time when simply moving the tag to the "current" head revisions of a
+ * module -- which I have found to be a typical tagging operation.
+ */
+ rev = branch_mode ? RCS_magicrev (vers->srcfile, version) : version;
+ oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1, 0);
+ if (oversion != NULL)
+ {
+ int isbranch = RCS_isbranch (finfo->rcs, symtag);
+
+ /*
+ * if versions the same and neither old or new are branches don't have
+ * to do anything
+ */
+ if (strcmp (version, oversion) == 0 && !branch_mode && !isbranch)
+ {
+ free (oversion);
+ freevers_ts (&vers);
+ return (0);
+ }
+
+ if (!force_tag_move)
+ {
+ /* we're NOT going to move the tag */
+ (void) printf ("W %s", finfo->fullname);
+
+ (void) printf (" : %s already exists on %s %s",
+ symtag, isbranch ? "branch" : "version", oversion);
+ (void) printf (" : NOT MOVING tag to %s %s\n",
+ branch_mode ? "branch" : "version", rev);
+ free (oversion);
+ freevers_ts (&vers);
+ return (0);
+ }
+ free (oversion);
+ }
+
+ if ((retcode = RCS_settag(vers->srcfile->path, symtag, rev)) != 0)
+ {
+ error (1, retcode == -1 ? errno : 0,
+ "failed to set tag %s to revision %s in %s",
+ symtag, rev, vers->srcfile->path);
+ freevers_ts (&vers);
+ return (1);
+ }
+
+ /* more warm fuzzies */
+ if (!really_quiet)
+ {
+ (void) printf ("T %s\n", finfo->fullname);
+ }
+
+ if (nversion != NULL)
+ {
+ free (nversion);
+ }
+ freevers_ts (&vers);
+ return (0);
+}
+
+/*
+ * Print a warm fuzzy message
+ */
+/* ARGSUSED */
+static Dtype
+tag_dirproc (dir, repos, update_dir)
+ char *dir;
+ char *repos;
+ char *update_dir;
+{
+ if (!quiet)
+ error (0, 0, "%s %s", delete_flag ? "Untagging" : "Tagging", update_dir);
+ return (R_PROCESS);
+}
+
+/* Code relating to the val-tags file. Note that this file has no way
+ of knowing when a tag has been deleted. The problem is that there
+ is no way of knowing whether a tag still exists somewhere, when we
+ delete it some places. Using per-directory val-tags files (in
+ CVSREP) might be better, but that might slow down the process of
+ verifying that a tag is correct (maybe not, for the likely cases,
+ if carefully done), and/or be harder to implement correctly. */
+
+struct val_args {
+ char *name;
+ int found;
+};
+
+/* Pass as a static until we get around to fixing start_recursion to pass along
+ a void * where we can stash it. */
+static struct val_args *val_args_static;
+
+static int val_fileproc PROTO ((struct file_info *finfo));
+
+static int
+val_fileproc (finfo)
+ struct file_info *finfo;
+{
+ RCSNode *rcsdata;
+ struct val_args *args = val_args_static;
+ char *tag;
+
+ if ((rcsdata = finfo->rcs) == NULL)
+ /* Not sure this can happen, after all we passed only
+ W_REPOS | W_ATTIC. */
+ return 0;
+
+ tag = RCS_gettag (rcsdata, args->name, 1, 0);
+ if (tag != NULL)
+ {
+ /* FIXME: should find out a way to stop the search at this point. */
+ args->found = 1;
+ free (tag);
+ }
+ return 0;
+}
+
+static Dtype val_direntproc PROTO ((char *, char *, char *));
+
+static Dtype
+val_direntproc (dir, repository, update_dir)
+ char *dir;
+ char *repository;
+ char *update_dir;
+{
+ /* This is not quite right--it doesn't get right the case of "cvs
+ update -d -r foobar" where foobar is a tag which exists only in
+ files in a directory which does not exist yet, but which is
+ about to be created. */
+ if (isdir (dir))
+ return 0;
+ return R_SKIP_ALL;
+}
+
+/* Check to see whether NAME is a valid tag. If so, return. If not
+ print an error message and exit. ARGC, ARGV, LOCAL, and AFLAG specify
+ which files we will be operating on.
+
+ REPOSITORY is the repository if we need to cd into it, or NULL if
+ we are already there, or "" if we should do a W_LOCAL recursion.
+ Sorry for three cases, but the "" case is needed in case the
+ working directories come from diverse parts of the repository, the
+ NULL case avoids an unneccesary chdir, and the non-NULL, non-""
+ case is needed for checkout, where we don't want to chdir if the
+ tag is found in CVSROOTADM_VALTAGS, but there is not (yet) any
+ local directory. */
+void
+tag_check_valid (name, argc, argv, local, aflag, repository)
+ char *name;
+ int argc;
+ char **argv;
+ int local;
+ int aflag;
+ char *repository;
+{
+ DBM *db;
+ char *valtags_filename;
+ int err;
+ datum mytag;
+ struct val_args the_val_args;
+ struct saved_cwd cwd;
+ int which;
+
+ /* Numeric tags require only a syntactic check. */
+ if (isdigit (name[0]))
+ {
+ char *p;
+ for (p = name; *p != '\0'; ++p)
+ {
+ if (!(isdigit (*p) || *p == '.'))
+ error (1, 0, "\
+Numeric tag %s contains characters other than digits and '.'", name);
+ }
+ return;
+ }
+
+ mytag.dptr = name;
+ mytag.dsize = strlen (name);
+
+ valtags_filename = xmalloc (strlen (CVSroot) + sizeof CVSROOTADM
+ + sizeof CVSROOTADM_HISTORY + 20);
+ strcpy (valtags_filename, CVSroot);
+ strcat (valtags_filename, "/");
+ strcat (valtags_filename, CVSROOTADM);
+ strcat (valtags_filename, "/");
+ strcat (valtags_filename, CVSROOTADM_VALTAGS);
+ db = dbm_open (valtags_filename, O_RDWR, 0666);
+ if (db == NULL)
+ {
+ if (!existence_error (errno))
+ error (1, errno, "cannot read %s", valtags_filename);
+
+ /* If the file merely fails to exist, we just keep going and create
+ it later if need be. */
+ }
+ else
+ {
+ datum val;
+
+ val = dbm_fetch (db, mytag);
+ if (val.dptr != NULL)
+ {
+ /* Found. The tag is valid. */
+ dbm_close (db);
+ free (valtags_filename);
+ return;
+ }
+ /* FIXME: should check errors somehow (add dbm_error to myndbm.c?). */
+ }
+
+ /* We didn't find the tag in val-tags, so look through all the RCS files
+ to see whether it exists there. Yes, this is expensive, but there
+ is no other way to cope with a tag which might have been created
+ by an old version of CVS, from before val-tags was invented. */
+
+ the_val_args.name = name;
+ the_val_args.found = 0;
+ val_args_static = &the_val_args;
+
+ which = W_REPOS | W_ATTIC;
+
+ if (repository != NULL)
+ {
+ if (repository[0] == '\0')
+ which |= W_LOCAL;
+ else
+ {
+ if (save_cwd (&cwd))
+ exit (EXIT_FAILURE);
+ if (chdir (repository) < 0)
+ error (1, errno, "cannot change to %s directory", repository);
+ }
+ }
+
+ err = start_recursion (val_fileproc, (FILESDONEPROC) NULL,
+ val_direntproc, (DIRLEAVEPROC) NULL,
+ argc, argv, local, which, aflag,
+ 1, NULL, 1, 0);
+ if (repository != NULL && repository[0] != '\0')
+ {
+ if (restore_cwd (&cwd, NULL))
+ exit (EXIT_FAILURE);
+ free_cwd (&cwd);
+ }
+
+ if (!the_val_args.found)
+ error (1, 0, "no such tag %s", name);
+ else
+ {
+ /* The tags is valid but not mentioned in val-tags. Add it. */
+ datum value;
+
+ if (noexec)
+ {
+ if (db != NULL)
+ dbm_close (db);
+ free (valtags_filename);
+ return;
+ }
+
+ if (db == NULL)
+ {
+ mode_t omask;
+ omask = umask (cvsumask);
+ db = dbm_open (valtags_filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
+ (void) umask (omask);
+
+ if (db == NULL)
+ {
+ error (0, errno, "cannot create %s", valtags_filename);
+ free (valtags_filename);
+ return;
+ }
+ }
+ value.dptr = "y";
+ value.dsize = 1;
+ if (dbm_store (db, mytag, value, DBM_REPLACE) < 0)
+ error (0, errno, "cannot store %s into %s", name,
+ valtags_filename);
+ dbm_close (db);
+ }
+ free (valtags_filename);
+}
diff --git a/contrib/cvs/src/update.c b/contrib/cvs/src/update.c
new file mode 100644
index 0000000..2478316
--- /dev/null
+++ b/contrib/cvs/src/update.c
@@ -0,0 +1,1830 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ *
+ * "update" updates the version in the present directory with respect to the RCS
+ * repository. The present version must have been created by "checkout". The
+ * user can keep up-to-date by calling "update" whenever he feels like it.
+ *
+ * The present version can be committed by "commit", but this keeps the version
+ * in tact.
+ *
+ * Arguments following the options are taken to be file names to be updated,
+ * rather than updating the entire directory.
+ *
+ * Modified or non-existent RCS files are checked out and reported as U
+ * <user_file>
+ *
+ * Modified user files are reported as M <user_file>. If both the RCS file and
+ * the user file have been modified, the user file is replaced by the result
+ * of rcsmerge, and a backup file is written for the user in .#file.version.
+ * If this throws up irreconcilable differences, the file is reported as C
+ * <user_file>, and as M <user_file> otherwise.
+ *
+ * Files added but not yet committed are reported as A <user_file>. Files
+ * removed but not yet committed are reported as R <user_file>.
+ *
+ * If the current directory contains subdirectories that hold concurrent
+ * versions, these are updated too. If the -d option was specified, new
+ * directories added to the repository are automatically created and updated
+ * as well.
+ */
+
+#include "cvs.h"
+#ifdef SERVER_SUPPORT
+#include "md5.h"
+#endif
+#include "watch.h"
+#include "fileattr.h"
+#include "edit.h"
+
+static int checkout_file PROTO((char *file, char *repository, List *entries,
+ RCSNode *rcsnode, Vers_TS *vers_ts, char *update_dir));
+#ifdef SERVER_SUPPORT
+static int patch_file PROTO((char *file, char *repository, List *entries,
+ RCSNode*rcsnode, Vers_TS *vers_ts, char *update_dir,
+ int *docheckout, struct stat *file_info,
+ unsigned char *checksum));
+#endif
+static int isemptydir PROTO((char *dir));
+static int merge_file PROTO((char *file, char *repository, List *entries,
+ Vers_TS *vers, char *update_dir));
+static int scratch_file PROTO((char *file, char *repository, List * entries,
+ char *update_dir));
+static Dtype update_dirent_proc PROTO((char *dir, char *repository, char *update_dir));
+static int update_dirleave_proc PROTO((char *dir, int err, char *update_dir));
+static int update_fileproc PROTO ((struct file_info *));
+static int update_filesdone_proc PROTO((int err, char *repository,
+ char *update_dir));
+static int write_letter PROTO((char *file, int letter, char *update_dir));
+#ifdef SERVER_SUPPORT
+static void join_file PROTO((char *file, RCSNode *rcsnode, Vers_TS *vers_ts,
+ char *update_dir, List *entries, char *repository));
+#else
+static void join_file PROTO((char *file, RCSNode *rcsnode, Vers_TS *vers_ts,
+ char *update_dir, List *entries));
+#endif
+
+static char *options = NULL;
+static char *tag = NULL;
+static char *date = NULL;
+static char *join_rev1, *date_rev1;
+static char *join_rev2, *date_rev2;
+static int aflag = 0;
+static int force_tag_match = 1;
+static int update_build_dirs = 0;
+static int update_prune_dirs = 0;
+static int pipeout = 0;
+#ifdef SERVER_SUPPORT
+static int patches = 0;
+#endif
+static List *ignlist = (List *) NULL;
+static time_t last_register_time;
+static const char *const update_usage[] =
+{
+ "Usage: %s %s [-APdflRp] [-k kopt] [-r rev|-D date] [-j rev]\n",
+ " [-I ign] [-W spec] [files...]\n",
+ "\t-A\tReset any sticky tags/date/kopts.\n",
+ "\t-P\tPrune empty directories.\n",
+ "\t-d\tBuild directories, like checkout does.\n",
+ "\t-f\tForce a head revision match if tag/date not found.\n",
+ "\t-l\tLocal directory only, no recursion.\n",
+ "\t-R\tProcess directories recursively.\n",
+ "\t-p\tSend updates to standard output.\n",
+ "\t-k kopt\tUse RCS kopt -k option on checkout.\n",
+ "\t-r rev\tUpdate using specified revision/tag.\n",
+ "\t-D date\tSet date to update from.\n",
+ "\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",
+ NULL
+};
+
+/*
+ * update is the argv,argc based front end for arg parsing
+ */
+int
+update (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c, err;
+ int local = 0; /* recursive by default */
+ int which; /* where to look for files and dirs */
+
+ if (argc == -1)
+ usage (update_usage);
+
+ ign_setup ();
+ wrap_setup ();
+
+ /* parse the args */
+ optind = 1;
+ while ((c = getopt (argc, argv, "ApPflRQqduk:r:D:j:I:W:")) != -1)
+ {
+ switch (c)
+ {
+ case 'A':
+ aflag = 1;
+ break;
+ case 'I':
+ ign_add (optarg, 0);
+ break;
+ case 'W':
+ wrap_add (optarg, 0);
+ break;
+ case 'k':
+ if (options)
+ free (options);
+ options = RCS_check_kflag (optarg);
+ break;
+ case 'l':
+ local = 1;
+ break;
+ case 'R':
+ local = 0;
+ break;
+ case 'Q':
+ case 'q':
+#ifdef SERVER_SUPPORT
+ /* The CVS 1.5 client sends these options (in addition to
+ Global_option requests), so we must ignore them. */
+ if (!server_active)
+#endif
+ error (1, 0,
+ "-q or -Q must be specified before \"%s\"",
+ command_name);
+ break;
+ case 'd':
+ update_build_dirs = 1;
+ break;
+ case 'f':
+ force_tag_match = 0;
+ break;
+ case 'r':
+ tag = optarg;
+ break;
+ case 'D':
+ date = Make_Date (optarg);
+ break;
+ case 'P':
+ update_prune_dirs = 1;
+ break;
+ case 'p':
+ pipeout = 1;
+ noexec = 1; /* so no locks will be created */
+ break;
+ case 'j':
+ if (join_rev2)
+ error (1, 0, "only two -j options can be specified");
+ if (join_rev1)
+ join_rev2 = optarg;
+ else
+ join_rev1 = optarg;
+ break;
+ case 'u':
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ patches = 1;
+ else
+#endif
+ usage (update_usage);
+ break;
+ case '?':
+ default:
+ usage (update_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ /* The first pass does the regular update. If we receive at least
+ one patch which failed, we do a second pass and just fetch
+ those files whose patches failed. */
+ do
+ {
+ int status;
+
+ start_server ();
+
+ if (local)
+ send_arg("-l");
+ if (update_build_dirs)
+ send_arg("-d");
+ if (pipeout)
+ send_arg("-p");
+ if (!force_tag_match)
+ send_arg("-f");
+ if (aflag)
+ send_arg("-A");
+ if (update_prune_dirs)
+ send_arg("-P");
+ client_prune_dirs = update_prune_dirs;
+ option_with_arg ("-r", tag);
+ if (date)
+ client_senddate (date);
+ if (join_rev1)
+ option_with_arg ("-j", join_rev1);
+ if (join_rev2)
+ option_with_arg ("-j", join_rev2);
+
+ /* 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. */
+ if (failed_patches == NULL)
+ {
+ struct request *rq;
+
+ for (rq = requests; rq->name != NULL; rq++)
+ {
+ if (strcmp (rq->name, "update-patches") == 0)
+ {
+ if (rq->status == rq_supported)
+ {
+ send_arg("-u");
+ }
+ break;
+ }
+ }
+ }
+
+ if (failed_patches == NULL)
+ {
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ send_files (argc, argv, local, aflag);
+ }
+ else
+ {
+ int i;
+
+ (void) printf ("%s client: refetching unpatchable files\n",
+ program_name);
+
+ if (toplevel_wd[0] != '\0'
+ && chdir (toplevel_wd) < 0)
+ {
+ error (1, errno, "could not chdir to %s", toplevel_wd);
+ }
+
+ for (i = 0; i < failed_patches_count; i++)
+ (void) unlink_file (failed_patches[i]);
+ send_file_names (failed_patches_count, failed_patches, 0);
+ send_files (failed_patches_count, failed_patches, local,
+ aflag);
+ }
+
+ failed_patches = NULL;
+ failed_patches_count = 0;
+
+ send_to_server ("update\012", 0);
+
+ status = get_responses_and_close ();
+ if (status != 0)
+ return status;
+
+ } while (failed_patches != NULL);
+
+ return 0;
+ }
+#endif
+
+ if (tag != NULL)
+ tag_check_valid (tag, argc, argv, local, aflag, "");
+ /* FIXME: We don't call tag_check_valid on join_rev1 and join_rev2
+ yet (make sure to handle ':' correctly if we do, though). */
+
+ /*
+ * If we are updating the entire directory (for real) and building dirs
+ * as we go, we make sure there is no static entries file and write the
+ * tag file as appropriate
+ */
+ if (argc <= 0 && !pipeout)
+ {
+ if (update_build_dirs)
+ {
+ if (unlink_file (CVSADM_ENTSTAT) < 0 && ! existence_error (errno))
+ error (1, errno, "cannot remove file %s", CVSADM_ENTSTAT);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_clear_entstat (".", Name_Repository (NULL, NULL));
+#endif
+ }
+
+ /* keep the CVS/Tag file current with the specified arguments */
+ if (aflag || tag || date)
+ {
+ WriteTag ((char *) NULL, tag, date);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_sticky (".", Name_Repository (NULL, NULL), tag, date);
+#endif
+ }
+ }
+
+ /* look for files/dirs locally and in the repository */
+ which = W_LOCAL | W_REPOS;
+
+ /* look in the attic too if a tag or date is specified */
+ if (tag != NULL || date != NULL || joining())
+ which |= W_ATTIC;
+
+ /* call the command line interface */
+ err = do_update (argc, argv, options, tag, date, force_tag_match,
+ local, update_build_dirs, aflag, update_prune_dirs,
+ pipeout, which, join_rev1, join_rev2, (char *) NULL);
+
+ /* free the space Make_Date allocated if necessary */
+ if (date != NULL)
+ free (date);
+
+ return (err);
+}
+
+/*
+ * Command line interface to update (used by checkout)
+ */
+int
+do_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag,
+ xprune, xpipeout, which, xjoin_rev1, xjoin_rev2, preload_update_dir)
+ int argc;
+ char **argv;
+ char *xoptions;
+ char *xtag;
+ char *xdate;
+ int xforce;
+ int local;
+ int xbuild;
+ int xaflag;
+ int xprune;
+ int xpipeout;
+ int which;
+ char *xjoin_rev1;
+ char *xjoin_rev2;
+ char *preload_update_dir;
+{
+ int err = 0;
+ char *cp;
+
+ /* fill in the statics */
+ options = xoptions;
+ tag = xtag;
+ date = xdate;
+ force_tag_match = xforce;
+ update_build_dirs = xbuild;
+ aflag = xaflag;
+ update_prune_dirs = xprune;
+ pipeout = xpipeout;
+
+ /* setup the join support */
+ join_rev1 = xjoin_rev1;
+ join_rev2 = xjoin_rev2;
+ if (join_rev1 && (cp = strchr (join_rev1, ':')) != NULL)
+ {
+ *cp++ = '\0';
+ date_rev1 = Make_Date (cp);
+ }
+ else
+ date_rev1 = (char *) NULL;
+ if (join_rev2 && (cp = strchr (join_rev2, ':')) != NULL)
+ {
+ *cp++ = '\0';
+ date_rev2 = Make_Date (cp);
+ }
+ else
+ date_rev2 = (char *) NULL;
+
+ /* call the recursion processor */
+ err = start_recursion (update_fileproc, update_filesdone_proc,
+ update_dirent_proc, update_dirleave_proc,
+ argc, argv, local, which, aflag, 1,
+ preload_update_dir, 1, 0);
+
+ /* see if we need to sleep before returning */
+ if (last_register_time)
+ {
+ time_t now;
+
+ (void) time (&now);
+ if (now == last_register_time)
+ sleep (1); /* to avoid time-stamp races */
+ }
+
+ return (err);
+}
+
+/*
+ * This is the callback proc for update. It is called for each file in each
+ * directory by the recursion code. The current directory is the local
+ * instantiation. file is the file name we are to operate on. update_dir is
+ * set to the path relative to where we started (for pretty printing).
+ * repository is the repository. entries and srcfiles are the pre-parsed
+ * entries and source control files.
+ *
+ * This routine decides what needs to be done for each file and does the
+ * appropriate magic for checkout
+ */
+static int
+update_fileproc (finfo)
+ struct file_info *finfo;
+{
+ int retval;
+ Ctype status;
+ Vers_TS *vers;
+
+ status = Classify_File (finfo->file, tag, date, options, force_tag_match,
+ aflag, finfo->repository, finfo->entries, finfo->rcs, &vers,
+ finfo->update_dir, pipeout);
+ if (pipeout)
+ {
+ /*
+ * We just return success without doing anything if any of the really
+ * funky cases occur
+ *
+ * If there is still a valid RCS file, do a regular checkout type
+ * operation
+ */
+ switch (status)
+ {
+ case T_UNKNOWN: /* unknown file was explicitly asked
+ * about */
+ case T_REMOVE_ENTRY: /* needs to be un-registered */
+ case T_ADDED: /* added but not committed */
+ retval = 0;
+ break;
+ case T_CONFLICT: /* old punt-type errors */
+ retval = 1;
+ break;
+ case T_UPTODATE: /* file was already up-to-date */
+ case T_NEEDS_MERGE: /* needs merging */
+ case T_MODIFIED: /* locally modified */
+ case T_REMOVED: /* removed but not committed */
+ case T_CHECKOUT: /* needs checkout */
+#ifdef SERVER_SUPPORT
+ case T_PATCH: /* needs patch */
+#endif
+ retval = checkout_file (finfo->file, finfo->repository, finfo->entries, finfo->rcs,
+ vers, finfo->update_dir);
+ break;
+
+ default: /* can't ever happen :-) */
+ error (0, 0,
+ "unknown file status %d for file %s", status, finfo->file);
+ retval = 0;
+ break;
+ }
+ }
+ else
+ {
+ switch (status)
+ {
+ case T_UNKNOWN: /* unknown file was explicitly asked
+ * about */
+ case T_UPTODATE: /* file was already up-to-date */
+ retval = 0;
+ break;
+ case T_CONFLICT: /* old punt-type errors */
+ retval = 1;
+ (void) write_letter (finfo->file, 'C', finfo->update_dir);
+ break;
+ case T_NEEDS_MERGE: /* needs merging */
+ if (noexec)
+ {
+ retval = 1;
+ (void) write_letter (finfo->file, 'C', finfo->update_dir);
+ }
+ else
+ {
+ if (wrap_merge_is_copy (finfo->file))
+ /* Should we be warning the user that we are
+ * overwriting the user's copy of the file? */
+ retval = checkout_file (finfo->file, finfo->repository, finfo->entries,
+ finfo->rcs, vers, finfo->update_dir);
+ else
+ retval = merge_file (finfo->file, finfo->repository, finfo->entries,
+ vers, finfo->update_dir);
+ }
+ break;
+ case T_MODIFIED: /* locally modified */
+ retval = 0;
+ if (vers->ts_conflict)
+ {
+ char *filestamp;
+ int retcode;
+
+ /*
+ * If the timestamp has changed and no conflict indicators
+ * are found, it isn't a 'C' any more.
+ */
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ retcode = vers->ts_conflict[0] != '=';
+ else {
+ filestamp = time_stamp (finfo->file);
+ retcode = strcmp (vers->ts_conflict, filestamp);
+ free (filestamp);
+ }
+#else
+ filestamp = time_stamp (finfo->file);
+ retcode = strcmp (vers->ts_conflict, filestamp);
+ free (filestamp);
+#endif
+
+ if (retcode)
+ {
+ /*
+ * If the timestamps differ, look for Conflict
+ * indicators to see if 'C' anyway.
+ */
+ run_setup ("%s", GREP);
+ run_arg (RCS_MERGE_PAT);
+ run_arg (finfo->file);
+ retcode = run_exec (RUN_TTY, DEVNULL,
+ RUN_TTY,RUN_NORMAL);
+ if (retcode == -1)
+ {
+ error (1, errno,
+ "fork failed while examining conflict in `%s'",
+ finfo->fullname);
+ }
+ }
+ if (!retcode)
+ {
+ (void) write_letter (finfo->file, 'C', finfo->update_dir);
+ retval = 1;
+ }
+ else
+ {
+ /* Reregister to clear conflict flag. */
+ Register (finfo->entries, finfo->file, vers->vn_rcs, vers->ts_rcs,
+ vers->options, vers->tag,
+ vers->date, (char *)0);
+ }
+ }
+ if (!retval)
+ retval = write_letter (finfo->file, 'M', finfo->update_dir);
+ break;
+#ifdef SERVER_SUPPORT
+ case T_PATCH: /* needs patch */
+ if (patches)
+ {
+ int docheckout;
+ struct stat file_info;
+ unsigned char checksum[16];
+
+ retval = patch_file (finfo->file, finfo->repository, finfo->entries, finfo->rcs,
+ vers, finfo->update_dir, &docheckout,
+ &file_info, checksum);
+ if (! docheckout)
+ {
+ if (server_active && retval == 0)
+ server_updated (finfo->file, finfo->update_dir, finfo->repository,
+ SERVER_PATCHED, &file_info,
+ checksum);
+ 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). */
+ /* Fall through. */
+#endif
+ case T_CHECKOUT: /* needs checkout */
+ retval = checkout_file (finfo->file, finfo->repository, finfo->entries, finfo->rcs,
+ vers, finfo->update_dir);
+#ifdef SERVER_SUPPORT
+ if (server_active && retval == 0)
+ server_updated (finfo->file, finfo->update_dir, finfo->repository,
+ SERVER_UPDATED, (struct stat *) NULL,
+ (unsigned char *) NULL);
+#endif
+ break;
+ case T_ADDED: /* added but not committed */
+ retval = write_letter (finfo->file, 'A', finfo->update_dir);
+ break;
+ case T_REMOVED: /* removed but not committed */
+ retval = write_letter (finfo->file, 'R', finfo->update_dir);
+ break;
+ case T_REMOVE_ENTRY: /* needs to be un-registered */
+ retval = scratch_file (finfo->file, finfo->repository, finfo->entries, finfo->update_dir);
+#ifdef SERVER_SUPPORT
+ if (server_active && retval == 0)
+ server_updated (finfo->file, finfo->update_dir, finfo->repository,
+ SERVER_UPDATED, (struct stat *) NULL,
+ (unsigned char *) NULL);
+#endif
+ break;
+ default: /* can't ever happen :-) */
+ error (0, 0,
+ "unknown file status %d for file %s", status, finfo->file);
+ retval = 0;
+ break;
+ }
+ }
+
+ /* only try to join if things have gone well thus far */
+ if (retval == 0 && join_rev1)
+#ifdef SERVER_SUPPORT
+ join_file (finfo->file, finfo->rcs, vers, finfo->update_dir, finfo->entries, finfo->repository);
+#else
+ join_file (finfo->file, finfo->rcs, vers, finfo->update_dir, finfo->entries);
+#endif
+
+ /* if this directory has an ignore list, add this file to it */
+ if (ignlist)
+ {
+ Node *p;
+
+ p = getnode ();
+ p->type = FILES;
+ p->key = xstrdup (finfo->file);
+ if (addnode (ignlist, p) != 0)
+ freenode (p);
+ }
+
+ freevers_ts (&vers);
+ return (retval);
+}
+
+static void update_ignproc PROTO ((char *, char *));
+
+static void
+update_ignproc (file, dir)
+ char *file;
+ char *dir;
+{
+ (void) write_letter (file, '?', dir);
+}
+
+/* ARGSUSED */
+static int
+update_filesdone_proc (err, repository, update_dir)
+ int err;
+ char *repository;
+ char *update_dir;
+{
+ /* if this directory has an ignore list, process it then free it */
+ if (ignlist)
+ {
+ ignore_files (ignlist, update_dir, update_ignproc);
+ dellist (&ignlist);
+ }
+
+ /* Clean up CVS admin dirs if we are export */
+ if (strcmp (command_name, "export") == 0)
+ {
+ /* I'm not sure the existence_error is actually possible (except
+ in cases where we really should print a message), but since
+ this code used to ignore all errors, I'll play it safe. */
+ if (unlink_file_dir (CVSADM) < 0 && !existence_error (errno))
+ error (0, errno, "cannot remove %s directory", CVSADM);
+ }
+#ifdef SERVER_SUPPORT
+ else if (!server_active && !pipeout)
+#else
+ else if (!pipeout)
+#endif /* SERVER_SUPPORT */
+ {
+ /* If there is no CVS/Root file, add one */
+ if (!isfile (CVSADM_ROOT))
+ Create_Root( (char *) NULL, CVSroot );
+ }
+
+ return (err);
+}
+
+/*
+ * update_dirent_proc () is called back by the recursion processor before a
+ * sub-directory is processed for update. In this case, update_dirent proc
+ * will probably create the directory unless -d isn't specified and this is a
+ * new directory. A return code of 0 indicates the directory should be
+ * processed by the recursion code. A return of non-zero indicates the
+ * recursion code should skip this directory.
+ */
+static Dtype
+update_dirent_proc (dir, repository, update_dir)
+ char *dir;
+ char *repository;
+ char *update_dir;
+{
+ if (ignore_directory (update_dir))
+ {
+ /* print the warm fuzzy message */
+ if (!quiet)
+ error (0, 0, "Ignoring %s", update_dir);
+ return R_SKIP_ALL;
+ }
+
+ if (!isdir (dir))
+ {
+ /* if we aren't building dirs, blow it off */
+ if (!update_build_dirs)
+ return (R_SKIP_ALL);
+
+ if (noexec)
+ {
+ /* ignore the missing dir if -n is specified */
+ error (0, 0, "New directory `%s' -- ignored", dir);
+ return (R_SKIP_ALL);
+ }
+ else
+ {
+ /* otherwise, create the dir and appropriate adm files */
+ make_directory (dir);
+ Create_Admin (dir, update_dir, repository, tag, date);
+ }
+ }
+ /* Do we need to check noexec here? */
+ else if (!pipeout)
+ {
+ char *cvsadmdir;
+
+ /* The directory exists. Check to see if it has a CVS
+ subdirectory. */
+
+ cvsadmdir = xmalloc (strlen (dir) + 80);
+ strcpy (cvsadmdir, dir);
+ strcat (cvsadmdir, "/");
+ strcat (cvsadmdir, CVSADM);
+
+ if (!isdir (cvsadmdir))
+ {
+ /* We cannot successfully recurse into a directory without a CVS
+ subdirectory. Generally we will have already printed
+ "? foo". */
+ free (cvsadmdir);
+ return R_SKIP_ALL;
+ }
+ free (cvsadmdir);
+ }
+
+ /*
+ * If we are building dirs and not going to stdout, we make sure there is
+ * no static entries file and write the tag file as appropriate
+ */
+ if (!pipeout)
+ {
+ if (update_build_dirs)
+ {
+ char tmp[PATH_MAX];
+
+ (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENTSTAT);
+ if (unlink_file (tmp) < 0 && ! existence_error (errno))
+ error (1, errno, "cannot remove file %s", tmp);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_clear_entstat (update_dir, repository);
+#endif
+ }
+
+ /* keep the CVS/Tag file current with the specified arguments */
+ if (aflag || tag || date)
+ {
+ WriteTag (dir, tag, date);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_set_sticky (update_dir, repository, tag, date);
+#endif
+ }
+
+ /* initialize the ignore list for this directory */
+ ignlist = getlist ();
+ }
+
+ /* print the warm fuzzy message */
+ if (!quiet)
+ error (0, 0, "Updating %s", update_dir);
+
+ return (R_PROCESS);
+}
+
+/*
+ * update_dirleave_proc () is called back by the recursion code upon leaving
+ * a directory. It will prune empty directories if needed and will execute
+ * any appropriate update programs.
+ */
+/* ARGSUSED */
+static int
+update_dirleave_proc (dir, err, update_dir)
+ char *dir;
+ int err;
+ char *update_dir;
+{
+ FILE *fp;
+
+ /* run the update_prog if there is one */
+ if (err == 0 && !pipeout && !noexec &&
+ (fp = fopen (CVSADM_UPROG, "r")) != NULL)
+ {
+ char *cp;
+ char *repository;
+ char line[MAXLINELEN];
+
+ repository = Name_Repository ((char *) NULL, update_dir);
+ if (fgets (line, sizeof (line), fp) != NULL)
+ {
+ if ((cp = strrchr (line, '\n')) != NULL)
+ *cp = '\0';
+ run_setup ("%s %s", line, repository);
+ (void) printf ("%s %s: Executing '", program_name, command_name);
+ run_print (stdout);
+ (void) printf ("'\n");
+ (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
+ }
+ (void) fclose (fp);
+ free (repository);
+ }
+
+ /* FIXME: chdir ("..") loses with symlinks. */
+ /* Prune empty dirs on the way out - if necessary */
+ (void) chdir ("..");
+ if (update_prune_dirs && isemptydir (dir))
+ {
+ /* I'm not sure the existence_error is actually possible (except
+ in cases where we really should print a message), but since
+ this code used to ignore all errors, I'll play it safe. */
+ if (unlink_file_dir (dir) < 0 && !existence_error (errno))
+ error (0, errno, "cannot remove %s directory", dir);
+ }
+
+ return (err);
+}
+
+/*
+ * Returns 1 if the argument directory is completely empty, other than the
+ * existence of the CVS directory entry. Zero otherwise.
+ */
+static int
+isemptydir (dir)
+ char *dir;
+{
+ DIR *dirp;
+ struct dirent *dp;
+
+ if ((dirp = opendir (dir)) == NULL)
+ {
+ error (0, 0, "cannot open directory %s for empty check", dir);
+ return (0);
+ }
+ while ((dp = readdir (dirp)) != NULL)
+ {
+ if (strcmp (dp->d_name, ".") != 0 && strcmp (dp->d_name, "..") != 0 &&
+ strcmp (dp->d_name, CVSADM) != 0)
+ {
+ (void) closedir (dirp);
+ return (0);
+ }
+ }
+ (void) closedir (dirp);
+ return (1);
+}
+
+/*
+ * scratch the Entries file entry associated with a file
+ */
+static int
+scratch_file (file, repository, entries, update_dir)
+ char *file;
+ char *repository;
+ List *entries;
+ char *update_dir;
+{
+ history_write ('W', update_dir, "", file, repository);
+ Scratch_Entry (entries, file);
+ (void) unlink_file (file);
+ return (0);
+}
+
+/*
+ * check out a file - essentially returns the result of the fork on "co".
+ */
+static int
+checkout_file (file, repository, entries, rcsnode, vers_ts, update_dir)
+ char *file;
+ char *repository;
+ List *entries;
+ RCSNode *rcsnode;
+ Vers_TS *vers_ts;
+ char *update_dir;
+{
+ char backup[PATH_MAX];
+ int set_time, retval = 0;
+ int retcode = 0;
+ int status;
+ int file_is_dead;
+
+ /* don't screw with backup files if we're going to stdout */
+ if (!pipeout)
+ {
+ (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, file);
+ if (isfile (file))
+ rename_file (file, backup);
+ else
+ (void) unlink_file (backup);
+ }
+
+ file_is_dead = RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs);
+
+ if (!file_is_dead)
+ {
+ /*
+ * if we are checking out to stdout, print a nice message to
+ * stderr, and add the -p flag to the command */
+ if (pipeout)
+ {
+ if (!quiet)
+ {
+ (void) fprintf (stderr, "\
+===================================================================\n");
+ if (update_dir[0])
+ (void) fprintf (stderr, "Checking out %s/%s\n",
+ update_dir, file);
+ else
+ (void) fprintf (stderr, "Checking out %s\n", file);
+ (void) fprintf (stderr, "RCS: %s\n", vers_ts->srcfile->path);
+ (void) fprintf (stderr, "VERS: %s\n", vers_ts->vn_rcs);
+ (void) fprintf (stderr, "***************\n");
+ }
+ }
+
+ status = RCS_checkout (vers_ts->srcfile->path,
+ pipeout ? NULL : file, vers_ts->vn_tag,
+ vers_ts->options, RUN_TTY, 0, 0);
+ }
+ if (file_is_dead || status == 0)
+ {
+ if (!pipeout)
+ {
+ Vers_TS *xvers_ts;
+ int resurrecting;
+
+ resurrecting = 0;
+
+ if (file_is_dead && joining())
+ {
+ if (RCS_getversion (vers_ts->srcfile, join_rev1,
+ date_rev1, 1, 0)
+ || (join_rev2 != NULL &&
+ RCS_getversion (vers_ts->srcfile, join_rev2,
+ date_rev2, 1, 0)))
+ {
+ /* when joining, we need to get dead files checked
+ out. Try harder. */
+ /* I think that RCS_FLAGS_FORCE is here only because
+ passing -f to co used to enable checking out
+ a dead revision in the old version of death
+ support which used a hacked RCS instead of using
+ the RCS state. */
+ retcode = RCS_checkout (vers_ts->srcfile->path, file,
+ vers_ts->vn_rcs,
+ vers_ts->options, RUN_TTY,
+ RCS_FLAGS_FORCE, 0);
+ if (retcode != 0)
+ {
+ error (retcode == -1 ? 1 : 0,
+ retcode == -1 ? errno : 0,
+ "could not check out %s", file);
+ (void) unlink_file (backup);
+ return (retcode);
+ }
+ file_is_dead = 0;
+ resurrecting = 1;
+ }
+ else
+ {
+ /* If the file is dead and does not contain either of
+ the join revisions, then we don't want to check it
+ out. */
+ return 0;
+ }
+ }
+
+ if (cvswrite == TRUE
+ && !file_is_dead
+ && !fileattr_get (file, "_watched"))
+ xchmod (file, 1);
+
+ {
+ /* A newly checked out file is never under the spell
+ of "cvs edit". If we think we were editing it
+ from a previous life, clean up. Would be better to
+ check for same the working directory instead of
+ same user, but that is hairy. */
+
+ struct addremove_args args;
+
+ editor_set (file, getcaller (), NULL);
+
+ memset (&args, 0, sizeof args);
+ args.remove_temp = 1;
+ watch_modify_watchers (file, &args);
+ }
+
+ /* 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;
+
+ wrap_fromcvs_process_file (file);
+
+ xvers_ts = Version_TS (repository, options, tag, date, file,
+ force_tag_match, set_time, entries, rcsnode);
+ if (strcmp (xvers_ts->options, "-V4") == 0)
+ xvers_ts->options[0] = '\0';
+
+ (void) time (&last_register_time);
+
+ if (file_is_dead)
+ {
+ if (xvers_ts->vn_user != NULL)
+ {
+ if (update_dir[0] == '\0')
+ error (0, 0,
+ "warning: %s is not (any longer) pertinent",
+ file);
+ else
+ error (0, 0,
+ "warning: %s/%s is not (any longer) pertinent",
+ update_dir, file);
+ }
+ Scratch_Entry (entries, file);
+ if (unlink_file (file) < 0 && ! existence_error (errno))
+ {
+ if (update_dir[0] == '\0')
+ error (0, errno, "cannot remove %s", file);
+ else
+ error (0, errno, "cannot remove %s/%s", update_dir,
+ file);
+ }
+ }
+ else
+ Register (entries, file,
+ resurrecting ? "0" : xvers_ts->vn_rcs,
+ xvers_ts->ts_user, xvers_ts->options,
+ xvers_ts->tag, xvers_ts->date,
+ (char *)0); /* Clear conflict flag on fresh checkout */
+
+ /* fix up the vers structure, in case it is used by join */
+ if (join_rev1)
+ {
+ if (vers_ts->vn_user != NULL)
+ free (vers_ts->vn_user);
+ if (vers_ts->vn_rcs != NULL)
+ free (vers_ts->vn_rcs);
+ vers_ts->vn_user = xstrdup (xvers_ts->vn_rcs);
+ vers_ts->vn_rcs = xstrdup (xvers_ts->vn_rcs);
+ }
+
+ /* If this is really Update and not Checkout, recode history */
+ if (strcmp (command_name, "update") == 0)
+ history_write ('U', update_dir, xvers_ts->vn_rcs, file,
+ repository);
+
+ freevers_ts (&xvers_ts);
+
+ if (!really_quiet && !file_is_dead)
+ {
+ write_letter (file, 'U', update_dir);
+ }
+ }
+ }
+ else
+ {
+ int old_errno = errno; /* save errno value over the rename */
+
+ if (!pipeout && isfile (backup))
+ rename_file (backup, file);
+
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
+ "could not check out %s", file);
+
+ retval = retcode;
+ }
+
+ if (!pipeout)
+ (void) unlink_file (backup);
+
+ return (retval);
+}
+
+#ifdef SERVER_SUPPORT
+/* Patch a file. Runs rcsdiff. This is only done when running as the
+ * server. The hope is that the diff will be smaller than the file
+ * itself.
+ */
+static int
+patch_file (file, repository, entries, rcsnode, vers_ts, update_dir,
+ docheckout, file_info, checksum)
+ char *file;
+ char *repository;
+ List *entries;
+ RCSNode *rcsnode;
+ Vers_TS *vers_ts;
+ char *update_dir;
+ int *docheckout;
+ struct stat *file_info;
+ unsigned char *checksum;
+{
+ char backup[PATH_MAX];
+ char file1[PATH_MAX];
+ char file2[PATH_MAX];
+ int retval = 0;
+ int retcode = 0;
+ int fail;
+ FILE *e;
+
+ *docheckout = 0;
+
+ if (pipeout || joining ())
+ {
+ *docheckout = 1;
+ return 0;
+ }
+
+ (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, file);
+ if (isfile (file))
+ rename_file (file, backup);
+ else
+ (void) unlink_file (backup);
+
+ (void) sprintf (file1, "%s/%s%s-1", CVSADM, CVSPREFIX, file);
+ (void) sprintf (file2, "%s/%s%s-2", CVSADM, CVSPREFIX, file);
+
+ fail = 0;
+
+ /* We need to check out both revisions first, to see if either one
+ has a trailing newline. Because of this, we don't use rcsdiff,
+ but just use diff. */
+ if (noexec)
+ retcode = 0;
+ else
+ retcode = RCS_checkout (vers_ts->srcfile->path, NULL,
+ vers_ts->vn_user,
+ vers_ts->options, file1, 0, 0);
+ if (retcode != 0)
+ fail = 1;
+ else
+ {
+ e = fopen (file1, "r");
+ if (e == NULL)
+ fail = 1;
+ else
+ {
+ if (fseek (e, (long) -1, SEEK_END) == 0
+ && getc (e) != '\n')
+ {
+ fail = 1;
+ }
+ fclose (e);
+ }
+ }
+
+ if (! fail)
+ {
+ /* Check it out into file, and then move to file2, so that we
+ can get the right modes into *FILE_INFO. We can't check it
+ out directly into file2 because co doesn't understand how
+ to do that. */
+ retcode = RCS_checkout (vers_ts->srcfile->path, file,
+ vers_ts->vn_rcs,
+ vers_ts->options, RUN_TTY, 0, 0);
+ if (retcode != 0)
+ fail = 1;
+ else
+ {
+ if (!isreadable (file))
+ {
+ /* File is dead. */
+ fail = 1;
+ }
+ else
+ {
+ rename_file (file, file2);
+ if (cvswrite == TRUE
+ && !fileattr_get (file, "_watched"))
+ xchmod (file2, 1);
+ e = fopen (file2, "r");
+ if (e == NULL)
+ fail = 1;
+ else
+ {
+ struct MD5Context context;
+ int nl;
+ unsigned char buf[8192];
+ unsigned len;
+
+ nl = 0;
+
+ /* Compute the MD5 checksum and make sure there is
+ a trailing newline. */
+ MD5Init (&context);
+ while ((len = fread (buf, 1, sizeof buf, e)) != 0)
+ {
+ nl = buf[len - 1] == '\n';
+ MD5Update (&context, buf, len);
+ }
+ MD5Final (checksum, &context);
+
+ if (ferror (e) || ! nl)
+ {
+ fail = 1;
+ }
+
+ fclose (e);
+ }
+ }
+ }
+ }
+
+ retcode = 0;
+ if (! fail)
+ {
+ /* FIXME: This whole thing with diff/patch is rather more
+ convoluted than necessary (lots of forks and execs, need to
+ worry about versions of diff and patch, etc.). Also, we
+ send context lines which aren't needed (in the rare case in
+ which the diff doesn't apply, the checksum would catches it).
+ Solution perhaps is to librarify the RCS routines which apply
+ deltas or something equivalent. */
+ /* This is -c, not -u, because we have no way of knowing which
+ DIFF is in use. */
+ run_setup ("%s -c %s %s", DIFF, file1, file2);
+
+ /* A retcode of 0 means no differences. 1 means some differences. */
+ if ((retcode = run_exec (RUN_TTY, file, RUN_TTY, RUN_NORMAL)) != 0
+ && retcode != 1)
+ {
+ fail = 1;
+ }
+ else
+ {
+#define BINARY "Binary"
+ char buf[sizeof BINARY];
+ unsigned int c;
+
+ /* Check the diff output to make sure patch will be handle it. */
+ e = fopen (file, "r");
+ if (e == NULL)
+ error (1, errno, "could not open diff output file %s", file);
+ c = fread (buf, 1, sizeof BINARY - 1, e);
+ buf[c] = '\0';
+ if (strcmp (buf, BINARY) == 0)
+ {
+ /* These are binary files. We could use diff -a, but
+ patch can't handle that. */
+ fail = 1;
+ }
+ fclose (e);
+ }
+ }
+
+ if (! fail)
+ {
+ Vers_TS *xvers_ts;
+
+ /* This stuff is just copied blindly from checkout_file. I
+ don't really know what it does. */
+ xvers_ts = Version_TS (repository, options, tag, date, file,
+ force_tag_match, 0, entries, rcsnode);
+ if (strcmp (xvers_ts->options, "-V4") == 0)
+ xvers_ts->options[0] = '\0';
+
+ Register (entries, file, xvers_ts->vn_rcs,
+ xvers_ts->ts_user, xvers_ts->options,
+ xvers_ts->tag, xvers_ts->date, NULL);
+
+ if (stat (file2, file_info) < 0)
+ error (1, errno, "could not stat %s", file2);
+
+ /* If this is really Update and not Checkout, recode history */
+ if (strcmp (command_name, "update") == 0)
+ history_write ('P', update_dir, xvers_ts->vn_rcs, file,
+ repository);
+
+ freevers_ts (&xvers_ts);
+
+ if (!really_quiet)
+ {
+ write_letter (file, 'P', update_dir);
+ }
+ }
+ else
+ {
+ int old_errno = errno; /* save errno value over the rename */
+
+ if (isfile (backup))
+ rename_file (backup, file);
+
+ if (retcode != 0 && retcode != 1)
+ error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
+ "could not diff %s", file);
+
+ *docheckout = 1;
+ retval = retcode;
+ }
+
+ (void) unlink_file (backup);
+ (void) unlink_file (file1);
+ (void) unlink_file (file2);
+
+ return (retval);
+}
+#endif
+
+/*
+ * 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;
+ int letter;
+ char *update_dir;
+{
+ if (!really_quiet)
+ {
+ char buf[2];
+ buf[0] = letter;
+ buf[1] = ' ';
+ cvs_output (buf, 2);
+ if (update_dir[0])
+ {
+ cvs_output (update_dir, 0);
+ cvs_output ("/", 1);
+ }
+ cvs_output (file, 0);
+ cvs_output ("\n", 1);
+ }
+ return (0);
+}
+
+/*
+ * Do all the magic associated with a file which needs to be merged
+ */
+static int
+merge_file (file, repository, entries, vers, update_dir)
+ char *file;
+ char *repository;
+ List *entries;
+ Vers_TS *vers;
+ char *update_dir;
+{
+ char user[PATH_MAX];
+ char backup[PATH_MAX];
+ int status;
+ int retcode = 0;
+
+ /*
+ * The users currently modified file is moved to a backup file name
+ * ".#filename.version", so that it will stay around for a few days
+ * before being automatically removed by some cron daemon. The "version"
+ * is the version of the file that the user was most up-to-date with
+ * before the merge.
+ */
+ (void) sprintf (backup, "%s%s.%s", BAKPREFIX, file, vers->vn_user);
+ if (update_dir[0])
+ (void) sprintf (user, "%s/%s", update_dir, file);
+ else
+ (void) strcpy (user, file);
+
+ (void) unlink_file (backup);
+ copy_file (file, backup);
+ xchmod (file, 1);
+
+ status = RCS_merge(vers->srcfile->path,
+ vers->options, vers->vn_user, vers->vn_rcs);
+ if (status != 0 && status != 1)
+ {
+ error (0, status == -1 ? errno : 0,
+ "could not merge revision %s of %s", vers->vn_user, user);
+ error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
+ user, backup);
+ rename_file (backup, file);
+ return (1);
+ }
+
+ if (strcmp (vers->options, "-V4") == 0)
+ vers->options[0] = '\0';
+ (void) time (&last_register_time);
+ {
+ char *cp = 0;
+
+ if (status)
+ cp = time_stamp (file);
+ Register (entries, file, vers->vn_rcs, vers->ts_rcs, vers->options,
+ vers->tag, vers->date, cp);
+ if (cp)
+ free (cp);
+ }
+
+ /* fix up the vers structure, in case it is used by join */
+ if (join_rev1)
+ {
+ if (vers->vn_user != NULL)
+ free (vers->vn_user);
+ vers->vn_user = xstrdup (vers->vn_rcs);
+ }
+
+#ifdef SERVER_SUPPORT
+ /* Send the new contents of the file before the message. If we
+ wanted to be totally correct, we would have the client write
+ the message only after the file has safely been written. */
+ if (server_active)
+ {
+ server_copy_file (file, update_dir, repository, backup);
+ server_updated (file, update_dir, repository, SERVER_MERGED,
+ (struct stat *) NULL, (unsigned char *) NULL);
+ }
+#endif
+
+ if (!noexec && !xcmp (backup, file))
+ {
+ printf ("%s already contains the differences between %s and %s\n",
+ user, vers->vn_user, vers->vn_rcs);
+ history_write ('G', update_dir, vers->vn_rcs, file, repository);
+ return (0);
+ }
+
+ if (status == 1)
+ {
+ if (!noexec)
+ error (0, 0, "conflicts found in %s", user);
+
+ write_letter (file, 'C', update_dir);
+
+ history_write ('C', update_dir, vers->vn_rcs, file, repository);
+
+ }
+ else if (retcode == -1)
+ {
+ error (1, errno, "fork failed while examining update of %s", user);
+ }
+ else
+ {
+ write_letter (file, 'M', update_dir);
+ history_write ('G', update_dir, vers->vn_rcs, file, repository);
+ }
+ return (0);
+}
+
+/*
+ * Do all the magic associated with a file which needs to be joined
+ * (-j option)
+ */
+static void
+#ifdef SERVER_SUPPORT
+join_file (file, rcsnode, vers, update_dir, entries, repository)
+ char *repository;
+#else
+join_file (file, rcsnode, vers, update_dir, entries)
+#endif
+ char *file;
+ RCSNode *rcsnode;
+ Vers_TS *vers;
+ char *update_dir;
+ List *entries;
+{
+ char user[PATH_MAX];
+ char backup[PATH_MAX];
+ char *options;
+ int status;
+
+ char *rev1;
+ char *rev2;
+ char *jrev1;
+ char *jrev2;
+ char *jdate1;
+ char *jdate2;
+
+ jrev1 = join_rev1;
+ jrev2 = join_rev2;
+ jdate1 = date_rev1;
+ jdate2 = date_rev2;
+
+ if (wrap_merge_is_copy (file))
+ {
+ /* FIXME: Should be including update_dir in message. */
+ error (0, 0,
+ "Cannot merge %s because it is a merge-by-copy file.", file);
+ return;
+ }
+
+ /* determine if we need to do anything at all */
+ if (vers->srcfile == NULL ||
+ vers->srcfile->path == NULL)
+ {
+ return;
+ }
+
+ /* in all cases, use two revs. */
+
+ /* if only one rev is specified, it becomes the second rev */
+ if (jrev2 == NULL)
+ {
+ jrev2 = jrev1;
+ jrev1 = NULL;
+ jdate2 = jdate1;
+ jdate1 = NULL;
+ }
+
+ /* The file in the working directory doesn't exist in CVS/Entries.
+ FIXME: Shouldn't this case result in additional processing (if
+ the file was added going from rev1 to rev2, then do the equivalent
+ of a "cvs add")? (yes; easier said than done.. :-) */
+ if (vers->vn_user == NULL)
+ {
+ /* No merge possible YET. */
+ if (jdate2 != NULL)
+ error (0, 0,
+ "file %s is present in revision %s as of %s",
+ file, jrev2, jdate2);
+ else
+ error (0, 0,
+ "file %s is present in revision %s",
+ file, jrev2);
+ return;
+ }
+
+ /* Fix for bug CVS/193:
+ * Used to dump core if the file had been removed on the current branch.
+ */
+ if (strcmp(vers->vn_user, "0") == 0)
+ {
+ error(0, 0,
+ "file %s has been deleted",
+ file);
+ return;
+ }
+
+ /* convert the second rev spec, walking branches and dates. */
+
+ rev2 = RCS_getversion (vers->srcfile, jrev2, jdate2, 1, 0);
+ if (rev2 == NULL)
+ {
+ if (!quiet)
+ {
+ if (jdate2 != NULL)
+ error (0, 0,
+ "cannot find revision %s as of %s in file %s",
+ jrev2, jdate2, file);
+ else
+ error (0, 0,
+ "cannot find revision %s in file %s",
+ jrev2, file);
+ }
+ return;
+ }
+
+ /* skip joining identical revs */
+ if (strcmp (rev2, vers->vn_user) == 0)
+ {
+ /* No merge necessary. */
+ free (rev2);
+ return;
+ }
+
+ if (jrev1 == NULL)
+ {
+ char *tst;
+ /* if the first rev is missing, then it is implied to be the
+ greatest common ancestor of both the join rev, and the
+ checked out rev. */
+
+ /* FIXME: What is this check for '!' about? If it is legal to
+ have '!' in the first character of vn_user, it isn't
+ documented at struct vers_ts in cvs.h. */
+ tst = vers->vn_user;
+ if (*tst == '!')
+ {
+ /* file was dead. merge anyway and pretend it's been
+ added. */
+ ++tst;
+ Register (entries, file, "0", vers->ts_user, vers->options,
+ vers->tag, (char *) 0, (char *) 0);
+ }
+ rev1 = gca (tst, rev2);
+ if (rev1 == NULL)
+ {
+ /* this should not be possible */
+ error (0, 0, "bad gca");
+ abort();
+ }
+
+ tst = RCS_gettag (vers->srcfile, rev2, 1, 0);
+ if (tst == NULL)
+ {
+ /* this should not be possible. */
+ error (0, 0, "cannot find gca");
+ abort();
+ }
+
+ free (tst);
+
+ /* these two cases are noops */
+ if (strcmp (rev1, rev2) == 0)
+ {
+ free (rev1);
+ free (rev2);
+ return;
+ }
+ }
+ else
+ {
+ /* otherwise, convert the first rev spec, walking branches and
+ dates. */
+
+ rev1 = RCS_getversion (vers->srcfile, jrev1, jdate1, 1, 0);
+ if (rev1 == NULL)
+ {
+ if (!quiet) {
+ if (jdate1 != NULL)
+ error (0, 0,
+ "cannot find revision %s as of %s in file %s",
+ jrev1, jdate1, file);
+ else
+ error (0, 0,
+ "cannot find revision %s in file %s",
+ jrev1, file);
+ }
+ return;
+ }
+ }
+
+ /* do the join */
+
+#if 0
+ dome {
+ /* special handling when two revisions are specified */
+ if (join_rev1 && join_rev2)
+ {
+ rev = RCS_getversion (vers->srcfile, join_rev2, date_rev2, 1, 0);
+ if (rev == NULL)
+ {
+ if (!quiet && date_rev2 == NULL)
+ error (0, 0,
+ "cannot find revision %s in file %s", join_rev2, file);
+ return;
+ }
+
+ baserev = RCS_getversion (vers->srcfile, join_rev1, date_rev1, 1, 0);
+ if (baserev == NULL)
+ {
+ if (!quiet && date_rev1 == NULL)
+ error (0, 0,
+ "cannot find revision %s in file %s", join_rev1, file);
+ free (rev);
+ return;
+ }
+
+ /*
+ * nothing to do if:
+ * second revision matches our BASE revision (vn_user) &&
+ * both revisions are on the same branch
+ */
+ if (strcmp (vers->vn_user, rev) == 0 &&
+ numdots (baserev) == numdots (rev))
+ {
+ /* might be the same branch. take a real look */
+ char *dot = strrchr (baserev, '.');
+ int len = (dot - baserev) + 1;
+
+ if (strncmp (baserev, rev, len) == 0)
+ return;
+ }
+ }
+ else
+ {
+ rev = RCS_getversion (vers->srcfile, join_rev1, date_rev1, 1, 0);
+ if (rev == NULL)
+ return;
+ if (strcmp (rev, vers->vn_user) == 0) /* no merge necessary */
+ {
+ free (rev);
+ return;
+ }
+
+ baserev = RCS_whatbranch (file, join_rev1, rcsnode);
+ if (baserev)
+ {
+ char *cp;
+
+ /* we get a branch -- turn it into a revision, or NULL if trunk */
+ if ((cp = strrchr (baserev, '.')) == NULL)
+ {
+ free (baserev);
+ baserev = (char *) NULL;
+ }
+ else
+ *cp = '\0';
+ }
+ }
+ if (baserev && strcmp (baserev, rev) == 0)
+ {
+ /* they match -> nothing to do */
+ free (rev);
+ free (baserev);
+ return;
+ }
+ }
+#endif
+
+ /* OK, so we have two revisions; continue on */
+
+#ifdef SERVER_SUPPORT
+ if (server_active && !isreadable (file))
+ {
+ int retcode;
+ /* The file is up to date. Need to check out the current contents. */
+ retcode = RCS_checkout (vers->srcfile->path, "", vers->vn_user, NULL,
+ RUN_TTY, 0, 0);
+ if (retcode != 0)
+ error (1, retcode == -1 ? errno : 0,
+ "failed to check out %s file", file);
+ }
+#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
+ * before being automatically removed by some cron daemon. The "version"
+ * is the version of the file that the user was most up-to-date with
+ * before the merge.
+ */
+ (void) sprintf (backup, "%s%s.%s", BAKPREFIX, file, vers->vn_user);
+ if (update_dir[0])
+ (void) sprintf (user, "%s/%s", update_dir, file);
+ else
+ (void) strcpy (user, file);
+
+ (void) unlink_file (backup);
+ copy_file (file, backup);
+ xchmod (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 (status != 0 && status != 1)
+ {
+ error (0, status == -1 ? errno : 0,
+ "could not merge revision %s of %s", rev2, user);
+ error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
+ user, backup);
+ rename_file (backup, file);
+ }
+ free (rev1);
+ free (rev2);
+
+#ifdef SERVER_SUPPORT
+ /*
+ * If we're in server mode, then we need to re-register the file
+ * even if there were no conflicts (status == 0).
+ * This tells server_updated() to send the modified file back to
+ * the client.
+ */
+ if (status == 1 || (status == 0 && server_active))
+#else
+ if (status == 1)
+#endif
+ {
+ char *cp = 0;
+
+ if (status)
+ cp = time_stamp (file);
+ Register (entries, file, vers->vn_rcs, vers->ts_rcs, vers->options,
+ vers->tag, vers->date, cp);
+ if (cp)
+ free(cp);
+ }
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ server_copy_file (file, update_dir, repository, backup);
+ server_updated (file, update_dir, repository, SERVER_MERGED,
+ (struct stat *) NULL, (unsigned char *) NULL);
+ }
+#endif
+}
+
+int
+joining ()
+{
+ return (join_rev1 != NULL);
+}
diff --git a/contrib/cvs/src/update.h b/contrib/cvs/src/update.h
new file mode 100644
index 0000000..bad6562
--- /dev/null
+++ b/contrib/cvs/src/update.h
@@ -0,0 +1,21 @@
+/* Declarations for update.c.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+int do_update PROTO((int argc, char *argv[], char *xoptions, char *xtag,
+ char *xdate, int xforce, int local, int xbuild,
+ int xaflag, int xprune, int xpipeout, int which,
+ char *xjoin_rev1, char *xjoin_rev2, char *preload_update_dir));
+int joining PROTO((void));
diff --git a/contrib/cvs/src/vers_ts.c b/contrib/cvs/src/vers_ts.c
new file mode 100644
index 0000000..34983a1
--- /dev/null
+++ b/contrib/cvs/src/vers_ts.c
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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.
+ */
+
+#include "cvs.h"
+
+#ifdef SERVER_SUPPORT
+static void time_stamp_server PROTO((char *, Vers_TS *));
+#endif
+
+/*
+ * Fill in and return a Vers_TS structure "user" is the name of the local
+ * file; entries is the entries file - preparsed for our pleasure. rcs is
+ * the current source control file - preparsed for our pleasure.
+ */
+Vers_TS *
+Version_TS (repository, options, tag, date, user, force_tag_match,
+ set_time, entries, rcs)
+ char *repository;
+ char *options;
+ char *tag;
+ char *date;
+ char *user;
+ int force_tag_match;
+ int set_time;
+ List *entries;
+ RCSNode *rcs;
+{
+ Node *p;
+ RCSNode *rcsdata;
+ Vers_TS *vers_ts;
+ struct stickydirtag *sdtp;
+
+ /* get a new Vers_TS struct */
+ vers_ts = (Vers_TS *) xmalloc (sizeof (Vers_TS));
+ memset ((char *) vers_ts, 0, sizeof (*vers_ts));
+
+ /*
+ * look up the entries file entry and fill in the version and timestamp
+ * if entries is NULL, there is no entries file so don't bother trying to
+ * look it up (used by checkout -P)
+ */
+ if (entries == NULL)
+ {
+ sdtp = NULL;
+ p = NULL;
+ }
+ else
+ {
+ p = findnode_fn (entries, user);
+ sdtp = (struct stickydirtag *) entries->list->data; /* list-private */
+ }
+
+ if (p != NULL)
+ {
+ Entnode *entdata = (Entnode *) p->data;
+
+ vers_ts->vn_user = xstrdup (entdata->version);
+ vers_ts->ts_rcs = xstrdup (entdata->timestamp);
+ vers_ts->ts_conflict = xstrdup (entdata->conflict);
+ if (!tag)
+ {
+ if (!(sdtp && sdtp->aflag))
+ vers_ts->tag = xstrdup (entdata->tag);
+ }
+ if (!date)
+ {
+ if (!(sdtp && sdtp->aflag))
+ vers_ts->date = xstrdup (entdata->date);
+ }
+ if (!options || (options && *options == '\0'))
+ {
+ if (!(sdtp && sdtp->aflag))
+ vers_ts->options = xstrdup (entdata->options);
+ }
+ vers_ts->entdata = entdata;
+ }
+
+ /*
+ * -k options specified on the command line override (and overwrite)
+ * options stored in the entries file
+ */
+ if (options)
+ vers_ts->options = xstrdup (options);
+ else if (!vers_ts->options)
+ {
+ if (sdtp && sdtp->aflag == 0)
+ vers_ts->options = xstrdup (sdtp->options);
+ else if (rcs != NULL)
+ {
+ /* If no keyword expansion was specified on command line,
+ use whatever was in the rcs file (if there is one). This
+ is how we, if we are the server, tell the client whether
+ a file is binary. */
+ char *rcsexpand = RCS_getexpand (rcs);
+ if (rcsexpand != NULL)
+ {
+ vers_ts->options = xmalloc (strlen (rcsexpand) + 3);
+ strcpy (vers_ts->options, "-k");
+ strcat (vers_ts->options, rcsexpand);
+ }
+ }
+ }
+ if (!vers_ts->options)
+ vers_ts->options = xstrdup ("");
+
+ /*
+ * if tags were specified on the command line, they override what is in
+ * the Entries file
+ */
+ if (tag || date)
+ {
+ vers_ts->tag = xstrdup (tag);
+ vers_ts->date = xstrdup (date);
+ }
+ else if (!vers_ts->entdata && (sdtp && sdtp->aflag == 0))
+ {
+ if (!vers_ts->tag)
+ vers_ts->tag = xstrdup (sdtp->tag);
+ if (!vers_ts->date)
+ vers_ts->date = xstrdup (sdtp->date);
+ }
+
+ /* Now look up the info on the source controlled file */
+ if (rcs != NULL)
+ {
+ rcsdata = rcs;
+ rcsdata->refcount++;
+ }
+ else if (repository != NULL)
+ rcsdata = RCS_parse (user, repository);
+ else
+ rcsdata = NULL;
+
+ if (rcsdata != NULL)
+ {
+ /* squirrel away the rcsdata pointer for others */
+ vers_ts->srcfile = rcsdata;
+
+ if (vers_ts->tag && strcmp (vers_ts->tag, TAG_BASE) == 0)
+ {
+ vers_ts->vn_rcs = xstrdup (vers_ts->vn_user);
+ vers_ts->vn_tag = xstrdup (vers_ts->vn_user);
+ }
+ else
+ {
+ vers_ts->vn_rcs = RCS_getversion (rcsdata, vers_ts->tag,
+ vers_ts->date, force_tag_match, 1);
+ if (vers_ts->vn_rcs == NULL)
+ vers_ts->vn_tag = NULL;
+ else
+ {
+ char *colon = strchr (vers_ts->vn_rcs, ':');
+ if (colon)
+ {
+ vers_ts->vn_tag = xstrdup (colon+1);
+ *colon = '\0';
+ }
+ else
+ vers_ts->vn_tag = xstrdup (vers_ts->vn_rcs);
+ }
+ }
+
+ /*
+ * If the source control file exists and has the requested revision,
+ * get the Date the revision was checked in. If "user" exists, set
+ * its mtime.
+ */
+ if (set_time)
+ {
+ struct utimbuf t;
+
+ memset ((char *) &t, 0, sizeof (t));
+ if (vers_ts->vn_rcs &&
+ (t.actime = t.modtime = RCS_getrevtime (rcsdata,
+ vers_ts->vn_rcs, (char *) 0, 0)) != -1)
+ (void) utime (user, &t);
+ }
+ }
+
+ /* get user file time-stamp in ts_user */
+ if (entries != (List *) NULL)
+ {
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ time_stamp_server (user, vers_ts);
+ else
+#endif
+ vers_ts->ts_user = time_stamp (user);
+ }
+
+ return (vers_ts);
+}
+
+#ifdef SERVER_SUPPORT
+
+/* Set VERS_TS->TS_USER to time stamp for FILE. */
+
+/* Separate these out to keep the logic below clearer. */
+#define mark_lost(V) ((V)->ts_user = 0)
+#define mark_unchanged(V) ((V)->ts_user = xstrdup ((V)->ts_rcs))
+
+static void
+time_stamp_server (file, vers_ts)
+ char *file;
+ Vers_TS *vers_ts;
+{
+ struct stat sb;
+ char *cp;
+
+ if (stat (file, &sb) < 0)
+ {
+ if (! existence_error (errno))
+ error (1, errno, "cannot stat temp file");
+ if (use_unchanged)
+ {
+ /* Missing file means lost or unmodified; check entries
+ file to see which.
+
+ XXX FIXME - If there's no entries file line, we
+ wouldn't be getting the file at all, so consider it
+ lost. I don't know that that's right, but it's not
+ clear to me that either choice is. Besides, would we
+ have an RCS string in that case anyways? */
+ if (vers_ts->entdata == NULL)
+ mark_lost (vers_ts);
+ else if (vers_ts->entdata->timestamp
+ && vers_ts->entdata->timestamp[0] == '=')
+ mark_unchanged (vers_ts);
+ else
+ mark_lost (vers_ts);
+ }
+ else
+ {
+ /* Missing file in the temp directory means that the file
+ was not modified. */
+ mark_unchanged (vers_ts);
+ }
+ }
+ else if (sb.st_mtime == 0)
+ {
+ if (use_unchanged)
+ /* We shouldn't reach this case any more! */
+ abort ();
+
+ /* Special code used by server.c to indicate the file was lost. */
+ mark_lost (vers_ts);
+ }
+ else
+ {
+ struct tm *tm_p;
+ struct tm local_tm;
+
+ vers_ts->ts_user = xmalloc (25);
+ /* We want to use the same timestamp format as is stored in the
+ st_mtime. For unix (and NT I think) this *must* be universal
+ time (UT), so that files don't appear to be modified merely
+ because the timezone has changed. For VMS, or hopefully other
+ systems where gmtime returns NULL, the modification time is
+ stored in local time, and therefore it is not possible to cause
+ st_mtime to be out of sync by changing the timezone. */
+ tm_p = gmtime (&sb.st_mtime);
+ if (tm_p)
+ {
+ memcpy (&local_tm, tm_p, sizeof (local_tm));
+ cp = asctime (&local_tm); /* copy in the modify time */
+ }
+ else
+ cp = ctime (&sb.st_mtime);
+
+ cp[24] = 0;
+ (void) strcpy (vers_ts->ts_user, cp);
+ }
+}
+
+#endif /* SERVER_SUPPORT */
+/*
+ * Gets the time-stamp for the file "file" and returns it in space it
+ * allocates
+ */
+char *
+time_stamp (file)
+ char *file;
+{
+ struct stat sb;
+ char *cp;
+ char *ts;
+
+ if (stat (file, &sb) < 0)
+ {
+ ts = NULL;
+ }
+ else
+ {
+ struct tm *tm_p;
+ struct tm local_tm;
+ ts = xmalloc (25);
+ /* We want to use the same timestamp format as is stored in the
+ st_mtime. For unix (and NT I think) this *must* be universal
+ time (UT), so that files don't appear to be modified merely
+ because the timezone has changed. For VMS, or hopefully other
+ systems where gmtime returns NULL, the modification time is
+ stored in local time, and therefore it is not possible to cause
+ st_mtime to be out of sync by changing the timezone. */
+ tm_p = gmtime (&sb.st_mtime);
+ if (tm_p)
+ {
+ memcpy (&local_tm, tm_p, sizeof (local_tm));
+ cp = asctime (&local_tm); /* copy in the modify time */
+ }
+ else
+ cp = ctime(&sb.st_mtime);
+
+ cp[24] = 0;
+ (void) strcpy (ts, cp);
+ }
+
+ return (ts);
+}
+
+/*
+ * free up a Vers_TS struct
+ */
+void
+freevers_ts (versp)
+ Vers_TS **versp;
+{
+ if ((*versp)->srcfile)
+ freercsnode (&((*versp)->srcfile));
+ if ((*versp)->vn_user)
+ free ((*versp)->vn_user);
+ if ((*versp)->vn_rcs)
+ free ((*versp)->vn_rcs);
+ if ((*versp)->vn_tag)
+ free ((*versp)->vn_tag);
+ if ((*versp)->ts_user)
+ free ((*versp)->ts_user);
+ if ((*versp)->ts_rcs)
+ free ((*versp)->ts_rcs);
+ if ((*versp)->options)
+ free ((*versp)->options);
+ if ((*versp)->tag)
+ free ((*versp)->tag);
+ if ((*versp)->date)
+ free ((*versp)->date);
+ if ((*versp)->ts_conflict)
+ free ((*versp)->ts_conflict);
+ free ((char *) *versp);
+ *versp = (Vers_TS *) NULL;
+}
diff --git a/contrib/cvs/src/version.c b/contrib/cvs/src/version.c
new file mode 100644
index 0000000..4848e82
--- /dev/null
+++ b/contrib/cvs/src/version.c
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1994 david d `zoo' zuhn
+ * Copyright (c) 1994 Free Software Foundation, Inc.
+ * Copyright (c) 1992, Brian Berliner and Jeff Polk
+ * 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 this CVS source distribution.
+ *
+ * version.c - the CVS version number
+ */
+
+#include "cvs.h"
+
+char *version_string = "\nConcurrent Versions System (CVS) 1.8.1";
+
+#ifdef CLIENT_SUPPORT
+#ifdef SERVER_SUPPORT
+char *config_string = " (client/server)\n";
+#else
+char *config_string = " (client)\n";
+#endif
+#else
+#ifdef SERVER_SUPPORT
+char *config_string = " (server)\n";
+#else
+char *config_string = "\n";
+#endif
+#endif
diff --git a/contrib/cvs/src/watch.c b/contrib/cvs/src/watch.c
new file mode 100644
index 0000000..0873489
--- /dev/null
+++ b/contrib/cvs/src/watch.c
@@ -0,0 +1,521 @@
+/* Implementation for "cvs watch add", "cvs watchers", and related commands
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include "cvs.h"
+#include "edit.h"
+#include "fileattr.h"
+#include "watch.h"
+
+const char *const watch_usage[] =
+{
+ "Usage: %s %s [on|off|add|remove] [-l] [-a action] [files...]\n",
+ "on/off: turn on/off read-only checkouts of files\n",
+ "add/remove: add or remove notification on actions\n",
+ "-l (on/off/add/remove): Local directory only, not recursive\n",
+ "-a (add/remove): Specify what actions, one of\n",
+ " edit,unedit,commit,all,none\n",
+ NULL
+};
+
+static struct addremove_args the_args;
+
+void
+watch_modify_watchers (file, what)
+ char *file;
+ struct addremove_args *what;
+{
+ char *curattr = fileattr_get0 (file, "_watchers");
+ char *p;
+ char *pend;
+ char *nextp;
+ char *who;
+ int who_len;
+ char *mycurattr;
+ char *mynewattr;
+ size_t mynewattr_size;
+
+ int add_edit_pending;
+ int add_unedit_pending;
+ int add_commit_pending;
+ int remove_edit_pending;
+ int remove_unedit_pending;
+ int remove_commit_pending;
+ int add_tedit_pending;
+ int add_tunedit_pending;
+ int add_tcommit_pending;
+
+ who = getcaller ();
+ who_len = strlen (who);
+
+ /* Look for current watcher types for this user. */
+ mycurattr = NULL;
+ if (curattr != NULL)
+ {
+ p = curattr;
+ while (1) {
+ if (strncmp (who, p, who_len) == 0
+ && p[who_len] == '>')
+ {
+ /* Found this user. */
+ mycurattr = p + who_len + 1;
+ }
+ p = strchr (p, ',');
+ if (p == NULL)
+ break;
+ ++p;
+ }
+ }
+ if (mycurattr != NULL)
+ {
+ mycurattr = xstrdup (mycurattr);
+ p = strchr (mycurattr, ',');
+ if (p != NULL)
+ *p = '\0';
+ }
+
+ /* Now copy mycurattr to mynewattr, making the requisite modifications.
+ Note that we add a dummy '+' to the start of mynewattr, to reduce
+ special cases (but then we strip it off when we are done). */
+
+ mynewattr_size = sizeof "+edit+unedit+commit+tedit+tunedit+tcommit";
+ if (mycurattr != NULL)
+ mynewattr_size += strlen (mycurattr);
+ mynewattr = xmalloc (mynewattr_size);
+ mynewattr[0] = '\0';
+
+ add_edit_pending = what->adding && what->edit;
+ add_unedit_pending = what->adding && what->unedit;
+ add_commit_pending = what->adding && what->commit;
+ remove_edit_pending = !what->adding && what->edit;
+ remove_unedit_pending = !what->adding && what->unedit;
+ remove_commit_pending = !what->adding && what->commit;
+ add_tedit_pending = what->add_tedit;
+ add_tunedit_pending = what->add_tunedit;
+ add_tcommit_pending = what->add_tcommit;
+
+ /* Copy over existing watch types, except those to be removed. */
+ p = mycurattr;
+ while (p != NULL)
+ {
+ pend = strchr (p, '+');
+ if (pend == NULL)
+ {
+ pend = p + strlen (p);
+ nextp = NULL;
+ }
+ else
+ nextp = pend + 1;
+
+ /* Process this item. */
+ if (pend - p == 4 && strncmp ("edit", p, 4) == 0)
+ {
+ if (!remove_edit_pending)
+ strcat (mynewattr, "+edit");
+ add_edit_pending = 0;
+ }
+ else if (pend - p == 6 && strncmp ("unedit", p, 6) == 0)
+ {
+ if (!remove_unedit_pending)
+ strcat (mynewattr, "+unedit");
+ add_unedit_pending = 0;
+ }
+ else if (pend - p == 6 && strncmp ("commit", p, 6) == 0)
+ {
+ if (!remove_commit_pending)
+ strcat (mynewattr, "+commit");
+ add_commit_pending = 0;
+ }
+ else if (pend - p == 5 && strncmp ("tedit", p, 5) == 0)
+ {
+ if (!what->remove_temp)
+ strcat (mynewattr, "+tedit");
+ add_tedit_pending = 0;
+ }
+ else if (pend - p == 7 && strncmp ("tunedit", p, 7) == 0)
+ {
+ if (!what->remove_temp)
+ strcat (mynewattr, "+tunedit");
+ add_tunedit_pending = 0;
+ }
+ else if (pend - p == 7 && strncmp ("tcommit", p, 7) == 0)
+ {
+ if (!what->remove_temp)
+ strcat (mynewattr, "+tcommit");
+ add_tcommit_pending = 0;
+ }
+ else
+ {
+ char *mp;
+
+ /* Copy over any unrecognized watch types, for future
+ expansion. */
+ mp = mynewattr + strlen (mynewattr);
+ *mp++ = '+';
+ strncpy (mp, p, pend - p);
+ *(mp + (pend - p)) = '\0';
+ }
+
+ /* Set up for next item. */
+ p = nextp;
+ }
+
+ /* Add in new watch types. */
+ if (add_edit_pending)
+ strcat (mynewattr, "+edit");
+ if (add_unedit_pending)
+ strcat (mynewattr, "+unedit");
+ if (add_commit_pending)
+ strcat (mynewattr, "+commit");
+ if (add_tedit_pending)
+ strcat (mynewattr, "+tedit");
+ if (add_tunedit_pending)
+ strcat (mynewattr, "+tunedit");
+ if (add_tcommit_pending)
+ strcat (mynewattr, "+tcommit");
+
+ {
+ char *curattr_new;
+
+ curattr_new =
+ fileattr_modify (curattr,
+ who,
+ mynewattr[0] == '\0' ? NULL : mynewattr + 1,
+ '>',
+ ',');
+ /* If the attribute is unchanged, don't rewrite the attribute file. */
+ if (!((curattr_new == NULL && curattr == NULL)
+ || (curattr_new != NULL
+ && curattr != NULL
+ && strcmp (curattr_new, curattr) == 0)))
+ fileattr_set (file,
+ "_watchers",
+ curattr_new);
+ if (curattr_new != NULL)
+ free (curattr_new);
+ }
+
+ if (curattr != NULL)
+ free (curattr);
+ if (mycurattr != NULL)
+ free (mycurattr);
+ if (mynewattr != NULL)
+ free (mynewattr);
+}
+
+static int addremove_fileproc PROTO ((struct file_info *finfo));
+
+static int
+addremove_fileproc (finfo)
+ struct file_info *finfo;
+{
+ watch_modify_watchers (finfo->file, &the_args);
+ return 0;
+}
+
+static int addremove_filesdoneproc PROTO ((int, char *, char *));
+
+static int
+addremove_filesdoneproc (err, repository, update_dir)
+ int err;
+ char *repository;
+ char *update_dir;
+{
+ if (the_args.setting_default)
+ watch_modify_watchers (NULL, &the_args);
+ return err;
+}
+
+static int watch_addremove PROTO ((int argc, char **argv));
+
+static int
+watch_addremove (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int local = 0;
+ int err;
+ int a_omitted;
+
+ a_omitted = 1;
+ the_args.commit = 0;
+ the_args.edit = 0;
+ the_args.unedit = 0;
+ optind = 1;
+ while ((c = getopt (argc, argv, "la:")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case 'a':
+ a_omitted = 0;
+ if (strcmp (optarg, "edit") == 0)
+ the_args.edit = 1;
+ else if (strcmp (optarg, "unedit") == 0)
+ the_args.unedit = 1;
+ else if (strcmp (optarg, "commit") == 0)
+ the_args.commit = 1;
+ else if (strcmp (optarg, "all") == 0)
+ {
+ the_args.edit = 1;
+ the_args.unedit = 1;
+ the_args.commit = 1;
+ }
+ else if (strcmp (optarg, "none") == 0)
+ {
+ the_args.edit = 0;
+ the_args.unedit = 0;
+ the_args.commit = 0;
+ }
+ else
+ usage (watch_usage);
+ break;
+ case '?':
+ default:
+ usage (watch_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (a_omitted)
+ {
+ the_args.edit = 1;
+ the_args.unedit = 1;
+ the_args.commit = 1;
+ }
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ start_server ();
+ ign_setup ();
+
+ if (local)
+ send_arg ("-l");
+ /* FIXME: copes poorly with "all" if server is extended to have
+ new watch types and client is still running an old version. */
+ if (the_args.edit)
+ {
+ send_arg ("-a");
+ send_arg ("edit");
+ }
+ if (the_args.unedit)
+ {
+ send_arg ("-a");
+ send_arg ("unedit");
+ }
+ if (the_args.commit)
+ {
+ send_arg ("-a");
+ send_arg ("commit");
+ }
+ if (!the_args.edit && !the_args.unedit && !the_args.commit)
+ {
+ send_arg ("-a");
+ send_arg ("none");
+ }
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* FIXME: We shouldn't have to send current files, but I'm not sure
+ whether it works. So send the files --
+ it's slower but it works. */
+ send_files (argc, argv, local, 0);
+ send_to_server (the_args.adding ?
+ "watch-add\012" : "watch-remove\012",
+ 0);
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ the_args.setting_default = (argc <= 0);
+
+ lock_tree_for_write (argc, argv, local, 0);
+
+ err = start_recursion (addremove_fileproc, addremove_filesdoneproc,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
+ 1, 0);
+
+ lock_tree_cleanup ();
+ return err;
+}
+
+int
+watch_add (argc, argv)
+ int argc;
+ char **argv;
+{
+ the_args.adding = 1;
+ return watch_addremove (argc, argv);
+}
+
+int
+watch_remove (argc, argv)
+ int argc;
+ char **argv;
+{
+ the_args.adding = 0;
+ return watch_addremove (argc, argv);
+}
+
+int
+watch (argc, argv)
+ int argc;
+ char **argv;
+{
+ if (argc <= 1)
+ usage (watch_usage);
+ if (strcmp (argv[1], "on") == 0)
+ {
+ --argc;
+ ++argv;
+ return watch_on (argc, argv);
+ }
+ else if (strcmp (argv[1], "off") == 0)
+ {
+ --argc;
+ ++argv;
+ return watch_off (argc, argv);
+ }
+ else if (strcmp (argv[1], "add") == 0)
+ {
+ --argc;
+ ++argv;
+ return watch_add (argc, argv);
+ }
+ else if (strcmp (argv[1], "remove") == 0)
+ {
+ --argc;
+ ++argv;
+ return watch_remove (argc, argv);
+ }
+ else
+ usage (watch_usage);
+ return 0;
+}
+
+static const char *const watchers_usage[] =
+{
+ "Usage: %s %s [files...]\n",
+ NULL
+};
+
+static int watchers_fileproc PROTO ((struct file_info *finfo));
+
+static int
+watchers_fileproc (finfo)
+ struct file_info *finfo;
+{
+ char *them;
+ char *p;
+
+ them = fileattr_get0 (finfo->file, "_watchers");
+ if (them == NULL)
+ return 0;
+
+ fputs (finfo->fullname, stdout);
+
+ p = them;
+ while (1)
+ {
+ putc ('\t', stdout);
+ while (*p != '>' && *p != '\0')
+ putc (*p++, stdout);
+ if (*p == '\0')
+ {
+ /* Only happens if attribute is misformed. */
+ putc ('\n', stdout);
+ break;
+ }
+ ++p;
+ putc ('\t', stdout);
+ while (1)
+ {
+ while (*p != '+' && *p != ',' && *p != '\0')
+ putc (*p++, stdout);
+ if (*p == '\0')
+ {
+ putc ('\n', stdout);
+ goto out;
+ }
+ if (*p == ',')
+ {
+ ++p;
+ break;
+ }
+ ++p;
+ putc ('\t', stdout);
+ }
+ putc ('\n', stdout);
+ }
+ out:;
+ return 0;
+}
+
+int
+watchers (argc, argv)
+ int argc;
+ char **argv;
+{
+ int local = 0;
+ int c;
+
+ if (argc == -1)
+ usage (watchers_usage);
+
+ optind = 1;
+ while ((c = getopt (argc, argv, "l")) != -1)
+ {
+ switch (c)
+ {
+ case 'l':
+ local = 1;
+ break;
+ case '?':
+ default:
+ usage (watchers_usage);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+#ifdef CLIENT_SUPPORT
+ if (client_active)
+ {
+ start_server ();
+ ign_setup ();
+
+ if (local)
+ send_arg ("-l");
+ send_file_names (argc, argv, SEND_EXPAND_WILD);
+ /* FIXME: We shouldn't have to send current files, but I'm not sure
+ whether it works. So send the files --
+ it's slower but it works. */
+ send_files (argc, argv, local, 0);
+ send_to_server ("watchers\012", 0);
+ return get_responses_and_close ();
+ }
+#endif /* CLIENT_SUPPORT */
+
+ return start_recursion (watchers_fileproc, (FILESDONEPROC) NULL,
+ (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL,
+ argc, argv, local, W_LOCAL, 0, 1, (char *)NULL,
+ 1, 0);
+}
diff --git a/contrib/cvs/src/watch.h b/contrib/cvs/src/watch.h
new file mode 100644
index 0000000..d279c71
--- /dev/null
+++ b/contrib/cvs/src/watch.h
@@ -0,0 +1,56 @@
+/* Interface to "cvs watch add", "cvs watchers", and related features
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+extern const char *const watch_usage[];
+
+/* Flags to pass between the various functions making up the
+ add/remove code. All in a single structure in case there is some
+ need to make the code reentrant some day. */
+
+struct addremove_args {
+ /* A flag for each watcher type. */
+ int edit;
+ int unedit;
+ int commit;
+
+ /* Are we adding or removing (non-temporary) edit,unedit,and/or commit
+ watches? */
+ int adding;
+
+ /* Should we add a temporary edit watch? */
+ int add_tedit;
+ /* Should we add a temporary unedit watch? */
+ int add_tunedit;
+ /* Should we add a temporary commit watch? */
+ int add_tcommit;
+
+ /* Should we remove all temporary watches? */
+ int remove_temp;
+
+ /* Should we set the default? This is here for passing among various
+ routines in watch.c (a good place for it if there is ever any reason
+ to make the stuff reentrant), not for watch_modify_watchers. */
+ int setting_default;
+};
+
+/* Modify the watchers for FILE. *WHAT tells what to do to them.
+ If FILE is NULL, modify default args (WHAT->SETTING_DEFAULT is
+ not used). */
+extern void watch_modify_watchers PROTO ((char *file,
+ struct addremove_args *what));
+
+extern int watch_add PROTO ((int argc, char **argv));
+extern int watch_remove PROTO ((int argc, char **argv));
diff --git a/contrib/cvs/src/wrapper.c b/contrib/cvs/src/wrapper.c
new file mode 100644
index 0000000..8a6ff94
--- /dev/null
+++ b/contrib/cvs/src/wrapper.c
@@ -0,0 +1,374 @@
+#include "cvs.h"
+
+/*
+ Original Author: athan@morgan.com <Andrew C. Athan> 2/1/94
+ Modified By: vdemarco@bou.shl.com
+
+ This package was written to support the NEXTSTEP concept of
+ "wrappers." These are essentially directories that are to be
+ treated as "files." This package allows such wrappers to be
+ "processed" on the way in and out of CVS. The intended use is to
+ wrap up a wrapper into a single tar, such that that tar can be
+ treated as a single binary file in CVS. To solve the problem
+ effectively, it was also necessary to be able to prevent rcsmerge
+ application at appropriate times.
+
+ ------------------
+ Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)
+
+ wildcard [option value][option value]...
+
+ where option is one of
+ -f from cvs filter value: path to filter
+ -t to cvs filter value: path to filter
+ -m update methodology value: MERGE or COPY
+
+ and value is a single-quote delimited value.
+
+ E.g:
+ *.nib -f 'gunzipuntar' -t 'targzip' -m 'COPY'
+*/
+
+
+typedef struct {
+ char *wildCard;
+ char *tocvsFilter;
+ char *fromcvsFilter;
+ char *conflictHook;
+ WrapMergeMethod mergeMethod;
+} WrapperEntry;
+
+static WrapperEntry **wrap_list=NULL;
+static WrapperEntry **wrap_saved_list=NULL;
+
+static int wrap_size=0;
+static int wrap_count=0;
+static int wrap_tempcount=0;
+static int wrap_saved_count=0;
+static int wrap_saved_tempcount=0;
+
+#define WRAPPER_GROW 8
+
+void wrap_add_entry PROTO((WrapperEntry *e,int temp));
+void wrap_kill PROTO((void));
+void wrap_kill_temp PROTO((void));
+void wrap_free_entry PROTO((WrapperEntry *e));
+void wrap_free_entry_internal PROTO((WrapperEntry *e));
+void wrap_restore_saved PROTO((void));
+
+void wrap_setup()
+{
+ char file[PATH_MAX];
+ struct passwd *pw;
+
+ /* Then add entries found in repository, if it exists */
+ (void) sprintf (file, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_WRAPPER);
+ if (isfile (file)){
+ wrap_add_file(file,0);
+ }
+
+ /* Then add entries found in home dir, (if user has one) and file exists */
+ if ((pw = (struct passwd *) getpwuid (getuid ())) && pw->pw_dir){
+ (void) sprintf (file, "%s/%s", pw->pw_dir, CVSDOTWRAPPER);
+ if (isfile (file)){
+ wrap_add_file (file, 0);
+ }
+ }
+
+ /* Then add entries found in CVSWRAPPERS environment variable. */
+ wrap_add (getenv (WRAPPER_ENV), 0);
+}
+
+/*
+ * Open a file and read lines, feeding each line to a line parser. Arrange
+ * for keeping a temporary list of wrappers at the end, if the "temp"
+ * argument is set.
+ */
+void
+wrap_add_file (file, temp)
+ const char *file;
+ int temp;
+{
+ FILE *fp;
+ char line[1024];
+
+ wrap_restore_saved();
+ wrap_kill_temp();
+
+ /* load the file */
+ if (!(fp = fopen (file, "r")))
+ return;
+ while (fgets (line, sizeof (line), fp))
+ wrap_add (line, temp);
+ (void) fclose (fp);
+}
+
+void
+wrap_kill()
+{
+ wrap_kill_temp();
+ while(wrap_count)
+ wrap_free_entry(wrap_list[--wrap_count]);
+}
+
+void
+wrap_kill_temp()
+{
+ WrapperEntry **temps=wrap_list+wrap_count;
+
+ while(wrap_tempcount)
+ wrap_free_entry(temps[--wrap_tempcount]);
+}
+
+void
+wrap_free_entry(e)
+ WrapperEntry *e;
+{
+ wrap_free_entry_internal(e);
+ free(e);
+}
+
+void
+wrap_free_entry_internal(e)
+ WrapperEntry *e;
+{
+ free(e->wildCard);
+ if(e->tocvsFilter)
+ free(e->tocvsFilter);
+ if(e->fromcvsFilter)
+ free(e->fromcvsFilter);
+ if(e->conflictHook)
+ free(e->conflictHook);
+}
+
+void
+wrap_restore_saved()
+{
+ if(!wrap_saved_list)
+ return;
+
+ wrap_kill();
+
+ free(wrap_list);
+
+ wrap_list=wrap_saved_list;
+ wrap_count=wrap_saved_count;
+ wrap_tempcount=wrap_saved_tempcount;
+
+ wrap_saved_list=NULL;
+ wrap_saved_count=0;
+ wrap_saved_tempcount=0;
+}
+
+void
+wrap_add (line, isTemp)
+ char *line;
+ int isTemp;
+{
+ char *temp;
+ char ctemp;
+ WrapperEntry e;
+ char opt;
+
+ if (!line || line[0] == '#')
+ return;
+
+ memset (&e, 0, sizeof(e));
+
+ /* Search for the wild card */
+ while(*line && isspace(*line))
+ ++line;
+ for(temp=line;*line && !isspace(*line);++line)
+ ;
+ if(temp==line)
+ return;
+
+ ctemp=*line;
+ *line='\0';
+
+ e.wildCard=xstrdup(temp);
+ *line=ctemp;
+
+ while(*line){
+ /* Search for the option */
+ while(*line && *line!='-')
+ ++line;
+ if(!*line)
+ break;
+ ++line;
+ if(!*line)
+ break;
+ opt=*line;
+
+ /* Search for the filter commandline */
+ for(++line;*line && *line!='\'';++line);
+ if(!*line)
+ break;
+
+ for(temp=++line;*line && (*line!='\'' || line[-1]=='\\');++line)
+ ;
+
+ if(line==temp+1)
+ break;
+
+ ctemp=*line;
+ *line='\0';
+ switch(opt){
+ case 'f':
+ if(e.fromcvsFilter)
+ free(e.fromcvsFilter);
+ /* FIXME: error message should say where the bad value
+ came from. */
+ e.fromcvsFilter=expand_path (temp, "<wrapper>", 0);
+ if (!e.fromcvsFilter)
+ error (1, 0, "Correct above errors first");
+ break;
+ case 't':
+ if(e.tocvsFilter)
+ free(e.tocvsFilter);
+ /* FIXME: error message should say where the bad value
+ came from. */
+ e.tocvsFilter=expand_path (temp, "<wrapper>", 0);
+ if (!e.tocvsFilter)
+ error (1, 0, "Correct above errors first");
+ break;
+ case 'c':
+ if(e.conflictHook)
+ free(e.conflictHook);
+ /* FIXME: error message should say where the bad value
+ came from. */
+ e.conflictHook=expand_path (temp, "<wrapper>", 0);
+ if (!e.conflictHook)
+ error (1, 0, "Correct above errors first");
+ break;
+ case 'm':
+ if(*temp=='C' || *temp=='c')
+ e.mergeMethod=WRAP_COPY;
+ else
+ e.mergeMethod=WRAP_MERGE;
+ break;
+ default:
+ break;
+ }
+ *line=ctemp;
+ if(!*line)break;
+ ++line;
+ }
+
+ wrap_add_entry(&e, isTemp);
+}
+
+void
+wrap_add_entry(e, temp)
+ WrapperEntry *e;
+ int temp;
+{
+ int x;
+ if(wrap_count+wrap_tempcount>=wrap_size){
+ wrap_size += WRAPPER_GROW;
+ wrap_list = (WrapperEntry **) xrealloc ((char *) wrap_list,
+ wrap_size *
+ sizeof (WrapperEntry *));
+ }
+
+ if(!temp && wrap_tempcount){
+ for(x=wrap_count+wrap_tempcount-1;x>=wrap_count;--x)
+ wrap_list[x+1]=wrap_list[x];
+ }
+
+ x=(temp ? wrap_count+(wrap_tempcount++):(wrap_count++));
+ wrap_list[x]=(WrapperEntry *)xmalloc(sizeof(WrapperEntry));
+ wrap_list[x]->wildCard=e->wildCard;
+ wrap_list[x]->fromcvsFilter=e->fromcvsFilter;
+ wrap_list[x]->tocvsFilter=e->tocvsFilter;
+ wrap_list[x]->conflictHook=e->conflictHook;
+ wrap_list[x]->mergeMethod=e->mergeMethod;
+}
+
+/* Return 1 if the given filename is a wrapper filename */
+int
+wrap_name_has (name,has)
+ const char *name;
+ WrapMergeHas has;
+{
+ int x,count=wrap_count+wrap_saved_count;
+ char *temp;
+
+ for(x=0;x<count;++x)
+ if (fnmatch (wrap_list[x]->wildCard, name, 0) == 0){
+ switch(has){
+ case WRAP_TOCVS:
+ temp=wrap_list[x]->tocvsFilter;
+ break;
+ case WRAP_FROMCVS:
+ temp=wrap_list[x]->fromcvsFilter;
+ break;
+ case WRAP_CONFLICT:
+ temp=wrap_list[x]->conflictHook;
+ break;
+ default:
+ abort ();
+ }
+ if(temp==NULL)
+ return (0);
+ else
+ return (1);
+ }
+ return (0);
+}
+
+WrapperEntry *
+wrap_matching_entry (name)
+ const char *name;
+{
+ int x,count=wrap_count+wrap_saved_count;
+
+ for(x=0;x<count;++x)
+ if (fnmatch (wrap_list[x]->wildCard, name, 0) == 0)
+ return wrap_list[x];
+ return (WrapperEntry *)NULL;
+}
+
+char *
+wrap_tocvs_process_file(fileName)
+ const char *fileName;
+{
+ WrapperEntry *e=wrap_matching_entry(fileName);
+ static char buf[L_tmpnam+1];
+
+ if(e==NULL || e->tocvsFilter==NULL)
+ return NULL;
+
+ tmpnam(buf);
+
+ run_setup(e->tocvsFilter,fileName,buf);
+ run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY );
+
+ return buf;
+}
+
+int
+wrap_merge_is_copy (fileName)
+ const char *fileName;
+{
+ WrapperEntry *e=wrap_matching_entry(fileName);
+ if(e==NULL || e->mergeMethod==WRAP_MERGE)
+ return 0;
+
+ return 1;
+}
+
+char *
+wrap_fromcvs_process_file(fileName)
+ const char *fileName;
+{
+ WrapperEntry *e=wrap_matching_entry(fileName);
+ static char buf[PATH_MAX];
+
+ if(e==NULL || e->fromcvsFilter==NULL)
+ return NULL;
+
+ run_setup(e->fromcvsFilter,fileName);
+ run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL );
+ return buf;
+}
OpenPOWER on IntegriCloud