diff options
author | peter <peter@FreeBSD.org> | 1996-08-20 23:46:10 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 1996-08-20 23:46:10 +0000 |
commit | 8982e501c77217c860f79bba431f46a62b607a21 (patch) | |
tree | 70187fdf5be4cbefd0baf46bddac7e5e32c13c24 /contrib/cvs/src | |
parent | 01ee40fd6a76f6ff7ef247fc1b2cf6e337f216c5 (diff) | |
download | FreeBSD-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')
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; +} |