diff options
author | gjb <gjb@FreeBSD.org> | 2013-10-09 17:07:20 +0000 |
---|---|---|
committer | gjb <gjb@FreeBSD.org> | 2013-10-09 17:07:20 +0000 |
commit | c5342bffee8407caeac0b651549206d534dfccb9 (patch) | |
tree | ca04a5cf90c8299963a38ecf5beb3ec437b5f1b1 /gnu/usr.bin/rcs/ci | |
parent | b33524a23e69ae8f48928d5b941a9a25a4609f30 (diff) | |
download | FreeBSD-src-c5342bffee8407caeac0b651549206d534dfccb9.zip FreeBSD-src-c5342bffee8407caeac0b651549206d534dfccb9.tar.gz |
Revert r256095, r256120 (partial), r256121:
r256095:
- Add gnu/usr.bin/rcs back to the base system.
r256120:
- Add WITHOUT_RCS back to src.conf.5.
r256121:
- Remove UPDATING entry regarding gnu/usr.bin/rcs removal.
Requested by: many
Approved by: re (marius)
Discussed with: core
Diffstat (limited to 'gnu/usr.bin/rcs/ci')
-rw-r--r-- | gnu/usr.bin/rcs/ci/Makefile | 8 | ||||
-rw-r--r-- | gnu/usr.bin/rcs/ci/ci.1 | 898 | ||||
-rw-r--r-- | gnu/usr.bin/rcs/ci/ci.c | 1318 |
3 files changed, 2224 insertions, 0 deletions
diff --git a/gnu/usr.bin/rcs/ci/Makefile b/gnu/usr.bin/rcs/ci/Makefile new file mode 100644 index 0000000..2fbb74f --- /dev/null +++ b/gnu/usr.bin/rcs/ci/Makefile @@ -0,0 +1,8 @@ +PROG= ci +SRCS= ci.c +CFLAGS+= -I${.CURDIR}/../lib +LDADD= ${LIBRCS} +DPADD= ${LIBRCS} + +.include "../../Makefile.inc" +.include <bsd.prog.mk> diff --git a/gnu/usr.bin/rcs/ci/ci.1 b/gnu/usr.bin/rcs/ci/ci.1 new file mode 100644 index 0000000..1378af2 --- /dev/null +++ b/gnu/usr.bin/rcs/ci/ci.1 @@ -0,0 +1,898 @@ +.de Id +.ds Rv \\$3 +.ds Dt \\$4 +.. +.Id $FreeBSD$ +.ds i \&\s-1ISO\s0 +.ds r \&\s-1RCS\s0 +.ds u \&\s-1UTC\s0 +.if n .ds - \%-- +.if t .ds - \(em +.TH CI 1 \*(Dt GNU +.SH NAME +ci \- check in RCS revisions +.SH SYNOPSIS +.B ci +.RI [ options ] " file " .\|.\|. +.SH DESCRIPTION +.B ci +stores new revisions into \*r files. +Each pathname matching an \*r suffix +is taken to be an \*r file. +All others +are assumed to be working files containing new revisions. +.B ci +deposits the contents of each working file +into the corresponding \*r file. +If only a working file is given, +.B ci +tries to find the corresponding \*r file in an \*r subdirectory +and then in the working file's directory. +For more details, see +.SM "FILE NAMING" +below. +.PP +For +.B ci +to work, the caller's login must be on the access list, +except if the access list is empty or the caller is the superuser or the +owner of the file. +To append a new revision to an existing branch, the tip revision on +that branch must be locked by the caller. Otherwise, only a +new branch can be created. This restriction is not enforced +for the owner of the file if non-strict locking is used +(see +.BR rcs (1)). +A lock held by someone else can be broken with the +.B rcs +command. +.PP +Unless the +.B \-f +option is given, +.B ci +checks whether the revision to be deposited differs from the preceding one. +If not, instead of creating a new revision +.B ci +reverts to the preceding one. +To revert, ordinary +.B ci +removes the working file and any lock; +.B "ci\ \-l" +keeps and +.B "ci\ \-u" +removes any lock, and then they both generate a new working file much as if +.B "co\ \-l" +or +.B "co\ \-u" +had been applied to the preceding revision. +When reverting, any +.B \-n +and +.B \-s +options apply to the preceding revision. +.PP +For each revision deposited, +.B ci +prompts for a log message. +The log message should summarize the change and must be terminated by +end-of-file or by a line containing +.BR \&. "\ by" +itself. +If several files are checked in +.B ci +asks whether to reuse the +previous log message. +If the standard input is not a terminal, +.B ci +suppresses the prompt +and uses the same log message for all files. +See also +.BR \-m . +.PP +If the \*r file does not exist, +.B ci +creates it and +deposits the contents of the working file as the initial revision +(default number: +.BR 1.1 ). +The access list is initialized to empty. +Instead of the log message, +.B ci +requests descriptive text (see +.B \-t +below). +.PP +The number +.I rev +of the deposited revision can be given by any of the options +.BR \-f , +.BR \-i , +.BR \-I , +.BR \-j , +.BR \-k , +.BR \-l , +.BR \-M , +.BR \-q , +.BR \-r , +or +.BR \-u . +.I rev +can be symbolic, numeric, or mixed. +Symbolic names in +.I rev +must already be defined; +see the +.B \-n +and +.B \-N +options for assigning names during checkin. +If +.I rev +is +.BR $ , +.B ci +determines the revision number from keyword values in the working file. +.PP +If +.I rev +begins with a period, +then the default branch (normally the trunk) is prepended to it. +If +.I rev +is a branch number followed by a period, +then the latest revision on that branch is used. +.PP +If +.I rev +is a revision number, it must be higher than the latest +one on the branch to which +.I rev +belongs, or must start a new branch. +.PP +If +.I rev +is a branch rather than a revision number, +the new revision is appended to that branch. The level number is obtained +by incrementing the tip revision number of that branch. +If +.I rev +indicates a non-existing branch, +that branch is created with the initial revision numbered +.IB rev .1\f1.\fP +.br +.ne 8 +.PP +If +.I rev +is omitted, +.B ci +tries to derive the new revision number from +the caller's last lock. If the caller has locked the tip revision of a branch, +the new revision is appended to that branch. +The new revision number is obtained +by incrementing the tip revision number. +If the caller locked a non-tip revision, a new branch is started at +that revision by incrementing the highest branch number at that revision. +The default initial branch and level numbers are +.BR 1 . +.PP +If +.I rev +is omitted and the caller has no lock, but owns +the file and locking +is not set to +.IR strict , +then the revision is appended to the +default branch (normally the trunk; see the +.B \-b +option of +.BR rcs (1)). +.PP +Exception: On the trunk, revisions can be appended to the end, but +not inserted. +.SH OPTIONS +.TP +.BI \-r rev +Check in revision +.IR rev . +.TP +.BR \-r +The bare +.B \-r +option (without any revision) has an unusual meaning in +.BR ci . +With other \*r commands, a bare +.B \-r +option specifies the most recent revision on the default branch, +but with +.BR ci , +a bare +.B \-r +option reestablishes the default behavior of releasing a lock and +removing the working file, and is used to override any default +.B \-l +or +.B \-u +options established by shell aliases or scripts. +.TP +.BR \-l [\f2rev\fP] +works like +.BR \-r , +except it performs an additional +.B "co\ \-l" +for the +deposited revision. Thus, the deposited revision is immediately +checked out again and locked. +This is useful for saving a revision although one wants to continue +editing it after the checkin. +.TP +.BR \-u [\f2rev\fP] +works like +.BR \-l , +except that the deposited revision is not locked. +This lets one read the working file +immediately after checkin. +.RS +.PP +The +.BR \-l , +bare +.BR \-r , +and +.B \-u +options are mutually exclusive and silently override each other. +For example, +.B "ci\ \-u\ \-r" +is equivalent to +.B "ci\ \-r" +because bare +.B \-r +overrides +.BR \-u . +.RE +.TP +.BR \-f [\f2rev\fP] +forces a deposit; the new revision is deposited even it is not different +from the preceding one. +.TP +.BR \-k [\f2rev\fP] +searches the working file for keyword values to determine its revision number, +creation date, state, and author (see +.BR co (1)), +and assigns these +values to the deposited revision, rather than computing them locally. +It also generates a default login message noting the login of the caller +and the actual checkin date. +This option is useful for software distribution. A revision that is sent to +several sites should be checked in with the +.B \-k +option at these sites to +preserve the original number, date, author, and state. +The extracted keyword values and the default log message can be overridden +with the options +.BR \-d , +.BR \-m , +.BR \-s , +.BR \-w , +and any option that carries a revision number. +.TP +.BR \-q [\f2rev\fP] +quiet mode; diagnostic output is not printed. +A revision that is not different from the preceding one is not deposited, +unless +.B \-f +is given. +.TP +.BR \-i [\f2rev\fP] +initial checkin; report an error if the \*r file already exists. +This avoids race conditions in certain applications. +.TP +.BR \-j [\f2rev\fP] +just checkin and do not initialize; +report an error if the \*r file does not already exist. +.TP +.BR \-I [\f2rev\fP] +interactive mode; +the user is prompted and questioned +even if the standard input is not a terminal. +.TP +.BR \-d "[\f2date\fP]" +uses +.I date +for the checkin date and time. +The +.I date +is specified in free format as explained in +.BR co (1). +This is useful for lying about the checkin date, and for +.B \-k +if no date is available. +If +.I date +is empty, the working file's time of last modification is used. +.TP +.BR \-M [\f2rev\fP] +Set the modification time on any new working file +to be the date of the retrieved revision. +For example, +.BI "ci\ \-d\ \-M\ \-u" "\ f" +does not alter +.IR f 's +modification time, even if +.IR f 's +contents change due to keyword substitution. +Use this option with care; it can confuse +.BR make (1). +.TP +.BI \-m "msg" +uses the string +.I msg +as the log message for all revisions checked in. +By convention, log messages that start with +.B # +are comments and are ignored by programs like GNU Emacs's +.B vc +package. +Also, log messages that start with +.BI { clumpname } +(followed by white space) are meant to be clumped together if possible, +even if they are associated with different files; the +.BI { clumpname } +label is used only for clumping, +and is not considered to be part of the log message itself. +.TP +.BI \-n "name" +assigns the symbolic name +.I name +to the number of the checked-in revision. +.B ci +prints an error message if +.I name +is already assigned to another +number. +.TP +.BI \-N "name" +same as +.BR \-n , +except that it overrides a previous assignment of +.IR name . +.TP +.BI \-s "state" +sets the state of the checked-in revision to the identifier +.IR state . +The default state is +.BR Exp . +.TP +.BI \-t file +writes descriptive text from the contents of the named +.I file +into the \*r file, +deleting the existing text. +The +.I file +cannot begin with +.BR \- . +.TP +.BI \-t\- string +Write descriptive text from the +.I string +into the \*r file, deleting the existing text. +.RS +.PP +The +.B \-t +option, in both its forms, has effect only during an initial checkin; +it is silently ignored otherwise. +.PP +During the initial checkin, if +.B \-t +is not given, +.B ci +obtains the text from standard input, +terminated by end-of-file or by a line containing +.BR \&. "\ by" +itself. +The user is prompted for the text if interaction is possible; see +.BR \-I . +.PP +For backward compatibility with older versions of \*r, a bare +.B \-t +option is ignored. +.RE +.TP +.B \-T +Set the \*r file's modification time to the new revision's time +if the former precedes the latter and there is a new revision; +preserve the \*r file's modification time otherwise. +If you have locked a revision, +.B ci +usually updates the \*r file's modification time to the current time, +because the lock is stored in the \*r file +and removing the lock requires changing the \*r file. +This can create an \*r file newer than the working file in one of two ways: +first, +.B "ci\ \-M" +can create a working file with a date before the current time; +second, when reverting to the previous revision +the \*r file can change while the working file remains unchanged. +These two cases can cause excessive recompilation caused by a +.BR make (1) +dependency of the working file on the \*r file. +The +.B \-T +option inhibits this recompilation by lying about the \*r file's date. +Use this option with care; it can suppress recompilation even when +a checkin of one working file should affect +another working file associated with the same \*r file. +For example, suppose the \*r file's time is 01:00, +the (changed) working file's time is 02:00, +some other copy of the working file has a time of 03:00, +and the current time is 04:00. +Then +.B "ci\ \-d\ \-T" +sets the \*r file's time to 02:00 instead of the usual 04:00; +this causes +.BR make (1) +to think (incorrectly) that the other copy is newer than the \*r file. +.TP +.BI \-w "login" +uses +.I login +for the author field of the deposited revision. +Useful for lying about the author, and for +.B \-k +if no author is available. +.TP +.BI \-V +Print \*r's version number. +.TP +.BI \-V n +Emulate \*r version +.IR n . +See +.BR co (1) +for details. +.TP +.BI \-x "suffixes" +specifies the suffixes for \*r files. +A nonempty suffix matches any pathname ending in the suffix. +An empty suffix matches any pathname of the form +.BI RCS/ path +or +.IB path1 /RCS/ path2. +The +.B \-x +option can specify a list of suffixes +separated by +.BR / . +For example, +.B \-x,v/ +specifies two suffixes: +.B ,v +and the empty suffix. +If two or more suffixes are specified, +they are tried in order when looking for an \*r file; +the first one that works is used for that file. +If no \*r file is found but an \*r file can be created, +the suffixes are tried in order +to determine the new \*r file's name. +The default for +.IR suffixes +is installation-dependent; normally it is +.B ,v/ +for hosts like Unix that permit commas in filenames, +and is empty (i.e. just the empty suffix) for other hosts. +.TP +.BI \-z zone +specifies the date output format in keyword substitution, +and specifies the default time zone for +.I date +in the +.BI \-d date +option. +The +.I zone +should be empty, a numeric \*u offset, or the special string +.B LT +for local time. +The default is an empty +.IR zone , +which uses the traditional \*r format of \*u without any time zone indication +and with slashes separating the parts of the date; +otherwise, times are output in \*i 8601 format with time zone indication. +For example, if local time is January 11, 1990, 8pm Pacific Standard Time, +eight hours west of \*u, +then the time is output as follows: +.RS +.LP +.RS +.nf +.ta \w'\f3\-z+05:30\fP 'u +\w'\f31990-01-11 09:30:00+05:30\fP 'u +.ne 4 +\f2option\fP \f2time output\fP +\f3\-z\fP \f31990/01/12 04:00:00\fP \f2(default)\fP +\f3\-zLT\fP \f31990-01-11 20:00:00\-08\fP +\f3\-z+05:30\fP \f31990-01-12 09:30:00+05:30\fP +.ta 4n +4n +4n +4n +.fi +.RE +.LP +The +.B \-z +option does not affect dates stored in \*r files, +which are always \*u. +.SH "FILE NAMING" +Pairs of \*r files and working files can be specified in three ways +(see also the +example section). +.PP +1) Both the \*r file and the working file are given. The \*r pathname is of +the form +.IB path1 / workfileX +and the working pathname is of the form +.IB path2 / workfile +where +.IB path1 / +and +.IB path2 / +are (possibly different or empty) paths, +.I workfile +is a filename, and +.I X +is an \*r suffix. +If +.I X +is empty, +.IB path1 / +must start with +.B RCS/ +or must contain +.BR /RCS/ . +.PP +2) Only the \*r file is given. Then the working file is created in the current +directory and its name is derived from the name of the \*r file +by removing +.IB path1 / +and the suffix +.IR X . +.PP +3) Only the working file is given. +Then +.B ci +considers each \*r suffix +.I X +in turn, looking for an \*r file of the form +.IB path2 /RCS/ workfileX +or (if the former is not found and +.I X +is nonempty) +.IB path2 / workfileX. +.PP +If the \*r file is specified without a path in 1) and 2), +.B ci +looks for the \*r file first in the directory +.B ./RCS +and then in the current +directory. +.PP +.B ci +reports an error if an attempt to open an \*r file fails for an unusual reason, +even if the \*r file's pathname is just one of several possibilities. +For example, to suppress use of \*r commands in a directory +.IR d , +create a regular file named +.IB d /RCS +so that casual attempts to use \*r commands in +.I d +fail because +.IB d /RCS +is not a directory. +.SH EXAMPLES +Suppose +.B ,v +is an \*r suffix and the current directory contains a subdirectory +.B RCS +with an \*r file +.BR io.c,v . +Then each of the following commands check in a copy of +.B io.c +into +.B RCS/io.c,v +as the latest revision, removing +.BR io.c . +.LP +.RS +.nf +.ft 3 +ci io.c; ci RCS/io.c,v; ci io.c,v; +ci io.c RCS/io.c,v; ci io.c io.c,v; +ci RCS/io.c,v io.c; ci io.c,v io.c; +.ft +.fi +.RE +.PP +Suppose instead that the empty suffix +is an \*r suffix and the current directory contains a subdirectory +.B RCS +with an \*r file +.BR io.c . +The each of the following commands checks in a new revision. +.LP +.RS +.nf +.ft 3 +ci io.c; ci RCS/io.c; +ci io.c RCS/io.c; +ci RCS/io.c io.c; +.ft +.fi +.RE +.SH "FILE MODES" +An \*r file created by +.B ci +inherits the read and execute permissions +from the working file. If the \*r file exists already, +.B ci +preserves its read and execute permissions. +.B ci +always turns off all write permissions of \*r files. +.SH FILES +Temporary files are created in the directory containing +the working file, and also in the temporary directory (see +.B \s-1TMPDIR\s0 +under +.BR \s-1ENVIRONMENT\s0 ). +A semaphore file or files are created in the directory containing the \*r file. +With a nonempty suffix, the semaphore names begin with +the first character of the suffix; therefore, do not specify an suffix +whose first character could be that of a working filename. +With an empty suffix, the semaphore names end with +.B _ +so working filenames should not end in +.BR _ . +.PP +.B ci +never changes an \*r or working file. +Normally, +.B ci +unlinks the file and creates a new one; +but instead of breaking a chain of one or more symbolic links to an \*r file, +it unlinks the destination file instead. +Therefore, +.B ci +breaks any hard or symbolic links to any working file it changes; +and hard links to \*r files are ineffective, +but symbolic links to \*r files are preserved. +.PP +The effective user must be able to +search and write the directory containing the \*r file. +Normally, the real user must be able to +read the \*r and working files +and to search and write the directory containing the working file; +however, some older hosts +cannot easily switch between real and effective users, +so on these hosts the effective user is used for all accesses. +The effective user is the same as the real user +unless your copies of +.B ci +and +.B co +have setuid privileges. +As described in the next section, +these privileges yield extra security if +the effective user owns all \*r files and directories, +and if only the effective user can write \*r directories. +.PP +Users can control access to \*r files by setting the permissions +of the directory containing the files; only users with write access +to the directory can use \*r commands to change its \*r files. +For example, in hosts that allow a user to belong to several groups, +one can make a group's \*r directories writable to that group only. +This approach suffices for informal projects, +but it means that any group member can arbitrarily change the group's \*r files, +and can even remove them entirely. +Hence more formal projects sometimes distinguish between an \*r administrator, +who can change the \*r files at will, and other project members, +who can check in new revisions but cannot otherwise change the \*r files. +.SH "SETUID USE" +To prevent anybody but their \*r administrator from deleting revisions, +a set of users can employ setuid privileges as follows. +.nr n \w'\(bu'+2n-1/1n +.ds n \nn +.if \n(.g .if r an-tag-sep .ds n \w'\(bu'u+\n[an-tag-sep]u +.IP \(bu \*n +Check that the host supports \*r setuid use. +Consult a trustworthy expert if there are any doubts. +It is best if the +.B seteuid +system call works as described in Posix 1003.1a Draft 5, +because \*r can switch back and forth easily +between real and effective users, even if the real user is +.BR root . +If not, the second best is if the +.B setuid +system call supports saved setuid +(the {\s-1_POSIX_SAVED_IDS\s0} behavior of Posix 1003.1-1990); +this fails only if the real or effective user is +.BR root . +If \*r detects any failure in setuid, it quits immediately. +.IP \(bu \nn +Choose a user +.I A +to serve as \*r administrator for the set of users. +Only +.I A +can invoke the +.B rcs +command on the users' \*r files. +.I A +should not be +.B root +or any other user with special powers. +Mutually suspicious sets of users should use different administrators. +.IP \(bu \nn +Choose a pathname +.I B +to be a directory of files to be executed by the users. +.IP \(bu \nn +Have +.I A +set up +.I B +to contain copies of +.B ci +and +.B co +that are setuid to +.I A +by copying the commands from their standard installation directory +.I D +as follows: +.LP +.RS +.nf +.ne 3 +\f3mkdir\fP \f2B\fP +\f3cp\fP \f2D\fP\^\f3/c[io]\fP \f2B\fP +\f3chmod go\-w,u+s\fP \f2B\fP\f3/c[io]\fP +.fi +.RE +.IP \(bu \nn +Have each user prepend +.I B +to their path as follows: +.LP +.RS +.nf +.ne 2 +\f3PATH=\fP\f2B\fP\f3:$PATH; export PATH\fP # ordinary shell +\f3set path=(\fP\f2B\fP \f3$path)\fP # C shell +.fi +.RE +.IP \(bu \nn +Have +.I A +create each \*r directory +.I R +with write access only to +.I A +as follows: +.LP +.RS +.nf +.ne 2 +\f3mkdir\fP \f2R\fP +\f3chmod go\-w\fP \f2R\fP +.fi +.RE +.IP \(bu \nn +If you want to let only certain users read the \*r files, +put the users into a group +.IR G , +and have +.I A +further protect the \*r directory as follows: +.LP +.RS +.nf +.ne 2 +\f3chgrp\fP \f2G R\fP +\f3chmod g\-w,o\-rwx\fP \f2R\fP +.fi +.RE +.IP \(bu \nn +Have +.I A +copy old \*r files (if any) into +.IR R , +to ensure that +.I A +owns them. +.IP \(bu \nn +An \*r file's access list limits who can check in and lock revisions. +The default access list is empty, +which grants checkin access to anyone who can read the \*r file. +If you want limit checkin access, +have +.I A +invoke +.B "rcs\ \-a" +on the file; see +.BR rcs (1). +In particular, +.BI "rcs\ \-e\ \-a" A +limits access to just +.IR A . +.IP \(bu \nn +Have +.I A +initialize any new \*r files with +.B "rcs\ \-i" +before initial checkin, adding the +.B \-a +option if you want to limit checkin access. +.IP \(bu \nn +Give setuid privileges only to +.BR ci , +.BR co , +and +.BR rcsclean ; +do not give them to +.B rcs +or to any other command. +.IP \(bu \nn +Do not use other setuid commands to invoke \*r commands; +setuid is trickier than you think! +.SH ENVIRONMENT +.TP +.B \s-1RCSINIT\s0 +options prepended to the argument list, separated by spaces. +A backslash escapes spaces within an option. +The +.B \s-1RCSINIT\s0 +options are prepended to the argument lists of most \*r commands. +Useful +.B \s-1RCSINIT\s0 +options include +.BR \-q , +.BR \-V , +.BR \-x , +and +.BR \-z . +.TP +.B \s-1TMPDIR\s0 +Name of the temporary directory. +If not set, the environment variables +.B \s-1TMP\s0 +and +.B \s-1TEMP\s0 +are inspected instead and the first value found is taken; +if none of them are set, +a host-dependent default is used, typically +.BR /tmp . +.SH DIAGNOSTICS +For each revision, +.B ci +prints the \*r file, the working file, and the number +of both the deposited and the preceding revision. +The exit status is zero if and only if all operations were successful. +.SH IDENTIFICATION +Author: Walter F. Tichy. +.br +Manual Page Revision: \*(Rv; Release Date: \*(Dt. +.br +Copyright \(co 1982, 1988, 1989 Walter F. Tichy. +.br +Copyright \(co 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert. +.SH "SEE ALSO" +co(1), +ident(1), make(1), rcs(1), rcsclean(1), rcsdiff(1), +rcsintro(1), rcsmerge(1), rlog(1), setuid(2), rcsfile(5) +.br +Walter F. Tichy, +\*r\*-A System for Version Control, +.I "Software\*-Practice & Experience" +.BR 15 , +7 (July 1985), 637-654. +.br diff --git a/gnu/usr.bin/rcs/ci/ci.c b/gnu/usr.bin/rcs/ci/ci.c new file mode 100644 index 0000000..749a4cf --- /dev/null +++ b/gnu/usr.bin/rcs/ci/ci.c @@ -0,0 +1,1318 @@ +/* Check in revisions of RCS files from working files. */ + +/* Copyright 1982, 1988, 1989 Walter Tichy + Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert + Distributed under license by the Free Software Foundation, Inc. + +This file is part of RCS. + +RCS 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. + +RCS 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 RCS; see the file COPYING. +If not, write to the Free Software Foundation, +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +Report problems and direct all questions to: + + rcs-bugs@cs.purdue.edu + +*/ + +/* + * Revision 5.30 1995/06/16 06:19:24 eggert + * Update FSF address. + * + * Revision 5.29 1995/06/01 16:23:43 eggert + * (main): Add -kb. + * Use `cmpdate', not `cmpnum', to compare dates. + * This is for MKS RCS's incompatible 20th-century date format. + * Don't worry about errno after ftruncate fails. + * Fix input file rewinding bug when large_memory && !maps_memory + * and checking in a branch tip. + * + * (fixwork): Fall back on chmod if fchmod fails, since it might be ENOSYS. + * + * Revision 5.28 1994/03/20 04:52:58 eggert + * Do not generate a corrupted RCS file if the user modifies the working file + * while `ci' is running. + * Do not remove the lock when `ci -l' reverts. + * Move buffer-flushes out of critical sections, since they aren't critical. + * Use ORCSerror to clean up after a fatal error. + * Specify subprocess input via file descriptor, not file name. + * + * Revision 5.27 1993/11/09 17:40:15 eggert + * -V now prints version on stdout and exits. Don't print usage twice. + * + * Revision 5.26 1993/11/03 17:42:27 eggert + * Add -z. Don't subtract from RCS file timestamp even if -T. + * Scan for and use Name keyword if -k. + * Don't discard ignored phrases. Improve quality of diagnostics. + * + * Revision 5.25 1992/07/28 16:12:44 eggert + * Add -i, -j, -V. Check that working and RCS files are distinct. + * + * Revision 5.24 1992/02/17 23:02:06 eggert + * `-rREV' now just specifies a revision REV; only bare `-r' reverts to default. + * Add -T. + * + * Revision 5.23 1992/01/27 16:42:51 eggert + * Always unlock branchpoint if caller has a lock. + * Add support for bad_chmod_close, bad_creat0. lint -> RCS_lint + * + * Revision 5.22 1992/01/06 02:42:34 eggert + * Invoke utime() before chmod() to keep some buggy systems happy. + * + * Revision 5.21 1991/11/20 17:58:07 eggert + * Don't read the delta tree from a nonexistent RCS file. + * + * Revision 5.20 1991/10/07 17:32:46 eggert + * Fix log bugs. Remove lint. + * + * Revision 5.19 1991/09/26 23:10:30 eggert + * Plug file descriptor leak. + * + * Revision 5.18 1991/09/18 07:29:10 eggert + * Work around a common ftruncate() bug. + * + * Revision 5.17 1991/09/10 22:15:46 eggert + * Fix test for redirected stdin. + * + * Revision 5.16 1991/08/19 23:17:54 eggert + * When there are no changes, revert to previous revision instead of aborting. + * Add piece tables, -M, -r$. Tune. + * + * Revision 5.15 1991/04/21 11:58:14 eggert + * Ensure that working file is newer than RCS file after ci -[lu]. + * Add -x, RCSINIT, MS-DOS support. + * + * Revision 5.14 1991/02/28 19:18:47 eggert + * Don't let a setuid ci create a new RCS file; rcs -i -a must be run first. + * Fix ci -ko -l mode bug. Open work file at most once. + * + * Revision 5.13 1991/02/25 07:12:33 eggert + * getdate -> getcurdate (SVR4 name clash) + * + * Revision 5.12 1990/12/31 01:00:12 eggert + * Don't use uninitialized storage when handling -{N,n}. + * + * Revision 5.11 1990/12/04 05:18:36 eggert + * Use -I for prompts and -q for diagnostics. + * + * Revision 5.10 1990/11/05 20:30:10 eggert + * Don't remove working file when aborting due to no changes. + * + * Revision 5.9 1990/11/01 05:03:23 eggert + * Add -I and new -t behavior. Permit arbitrary data in logs. + * + * Revision 5.8 1990/10/04 06:30:09 eggert + * Accumulate exit status across files. + * + * Revision 5.7 1990/09/25 20:11:46 hammer + * fixed another small typo + * + * Revision 5.6 1990/09/24 21:48:50 hammer + * added cleanups from Paul Eggert. + * + * Revision 5.5 1990/09/21 06:16:38 hammer + * made it handle multiple -{N,n}'s. Also, made it treat re-directed stdin + * the same as the terminal + * + * Revision 5.4 1990/09/20 02:38:51 eggert + * ci -k now checks dates more thoroughly. + * + * Revision 5.3 1990/09/11 02:41:07 eggert + * Fix revision bug with `ci -k file1 file2'. + * + * Revision 5.2 1990/09/04 08:02:10 eggert + * Permit adjacent revisions with identical time stamps (possible on fast hosts). + * Improve incomplete line handling. Standardize yes-or-no procedure. + * + * Revision 5.1 1990/08/29 07:13:44 eggert + * Expand locker value like co. Clean old log messages too. + * + * Revision 5.0 1990/08/22 08:10:00 eggert + * Don't require a final newline. + * Make lock and temp files faster and safer. + * Remove compile-time limits; use malloc instead. + * Permit dates past 1999/12/31. Switch to GMT. + * Add setuid support. Don't pass +args to diff. Check diff's output. + * Ansify and Posixate. Add -k, -V. Remove snooping. Tune. + * Check diff's output. + * + * Revision 4.9 89/05/01 15:10:54 narten + * changed copyright header to reflect current distribution rules + * + * Revision 4.8 88/11/08 13:38:23 narten + * changes from root@seismo.CSS.GOV (Super User) + * -d with no arguments uses the mod time of the file it is checking in + * + * Revision 4.7 88/08/09 19:12:07 eggert + * Make sure workfile is a regular file; use its mode if RCSfile doesn't have one. + * Use execv(), not system(); allow cc -R; remove lint. + * isatty(fileno(stdin)) -> ttystdin() + * + * Revision 4.6 87/12/18 11:34:41 narten + * lint cleanups (from Guy Harris) + * + * Revision 4.5 87/10/18 10:18:48 narten + * Updating version numbers. Changes relative to revision 1.1 are actually + * relative to 4.3 + * + * Revision 1.3 87/09/24 13:57:19 narten + * Sources now pass through lint (if you ignore printf/sprintf/fprintf + * warnings) + * + * Revision 1.2 87/03/27 14:21:33 jenkins + * Port to suns + * + * Revision 4.3 83/12/15 12:28:54 wft + * ci -u and ci -l now set mode of working file properly. + * + * Revision 4.2 83/12/05 13:40:54 wft + * Merged with 3.9.1.1: added calls to clearerr(stdin). + * made rewriteflag external. + * + * Revision 4.1 83/05/10 17:03:06 wft + * Added option -d and -w, and updated assingment of date, etc. to new delta. + * Added handling of default branches. + * Option -k generates std. log message; fixed undef. pointer in reading of log. + * Replaced getlock() with findlock(), link--unlink with rename(), + * getpwuid() with getcaller(). + * Moved all revision number generation to new routine addelta(). + * Removed calls to stat(); now done by pairfilenames(). + * Changed most calls to catchints() with restoreints(). + * Directed all interactive messages to stderr. + * + * Revision 3.9.1.1 83/10/19 04:21:03 lepreau + * Added clearerr(stdin) to getlogmsg() for re-reading stdin. + * + * Revision 3.9 83/02/15 15:25:44 wft + * 4.2 prerelease + * + * Revision 3.9 83/02/15 15:25:44 wft + * Added call to fastcopy() to copy remainder of RCS file. + * + * Revision 3.8 83/01/14 15:34:05 wft + * Added ignoring of interrupts while new RCS file is renamed; + * Avoids deletion of RCS files by interrupts. + * + * Revision 3.7 82/12/10 16:09:20 wft + * Corrected checking of return code from diff. + * + * Revision 3.6 82/12/08 21:34:49 wft + * Using DATEFORM to prepare date of checked-in revision; + * Fixed return from addbranch(). + * + * Revision 3.5 82/12/04 18:32:42 wft + * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated + * field lockedby in removelock(), moved getlogmsg() before calling diff. + * + * Revision 3.4 82/12/02 13:27:13 wft + * added option -k. + * + * Revision 3.3 82/11/28 20:53:31 wft + * Added mustcheckin() to check for redundant checkins. + * Added xpandfile() to do keyword expansion for -u and -l; + * -m appends linefeed to log message if necessary. + * getlogmsg() suppresses prompt if stdin is not a terminal. + * Replaced keeplock with lockflag, fclose() with ffclose(), + * %02d with %.2d, getlogin() with getpwuid(). + * + * Revision 3.2 82/10/18 20:57:23 wft + * An RCS file inherits its mode during the first ci from the working file, + * otherwise it stays the same, except that write permission is removed. + * Fixed ci -l, added ci -u (both do an implicit co after the ci). + * Fixed call to getlogin(), added call to getfullRCSname(), added check + * for write error. + * Changed conflicting identifiers. + * + * Revision 3.1 82/10/13 16:04:59 wft + * fixed type of variables receiving from getc() (char -> int). + * added include file dbm.h for getting BYTESIZ. This is used + * to check the return code from diff portably. + */ + +#include "rcsbase.h" + +struct Symrev { + char const *ssymbol; + int override; + struct Symrev * nextsym; +}; + +static char const *getcurdate P((void)); +static int addbranch P((struct hshentry*,struct buf*,int)); +static int addelta P((void)); +static int addsyms P((char const*)); +static int fixwork P((mode_t,time_t)); +static int removelock P((struct hshentry*)); +static int xpandfile P((RILE*,struct hshentry const*,char const**,int)); +static struct cbuf getlogmsg P((void)); +static void cleanup P((void)); +static void incnum P((char const*,struct buf*)); +static void addassoclst P((int,char const*)); + +static FILE *exfile; +static RILE *workptr; /* working file pointer */ +static struct buf newdelnum; /* new revision number */ +static struct cbuf msg; +static int exitstatus; +static int forceciflag; /* forces check in */ +static int keepflag, keepworkingfile, rcsinitflag; +static struct hshentries *gendeltas; /* deltas to be generated */ +static struct hshentry *targetdelta; /* old delta to be generated */ +static struct hshentry newdelta; /* new delta to be inserted */ +static struct stat workstat; +static struct Symrev *assoclst, **nextassoc; + +mainProg(ciId, "ci", "$FreeBSD$") +{ + static char const cmdusage[] = + "\nci usage: ci -{fIklMqru}[rev] -d[date] -mmsg -{nN}name -sstate -ttext -T -Vn -wwho -xsuff -zzone file ..."; + static char const default_state[] = DEFAULTSTATE; + + char altdate[datesize]; + char olddate[datesize]; + char newdatebuf[datesize + zonelenmax]; + char targetdatebuf[datesize + zonelenmax]; + char *a, **newargv, *textfile; + char const *author, *krev, *rev, *state; + char const *diffname, *expname; + char const *newworkname; + int initflag, mustread; + int lockflag, lockthis, mtimeflag, removedlock, Ttimeflag; + int r; + int changedRCS, changework, dolog, newhead; + int usestatdate; /* Use mod time of file for -d. */ + mode_t newworkmode; /* mode for working file */ + time_t mtime, wtime; + struct hshentry *workdelta; + + setrid(); + + author = rev = state = textfile = 0; + initflag = lockflag = mustread = false; + mtimeflag = false; + Ttimeflag = false; + altdate[0]= '\0'; /* empty alternate date for -d */ + usestatdate=false; + suffixes = X_DEFAULT; + nextassoc = &assoclst; + + argc = getRCSINIT(argc, argv, &newargv); + argv = newargv; + while (a = *++argv, 0<--argc && *a++=='-') { + switch (*a++) { + + case 'r': + if (*a) + goto revno; + keepworkingfile = lockflag = false; + break; + + case 'l': + keepworkingfile = lockflag = true; + revno: + if (*a) { + if (rev) warn("redefinition of revision number"); + rev = a; + } + break; + + case 'u': + keepworkingfile=true; lockflag=false; + goto revno; + + case 'i': + initflag = true; + goto revno; + + case 'j': + mustread = true; + goto revno; + + case 'I': + interactiveflag = true; + goto revno; + + case 'q': + quietflag=true; + goto revno; + + case 'f': + forceciflag=true; + goto revno; + + case 'k': + keepflag=true; + goto revno; + + case 'm': + if (msg.size) redefined('m'); + msg = cleanlogmsg(a, strlen(a)); + if (!msg.size) + error("missing message for -m option"); + break; + + case 'n': + if (!*a) { + error("missing symbolic name after -n"); + break; + } + checkssym(a); + addassoclst(false, a); + break; + + case 'N': + if (!*a) { + error("missing symbolic name after -N"); + break; + } + checkssym(a); + addassoclst(true, a); + break; + + case 's': + if (*a) { + if (state) redefined('s'); + checksid(a); + state = a; + } else + error("missing state for -s option"); + break; + + case 't': + if (*a) { + if (textfile) redefined('t'); + textfile = a; + } + break; + + case 'd': + if (altdate[0] || usestatdate) + redefined('d'); + altdate[0] = '\0'; + if (!(usestatdate = !*a)) + str2date(a, altdate); + break; + + case 'M': + mtimeflag = true; + goto revno; + + case 'w': + if (*a) { + if (author) redefined('w'); + checksid(a); + author = a; + } else + error("missing author for -w option"); + break; + + case 'x': + suffixes = a; + break; + + case 'V': + setRCSversion(*argv); + break; + + case 'z': + zone_set(a); + break; + + case 'T': + if (!*a) { + Ttimeflag = true; + break; + } + /* fall into */ + default: + error("unknown option: %s%s", *argv, cmdusage); + }; + } /* end processing of options */ + + /* Handle all pathnames. */ + if (nerror) cleanup(); + else if (argc < 1) faterror("no input file%s", cmdusage); + else for (; 0 < argc; cleanup(), ++argv, --argc) { + targetdelta = 0; + ffree(); + + switch (pairnames(argc, argv, rcswriteopen, mustread, false)) { + + case -1: /* New RCS file */ +# if has_setuid && has_getuid + if (euid() != ruid()) { + workerror("setuid initial checkin prohibited; use `rcs -i -a' first"); + continue; + } +# endif + rcsinitflag = true; + break; + + case 0: /* Error */ + continue; + + case 1: /* Normal checkin with prev . RCS file */ + if (initflag) { + rcserror("already exists"); + continue; + } + rcsinitflag = !Head; + } + + /* + * RCSname contains the name of the RCS file, and + * workname contains the name of the working file. + * If the RCS file exists, finptr contains the file descriptor for the + * RCS file, and RCSstat is set. The admin node is initialized. + */ + + diagnose("%s <-- %s\n", RCSname, workname); + + if (!(workptr = Iopen(workname, FOPEN_R_WORK, &workstat))) { + eerror(workname); + continue; + } + + if (finptr) { + if (same_file(RCSstat, workstat, 0)) { + rcserror("RCS file is the same as working file %s.", + workname + ); + continue; + } + if (!checkaccesslist()) + continue; + } + + krev = rev; + if (keepflag) { + /* get keyword values from working file */ + if (!getoldkeys(workptr)) continue; + if (!rev && !*(krev = prevrev.string)) { + workerror("can't find a revision number"); + continue; + } + if (!*prevdate.string && *altdate=='\0' && usestatdate==false) + workwarn("can't find a date"); + if (!*prevauthor.string && !author) + workwarn("can't find an author"); + if (!*prevstate.string && !state) + workwarn("can't find a state"); + } /* end processing keepflag */ + + /* Read the delta tree. */ + if (finptr) + gettree(); + + /* expand symbolic revision number */ + if (!fexpandsym(krev, &newdelnum, workptr)) + continue; + + /* splice new delta into tree */ + if ((removedlock = addelta()) < 0) + continue; + + newdelta.num = newdelnum.string; + newdelta.branches = 0; + newdelta.lockedby = 0; /* This might be changed by addlock(). */ + newdelta.selector = true; + newdelta.name = 0; + clear_buf(&newdelta.ig); + clear_buf(&newdelta.igtext); + /* set author */ + if (author) + newdelta.author=author; /* set author given by -w */ + else if (keepflag && *prevauthor.string) + newdelta.author=prevauthor.string; /* preserve old author if possible*/ + else newdelta.author=getcaller();/* otherwise use caller's id */ + newdelta.state = default_state; + if (state) + newdelta.state=state; /* set state given by -s */ + else if (keepflag && *prevstate.string) + newdelta.state=prevstate.string; /* preserve old state if possible */ + if (usestatdate) { + time2date(workstat.st_mtime, altdate); + } + if (*altdate!='\0') + newdelta.date=altdate; /* set date given by -d */ + else if (keepflag && *prevdate.string) { + /* Preserve old date if possible. */ + str2date(prevdate.string, olddate); + newdelta.date = olddate; + } else + newdelta.date = getcurdate(); /* use current date */ + /* now check validity of date -- needed because of -d and -k */ + if (targetdelta && + cmpdate(newdelta.date,targetdelta->date) < 0) { + rcserror("Date %s precedes %s in revision %s.", + date2str(newdelta.date, newdatebuf), + date2str(targetdelta->date, targetdatebuf), + targetdelta->num + ); + continue; + } + + + if (lockflag && addlock(&newdelta, true) < 0) continue; + + if (keepflag && *prevname.string) + if (addsymbol(newdelta.num, prevname.string, false) < 0) + continue; + if (!addsyms(newdelta.num)) + continue; + + + putadmin(); + puttree(Head,frewrite); + putdesc(false,textfile); + + changework = Expand < MIN_UNCHANGED_EXPAND; + dolog = true; + lockthis = lockflag; + workdelta = &newdelta; + + /* build rest of file */ + if (rcsinitflag) { + diagnose("initial revision: %s\n", newdelta.num); + /* get logmessage */ + newdelta.log=getlogmsg(); + putdftext(&newdelta, workptr, frewrite, false); + RCSstat.st_mode = workstat.st_mode; + RCSstat.st_nlink = 0; + changedRCS = true; + } else { + diffname = maketemp(0); + newhead = Head == &newdelta; + if (!newhead) + foutptr = frewrite; + expname = buildrevision( + gendeltas, targetdelta, (FILE*)0, false + ); + if ( + !forceciflag && + strcmp(newdelta.state, targetdelta->state) == 0 && + (changework = rcsfcmp( + workptr, &workstat, expname, targetdelta + )) <= 0 + ) { + diagnose("file is unchanged; reverting to previous revision %s\n", + targetdelta->num + ); + if (removedlock < lockflag) { + diagnose("previous revision was not locked; ignoring -l option\n"); + lockthis = 0; + } + dolog = false; + if (! (changedRCS = lockflag<removedlock || assoclst)) + workdelta = targetdelta; + else { + /* + * We have started to build the wrong new RCS file. + * Start over from the beginning. + */ + long hwm = ftell(frewrite); + int bad_truncate; + Orewind(frewrite); + + /* + * Work around a common ftruncate() bug: + * NFS won't let you truncate a file that you + * currently lack permissions for, even if you + * had permissions when you opened it. + * Also, Posix 1003.1b-1993 sec 5.6.7.2 p 128 l 1022 + * says ftruncate might fail because it's not supported. + */ +# if !has_ftruncate +# undef ftruncate +# define ftruncate(fd,length) (-1) +# endif + bad_truncate = ftruncate(fileno(frewrite), (off_t)0); + + Irewind(finptr); + Lexinit(); + getadmin(); + gettree(); + if (!(workdelta = genrevs( + targetdelta->num, (char*)0, (char*)0, (char*)0, + &gendeltas + ))) + continue; + workdelta->log = targetdelta->log; + if (newdelta.state != default_state) + workdelta->state = newdelta.state; + if (lockthis<removedlock && removelock(workdelta)<0) + continue; + if (!addsyms(workdelta->num)) + continue; + if (dorewrite(true, true) != 0) + continue; + fastcopy(finptr, frewrite); + if (bad_truncate) + while (ftell(frewrite) < hwm) + /* White out any earlier mistake with '\n's. */ + /* This is unlikely. */ + afputc('\n', frewrite); + } + } else { + int wfd = Ifileno(workptr); + struct stat checkworkstat; + char const *diffv[6 + !!OPEN_O_BINARY], **diffp; +# if large_memory && !maps_memory + FILE *wfile = workptr->stream; + long wfile_off; +# endif +# if !has_fflush_input && !(large_memory && maps_memory) + off_t wfd_off; +# endif + + diagnose("new revision: %s; previous revision: %s\n", + newdelta.num, targetdelta->num + ); + newdelta.log = getlogmsg(); +# if !large_memory + Irewind(workptr); +# if has_fflush_input + if (fflush(workptr) != 0) + Ierror(); +# endif +# else +# if !maps_memory + if ( + (wfile_off = ftell(wfile)) == -1 + || fseek(wfile, 0L, SEEK_SET) != 0 +# if has_fflush_input + || fflush(wfile) != 0 +# endif + ) + Ierror(); +# endif +# endif +# if !has_fflush_input && !(large_memory && maps_memory) + wfd_off = lseek(wfd, (off_t)0, SEEK_CUR); + if (wfd_off == -1 + || (wfd_off != 0 + && lseek(wfd, (off_t)0, SEEK_SET) != 0)) + Ierror(); +# endif + diffp = diffv; + *++diffp = DIFF; + *++diffp = DIFFFLAGS; +# if OPEN_O_BINARY + if (Expand == BINARY_EXPAND) + *++diffp = "--binary"; +# endif + *++diffp = newhead ? "-" : expname; + *++diffp = newhead ? expname : "-"; + *++diffp = 0; + switch (runv(wfd, diffname, diffv)) { + case DIFF_FAILURE: case DIFF_SUCCESS: break; + default: rcsfaterror("diff failed"); + } +# if !has_fflush_input && !(large_memory && maps_memory) + if (lseek(wfd, wfd_off, SEEK_CUR) == -1) + Ierror(); +# endif +# if large_memory && !maps_memory + if (fseek(wfile, wfile_off, SEEK_SET) != 0) + Ierror(); +# endif + if (newhead) { + Irewind(workptr); + putdftext(&newdelta, workptr, frewrite, false); + if (!putdtext(targetdelta,diffname,frewrite,true)) continue; + } else + if (!putdtext(&newdelta,diffname,frewrite,true)) continue; + + /* + * Check whether the working file changed during checkin, + * to avoid producing an inconsistent RCS file. + */ + if ( + fstat(wfd, &checkworkstat) != 0 + || workstat.st_mtime != checkworkstat.st_mtime + || workstat.st_size != checkworkstat.st_size + ) { + workerror("file changed during checkin"); + continue; + } + + changedRCS = true; + } + } + + /* Deduce time_t of new revision if it is needed later. */ + wtime = (time_t)-1; + if (mtimeflag | Ttimeflag) + wtime = date2time(workdelta->date); + + if (donerewrite(changedRCS, + !Ttimeflag ? (time_t)-1 + : finptr && wtime < RCSstat.st_mtime ? RCSstat.st_mtime + : wtime + ) != 0) + continue; + + if (!keepworkingfile) { + Izclose(&workptr); + r = un_link(workname); /* Get rid of old file */ + } else { + newworkmode = WORKMODE(RCSstat.st_mode, + ! (Expand==VAL_EXPAND || lockthis < StrictLocks) + ); + mtime = mtimeflag ? wtime : (time_t)-1; + + /* Expand if it might change or if we can't fix mode, time. */ + if (changework || (r=fixwork(newworkmode,mtime)) != 0) { + Irewind(workptr); + /* Expand keywords in file. */ + locker_expansion = lockthis; + workdelta->name = + namedrev( + assoclst ? assoclst->ssymbol + : keepflag && *prevname.string ? prevname.string + : rev, + workdelta + ); + switch (xpandfile( + workptr, workdelta, &newworkname, dolog + )) { + default: + continue; + + case 0: + /* + * No expansion occurred; try to reuse working file + * unless we already tried and failed. + */ + if (changework) + if ((r=fixwork(newworkmode,mtime)) == 0) + break; + /* fall into */ + case 1: + Izclose(&workptr); + aflush(exfile); + ignoreints(); + r = chnamemod(&exfile, newworkname, + workname, 1, newworkmode, mtime + ); + keepdirtemp(newworkname); + restoreints(); + } + } + } + if (r != 0) { + eerror(workname); + continue; + } + diagnose("done\n"); + + } + + tempunlink(); + exitmain(exitstatus); +} /* end of main (ci) */ + + static void +cleanup() +{ + if (nerror) exitstatus = EXIT_FAILURE; + Izclose(&finptr); + Izclose(&workptr); + Ozclose(&exfile); + Ozclose(&fcopy); + ORCSclose(); + dirtempunlink(); +} + +#if RCS_lint +# define exiterr ciExit +#endif + void +exiterr() +{ + ORCSerror(); + dirtempunlink(); + tempunlink(); + _exit(EXIT_FAILURE); +} + +/*****************************************************************/ +/* the rest are auxiliary routines */ + + + static int +addelta() +/* Function: Appends a delta to the delta tree, whose number is + * given by newdelnum. Updates Head, newdelnum, newdelnumlength, + * and the links in newdelta. + * Return -1 on error, 1 if a lock is removed, 0 otherwise. + */ +{ + register char *tp; + register int i; + int removedlock; + int newdnumlength; /* actual length of new rev. num. */ + + newdnumlength = countnumflds(newdelnum.string); + + if (rcsinitflag) { + /* this covers non-existing RCS file and a file initialized with rcs -i */ + if (newdnumlength==0 && Dbranch) { + bufscpy(&newdelnum, Dbranch); + newdnumlength = countnumflds(Dbranch); + } + if (newdnumlength==0) bufscpy(&newdelnum, "1.1"); + else if (newdnumlength==1) bufscat(&newdelnum, ".1"); + else if (newdnumlength>2) { + rcserror("Branch point doesn't exist for revision %s.", + newdelnum.string + ); + return -1; + } /* newdnumlength == 2 is OK; */ + Head = &newdelta; + newdelta.next = 0; + return 0; + } + if (newdnumlength==0) { + /* derive new revision number from locks */ + switch (findlock(true, &targetdelta)) { + + default: + /* found two or more old locks */ + return -1; + + case 1: + /* found an old lock */ + /* check whether locked revision exists */ + if (!genrevs(targetdelta->num,(char*)0,(char*)0,(char*)0,&gendeltas)) + return -1; + if (targetdelta==Head) { + /* make new head */ + newdelta.next=Head; + Head= &newdelta; + } else if (!targetdelta->next && countnumflds(targetdelta->num)>2) { + /* new tip revision on side branch */ + targetdelta->next= &newdelta; + newdelta.next = 0; + } else { + /* middle revision; start a new branch */ + bufscpy(&newdelnum, ""); + return addbranch(targetdelta, &newdelnum, 1); + } + incnum(targetdelta->num, &newdelnum); + return 1; /* successful use of existing lock */ + + case 0: + /* no existing lock; try Dbranch */ + /* update newdelnum */ + if (StrictLocks || !myself(RCSstat.st_uid)) { + rcserror("no lock set by %s", getcaller()); + return -1; + } + if (Dbranch) { + bufscpy(&newdelnum, Dbranch); + } else { + incnum(Head->num, &newdelnum); + } + newdnumlength = countnumflds(newdelnum.string); + /* now fall into next statement */ + } + } + if (newdnumlength<=2) { + /* add new head per given number */ + if(newdnumlength==1) { + /* make a two-field number out of it*/ + if (cmpnumfld(newdelnum.string,Head->num,1)==0) + incnum(Head->num, &newdelnum); + else + bufscat(&newdelnum, ".1"); + } + if (cmpnum(newdelnum.string,Head->num) <= 0) { + rcserror("revision %s too low; must be higher than %s", + newdelnum.string, Head->num + ); + return -1; + } + targetdelta = Head; + if (0 <= (removedlock = removelock(Head))) { + if (!genrevs(Head->num,(char*)0,(char*)0,(char*)0,&gendeltas)) + return -1; + newdelta.next = Head; + Head = &newdelta; + } + return removedlock; + } else { + /* put new revision on side branch */ + /*first, get branch point */ + tp = newdelnum.string; + for (i = newdnumlength - ((newdnumlength&1) ^ 1); --i; ) + while (*tp++ != '.') + continue; + *--tp = 0; /* Kill final dot to get old delta temporarily. */ + if (!(targetdelta=genrevs(newdelnum.string,(char*)0,(char*)0,(char*)0,&gendeltas))) + return -1; + if (cmpnum(targetdelta->num, newdelnum.string) != 0) { + rcserror("can't find branch point %s", newdelnum.string); + return -1; + } + *tp = '.'; /* Restore final dot. */ + return addbranch(targetdelta, &newdelnum, 0); + } +} + + + + static int +addbranch(branchpoint, num, removedlock) + struct hshentry *branchpoint; + struct buf *num; + int removedlock; +/* adds a new branch and branch delta at branchpoint. + * If num is the null string, appends the new branch, incrementing + * the highest branch number (initially 1), and setting the level number to 1. + * the new delta and branchhead are in globals newdelta and newbranch, resp. + * the new number is placed into num. + * Return -1 on error, 1 if a lock is removed, 0 otherwise. + * If REMOVEDLOCK is 1, a lock was already removed. + */ +{ + struct branchhead *bhead, **btrail; + struct buf branchnum; + int result; + int field, numlength; + static struct branchhead newbranch; /* new branch to be inserted */ + + numlength = countnumflds(num->string); + + if (!branchpoint->branches) { + /* start first branch */ + branchpoint->branches = &newbranch; + if (numlength==0) { + bufscpy(num, branchpoint->num); + bufscat(num, ".1.1"); + } else if (numlength&1) + bufscat(num, ".1"); + newbranch.nextbranch = 0; + + } else if (numlength==0) { + /* append new branch to the end */ + bhead=branchpoint->branches; + while (bhead->nextbranch) bhead=bhead->nextbranch; + bhead->nextbranch = &newbranch; + bufautobegin(&branchnum); + getbranchno(bhead->hsh->num, &branchnum); + incnum(branchnum.string, num); + bufautoend(&branchnum); + bufscat(num, ".1"); + newbranch.nextbranch = 0; + } else { + /* place the branch properly */ + field = numlength - ((numlength&1) ^ 1); + /* field of branch number */ + btrail = &branchpoint->branches; + while (0 < (result=cmpnumfld(num->string,(*btrail)->hsh->num,field))) { + btrail = &(*btrail)->nextbranch; + if (!*btrail) { + result = -1; + break; + } + } + if (result < 0) { + /* insert/append new branchhead */ + newbranch.nextbranch = *btrail; + *btrail = &newbranch; + if (numlength&1) bufscat(num, ".1"); + } else { + /* branch exists; append to end */ + bufautobegin(&branchnum); + getbranchno(num->string, &branchnum); + targetdelta = genrevs( + branchnum.string, (char*)0, (char*)0, (char*)0, + &gendeltas + ); + bufautoend(&branchnum); + if (!targetdelta) + return -1; + if (cmpnum(num->string,targetdelta->num) <= 0) { + rcserror("revision %s too low; must be higher than %s", + num->string, targetdelta->num + ); + return -1; + } + if (!removedlock + && 0 <= (removedlock = removelock(targetdelta)) + ) { + if (numlength&1) + incnum(targetdelta->num,num); + targetdelta->next = &newdelta; + newdelta.next = 0; + } + return removedlock; + /* Don't do anything to newbranch. */ + } + } + newbranch.hsh = &newdelta; + newdelta.next = 0; + if (branchpoint->lockedby) + if (strcmp(branchpoint->lockedby, getcaller()) == 0) + return removelock(branchpoint); /* This returns 1. */ + return removedlock; +} + + static int +addsyms(num) + char const *num; +{ + register struct Symrev *p; + + for (p = assoclst; p; p = p->nextsym) + if (addsymbol(num, p->ssymbol, p->override) < 0) + return false; + return true; +} + + + static void +incnum(onum,nnum) + char const *onum; + struct buf *nnum; +/* Increment the last field of revision number onum by one and + * place the result into nnum. + */ +{ + register char *tp, *np; + register size_t l; + + l = strlen(onum); + bufalloc(nnum, l+2); + np = tp = nnum->string; + VOID strcpy(np, onum); + for (tp = np + l; np != tp; ) + if (isdigit(*--tp)) { + if (*tp != '9') { + ++*tp; + return; + } + *tp = '0'; + } else { + tp++; + break; + } + /* We changed 999 to 000; now change it to 1000. */ + *tp = '1'; + tp = np + l; + *tp++ = '0'; + *tp = 0; +} + + + + static int +removelock(delta) +struct hshentry * delta; +/* function: Finds the lock held by caller on delta, + * removes it, and returns nonzero if successful. + * Print an error message and return -1 if there is no such lock. + * An exception is if !StrictLocks, and caller is the owner of + * the RCS file. If caller does not have a lock in this case, + * return 0; return 1 if a lock is actually removed. + */ +{ + register struct rcslock *next, **trail; + char const *num; + + num=delta->num; + for (trail = &Locks; (next = *trail); trail = &next->nextlock) + if (next->delta == delta) + if (strcmp(getcaller(), next->login) == 0) { + /* We found a lock on delta by caller; delete it. */ + *trail = next->nextlock; + delta->lockedby = 0; + return 1; + } else { + rcserror("revision %s locked by %s", num, next->login); + return -1; + } + if (!StrictLocks && myself(RCSstat.st_uid)) + return 0; + rcserror("no lock set by %s for revision %s", getcaller(), num); + return -1; +} + + + + static char const * +getcurdate() +/* Return a pointer to the current date. */ +{ + static char buffer[datesize]; /* date buffer */ + + if (!buffer[0]) + time2date(now(), buffer); + return buffer; +} + + static int +#if has_prototypes +fixwork(mode_t newworkmode, time_t mtime) + /* The `#if has_prototypes' is needed because mode_t might promote to int. */ +#else + fixwork(newworkmode, mtime) + mode_t newworkmode; + time_t mtime; +#endif +{ + return + 1 < workstat.st_nlink + || (newworkmode&S_IWUSR && !myself(workstat.st_uid)) + || setmtime(workname, mtime) != 0 + ? -1 + : workstat.st_mode == newworkmode ? 0 +#if has_fchmod + : fchmod(Ifileno(workptr), newworkmode) == 0 ? 0 +#endif +#if bad_chmod_close + : -1 +#else + : chmod(workname, newworkmode) +#endif + ; +} + + static int +xpandfile(unexfile, delta, exname, dolog) + RILE *unexfile; + struct hshentry const *delta; + char const **exname; + int dolog; +/* + * Read unexfile and copy it to a + * file, performing keyword substitution with data from delta. + * Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise. + * If successful, stores the stream descriptor into *EXFILEP + * and its name into *EXNAME. + */ +{ + char const *targetname; + int e, r; + + targetname = makedirtemp(1); + if (!(exfile = fopenSafer(targetname, FOPEN_W_WORK))) { + eerror(targetname); + workerror("can't build working file"); + return -1; + } + r = 0; + if (MIN_UNEXPAND <= Expand) + fastcopy(unexfile,exfile); + else { + for (;;) { + e = expandline( + unexfile, exfile, delta, false, (FILE*)0, dolog + ); + if (e < 0) + break; + r |= e; + if (e <= 1) + break; + } + } + *exname = targetname; + return r & 1; +} + + + + +/* --------------------- G E T L O G M S G --------------------------------*/ + + + static struct cbuf +getlogmsg() +/* Obtain and yield a log message. + * If a log message is given with -m, yield that message. + * If this is the initial revision, yield a standard log message. + * Otherwise, reads a character string from the terminal. + * Stops after reading EOF or a single '.' on a + * line. getlogmsg prompts the first time it is called for the + * log message; during all later calls it asks whether the previous + * log message can be reused. + */ +{ + static char const + emptych[] = EMPTYLOG, + initialch[] = "Initial revision"; + static struct cbuf const + emptylog = { emptych, sizeof(emptych)-sizeof(char) }, + initiallog = { initialch, sizeof(initialch)-sizeof(char) }; + static struct buf logbuf; + static struct cbuf logmsg; + + register char *tp; + register size_t i; + char const *caller; + + if (msg.size) return msg; + + if (keepflag) { + /* generate std. log message */ + caller = getcaller(); + i = sizeof(ciklog)+strlen(caller)+3; + bufalloc(&logbuf, i + datesize + zonelenmax); + tp = logbuf.string; + VOID sprintf(tp, "%s%s at ", ciklog, caller); + VOID date2str(getcurdate(), tp+i); + logmsg.string = tp; + logmsg.size = strlen(tp); + return logmsg; + } + + if (!targetdelta && ( + cmpnum(newdelnum.string,"1.1")==0 || + cmpnum(newdelnum.string,"1.0")==0 + )) + return initiallog; + + if (logmsg.size) { + /*previous log available*/ + if (yesorno(true, "reuse log message of previous file? [yn](y): ")) + return logmsg; + } + + /* now read string from stdin */ + logmsg = getsstdin("m", "log message", "", &logbuf); + + /* now check whether the log message is not empty */ + if (logmsg.size) + return logmsg; + return emptylog; +} + +/* Make a linked list of Symbolic names */ + + static void +addassoclst(flag, sp) + int flag; + char const *sp; +{ + struct Symrev *pt; + + pt = talloc(struct Symrev); + pt->ssymbol = sp; + pt->override = flag; + pt->nextsym = 0; + *nextassoc = pt; + nextassoc = &pt->nextsym; +} |