summaryrefslogtreecommitdiffstats
path: root/gnu/usr.bin/rcs/ci
diff options
context:
space:
mode:
authorgjb <gjb@FreeBSD.org>2013-10-09 17:07:20 +0000
committergjb <gjb@FreeBSD.org>2013-10-09 17:07:20 +0000
commitc5342bffee8407caeac0b651549206d534dfccb9 (patch)
treeca04a5cf90c8299963a38ecf5beb3ec437b5f1b1 /gnu/usr.bin/rcs/ci
parentb33524a23e69ae8f48928d5b941a9a25a4609f30 (diff)
downloadFreeBSD-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/Makefile8
-rw-r--r--gnu/usr.bin/rcs/ci/ci.1898
-rw-r--r--gnu/usr.bin/rcs/ci/ci.c1318
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;
+}
OpenPOWER on IntegriCloud