diff options
author | 1995-12-19 09:21:26 +0000 | |
---|---|---|
committer | 1995-12-19 09:21:26 +0000 | |
commit | 1e72d8d26fae84dfb4bcd4cecabd10b989ec3f29 (patch) | |
tree | 8e75671b795f0e506ffa01d9af4546c5238f2d89 /gnu/usr.bin/cvs/src | |
parent | Added support for using a special makefile: Makefile.bsd-wrapper when (diff) | |
download | wireguard-openbsd-1e72d8d26fae84dfb4bcd4cecabd10b989ec3f29.tar.xz wireguard-openbsd-1e72d8d26fae84dfb4bcd4cecabd10b989ec3f29.zip |
raw import of cvs-1.6
Diffstat (limited to 'gnu/usr.bin/cvs/src')
60 files changed, 35590 insertions, 0 deletions
diff --git a/gnu/usr.bin/cvs/src/ChangeLog b/gnu/usr.bin/cvs/src/ChangeLog new file mode 100644 index 00000000000..1937aef1d7b --- /dev/null +++ b/gnu/usr.bin/cvs/src/ChangeLog @@ -0,0 +1,2415 @@ +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. + diff --git a/gnu/usr.bin/cvs/src/ChangeLog.fsf b/gnu/usr.bin/cvs/src/ChangeLog.fsf new file mode 100644 index 00000000000..47ef44dfeb1 --- /dev/null +++ b/gnu/usr.bin/cvs/src/ChangeLog.fsf @@ -0,0 +1,520 @@ +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. + diff --git a/gnu/usr.bin/cvs/src/Makefile.in b/gnu/usr.bin/cvs/src/Makefile.in new file mode 100644 index 00000000000..73478a8e619 --- /dev/null +++ b/gnu/usr.bin/cvs/src/Makefile.in @@ -0,0 +1,192 @@ +# 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 entries.c find_names.c hash.c history.c ignore.c \ +import.c lock.c log.c logmsg.c main.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 server.c status.c subr.c filesubr.c run.c tag.c update.c \ +wrapper.c vers_ts.c version.c +MSOURCES = mkmodules.c + +OBJECTS = add.o admin.o checkin.o checkout.o classify.o commit.o \ +create_adm.o cvsrc.o diff.o entries.o find_names.o hash.o history.o \ +ignore.o import.o lock.o log.o logmsg.o main.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 status.o tag.o update.o wrapper.o vers_ts.o server.o client.o + +MOBJECTS = hash.o mkmodules.o myndbm.o + +COMMON_OBJECTS = subr.o filesubr.o run.o version.o + +HEADERS = cvs.h rcs.h hash.h myndbm.h patchlevel.h \ + update.h server.h client.h + +DISTFILES = .cvsignore Makefile.in ChangeLog ChangeLog.fsf \ + NOTES README-rm-add \ + sanity.sh cvsbug.sh \ + $(HEADERS) options.h.in $(SOURCES) $(MSOURCES) + +PROGS = cvs mkmodules 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: $(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: $(DISTFILES) + ctags $(DISTFILES) + +TAGS: $(DISTFILES) + etags `for i in $(DISTFILES); do echo $(srcdir)/$$i; done` + +ls: + @echo $(DISTFILES) +.PHONY: ls + +clean: + /bin/rm -f $(PROGS) *.o core +.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) $(COMMON_OBJECTS) + $(CC) $(OBJECTS) $(COMMON_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) + +mkmodules: $(MOBJECTS) $(COMMON_OBJECTS) + $(CC) $(LDFLAGS) -o $@ $(MOBJECTS) $(COMMON_OBJECTS) ../lib/libcvs.a $(LIBS) $(LIBIBERTY) + +cvsbug: cvsbug.sh + cp $(srcdir)/cvsbug.sh cvsbug + +# Compilation rules. + +$(OBJECTS) $(COMMON_OBJECTS) mkmodules.o: $(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/gnu/usr.bin/cvs/src/NOTES b/gnu/usr.bin/cvs/src/NOTES new file mode 100644 index 00000000000..646ebdf8520 --- /dev/null +++ b/gnu/usr.bin/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/gnu/usr.bin/cvs/src/README-rm-add b/gnu/usr.bin/cvs/src/README-rm-add new file mode 100644 index 00000000000..17c721fd958 --- /dev/null +++ b/gnu/usr.bin/cvs/src/README-rm-add @@ -0,0 +1,48 @@ +WHAT THE "DEATH SUPPORT" FEATURES DO: + +(this really should be in the main manual, but noone has gotten around +to updating it). + +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. + +First, the following now works as expected: + + touch foo + cvs add foo ; cvs ci -m "added" foo + rm foo + cvs rm foo ; cvs ci -m "removed" foo + touch foo + cvs add foo ; cvs ci -m "resurrected" foo + +Second, 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. + +I also extended the "cvs update -j" semantic slightly. Like before, +if you use two -j options, the changes made between the first and the +second will be merged into your working files. This has not changed. + +If you use only one -j option, it is used as the second -j option. +The first is assumed to be the greatest common ancestor revision +between the revision specified by the -j and the BASE revision of your +working file. diff --git a/gnu/usr.bin/cvs/src/add.c b/gnu/usr.bin/cvs/src/add.c new file mode 100644 index 00000000000..d5224f16e08 --- /dev/null +++ b/gnu/usr.bin/cvs/src/add.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. + * + * 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 "save-cwd.h" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)add.c 1.55 94/10/22 $"; +USE(rcsid); +#endif + +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 (); + option_with_arg ("-k", 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_files (argc, argv, 0, 0); + if (fprintf (to_server, "add\n") < 0) + error (1, errno, "writing to server"); + 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, (List *) 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) + { +#ifdef DEATH_SUPPORT + 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 +#endif /* DEATH_SUPPORT */ + error (0, 0, "scheduling %s `%s' for addition", + (wrap_name_has (user, WRAP_TOCVS) + ? "wrapper" + : "file"), + user); + } + } + } + } +#ifdef DEATH_SUPPORT + 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; + } + } +#endif /* DEATH_SUPPORT */ + 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 ((mode_t) 2); + 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 (1); + 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; + +#ifndef DEATH_SUPPORT + /* when using the rcs death support, this case is not a problem. */ + /* + * There may be an old file with the same name in the Attic! This is, + * perhaps, an awkward place to check for this, but other places are + * equally awkward. + */ + (void) sprintf (fname, "%s/%s/%s%s", repository, CVSATTIC, user, RCSEXT); + if (isreadable (fname)) + { + error (0, 0, "there is an old file %s already in %s/%s", user, + repository, CVSATTIC); + return (1); + } +#endif /* no DEATH_SUPPORT */ + + if (noexec) + return (0); + + /* + * The options for the "add" command are store in the file CVS/user,p + * XXX - no they are not! + */ + (void) sprintf (fname, "%s/%s%s", CVSADM, user, CVSEXT_OPT); + fp = open_file (fname, "w+"); + if (fclose (fp) == EOF) + error(1, errno, "cannot close %s", fname); + + /* + * And 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 ,p + * and ,t files, 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/gnu/usr.bin/cvs/src/admin.c b/gnu/usr.bin/cvs/src/admin.c new file mode 100644 index 00000000000..b85df3622a9 --- /dev/null +++ b/gnu/usr.bin/cvs/src/admin.c @@ -0,0 +1,149 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)admin.c 1.20 94/09/30 $"; +USE(rcsid); +#endif + +static Dtype admin_dirproc PROTO((char *dir, char *repos, char *update_dir)); +static int admin_fileproc PROTO((char *file, char *update_dir, + char *repository, List *entries, + List *srcfiles)); + +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; + + if (argc <= 1) + usage (admin_usage); + + 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 = 1; i <= ac; ++i) + send_arg (av[i]); + +#if 0 + /* 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_file_names (argc, argv); +#else + send_files (argc, argv, 0, 0); +#endif + if (fprintf (to_server, "admin\n") < 0) + error (1, errno, "writing to server"); + return get_responses_and_close (); + } +#endif /* CLIENT_SUPPORT */ + + /* start the recursion processor */ + err = start_recursion (admin_fileproc, (int (*) ()) NULL, admin_dirproc, + (int (*) ()) 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 (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + Vers_TS *vers; + char *version; + char **argv; + int argc; + int retcode = 0; + + vers = Version_TS (repository, (char *) NULL, (char *) NULL, (char *) NULL, + file, 0, 0, entries, srcfiles); + + version = vers->vn_user; + if (version == NULL) + return (0); + else if (strcmp (version, "0") == 0) + { + error (0, 0, "cannot admin newly added file `%s'", file); + return (0); + } + + run_setup ("%s%s", 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, file); + return (1); + } + return (0); +} + +/* + * 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/gnu/usr.bin/cvs/src/checkin.c b/gnu/usr.bin/cvs/src/checkin.c new file mode 100644 index 00000000000..09a81f764ff --- /dev/null +++ b/gnu/usr.bin/cvs/src/checkin.c @@ -0,0 +1,204 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)checkin.c 1.48 94/10/07 $"; +USE(rcsid); +#endif + +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 (errno != ENOENT) + error (1, errno, "cannot remove %s", file); + copy_file (tocvsPath, file); + } + else + { + copy_file (file, fname); + } + } + + run_setup ("%s%s -f %s%s", Rcsbin, RCS_CI, + rev ? "-r" : "", rev ? rev : ""); + run_args ("-m%s", make_message_rcslegal (message)); + run_arg (rcs); + + switch (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) + { + 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'; + run_setup ("%s%s -q %s %s%s", Rcsbin, RCS_CO, options, + rev ? "-r" : "", rev ? rev : ""); + run_arg (rcs); + (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + 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) + xchmod (file, 0); + +#ifndef DEATH_SUPPORT + /* With death_support, files added with tags go into branches immediately. */ + + /* for added files with symbolic tags, need to add the tag too */ + if (type == 'A' && tag && !isdigit (*tag)) + { + (void) RCS_settag(rcs, tag, rev); + } +#endif /* No DEATH_SUPPORT */ + + /* re-register with the new data */ + vers = Version_TS (repository, (char *) NULL, tag, (char *) NULL, + file, 1, set_time, entries, (List *) 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); + } +#endif + + return (0); +} diff --git a/gnu/usr.bin/cvs/src/checkout.c b/gnu/usr.bin/cvs/src/checkout.c new file mode 100644 index 00000000000..422cfeeb75f --- /dev/null +++ b/gnu/usr.bin/cvs/src/checkout.c @@ -0,0 +1,869 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)checkout.c 1.78 94/10/07 $"; +USE(rcsid); +#endif + +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 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 (); + } + + if (fprintf + (to_server, + strcmp (command_name, "export") == 0 ? "export\n" : "co\n") + < 0) + error (1, errno, "writing to server"); + + return get_responses_and_close (); + } +#endif + + 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)) + (void) CVS_MKDIR (repository, 0777); + + /* 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); + } + 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; + } + else + which = W_LOCAL | W_REPOS; + + if (tag != NULL || date != NULL) + which |= W_ATTIC; + + /* + * 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[MAXLINELEN]; + char *user; + Vers_TS *vers; + + user = argv[i]; + vers = Version_TS (repository, options, tag, date, user, + force_tag_match, 0, entries, (List *) NULL); + if (vers->ts_user == NULL) + { + (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); + } + 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 (".", cp, 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/gnu/usr.bin/cvs/src/classify.c b/gnu/usr.bin/cvs/src/classify.c new file mode 100644 index 00000000000..23fafca9b2c --- /dev/null +++ b/gnu/usr.bin/cvs/src/classify.c @@ -0,0 +1,504 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)classify.c 1.17 94/10/07 $"; +USE(rcsid); +#endif + +#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, srcfiles, versp, update_dir, pipeout) + char *file; + char *tag; + char *date; + char *options; + int force_tag_match; + int aflag; + char *repository; + List *entries; + List *srcfiles; + 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, srcfiles); + + 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; + } + } +#ifdef DEATH_SUPPORT + 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; + } + } +#endif + 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; +#ifdef DEATH_SUPPORT + else if (RCS_isdead (vers->srcfile, vers->vn_rcs)) + /* we are resurrecting. */ + ret = T_ADDED; +#endif /* DEATH_SUPPORT */ + else + { +#ifdef DEATH_SUPPORT + 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 + { +#endif /* DEATH_SUPPORT */ + /* + * 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); +#ifdef DEATH_SUPPORT + } +#endif + 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/gnu/usr.bin/cvs/src/client.c b/gnu/usr.bin/cvs/src/client.c new file mode 100644 index 00000000000..5f34c5dcd7b --- /dev/null +++ b/gnu/usr.bin/cvs/src/client.c @@ -0,0 +1,3190 @@ +/* CVS client-related stuff. */ + +#include "cvs.h" + +#ifdef CLIENT_SUPPORT + +#include "update.h" /* Things shared with update.c */ +#include "md5.h" + +#if HAVE_KERBEROS +#define CVS_PORT 1999 + +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <krb.h> + +extern char *krb_realmofhost (); +#ifndef HAVE_KRB_GET_ERR_TEXT +#define krb_get_err_text(status) krb_err_txt[status] +#endif +#endif + +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)); + +#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 +mode_to_string (mode) + mode_t mode; +#endif +{ + 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; +{ + 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 /* 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; + +/* Set server_host and server_cvsroot. */ +static void +parse_cvsroot () +{ + char *p; + + server_host = xstrdup (CVSroot); + server_cvsroot = strchr (server_host, ':'); + *server_cvsroot = '\0'; + ++server_cvsroot; + + if ( (p = strchr (server_host, '@')) == NULL) { + server_user = NULL; + } else { + server_user = server_host; + server_host = p; + ++server_host; + *p = '\0'; + } + + client_active = 1; +} + +/* Stream to write to the server. */ +FILE *to_server; +/* Stream to read from the server. */ +FILE *from_server; + +#if ! RSH_NOT_TRANSPARENT +/* Process ID of rsh subprocess. */ +static int rsh_pid = -1; +#endif + +/* + * Read a line from the server. + * + * 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; + int input_index = 0; + int result_size = 80; + + fflush (to_server); + result = (char *) xmalloc (result_size); + + while (1) + { + c = getc (from_server); + + if (c == EOF) + { + free (result); + if (ferror (from_server)) + error (1, errno, "reading from server"); + /* It's end of file. */ + if (eof_ok) + return 0; + else + error (1, 0, "premature end of file from server"); + } + + 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'; + + 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[2] = { "gunzip" }; + 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. + */ + if (fprintf(to_server, "%s\n", rq->name) < 0) + error (1, errno, "writing to server"); + 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_dirname; + +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 *dirname; + 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'; + + dirname = xstrdup (short_pathname); + p = strrchr (dirname, '/'); + if (p == NULL) + { + dirname = xrealloc (dirname, 2); + dirname[0] = '.'; dirname[1] = '\0'; + } + else + *p = '\0'; + if (client_prune_dirs) + add_prune_candidate (dirname); + + 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_dirname == NULL + || strcmp (last_dirname, dirname) != 0) + { + if (last_dirname) + free (last_dirname); + last_dirname = dirname; + + 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 (dirname) < 0) + { + char *dir; + char *dirp; + + if (errno != ENOENT) + error (1, errno, "could not chdir to %s", dirname); + + /* Directory does not exist, we need to create it. */ + dir = xmalloc (strlen (dirname) + 1); + dirp = dirname; + 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 DIRNAME (with + DIRP) and REPOSDIRNAME (with RDIRP) respectively. + + We need to be careful when we are checking out a + module, however, since DIRNAME 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, dirname, dirp - dirname); + dir[dirp - dirname] = '\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, dirname); + } + + if (CVS_MKDIR (dir, 0777) < 0) + { + if (errno != EEXIST) + error (1, errno, "cannot make directory %s", dir); + + /* 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 (dirname) < 0) + error (1, errno, "could not chdir to %s", dirname); + } + + if (strcmp (command_name, "export") != 0) + { + if (last_entries) + Entries_Close (last_entries); + last_entries = Entries_Open (0); + } + } + else + free (dirname); + 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; + + read_line (&newname, 0); + 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); +} + +/* + * 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; +} + +/* + * 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; + + read_line (&entries_line, 0); + + if (data->contents == UPDATE_ENTRIES_UPDATE + || data->contents == UPDATE_ENTRIES_PATCH) + { + char *size_string; + char *mode_string; + int size; + int size_read; + int size_left; + int fd; + char *buf; + char *buf2; + 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 _POSIX_NO_TRUNC + sprintf (temp_filename, ".new.%.9s", filename); +#else + sprintf (temp_filename, ".new.%s", filename); +#endif + buf = xmalloc (size); + fd = open (temp_filename, O_WRONLY | O_CREAT | O_TRUNC, 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) + { + buf2 = buf; + size_left = size; + while ((size_read = fread (buf2, 1, size_left, from_server)) != size_left) + { + if (feof (from_server)) + /* FIXME: Should delete temp_filename. */ + error (1, 0, "unexpected end of file from server"); + else if (ferror (from_server)) + /* FIXME: Should delete temp_filename. */ + error (1, errno, "reading from server"); + else + { + /* short reads are ok if we keep trying */ + buf2 += size_read; + size_left -= size_read; + } + } + 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 %d", 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 + if (use_gzip) + { + 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 + rename_file (temp_filename, filename); +#endif + } + 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 (buf); + } + + /* + * Process the entries line. Do this after we've written the file, + * since we need the timestamp. + */ + if (strcmp (command_name, "export") != 0) + { + 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 *local_timestamp; + char *file_timestamp; + + char *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; + + local_timestamp = data->timestamp; + if (local_timestamp == NULL || ts[0] == '+') + file_timestamp = time_stamp (filename); + + /* + * 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; + + Register (ent_list, filename, vn, local_timestamp, + options, tag, date, ts[0] == '+' ? file_timestamp : NULL); + 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 && errno != ENOENT) + 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 && errno != ENOENT) + 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); +} + +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 requests 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 (errno != ENOENT) + 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)) + { + run_setup ("%s -fr", RM); + run_arg (p->dir); + (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + } + 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; + + 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) + { + if (fprintf (to_server, "Directory ") < 0) + error (1, errno, "writing to server"); + if (fprintf (to_server, "%s", update_dir) < 0) + error (1, errno, "writing to server"); + + if (fprintf (to_server, "\n%s\n", repos) + < 0) + error (1, errno, "writing to server"); + } + else + { + if (fprintf (to_server, "Repository %s\n", repos) < 0) + error (1, errno, "writing to server"); + } + 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)) + { + if (fprintf (to_server, "Static-directory\n") < 0) + error (1, errno, "writing to server"); + } + } + 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 (errno != ENOENT) + error (1, errno, "reading %s", adm_name); + } + else + { + char line[80]; + char *nl; + if (fprintf (to_server, "Sticky ") < 0) + error (1, errno, "writing to server"); + while (fgets (line, sizeof (line), f) != NULL) + { + if (fprintf (to_server, "%s", line) < 0) + error (1, errno, "writing to server"); + nl = strchr (line, '\n'); + if (nl != NULL) + break; + } + if (nl == NULL) + if (fprintf (to_server, "\n") < 0) + error (1, errno, "writing to server"); + 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 (errno != ENOENT) + error (1, errno, "reading %s", adm_name); + } + else + { + char line[80]; + char *nl; + if (fprintf (to_server, "Checkin-prog ") < 0) + error (1, errno, "writing to server"); + while (fgets (line, sizeof (line), f) != NULL) + { + if (fprintf (to_server, "%s", line) < 0) + error (1, errno, "writing to server"); + nl = strchr (line, '\n'); + if (nl != NULL) + break; + } + if (nl == NULL) + if (fprintf (to_server, "\n") < 0) + error (1, errno, "writing to server"); + 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 (errno != ENOENT) + error (1, errno, "reading %s", adm_name); + } + else + { + char line[80]; + char *nl; + if (fprintf (to_server, "Update-prog ") < 0) + error (1, errno, "writing to server"); + while (fgets (line, sizeof (line), f) != NULL) + { + if (fprintf (to_server, "%s", line) < 0) + error (1, errno, "writing to server"); + nl = strchr (line, '\n'); + if (nl != NULL) + break; + } + if (nl == NULL) + if (fprintf (to_server, "\n") < 0) + error (1, errno, "writing to server"); + if (fclose (f) == EOF) + error (0, errno, "closing %s", 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); +} + +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; +} + +void +client_expand_modules (argc, argv, local) + int argc; + char **argv; + int local; +{ + int errs; + int i; + + for (i = 0; i < argc; ++i) + send_arg (argv[i]); + send_a_repository ("", server_cvsroot, ""); + if (fprintf (to_server, "expand-modules\n") < 0) + error (1, errno, "writing to server"); + 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, ""); +} + +void +client_send_expansions (local) + int local; +{ + int i; + char *argv[1]; + for (i = 0; i < modules_count; ++i) + { + argv[0] = modules_vector[i]; + if (isfile (argv[0])) + send_files (1, argv, local, 0); + else + send_file_names (1, argv); + } + 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 +#define RSP_LINE(n, f, t, s) {n, s} +#endif + + 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("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("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("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 + +/* + * 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; + +int +get_responses_and_close () +{ + int errs = get_server_responses (); + + do_deferred_progs (); + + if (client_prune_dirs) + process_prune_candidates (); + +#ifdef HAVE_KERBEROS + if (server_fd != -1) + { + if (shutdown (server_fd, 1) < 0) + error (1, errno, "shutting down connection to %s", server_host); + /* + * In this case, both sides of the net connection will use the + * same fd. + */ + if (fileno (from_server) != fileno (to_server)) + { + if (fclose (to_server) != 0) + error (1, errno, "closing down connection to %s", server_host); + } + } + else +#endif +#ifdef SHUTDOWN_SERVER + SHUTDOWN_SERVER (fileno (to_server)); +#else + { + if (fclose (to_server) == EOF) + 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 + +#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 + + return errs; +} + +#ifndef RSH_NOT_TRANSPARENT +static void start_rsh_server PROTO((int *, int *)); +#endif + +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?"); +} + +/* Contact the server. */ + +void +start_server () +{ + int tofd, fromfd; + char *log = getenv ("CVS_CLIENT_LOG"); + +#if HAVE_KERBEROS + { + struct hostent *hp; + char *hname; + const char *realm; + const char *portenv; + int port; + struct sockaddr_in sin; + int s; + KTEXT_ST ticket; + 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); + + realm = krb_realmofhost (hname); + + portenv = getenv ("CVS_CLIENT_PORT"); + if (portenv != NULL) + { + port = atoi (portenv); + if (port <= 0) + goto try_rsh_no_message; + 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 + { + 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 + { + server_fd = s; + close_on_exec (server_fd); + /* + * If we do any filtering, TOFD and FROMFD will be + * closed. So make sure they're copies of SERVER_FD, + * and not the same fd number. + */ + if (log) + { + tofd = dup (s); + fromfd = dup (s); + } + else + tofd = fromfd = s; + } + } + + if (tofd == -1) + { + 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 +#if defined (START_SERVER) + START_SERVER (&tofd, &fromfd, getcaller (), + server_user, server_host, server_cvsroot); +#endif /* START_SERVER */ +#endif /* RSH_NOT_TRANSPARENT */ + } + free (hname); + } + +#else /* ! HAVE_KERBEROS */ +#if ! RSH_NOT_TRANSPARENT + start_rsh_server (&tofd, &fromfd); +#else +#if defined(START_SERVER) + /* This is all a real mess. We now have three ways of connecting + to the server, and there's a fourth on the horizon. We should + clean this all up before adding the fourth. */ + START_SERVER (&tofd, &fromfd, getcaller (), + server_user, server_host, server_cvsroot); +#endif /* START_SERVER */ +#endif /* RSH_NOT_TRANSPARENT */ +#endif /* ! HAVE_KERBEROS */ + + close_on_exec (tofd); + close_on_exec (fromfd); + + if (log) + { + int len = strlen (log); + char *buf = xmalloc (5 + len); + char *p; + static char *teeprog[3] = { "tee" }; + + teeprog[1] = buf; + strcpy (buf, log); + p = buf + len; + + strcpy (p, ".in"); + tofd = filter_stream_through_program (tofd, 0, teeprog, 0); + + strcpy (p, ".out"); + fromfd = filter_stream_through_program (fromfd, 1, teeprog, 0); + + free (buf); + } + + /* Should be using 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); + /* Should be using binary mode on systems which have it. */ + 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_dirname != NULL) + free (last_dirname); + last_dirname = 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; + + if (fprintf (to_server, "Root %s\n", server_cvsroot) < 0) + error (1, errno, "writing to server"); + { + struct response *rs; + if (fprintf (to_server, "Valid-responses") < 0) + error (1, errno, "writing to server"); + for (rs = responses; rs->name != NULL; ++rs) + { + if (fprintf (to_server, " %s", rs->name) < 0) + error (1, errno, "writing to server"); + } + if (fprintf (to_server, "\n") < 0) + error (1, errno, "writing to server"); + } + if (fprintf (to_server, "valid-requests\n") < 0) + error (1, errno, "writing to server"); + if (get_server_responses ()) + exit (1); + + /* + * 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) + { + if (fprintf (to_server, "Global_option -n\n") < 0) + error (1, errno, "writing to server"); + } + else + error (1, 0, + "This server does not support the global -n option."); + } + if (quiet) + { + if (have_global) + { + if (fprintf (to_server, "Global_option -q\n") < 0) + error (1, errno, "writing to server"); + } + else + error (1, 0, + "This server does not support the global -q option."); + } + if (really_quiet) + { + if (have_global) + { + if (fprintf (to_server, "Global_option -Q\n") < 0) + error (1, errno, "writing to server"); + } + else + error (1, 0, + "This server does not support the global -Q option."); + } + if (!cvswrite) + { + if (have_global) + { + if (fprintf (to_server, "Global_option -r\n") < 0) + error (1, errno, "writing to server"); + } + else + error (1, 0, + "This server does not support the global -r option."); + } + if (trace) + { + if (have_global) + { + if (fprintf (to_server, "Global_option -t\n") < 0) + error (1, errno, "writing to server"); + } + else + error (1, 0, + "This server does not support the global -t option."); + } + if (logoff) + { + if (have_global) + { + if (fprintf (to_server, "Global_option -l\n") < 0) + error (1, errno, "writing to server"); + } + else + error (1, 0, + "This server does not support the global -l option."); + } + } + if (gzip_level) + { + if (supported_request ("gzip-file-contents")) + { + if (fprintf (to_server, "gzip-file-contents %d\n", gzip_level) < 0) + error (1, 0, "writing to server"); + } + else + { + fprintf (stderr, "server doesn't support gzip-file-contents\n"); + gzip_level = 0; + } + } +} + +#ifndef RSH_NOT_TRANSPARENT +/* Contact the server by starting it with rsh. */ + +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 + + +/* Send an argument STRING. */ +void +send_arg (string) + char *string; +{ + char *p = string; + if (fprintf (to_server, "Argument ") < 0) + error (1, errno, "writing to server"); + while (*p) + { + if (*p == '\n') + { + if (fprintf (to_server, "\nArgumentx ") < 0) + error (1, errno, "writing to server"); + } + else if (putc (*p, to_server) == EOF) + error (1, errno, "writing to server"); + ++p; + } + if (putc ('\n', to_server) == EOF) + error (1, errno, "writing to server"); +} + +void +send_modified (file, short_pathname) + char *file; + char *short_pathname; +{ + /* File was modified, send it. */ + struct stat sb; + int fd; + char *buf; + char *mode_string; + int bufsize; + + /* 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); + + fd = open (file, O_RDONLY); + 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]; +#endif + +#ifdef LINES_CRLF_TERMINATED + /* 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 + + fd = filter_through_gzip (fd, 1, gzip_level, &gzip_pid); + 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 %d", gzip_pid); + else if (gzip_status != 0) + error (1, errno, "gzip exited %d", gzip_status); + +#if LINES_CRLF_TERMINATED + if (unlink (tempfile) < 0) + error (0, errno, "warning: can't remove temp file %s", tempfile); +#endif LINES_CRLF_TERMINATED + + fprintf (to_server, "Modified %s\n%s\nz%lu\n", file, mode_string, + (unsigned long) newsize); + fwrite (buf, newsize, 1, to_server); + if (feof (to_server) || ferror (to_server)) + error (1, errno, "writing to server"); + } + else + { + int newsize; + + { + char *bufp = buf; + int len; + + 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); + + if (fprintf (to_server, "Modified %s\n%s\n%lu\n", file, + mode_string, (unsigned long) newsize) < 0) + error (1, errno, "writing to server"); + + /* + * Note that this only ends with a newline if the file ended with + * one. + */ + if (newsize > 0) + if (fwrite (buf, newsize, 1, to_server) != 1) + error (1, errno, "writing to server"); + } + free (buf); + free (mode_string); +} + +/* Deal with one file. */ +static int +send_fileproc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + Vers_TS *vers; + int update_dir_len = strlen (update_dir); + char *short_pathname = xmalloc (update_dir_len + strlen (file) + 40); + strcpy (short_pathname, update_dir); + if (update_dir[0] != '\0') + strcat (short_pathname, "/"); + strcat (short_pathname, file); + + send_a_repository ("", repository, update_dir); + + vers = Version_TS ((char *)NULL, (char *)NULL, (char *)NULL, + (char *)NULL, + file, 0, 0, entries, (List *)NULL); + + if (vers->vn_user != NULL) + { + /* The Entries request. */ + /* Not sure about whether this deals with -k and stuff right. */ + if (fprintf (to_server, "Entry /%s/%s/%s%s/%s/", 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) < 0) + error (1, errno, "writing to server"); + if (vers->entdata != NULL && vers->entdata->tag) + { + if (fprintf (to_server, "T%s", vers->entdata->tag) < 0) + error (1, errno, "writing to server"); + } + else if (vers->entdata != NULL && vers->entdata->date) + if (fprintf (to_server, "D%s", vers->entdata->date) < 0) + error (1, errno, "writing to server"); + if (fprintf (to_server, "\n") < 0) + error (1, errno, "writing to server"); + } + + 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... */ + if (fprintf (to_server, "Lost %s\n", file) < 0) + error (1, errno, "writing to server"); + /* + * 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 (file, short_pathname); + } + else + { + /* Only use this request if the server supports it... */ + if (use_unchanged) + if (fprintf (to_server, "Unchanged %s\n", file) < 0) + error (1, errno, "writing to server"); + } + + /* if this directory has an ignore list, add this file to it */ + if (ignlist) + { + Node *p; + + p = getnode (); + p->type = FILES; + p->key = xstrdup (file); + (void) addnode (ignlist, p); + } + + free (short_pathname); + return 0; +} + +/* + * 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) + int argc; + char **argv; +{ + int i; + char *p; + char *q; + int level; + int max_level; + + /* 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")) + { + if (fprintf (to_server, "Max-dotdot %d\n", max_level) < 0) + error (1, errno, "writing to server"); + } + 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) + send_arg (argv[i]); +} + + +/* + * 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; + + send_file_names (argc, argv); + + /* + * 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, update_filesdone_proc, + send_dirent_proc, (int (*) ())NULL, + argc, argv, local, W_LOCAL, aflag, 0, (char *)NULL, 0, 0); + if (err) + exit (1); + 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); + 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, "."); +} + +/* + * 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; + if (fprintf (to_server, "Argument %s\n", option) < 0) + error (1, errno, "writing to server"); + 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 + + 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 */ +} + +#endif /* CLIENT_SUPPORT */ diff --git a/gnu/usr.bin/cvs/src/client.h b/gnu/usr.bin/cvs/src/client.h new file mode 100644 index 00000000000..0602900aaf5 --- /dev/null +++ b/gnu/usr.bin/cvs/src/client.h @@ -0,0 +1,163 @@ +/* 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)); + +/* + * Flag variable for seeing whether common code is running as a client + * or to do a local operation. + */ +extern int client_active; + +/* Is the -P option to checkout or update specified? */ +extern int client_prune_dirs; + +/* Stream to write to the server. */ +extern FILE *to_server; +/* Stream to read from the server. */ +extern FILE *from_server; + +/* 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)); + +/* + * 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 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 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)); + +#endif /* CLIENT_SUPPORT */ diff --git a/gnu/usr.bin/cvs/src/commit.c b/gnu/usr.bin/cvs/src/commit.c new file mode 100644 index 00000000000..a50e84298cc --- /dev/null +++ b/gnu/usr.bin/cvs/src/commit.c @@ -0,0 +1,1840 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)commit.c 1.101 94/10/07 $"; +USE(rcsid); +#endif + +static Dtype check_direntproc PROTO((char *dir, char *repos, char *update_dir)); +static int check_fileproc PROTO((char *file, char *update_dir, char *repository, + List * entries, List * srcfiles)); +static int check_filesdoneproc PROTO((int err, char *repos, char *update_dir)); +static int checkaddfile PROTO((char *file, char *repository, char *tag, + List *srcfiles)); +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((char *file, char *update_dir, char *repository, + List * entries, List * srcfiles)); +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 fsortcmp PROTO((const Node * p, const Node * q)); +static int lock_RCS PROTO((char *user, char *rcs, char *rev, char *repository)); +static int lock_filesdoneproc PROTO((int err, char *repository, char *update_dir)); +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, List *srcfiles)); +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 List *locklist; +static char *message; + +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 +}; + +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"); + + 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); + + 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) + { + /* + * 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 (); + + ign_setup (); + + /* + * We do this once, not once for each directory as in normal CVS. + * The protocol is designed this way. This is a feature. + * + * We could provide the lists of changed, modified, etc. files, + * however. Our failure to do so is just laziness, not design. + */ + if (use_editor) + do_editor (".", &message, (char *)NULL, (List *)NULL); + + /* 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); + + send_files (argc, argv, local, 0); + + if (fprintf (to_server, "ci\n") < 0) + error (1, errno, "writing to server"); + return get_responses_and_close (); + } +#endif + + /* XXX - this is not the perfect check for this */ + if (argc <= 0) + write_dirtag = tag; + + wrap_setup (); + + /* + * Run the recursion processor to find all the dirs to lock and lock all + * the dirs + */ + locklist = getlist (); + err = start_recursion ((int (*) ()) NULL, lock_filesdoneproc, + (Dtype (*) ()) NULL, (int (*) ()) NULL, argc, + argv, local, W_LOCAL, aflag, 0, (char *) NULL, 0, + 0); + sortlist (locklist, fsortcmp); + if (Writer_Lock (locklist) != 0) + error (1, 0, "lock failed - giving up"); + + /* + * 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, (int (*) ()) NULL, argc, + argv, local, W_LOCAL, aflag, 0, (char *) NULL, 1, + 0); + if (err) + { + Lock_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_Cleanup (); + dellist (&mulist); + dellist (&locklist); + 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)); +} + +/* + * 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 (locklist, p) != 0) + freenode (p); + 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 (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + 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 (file, (char *) NULL, (char *) NULL, + (char *) NULL, 1, aflag, repository, + entries, srcfiles, &vers, update_dir, 0); + if (status == T_UPTODATE || status == T_MODIFIED || + status == T_ADDED) + { + Ctype xstatus; + + freevers_ts (&vers); + xstatus = Classify_File (file, tag, (char *) NULL, + (char *) NULL, 1, aflag, repository, + entries, srcfiles, &vers, 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 (file, xtag, (char *) NULL, + (char *) NULL, 1, aflag, repository, + entries, srcfiles, &vers, 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 (file, xtag, (char *) NULL, + (char *) NULL, 1, aflag, repository, + entries, srcfiles, &vers, 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 (file, tag, (char *) NULL, (char *) NULL, + 1, 0, repository, entries, srcfiles, &vers, + 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: + if (update_dir[0] == '\0') + error (0, 0, "Up-to-date check failed for `%s'", file); + else + error (0, 0, "Up-to-date check failed for `%s/%s'", + update_dir, file); + 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) + { + if (update_dir[0] == '\0') + error (0, 0, + "cannot commit with sticky date for file `%s'", + file); + else + error + (0, 0, + "cannot commit with sticky date for file `%s/%s'", + update_dir, file); + freevers_ts (&vers); + return (1); + } + if (status == T_MODIFIED && vers->tag && + !RCS_isbranch (file, vers->tag, srcfiles)) + { + if (update_dir[0] == '\0') + error (0, 0, + "sticky tag `%s' for file `%s' is not a branch", + vers->tag, file); + else + error + (0, 0, + "sticky tag `%s' for file `%s/%s' is not a branch", + vers->tag, update_dir, file); + 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 (file); + retcode = strcmp (vers->ts_conflict, filestamp); + free (filestamp); + } +#else + filestamp = time_stamp (file); + retcode = strcmp (vers->ts_conflict, filestamp); + free (filestamp); +#endif + if (retcode == 0) + { + if (update_dir[0] == '\0') + error (0, 0, + "file `%s' had a conflict and has not been modified", + file); + else + error (0, 0, + "file `%s/%s' had a conflict and has not been modified", + update_dir, file); + 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 -s", GREP); + run_arg (RCS_MERGE_PAT); + run_arg (file); + retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + + if (retcode == -1) + { + if (update_dir[0] == '\0') + error (1, errno, + "fork failed while examining conflict in `%s'", + file); + else + error (1, errno, + "fork failed while examining conflict in `%s/%s'", + update_dir, file); + } + else if (retcode == 0) + { + if (update_dir[0] == '\0') + error (0, 0, + "file `%s' still contains conflict indicators", + file); + else + error (0, 0, + "file `%s/%s' still contains conflict indicators", + update_dir, file); + freevers_ts (&vers); + return (1); + } + } + + if (status == T_REMOVED && vers->tag && isdigit (*vers->tag)) + { + if (update_dir[0] == '\0') + error (0, 0, + "cannot remove file `%s' which has a numeric sticky tag of `%s'", + file, vers->tag); + else + error (0, 0, + "cannot remove file `%s/%s' which has a numeric sticky tag of `%s'", + update_dir, file, vers->tag); + freevers_ts (&vers); + return (1); + } + if (status == T_ADDED) + { + char rcs[PATH_MAX]; + +#ifdef DEATH_SUPPORT + /* Don't look in the attic; if it exists there we will + move it back out in checkaddfile. */ + sprintf(rcs, "%s/%s%s", repository, file, RCSEXT); +#else + locate_rcs (file, repository, rcs); +#endif + if (isreadable (rcs)) + { + if (update_dir[0] == '\0') + error (0, 0, + "cannot add file `%s' when RCS file `%s' already exists", + file, rcs); + else + error (0, 0, + "cannot add file `%s/%s' when RCS file `%s' already exists", + update_dir, file, rcs); + freevers_ts (&vers); + return (1); + } + if (vers->tag && isdigit (*vers->tag) && + numdots (vers->tag) > 1) + { + if (update_dir[0] == '\0') + error (0, 0, + "cannot add file `%s' with revision `%s'; must be on trunk", + file, vers->tag); + else + error (0, 0, + "cannot add file `%s/%s' with revision `%s'; must be on trunk", + update_dir, file, vers->tag); + freevers_ts (&vers); + return (1); + } + } + + /* done with consistency checks; now, to get on with the commit */ + if (update_dir[0] == '\0') + xdir = "."; + else + xdir = 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 (file); + p->type = UPDATE; + p->delproc = update_delproc; + p->data = (char *) status; + (void) addnode (ulist, p); + + p = getnode (); + p->key = xstrdup (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 (file, vers->tag, srcfiles); + 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: + if (update_dir[0] == '\0') + error (0, 0, "nothing known about `%s'", file); + else + error (0, 0, "nothing known about `%s/%s'", update_dir, file); + 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 (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + Node *p; + int err = 0; + List *ulist, *cilist; + struct commit_info *ci; + char rcs[PATH_MAX]; + + if (update_dir[0] == '\0') + p = findnode (mulist, "."); + else + p = findnode (mulist, 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 (update_dir, &message, repository, ulist); + } + + p = findnode (cilist, file); + if (p == NULL) + return (0); + + ci = (struct commit_info *) p->data; + if (ci->status == T_MODIFIED) + { + if (lockrcsfile (file, repository, ci->rev) != 0) + { + unlockrcs (file, repository); + err = 1; + goto out; + } + } + else if (ci->status == T_ADDED) + { + if (checkaddfile (file, repository, ci->tag, srcfiles) != 0) + { + fixaddfile (file, repository); + err = 1; + goto out; + } + +#ifdef DEATH_SUPPORT + /* 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 (file, repository, rcs); + ci->rev = RCS_whatbranch (file, ci->tag, srcfiles); + err = Checkin ('A', file, update_dir, repository, rcs, ci->rev, + ci->tag, ci->options, message, entries); + if (err != 0) + { + unlockrcs (file, repository); + fixbranch (file, repository, sbranch); + } + + ci->status = T_UPTODATE; + } +#endif /* DEATH_SUPPORT */ + } + + /* + * 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 (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 (file, ci->rev ? ci->rev : xrev, ci->tag, ci->options, + update_dir, repository, entries); + if (xrev) + free (xrev); + } + else if (ci->status == T_MODIFIED) + { + locate_rcs (file, repository, rcs); + err = Checkin ('M', file, update_dir, repository, + rcs, ci->rev, ci->tag, + ci->options, message, entries); + if (err != 0) + { + unlockrcs (file, repository); + fixbranch (file, repository, sbranch); + } + } + else if (ci->status == T_REMOVED) + { + err = remove_file (file, repository, ci->tag, message, + entries, srcfiles); +#ifdef SERVER_SUPPORT + if (server_active) { + server_scratch_entry_only (); + server_updated (file, update_dir, repository, + /* Doesn't matter, it won't get checked. */ + SERVER_UPDATED, (struct stat *) NULL, + (unsigned char *) NULL); + } +#endif + } + +out: + if (err != 0) + { + /* on failure, remove the file from ulist */ + p = findnode (ulist, 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); + + if (err == 0 && run_module_prog) + { + char *cp; + FILE *fp; + char line[MAXLINELEN]; + char *repository; + + /* It is not an error if Checkin.prog does not exist. */ + if ((fp = fopen (CVSADM_CIPROG, "r")) != NULL) + { + if (fgets (line, sizeof (line), fp) != NULL) + { + if ((cp = strrchr (line, '\n')) != NULL) + *cp = '\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); + } + (void) fclose (fp); + } + } + + 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, srcfiles) + char *file; + char *repository; + char *tag; + char *message; + List *entries; + List *srcfiles; +{ + mode_t omask; + int retcode; + char rcs[PATH_MAX]; + char *tmp; + +#ifdef DEATH_SUPPORT + int branch; + char *lockflag; + char *corev; + char *rev; + char *prev_rev; + Node *p; + RCSNode *rcsfile; + + corev = NULL; + rev = NULL; + prev_rev = NULL; + lockflag = 0; +#endif /* DEATH_SUPPORT */ + + retcode = 0; + + locate_rcs (file, repository, rcs); + +#ifdef DEATH_SUPPORT + branch = 0; + if (tag && !(branch = RCS_isbranch (file, tag, srcfiles))) +#else + if (tag) +#endif + { + /* 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); + } + +#ifdef DEATH_SUPPORT + /* 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 = "-l"; + if (branch) + { + char *branchname; + + rev = RCS_whatbranch (file, tag, srcfiles); + if (rev == NULL) + { + error (0, 0, "cannot find branch \"%s\".", tag); + return (1); + } + + p = findnode (srcfiles, file); + if (p == NULL) + { + error (0, 0, "boy, I'm confused."); + return (1); + } + rcsfile = (RCSNode *) p->data; + branchname = RCS_getbranch (rcsfile, rev, 1); + if (branchname == NULL) + { + /* no revision exists on this branch. use the previous + revision but do not lock. */ + corev = RCS_gettag (rcsfile, tag, 1); + prev_rev = xstrdup(rev); + lockflag = ""; + } else + { + corev = xstrdup (rev); + prev_rev = xstrdup(branchname); + free (branchname); + } + + } else /* Not a branch */ + { + + /* Get current head revision of file. */ + p = findnode (srcfiles, file); + if (p == NULL) + { + error (0, 0, "could not find parsed rcsfile %s", file); + return (1); + } + rcsfile = (RCSNode *) p->data; + prev_rev = RCS_head (rcsfile); + } + + /* 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. */ + run_setup ("%s%s %s %s%s %s", Rcsbin, RCS_CO, + lockflag, + rev ? "-r" : "", + rev ? corev : "", rcs); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, DEVNULL, RUN_NORMAL)) + != 0) { + if (!quiet) + error (0, retcode == -1 ? errno : 0, + "failed to check out `%s'", rcs); + return (1); + } + + if (corev != NULL) + free (corev); + +#ifdef DEATH_STATE + run_setup ("%s%s -f -sdead %s%s", Rcsbin, RCS_CI, rev ? "-r" : "", +#else + run_setup ("%s%s -K %s%s", Rcsbin, RCS_CI, rev ? "-r" : "", +#endif + rev ? rev : ""); + run_args ("-m%s", make_message_rcslegal (message)); + run_arg (rcs); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, DEVNULL, RUN_NORMAL)) + != 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) +#else /* No DEATH_SUPPORT */ + else +#endif /* No DEATH_SUPPORT */ + { + /* 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 (2); + (void) CVS_MKDIR (tmp, 0777); + (void) umask (omask); + (void) sprintf (tmp, "%s/%s/%s%s", repository, CVSATTIC, file, RCSEXT); + +#ifdef DEATH_SUPPORT + 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); +#else /* No DEATH_SUPPORT */ + + if ((strcmp (rcs, tmp) == 0 || rename (rcs, tmp) != -1) || + (!isreadable (rcs) && isreadable (tmp))) + { + Scratch_Entry (entries, file); + /* FIXME: should free tmp. */ + return (0); + } + /* FIXME: should free tmp. */ + } + return (1); +#endif /* No DEATH_SUPPORT */ +} + +/* + * 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_OPT); + (void) unlink_file (tmp); + (void) sprintf (tmp, "%s/%s%s", CVSADM, file, CVSEXT_LOG); + (void) unlink_file (tmp); + } + else + fixaddfile (file, repository); + 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, srcfiles) + char *file; + char *repository; + char *tag; + List *srcfiles; +{ + FILE *fp; + char *cp; + char rcs[PATH_MAX]; + char fname[PATH_MAX]; + mode_t omask; + int retcode = 0; +#ifdef DEATH_SUPPORT + int newfile = 0; +#endif + + if (tag) + { + (void) sprintf(rcs, "%s/%s", repository, CVSATTIC); + omask = umask (2); + 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); + +#ifdef DEATH_SUPPORT + if (isreadable(rcs)) + { + /* file has existed in the past. Prepare to resurrect. */ + char oldfile[PATH_MAX]; + char *rev; + Node *p; + 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); + } + } + + p = findnode (srcfiles, file); + if (p == NULL) + { + error (0, 0, "could not find parsed rcsfile %s", file); + return (1); + } + + rcsfile = (RCSNode *) p->data; + rev = RCS_getversion (rcsfile, tag, NULL, 1); + /* 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 -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); + + (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_OPT); + fp = fopen (fname, "r"); + /* If the file does not exist, no big deal. In particular, the + server does not (yet at least) create CVSEXT_OPT files. */ + if (fp == NULL) + { + if (errno != ENOENT) + error (1, errno, "cannot open %s", fname); + } + else + { + while (fgets (fname, sizeof (fname), fp) != NULL) + { + if ((cp = strrchr (fname, '\n')) != NULL) + *cp = '\0'; + if (*fname) + run_arg (fname); + } + (void) fclose (fp); + } + 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[PATH_MAX]; + + /* 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); + + /* commit a dead revision. */ + (void) sprintf (tmp, "-mfile %s was initially added on branch %s.", file, tag); +#ifdef DEATH_STATE + run_setup ("%s%s -q -f -sdead", Rcsbin, RCS_CI); +#else + run_setup ("%s%s -q -K", Rcsbin, RCS_CI); +#endif + run_arg (tmp); + 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 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. */ + Node *p; + RCSNode *rcsfile; + + rcsfile = RCS_parse (file, repository); + if (rcsfile == NULL) + { + error (0, 0, "could not read %s", rcs); + return (1); + } + + if (!RCS_nodeisbranch (tag, rcsfile)) { + /* branch does not exist. Stub it. */ + char *head; + char *magicrev; + + head = RCS_getversion (rcsfile, NULL, NULL, 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); + } + } + + /* add (replace) this rcs file to our list */ + p = findnode (srcfiles, file); + + if (p != NULL) + freercsnode((RCSNode **) &p->data); + + delnode(p); + + RCS_addnode (file, rcsfile, srcfiles); + } +#else /* No DEATH_SUPPORT */ + run_setup ("%s%s -i", Rcsbin, RCS); + run_args ("-t%s/%s%s", CVSADM, file, CVSEXT_LOG); + (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_OPT); + fp = open_file (fname, "r"); + while (fgets (fname, sizeof (fname), fp) != NULL) + { + if ((cp = strrchr (fname, '\n')) != NULL) + *cp = '\0'; + if (*fname) + run_arg (fname); + } + (void) fclose (fp); + 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); + } +#endif /* No DEATH_SUPPORT */ + + 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/gnu/usr.bin/cvs/src/create_adm.c b/gnu/usr.bin/cvs/src/create_adm.c new file mode 100644 index 00000000000..1fe81855adf --- /dev/null +++ b/gnu/usr.bin/cvs/src/create_adm.c @@ -0,0 +1,144 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)create_adm.c 1.28 94/09/23 $"; +USE(rcsid); +#endif + +/* 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); + +#ifdef CVSADM_ROOT + /* record the current cvs root for later use */ + + Create_Root (dir, CVSroot); +#endif /* CVSADM_ROOT */ + 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); + + if (trace) + { + fprintf (stderr, "%c<- Create_Admin\n", + (server_active) ? 'S' : ' '); + } +#endif + +} diff --git a/gnu/usr.bin/cvs/src/cvs.h b/gnu/usr.bin/cvs/src/cvs.h new file mode 100644 index 00000000000..d3133cd5abc --- /dev/null +++ b/gnu/usr.bin/cvs/src/cvs.h @@ -0,0 +1,531 @@ +/* $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 */ + +/* AIX requires this to be the first thing in the file. */ +#ifdef __GNUC__ +#define alloca __builtin_alloca +#else /* not __GNUC__ */ +#if HAVE_ALLOCA_H +#include <alloca.h> +#else /* not HAVE_ALLOCA_H */ +#ifdef _AIX + #pragma alloca +#else /* not _AIX */ +char *alloca (); +#endif /* not _AIX */ +#endif /* not HAVE_ALLOCA_H */ +#endif /* not __GNUC__ */ + +/* 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 + +#if __GNUC__ == 2 +#define USE(var) static const char sizeof##var = sizeof(sizeof##var) + sizeof(var) +#else +#define USE(var) static const char standalone_semis_illegal_sigh +#endif + + +#include <stdio.h> + +#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" + +/* Define to enable alternate death support (which uses the RCS state). */ +#define DEATH_STATE 1 + +#define DEATH_SUPPORT 1 + +#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. + */ +#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" + +/* + * 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_IGNORE "cvsignore" +#define CVSROOTADM_CHECKOUTLIST "checkoutlist" +#define CVSROOTADM_WRAPPER "cvswrappers" +#define CVSNULLREPOS "Emptydir" /* an empty directory */ + +/* support for the modules file (CVSROOTADM_MODULES) */ +#define CVSMODULE_OPTS "ad:i:lo:e:s:t:u:"/* options in modules file */ +#define CVSMODULE_SPEC '&' /* special delimiter */ + +/* 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 CVSTFL "#cvs.tfl" +#define CVSRFL "#cvs.rfl" +#define CVSWFL "#cvs.wfl" +#define CVSRFLPAT "#cvs.rfl.*" /* wildcard expr to match read locks */ +#define CVSEXT_OPT ",p" +#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 */ +#define BAKPREFIX ".#" /* when rcsmerge'ing */ +#ifndef DEVNULL +#define DEVNULL "/dev/null" +#endif + +#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 */ + +/* + * 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 *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 +{ + char *vn_user; /* rcs version user file derives from + * it can have the following special + * values: + * empty = no user file + * 0 = user file is new + * -vers = user file to be removed */ + char *vn_rcs; /* the version for the rcs file + * (tag version?) */ + char *ts_user; /* the timestamp for the user file */ + char *ts_rcs; /* the user timestamp from entries */ + char *options; /* opts from Entries file + * (keyword expansion) */ + char *ts_conflict; /* Holds time_stamp of conflict */ + char *tag; /* tag stored in the Entries file */ + char *date; /* date stored in the Entries file */ + Entnode *entdata; /* pointer to entries file node */ + RCSNode *srcfile; /* pointer to parsed src file info */ +}; +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, *command_name; +extern char *Rcsbin, *Editor, *CVSroot; +#ifdef CVSADM_ROOT +extern char *CVSADM_Root; +extern int cvsadmin_root; +#endif /* CVSADM_ROOT */ +extern char *CurDir; +extern int really_quiet, quiet; +extern int use_editor; +extern int cvswrite; + +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 *)); + +#include "error.h" + +DBM *open_module PROTO((void)); +FILE *open_file PROTO((const char *, const char *)); +List *Find_Dirs 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)); +#ifdef CVSADM_ROOT +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)); +#endif /* CVSADM_ROOT */ +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)); +char *xrealloc PROTO((char *ptr, size_t bytes)); +char *xstrdup PROTO((const char *str)); +int No_Difference PROTO((char *file, Vers_TS * vers, List * entries, + char *repository, char *update_dir)); +int Parse_Info PROTO((char *infofile, char *repository, int PROTO((*callproc)) PROTO(()), int all)); +int Reader_Lock PROTO((char *xrepository)); +int SIG_register PROTO((int sig, RETSIGTYPE PROTO((*fn)) PROTO(()))); +int Writer_Lock PROTO((List * list)); +int ign_name PROTO((char *name)); +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 isabsolute PROTO((const char *filename)); +char *last_component PROTO((char *path)); + +int joining PROTO((void)); +int numdots PROTO((const char *s)); +int unlink_file PROTO((const char *f)); +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)); +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)); +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)); +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)); +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)); +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, List *srcfiles, 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, List * xfiles)); +void do_editor PROTO((char *dir, char **messagep, + char *repository, List * changes)); +int do_module PROTO((DBM * db, char *mname, enum mtype m_type, char *msg, + int PROTO((*callback_proc)) (), char *where, int shorten, + int local_specified, int run_module_prog, char *extra_arg)); +int do_recursion PROTO((int PROTO((*xfileproc)) (), int PROTO((*xfilesdoneproc)) (), + Dtype PROTO((*xdirentproc)) (), int PROTO((*xdirleaveproc)) (), + Dtype xflags, int xwhich, int xaflag, int xreadlock, + int xdosrcs)); +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)); +void history_write PROTO((int type, char *update_dir, char *revs, char *name, + char *repository)); +int start_recursion PROTO((int PROTO((*fileproc)) (), int PROTO((*filesdoneproc)) (), + Dtype PROTO((*direntproc)) (), int PROTO((*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 *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 *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)); diff --git a/gnu/usr.bin/cvs/src/cvsbug.sh b/gnu/usr.bin/cvs/src/cvsbug.sh new file mode 100644 index 00000000000..b47597f93cd --- /dev/null +++ b/gnu/usr.bin/cvs/src/cvsbug.sh @@ -0,0 +1,538 @@ +#!/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. +# Modified by Jim Blandy for CVS 1.5. +# $CVSid: @(#)cvsbug.sh 1.2 94/10/22 $ +# +# 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="post-cvs-1.5" + +# 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: +` + if [ -n "$ORGANIZATION" ]; then + echo "$ORGANIZATION" + else + echo " $ORGANIZATION_C" ; + fi ; +` +>Confidential: $CONFIDENTIAL_C +>Synopsis: $SYNOPSIS_C +>Severity: $SEVERITY_C +>Priority: $PRIORITY_C +>Category: $CATEGORY_C +>Class: $CLASS_C +>Release: `if [ -n "$DEFAULT_RELEASE" ]; then + echo "$DEFAULT_RELEASE" + else + echo " $RELEASE_C" + fi; ` +>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/gnu/usr.bin/cvs/src/cvsrc.c b/gnu/usr.bin/cvs/src/cvsrc.c new file mode 100644 index 00000000000..ec594eb5456 --- /dev/null +++ b/gnu/usr.bin/cvs/src/cvsrc.c @@ -0,0 +1,139 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)cvsrc.c 1.9 94/09/30 $"; +USE(rcsid); +#endif /* lint */ + +/* 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 (); + +void +read_cvsrc (argc, argv) + int *argc; + char ***argv; +{ + char *homedir; + char *homeinit; + FILE *cvsrcfile; + + char linebuf [MAXLINELEN]; + + char *optstart; + + 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 = getenv ("HOME"); + 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 (access (homeinit, R_OK) != 0) + { + free (homeinit); + return; + } + + /* now scan the file until we find the line for the command in question */ + + cvsrcfile = open_file (homeinit, "r"); + while (fgets (linebuf, MAXLINELEN, cvsrcfile)) + { + /* skip over comment lines */ + if (linebuf[0] == '#') + continue; + + /* stop if we match the current command */ + if (!strncmp (linebuf, (*argv)[0], strlen ((*argv)[0]))) + { + found = 1; + break; + } + } + + fclose (cvsrcfile); + + if (found) + { + /* skip over command in the options line */ + optstart = strtok(linebuf+strlen((*argv)[0]), "\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); + } + + /* 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/gnu/usr.bin/cvs/src/diff.c b/gnu/usr.bin/cvs/src/diff.c new file mode 100644 index 00000000000..b7a41697082 --- /dev/null +++ b/gnu/usr.bin/cvs/src/diff.c @@ -0,0 +1,620 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)diff.c 1.61 94/10/22 $"; +USE(rcsid); +#endif + +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, + List *srcfiles, Vers_TS *vers)); +static int diff_fileproc PROTO((char *file, char *update_dir, char *repository, + List * entries, List * srcfiles)); +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); + +#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 */ + +/* Idea: often times the changed region of a file is relatively small. + It would be cool if the client could just divide the file into 4k + blocks or whatever and send hash values for the blocks. Send hash + values for blocks aligned with the beginning of the file and the + end of the file. Then the server can tell how much of the head and + tail of the file is unchanged. Well, hash collisions will screw + things up, but MD5 has 128 bits of hash value... */ + + /* Send the current files unless diffing two revs from the archive */ + if (diff_rev2 == NULL && diff_date2 == NULL) + send_files (argc, argv, local); + else + send_file_names (argc, argv); +#else + send_files (argc, argv, local, 0); +#endif + + if (fprintf (to_server, "diff\n") < 0) + error (1, errno, "writing to server"); + err = get_responses_and_close (); + free (options); + return (err); + } +#endif + + 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 (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + 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 (repository, (char *) NULL, (char *) NULL, (char *) NULL, + file, 1, 0, entries, srcfiles); + + 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", file); + 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", file); + 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", file); + 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", file); + freevers_ts (&vers); + diff_mark_errors (err); + return (err); + } + else + { + if (vers->ts_user == NULL) + { + error (0, 0, "cannot find %s", file); + 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 (file, repository, entries, srcfiles, vers)) + { + freevers_ts (&vers); + return (0); + } + +#ifdef DEATH_SUPPORT + /* FIXME: Check whether use_rev1 and use_rev2 are dead and deal + accordingly. */ +#endif + + /* Output an "Index:" line for patch to use */ + (void) fflush (stdout); + if (update_dir[0]) + (void) printf ("Index: %s/%s\n", update_dir, file); + else + (void) printf ("Index: %s\n", file); + (void) fflush (stdout); + + tocvsPath = wrap_tocvs_process_file(file); + if (tocvsPath) + { + /* Backup the current version of the file to CVS/,,filename */ + sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, file); + if (unlink_file_dir (fname) < 0) + if (errno != ENOENT) + error (1, errno, "cannot remove %s", file); + rename_file (file, fname); + /* Copy the wrapped file to the current directory then go to work */ + copy_file (tocvsPath, file); + } + + if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED) + { + (void) printf ("===================================================================\nRCS file: %s\n", + file); + (void) printf ("diff -N %s\n", file); + + if (empty_file == DIFF_ADDED) + { + run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, file); + } + else + { + /* + * 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". + */ + run_setup ("%s%s -p -q %s -r%s", Rcsbin, RCS_CO, + *options ? options : vers->options, vers->vn_rcs); + run_arg (vers->srcfile->path); + if (run_exec (RUN_TTY, tmpnam (tmp), RUN_TTY, RUN_REALLY) == -1) + { + (void) unlink (tmp); + error (1, errno, "fork failed during checkout of %s", + vers->srcfile->path); + } + + run_setup ("%s %s %s %s", DIFF, opts, tmp, DEVNULL); + } + } + else + { + if (use_rev2) + { + run_setup ("%s%s %s %s -r%s -r%s", Rcsbin, RCS_DIFF, + opts, *options ? options : vers->options, + use_rev1, use_rev2); + } + else + { + run_setup ("%s%s %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 (file) < 0) + if (errno != ENOENT) + error (1, errno, "cannot remove %s", file); + + rename_file (fname,file); + if (unlink_file (tocvsPath) < 0) + error (1, errno, "cannot remove %s", 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 + */ +/* 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??? */ + 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, srcfiles, vers) + char *file; + char *repository; + List *entries; + List *srcfiles; + Vers_TS *vers; +{ + Vers_TS *xvers; + char tmp[L_tmpnam+1]; + + /* 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, srcfiles); + if (xvers->vn_rcs == NULL) + { + 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); + 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, srcfiles); + if (xvers->vn_rcs == NULL) + { + 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); + 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. + */ + run_setup ("%s%s -p -q %s -r%s", Rcsbin, RCS_CO, + *options ? options : vers->options, use_rev1); + run_arg (vers->srcfile->path); + switch (run_exec (RUN_TTY, tmpnam (tmp), RUN_TTY, RUN_REALLY)) + { + 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/gnu/usr.bin/cvs/src/entries.c b/gnu/usr.bin/cvs/src/entries.c new file mode 100644 index 00000000000..f5b40121632 --- /dev/null +++ b/gnu/usr.bin/cvs/src/entries.c @@ -0,0 +1,536 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)entries.c 1.44 94/10/07 $"; +USE(rcsid); +#endif + +static Node *AddEntryNode PROTO((List * list, char *name, char *version, + char *timestamp, char *options, char *tag, + char *date, char *conflict)); + +static FILE *entfile; +static char *entfilename; /* for error messages */ + +/* + * 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; +{ + Entnode *p; + + p = (Entnode *) node->data; + if (fprintf (entfile, "/%s/%s/%s", node->key, p->version, + p->timestamp) == EOF) + error (1, errno, "cannot write %s", entfilename); + if (p->conflict) + { + if (fprintf (entfile, "+%s", p->conflict) < 0) + error (1, errno, "cannot write %s", entfilename); + } + if (fprintf (entfile, "/%s/", p->options) < 0) + error (1, errno, "cannot write %s", entfilename); + + if (p->tag) + { + if (fprintf (entfile, "T%s\n", p->tag) < 0) + error (1, errno, "cannot write %s", entfilename); + } + else if (p->date) + { + if (fprintf (entfile, "D%s\n", p->date) < 0) + error (1, errno, "cannot write %s", entfilename); + } + else if (fprintf (entfile, "\n") < 0) + 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 (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; +{ + 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 + } + + node = AddEntryNode (list, fname, vn, ts, options, tag, date, ts_conflict); + + 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); +} + +struct entent { + char *user; + char *vn; + char *ts; + char *options; + char *tag; + char *date; + char *ts_conflict; +}; + +struct entent * +fgetentent(fpin) + FILE *fpin; +{ + static struct entent ent; + static char line[MAXLINELEN]; + register char *cp; + char *user, *vn, *ts, *options; + char *tag_or_date, *tag, *date, *ts_conflict; + + while (fgets (line, sizeof (line), fpin) != NULL) + { + 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) + { + extern char *ctime (); + char *c = ctime (&sb.st_mtime); + + if (!strncmp (ts + 25, c, 24)) + ts = time_stamp (user); + else + { + ts += 24; + ts[0] = '*'; + } + } + } + + ent.user = user; + ent.vn = vn; + ent.ts = ts; + ent.options = options; + ent.tag = tag; + ent.date = date; + ent.ts_conflict = ts_conflict; + + return &ent; + } + + return NULL; +} + + +/* + * Read the entries file into a list, hashing on the file name. + */ +List * +Entries_Open (aflag) + int aflag; +{ + List *entries; + struct entent *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->user, + ent->vn, + ent->ts, + ent->options, + ent->tag, + ent->date, + ent->ts_conflict); + } + + fclose (fpin); + } + + fpin = fopen (CVSADM_ENTLOG, "r"); + if (fpin != NULL) { + while ((ent = fgetentent (fpin)) != NULL) + { + (void) AddEntryNode (entries, + ent->user, + ent->vn, + ent->ts, + ent->options, + ent->tag, + ent->date, + ent->ts_conflict); + } + do_rewrite = 1; + fclose (fpin); + } + + if (do_rewrite && !noexec) + write_entries (entries); + + /* clean up and return */ + if (fpin) + (void) fclose (fpin); + 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; + free (p->version); + free (p->timestamp); + free (p->options); + if (p->tag) + free (p->tag); + if (p->date) + free (p->date); + if (p->conflict) + free (p->conflict); + free ((char *) p); +} + +/* + * Get an Entries file list node, initialize it, and add it to the specified + * list + */ +static Node * +AddEntryNode (list, name, version, timestamp, options, tag, date, conflict) + List *list; + char *name; + char *version; + char *timestamp; + char *options; + char *tag; + char *date; + char *conflict; +{ + Node *p; + Entnode *entdata; + + /* was it already there? */ + if ((p = findnode (list, name)) != 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 */ + p->key = xstrdup (name); + + /* malloc the data parts and fill them in */ + p->data = xmalloc (sizeof (Entnode)); + entdata = (Entnode *) p->data; + entdata->version = xstrdup (version); + entdata->timestamp = xstrdup (timestamp); + if (entdata->timestamp == NULL) + entdata->timestamp = xstrdup ("");/* must be non-NULL */ + entdata->options = xstrdup (options); + if (entdata->options == NULL) + entdata->options = xstrdup ("");/* must be non-NULL */ + entdata->conflict = xstrdup (conflict); + entdata->tag = xstrdup (tag); + entdata->date = xstrdup (date); + + /* 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 && errno != ENOENT) + 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; + char line[MAXLINELEN]; + char *cp; + + if (tagp) + *tagp = (char *) NULL; + if (datep) + *datep = (char *) NULL; + fp = fopen (CVSADM_TAG, "r"); + if (fp) + { + if (fgets (line, sizeof (line), fp) != NULL) + { + if ((cp = strrchr (line, '\n')) != NULL) + *cp = '\0'; + if (*line == 'T' && tagp) + *tagp = xstrdup (line + 1); + else if (*line == 'D' && datep) + *datep = xstrdup (line + 1); + } + (void) fclose (fp); + } +} diff --git a/gnu/usr.bin/cvs/src/filesubr.c b/gnu/usr.bin/cvs/src/filesubr.c new file mode 100644 index 00000000000..c2fb5a4304d --- /dev/null +++ b/gnu/usr.bin/cvs/src/filesubr.c @@ -0,0 +1,585 @@ +/* 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" + +#ifndef lint +static const char rcsid[] = "$CVSid:$"; +USE(rcsid); +#endif + +/* + * 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; +{ + struct stat sb; + + if (stat (file, &sb) < 0) + return (0); + return (1); +} + +/* + * Returns non-zero if the argument file is readable. + * XXX - must be careful if "cvs" is ever made setuid! + */ +int +isreadable (file) + const char *file; +{ + return (access (file, R_OK) != -1); +} + +/* + * Returns non-zero if the argument file is writable + * XXX - muct be careful if "cvs" is ever made setuid! + */ +int +iswritable (file) + const char *file; +{ + return (access (file, W_OK) != -1); +} + +/* + * 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 buf; + + if (stat (name, &buf) == 0 && (!S_ISDIR (buf.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 (errno != ENOENT) + { + 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. Adding write permissions 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; + } + if (writable) + { + oumask = umask (0); + (void) umask (oumask); + 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); + } + + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> chmod(%s,%o)\n", + (server_active) ? 'S' : ' ', fname, mode); +#else + (void) fprintf (stderr, "-> chmod(%s,%o)\n", fname, 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. + */ +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); + + if (unlink (f) != 0) + { + /* under NEXTSTEP errno is set to return EPERM if + * the file is a directory,or if the user is not + * allowed to read or write to the file. + * [This is probably a bug in the O/S] + * other systems will return EISDIR to indicate + * that the path is a directory. + */ + if (errno == EISDIR || errno == EPERM) + return deep_remove_dir (f); + else + /* The file wasn't a directory and some other + * error occured + */ + 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 ) + { + 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); + + if (unlink (buf) != 0 ) + { + if (errno == EISDIR || errno == EPERM) + { + if (deep_remove_dir (buf)) + { + closedir (dirp); + return -1; + } + } + else + { + /* buf isn't a directory, or there are + * some sort of permision problems + */ + 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; +} diff --git a/gnu/usr.bin/cvs/src/find_names.c b/gnu/usr.bin/cvs/src/find_names.c new file mode 100644 index 00000000000..82959b5754b --- /dev/null +++ b/gnu/usr.bin/cvs/src/find_names.c @@ -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. + * + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)find_names.c 1.45 94/10/22 $"; +USE(rcsid); +#endif + +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_Dirs (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) + 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/gnu/usr.bin/cvs/src/hash.c b/gnu/usr.bin/cvs/src/hash.c new file mode 100644 index 00000000000..8ac93237c38 --- /dev/null +++ b/gnu/usr.bin/cvs/src/hash.c @@ -0,0 +1,400 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)hash.c 1.19 94/09/23 $"; +USE(rcsid); +#endif + +/* 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; + + while (*key != 0) + { + h = (h << 4) + *key++; + 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 on error or not found. + */ +Node * +findnode (list, key) + List *list; + const char *key; +{ + Node *head, *p; + + if (list == (List *) NULL) + return ((Node *) NULL); + + head = list->hasharray[hashp (key)]; + if (head == (Node *) NULL) + return ((Node *) NULL); + + for (p = head->hashnext; p != head; p = p->hashnext) + if (strcmp (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); +} + +/* + * 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"); + } + + 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/gnu/usr.bin/cvs/src/hash.h b/gnu/usr.bin/cvs/src/hash.h new file mode 100644 index 00000000000..e30511a2701 --- /dev/null +++ b/gnu/usr.bin/cvs/src/hash.h @@ -0,0 +1,55 @@ +/* $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 +}; +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 *getnode PROTO((void)); +int addnode PROTO((List * list, Node * p)); +int walklist PROTO((List * list, int (*)(Node *n, void *closure), void *closure)); +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/gnu/usr.bin/cvs/src/history.c b/gnu/usr.bin/cvs/src/history.c new file mode 100644 index 00000000000..7a40b7bd8bd --- /dev/null +++ b/gnu/usr.bin/cvs/src/history.c @@ -0,0 +1,1489 @@ +/* + * + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)history.c 1.33 94/09/21 $"; +USE(rcsid); +#endif + +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); + + if (fprintf (to_server, "history\n") < 0) + error (1, errno, "writing to server"); + 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]; + static char username[20]; /* !!! Should be global */ + 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; + if ((fd = open (fname, O_WRONLY | O_APPEND | O_CREAT, 0666)) < 0) + error (1, errno, "cannot open history file: %s", fname); + + repos = Short_Repository (repository); + + if (!PrCurDir) + { + struct passwd *pw; + + (void) strcpy (username, getcaller ()); + PrCurDir = CurDir; + if (!(pw = (struct passwd *) getpwnam (username))) + error (0, 0, "cannot find own username"); + else + { + /* Assumes neither CurDir nor pw->pw_dir ends in '/' */ + i = strlen (pw->pw_dir); + if (!strncmp (CurDir, pw->pw_dir, 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 (pw->pw_dir) < 0) + error (1, errno, "can't chdir(%s)", pw->pw_dir); + if (!getwd (homedir)) + error (1, errno, "can't getwd in %s", pw->pw_dir); + (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; + + memset ((char *) hr, 0, sizeof (*hr)); + while (isspace (*line)) + line++; + if (!(rtn = strchr (line, '\n'))) + return (""); + *rtn++ = '\0'; + + hr->type = line++; + (void) sscanf (line, "%x", &hr->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)) < 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, (List *) 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/gnu/usr.bin/cvs/src/ignore.c b/gnu/usr.bin/cvs/src/ignore.c new file mode 100644 index 00000000000..c4bf510ecd1 --- /dev/null +++ b/gnu/usr.bin/cvs/src/ignore.c @@ -0,0 +1,275 @@ +/* + * .cvsignore file support contributed by David G. Grubbs <dgg@odi.com> + */ + +#include "cvs.h" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)ignore.c 1.16 94/09/24 $"; +USE(rcsid); +#endif + +/* + * 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* .del-* *.a *.o *.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej"; + +#define IGN_GROW 16 /* grow the list by 16 elements at a + * time */ + +/* + * 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; + + /* Start with default list and special case */ + tmp = xstrdup (ign_default); + ign_add (tmp, 0); + free (tmp); + + /* 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 (errno != ENOENT) + 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 == '!') + 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; + } +} + +/* 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); + + while (*cpp) + if (fnmatch (*cpp++, name, 0) == 0) + return (1); + return (0); +} + + +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; +} diff --git a/gnu/usr.bin/cvs/src/import.c b/gnu/usr.bin/cvs/src/import.c new file mode 100644 index 00000000000..95d3c4338d0 --- /dev/null +++ b/gnu/usr.bin/cvs/src/import.c @@ -0,0 +1,1175 @@ +/* + * 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 "save-cwd.h" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)import.c 1.63 94/09/30 $"; +USE(rcsid); +#endif + +#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; + + ign_setup (); + + 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); + + { + int i; + for (i = 0; i < argc; ++i) + send_arg (argv[i]); + } + + logfp = stdin; + client_import_setup (repository); + err = import_descend (message, argv[1], argc - 2, argv + 2); + client_import_done (); + if (fprintf (to_server, "import\n") < 0) + error (1, errno, "writing to server"); + 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 (2); + 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) + { + (void) printf ("\n%d conflicts created by this import.\n", + conflicts); + (void) printf ("Use the following command to help the merge:\n\n"); + (void) printf ("\t%s checkout -j%s:yesterday -j%s %s\n\n", + program_name, argv[1], argv[1], argv[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) + (void) printf ("\nNo conflicts created by this import\n\n"); + (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; + if (ign_name (dp->d_name)) + { +#ifdef SERVER_SUPPORT + /* CVS directories are created by server.c because it doesn't + special-case import. So don't print a message about them. + Do print a message about other ignored files (although + most of these will get ignored on the client side). */ + if (server_active && strcmp (dp->d_name, CVSADM) == 0) + continue; +#endif + 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, (List *) NULL); +#ifdef DEATH_SUPPORT + if (vers->vn_rcs != NULL + && !RCS_isdead(vers->srcfile, vers->vn_rcs)) +#else + if (vers->vn_rcs != NULL) +#endif + { + 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%d", tmpdir, 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. + */ +#ifdef HAVE_RCS5 + run_setup ("%s%s -q -f -r%s -p -ko", Rcsbin, RCS_CO, vers->vn_rcs); +#else + run_setup ("%s%s -q -f -r%s -p", Rcsbin, RCS_CO, vers->vn_rcs); +#endif + run_arg (vers->srcfile->path); + if ((retcode = run_exec (RUN_TTY, xtmpfile, RUN_TTY, + RUN_NORMAL|RUN_REALLY)) != 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; + struct stat vfile_stat; + + 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); + + /* We used to deposit the revision with -r; RCS would delete the + working file, but we'd keep a hard link to it, and rename it + back after running RCS (ooh, atomicity). However, that + strategy doesn't work on operating systems without hard links + (like Windows NT). Instead, let's deposit it using -u, and + restore its permission bits afterwards. This also means the + file always exists under its own name. */ + if (! tocvsPath) + stat (vfile, &vfile_stat); + + run_setup ("%s%s -q -f %s%s", Rcsbin, RCS_CI, + (tocvsPath ? "-r" : "-u"), + vbranch); + run_args ("-m%s", make_message_rcslegal (message)); + if (use_file_modtime) + run_arg ("-d"); + run_arg (tocvsPath == NULL ? vfile : tocvsPath); + run_arg (rcs); + status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + ierrno = errno; + + /* Restore the permissions on vfile. */ + if (! tocvsPath) + chmod (vfile, vfile_stat.st_mode); + + 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, (List *) 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 */ + {"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 */ + {"e", "# "}, /* efl */ + {"epsf", "% "}, /* encapsulated postscript */ + {"epsi", "% "}, /* encapsulated postscript */ + {"el", "; "}, /* Emacs Lisp */ + {"f", "c "}, /* Fortran */ + {"for", "c "}, + {"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) */ + {"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 */ + {"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 */ +#ifdef SYSTEM_COMMENT_TABLE + SYSTEM_COMMENT_TABLE +#endif + {"", "# "}, /* 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, *buf; + int i, ierrno, err = 0; + mode_t mode; + char *tocvsPath; + char *userfile; + + if (noexec) + return (0); + +#ifdef LINES_CRLF_TERMINATED + /* There exits a port of RCS to such a system that stores files with + straight newlines. If we ever reach this point on such a system, + we'll need to decide what to do with the open_file call below. */ + abort (); +#endif + 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+"); + if (fprcs == NULL) { + ierrno = errno; + goto write_error_noclose; + } + + /* + * putadmin() + */ + if (fprintf (fprcs, "head %s;\n", vhead) < 0 || + fprintf (fprcs, "branch %s;\n", vbranch) < 0 || + fprintf (fprcs, "access ;\n") < 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;\n", vtag, vbranch) < 0 || + fprintf (fprcs, "locks ; strict;\n") < 0 || + /* XXX - make sure @@ processing works in the RCS file */ + fprintf (fprcs, "comment @%s@;\n", get_comment (user)) < 0) + { + goto write_error; + } + + if (keyword_opt != NULL) + if (fprintf (fprcs, "expand @%s@;\n", keyword_opt) < 0) + { + goto write_error; + } + + if (fprintf (fprcs, "\n") < 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, "\n%s\n", vhead) < 0 || + fprintf (fprcs, "date %s; author %s; state Exp;\n", + altdate1, author) < 0 || + fprintf (fprcs, "branches %s.1;\n", vbranch) < 0 || + fprintf (fprcs, "next ;\n") < 0 || + fprintf (fprcs, "\n%s.1\n", vbranch) < 0 || + fprintf (fprcs, "date %s; author %s; state Exp;\n", + altdate2, author) < 0 || + fprintf (fprcs, "branches ;\n") < 0 || + fprintf (fprcs, "next ;\n\n") < 0 || + /* + * putdesc() + */ + fprintf (fprcs, "\ndesc\n") < 0 || + fprintf (fprcs, "@@\n\n\n") < 0 || + /* + * putdelta() + */ + fprintf (fprcs, "\n%s\n", vhead) < 0 || + fprintf (fprcs, "log\n") < 0 || + fprintf (fprcs, "@Initial revision\n@\n") < 0 || + fprintf (fprcs, "text\n@") < 0) + { + goto write_error; + } + + if (sb.st_size > 0) + { + off_t size; + + size = sb.st_size; + buf = xmalloc ((int) size); + if (fread (buf, (int) size, 1, fpuser) != 1) + error (1, errno, "cannot read file %s for copying", user); + if (expand_at_signs (buf, size, fprcs) < 0) + { + free (buf); + goto write_error; + } + free (buf); + } + if (fprintf (fprcs, "@\n\n") < 0 || + fprintf (fprcs, "\n%s.1\n", vbranch) < 0 || + fprintf (fprcs, "log\n@") < 0 || + expand_at_signs (message, (off_t) strlen (message), fprcs) < 0 || + fprintf (fprcs, "@\ntext\n") < 0 || + fprintf (fprcs, "@@\n") < 0) + { + goto write_error; + } + if (fclose (fprcs) == EOF) + { + ierrno = errno; + goto write_error_noclose; + } + (void) fclose (fpuser); + + /* + * Fix the modes on the RCS files. They must maintain the same modes as + * the original user file, except that all write permissions must be + * turned off. + */ + mode = sb.st_mode & ~(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; + + for (cp = buf, end = buf + size; cp < end; cp++) + { + if (*cp == '@') + { + if (putc ('@', fp) == EOF) + return EOF; + } + if (putc (*cp, fp) == EOF) + 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 */ + { + if (repos_len) + (void) printf ("%c %s/%s\n", ch, repository + repos_len + 1, fname); + else if (repository[0]) + (void) printf ("%c %s/%s\n", ch, repository, fname); + else + (void) printf ("%c %s\n", ch, fname); + } + + 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 +#ifdef SERVER_SUPPORT + /* Needs to go on stdout, not stderr, to avoid being interspersed + with the add_log messages. */ + printf ("%s %s: Importing %s\n", + program_name, command_name, repository); +#else + error (0, 0, "Importing %s", repository); +#endif + + 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 + { + if (isfile (repository)) + { + 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 (1); + free_cwd (&cwd); + return (err); +} diff --git a/gnu/usr.bin/cvs/src/lock.c b/gnu/usr.bin/cvs/src/lock.c new file mode 100644 index 00000000000..62bb88d4648 --- /dev/null +++ b/gnu/usr.bin/cvs/src/lock.c @@ -0,0 +1,535 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)lock.c 1.50 94/09/30 $"; +USE(rcsid); +#endif + +extern char *ctime (); + +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 unlock PROTO((char *repository)); +static void lock_wait PROTO((char *repository)); + +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) + { + unlock (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; +{ + unlock (p->key); + return (0); +} + +/* + * Remove the lock files (without complaining if they are not there), + */ +static void +unlock (repository) + char *repository; +{ + char tmp[PATH_MAX]; + struct stat sb; + + if (readlock[0] != '\0') + { + (void) sprintf (tmp, "%s/%s", repository, readlock); + if (unlink (tmp) < 0 && errno != ENOENT) + error (0, errno, "failed to remove lock %s", tmp); + } + + if (writelock[0] != '\0') + { + (void) sprintf (tmp, "%s/%s", repository, writelock); + if (unlink (tmp) < 0 && errno != ENOENT) + 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 (stat (tmp, &sb) != -1 && sb.st_uid == geteuid ()) + { + (void) rmdir (tmp); + } + } + cleanup_lckdir = 0; +} + +/* + * 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.%d", CVSRFL, hostname, +#else + "%s.%d", CVSRFL, +#endif + getpid ()); + + /* remember what we're locking (for lock_cleanup) */ + repository = xrepository; + +#ifdef BOGUS_UNLESS_PROVEN_OTHERWISE + /* make sure we can write the repository */ + (void) sprintf (tmp, +#ifdef HAVE_LONG_FILE_NAMES + "%s/%s.%s.%d", xrepository, CVSTFL, hostname, +#else + "%s/%s.%d", xrepository, CVSTFL, +#endif + getpid()); + if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF) + { + error (0, errno, "cannot create read lock in repository `%s'", + xrepository); + readlock[0] = '\0'; + if (unlink (tmp) < 0 && errno != ENOENT) + error (0, errno, "failed to remove lock %s", tmp); + return (1); + } + if (unlink (tmp) < 0) + error (0, errno, "failed to remove lock %s", tmp); +#endif + + /* 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.%d", CVSWFL, hostname, +#else + "%s.%d", CVSWFL, +#endif + getpid()); + +#ifdef BOGUS_UNLESS_PROVEN_OTHERWISE + /* make sure we can write the repository */ + (void) sprintf (tmp, +#ifdef HAVE_LONG_FILE_NAMES + "%s/%s.%s.%d", repository, CVSTFL, hostname, +#else + "%s/%s.%d", repository, CVSTFL, +#endif + getpid ()); + if ((fp = fopen (tmp, "w+")) == NULL || fclose (fp) == EOF) + { + error (0, errno, "cannot create write lock in repository `%s'", + repository); + if (unlink (tmp) < 0 && errno != ENOENT) + error (0, errno, "failed to remove lock %s", tmp); + return (L_ERROR); + } + if (unlink (tmp) < 0) + error (0, errno, "failed to remove lock %s", tmp); +#endif + + /* 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 && errno != ENOENT) + 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[MAXLINELEN]; + 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 + + (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); + goto again; + } +#endif + set_lockers_name (&sb); + } + + 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; +#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 (;;) + { + SIG_beginCrSect (); + if (CVS_MKDIR (masterlock, 0777) == 0) + { + cleanup_lckdir = 1; + SIG_endCrSect (); + return (L_OK); + } + SIG_endCrSect (); + + 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 (errno == ENOENT) + 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)) + { + 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() +{ + 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); +} diff --git a/gnu/usr.bin/cvs/src/log.c b/gnu/usr.bin/cvs/src/log.c new file mode 100644 index 00000000000..d11757166b6 --- /dev/null +++ b/gnu/usr.bin/cvs/src/log.c @@ -0,0 +1,178 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)log.c 1.44 94/09/30 $"; +USE(rcsid); +#endif + +static Dtype log_dirproc PROTO((char *dir, char *repository, char *update_dir)); +static int log_fileproc PROTO((char *file, char *update_dir, char *repository, + List * entries, List * srcfiles)); + +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]); + +#if 0 +/* 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_file_names (argc - i, argv + i); +#else + send_files (argc - i, argv + i, local, 0); +#endif + + if (fprintf (to_server, "log\n") < 0) + error (1, errno, "writing to server"); + err = get_responses_and_close (); + return err; + } + + ac = argc; + av = argv; +#endif + + err = start_recursion (log_fileproc, (int (*) ()) NULL, log_dirproc, + (int (*) ()) 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 (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + Node *p; + RCSNode *rcsfile; + int retcode = 0; + + p = findnode (srcfiles, file); + if (p == NULL || (rcsfile = (RCSNode *) p->data) == NULL) + { + /* no rcs file. What *do* we know about this file? */ + p = findnode (entries, 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", + file); + return(0); + } + } + + if (!really_quiet) + error (0, 0, "nothing known about %s", file); + + return (1); + } + + run_setup ("%s%s", 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 (*update_dir) + { + char *workfile = xmalloc (strlen (update_dir) + strlen (file) + 2); + sprintf (workfile, "%s/%s", update_dir, 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", 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/gnu/usr.bin/cvs/src/logmsg.c b/gnu/usr.bin/cvs/src/logmsg.c new file mode 100644 index 00000000000..f39dd31e888 --- /dev/null +++ b/gnu/usr.bin/cvs/src/logmsg.c @@ -0,0 +1,459 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)logmsg.c 1.48 94/09/29 $"; +USE(rcsid); +#endif + +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. + * + * rcsinfo - is the name of a file containing lines tacked onto the end of the + * RCS info offered to the user for editing. If specified, the '-m' flag to + * "commit" is disabled -- users are forced to run the editor. + * + */ +void +do_editor (dir, messagep, repository, changes) + char *dir; + char **messagep; + char *repository; + List *changes; +{ + static int reuse_log_message = 0; + char line[MAXLINELEN], fname[L_tmpnam+1]; + struct stat pre_stbuf, post_stbuf; + int retcode = 0; + + if (noexec || reuse_log_message) + return; + + /* 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); + + (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 */ + (void) fclose (fp); + 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 + { + *messagep = (char *) xmalloc (post_stbuf.st_size + 1); + *messagep[0] = '\0'; + } + +/* !!! XXX FIXME: fgets is broken. This should not have any line + length limits. */ + + if (*messagep) + { + while (fgets (line, sizeof (line), fp) != NULL) + { + if (strncmp (line, CVSEDITPREFIX, sizeof (CVSEDITPREFIX) - 1) == 0) + continue; + (void) strcat (*messagep, line); + } + } + (void) fclose (fp); + 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 = '\0'; + (void) fgets (line, sizeof (line), stdin); + if (*line == '\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"); + } + } + (void) unlink_file (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; + char line[MAXLINELEN]; + + /* 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) + { + while (fgets (line, sizeof (line), tfp) != NULL) + (void) fputs (line, fp); + (void) fclose (tfp); + return (0); + } + else + { + error (0, 0, "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, *Popen (); + char *prog = xmalloc (MAXPROGLEN); + char *cp; + int c; + + /* XXX <woods@web.net> -- this is gross, ugly, and a hack! FIXME! */ + /* + * A maximum of 6 %s arguments are supported in the filter + */ + (void) sprintf (prog, filter, title, title, title, title, title, title); + if ((pipefp = 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); + return (pclose (pipefp)); +} + +/* + * 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/gnu/usr.bin/cvs/src/main.c b/gnu/usr.bin/cvs/src/main.c new file mode 100644 index 00000000000..cdf83511618 --- /dev/null +++ b/gnu/usr.bin/cvs/src/main.c @@ -0,0 +1,706 @@ +/* + * 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 + * 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" +#include "patchlevel.h" + +#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 + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)main.c 1.78 94/10/07 $\n"; +USE(rcsid); +#endif + +char *program_name; +char *command_name = ""; + +/* + * Since some systems don't define this... + */ +#ifndef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif + +char hostname[MAXHOSTNAMELEN]; + +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; + +char *CurDir; + +/* + * Defaults, for the environment variables that are not set + */ +char *Rcsbin = RCSBIN_DFLT; +char *Editor = EDITOR_DFLT; +char *CVSroot = CVSROOT_DFLT; +#ifdef CVSADM_ROOT +/* + * The path found in CVS/Root must match $CVSROOT and/or 'cvs -d root' + */ +char *CVSADM_Root = CVSROOT_DFLT; +#endif /* CVSADM_ROOT */ + +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)); + +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), +#ifndef CVS_NOADMIN + CMD_ENTRY("admin", "adm", "rcs", admin, client_admin), +#endif + 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("export", "exp", "ex", checkout, client_export), + CMD_ENTRY("history", "hi", "his", history, client_history), + CMD_ENTRY("import", "im", "imp", import, client_import), + CMD_ENTRY("log", "lo", "rlog", cvslog, client_log), + 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("update", "up", "upd", update, client_update), + +#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 + "\n", + " and where 'command' is:\n", + " add Adds a new file/directory to the repository\n", + " admin Administration front end for rcs\n", + " checkout Checkout sources for editing\n", + " commit Checks files into the repository\n", + " diff Runs diffs between revisions\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", + " 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", + " rtag Add a symbolic tag to the RCS file\n", + " update Brings work tree in sync with repository\n", + NULL, +}; + +static RETSIGTYPE +main_cleanup () +{ + exit (1); +} + +static void +error_cleanup () +{ + Lock_Cleanup(); +#ifdef SERVER_SUPPORT + if (server_active) + server_cleanup (0); +#endif +} + +#define KF_GETOPT_LONG 1 + +int +main (argc, argv) + int argc; + char **argv; +{ + extern char *version_string; + extern char *config_string; + char *cp; + const struct cmd *cm; + int c, err = 0; + static int help = FALSE, version_flag = FALSE; + int rcsbin_update_env, cvs_update_env = 0; + char tmp[PATH_MAX]; + static struct option long_options[] = + { + {"help", 0, &help, TRUE}, + {"version", 0, &version_flag, 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); + + /* + * Just save the last component of the path for error messages + */ + 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; + + /* 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; + + while ((c = getopt_long + (argc, argv, "Qqrwtnlvb:e:d:Hfz:", 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; +#ifdef CLIENT_SUPPORT + case 'z': + gzip_level = atoi (optarg); + if (gzip_level <= 0 || gzip_level > 9) + error (1, 0, + "gzip compression level must be between 1 and 9"); + break; +#endif + case '?': + default: + usage (usg); + } + } + + if (version_flag == TRUE) + { + (void) fputs (version_string, stdout); + (void) fputs (config_string, stdout); + (void) sprintf (tmp, "Patch Level: %d\n", PATCHLEVEL); + (void) fputs (tmp, 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); + } + + 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 (1); + } + + 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 (1); + } + + /* 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 (1); + } + + pw = getpwnam (user); + if (pw == NULL) + { + printf ("E Fatal error, aborting.\n\ +error 0 %s: no such user\n", user); + exit (1); + } + + 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 */ + +#ifdef CVSADM_ROOT + /* + * 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 + else +#endif + { + /* + * 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 "); + } + } + } +#endif /* CVSADM_ROOT */ + + /* + * 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 (access (path, R_OK | X_OK)) + { + save_errno = errno; +#ifdef CLIENT_SUPPORT + if (strchr (CVSroot, ':') == NULL) + { +#endif + error (0, 0, + "Sorry, you don't have sufficient access to %s", CVSroot); + error (1, save_errno, "%s", path); +#ifdef CLIENT_SUPPORT + } +#endif + } + (void) strcat (path, "/"); + (void) strcat (path, CVSROOTADM_HISTORY); + if (isfile (path) && access (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); + +#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 */ + } + /* + * If the command's error count is modulo 256, we need to change it + * so that we don't overflow the 8-bits we get to report exit status + */ + if (err && (err % 256) == 0) + err = 1; + Lock_Cleanup (); + return (err); +} + +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 (1); +} diff --git a/gnu/usr.bin/cvs/src/mkmodules.c b/gnu/usr.bin/cvs/src/mkmodules.c new file mode 100644 index 00000000000..c4453588921 --- /dev/null +++ b/gnu/usr.bin/cvs/src/mkmodules.c @@ -0,0 +1,434 @@ +/* + * 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. + * + * mkmodules + * + * Re-build the modules database for the CVS system. Accepts one argument, + * which is the directory that the modules,v file lives in. + */ + +#include "cvs.h" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)mkmodules.c 1.45 94/09/30 $"; +USE(rcsid); +#endif + +#ifndef DBLKSIZ +#define DBLKSIZ 4096 /* since GNU ndbm doesn't define it */ +#endif + +char *program_name, *command_name; + +char *Rcsbin = RCSBIN_DFLT; +char *CVSroot = CVSROOT_DFLT; +int noexec = 0; /* Here only to satisfy use in subr.c */ +int trace = 0; /* Here only to satisfy use in subr.c */ + +static int checkout_file PROTO((char *file, char *temp)); +static void make_tempfile PROTO((char *temp)); +static void mkmodules_usage PROTO((void)); +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 */ + + +int +main (argc, argv) + int argc; + char **argv; +{ + char temp[PATH_MAX]; + char *cp, *last, *fname; +#ifdef MY_NDBM + DBM *db; +#endif + FILE *fp; + char line[512]; + static struct _checkout_file { + char *filename; + char *errormsg; + } *fileptr, filelist[] = { + {CVSROOTADM_LOGINFO, + "no logging of 'cvs commit' messages is done without a %s file"}, + {CVSROOTADM_RCSINFO, + "a %s file can be used to configure 'cvs commit' templates"}, + {CVSROOTADM_EDITINFO, + "a %s file can be used to validate log messages"}, + {CVSROOTADM_COMMITINFO, + "a %s file can be used to configure 'cvs commit' checking"}, + {CVSROOTADM_TAGINFO, + "a %s file can be used to configure 'cvs tag' checking"}, + {CVSROOTADM_IGNORE, + "a %s file can be used to specify files to ignore"}, + {CVSROOTADM_CHECKOUTLIST, + "a %s file can specify extra CVSROOT files to auto-checkout"}, + {CVSROOTADM_WRAPPER, + "a %s file can be used to specify files to treat as wrappers"}, + {NULL, NULL}}; + + /* + * Just save the last component of the path for error messages + */ + program_name = last_component (argv[0]); + + if (argc != 2) + mkmodules_usage (); + + if ((cp = getenv (RCSBIN_ENV)) != NULL) + Rcsbin = cp; + + /* + * If Rcsbin is set to something, make sure it is terminated with a slash + * character. If not, add one. + */ + if (Rcsbin[0] != '\0') + { + 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, "/"); + } + } + + if (chdir (argv[1]) < 0) + error (1, errno, "cannot chdir to %s", argv[1]); + + /* + * 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 (1); + /* 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++) { + 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); + } + + 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 -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 */ +} + +/* + * For error() only + */ +void +Lock_Cleanup () +{ +} + +int server_active = 0; + +void +server_cleanup () +{ +} + +static void +mkmodules_usage () +{ + (void) fprintf (stderr, "Usage: %s modules-directory\n", program_name); + exit (1); +} diff --git a/gnu/usr.bin/cvs/src/modules.c b/gnu/usr.bin/cvs/src/modules.c new file mode 100644 index 00000000000..9dcce131473 --- /dev/null +++ b/gnu/usr.bin/cvs/src/modules.c @@ -0,0 +1,870 @@ +/* + * 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 "save-cwd.h" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)modules.c 1.62 94/09/29 $"; +USE(rcsid); +#endif + +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; + int (*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[MAXLINELEN]; + char *xmodargv[MAXFILEPERDIR]; + char **modargv; + char *value; + char *zvalue; + char *mwhere = NULL; + char *mfile = NULL; + char *spec_opt = NULL; + char xvalue[PATH_MAX]; + int modargc, alias = 0; + datum key, val; + char *cp; + int c, err = 0; + +#ifdef SERVER_SUPPORT + if (trace) + { + fprintf (stderr, "%c-> do_module (%s, %s, %s, %s)\n", + (server_active) ? 'S' : ' ', + mname, msg, where ? where : "", + extra_arg ? extra_arg : ""); + } +#endif + + /* remember where we start */ + if (save_cwd (&cwd)) + exit (1); + + /* 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: + + /* 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)) + (void) CVS_MKDIR (nullrepos, 0777); + 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 */ + (void) sprintf (line, "%s %s", "XXX", value); + + /* turn the line into an argv[] array */ + line2argv (&modargc, xmodargv, line); + 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; + 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); + 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); + 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); + return (err); + } + + /* otherwise, process this module */ + err += callback_proc (&modargc, modargv, where, mwhere, mfile, shorten, + local_specified, mname, msg); + + /* clean up */ + free_names (&modargc, modargv); + + /* 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 (1); + 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[MAXLINELEN], *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 */ + (void) sprintf (line, "%s %s", s_h->modname, s_h->rest); + line2argv (&moduleargc, moduleargv, line); + argc = moduleargc; + argv = moduleargv; + + optind = 1; + 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); + } + } +} diff --git a/gnu/usr.bin/cvs/src/myndbm.c b/gnu/usr.bin/cvs/src/myndbm.c new file mode 100644 index 00000000000..fef326576bd --- /dev/null +++ b/gnu/usr.bin/cvs/src/myndbm.c @@ -0,0 +1,213 @@ +/* + * 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 "cvs.h" + +#ifdef MY_NDBM + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)myndbm.c 1.7 94/09/23 $"; +USE(rcsid); +#endif + +static void mydbm_load_file (); + +/* ARGSUSED */ +DBM * +mydbm_open (file, flags, mode) + char *file; + int flags; + int mode; +{ + FILE *fp; + DBM *db; + + if ((fp = fopen (file, "r")) == NULL) + return ((DBM *) 0); + + db = (DBM *) xmalloc (sizeof (*db)); + db->dbm_list = getlist (); + + mydbm_load_file (fp, db->dbm_list); + (void) fclose (fp); + return (db); +} + +void +mydbm_close (db) + DBM *db; +{ + 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); +} + +static void +mydbm_load_file (fp, list) + FILE *fp; + List *list; +{ + char line[MAXLINELEN], value[MAXLINELEN]; + char *cp, *vp; + int len, cont; + + 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) + { + 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); + } + } + } +} + +#endif /* MY_NDBM */ diff --git a/gnu/usr.bin/cvs/src/myndbm.h b/gnu/usr.bin/cvs/src/myndbm.h new file mode 100644 index 00000000000..3af31305506 --- /dev/null +++ b/gnu/usr.bin/cvs/src/myndbm.h @@ -0,0 +1,36 @@ +/* $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() */ +} 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 + +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)); + +#endif /* MY_NDBM */ diff --git a/gnu/usr.bin/cvs/src/no_diff.c b/gnu/usr.bin/cvs/src/no_diff.c new file mode 100644 index 00000000000..281d34866bc --- /dev/null +++ b/gnu/usr.bin/cvs/src/no_diff.c @@ -0,0 +1,135 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)no_diff.c 1.39 94/10/07 $"; +USE(rcsid); +#endif + +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 (""); + + run_setup ("%s%s -p -q -r%s %s", Rcsbin, RCS_CO, + vers->vn_user ? vers->vn_user : "", options); + run_arg (vers->srcfile->path); + if ((retcode = run_exec (RUN_TTY, tmpnam (tmp), RUN_TTY, RUN_REALLY)) == 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/gnu/usr.bin/cvs/src/options.h.in b/gnu/usr.bin/cvs/src/options.h.in new file mode 100644 index 00000000000..de55040ea72 --- /dev/null +++ b/gnu/usr.bin/cvs/src/options.h.in @@ -0,0 +1,228 @@ +/* + * 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. If you are + * using the GNU version of diff (version 1.15 or later), this should + * be "diff -a". + * + * 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 "@gdiff_path@" +#endif + +/* + * The "grep" program to execute when checking to see if a merged file had + * any conflicts. This "grep" must support the "-s" option and a standard + * regular expression as an argument. Specify a full pathname if your site + * wants to use a particular grep. + */ + +#ifndef GREP +#define GREP "@ggrep_path@" +#endif + +/* + * The "rm" program to execute when pruning directories that are not part of + * a release. This "rm" must support the "-fr" options. Specify a full + * pathname if your site wants to use a particular rm. + */ +#ifndef RM +#define RM "rm" +#endif + +/* + * The "sort" program to execute when displaying the module database. Specify + * a full pathname if your site wants to use a particular sort. + */ +#ifndef SORT +#define SORT "sort" +#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. + * + * 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 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 admin" command allows people to get around most of the logging + * and info procedures within CVS. For exmaple, "cvs tag tagname filename" + * will perform some validity checks on the tag, while "cvs admin -Ntagname" + * will not perform those checks. For this reason, some sites may wish to + * disable the admin function completely. + * + * To disable the admin function, uncomment the lines below. + */ +#ifndef CVS_NOADMIN +/* #define CVS_NOADMIN */ +#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 + +/* 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/gnu/usr.bin/cvs/src/parseinfo.c b/gnu/usr.bin/cvs/src/parseinfo.c new file mode 100644 index 00000000000..d19e774f294 --- /dev/null +++ b/gnu/usr.bin/cvs/src/parseinfo.c @@ -0,0 +1,196 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)parseinfo.c 1.18 94/09/23 $"; +USE(rcsid); +#endif + +/* + * 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; + int (*callproc) (); + int all; +{ + int err = 0; + FILE *fp_info; + char infopath[PATH_MAX]; + char line[MAXLINELEN]; + char *default_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'; + + /* FIXME: probably should allow multiple occurrences of CVSROOT. */ + /* FIXME-maybe: perhaps should allow CVSREAD and other cvs + settings (if there is a need for them, which isn't clear). */ + /* FIXME-maybe: Should there be a way to substitute arbitrary + environment variables? Probably not, because then what gets + substituted would depend on who runs cvs. A better feature might + be to allow a file in CVSROOT to specify variables to be + substituted. */ + { + char *p, envname[128]; + + strcpy(envname, "$"); + /* FIXME: I'm not at all sure this should be CVSROOT_ENV as opposed + to literal CVSROOT. The value we subsitute is the cvs root + in use which is not the same thing as the environment variable + CVSROOT_ENV. */ + strcat(envname, CVSROOT_ENV); + + cp = xstrdup(value); + if ((p = strstr(cp, envname))) { + if (strlen(line) + strlen(CVSroot) + 1 > MAXLINELEN) { + /* FIXME: there is no reason for this arbitrary limit. */ + error(0, 0, + "line %d in %s too long to expand $CVSROOT, ignored", + line_number, infofile); + continue; + } + if (p > cp) { + strncpy(value, cp, p - cp); + value[p - cp] = '\0'; + strcat(value, CVSroot); + } else + strcpy(value, CVSroot); + strcat(value, p + strlen(envname)); + } + free(cp); + } + + /* + * 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 (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, 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, 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); + + return (err); +} diff --git a/gnu/usr.bin/cvs/src/patch.c b/gnu/usr.bin/cvs/src/patch.c new file mode 100644 index 00000000000..f9e9f3a9f1b --- /dev/null +++ b/gnu/usr.bin/cvs/src/patch.c @@ -0,0 +1,601 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)patch.c 1.57 94/09/30 $"; +USE(rcsid); +#endif + +static RETSIGTYPE patch_cleanup PROTO((void)); +static Dtype patch_dirproc PROTO((char *dir, char *repos, char *update_dir)); +static int patch_fileproc PROTO((char *file, char *update_dir, char *repository, + List * entries, List * srcfiles)); +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 char *rev2 = NULL; +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]); + } + + if (fprintf (to_server, "rdiff\n") < 0) + error (1, errno, "writing to server"); + 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; + + /* start the recursion processor */ + err = start_recursion (patch_fileproc, (int (*) ()) NULL, patch_dirproc, + (int (*) ()) 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 (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + struct utimbuf t; + char *vers_tag, *vers_head; + char rcsspace[PATH_MAX]; + char *rcs = rcsspace; + Node *p; + 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[MAXLINELEN], line2[MAXLINELEN]; + char *cp1, *cp2, *commap; + FILE *fp; + + /* find the parsed rcs file */ + p = findnode (srcfiles, file); + if (p == NULL) + return (1); + rcsfile = (RCSNode *) p->data; + if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC)) + isattic = 1; + + (void) sprintf (rcs, "%s%s", 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); + + 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); + + 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 "); + if (vers_tag == NULL) + (void) printf ("%s is new; current revision %s\n", rcs, vers_head); + else if (vers_head == NULL) +#ifdef DEATH_SUPPORT + { + (void) printf ("%s is removed; not included in ", rcs); + 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 ("%s is removed; not included in release %s\n", + rcs, rev2 ? rev2 : date2); +#endif + else + (void) printf ("%s changed from revision %s to %s\n", + rcs, 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) + { + run_setup ("%s%s %s -p -q -r%s", Rcsbin, RCS_CO, options, vers_tag); + run_arg (rcsfile->path); + if ((retcode = run_exec (RUN_TTY, tmpfile1, RUN_TTY, RUN_NORMAL)) != 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) + { + run_setup ("%s%s %s -p -q -r%s", Rcsbin, RCS_CO, options, vers_head); + run_arg (rcsfile->path); + if ((retcode = run_exec (RUN_TTY, tmpfile2, RUN_TTY, RUN_NORMAL)) != 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); + switch (run_exec (RUN_TTY, tmpfile3, RUN_TTY, RUN_NORMAL)) + { + 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); + if (update_dir[0]) + (void) printf ("Index: %s/%s\n", update_dir, file); + else + (void) printf ("Index: %s\n", file); + (void) fflush (stdout); + + fp = open_file (tmpfile3, "r"); + if (fgets (line1, sizeof (line1), fp) == NULL || + fgets (line2, sizeof (line2), fp) == NULL) + { + 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); + commap = strrchr (rcs, ','); + *commap = '\0'; + if (vers_tag != NULL) + { + (void) sprintf (file1, "%s%s%s:%s", update_dir, + update_dir[0] ? "/" : "", rcs, vers_tag); + } + else + { + (void) strcpy (file1, DEVNULL); + } + (void) sprintf (file2, "%s%s%s:%s", update_dir, + update_dir[0] ? "/" : "", rcs, + vers_head ? vers_head : "removed"); + 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); + } + + if (update_dir[0] != '\0') + (void) printf ("%s/", update_dir); + (void) printf ("%s%s", rcs, cp2); + while (fgets (line1, sizeof (line1), fp) != NULL) + (void) printf ("%s", line1); + (void) fclose (fp); + break; + default: + error (0, 0, "diff failed for %s", rcs); + } + out: + (void) unlink_file (tmpfile1); + (void) unlink_file (tmpfile2); + (void) unlink_file (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/gnu/usr.bin/cvs/src/patchlevel.h b/gnu/usr.bin/cvs/src/patchlevel.h new file mode 100644 index 00000000000..50d3863dcd4 --- /dev/null +++ b/gnu/usr.bin/cvs/src/patchlevel.h @@ -0,0 +1 @@ +#define PATCHLEVEL 2 diff --git a/gnu/usr.bin/cvs/src/rcs.c b/gnu/usr.bin/cvs/src/rcs.c new file mode 100644 index 00000000000..9722b338bfb --- /dev/null +++ b/gnu/usr.bin/cvs/src/rcs.c @@ -0,0 +1,1704 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)rcs.c 1.40 94/10/07 $"; +USE(rcsid); +#endif + +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 parse_rcs_proc PROTO((Node * file, void *closure)); +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 null_delproc PROTO((Node * p)); +static void rcsnode_delproc PROTO((Node * p)); +static void rcsvers_delproc PROTO((Node * p)); + +static List *rcslist; +static char *repository; + +/* + * 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 all the rcs files specified and return a list + */ +List * +RCS_parsefiles (files, xrepos) + List *files; + char *xrepos; +{ + /* initialize */ + repository = xrepos; + rcslist = getlist (); + + /* walk the list parsing files */ + if (walklist (files, parse_rcs_proc, NULL) != 0) + { + /* free the list and return NULL on error */ + dellist (&rcslist); + return ((List *) NULL); + } + else + /* return the list we built */ + return (rcslist); +} + +/* + * Parse an rcs file into a node on the rcs list + */ +static int +parse_rcs_proc (file, closure) + Node *file; + void *closure; +{ + RCSNode *rdata; + + /* parse the rcs file into rdata */ + rdata = RCS_parse (file->key, repository); + + /* if we got a valid RCSNode back, put it on the list */ + if (rdata != (RCSNode *) NULL) + RCS_addnode (file->key, rdata, rcslist); + + return (0); +} + +/* + * Add an RCSNode to a list of them. + */ + +void +RCS_addnode (file, rcs, list) + const char *file; + RCSNode *rcs; + List *list; +{ + Node *p; + + p = getnode (); + p->key = xstrdup (file); + p->delproc = rcsnode_delproc; + p->type = RCSNODE; + p->data = (char *) rcs; + (void) addnode (list, p); +} + + +/* + * 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]; + +#ifdef LINES_CRLF_TERMINATED + /* Some ports of RCS to Windows NT write RCS files with newline- + delimited lines. We would need to pass fopen a "binary" flag. */ + abort (); +#endif + + (void) sprintf (rcsfile, "%s/%s%s", repos, file, RCSEXT); + if ((fp = fopen (rcsfile, "r")) != NULL) + { + rcs = RCS_parsercsfile_i(fp, rcsfile); + if (rcs != NULL) + rcs->flags |= VALID; + + fclose (fp); + return (rcs); + } + else if (errno != ENOENT) + { + error (0, errno, "cannot open %s", rcsfile); + return NULL; + } + +#ifdef LINES_CRLF_TERMINATED + /* Some ports of RCS to Windows NT write RCS files with newline- + delimited lines. We would need to pass fopen a "binary" flag. */ + abort (); +#endif + + (void) sprintf (rcsfile, "%s/%s/%s%s", repos, CVSATTIC, file, RCSEXT); + if ((fp = fopen (rcsfile, "r")) != NULL) + { + rcs = RCS_parsercsfile_i(fp, rcsfile); + if (rcs != NULL) + { + rcs->flags |= INATTIC; + rcs->flags |= VALID; + } + + fclose (fp); + return (rcs); + } + else if (errno != ENOENT) + { + 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; + +#ifdef LINES_CRLF_TERMINATED + /* Some ports of RCS to Windows NT write RCS files with newline- + delimited lines. We would need to pass fopen a "binary" flag. */ + abort (); +#endif + + /* open the rcsfile */ + if ((fp = fopen (rcsfile, "r")) == 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 (RCSHEAD, key) == 0 && value != NULL) + rdata->head = xstrdup (value); + + if (getrcskey (fp, &key, &value) == -1 || key == NULL) + 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 + * + * There are no allowances for error here. + */ +void +RCS_reparsercsfile (rdata) + RCSNode *rdata; +{ + FILE *fp; + char *rcsfile; + + Node *q, *r; + RCSVers *vnode; + int n; + char *cp; + char *key, *value; + + rcsfile = rdata->path; + +#ifdef LINES_CRLF_TERMINATED + /* Some ports of RCS to Windows NT write RCS files with newline- + delimited lines. We would need to pass fopen a "binary" flag. */ + abort (); +#endif + + fp = fopen(rcsfile, "r"); + if (fp == NULL) + error (1, 0, "unable to reopen `%s'", rcsfile); + + /* make a node */ + rdata->versions = getlist (); + rdata->dates = 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) + { + 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; + } + } + + /* + * 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; + char date[MAXDATELEN]; + + /* 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++; + (void) strcpy (date, valp); + + /* get the nodes (q is by version, r is by date) */ + q = getnode (); + r = getnode (); + q->type = RCSVERS; + r->type = RCSVERS; + q->delproc = rcsvers_delproc; + r->delproc = null_delproc; + q->data = r->data = xmalloc (sizeof (RCSVers)); + memset (q->data, 0, sizeof (RCSVers)); + vnode = (RCSVers *) q->data; + + /* fill in the version before we forget it */ + q->key = vnode->version = xstrdup (key); + + /* throw away the author field */ + (void) getrcskey (fp, &key, &value); + + /* throw away the state field */ + (void) getrcskey (fp, &key, &value); +#ifdef DEATH_SUPPORT + /* Accept this regardless of DEATH_STATE, so that we can read + repositories created with different versions of CVS. */ + if (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; + } +#endif + + /* fill in the date field */ + r->key = vnode->date = xstrdup (date); + + /* fill in the branch list (if any branches exist) */ + (void) getrcskey (fp, &key, &value); + 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); + 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??? + */ + while ((n = getrcskey (fp, &key, &value)) >= 0) + { +#ifdef DEATH_SUPPORT + /* Enable use of repositories created with a CVS which defines + DEATH_SUPPORT and not DEATH_STATE. */ + if (strcmp(key, RCSDEAD) == 0) + { + vnode->dead = 1; + continue; + } +#endif + /* 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; + } + + /* add the nodes to the lists */ + (void) addnode (rdata->versions, q); + (void) addnode (rdata->dates, r); + + /* + * if we left the loop because there were no more keys, we break out + * of the revision processing loop + */ + if (n < 0) + break; + } + + fclose (fp); + rdata->flags &= ~PARTIAL; +} + +/* + * rcsnode_delproc - free up an RCS type node + */ +static void +rcsnode_delproc (p) + Node *p; +{ + freercsnode ((RCSNode **) & p->data); +} + +/* + * 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); + dellist (&(*rnodep)->dates); + if ((*rnodep)->symbols != (List *) NULL) + dellist (&(*rnodep)->symbols); + if ((*rnodep)->symbols_data != (char *) NULL) + free ((*rnodep)->symbols_data); + 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->next != (char *) NULL) + free (rnode->next); + free ((char *) rnode); +} + +/* + * null_delproc - don't free anything since it will be free'd by someone else + */ +/* ARGSUSED */ +static void +null_delproc (p) + Node *p; +{ + /* don't do anything */ +} + +/* + * 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" + */ + +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'; + + /* if we got "desc", we are done with the file */ + if (strcmp (RCSDESC, key) == 0) + { + *keyp = (char *) NULL; + *valp = (char *) NULL; + return (-1); + } + + /* 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; + } + } + + /* 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); +} + +/* + * 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) + RCSNode *rcs; + char *tag; + char *date; + int force_tag_match; +{ + /* make sure we have something to look at... */ + if (rcs == NULL) + return ((char *) 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); + 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)); + 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, tag, force_tag_match) + RCSNode *rcs; + char *tag; + int force_tag_match; +{ + Node *p; + + /* make sure we have something to look at... */ + if (rcs == NULL) + return ((char *) NULL); + + /* XXX this is probably not necessary, --jtc */ + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs); + + /* 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) + 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 a list of RCSNodes, returns non-zero if the specified + * revision number or symbolic tag resolves to a "branch" within the + * rcs file. + */ +int +RCS_isbranch (file, rev, srcfiles) + char *file; + char *rev; + List *srcfiles; +{ + Node *p; + RCSNode *rcs; + + /* 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 */ + p = findnode (srcfiles, file); + if (p == NULL) + return (0); + + /* now, look for a match in the symbols list */ + rcs = (RCSNode *) p->data; + return (RCS_nodeisbranch (rev, rcs)); +} + +/* + * 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 (rev, rcs) + char *rev; + RCSNode *rcs; +{ + 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 (file, rev, srcfiles) + char *file; + char *rev; + List *srcfiles; +{ + int dots; + Node *p; + RCSNode *rcs; + + /* assume no branch if you can't find the RCS info */ + p = findnode (srcfiles, file); + if (p == NULL) + return ((char *) NULL); + + /* now, look for a match in the symbols list */ + rcs = (RCSNode *) p->data; + 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... */ + if (rcs == NULL) + return ((char *) NULL); + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs); + + /* 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... */ + if (rcs == NULL) + return ((char *) NULL); + + if (rcs->branch) + return (RCS_getbranch (rcs, rcs->branch, 1)); + + /* + * NOTE: we call getbranch with force_tag_match set to avoid any + * possibility of recursion + */ + 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... */ + if (rcs == NULL) + return ((char *) NULL); + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs); + + /* 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); + + 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... */ + if (rcs == NULL) + return (revdate); + + if (rcs->flags & PARTIAL) + RCS_reparsercsfile (rcs); + + /* 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 (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); + + 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); +} + +#ifdef DEATH_SUPPORT +/* + * 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); + + p = findnode (rcs->versions, tag); + if (p == NULL) + return (0); + + version = (RCSVers *) p->data; + return (version->dead); +} +#endif /* DEATH_SUPPORT */ diff --git a/gnu/usr.bin/cvs/src/rcs.h b/gnu/usr.bin/cvs/src/rcs.h new file mode 100644 index 00000000000..d5ac5989c29 --- /dev/null +++ b/gnu/usr.bin/cvs/src/rcs.h @@ -0,0 +1,99 @@ +/* $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_MERGE "merge" +#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 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; + List *symbols; + List *versions; + List *dates; +}; + +typedef struct rcsnode RCSNode; + +struct rcsversnode +{ + char *version; + char *date; + 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 + */ +List *RCS_parsefiles PROTO((List * files, char *xrepos)); +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 *tag, int force_tag_match)); +char *RCS_getversion PROTO((RCSNode * rcs, char *tag, char *date, + int force_tag_match)); +char *RCS_magicrev PROTO((RCSNode *rcs, char *rev)); +int RCS_isbranch PROTO((char *file, char *rev, List *srcfiles)); +int RCS_nodeisbranch PROTO((char *rev, RCSNode *rcs)); +char *RCS_whatbranch PROTO((char *file, char *tag, List *srcfiles)); +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)); +void RCS_addnode PROTO((const char *file, RCSNode *rcs, List *list)); +char *RCS_getbranch PROTO((RCSNode * rcs, char *tag, int force_tag_match)); + +#ifdef DEATH_SUPPORT +int RCS_isdead PROTO((RCSNode *, const char *)); +#endif diff --git a/gnu/usr.bin/cvs/src/rcscmds.c b/gnu/usr.bin/cvs/src/rcscmds.c new file mode 100644 index 00000000000..593cf5c0bba --- /dev/null +++ b/gnu/usr.bin/cvs/src/rcscmds.c @@ -0,0 +1,104 @@ +/* + * 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" + +int +RCS_settag(path, tag, rev) + const char *path; + const char *tag; + const char *rev; +{ + run_setup ("%s%s -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; +{ + run_setup ("%s%s -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 -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 -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; +{ + run_setup ("%s%s -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; + + /* We pass -E to rcsmerge so that it will not indicate a conflict if + both things we are merging are modified the same way. + + Well, okay, but my rcsmerge doesn't take a -E option. --JimB */ + /* XXX - Do merge by hand instead of using rcsmerge, due to -k handling */ + + run_setup ("%s%s %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 -s", GREP); + run_arg (RCS_MERGE_PAT); + run_arg (path); + status = (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL) == 0); + + } +#endif + return status; +} diff --git a/gnu/usr.bin/cvs/src/recurse.c b/gnu/usr.bin/cvs/src/recurse.c new file mode 100644 index 00000000000..fdf972f5a9f --- /dev/null +++ b/gnu/usr.bin/cvs/src/recurse.c @@ -0,0 +1,633 @@ +/* + * 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 "save-cwd.h" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)recurse.c 1.31 94/09/30 $"; +USE(rcsid); +#endif + +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 int (*fileproc) (); +static int (*filesdoneproc) (); +static Dtype (*direntproc) (); +static int (*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 *entries = NULL; +static List *srcfiles = 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 { + int (*fileproc)(); + int (*filesdoneproc) (); + Dtype (*direntproc) (); + int (*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) + int (*fileproc) (); + int (*filesdoneproc) (); + Dtype (*direntproc) (); + int (*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; + + 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 (entries) + { + Entries_Close (entries); + entries = NULL; + } + if (srcfiles) + dellist (&srcfiles); + 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_Dirs ((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; + + dir = xstrdup (argv[i]); + if ((comp = strrchr (dir, '/')) == NULL) + { + /* no dir component. What we have is an implied "./" */ + comp = dir; + dir = xstrdup("."); + } + else + { + char *p = comp; + + *p++ = '\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); + + 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); + + + 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) + int (*xfileproc) (); + int (*xfilesdoneproc) (); + Dtype (*xdirentproc) (); + int (*xdirleaveproc) (); + Dtype xflags; + int xwhich; + int xaflag; + int xreadlock; + int xdosrcs; +{ + int err = 0; + int dodoneproc = 1; + char *srepository; + + /* 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; + + /* + * 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 */ + + /* + * 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_Dirs (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) + { + /* read lock it if necessary */ + if (readlock && repository && Reader_Lock (repository) != 0) + error (1, 0, "read lock failed - giving up"); + + /* pre-parse the source files */ + if (dosrcs && repository) + srcfiles = RCS_parsefiles (filelist, repository); + else + srcfiles = (List *) NULL; + + /* process the files */ + err += walklist (filelist, do_file_proc, NULL); + + /* unlock it */ + if (readlock) + Lock_Cleanup (); + + /* clean up */ + dellist (&filelist); + dellist (&srcfiles); + 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 : "."); + + /* 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; +{ + if (fileproc != NULL) + return (fileproc (p->key, update_dir, repository, entries, srcfiles)); + else + return (0); +} + +/* + * 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 (1); + 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 (1); + free_cwd (&cwd); + dirlist = sdirlist; + repository = srepository; + } + + /* put back update_dir */ + if ((cp = strrchr (update_dir, '/')) != NULL) + *cp = '\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 (1); + 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 (1); + free_cwd (&cwd); + } + + dirlist = save_dirlist; + filelist = NULL; + return(err); +} diff --git a/gnu/usr.bin/cvs/src/release.c b/gnu/usr.bin/cvs/src/release.c new file mode 100644 index 00000000000..987edd0d301 --- /dev/null +++ b/gnu/usr.bin/cvs/src/release.c @@ -0,0 +1,261 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)release.c 1.23 94/09/21 $"; +USE(rcsid); +#endif + +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; + +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; + +#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++; + 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_name, 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 + arg_start_idx = 0; +#endif /* SERVER_SUPPORT */ + + 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. + * + * This is "popen()" instead of "Popen()" since we + * wouldn't want the `noexec' flag to stop it. + */ + fp = 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 ? "(and delete) " : "", thisarg); + c = !yesno (); + if (c) /* "No" */ + { + (void) fprintf (stderr, "** `%s' aborted by user choice.\n", + command_name); + free (repository); + continue; + } + } + +#ifdef CLIENT_SUPPORT + if (client_active) + { + if (fprintf (to_server, "Argument %s\n", thisarg) < 0) + error (1, errno, "writing to server"); + if (fprintf (to_server, "release\n") < 0) + error (1, errno, "writing to server"); + } + 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) 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 */ +} + + +/* 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; + int retcode = 0; + + (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? + */ + run_setup ("%s -fr", RM); + run_arg (dir); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 0) + error (0, retcode == -1 ? errno : 0, + "deletion of module %s failed.", dir); +} diff --git a/gnu/usr.bin/cvs/src/remove.c b/gnu/usr.bin/cvs/src/remove.c new file mode 100644 index 00000000000..95140d66ea0 --- /dev/null +++ b/gnu/usr.bin/cvs/src/remove.c @@ -0,0 +1,206 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)remove.c 1.39 94/10/07 $"; +USE(rcsid); +#endif + +static int remove_fileproc PROTO((char *file, char *update_dir, + char *repository, List *entries, + List *srcfiles)); +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_files (argc, argv, local, 0); + if (fprintf (to_server, "remove\n") < 0) + error (1, errno, "writing to server"); + return get_responses_and_close (); + } +#endif + + /* start the recursion processor */ + err = start_recursion (remove_fileproc, (int (*) ()) NULL, remove_dirproc, + (int (*) ()) 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; use `%s' to remove it first" : + "%d files exist; use `%s' to remove them first"), + existing_files, RM); + + return (err); +} + +/* + * remove the file, only if it has already been physically removed + */ +/* ARGSUSED */ +static int +remove_fileproc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + char fname[PATH_MAX]; + Vers_TS *vers; + + /* + * If unlinking the file works, good. If not, the "unremoved" + * error will indicate problems. + */ + if (force) + (void) unlink (file); + + vers = Version_TS (repository, (char *) NULL, (char *) NULL, (char *) NULL, + file, 0, 0, entries, srcfiles); + + if (vers->ts_user != NULL) + { + existing_files++; + if (!quiet) + error (0, 0, "file `%s' still in working directory", file); + } + else if (vers->vn_user == NULL) + { + if (!quiet) + error (0, 0, "nothing known about `%s'", file); + } + 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 ,p and ,t file for it and scratch it from the + * entries file. + */ + Scratch_Entry (entries, file); + (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_OPT); + (void) unlink_file (fname); + (void) sprintf (fname, "%s/%s%s", CVSADM, file, CVSEXT_LOG); + (void) unlink_file (fname); + if (!quiet) + error (0, 0, "removed `%s'", file); + +#ifdef SERVER_SUPPORT + if (server_active) + server_checked_in (file, update_dir, repository); +#endif + } + else if (vers->vn_user[0] == '-') + { + if (!quiet) + error (0, 0, "file `%s' already scheduled for removal", file); + } + else + { + /* Re-register it with a negative version number. */ + (void) strcpy (fname, "-"); + (void) strcat (fname, vers->vn_user); + Register (entries, file, fname, vers->ts_rcs, vers->options, + vers->tag, vers->date, vers->ts_conflict); + if (!quiet) + error (0, 0, "scheduling `%s' for removal", file); + removed_files++; + +#ifdef SERVER_SUPPORT + if (server_active) + server_checked_in (file, update_dir, 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/gnu/usr.bin/cvs/src/repos.c b/gnu/usr.bin/cvs/src/repos.c new file mode 100644 index 00000000000..85664334afe --- /dev/null +++ b/gnu/usr.bin/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. + * + * Name of Repository + * + * Determine the name of the RCS repository and sets "Repository" accordingly. + */ + +#include "cvs.h" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)repos.c 1.32 94/09/23 $"; +USE(rcsid); +#endif + +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/gnu/usr.bin/cvs/src/root.c b/gnu/usr.bin/cvs/src/root.c new file mode 100644 index 00000000000..8b7c83807cb --- /dev/null +++ b/gnu/usr.bin/cvs/src/root.c @@ -0,0 +1,179 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)root.c,v 1.2 1994/09/15 05:32:17 zoo Exp"; +USE(rcsid); +#endif + +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 possiible 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 + if (root[0] != '/') +#endif + { + 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 + if (!isdir (root)) +#endif + { + 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]; + + /* 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/gnu/usr.bin/cvs/src/rtag.c b/gnu/usr.bin/cvs/src/rtag.c new file mode 100644 index 00000000000..238ff7db595 --- /dev/null +++ b/gnu/usr.bin/cvs/src/rtag.c @@ -0,0 +1,692 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)rtag.c 1.61 94/09/30 $"; +USE(rcsid); +#endif + +static int check_fileproc PROTO((char *file, char *update_dir, + char *repository, List * entries, + List * srcfiles)); +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((char *file, char *update_dir, + char *repository, List * entries, + List * srcfiles)); +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 delete; /* 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 = 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 && 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) + send_arg("-d"); + if (branch_mode) + send_arg("-b"); + if (force_tag_move) + send_arg("-T"); + 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]); + } + + if (fprintf (to_server, "rtag\n") < 0) + error (1, errno, "writing to server"); + 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 ? "D" : (numtag ? numtag : + (date ? date : "A"))), symtag, argv[i], ""); + err += do_module (db, argv[i], TAG, delete ? "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 || attic_too || (force_tag_match && numtag)) + which = W_REPOS | W_ATTIC; + else + which = W_REPOS; + + /* 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, + (Dtype (*) ()) NULL, (int (*) ()) 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, (int (*) ()) NULL, rtag_dirproc, + (int (*) ()) 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(file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List * entries; + List * srcfiles; +{ + char *xdir; + Node *p; + Vers_TS *vers; + + if (update_dir[0] == '\0') + xdir = "."; + else + xdir = 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 (file); + p->type = UPDATE; + p->delproc = tag_delproc; + vers = Version_TS (repository, (char *) NULL, (char *) NULL, + (char *) NULL, file, 0, 0, entries, srcfiles); + p->data = RCS_getversion(vers->srcfile, numtag, date, force_tag_match); + if (p->data != NULL) + { + int addit = 1; + char *oversion; + + oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1); + if (oversion == NULL) + { + if (delete) + { + 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 ? "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 (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + Node *p; + RCSNode *rcsfile; + char *version, *rev; + int retcode = 0; + + /* find the parsed RCS data */ + p = findnode (srcfiles, file); + if (p == NULL) + return (1); + rcsfile = (RCSNode *) p->data; + + /* + * 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) + 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); + 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 Version_Number 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 *) 0, 1); + if (oversion != NULL) + { + int isbranch = RCS_isbranch (file, symtag, srcfiles); + + /* + * 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 */ + if (update_dir[0]) + (void) printf ("W %s/%s", update_dir, file); + else + (void) printf ("W %s", file); + + (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); + free (version); + return (1); + } + free (version); + return (0); +} + +/* + * If -d is specified, "force_tag_match" is set, so that this call to + * Version_Number() 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 *) 0, 1); + if (version == NULL) + return (0); + free (version); + } + + version = RCS_getversion (rcsfile, symtag, (char *) 0, 1); + 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 ? "Untagging" : "Tagging", update_dir); + return (R_PROCESS); +} + + + diff --git a/gnu/usr.bin/cvs/src/run.c b/gnu/usr.bin/cvs/src/run.c new file mode 100644 index 00000000000..ebc26ae42d8 --- /dev/null +++ b/gnu/usr.bin/cvs/src/run.c @@ -0,0 +1,537 @@ +/* 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 _MINIX +#undef POSIX /* Minix 1.6 doesn't support POSIX.1 sigaction yet */ +#endif + +#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 + 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 + 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 + 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; + } + 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; + + /* restore the signals */ +#ifdef POSIX + 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 * +Popen (cmd, mode) + const char *cmd; + const char *mode; +{ + if (trace) +#ifdef SERVER_SUPPORT + (void) fprintf (stderr, "%c-> Popen(%s,%s)\n", + (server_active) ? 'S' : ' ', cmd, mode); +#else + (void) fprintf (stderr, "-> 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 + */ + +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/gnu/usr.bin/cvs/src/sanity.sh b/gnu/usr.bin/cvs/src/sanity.sh new file mode 100644 index 00000000000..1d6ea42ed3d --- /dev/null +++ b/gnu/usr.bin/cvs/src/sanity.sh @@ -0,0 +1,1506 @@ +#!/bin/sh +# a quick sanity test for cvs. +# +# Copyright (C) 1992, 1993 Cygnus Support +# +# Original Author: K. Richard Pixley + +# usage: sanity.sh [-r] @var{cvs-to-test} +# -r means to test remote instead of local cvs. + +# See TODO list at end of file. + +TESTDIR=/tmp/cvs-sanity + +# "debugger" +#set -x + +echo This test should produce no other output than this line, and "Ok." + +# clean any old remnants +rm -rf ${TESTDIR} + +if test x"$1" = x"-r"; then + shift + remote=yes +else + remote=no +fi + +testcvs=$1; shift + +# Remaining arguments are the names of tests to run. +if test x"$*" = x; then + tests="basic0 basic1 basic2 basic3 rtags death import new conflicts modules mflag errmsg1" +else + tests="$*" +fi + +# 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}. +CVS="${testcvs} -Q" + +LOGFILE=`pwd`/check.log +if test -f check.log; then mv check.log check.plog; fi + +mkdir ${TESTDIR} +cd ${TESTDIR} + +# so far so good. Let's try something harder. + +# 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 + +# This one should succeed. No warnings. +echo 'CVSROOT -i mkmodules CVSROOT' > cvsroot/CVSROOT/modules +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 + +cd .. ; rm -rf tmp + +# Try setting CVSROOT so we don't have to worry about it anymore. (now that +# we've tested -d cvsroot.) +CVSROOT_FILENAME=`pwd`/cvsroot +CVSROOT=${CVSROOT_FILENAME} ; export CVSROOT +if test "x$remote" = xyes; then + CVSROOT=`hostname`:${CVSROOT_FILENAME} ; 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 + +mkdir tmp ; cd tmp +if ${CVS} -d `pwd`/../cvsroot co CVSROOT ; then + echo "PASS: test 5" >>${LOGFILE} +else + echo "FAIL: test 5" | tee -a ${LOGFILE}; exit 1 +fi + +cd .. ; rm -rf tmp + +# start keeping history +touch ${CVSROOT_FILENAME}/CVSROOT/history + +### The big loop +for what in $tests; do + case $what in + basic0) # Now, let's build something. +# mkdir first-dir + # this doesn't yet work, though I think maybe it should. xoxorich. +# if ${CVS} add first-dir ; then +# true +# else +# echo cvs does not yet add top level directories cleanly. + mkdir ${CVSROOT_FILENAME}/first-dir +# fi +# rm -rf first-dir + + # check out an empty directory + if ${CVS} co first-dir ; then + echo "PASS: test 6" >>${LOGFILE} + else + echo "FAIL: test 6" | tee -a ${LOGFILE}; exit 1 + fi + + # update the empty directory + if ${CVS} update first-dir ; then + echo "PASS: test 7" >>${LOGFILE} + else + echo "FAIL: test 7" | tee -a ${LOGFILE}; exit 1 + fi + + # diff -u the empty directory + if ${CVS} diff -u first-dir ; then + echo "PASS: test 8" >>${LOGFILE} + else + echo "FAIL: test 8" | tee -a ${LOGFILE}; exit 1 + fi + + # diff -c the empty directory + if ${CVS} diff -c first-dir ; then + echo "PASS: test 9" >>${LOGFILE} + else + echo "FAIL: test 9" | tee -a ${LOGFILE}; exit 1 + fi + + # log the empty directory + if ${CVS} log first-dir ; then + echo "PASS: test 10" >>${LOGFILE} + else + echo "FAIL: test 10" | tee -a ${LOGFILE}; exit 1 + fi + + # status the empty directory + if ${CVS} status first-dir ; then + echo "PASS: test 11" >>${LOGFILE} + else + echo "FAIL: test 11" | tee -a ${LOGFILE}; exit 1 + fi + + # tag the empty directory + if ${CVS} tag first first-dir ; then + echo "PASS: test 12" >>${LOGFILE} + else + echo "FAIL: test 12" | tee -a ${LOGFILE}; exit 1 + fi + + # rtag the empty directory + if ${CVS} rtag empty first-dir ; then + echo "PASS: test 13" >>${LOGFILE} + else + echo "FAIL: test 13" | tee -a ${LOGFILE}; exit 1 + fi + ;; + + basic1) # first dive - add a files, first singly, then in a group. + rm -rf ${CVSROOT_FILENAME}/first-dir + rm -rf first-dir + mkdir ${CVSROOT_FILENAME}/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 + + if test "x${do}-$j" = "xadd-add" || test "x${do}-$j" = "xrm-rm" ; then + true + else + # diff -c all + if ${CVS} diff -c >> ${LOGFILE} || [ $? = 1 ] ; then + echo "PASS: test 19-${do}-$j" >>${LOGFILE} + else + echo "FAIL: test 19-${do}-$j" | tee -a ${LOGFILE} + fi + + # diff -u all + if ${CVS} diff -u >> ${LOGFILE} || [ $? = 1 ] ; then + echo "PASS: test 20-${do}-$j" >>${LOGFILE} + else + echo "FAIL: test 20-${do}-$j" | tee -a ${LOGFILE} + fi + 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 + true + else + echo '***' failed test 24-${do}-$j. ; exit 1 + fi + + if test "x${do}-$j" = "xadd-add" || test "x${do}-$j" = "xrm-rm" ; then + true + else + # diff all + if ${CVS} diff -u >> ${LOGFILE} || [ $? = 1 ] ; then + true + else + echo '***' failed test 25-${do}-$j. # FIXME; exit 1 + fi + + # diff all + if ${CVS} diff -u first-dir >> ${LOGFILE} || [ $? = 1 ] ; then + true + else + echo '***' failed test 26-${do}-$j. # FIXME; exit 1 + fi + fi + + # update all. + if ${CVS} co first-dir ; then + true + else + echo '***' failed test 27-${do}-$j. ; 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 + true + else + echo '***' failed test 28. ; exit 1 + fi + cd .. + ;; + + basic2) # second dive - add bunch o' files in bunch o' added directories + for i in first-dir dir1 dir2 dir3 dir4 ; do + if [ ! -d $i ] ; then + mkdir $i + if ${CVS} add $i >> ${LOGFILE}; then + true + else + echo '***' failed test 29-$i. ; exit 1 + fi + fi + + cd $i + + for j in file6 file7 file8 file9 file10 file11 file12 file13; do + echo $j > $j + done + + if ${CVS} add file6 file7 file8 file9 file10 file11 file12 file13 2>> ${LOGFILE}; then + true + else + echo '***' failed test 30-$i-$j. ; exit 1 + fi + done + cd ../../../../.. + if ${CVS} update first-dir ; then + true + else + echo '***' failed test 31. ; exit 1 + fi + + # fixme: doesn't work right for added files. + if ${CVS} log first-dir >> ${LOGFILE}; then + true + else + echo '***' failed test 32. # ; exit 1 + fi + + if ${CVS} status first-dir >> ${LOGFILE}; then + true + else + echo '***' failed test 33. ; exit 1 + fi + +# if ${CVS} diff -u first-dir >> ${LOGFILE} || [ $? = 1 ] ; then +# true +# else +# echo '***' failed test 34. # ; exit 1 +# fi + + if ${CVS} ci -m "second dive" first-dir >> ${LOGFILE} 2>&1; then + true + else + echo '***' failed test 35. ; exit 1 + fi + + if ${CVS} tag second-dive first-dir ; then + true + else + echo '***' failed test 36. ; exit 1 + fi + ;; + + basic3) # third dive - in bunch o' directories, add bunch o' files, delete some, change some. + for i in first-dir dir1 dir2 dir3 dir4 ; do + cd $i + + # modify some files + for j in file6 file8 file10 file12 ; do + echo $j >> $j + done + + # delete some files + rm file7 file9 file11 file13 + + if ${CVS} rm file7 file9 file11 file13 2>> ${LOGFILE}; then + true + else + echo '***' failed test 37-$i. ; exit 1 + fi + + # and add some new ones + for j in file14 file15 file16 file17 ; do + echo $j > $j + done + + if ${CVS} add file14 file15 file16 file17 2>> ${LOGFILE}; then + true + else + echo '***' failed test 38-$i. ; exit 1 + fi + done + cd ../../../../.. + if ${CVS} update first-dir ; then + true + else + echo '***' failed test 39. ; exit 1 + fi + + # fixme: doesn't work right for added files + if ${CVS} log first-dir >> ${LOGFILE}; then + true + else + echo '***' failed test 40. # ; exit 1 + fi + + if ${CVS} status first-dir >> ${LOGFILE}; then + true + else + echo '***' failed test 41. ; exit 1 + fi + +# if ${CVS} diff -u first-dir >> ${LOGFILE} || [ $? = 1 ] ; then +# true +# else +# echo '***' failed test 42. # ; exit 1 +# fi + + if ${CVS} ci -m "third dive" first-dir >>${LOGFILE} 2>&1; then + true + else + echo '***' failed test 43. ; exit 1 + fi + + if ${CVS} tag third-dive first-dir ; then + true + else + echo '***' failed test 44. ; exit 1 + fi + + # Hmm... fixme. +# if ${CVS} release first-dir ; then +# true +# else +# echo '***' failed test 45. # ; exit 1 +# fi + + # end of third dive + rm -rf first-dir + ;; + + rtags) # now try some rtags + # rtag HEADS + if ${CVS} rtag rtagged-by-head first-dir ; then + true + else + echo '***' failed test 46. ; exit 1 + fi + + # tag by tag + if ${CVS} rtag -r rtagged-by-head rtagged-by-tag first-dir ; then + true + else + echo '***' failed test 47. ; exit 1 + fi + + # tag by revision + if ${CVS} rtag -r1.1 rtagged-by-revision first-dir ; then + true + else + echo '***' failed test 48. ; exit 1 + fi + + # rdiff by revision + if ${CVS} rdiff -r1.1 -rrtagged-by-head first-dir >> ${LOGFILE} || [ $? = 1 ] ; then + true + else + echo '***' failed test 49. ; 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 + true + else + echo '***' failed test 50. ; exit 1 + fi + + mv first-dir 1dir + if ${CVS} export -r rtagged-by-tag first-dir ; then + true + else + echo '***' failed test 51. ; exit 1 + fi + + if diff -c -r 1dir first-dir ; then + true + else + echo '***' failed test 52. ; exit 1 + fi + rm -rf 1dir first-dir + + # For some reason, this command has stopped working and hence much of this sequence is currently off. + # export by revision vs checkout by rtagged-by-revision and compare. +# if ${CVS} export -r1.1 first-dir ; then +# true +# else +# echo '***' failed test 53. # ; exit 1 +# fi + # note sidestep below + #mv first-dir 1dir + + if ${CVS} co -rrtagged-by-revision first-dir ; then + true + else + echo '***' failed test 54. ; exit 1 + fi + # fixme: this is here temporarily to sidestep test 53. + ln -s first-dir 1dir + + # 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 -)) + + if diff --exclude=CVS -c -r 1dir first-dir ; then + true + else + echo '***' failed test 55. ; exit 1 + fi + + # interrupt, while we've got a clean 1.1 here, let's import it into another tree. + cd 1dir + if ${CVS} import -m "first-import" second-dir first-immigration immigration1 immigration1_0 ; then + true + else + echo '***' failed test 56. ; exit 1 + fi + cd .. + + if ${CVS} export -r HEAD second-dir ; then + true + else + echo '***' failed test 57. ; exit 1 + fi + + if diff --exclude=CVS -c -r first-dir second-dir ; then + true + else + echo '***' failed test 58. ; exit 1 + fi + + rm -rf 1dir 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 + true + else + echo '***' failed test 59. ; 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 + true + else + echo '***' failed test 60a. ; exit 1 + fi + if ${CVS} tag -l rtagged-by-revision ; then + true + else + echo '***' failed test 60b. ; exit 1 + fi + + cd .. ; mv first-dir 1dir + mv first-dir.cpy first-dir ; cd first-dir + if ${CVS} diff -u >> ${LOGFILE} || [ $? = 1 ] ; then + true + else + echo '***' failed test 61. ; exit 1 + fi + + if ${CVS} update ; then + true + else + echo '***' failed test 62. ; exit 1 + fi + + cd .. + +# Haven't investigated why this is failing. +# if diff --exclude=CVS -c -r 1dir first-dir ; then +# true +# else +# echo '***' failed test 63. # ; exit 1 +# fi + rm -rf 1dir first-dir + + if ${CVS} his -e -a >> ${LOGFILE}; then + true + else + echo '***' failed test 64. ; exit 1 + fi + ;; + + death) # next dive. test death support. + rm -rf ${CVSROOT_FILENAME}/first-dir + mkdir ${CVSROOT_FILENAME}/first-dir + if ${CVS} co first-dir ; then + true + else + echo '***' failed test 65 ; exit 1 + fi + + cd first-dir + + # add a file. + touch file1 + if ${CVS} add file1 2>> ${LOGFILE}; then + true + else + echo '***' failed test 66 ; exit 1 + fi + + # commit + if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then + true + else + echo '***' failed test 67 ; exit 1 + fi + + # remove + rm file1 + if ${CVS} rm file1 2>> ${LOGFILE}; then + true + else + echo '***' failed test 68 ; exit 1 + fi + + # commit + if ${CVS} ci -m test >>${LOGFILE} ; then + true + else + echo '***' failed test 69 ; exit 1 + fi + + # add again and create second file + touch file1 file2 + if ${CVS} add file1 file2 2>> ${LOGFILE}; then + true + else + echo '***' failed test 70 ; exit 1 + fi + + # commit + if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then + true + else + echo '***' failed test 71 ; exit 1 + fi + + # log + if ${CVS} log file1 >> ${LOGFILE}; then + true + else + echo '***' failed test 72 ; exit 1 + fi + + + # branch1 + if ${CVS} tag -b branch1 ; then + true + else + echo '***' failed test 73 ; exit 1 + fi + + # and move to the branch. + if ${CVS} update -r branch1 ; then + true + else + echo '***' failed test 74 ; exit 1 + fi + + # add a file in the branch + echo line1 from branch1 >> file3 + if ${CVS} add file3 2>> ${LOGFILE}; then + true + else + echo '***' failed test 75 ; exit 1 + fi + + # commit + if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then + true + else + echo '***' failed test 76 ; exit 1 + fi + + # remove + rm file3 + if ${CVS} rm file3 2>> ${LOGFILE}; then + true + else + echo '***' failed test 77 ; exit 1 + fi + + # commit + if ${CVS} ci -m test >>${LOGFILE} ; then + true + else + echo '***' failed test 78 ; exit 1 + fi + + # add again + echo line1 from branch1 >> file3 + if ${CVS} add file3 2>> ${LOGFILE}; then + true + else + echo '***' failed test 79 ; exit 1 + fi + + # commit + if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then + true + else + echo '***' failed test 80 ; exit 1 + fi + + # change the first file + echo line2 from branch1 >> file1 + + # commit + if ${CVS} ci -m test >> ${LOGFILE} 2>&1; then + true + else + echo '***' failed test 81 ; exit 1 + fi + + # remove the second + rm file2 + if ${CVS} rm file2 2>> ${LOGFILE}; then + true + else + echo '***' failed test 82 ; exit 1 + fi + + # commit + if ${CVS} ci -m test >>${LOGFILE}; then + true + else + echo '***' failed test 83 ; exit 1 + fi + + # back to the trunk. + if ${CVS} update -A 2>> ${LOGFILE}; then + true + else + echo '***' failed test 84 ; exit 1 + fi + + if [ -f file3 ] ; then + echo '***' failed test 85 ; exit 1 + else + true + fi + + # join + if ${CVS} update -j branch1 >> ${LOGFILE} 2>&1; then + true + else + echo '***' failed test 86 ; exit 1 + fi + + if [ -f file3 ] ; then + true + else + echo '***' failed test 87 ; exit 1 + fi + + # update + if ${CVS} update ; then + true + else + echo '***' failed test 88 ; exit 1 + fi + + # commit + if ${CVS} ci -m test >>${LOGFILE} 2>&1; then + true + else + echo '***' failed test 89 ; exit 1 + fi + + # remove first file. + rm file1 + if ${CVS} rm file1 2>> ${LOGFILE}; then + true + else + echo '***' failed test 90 ; exit 1 + fi + + # commit + if ${CVS} ci -m test >>${LOGFILE}; then + true + else + echo '***' failed test 91 ; exit 1 + fi + + if [ -f file1 ] ; then + echo '***' failed test 92 ; exit 1 + else + true + fi + + # back to branch1 + if ${CVS} update -r branch1 2>> ${LOGFILE}; then + true + else + echo '***' failed test 93 ; exit 1 + fi + + if [ -f file1 ] ; then + true + else + echo '***' failed test 94 ; exit 1 + fi + + # and join + if ${CVS} update -j HEAD >> ${LOGFILE} 2>&1; then + true + else + echo '***' failed test 95 ; exit 1 + fi + + cd .. ; rm -rf first-dir ${CVSROOT_FILENAME}/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 + + if ${CVS} import -m first-import first-dir vendor-branch junk-1_0 ; then + true + else + echo '***' failed test 96 ; exit 1 + fi + cd .. + + # co + if ${CVS} co first-dir ; then + true + else + echo '***' failed test 97 ; exit 1 + fi + + cd first-dir + for i in 1 2 3 4 ; do + if [ -f imported-file"$i" ] ; then + true + else + echo '***' failed test 98-$i ; exit 1 + fi + done + + # remove + rm imported-file1 + if ${CVS} rm imported-file1 2>> ${LOGFILE}; then + true + else + echo '***' failed test 99 ; 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 + true + else + echo '***' failed test 100 ; exit 1 + fi + + # log + if ${CVS} log imported-file1 | grep '1.1.1.2 (dead)' ; then + echo '***' failed test 101 ; exit 1 + else + true + fi + + # update into the vendor branch. + if ${CVS} update -rvendor-branch ; then + true + else + echo '***' failed test 102 ; exit 1 + fi + + # remove file4 on the vendor branch + rm imported-file4 + + if ${CVS} rm imported-file4 2>> ${LOGFILE}; then + true + else + echo '***' failed test 103 ; exit 1 + fi + + # commit + if ${CVS} ci -m vendor-removed imported-file4 >>${LOGFILE}; then + true + else + echo '***' failed test 104 ; exit 1 + fi + + # update to main line + if ${CVS} update -A 2>> ${LOGFILE}; then + true + else + echo '***' failed test 105 ; 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 + + if ${CVS} import -m second-import first-dir vendor-branch junk-2_0 ; then + true + else + echo '***' failed test 106 ; exit 1 + fi + cd .. + + # co + if ${CVS} co first-dir ; then + true + else + echo '***' failed test 107 ; exit 1 + fi + + cd first-dir + + if [ -f imported-file1 ] ; then + echo '***' failed test 108 ; exit 1 + else + true + fi + + for i in 2 3 ; do + if [ -f imported-file"$i" ] ; then + true + else + echo '***' failed test 109-$i ; exit 1 + fi + done + + # check vendor branch for file4 + if ${CVS} update -rvendor-branch ; then + true + else + echo '***' failed test 110 ; exit 1 + fi + + if [ -f imported-file4 ] ; then + true + else + echo '***' failed test 111 ; exit 1 + fi + + # update to main line + if ${CVS} update -A 2>> ${LOGFILE}; then + true + else + echo '***' failed test 112 ; exit 1 + fi + + cd .. + + if ${CVS} co -jjunk-1_0 -jjunk-2_0 first-dir >>${LOGFILE} 2>&1; then + true + else + echo '***' failed test 113 ; exit 1 + fi + + cd first-dir + + if [ -f imported-file1 ] ; then + echo '***' failed test 114 ; exit 1 + else + true + fi + + for i in 2 3 ; do + if [ -f imported-file"$i" ] ; then + true + else + echo '***' failed test 115-$i ; exit 1 + fi + done + + if cat imported-file2 | grep '====' >> ${LOGFILE}; then + true + else + echo '***' failed test 116 ; exit 1 + fi + cd .. ; rm -rf first-dir ${CVSROOT_FILENAME}/first-dir + ;; + + new) # look for stray "no longer pertinent" messages. + rm -rf first-dir ${CVSROOT_FILENAME}/first-dir + mkdir ${CVSROOT_FILENAME}/first-dir + + if ${CVS} co first-dir ; then + true + else + echo '***' failed test 117 ; exit 1 + fi + + cd first-dir + touch a + + if ${CVS} add a 2>>${LOGFILE}; then + true + else + echo '***' failed test 118 ; exit 1 + fi + + if ${CVS} ci -m added >>${LOGFILE} 2>&1; then + true + else + echo '***' failed test 119 ; exit 1 + fi + + rm a + + if ${CVS} rm a 2>>${LOGFILE}; then + true + else + echo '***' failed test 120 ; exit 1 + fi + + if ${CVS} ci -m removed >>${LOGFILE} ; then + true + else + echo '***' failed test 121 ; exit 1 + fi + + if ${CVS} update -A 2>&1 | grep longer ; then + echo '***' failed test 122 ; exit 1 + else + true + fi + + if ${CVS} update -rHEAD 2>&1 | grep longer ; then + echo '***' failed test 123 ; exit 1 + else + true + fi + + cd .. ; rm -rf first-dir ; rm -rf ${CVSROOT_FILENAME}/first-dir + ;; + + conflicts) + rm -rf first-dir ${CVSROOT_FILENAME}/first-dir + mkdir ${CVSROOT_FILENAME}/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 + if ${CVS} ci -m changed >>${LOGFILE} 2>&1; then + echo 'PASS: test 128' >>${LOGFILE} + else + echo 'FAIL: test 128' | tee -a ${LOGFILE} + fi + + cd ../../2/first-dir + echo add a conflicting line >>a + if ${CVS} ci -m changed >>${LOGFILE} 2>&1; then + echo 'FAIL: test 129' | tee -a ${LOGFILE} + else + # Should be printing `out of date check failed'. + echo 'PASS: test 129' >>${LOGFILE} + fi + + if ${CVS} update 2>>${LOGFILE}; then + # We should get a conflict, but that doesn't affect + # exit status + echo 'PASS: test 130' >>${LOGFILE} + else + echo 'FAIL: test 130' | tee -a ${LOGFILE} + fi + + # 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 + if ${testcvs} -q update >>${LOGFILE}; then + echo 'PASS: test 136' >>${LOGFILE} + else + echo 'FAIL: test 136' | tee -a ${LOGFILE} + fi + if test -f first-dir/abc; then + echo 'PASS: test 137' >>${LOGFILE} + else + echo 'FAIL: test 137' | tee -a ${LOGFILE} + fi + + # 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_FILENAME}/first-dir + ;; + modules) + # The following line stolen from cvsinit.sh. FIXME: create our + # repository via cvsinit.sh; that way we test it too. + (cd ${CVSROOT_FILENAME}/CVSROOT; ci -q -u -t/dev/null \ + -m'initial checkin of modules' modules) + + rm -rf first-dir ${CVSROOT_FILENAME}/first-dir + mkdir ${CVSROOT_FILENAME}/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} + fi + + cd first-dir + mkdir subdir + ${testcvs} add subdir >>${LOGFILE} + cd subdir + + touch a + + if ${testcvs} add a 2>>${LOGFILE} ; then + echo 'PASS: test 144' >>${LOGFILE} + else + echo 'FAIL: test 144' | tee -a ${LOGFILE} + fi + + if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then + echo 'PASS: test 145' >>${LOGFILE} + else + echo 'FAIL: test 145' | tee -a ${LOGFILE} + fi + + cd .. + if ${testcvs} -q co CVSROOT >>${LOGFILE}; then + echo 'PASS: test 146' >>${LOGFILE} + else + echo 'FAIL: test 146' | tee -a ${LOGFILE} + 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} + fi + + echo realmodule first-dir/subdir a >>CVSROOT/modules + echo aliasmodule -a first-dir/subdir/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} + fi + cd .. + if ${testcvs} co realmodule >>${LOGFILE}; then + echo 'PASS: test 149' >>${LOGFILE} + else + echo 'FAIL: test 149' | tee -a ${LOGFILE} + fi + if test -d realmodule && test -f realmodule/a; then + echo 'PASS: test 150' >>${LOGFILE} + else + echo 'FAIL: test 150' | tee -a ${LOGFILE} + fi + if ${testcvs} co aliasmodule >>${LOGFILE}; then + echo 'PASS: test 151' >>${LOGFILE} + else + echo 'FAIL: test 151' | tee -a ${LOGFILE} + fi + if test -d aliasmodule; then + echo 'FAIL: test 152' | tee -a ${LOGFILE} + 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} + 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} + fi + if ${testcvs} -q co realmodule; then + echo 'PASS: test 155' >>${LOGFILE} + else + echo 'FAIL: test 155' | tee -a ${LOGFILE} + fi + cd .. + rm -rf 1 ; rm -rf ${CVSROOT_FILENAME}/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} + 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} + 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} + 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} + 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} + fi + if ${testcvs} ci -m "$message" >>${LOGFILE} 2>&1; then + echo 'PASS: test 161' >>${LOGFILE} + else + echo 'FAIL: test 161' | tee -a ${LOGFILE} + fi + # Clean up + cd ..; rm -rf a-dir ${CVSROOT_FILENAME}/a-dir + done + ;; + errmsg1) + mkdir ${CVSROOT_FILENAME}/1dir + mkdir 1 + cd 1 + if ${testcvs} -q co 1dir; then + echo 'PASS: test 162' >>${LOGFILE} + else + echo 'FAIL: test 162' | tee -a ${LOGFILE} + 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} + fi + if ${testcvs} ci -m added >>${LOGFILE} 2>&1; then + echo 'PASS: test 164' >>${LOGFILE} + else + echo 'FAIL: test 164' | tee -a ${LOGFILE} + 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} + 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} + fi + if ${testcvs} ci -m removed >>${LOGFILE} 2>&1; then + echo 'PASS: test 167' >>${LOGFILE} + else + echo 'FAIL: test 167' | tee -a ${LOGFILE} + fi + cd ../../2/1dir + ${testcvs} -q update 2>../tst167.err + cat <<EOF >../tst167.ans +cvs server: warning: foo is not (any longer) pertinent +cvs update: unable to remove ./foo: Permission denied +EOF + if cmp ../tst167.ans ../tst167.err >/dev/null || + ( echo 'cvs [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} + fi + + cd .. + chmod u+w 1dir + cd .. + rm -rf 1 2 ${CVSROOT_FILENAME}/1dir + ;; + + *) echo $what is not the name of a test -- ignored ;; + esac +done + +echo Ok. + +# 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. +# * Zero length files (check in, check out). +# * `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). +# 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/gnu/usr.bin/cvs/src/server.c b/gnu/usr.bin/cvs/src/server.c new file mode 100644 index 00000000000..ebdefbb90d5 --- /dev/null +++ b/gnu/usr.bin/cvs/src/server.c @@ -0,0 +1,3577 @@ +#include "cvs.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 + + +/* 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)); + +void server_cleanup PROTO((int sig)); + +/* + * 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?"); +} + +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 (1); + } + 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 (access (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) && access (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 void +dirswitch (dir, repos) + char *dir; + char *repos; +{ + char *dirname; + int status; + FILE *f; + + server_write_entries (); + + if (error_pending()) return; + + dirname = malloc (strlen (server_temp_dir) + strlen (dir) + 40); + if (dirname == NULL) + { + pending_error = ENOMEM; + return; + } + + strcpy (dirname, server_temp_dir); + strcat (dirname, "/"); + strcat (dirname, dir); + + status = mkdir_p (dirname); + if (status != 0 + && status != EEXIST) + { + pending_error = status; + pending_error_text = malloc (80 + strlen(dirname)); + sprintf(pending_error_text, "E cannot mkdir %s", dirname); + return; + } + if (chdir (dirname) < 0) + { + pending_error = errno; + pending_error_text = malloc (80 + strlen(dirname)); + sprintf(pending_error_text, "E cannot change to %s", dirname); + 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; + } + free (dirname); +} + +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 %d", 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); + } +} + +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; + } +} + +/* + * 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) + +/* 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 *)); + +/* 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; +} + +/* 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->output) + abort (); + + 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; +} + +/* + * Set buffer BUF to non-blocking I/O. Returns 0 for success or errno + * code. + */ + +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*/ +} + +static struct buffer protocol; + +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 (1); +} + +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; + +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; + + /* + * 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; + } + + 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; + + 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]); + + /* + * 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 outbuf; + 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; + + 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; + } + + outbuf.data = outbuf.last = NULL; + outbuf.fd = STDOUT_FILENO; + outbuf.output = 1; + outbuf.nonblocking = 0; + outbuf.memory_error = outbuf_memory_error; + + 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 (&outbuf); + 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; + + 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; + + FD_ZERO (&readfds); + FD_ZERO (&writefds); + if (! buf_empty_p (&outbuf)) + 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 (&outbuf); + } + + if (stdout_pipe[0] >= 0 + && (FD_ISSET (stdout_pipe[0], &readfds))) + { + int status; + + status = buf_input_data (&stdoutbuf, (int *) NULL); + + buf_copy_lines (&outbuf, &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 (&outbuf); + } + + if (stderr_pipe[0] >= 0 + && (FD_ISSET (stderr_pipe[0], &readfds))) + { + int status; + + status = buf_input_data (&stderrbuf, (int *) NULL); + + buf_copy_lines (&outbuf, &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 (&outbuf); + } + + 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 (&outbuf, &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 (&outbuf); + } + } + + /* + * 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 (&outbuf, &stdoutbuf, 'M'); + } + if (! buf_empty_p (&stderrbuf)) + { + buf_append_char (&stderrbuf, '\n'); + buf_copy_lines (&outbuf, &stderrbuf, 'E'); + } + if (! buf_empty_p (&protocol_inbuf)) + buf_output0 (&outbuf, + "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 (&outbuf); + buf_send_output (&outbuf); + } + + 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; +} + +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 (trace) + { + (void) fprintf (stderr, + "%c-> server_register(%s, %s, %s, %s, %s, %s, %s)\n", + (server_active) ? 'S' : ' ', /* silly */ + name, version, timestamp, options, tag, + date, conflict); + } + + if (options == NULL) + options = ""; + + 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); +} + +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 + { + buf_output0 (&protocol, "Checked-in "); + output_dir (update_dir, repository); + buf_output0 (&protocol, file); + buf_output (&protocol, "\n", 1); + new_entries_line (); + } + 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) + buf_output0 (&protocol, "Checked-in "); + 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_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 (errno == ENOENT) + { + /* + * 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) + { + 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, 0); + if (fd < 0) + error (1, errno, "reading %s", short_pathname); + fd = filter_through_gzip (fd, 1, gzip_level, &gzip_pid); + f = fdopen (fd, "r"); + 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 %d", 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, "r"); + 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); +} + +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\n", mwhere); + 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("Argument", serve_argument, rq_essential), + REQ_LINE("Argumentx", serve_argumentx, rq_essential), + REQ_LINE("Global_option", serve_global_option, 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(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"); +} + +/* + * 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++; +} + +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 (1); + } + 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 (1); + } + 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, "%d", 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 (1); + } + + argument_count = 1; + argument_vector[0] = "Dummy argument 0"; + + 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; +} + +#endif /* SERVER_SUPPORT */ diff --git a/gnu/usr.bin/cvs/src/server.h b/gnu/usr.bin/cvs/src/server.h new file mode 100644 index 00000000000..d0560be7661 --- /dev/null +++ b/gnu/usr.bin/cvs/src/server.h @@ -0,0 +1,130 @@ +/* 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)); + +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)); + +#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/gnu/usr.bin/cvs/src/status.c b/gnu/usr.bin/cvs/src/status.c new file mode 100644 index 00000000000..d987a91ac8c --- /dev/null +++ b/gnu/usr.bin/cvs/src/status.c @@ -0,0 +1,282 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)status.c 1.56 94/10/07 $"; +USE(rcsid); +#endif + +static Dtype status_dirproc PROTO((char *dir, char *repos, char *update_dir)); +static int status_fileproc PROTO((char *file, char *update_dir, + char *repository, List * entries, + List * srcfiles)); +static int tag_list_proc PROTO((Node * p, void *closure)); + +static int local = 0; +static int long_format = 0; +static char *xfile; +static List *xsrcfiles; + +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"); + + /* XXX This should only need to send file info; the file + contents themselves will not be examined. */ + send_files (argc, argv, local, 0); + + if (fprintf (to_server, "status\n") < 0) + error (1, errno, "writing to server"); + err = get_responses_and_close (); + + return err; + } +#endif + + /* start the recursion processor */ + err = start_recursion (status_fileproc, (int (*) ()) NULL, status_dirproc, + (int (*) ()) 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 (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + Ctype status; + char *sstat; + Vers_TS *vers; + + status = Classify_File (file, (char *) NULL, (char *) NULL, (char *) NULL, + 1, 0, repository, entries, srcfiles, &vers, + 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", file, sstat); + else + (void) printf ("File: %-17s\tStatus: %s\n\n", file, sstat); + + if (vers->vn_user == NULL) + (void) printf (" Working revision:\tNo entry for %s\n", 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 + { + int isbranch = RCS_isbranch (file, edata->tag, srcfiles); + + (void) printf (" Sticky Tag:\t\t%s (%s: %s)\n", + edata->tag, + isbranch ? "branch" : "revision", + isbranch ? + RCS_whatbranch(file, edata->tag, srcfiles) : + vers->vn_rcs); + } + } + } + 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) + { + xfile = file; + xsrcfiles = srcfiles; + (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; +{ + int isbranch = RCS_isbranch (xfile, p->key, xsrcfiles); + + (void) printf ("\t%-25.25s\t(%s: %s)\n", p->key, + isbranch ? "branch" : "revision", + isbranch ? RCS_whatbranch(xfile, p->key, xsrcfiles) : + p->data); + return (0); +} diff --git a/gnu/usr.bin/cvs/src/subr.c b/gnu/usr.bin/cvs/src/subr.c new file mode 100644 index 00000000000..b94fc8abfa5 --- /dev/null +++ b/gnu/usr.bin/cvs/src/subr.c @@ -0,0 +1,310 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)subr.c 1.64 94/10/07 $"; +USE(rcsid); +#endif + +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. + */ +char * +xrealloc (ptr, bytes) + char *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); +} + +/* + * 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 = getenv("LOGNAME")) || (name = getenv("USER")) || + (name = getlogin ())) && *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'. + */ +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/gnu/usr.bin/cvs/src/tag.c b/gnu/usr.bin/cvs/src/tag.c new file mode 100644 index 00000000000..a2544afd8d5 --- /dev/null +++ b/gnu/usr.bin/cvs/src/tag.c @@ -0,0 +1,582 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)tag.c 1.60 94/09/30 $"; +USE(rcsid); +#endif + +static int check_fileproc PROTO((char *file, char *update_dir, + char *repository, List * entries, + List * srcfiles)); +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((char *file, char *update_dir, + char *repository, List * entries, + List * srcfiles)); + +static char *numtag; +static char *date = NULL; +static char *symtag; +static int delete; /* 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] 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 = 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 (delete && 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) + send_arg("-d"); + if (branch_mode) + send_arg("-b"); + if (force_tag_move) + send_arg("-F"); + + send_arg (symtag); + +#if 0 + /* 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_file_names (argc, argv); +#else + send_files (argc, argv, local, 0); +#endif + if (fprintf (to_server, "tag\n") < 0) + error (1, errno, "writing to server"); + return get_responses_and_close (); + } +#endif + + /* 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, + (Dtype (*) ()) NULL, (int (*) ()) 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, (int (*) ()) NULL, tag_dirproc, + (int (*) ()) 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(file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List * entries; + List * srcfiles; +{ + char *xdir; + Node *p; + Vers_TS *vers; + + if (update_dir[0] == '\0') + xdir = "."; + else + xdir = 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 (file); + p->type = UPDATE; + p->delproc = tag_delproc; + vers = Version_TS (repository, (char *) NULL, (char *) NULL, (char *) NULL, + file, 0, 0, entries, srcfiles); + p->data = RCS_getversion(vers->srcfile, numtag, date, force_tag_match); + if (p->data != NULL) + { + int addit = 1; + char *oversion; + + oversion = RCS_getversion (vers->srcfile, symtag, (char *) NULL, 1); + if (oversion == NULL) + { + if (delete) + { + 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 ? "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 (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + char *version, *oversion; + char *nversion = NULL; + char *rev; + Vers_TS *vers; + int retcode = 0; + + vers = Version_TS (repository, (char *) NULL, (char *) NULL, (char *) NULL, + file, 0, 0, entries, srcfiles); + + if ((numtag != NULL) || (date != NULL)) + { + nversion = RCS_getversion(vers->srcfile, + numtag, + date, + force_tag_match); + if (nversion == NULL) + { + freevers_ts (&vers); + return (0); + } + } + if (delete) + { + + /* + * If -d is specified, "force_tag_match" is set, so that this call to + * Version_Number() 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); + 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) + { + if (update_dir[0]) + (void) printf ("D %s/%s\n", update_dir, file); + else + (void) printf ("D %s\n", file); + } + + 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'", file); + freevers_ts (&vers); + return (0); + } + else if (version[0] == '-') + { + if (!quiet) + error (0, 0, "skipping removed but un-commited file `%s'", file); + freevers_ts (&vers); + return (0); + } + else if (vers->srcfile == NULL) + { + if (!quiet) + error (0, 0, "cannot find revision control file for `%s'", 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 Version_Number 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); + if (oversion != NULL) + { + int isbranch = RCS_isbranch (file, symtag, srcfiles); + + /* + * 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 */ + if (update_dir[0]) + (void) printf ("W %s/%s", update_dir, file); + else + (void) printf ("W %s", file); + + (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) + { + if (update_dir[0]) + (void) printf ("T %s/%s\n", update_dir, file); + else + (void) printf ("T %s\n", file); + } + + freevers_ts (&vers); + if (nversion != NULL) + { + free(nversion); + } + 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 ? "Untagging" : "Tagging", update_dir); + return (R_PROCESS); +} diff --git a/gnu/usr.bin/cvs/src/update.c b/gnu/usr.bin/cvs/src/update.c new file mode 100644 index 00000000000..0e01faeda74 --- /dev/null +++ b/gnu/usr.bin/cvs/src/update.c @@ -0,0 +1,1872 @@ +/* + * 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 CLIENT_SUPPORT +#include "update.h" +#endif +#ifdef SERVER_SUPPORT +#include "md5.h" +#endif + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)update.c 1.95 94/10/22 $"; +USE(rcsid); +#endif + +static int checkout_file PROTO((char *file, char *repository, List *entries, + List *srcfiles, Vers_TS *vers_ts, char *update_dir)); +#ifdef SERVER_SUPPORT +static int patch_file PROTO((char *file, char *repository, List *entries, + List *srcfiles, 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_file_proc PROTO((char *file, char *update_dir, char *repository, + List * entries, List * srcfiles)); +#ifndef CLIENT_SUPPORT +static int update_filesdone_proc PROTO((int err, char *repository, char *update_dir)); +#endif +static int write_letter PROTO((char *file, int letter, char *update_dir)); +static void ignore_files PROTO((List * ilist, char *update_dir)); +#ifdef SERVER_SUPPORT +static void join_file PROTO((char *file, List *srcfiles, Vers_TS *vers_ts, + char *update_dir, List *entries, char *repository)); +#else +static void join_file PROTO((char *file, List *srcfiles, 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 +#ifdef CLIENT_SUPPORT +List *ignlist = (List *) NULL; +#else +static List *ignlist = (List *) NULL; +#endif +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 (); + + ign_setup (); + + 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_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_files (failed_patches_count, failed_patches, local, + aflag); + } + + failed_patches = NULL; + failed_patches_count = 0; + + if (fprintf (to_server, "update\n") < 0) + error (1, errno, "writing to server"); + + status = get_responses_and_close (); + if (status != 0) + return status; + + } while (failed_patches != NULL); + + return 0; + } +#endif + + /* + * 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 && errno != ENOENT) + 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_file_proc, 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_file_proc (file, update_dir, repository, entries, srcfiles) + char *file; + char *update_dir; + char *repository; + List *entries; + List *srcfiles; +{ + int retval; + Ctype status; + Vers_TS *vers; + + status = Classify_File (file, tag, date, options, force_tag_match, + aflag, repository, entries, srcfiles, &vers, + 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 (file, repository, entries, srcfiles, + vers, update_dir); + break; + + default: /* can't ever happen :-) */ + error (0, 0, + "unknown file status %d for file %s", status, 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 (file, 'C', update_dir); + break; + case T_NEEDS_MERGE: /* needs merging */ + if (noexec) + { + retval = 1; + (void) write_letter (file, 'C', update_dir); + } + else + { + if (wrap_merge_is_copy (file)) + /* Should we be warning the user that we are + * overwriting the user's copy of the file? */ + retval = checkout_file (file, repository, entries, + srcfiles, vers, update_dir); + else + retval = merge_file (file, repository, entries, + vers, 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 (file); + retcode = strcmp (vers->ts_conflict, filestamp); + free (filestamp); + } +#else + filestamp = time_stamp (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 -s", GREP); + run_arg (RCS_MERGE_PAT); + run_arg (file); + retcode = run_exec (RUN_TTY, RUN_TTY, + RUN_TTY,RUN_NORMAL); + if (retcode == -1) + { + if (update_dir[0] == '\0') + error (1, errno, + "fork failed while examining conflict in `%s'", + file); + else + error (1, errno, + "fork failed while examining conflict in `%s/%s'", + update_dir, file); + } + } + if (!retcode) + { + (void) write_letter (file, 'C', update_dir); + retval = 1; + } + else + { + /* Reregister to clear conflict flag. */ + Register (entries, file, vers->vn_rcs, vers->ts_rcs, + vers->options, vers->tag, + vers->date, (char *)0); + } + } + if (!retval) + retval = write_letter (file, 'M', 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 (file, repository, entries, srcfiles, + vers, update_dir, &docheckout, + &file_info, checksum); + if (! docheckout) + { + if (server_active && retval == 0) + server_updated (file, update_dir, 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 (file, repository, entries, srcfiles, + vers, update_dir); +#ifdef SERVER_SUPPORT + if (server_active && retval == 0) + server_updated (file, update_dir, repository, + SERVER_UPDATED, (struct stat *) NULL, + (unsigned char *) NULL); +#endif + break; + case T_ADDED: /* added but not committed */ + retval = write_letter (file, 'A', update_dir); + break; + case T_REMOVED: /* removed but not committed */ + retval = write_letter (file, 'R', update_dir); + break; + case T_REMOVE_ENTRY: /* needs to be un-registered */ + retval = scratch_file (file, repository, entries, update_dir); +#ifdef SERVER_SUPPORT + if (server_active && retval == 0) + server_updated (file, update_dir, 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, 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 (file, srcfiles, vers, update_dir, entries, repository); +#else + join_file (file, srcfiles, vers, update_dir, 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 (file); + if (addnode (ignlist, p) != 0) + freenode (p); + } + + freevers_ts (&vers); + return (retval); +} + +/* + * update_filesdone_proc () is used + */ +/* ARGSUSED */ +#ifdef CLIENT_SUPPORT +/* Also used by client.c */ +int +#else +static int +#endif +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); + dellist (&ignlist); + } + + /* Clean up CVS admin dirs if we are export */ +#ifdef CLIENT_SUPPORT + /* In the client, we need to clean these up after we create them. Doing + it here might would clean up the user's previous contents even on + SIGINT which probably is bad. */ + if (!client_active && strcmp (command_name, "export") == 0) +#else + if (strcmp (command_name, "export") == 0) +#endif + { + run_setup ("%s -fr", RM); + run_arg (CVSADM); + (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + } +#ifdef CVSADM_ROOT +#ifdef SERVER_SUPPORT + else if (!server_active && !pipeout) +#else + else if (!pipeout) +#endif /* SERVER_SUPPORT */ + { + /* If there is no CVS/Root file, add one */ +#ifdef CLIENT_SUPPORT + if (!isfile (CVSADM_ROOT) + /* but only if we want it */ + && ! (getenv ("CVS_IGNORE_REMOTE_ROOT") && strchr (CVSroot, ':')) + ) +#else /* No CLIENT_SUPPORT */ + if (!isfile (CVSADM_ROOT)) +#endif /* No CLIENT_SUPPORT */ + Create_Root( (char *) NULL, CVSroot ); + } +#endif /* CVSADM_ROOT */ + + 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); + } + } + + /* + * 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 && errno != ENOENT) + 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); + } + + /* Prune empty dirs on the way out - if necessary */ + (void) chdir (".."); + if (update_prune_dirs && isemptydir (dir)) + { + run_setup ("%s -fr", RM); + run_arg (dir); + (void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + } + + 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, srcfiles, vers_ts, update_dir) + char *file; + char *repository; + List *entries; + List *srcfiles; + Vers_TS *vers_ts; + char *update_dir; +{ + char backup[PATH_MAX]; + int set_time, retval = 0; + int retcode = 0; +#ifdef DEATH_SUPPORT + int file_is_dead; +#endif + + /* 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); + } + +#ifdef DEATH_SUPPORT + file_is_dead = RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs); + + if (!file_is_dead) { +#endif + + run_setup ("%s%s -q -r%s %s", Rcsbin, RCS_CO, vers_ts->vn_rcs, + vers_ts->options); + + /* + * if we are checking out to stdout, print a nice message to stderr, and + * add the -p flag to the command + */ + if (pipeout) + { + run_arg ("-p"); + 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"); + } + } + + /* tack on the rcs and maybe the user file */ + run_arg (vers_ts->srcfile->path); + if (!pipeout) + run_arg (file); + +#ifdef DEATH_SUPPORT + } + if (file_is_dead || (retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, +#else + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, +#endif + (pipeout ? (RUN_NORMAL|RUN_REALLY) : RUN_NORMAL))) == 0) + { + if (!pipeout) + { + Vers_TS *xvers_ts; +#ifdef DEATH_SUPPORT + int resurrecting; + + resurrecting = 0; + + if (file_is_dead && joining()) + { + /* when joining, we need to get dead files checked + out. Try harder. */ + run_setup ("%s%s -q -r%s %s", Rcsbin, RCS_CO, vers_ts->vn_rcs, + vers_ts->options); + + run_arg ("-f"); + run_arg (vers_ts->srcfile->path); + run_arg (file); + if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL)) != 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; + } + + if (cvswrite == TRUE && !file_is_dead) + xchmod (file, 1); +#else /* No DEATH_SUPPORT */ + if (cvswrite == TRUE) + xchmod (file, 1); +#endif /* No DEATH_SUPPORT */ + + /* 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, srcfiles); + if (strcmp (xvers_ts->options, "-V4") == 0) + xvers_ts->options[0] = '\0'; + + (void) time (&last_register_time); + +#ifdef DEATH_SUPPORT + 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 && errno != ENOENT) + { + 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 */ +#else /* No DEATH_SUPPORT */ + Register (entries, file, 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 */ +#endif /* No DEATH_SUPPORT */ + + /* 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); + +#ifdef DEATH_SUPPORT + if (!really_quiet && !file_is_dead) +#else + if (!really_quiet) +#endif + { + if (update_dir[0]) + (void) printf ("U %s/%s\n", update_dir, file); + else + (void) printf ("U %s\n", file); + } + } + } + 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, srcfiles, vers_ts, update_dir, + docheckout, file_info, checksum) + char *file; + char *repository; + List *entries; + List *srcfiles; + 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. */ + run_setup ("%s%s -q -p -r%s %s %s", Rcsbin, RCS_CO, vers_ts->vn_user, + vers_ts->options, vers_ts->srcfile->path); + if (run_exec (RUN_TTY, file1, RUN_TTY, RUN_NORMAL) != 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. */ + run_setup ("%s%s -q -r%s %s %s %s", Rcsbin, RCS_CO, vers_ts->vn_rcs, + vers_ts->options, vers_ts->srcfile->path, file); + if (run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL) != 0) + fail = 1; + else + { + if (!isreadable (file)) + { + /* File is dead. */ + fail = 1; + } + else + { + rename_file (file, file2); + if (cvswrite == TRUE) + 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); + } + } + } + } + + 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, srcfiles); + 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) + { + if (update_dir[0]) + (void) printf ("P %s/%s\n", update_dir, file); + else + (void) printf ("P %s\n", file); + } + } + 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) + { + if (update_dir[0]) + (void) printf ("%c %s/%s\n", letter, update_dir, file); + else + (void) printf ("%c %s\n", letter, file); + } + 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); + + if (!really_quiet) + (void) printf ("C %s\n", user); + + 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 + { + if (!really_quiet) + (void) printf ("M %s\n", user); + 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, srcfiles, vers, update_dir, entries, repository) + char *repository; +#else +join_file (file, srcfiles, vers, update_dir, entries) +#endif + char *file; + List *srcfiles; + 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; + } + + /* convert the second rev spec, walking branches and dates. */ + + rev2 = RCS_getversion (vers->srcfile, jrev2, jdate2, 1); + 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); + 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); + 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); + 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); + 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); + if (rev == NULL) + return; + if (strcmp (rev, vers->vn_user) == 0) /* no merge necessary */ + { + free (rev); + return; + } + + baserev = RCS_whatbranch (file, join_rev1, srcfiles); + 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. */ + run_setup ("%s%s -q -r%s", Rcsbin, RCS_CO, vers->vn_user); + run_arg (vers->srcfile->path); + retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL); + 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); + + if (status == 1) + { + 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 +} + +/* + * Process the current directory, looking for files not in ILIST and not on + * the global ignore list for this directory. + */ +static void +ignore_files (ilist, update_dir) + List *ilist; + char *update_dir; +{ + 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 (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 + } + + if (ign_name (file)) + continue; + (void) write_letter (file, '?', xdir); + } + (void) closedir (dirp); +} + +int +joining () +{ + return (join_rev1 != NULL); +} diff --git a/gnu/usr.bin/cvs/src/update.h b/gnu/usr.bin/cvs/src/update.h new file mode 100644 index 00000000000..68c91d55ab9 --- /dev/null +++ b/gnu/usr.bin/cvs/src/update.h @@ -0,0 +1,9 @@ +/* Definitions of routines shared between local and client/server + "update" code. */ + +/* List of files that we have either processed or are willing to + ignore. Any file not on this list gets a question mark printed. */ +extern List *ignlist; + +extern int +update_filesdone_proc PROTO((int err, char *repository, char *update_dir)); diff --git a/gnu/usr.bin/cvs/src/vers_ts.c b/gnu/usr.bin/cvs/src/vers_ts.c new file mode 100644 index 00000000000..2fc912521df --- /dev/null +++ b/gnu/usr.bin/cvs/src/vers_ts.c @@ -0,0 +1,308 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)vers_ts.c 1.45 94/10/07 $"; +USE(rcsid); +#endif + +#define ctime(X) do not use ctime, please + +#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. xfiles is + * all source code control files, preparsed for our pleasure + */ +Vers_TS * +Version_TS (repository, options, tag, date, user, force_tag_match, + set_time, entries, xfiles) + char *repository; + char *options; + char *tag; + char *date; + char *user; + int force_tag_match; + int set_time; + List *entries; + List *xfiles; +{ + 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 (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 (sdtp && sdtp->aflag == 0) + { + if (!vers_ts->options) + vers_ts->options = xstrdup (sdtp->options); + } + 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 (xfiles != (List *) NULL) + { + p = findnode (xfiles, user); + if (p != NULL) + { + rcsdata = (RCSNode *) p->data; + rcsdata->refcount++; + } + else + rcsdata = NULL; + } + 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; + +#ifndef DEATH_SUPPORT + /* (is this indeed death support? I haven't looked carefully). */ + /* get RCS version number into vn_rcs (if appropriate) */ + if (((vers_ts->tag || vers_ts->date) && force_tag_match) || + ((rcsdata->flags & VALID) && (rcsdata->flags & INATTIC) == 0)) + { +#endif + if (vers_ts->tag && strcmp (vers_ts->tag, TAG_BASE) == 0) + vers_ts->vn_rcs = xstrdup (vers_ts->vn_user); + else + vers_ts->vn_rcs = RCS_getversion (rcsdata, vers_ts->tag, + vers_ts->date, force_tag_match); +#ifndef DEATH_SUPPORT + } +#endif + + /* + * 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 (errno != ENOENT) + 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 + { + vers_ts->ts_user = xmalloc (25); + cp = asctime (gmtime (&sb.st_mtime)); /* copy in the modify time */ + 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 + { + ts = xmalloc (25); + cp = asctime (gmtime (&sb.st_mtime)); /* copy in the modify time */ + 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)->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/gnu/usr.bin/cvs/src/version.c b/gnu/usr.bin/cvs/src/version.c new file mode 100644 index 00000000000..e01fb7aef50 --- /dev/null +++ b/gnu/usr.bin/cvs/src/version.c @@ -0,0 +1,34 @@ +/* + * 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" + +#ifndef lint +static const char rcsid[] = "$CVSid: @(#)version.c 1.15 94/10/03 $"; +USE(rcsid); +#endif + +char *version_string = "\nConcurrent Versions System (CVS) 1.6"; + +#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/gnu/usr.bin/cvs/src/wrapper.c b/gnu/usr.bin/cvs/src/wrapper.c new file mode 100644 index 00000000000..2aafb2f69ca --- /dev/null +++ b/gnu/usr.bin/cvs/src/wrapper.c @@ -0,0 +1,362 @@ +#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); + e.fromcvsFilter=xstrdup(temp); + break; + case 't': + if(e.tocvsFilter) + free(e.tocvsFilter); + e.tocvsFilter=xstrdup(temp); + break; + case 'c': + if(e.conflictHook) + free(e.conflictHook); + e.conflictHook=xstrdup(temp); + 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; +} |