summaryrefslogtreecommitdiffstats
path: root/gnu
diff options
context:
space:
mode:
authorjkh <jkh@FreeBSD.org>1993-06-18 04:22:21 +0000
committerjkh <jkh@FreeBSD.org>1993-06-18 04:22:21 +0000
commit7067738d6ce62410bf037507ed279733f38b3910 (patch)
tree6f106f098cccf2f7c2095fd71351c516fa2c3890 /gnu
downloadFreeBSD-src-7067738d6ce62410bf037507ed279733f38b3910.zip
FreeBSD-src-7067738d6ce62410bf037507ed279733f38b3910.tar.gz
Updated GNU utilities
Diffstat (limited to 'gnu')
-rw-r--r--gnu/usr.bin/rcs/Makefile3
-rw-r--r--gnu/usr.bin/rcs/Makefile.inc3
-rw-r--r--gnu/usr.bin/rcs/ci/Makefile7
-rw-r--r--gnu/usr.bin/rcs/ci/ci.1772
-rw-r--r--gnu/usr.bin/rcs/ci/ci.c1165
-rw-r--r--gnu/usr.bin/rcs/co/Makefile7
-rw-r--r--gnu/usr.bin/rcs/co/co.1569
-rw-r--r--gnu/usr.bin/rcs/co/co.c769
-rw-r--r--gnu/usr.bin/rcs/doc/rcs.ms1524
-rw-r--r--gnu/usr.bin/rcs/doc/rcs_func.ms95
-rw-r--r--gnu/usr.bin/rcs/ident/Makefile7
-rw-r--r--gnu/usr.bin/rcs/ident/ident.176
-rw-r--r--gnu/usr.bin/rcs/ident/ident.c214
-rw-r--r--gnu/usr.bin/rcs/lib/Makefile5
-rw-r--r--gnu/usr.bin/rcs/lib/conf.h495
-rw-r--r--gnu/usr.bin/rcs/lib/maketime.c344
-rw-r--r--gnu/usr.bin/rcs/lib/merger.c139
-rw-r--r--gnu/usr.bin/rcs/lib/partime.c639
-rw-r--r--gnu/usr.bin/rcs/lib/rcsbase.h677
-rw-r--r--gnu/usr.bin/rcs/lib/rcsedit.c1656
-rw-r--r--gnu/usr.bin/rcs/lib/rcsfcmp.c321
-rw-r--r--gnu/usr.bin/rcs/lib/rcsfnms.c1088
-rw-r--r--gnu/usr.bin/rcs/lib/rcsgen.c432
-rw-r--r--gnu/usr.bin/rcs/lib/rcskeep.c422
-rw-r--r--gnu/usr.bin/rcs/lib/rcskeys.c102
-rw-r--r--gnu/usr.bin/rcs/lib/rcslex.c1241
-rw-r--r--gnu/usr.bin/rcs/lib/rcsmap.c68
-rw-r--r--gnu/usr.bin/rcs/lib/rcsrev.c790
-rw-r--r--gnu/usr.bin/rcs/lib/rcssyn.c857
-rw-r--r--gnu/usr.bin/rcs/lib/rcsutil.c994
-rw-r--r--gnu/usr.bin/rcs/merge/Makefile7
-rw-r--r--gnu/usr.bin/rcs/merge/merge.1102
-rw-r--r--gnu/usr.bin/rcs/merge/merge.c97
-rw-r--r--gnu/usr.bin/rcs/rcs/Makefile10
-rw-r--r--gnu/usr.bin/rcs/rcs/rcs.1397
-rw-r--r--gnu/usr.bin/rcs/rcs/rcs.c1554
-rw-r--r--gnu/usr.bin/rcs/rcs/rcsfile.5224
-rw-r--r--gnu/usr.bin/rcs/rcs/rcsintro.1292
-rw-r--r--gnu/usr.bin/rcs/rcsclean/Makefile7
-rw-r--r--gnu/usr.bin/rcs/rcsclean/rcsclean.1177
-rw-r--r--gnu/usr.bin/rcs/rcsclean/rcsclean.c297
-rw-r--r--gnu/usr.bin/rcs/rcsdiff/Makefile7
-rw-r--r--gnu/usr.bin/rcs/rcsdiff/rcsdiff.1152
-rw-r--r--gnu/usr.bin/rcs/rcsdiff/rcsdiff.c422
-rw-r--r--gnu/usr.bin/rcs/rcsfreeze/Makefile7
-rw-r--r--gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.168
-rw-r--r--gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.sh100
-rw-r--r--gnu/usr.bin/rcs/rcsmerge/Makefile7
-rw-r--r--gnu/usr.bin/rcs/rcsmerge/rcsmerge.1140
-rw-r--r--gnu/usr.bin/rcs/rcsmerge/rcsmerge.c252
-rwxr-xr-xgnu/usr.bin/rcs/rcstest397
-rw-r--r--gnu/usr.bin/rcs/rlog/Makefile7
-rw-r--r--gnu/usr.bin/rcs/rlog/rlog.1260
-rw-r--r--gnu/usr.bin/rcs/rlog/rlog.c1204
54 files changed, 21668 insertions, 0 deletions
diff --git a/gnu/usr.bin/rcs/Makefile b/gnu/usr.bin/rcs/Makefile
new file mode 100644
index 0000000..2181815
--- /dev/null
+++ b/gnu/usr.bin/rcs/Makefile
@@ -0,0 +1,3 @@
+SUBDIR= lib ci co ident merge rcs rcsdiff rcsmerge rlog rcsfreeze
+
+.include <bsd.subdir.mk>
diff --git a/gnu/usr.bin/rcs/Makefile.inc b/gnu/usr.bin/rcs/Makefile.inc
new file mode 100644
index 0000000..b9eca7d
--- /dev/null
+++ b/gnu/usr.bin/rcs/Makefile.inc
@@ -0,0 +1,3 @@
+# @(#)Makefile.inc 5.1 (Berkeley) 5/11/90
+
+BINDIR?= /usr/bin
diff --git a/gnu/usr.bin/rcs/ci/Makefile b/gnu/usr.bin/rcs/ci/Makefile
new file mode 100644
index 0000000..9b64e08
--- /dev/null
+++ b/gnu/usr.bin/rcs/ci/Makefile
@@ -0,0 +1,7 @@
+PROG= ci
+
+SRCS= ci.c
+LDADD= -L${.CURDIR}/../lib/obj -lrcs
+CFLAGS+= -I${.CURDIR}/../lib
+
+.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..5736dc9
--- /dev/null
+++ b/gnu/usr.bin/rcs/ci/ci.1
@@ -0,0 +1,772 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+..
+.Id $Id: ci.1,v 5.9 1991/10/07 17:32:46 eggert Exp $
+.ds r \&\s-1RCS\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 may 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 \-k ,
+.BR \-l ,
+.BR \-M ,
+.BR \-q ,
+.BR \-r ,
+or
+.BR \-u .
+.I rev
+may be symbolic, numeric, or mixed.
+If
+.I rev
+is
+.BR $ ,
+.B ci
+determines the revision number from keyword values in the working file.
+.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
+.BR \-r [\f2rev\fP]
+checks in a revision, releases the corresponding lock, and
+removes the working file. This is the default.
+.RS
+.PP
+The
+.B \-r
+option has an unusual meaning in
+.BR ci .
+In other \*r commands,
+.B \-r
+merely specifies a revision number,
+but in
+.B ci
+it also releases a lock and removes the working file.
+See
+.B \-u
+for a tricky example.
+.RE
+.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 ,
+.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
+.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 may 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]
+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.
+.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
+may not 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
+.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 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/ file
+or
+.IB path /RCS/ file.
+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 file names,
+and is empty (i.e. just the empty suffix) for other hosts.
+.SH "FILE NAMING"
+Pairs of \*r files and working files may 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 be
+.B RCS/
+or must end in
+.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
+Several temporary files may be 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 '+1n-1/1n
+.IP \(bu \nn
+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 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
+will be able to 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 path name
+.I B
+that will 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 ,
+and
+.BR \-x .
+.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
+Revision Number: \*(Rv; Release Date: \*(Dt.
+.br
+Copyright \(co 1982, 1988, 1989 by Walter F. Tichy.
+.br
+Copyright \(co 1990, 1991 by Paul Eggert.
+.SH "SEE ALSO"
+co(1), ident(1), make(1), rcs(1), rcsclean(1), rcsdiff(1),
+rcsintro(1), rcsmerge(1), rlog(1), 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..566747e
--- /dev/null
+++ b/gnu/usr.bin/rcs/ci/ci.c
@@ -0,0 +1,1165 @@
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+/*
+ * RCS checkin operation
+ */
+/*******************************************************************
+ * check revisions into RCS files
+ *******************************************************************
+ */
+
+
+
+/* $Log: ci.c,v $
+ * 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*));
+static int addelta P((void));
+static int addsyms P((char const*));
+static int fixwork P((mode_t,char const*));
+static int removelock P((struct hshentry*));
+static int xpandfile P((RILE*,char const*,struct hshentry const*,char const**));
+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 *));
+
+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, *lastassoc;
+
+mainProg(ciId, "ci", "$Id: ci.c,v 5.21 1991/11/20 17:58:07 eggert Exp $")
+{
+ static char const cmdusage[] =
+ "\nci usage: ci -{fklqru}[rev] -mmsg -{nN}name -sstate -t[textfile] -Vn file ...";
+ static char const default_state[] = DEFAULTSTATE;
+
+ char altdate[datesize];
+ char olddate[datesize];
+ char newdatebuf[datesize], targetdatebuf[datesize];
+ char *a, **newargv, *textfile;
+ char const *author, *krev, *rev, *state;
+ char const *diffilename, *expfilename;
+ char const *workdiffname, *newworkfilename;
+ char const *mtime;
+ int lockflag, lockthis, mtimeflag, removedlock;
+ int r;
+ int changedRCS, changework, newhead;
+ int usestatdate; /* Use mod time of file for -d. */
+ mode_t newworkmode; /* mode for working file */
+ struct hshentry *workdelta;
+
+ setrid();
+
+ author = rev = state = textfile = nil;
+ lockflag = false;
+ mtimeflag = false;
+ altdate[0]= '\0'; /* empty alternate date for -d */
+ usestatdate=false;
+ suffixes = X_DEFAULT;
+
+ argc = getRCSINIT(argc, argv, &newargv);
+ argv = newargv;
+ while (a = *++argv, 0<--argc && *a++=='-') {
+ switch (*a++) {
+
+ case 'r':
+ keepworkingfile = lockflag = false;
+ revno:
+ if (*a) {
+ if (rev) warn("redefinition of revision number");
+ rev = a;
+ }
+ break;
+
+ case 'l':
+ keepworkingfile=lockflag=true;
+ goto revno;
+
+ case 'u':
+ keepworkingfile=true; lockflag=false;
+ 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)
+ warn("missing message for -m option");
+ break;
+
+ case 'n':
+ if (!*a) {
+ error("missing symbolic name after -n");
+ break;
+ }
+ checksid(a);
+ addassoclst(false, a);
+ break;
+
+ case 'N':
+ if (!*a) {
+ error("missing symbolic name after -N");
+ break;
+ }
+ checksid(a);
+ addassoclst(true, a);
+ break;
+
+ case 's':
+ if (*a) {
+ if (state) redefined('s');
+ checksid(a);
+ state = a;
+ } else
+ warn("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
+ warn("missing author for -w option");
+ break;
+
+ case 'x':
+ suffixes = a;
+ break;
+
+ case 'V':
+ setRCSversion(*argv);
+ break;
+
+
+
+ default:
+ faterror("unknown option: %s%s", *argv, cmdusage);
+ };
+ } /* end processing of options */
+
+ if (argc<1) faterror("no input file%s", cmdusage);
+
+ /* now handle all filenames */
+ do {
+ targetdelta=nil;
+ ffree();
+
+ switch (pairfilenames(argc, argv, rcswriteopen, false, false)) {
+
+ case -1: /* New RCS file */
+# if has_setuid && has_getuid
+ if (euid() != ruid()) {
+ error("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 */
+ rcsinitflag = !Head;
+ }
+
+ /* now RCSfilename contains the name of the RCS file, and
+ * workfilename contains the name of the working file.
+ * If the RCS file exists, finptr contains the file descriptor for the
+ * RCS file. The admin node is initialized.
+ * RCSstat is set.
+ */
+
+ diagnose("%s <-- %s\n", RCSfilename,workfilename);
+
+ if (!(workptr = Iopen(workfilename, FOPEN_R_WORK, &workstat))) {
+ eerror(workfilename);
+ continue;
+ }
+ if (finptr && !checkaccesslist()) continue; /* give up */
+
+ krev = rev;
+ if (keepflag) {
+ /* get keyword values from working file */
+ if (!getoldkeys(workptr)) continue;
+ if (!rev && !*(krev = prevrev.string)) {
+ error("can't find a revision number in %s",workfilename);
+ continue;
+ }
+ if (!*prevdate.string && *altdate=='\0' && usestatdate==false)
+ warn("can't find a date in %s", workfilename);
+ if (!*prevauthor.string && !author)
+ warn("can't find an author in %s", workfilename);
+ if (!*prevstate.string && !state)
+ warn("can't find a state in %s", workfilename);
+ } /* 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=nil;
+ newdelta.lockedby=nil; /*might be changed by addlock() */
+ newdelta.selector = true;
+ /* set author */
+ if (author!=nil)
+ 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!=nil)
+ 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!=nil &&
+ cmpnum(newdelta.date,targetdelta->date) < 0) {
+ error("Date %s precedes %s in existing revision %s.",
+ date2str(newdelta.date, newdatebuf),
+ date2str(targetdelta->date, targetdatebuf),
+ targetdelta->num
+ );
+ continue;
+ }
+
+
+ if (lockflag && addlock(&newdelta) < 0) continue;
+ if (!addsyms(newdelta.num))
+ continue;
+
+
+ putadmin(frewrite);
+ puttree(Head,frewrite);
+ putdesc(false,textfile);
+
+ changework = Expand != OLD_EXPAND;
+ lockthis = lockflag;
+ workdelta = &newdelta;
+
+ /* build rest of file */
+ if (rcsinitflag) {
+ diagnose("initial revision: %s\n", newdelnum.string);
+ /* get logmessage */
+ newdelta.log=getlogmsg();
+ if (!putdftext(newdelnum.string,newdelta.log,workptr,frewrite,false)) continue;
+ RCSstat.st_mode = workstat.st_mode;
+ changedRCS = true;
+ } else {
+ diffilename = maketemp(0);
+ workdiffname = workfilename;
+ if (workdiffname[0] == '+') {
+ /* Some diffs have options with leading '+'. */
+ char *dp = ftnalloc(char, strlen(workfilename)+3);
+ workdiffname = dp;
+ *dp++ = '.';
+ *dp++ = SLASH;
+ VOID strcpy(dp, workfilename);
+ }
+ newhead = Head == &newdelta;
+ if (!newhead)
+ foutptr = frewrite;
+ expfilename = buildrevision(
+ gendeltas, targetdelta, (FILE*)0, false
+ );
+ if (
+ !forceciflag &&
+ (changework = rcsfcmp(
+ workptr, &workstat, expfilename, 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;
+ }
+ if (!(changedRCS =
+ lockflag < removedlock
+ || assoclst
+ || newdelta.state != default_state
+ && strcmp(newdelta.state, targetdelta->state) != 0
+ ))
+ 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;
+ if (fseek(frewrite, 0L, SEEK_SET) != 0)
+ Oerror();
+# if !has_ftruncate
+ bad_truncate = 1;
+# else
+ /*
+ * Work around a common ftruncate() bug.
+ * We can't rely on has_truncate, because we might
+ * be using a filesystem exported to us via NFS.
+ */
+ bad_truncate = ftruncate(fileno(frewrite),(off_t)0);
+ if (bad_truncate && errno != EACCES)
+ Oerror();
+# endif
+ 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 (removedlock && removelock(workdelta)<0)
+ continue;
+ if (!addsyms(workdelta->num))
+ continue;
+ if (!dorewrite(true, true))
+ 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 {
+ diagnose("new revision: %s; previous revision: %s\n",
+ newdelnum.string, targetdelta->num
+ );
+ newdelta.log = getlogmsg();
+ switch (run((char*)0, diffilename,
+ DIFF DIFF_FLAGS,
+ newhead ? workdiffname : expfilename,
+ newhead ? expfilename : workdiffname,
+ (char*)0
+ )) {
+ case DIFF_FAILURE: case DIFF_SUCCESS: break;
+ default: faterror("diff failed");
+ }
+ if (newhead) {
+ Irewind(workptr);
+ if (!putdftext(newdelnum.string,newdelta.log,workptr,frewrite,false)) continue;
+ if (!putdtext(targetdelta->num,targetdelta->log,diffilename,frewrite,true)) continue;
+ } else
+ if (!putdtext(newdelnum.string,newdelta.log,diffilename,frewrite,true)) continue;
+ changedRCS = true;
+ }
+ }
+ if (!donerewrite(changedRCS))
+ continue;
+
+ if (!keepworkingfile) {
+ Izclose(&workptr);
+ r = un_link(workfilename); /* Get rid of old file */
+ } else {
+ newworkmode = WORKMODE(RCSstat.st_mode,
+ ! (Expand==VAL_EXPAND || lockthis < StrictLocks)
+ );
+ mtime = mtimeflag ? workdelta->date : (char const*)0;
+
+ /* 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;
+ switch (xpandfile(
+ workptr, workfilename,
+ workdelta, &newworkfilename
+ )) {
+ 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:
+ if (!(r = setfiledate(newworkfilename,mtime))) {
+ Izclose(&workptr);
+ ignoreints();
+ r = chnamemod(&exfile, newworkfilename, workfilename, newworkmode);
+ keepdirtemp(newworkfilename);
+ restoreints();
+ }
+ }
+ }
+ }
+ if (r != 0) {
+ eerror(workfilename);
+ continue;
+ }
+ diagnose("done\n");
+
+ } while (cleanup(),
+ ++argv, --argc >=1);
+
+ tempunlink();
+ exitmain(exitstatus);
+} /* end of main (ci) */
+
+ static void
+cleanup()
+{
+ if (nerror) exitstatus = EXIT_FAILURE;
+ Izclose(&finptr);
+ Izclose(&workptr);
+ Ozclose(&exfile);
+ Ozclose(&fcopy);
+ Ozclose(&frewrite);
+ dirtempunlink();
+}
+
+#if lint
+# define exiterr ciExit
+#endif
+ exiting void
+exiterr()
+{
+ 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 unsigned i;
+ int removedlock;
+ unsigned 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!=nil)) {
+ bufscpy(&newdelnum, Dbranch);
+ newdnumlength = countnumflds(Dbranch);
+ }
+ if (newdnumlength==0) bufscpy(&newdelnum, "1.1");
+ else if (newdnumlength==1) bufscat(&newdelnum, ".1");
+ else if (newdnumlength>2) {
+ error("Branch point doesn't exist for %s.",newdelnum.string);
+ return -1;
+ } /* newdnumlength == 2 is OK; */
+ Head = &newdelta;
+ newdelta.next=nil;
+ 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 = nil;
+ } else {
+ /* middle revision; start a new branch */
+ bufscpy(&newdelnum, "");
+ return addbranch(targetdelta,&newdelnum);
+ }
+ 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)) {
+ error("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) {
+ error("deltanumber %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++ != '.')
+ ;
+ *--tp = 0; /* Kill final dot to get old delta temporarily. */
+ if (!(targetdelta=genrevs(newdelnum.string,(char*)nil,(char*)nil,(char*)nil,&gendeltas)))
+ return -1;
+ if (cmpnum(targetdelta->num, newdelnum.string) != 0) {
+ error("can't find branchpoint %s", newdelnum.string);
+ return -1;
+ }
+ *tp = '.'; /* Restore final dot. */
+ return addbranch(targetdelta,&newdelnum);
+ }
+}
+
+
+
+ static int
+addbranch(branchpoint,num)
+ struct hshentry *branchpoint;
+ struct buf *num;
+/* 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.
+ */
+{
+ struct branchhead *bhead, **btrail;
+ struct buf branchnum;
+ int removedlock, result;
+ unsigned field, numlength;
+ static struct branchhead newbranch; /* new branch to be inserted */
+
+ numlength = countnumflds(num->string);
+
+ if (branchpoint->branches==nil) {
+ /* 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=nil;
+
+ } 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=nil;
+ } 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*)nil,
+ (char*)nil,(char*)nil,&gendeltas);
+ bufautoend(&branchnum);
+ if (!targetdelta)
+ return -1;
+ if (cmpnum(num->string,targetdelta->num) <= 0) {
+ error("deltanumber %s too low; must be higher than %s",
+ num->string,targetdelta->num);
+ return -1;
+ }
+ if (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=nil;
+ return 0;
+}
+
+ 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))
+ 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 lock *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 {
+ error("revision %s locked by %s",num,next->login);
+ return -1;
+ }
+ if (!StrictLocks && myself(RCSstat.st_uid))
+ return 0;
+ error("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 */
+ time_t t;
+
+ if (!buffer[0]) {
+ t = time((time_t *)0);
+ if (t == -1)
+ faterror("time not available");
+ time2date(t, buffer);
+ }
+ return buffer;
+}
+
+ static int
+#if has_prototypes
+fixwork(mode_t newworkmode, char const *mtime)
+ /* The `#if has_prototypes' is needed because mode_t might promote to int. */
+#else
+ fixwork(newworkmode, mtime)
+ mode_t newworkmode;
+ char const *mtime;
+#endif
+{
+ int r;
+ return
+ 1 < workstat.st_nlink
+ || newworkmode&S_IWUSR && !myself(workstat.st_uid)
+ ? -1
+ :
+ workstat.st_mode != newworkmode
+ &&
+ (r =
+# if has_fchmod
+ fchmod(Ifileno(workptr), newworkmode)
+# else
+ chmod(workfilename, newworkmode)
+# endif
+ ) != 0
+ ? r
+ :
+ setfiledate(workfilename, mtime);
+}
+
+ static int
+xpandfile(unexfile, dir, delta, exfilename)
+ RILE *unexfile;
+ char const *dir;
+ struct hshentry const *delta;
+ char const **exfilename;
+/*
+ * Read unexfile and copy it to a
+ * file in dir, 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 *EXFILENAME.
+ */
+{
+ char const *targetfname;
+ int e, r;
+
+ targetfname = makedirtemp(dir, 1);
+ if (!(exfile = fopen(targetfname, FOPEN_W_WORK))) {
+ eerror(targetfname);
+ error("can't expand working file");
+ return -1;
+ }
+ r = 0;
+ if (Expand == OLD_EXPAND)
+ fastcopy(unexfile,exfile);
+ else {
+ for (;;) {
+ e = expandline(unexfile,exfile,delta,false,(FILE*)nil);
+ if (e < 0)
+ break;
+ r |= e;
+ if (e <= 1)
+ break;
+ }
+ }
+ *exfilename = targetfname;
+ aflush(exfile);
+ 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);
+ 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 * sp;
+{
+ struct Symrev *pt;
+
+ pt = talloc(struct Symrev);
+ pt->ssymbol = sp;
+ pt->override = flag;
+ pt->nextsym = nil;
+ if (lastassoc)
+ lastassoc->nextsym = pt;
+ else
+ assoclst = pt;
+ lastassoc = pt;
+ return;
+}
diff --git a/gnu/usr.bin/rcs/co/Makefile b/gnu/usr.bin/rcs/co/Makefile
new file mode 100644
index 0000000..e9de8da
--- /dev/null
+++ b/gnu/usr.bin/rcs/co/Makefile
@@ -0,0 +1,7 @@
+PROG= co
+
+SRCS= co.c
+LDADD= -L${.CURDIR}/../lib/obj -lrcs
+CFLAGS+= -I${.CURDIR}/../lib
+
+.include <bsd.prog.mk>
diff --git a/gnu/usr.bin/rcs/co/co.1 b/gnu/usr.bin/rcs/co/co.1
new file mode 100644
index 0000000..d9ce65e
--- /dev/null
+++ b/gnu/usr.bin/rcs/co/co.1
@@ -0,0 +1,569 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+..
+.Id $Id: co.1,v 5.7 1991/08/19 03:13:55 eggert Exp $
+.ds g \&\s-1UTC\s0
+.ds r \&\s-1RCS\s0
+.if n .ds - \%--
+.if t .ds - \(em
+.TH CO 1 \*(Dt GNU
+.SH NAME
+co \- check out RCS revisions
+.SH SYNOPSIS
+.B co
+.RI [ options ] " file " .\|.\|.
+.SH DESCRIPTION
+.B co
+retrieves a revision from each \*r file and stores it into
+the corresponding working file.
+.PP
+Pathnames matching an \*r suffix denote \*r files;
+all others denote working files.
+Names are paired as explained in
+.BR ci (1).
+.PP
+Revisions of an \*r file may be checked out locked or unlocked. Locking a
+revision prevents overlapping updates. A revision checked out for reading or
+processing (e.g., compiling) need not be locked. A revision checked out
+for editing and later checkin must normally be locked. Checkout with locking
+fails if the revision to be checked out is currently locked by another user.
+(A lock may be broken with
+.BR rcs "(1).)\ \&"
+Checkout with locking also requires the caller to be on the access list of
+the \*r file, unless he is the owner of the
+file or the superuser, or the access list is empty.
+Checkout without locking is not subject to accesslist restrictions, and is
+not affected by the presence of locks.
+.PP
+A revision is selected by options for revision or branch number,
+checkin date/time, author, or state.
+When the selection options
+are applied in combination,
+.B co
+retrieves the latest revision
+that satisfies all of them.
+If none of the selection options
+is specified,
+.B co
+retrieves the latest revision
+on the default branch (normally the trunk, see the
+.B \-b
+option of
+.BR rcs (1)).
+A revision or branch number may be attached
+to any of the options
+.BR \-f ,
+.BR \-I ,
+.BR \-l ,
+.BR \-M ,
+.BR \-p ,
+.BR \-q ,
+.BR \-r ,
+or
+.BR \-u .
+The options
+.B \-d
+(date),
+.B \-s
+(state), and
+.B \-w
+(author)
+retrieve from a single branch, the
+.I selected
+branch,
+which is either specified by one of
+.BR \-f,
+\&.\|.\|.,
+.BR \-u ,
+or the default branch.
+.PP
+A
+.B co
+command applied to an \*r
+file with no revisions creates a zero-length working file.
+.B co
+always performs keyword substitution (see below).
+.SH OPTIONS
+.TP
+.BR \-r [\f2rev\fP]
+retrieves the latest revision whose number is less than or equal to
+.I rev.
+If
+.I rev
+indicates a branch rather than a revision,
+the latest revision on that branch is retrieved.
+If
+.I rev
+is omitted, the latest revision on the default branch
+(see the
+.B \-b
+option of
+.BR rcs (1))
+is retrieved.
+If
+.I rev
+is
+.BR $ ,
+.B co
+determines the revision number from keyword values in the working file.
+Otherwise, a revision is composed of one or more numeric or symbolic fields
+separated by periods. The numeric equivalent of a symbolic field
+is specified with the
+.B \-n
+option of the commands
+.BR ci (1)
+and
+.BR rcs (1).
+.TP
+.BR \-l [\f2rev\fP]
+same as
+.BR \-r ,
+except that it also locks the retrieved revision for
+the caller.
+.TP
+.BR \-u [\f2rev\fP]
+same as
+.BR \-r ,
+except that it unlocks the retrieved revision if it was
+locked by the caller. If
+.I rev
+is omitted,
+.B \-u
+retrieves the revision locked by the caller, if there is one; otherwise,
+it retrieves the latest revision on the default branch.
+.TP
+.BR \-f [\f2rev\fP]
+forces the overwriting of the working file;
+useful in connection with
+.BR \-q .
+See also
+.SM "FILE MODES"
+below.
+.TP
+.B \-kkv
+Generate keyword strings using the default form, e.g.\&
+.B "$\&Revision: \*(Rv $"
+for the
+.B Revision
+keyword.
+A locker's name is inserted in the value of the
+.BR Header ,
+.BR Id ,
+and
+.B Locker
+keyword strings
+only as a file is being locked,
+i.e. by
+.B "ci\ \-l"
+and
+.BR "co\ \-l".
+This is the default.
+.TP
+.B \-kkvl
+Like
+.BR \-kkv ,
+except that a locker's name is always inserted
+if the given revision is currently locked.
+.TP
+.BR \-kk
+Generate only keyword names in keyword strings; omit their values.
+See
+.SM "KEYWORD SUBSTITUTION"
+below.
+For example, for the
+.B Revision
+keyword, generate the string
+.B $\&Revision$
+instead of
+.BR "$\&Revision: \*(Rv $".
+This option is useful to ignore differences due to keyword substitution
+when comparing different revisions of a file.
+.TP
+.BR \-ko
+Generate the old keyword string,
+present in the working file just before it was checked in.
+For example, for the
+.B Revision
+keyword, generate the string
+.B "$\&Revision: 1.1 $"
+instead of
+.B "$\&Revision: \*(Rv $"
+if that is how the string appeared when the file was checked in.
+This can be useful for binary file formats
+that cannot tolerate any changes to substrings
+that happen to take the form of keyword strings.
+.TP
+.BR \-kv
+Generate only keyword values for keyword strings.
+For example, for the
+.B Revision
+keyword, generate the string
+.B \*(Rv
+instead of
+.BR "$\&Revision: \*(Rv $".
+This can help generate files in programming languages where it is hard to
+strip keyword delimiters like
+.B "$\&Revision:\ $"
+from a string.
+However, further keyword substitution cannot be performed once the
+keyword names are removed, so this option should be used with care.
+Because of this danger of losing keywords,
+this option cannot be combined with
+.BR \-l ,
+and the owner write permission of the working file is turned off;
+to edit the file later, check it out again without
+.BR \-kv .
+.TP
+.BR \-p [\f2rev\fP]
+prints the retrieved revision on the standard output rather than storing it
+in the working file.
+This option is useful when
+.B co
+is part of a pipe.
+.TP
+.BR \-q [\f2rev\fP]
+quiet mode; diagnostics are not printed.
+.TP
+.BR \-I [\f2rev\fP]
+interactive mode;
+the user is prompted and questioned
+even if the standard input is not a terminal.
+.TP
+.BI \-d date
+retrieves the latest revision on the selected branch whose checkin date/time is
+less than or equal to
+.I date.
+The date and time may be given in free format.
+The time zone
+.B LT
+stands for local time;
+other common time zone names are understood.
+For example, the following
+.IR date s
+are equivalent
+if local time is January 11, 1990, 8pm Pacific Standard Time,
+eight hours west of Coordinated Universal Time (\*g):
+.RS
+.LP
+.RS
+.nf
+.ta \w'\f3Thu, 11 Jan 1990 20:00:00 \-0800\fP 'u
+.ne 9
+\f38:00 pm lt\fP
+\f34:00 AM, Jan. 12, 1990\fP note: default is \*g
+\f31990/01/12 04:00:00\fP \*r date format
+\f3Thu Jan 11 20:00:00 1990 LT\fP output of \f3ctime\fP(3) + \f3LT\fP
+\f3Thu Jan 11 20:00:00 PST 1990\fP output of \f3date\fP(1)
+\f3Fri Jan 12 04:00:00 GMT 1990\fP
+\f3Thu, 11 Jan 1990 20:00:00 \-0800\fP
+\f3Fri-JST, 1990, 1pm Jan 12\fP
+\f312-January-1990, 04:00-WET\fP
+.ta 4n +4n +4n +4n
+.fi
+.RE
+.LP
+Most fields in the date and time may be defaulted.
+The default time zone is \*g.
+The other defaults are determined in the order year, month, day,
+hour, minute, and second (most to least significant). At least one of these
+fields must be provided. For omitted fields that are of higher significance
+than the highest provided field, the time zone's current values are assumed.
+For all other omitted fields,
+the lowest possible values are assumed.
+For example, the date
+.B "20, 10:30"
+defaults to
+10:30:00 \*g of the 20th of the \*g time zone's current month and year.
+The date/time must be quoted if it contains spaces.
+.RE
+.TP
+.BR \-M [\f2rev\fP]
+Set the modification time on the new working file
+to be the date of the retrieved revision.
+Use this option with care; it can confuse
+.BR make (1).
+.TP
+.BI \-s state
+retrieves the latest revision on the selected branch whose state is set to
+.I state.
+.TP
+.BR \-w [\f2login\fP]
+retrieves the latest revision on the selected branch which was checked in
+by the user with login name
+.I login.
+If the argument
+.I login
+is
+omitted, the caller's login is assumed.
+.TP
+.BI \-j joinlist
+generates a new revision which is the join of the revisions on
+.I joinlist.
+This option is largely obsoleted by
+.BR rcsmerge (1)
+but is retained for backwards compatibility.
+.RS
+.PP
+The
+.I joinlist
+is a comma-separated list of pairs of the form
+.IB rev2 : rev3,
+where
+.I rev2
+and
+.I rev3
+are (symbolic or numeric)
+revision numbers.
+For the initial such pair,
+.I rev1
+denotes the revision selected
+by the above options
+.BR \-f,
+\&.\|.\|.,
+.BR \-w .
+For all other pairs,
+.I rev1
+denotes the revision generated by the previous pair.
+(Thus, the output
+of one join becomes the input to the next.)
+.PP
+For each pair,
+.B co
+joins revisions
+.I rev1
+and
+.I rev3
+with respect to
+.I rev2.
+This means that all changes that transform
+.I rev2
+into
+.I rev1
+are applied to a copy of
+.I rev3.
+This is particularly useful if
+.I rev1
+and
+.I rev3
+are the ends of two branches that have
+.I rev2
+as a common ancestor. If
+.IR rev1 < rev2 < rev3
+on the same branch,
+joining generates a new revision which is like
+.I rev3,
+but with all changes that lead from
+.I rev1
+to
+.I rev2
+undone.
+If changes from
+.I rev2
+to
+.I rev1
+overlap with changes from
+.I rev2
+to
+.I rev3,
+.B co
+reports overlaps as described in
+.BR merge (1).
+.PP
+For the initial pair,
+.I rev2
+may be omitted. The default is the common
+ancestor.
+If any of the arguments indicate branches, the latest revisions
+on those branches are assumed.
+The options
+.B \-l
+and
+.B \-u
+lock or unlock
+.I rev1.
+.RE
+.TP
+.BI \-V n
+Emulate \*r version
+.I n,
+where
+.I n
+may be
+.BR 3 ,
+.BR 4 ,
+or
+.BR 5 .
+This may be useful when interchanging \*r files with others who are
+running older versions of \*r.
+To see which version of \*r your correspondents are running, have them invoke
+.B rlog
+on an \*r file;
+if none of the first few lines of output contain the string
+.B branch:
+it is version 3;
+if the dates' years have just two digits, it is version 4;
+otherwise, it is version 5.
+An \*r file generated while emulating version 3 will lose its default branch.
+An \*r revision generated while emulating version 4 or earlier will have
+a timestamp that is off by up to 13 hours.
+A revision extracted while emulating version 4 or earlier will contain
+dates of the form
+.IB yy / mm / dd
+instead of
+.IB yyyy / mm / dd
+and may also contain different white space in the substitution for
+.BR $\&Log$ .
+.TP
+.BI \-x "suffixes"
+Use
+.I suffixes
+to characterize \*r files.
+See
+.BR ci (1)
+for details.
+.SH "KEYWORD SUBSTITUTION"
+Strings of the form
+.BI $ keyword $
+and
+.BI $ keyword : .\|.\|. $
+embedded in
+the text are replaced
+with strings of the form
+.BI $ keyword : value $
+where
+.I keyword
+and
+.I value
+are pairs listed below.
+Keywords may be embedded in literal strings
+or comments to identify a revision.
+.PP
+Initially, the user enters strings of the form
+.BI $ keyword $ .
+On checkout,
+.B co
+replaces these strings with strings of the form
+.BI $ keyword : value $ .
+If a revision containing strings of the latter form
+is checked back in, the value fields will be replaced during the next
+checkout.
+Thus, the keyword values are automatically updated on checkout.
+This automatic substitution can be modified by the
+.B \-k
+options.
+.PP
+Keywords and their corresponding values:
+.TP
+.B $\&Author$
+The login name of the user who checked in the revision.
+.TP
+.B $\&Date$
+The date and time (\*g) the revision was checked in.
+.TP
+.B $\&Header$
+A standard header containing the full pathname of the \*r file, the
+revision number, the date (\*g), the author, the state,
+and the locker (if locked).
+.TP
+.B $\&Id$
+Same as
+.BR $\&Header$ ,
+except that the \*r filename is without a path.
+.TP
+.B $\&Locker$
+The login name of the user who locked the revision (empty if not locked).
+.TP
+.B $\&Log$
+The log message supplied during checkin, preceded by a header
+containing the \*r filename, the revision number, the author, and the date
+(\*g).
+Existing log messages are
+.I not
+replaced.
+Instead, the new log message is inserted after
+.BR $\&Log: .\|.\|. $ .
+This is useful for
+accumulating a complete change log in a source file.
+.TP
+.B $\&RCSfile$
+The name of the \*r file without a path.
+.TP
+.B $\&Revision$
+The revision number assigned to the revision.
+.TP
+.B $\&Source$
+The full pathname of the \*r file.
+.TP
+.B $\&State$
+The state assigned to the revision with the
+.B \-s
+option of
+.BR rcs (1)
+or
+.BR ci (1).
+.SH "FILE MODES"
+The working file inherits the read and execute permissions from the \*r
+file. In addition, the owner write permission is turned on, unless
+.B \-kv
+is set or the file
+is checked out unlocked and locking is set to strict (see
+.BR rcs (1)).
+.PP
+If a file with the name of the working file exists already and has write
+permission,
+.B co
+aborts the checkout,
+asking beforehand if possible.
+If the existing working file is
+not writable or
+.B \-f
+is given, the working file is deleted without asking.
+.SH FILES
+.B co
+accesses files much as
+.BR ci (1)
+does, except that it does not need to read the working file.
+.SH ENVIRONMENT
+.TP
+.B \s-1RCSINIT\s0
+options prepended to the argument list, separated by spaces.
+See
+.BR ci (1)
+for details.
+.SH DIAGNOSTICS
+The \*r pathname, the working pathname,
+and the revision number retrieved are
+written to the diagnostic output.
+The exit status is zero if and only if all operations were successful.
+.SH IDENTIFICATION
+Author: Walter F. Tichy.
+.br
+Revision Number: \*(Rv; Release Date: \*(Dt.
+.br
+Copyright \(co 1982, 1988, 1989 by Walter F. Tichy.
+.br
+Copyright \(co 1990, 1991 by Paul Eggert.
+.SH "SEE ALSO"
+ci(1), ctime(3), date(1), ident(1), make(1),
+rcs(1), rcsdiff(1), rcsintro(1), rcsmerge(1), rlog(1),
+rcsfile(5)
+.br
+Walter F. Tichy,
+\*r\*-A System for Version Control,
+.I "Software\*-Practice & Experience"
+.BR 15 ,
+7 (July 1985), 637-654.
+.SH LIMITS
+Links to the \*r and working files are not preserved.
+.PP
+There is no way to selectively suppress the expansion of keywords, except
+by writing them differently. In nroff and troff, this is done by embedding the
+null-character
+.B \e&
+into the keyword.
+.SH BUGS
+The
+.B \-d
+option sometimes gets confused, and accepts no date before 1970.
+.br
diff --git a/gnu/usr.bin/rcs/co/co.c b/gnu/usr.bin/rcs/co/co.c
new file mode 100644
index 0000000..9435574
--- /dev/null
+++ b/gnu/usr.bin/rcs/co/co.c
@@ -0,0 +1,769 @@
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+/*
+ * RCS checkout operation
+ */
+/*****************************************************************************
+ * check out revisions from RCS files
+ *****************************************************************************
+ */
+
+
+/* $Log: co.c,v $
+ * Revision 5.9 1991/10/07 17:32:46 eggert
+ * ci -u src/RCS/co.c,v src/co.c <<\.
+ * -k affects just working file, not RCS file.
+ *
+ * Revision 5.8 1991/08/19 03:13:55 eggert
+ * Warn before removing somebody else's file.
+ * Add -M. Fix co -j bugs. Tune.
+ *
+ * Revision 5.7 1991/04/21 11:58:15 eggert
+ * Ensure that working file is newer than RCS file after co -[lu].
+ * Add -x, RCSINIT, MS-DOS support.
+ *
+ * Revision 5.6 1990/12/04 05:18:38 eggert
+ * Don't checkaccesslist() unless necessary.
+ * Use -I for prompts and -q for diagnostics.
+ *
+ * Revision 5.5 1990/11/01 05:03:26 eggert
+ * Fix -j. Add -I.
+ *
+ * Revision 5.4 1990/10/04 06:30:11 eggert
+ * Accumulate exit status across files.
+ *
+ * Revision 5.3 1990/09/11 02:41:09 eggert
+ * co -kv yields a readonly working file.
+ *
+ * Revision 5.2 1990/09/04 08:02:13 eggert
+ * Standardize yes-or-no procedure.
+ *
+ * Revision 5.0 1990/08/22 08:10:02 eggert
+ * Permit multiple locks by same user. Add setuid support.
+ * Remove compile-time limits; use malloc instead.
+ * Permit dates past 1999/12/31. Switch to GMT.
+ * Make lock and temp files faster and safer.
+ * Ansify and Posixate. Add -k, -V. Remove snooping. Tune.
+ *
+ * Revision 4.7 89/05/01 15:11:41 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.6 88/08/09 19:12:15 eggert
+ * Fix "co -d" core dump; rawdate wasn't always initialized.
+ * Use execv(), not system(); fix putchar('\0') and diagnose() botches; remove lint
+ *
+ * Revision 4.5 87/12/18 11:35:40 narten
+ * lint cleanups (from Guy Harris)
+ *
+ * Revision 4.4 87/10/18 10:20:53 narten
+ * Updating version numbers changes relative to 1.1, are actually
+ * relative to 4.2
+ *
+ * Revision 1.3 87/09/24 13:58:30 narten
+ * Sources now pass through lint (if you ignore printf/sprintf/fprintf
+ * warnings)
+ *
+ * Revision 1.2 87/03/27 14:21:38 jenkins
+ * Port to suns
+ *
+ * Revision 4.2 83/12/05 13:39:48 wft
+ * made rewriteflag external.
+ *
+ * Revision 4.1 83/05/10 16:52:55 wft
+ * Added option -u and -f.
+ * Added handling of default branch.
+ * Replaced getpwuid() with getcaller().
+ * Removed calls to stat(); now done by pairfilenames().
+ * Changed and renamed rmoldfile() to rmworkfile().
+ * Replaced catchints() calls with restoreints(), unlink()--link() with rename();
+ *
+ * Revision 3.7 83/02/15 15:27:07 wft
+ * Added call to fastcopy() to copy remainder of RCS file.
+ *
+ * Revision 3.6 83/01/15 14:37:50 wft
+ * Added ignoring of interrupts while RCS file is renamed; this avoids
+ * deletion of RCS files during the unlink/link window.
+ *
+ * Revision 3.5 82/12/08 21:40:11 wft
+ * changed processing of -d to use DATEFORM; removed actual from
+ * call to preparejoin; re-fixed printing of done at the end.
+ *
+ * Revision 3.4 82/12/04 18:40:00 wft
+ * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE.
+ * Fixed printing of "done".
+ *
+ * Revision 3.3 82/11/28 22:23:11 wft
+ * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
+ * %02d with %.2d, mode generation for working file with WORKMODE.
+ * Fixed nil printing. Fixed -j combined with -l and -p, and exit
+ * for non-existing revisions in preparejoin().
+ *
+ * Revision 3.2 82/10/18 20:47:21 wft
+ * Mode of working file is now maintained even for co -l, but write permission
+ * is removed.
+ * The working file inherits its mode from the RCS file, plus write permission
+ * for the owner. The write permission is not given if locking is strict and
+ * co does not lock.
+ * An existing working file without write permission is deleted automatically.
+ * Otherwise, co asks (empty answer: abort co).
+ * Call to getfullRCSname() added, check for write error added, call
+ * for getlogin() fixed.
+ *
+ * Revision 3.1 82/10/13 16:01:30 wft
+ * fixed type of variables receiving from getc() (char -> int).
+ * removed unused variables.
+ */
+
+
+
+
+#include "rcsbase.h"
+
+static char const *getancestor P((char const*,char const*));
+static int buildjoin P((char const*));
+static int preparejoin P((void));
+static int rmlock P((struct hshentry const*));
+static int rmworkfile P((void));
+static void cleanup P((void));
+
+static char const quietarg[] = "-q";
+
+static char const *expandarg, *join, *suffixarg, *versionarg;
+static char const *joinlist[joinlength]; /* revisions to be joined */
+static FILE *neworkptr;
+static int exitstatus;
+static int forceflag;
+static int lastjoin; /* index of last element in joinlist */
+static int lockflag; /* -1 -> unlock, 0 -> do nothing, 1 -> lock */
+static int mtimeflag;
+static struct hshentries *gendeltas; /* deltas to be generated */
+static struct hshentry *targetdelta; /* final delta to be generated */
+static struct stat workstat;
+
+mainProg(coId, "co", "$Id: co.c,v 5.9 1991/10/07 17:32:46 eggert Exp $")
+{
+ static char const cmdusage[] =
+ "\nco usage: co -{flpqru}[rev] -ddate -jjoinlist -sstate -w[login] -Vn file ...";
+
+ char *a, **newargv;
+ char const *author, *date, *rev, *state;
+ char const *joinfilename, *newdate, *neworkfilename;
+ int changelock; /* 1 if a lock has been changed, -1 if error */
+ int expmode, r, tostdout, workstatstat;
+ struct buf numericrev; /* expanded revision number */
+ char finaldate[datesize];
+
+ setrid();
+ author = date = rev = state = nil;
+ bufautobegin(&numericrev);
+ expmode = -1;
+ suffixes = X_DEFAULT;
+ tostdout = false;
+
+ argc = getRCSINIT(argc, argv, &newargv);
+ argv = newargv;
+ while (a = *++argv, 0<--argc && *a++=='-') {
+ switch (*a++) {
+
+ case 'r':
+ revno:
+ if (*a) {
+ if (rev) warn("redefinition of revision number");
+ rev = a;
+ }
+ break;
+
+ case 'f':
+ forceflag=true;
+ goto revno;
+
+ case 'l':
+ if (lockflag < 0) {
+ warn("-l overrides -u.");
+ }
+ lockflag = 1;
+ goto revno;
+
+ case 'u':
+ if (0 < lockflag) {
+ warn("-l overrides -u.");
+ }
+ lockflag = -1;
+ goto revno;
+
+ case 'p':
+ tostdout = true;
+ goto revno;
+
+ case 'I':
+ interactiveflag = true;
+ goto revno;
+
+ case 'q':
+ quietflag=true;
+ goto revno;
+
+ case 'd':
+ if (date)
+ redefined('d');
+ str2date(a, finaldate);
+ date=finaldate;
+ break;
+
+ case 'j':
+ if (*a) {
+ if (join) redefined('j');
+ join = a;
+ }
+ break;
+
+ case 'M':
+ mtimeflag = true;
+ goto revno;
+
+ case 's':
+ if (*a) {
+ if (state) redefined('s');
+ state = a;
+ }
+ break;
+
+ case 'w':
+ if (author) redefined('w');
+ if (*a)
+ author = a;
+ else
+ author = getcaller();
+ break;
+
+ case 'x':
+ suffixarg = *argv;
+ suffixes = a;
+ break;
+
+ case 'V':
+ versionarg = *argv;
+ setRCSversion(versionarg);
+ break;
+
+ case 'k': /* set keyword expand mode */
+ expandarg = *argv;
+ if (0 <= expmode) redefined('k');
+ if (0 <= (expmode = str2expmode(a)))
+ break;
+ /* fall into */
+ default:
+ faterror("unknown option: %s%s", *argv, cmdusage);
+
+ };
+ } /* end of option processing */
+
+ if (argc<1) faterror("no input file%s", cmdusage);
+ if (tostdout)
+# if text_equals_binary_stdio || text_work_stdio
+ workstdout = stdout;
+# else
+ if (!(workstdout = fdopen(STDOUT_FILENO, FOPEN_W_WORK)))
+ efaterror("stdout");
+# endif
+
+ /* now handle all filenames */
+ do {
+ ffree();
+
+ if (pairfilenames(argc, argv, lockflag?rcswriteopen:rcsreadopen, true, false) <= 0)
+ continue;
+
+ /* now RCSfilename contains the name of the RCS file, and finptr
+ * points at it. workfilename contains the name of the working file.
+ * Also, RCSstat has been set.
+ */
+ diagnose("%s --> %s\n", RCSfilename,tostdout?"stdout":workfilename);
+
+ workstatstat = -1;
+ if (tostdout) {
+ neworkfilename = 0;
+ neworkptr = workstdout;
+ } else {
+ workstatstat = stat(workfilename, &workstat);
+ neworkfilename = makedirtemp(workfilename, 1);
+ if (!(neworkptr = fopen(neworkfilename, FOPEN_W_WORK))) {
+ if (errno == EACCES)
+ error("%s: parent directory isn't writable",
+ workfilename
+ );
+ else
+ eerror(neworkfilename);
+ continue;
+ }
+ }
+
+ gettree(); /* reads in the delta tree */
+
+ if (Head==nil) {
+ /* no revisions; create empty file */
+ diagnose("no revisions present; generating empty revision 0.0\n");
+ Ozclose(&fcopy);
+ if (workstatstat == 0)
+ if (!rmworkfile()) continue;
+ changelock = 0;
+ newdate = 0;
+ /* Can't reserve a delta, so don't call addlock */
+ } else {
+ if (rev!=nil) {
+ /* expand symbolic revision number */
+ if (!expandsym(rev, &numericrev))
+ continue;
+ } else
+ switch (lockflag<0 ? findlock(false,&targetdelta) : 0) {
+ default:
+ continue;
+ case 0:
+ bufscpy(&numericrev, Dbranch?Dbranch:"");
+ break;
+ case 1:
+ bufscpy(&numericrev, targetdelta->num);
+ break;
+ }
+ /* get numbers of deltas to be generated */
+ if (!(targetdelta=genrevs(numericrev.string,date,author,state,&gendeltas)))
+ continue;
+ /* check reservations */
+ changelock =
+ lockflag < 0 ?
+ rmlock(targetdelta)
+ : lockflag == 0 ?
+ 0
+ :
+ addlock(targetdelta);
+
+ if (
+ changelock < 0 ||
+ changelock && !checkaccesslist() ||
+ !dorewrite(lockflag, changelock)
+ )
+ continue;
+
+ if (0 <= expmode)
+ Expand = expmode;
+ if (0 < lockflag && Expand == VAL_EXPAND) {
+ error("cannot combine -kv and -l");
+ continue;
+ }
+
+ if (join && !preparejoin()) continue;
+
+ diagnose("revision %s%s\n",targetdelta->num,
+ 0<lockflag ? " (locked)" :
+ lockflag<0 ? " (unlocked)" : "");
+
+ /* Prepare to remove old working file if necessary. */
+ if (workstatstat == 0)
+ if (!rmworkfile()) continue;
+
+ /* skip description */
+ getdesc(false); /* don't echo*/
+
+ locker_expansion = 0 < lockflag;
+ joinfilename = buildrevision(
+ gendeltas, targetdelta,
+ join&&tostdout ? (FILE*)0 : neworkptr,
+ Expand!=OLD_EXPAND
+ );
+# if !large_memory
+ if (fcopy == neworkptr)
+ fcopy = 0; /* Don't close it twice. */
+# endif
+ if_advise_access(changelock && gendeltas->first!=targetdelta,
+ finptr, MADV_SEQUENTIAL
+ );
+
+ if (!donerewrite(changelock))
+ continue;
+
+ newdate = targetdelta->date;
+ if (join) {
+ newdate = 0;
+ if (!joinfilename) {
+ aflush(neworkptr);
+ joinfilename = neworkfilename;
+ }
+ if (!buildjoin(joinfilename))
+ continue;
+ }
+ }
+ if (!tostdout) {
+ r = 0;
+ if (mtimeflag && newdate) {
+ if (!join)
+ aflush(neworkptr);
+ r = setfiledate(neworkfilename, newdate);
+ }
+ if (r == 0) {
+ ignoreints();
+ r = chnamemod(&neworkptr, neworkfilename, workfilename,
+ WORKMODE(RCSstat.st_mode,
+ !(Expand==VAL_EXPAND || lockflag<=0&&StrictLocks)
+ )
+ );
+ keepdirtemp(neworkfilename);
+ restoreints();
+ }
+ if (r != 0) {
+ eerror(workfilename);
+ error("see %s", neworkfilename);
+ continue;
+ }
+ diagnose("done\n");
+ }
+ } while (cleanup(),
+ ++argv, --argc >=1);
+
+ tempunlink();
+ Ofclose(workstdout);
+ exitmain(exitstatus);
+
+} /* end of main (co) */
+
+ static void
+cleanup()
+{
+ if (nerror) exitstatus = EXIT_FAILURE;
+ Izclose(&finptr);
+ Ozclose(&frewrite);
+# if !large_memory
+ if (fcopy!=workstdout) Ozclose(&fcopy);
+# endif
+ if (neworkptr!=workstdout) Ozclose(&neworkptr);
+ dirtempunlink();
+}
+
+#if lint
+# define exiterr coExit
+#endif
+ exiting void
+exiterr()
+{
+ dirtempunlink();
+ tempunlink();
+ _exit(EXIT_FAILURE);
+}
+
+
+/*****************************************************************
+ * The following routines are auxiliary routines
+ *****************************************************************/
+
+ static int
+rmworkfile()
+/* Function: prepares to remove workfilename, if it exists, and if
+ * it is read-only.
+ * Otherwise (file writable):
+ * if !quietmode asks the user whether to really delete it (default: fail);
+ * otherwise failure.
+ * Returns true if permission is gotten.
+ */
+{
+ if (workstat.st_mode&(S_IWUSR|S_IWGRP|S_IWOTH) && !forceflag) {
+ /* File is writable */
+ if (!yesorno(false, "writable %s exists%s; remove it? [ny](n): ",
+ workfilename,
+ myself(workstat.st_uid) ? "" : ", and you do not own it"
+ )) {
+ error(!quietflag && ttystdin()
+ ? "checkout aborted"
+ : "writable %s exists; checkout aborted", workfilename);
+ return false;
+ }
+ }
+ /* Actual unlink is done later by caller. */
+ return true;
+}
+
+
+ static int
+rmlock(delta)
+ struct hshentry const *delta;
+/* Function: removes the lock held by caller on delta.
+ * Returns -1 if someone else holds the lock,
+ * 0 if there is no lock on delta,
+ * and 1 if a lock was found and removed.
+ */
+{ register struct lock * next, * trail;
+ char const *num;
+ struct lock dummy;
+ int whomatch, nummatch;
+
+ num=delta->num;
+ dummy.nextlock=next=Locks;
+ trail = &dummy;
+ while (next!=nil) {
+ whomatch = strcmp(getcaller(), next->login);
+ nummatch=strcmp(num,next->delta->num);
+ if ((whomatch==0) && (nummatch==0)) break;
+ /*found a lock on delta by caller*/
+ if ((whomatch!=0)&&(nummatch==0)) {
+ error("revision %s locked by %s; use co -r or rcs -u",num,next->login);
+ return -1;
+ }
+ trail=next;
+ next=next->nextlock;
+ }
+ if (next!=nil) {
+ /*found one; delete it */
+ trail->nextlock=next->nextlock;
+ Locks=dummy.nextlock;
+ next->delta->lockedby=nil; /* reset locked-by */
+ return 1; /*success*/
+ } else return 0; /*no lock on delta*/
+}
+
+
+
+
+/*****************************************************************
+ * The rest of the routines are for handling joins
+ *****************************************************************/
+
+
+ static char const *
+addjoin(joinrev)
+ char *joinrev;
+/* Add joinrev's number to joinlist, yielding address of char past joinrev,
+ * or nil if no such revision exists.
+ */
+{
+ register char *j;
+ register struct hshentry const *d;
+ char terminator;
+ struct buf numrev;
+ struct hshentries *joindeltas;
+
+ j = joinrev;
+ for (;;) {
+ switch (*j++) {
+ default:
+ continue;
+ case 0:
+ case ' ': case '\t': case '\n':
+ case ':': case ',': case ';':
+ break;
+ }
+ break;
+ }
+ terminator = *--j;
+ *j = 0;
+ bufautobegin(&numrev);
+ d = 0;
+ if (expandsym(joinrev, &numrev))
+ d = genrevs(numrev.string,(char*)nil,(char*)nil,(char*)nil,&joindeltas);
+ bufautoend(&numrev);
+ *j = terminator;
+ if (d) {
+ joinlist[++lastjoin] = d->num;
+ return j;
+ }
+ return nil;
+}
+
+ static int
+preparejoin()
+/* Function: Parses a join list pointed to by join and places pointers to the
+ * revision numbers into joinlist.
+ */
+{
+ register char const *j;
+
+ j=join;
+ lastjoin= -1;
+ for (;;) {
+ while ((*j==' ')||(*j=='\t')||(*j==',')) j++;
+ if (*j=='\0') break;
+ if (lastjoin>=joinlength-2) {
+ error("too many joins");
+ return(false);
+ }
+ if (!(j = addjoin(j))) return false;
+ while ((*j==' ') || (*j=='\t')) j++;
+ if (*j == ':') {
+ j++;
+ while((*j==' ') || (*j=='\t')) j++;
+ if (*j!='\0') {
+ if (!(j = addjoin(j))) return false;
+ } else {
+ error("join pair incomplete");
+ return false;
+ }
+ } else {
+ if (lastjoin==0) { /* first pair */
+ /* common ancestor missing */
+ joinlist[1]=joinlist[0];
+ lastjoin=1;
+ /*derive common ancestor*/
+ if (!(joinlist[0] = getancestor(targetdelta->num,joinlist[1])))
+ return false;
+ } else {
+ error("join pair incomplete");
+ return false;
+ }
+ }
+ }
+ if (lastjoin<1) {
+ error("empty join");
+ return false;
+ } else return true;
+}
+
+
+
+ static char const *
+getancestor(r1, r2)
+ char const *r1, *r2;
+/* Yield the common ancestor of r1 and r2 if successful, nil otherwise.
+ * Work reliably only if r1 and r2 are not branch numbers.
+ */
+{
+ static struct buf t1, t2;
+
+ unsigned l1, l2, l3;
+ char const *r;
+
+ l1 = countnumflds(r1);
+ l2 = countnumflds(r2);
+ if ((2<l1 || 2<l2) && cmpnum(r1,r2)!=0) {
+ /* not on main trunk or identical */
+ l3 = 0;
+ while (cmpnumfld(r1, r2, l3+1)==0 && cmpnumfld(r1, r2, l3+2)==0)
+ l3 += 2;
+ /* This will terminate since r1 and r2 are not the same; see above. */
+ if (l3==0) {
+ /* no common prefix; common ancestor on main trunk */
+ VOID partialno(&t1, r1, l1>2 ? (unsigned)2 : l1);
+ VOID partialno(&t2, r2, l2>2 ? (unsigned)2 : l2);
+ r = cmpnum(t1.string,t2.string)<0 ? t1.string : t2.string;
+ if (cmpnum(r,r1)!=0 && cmpnum(r,r2)!=0)
+ return r;
+ } else if (cmpnumfld(r1, r2, l3+1)!=0)
+ return partialno(&t1,r1,l3);
+ }
+ error("common ancestor of %s and %s undefined", r1, r2);
+ return nil;
+}
+
+
+
+ static int
+buildjoin(initialfile)
+ char const *initialfile;
+/* Function: merge pairs of elements in joinlist into initialfile
+ * If workstdout is set, copy result to stdout.
+ * All unlinking of initialfile, rev2, and rev3 should be done by tempunlink().
+ */
+{
+ struct buf commarg;
+ struct buf subs;
+ char const *rev2, *rev3;
+ int i;
+ char const *cov[10], *mergev[12];
+ char const **p;
+
+ bufautobegin(&commarg);
+ bufautobegin(&subs);
+ rev2 = maketemp(0);
+ rev3 = maketemp(3); /* buildrevision() may use 1 and 2 */
+
+ cov[0] = nil;
+ /* cov[1] setup below */
+ cov[2] = CO;
+ /* cov[3] setup below */
+ p = &cov[4];
+ if (expandarg) *p++ = expandarg;
+ if (suffixarg) *p++ = suffixarg;
+ if (versionarg) *p++ = versionarg;
+ *p++ = quietarg;
+ *p++ = RCSfilename;
+ *p = nil;
+
+ mergev[0] = nil;
+ mergev[1] = nil;
+ mergev[2] = MERGE;
+ mergev[3] = mergev[5] = "-L";
+ /* rest of mergev setup below */
+
+ i=0;
+ while (i<lastjoin) {
+ /*prepare marker for merge*/
+ if (i==0)
+ bufscpy(&subs, targetdelta->num);
+ else {
+ bufscat(&subs, ",");
+ bufscat(&subs, joinlist[i-2]);
+ bufscat(&subs, ":");
+ bufscat(&subs, joinlist[i-1]);
+ }
+ diagnose("revision %s\n",joinlist[i]);
+ bufscpy(&commarg, "-p");
+ bufscat(&commarg, joinlist[i]);
+ cov[1] = rev2;
+ cov[3] = commarg.string;
+ if (runv(cov))
+ goto badmerge;
+ diagnose("revision %s\n",joinlist[i+1]);
+ bufscpy(&commarg, "-p");
+ bufscat(&commarg, joinlist[i+1]);
+ cov[1] = rev3;
+ cov[3] = commarg.string;
+ if (runv(cov))
+ goto badmerge;
+ diagnose("merging...\n");
+ mergev[4] = subs.string;
+ mergev[6] = joinlist[i+1];
+ p = &mergev[7];
+ if (quietflag) *p++ = quietarg;
+ if (lastjoin<=i+2 && workstdout) *p++ = "-p";
+ *p++ = initialfile;
+ *p++ = rev2;
+ *p++ = rev3;
+ *p = nil;
+ switch (runv(mergev)) {
+ case DIFF_FAILURE: case DIFF_SUCCESS:
+ break;
+ default:
+ goto badmerge;
+ }
+ i=i+2;
+ }
+ bufautoend(&commarg);
+ bufautoend(&subs);
+ return true;
+
+ badmerge:
+ nerror++;
+ bufautoend(&commarg);
+ bufautoend(&subs);
+ return false;
+}
diff --git a/gnu/usr.bin/rcs/doc/rcs.ms b/gnu/usr.bin/rcs/doc/rcs.ms
new file mode 100644
index 0000000..7b3f807
--- /dev/null
+++ b/gnu/usr.bin/rcs/doc/rcs.ms
@@ -0,0 +1,1524 @@
+.\" Format this file with:
+.\" pic file | tbl | troff -ms
+.\"
+.\" \*s stands for $, and avoids problems when this file is checked in.
+.ds s $
+.\" PS and PE center pic diagrams. (The corresponding ms-macros may not.)
+.de PS
+.nr pE (\\n(.lu-\\$2u)/2u
+.in +\\n(pEu
+.ne \\$1u
+..
+.de PE
+.in -\\n(pEu
+..
+.de D(
+.DS
+.nr VS 12p
+.vs 12p
+.I
+..
+.de D)
+.DE
+.nr VS 18p
+.vs 18p
+.R
+..
+.de Id
+.ND \\$4
+..
+.Id $Id: rcs.ms,v 5.2 1991/01/03 10:57:28 eggert Exp $
+.RP
+.TL
+RCS\*-A System for Version Control
+.sp
+.AU
+Walter F. Tichy
+.AI
+Department of Computer Sciences
+Purdue University
+West Lafayette, Indiana 47907
+.sp
+.AB
+An important problem in program development and maintenance is version control,
+i.e., the task of keeping a software system consisting of many versions and
+configurations well organized.
+The Revision Control System (RCS)
+is a software tool that assists with that task.
+RCS manages revisions of text documents, in particular source programs,
+documentation, and test data.
+It automates the storing, retrieval, logging and identification of revisions,
+and it provides selection mechanisms for composing configurations.
+This paper introduces basic version control concepts and
+discusses the practice of version control
+using RCS.
+For conserving space, RCS stores deltas, i.e., differences between
+successive revisions. Several delta storage methods are discussed.
+Usage statistics show that RCS's delta storage method is
+space and time efficient.
+The paper concludes with a detailed survey of version control tools.
+.sp
+\fBKeywords\fR: configuration management, history management,
+version control, revisions, deltas.
+.AE
+.FS
+An earlier version of this paper was published in
+.I "Software\*-Practice & Experience"
+.B 15 ,
+7 (July 1985), 637-654.
+.FE
+.nr VS 18p
+.LP
+.NH
+Introduction
+.PP
+Version control is the task of keeping software
+systems consisting of many versions and configurations well organized.
+The Revision Control System (RCS) is a set of UNIX
+commands that assist with that task.
+.PP
+RCS' primary function is to manage \fIrevision groups\fR.
+A revision group is a set of text documents, called \fIrevisions\fR,
+that evolved from each other. A new revision is
+created by manually editing an existing one.
+RCS organizes the revisions into an ancestral tree. The initial revision
+is the root of the tree, and the tree edges indicate
+from which revision a given one evolved.
+Besides managing individual revision groups, RCS provides
+flexible selection functions for composing configurations.
+RCS may be combined with MAKE\u1\d,
+resulting in a powerful package for version control.
+.PP
+RCS also offers facilities for
+merging updates with customer modifications,
+for distributed software development, and
+for automatic identification.
+Identification is the `stamping'
+of revisions and configurations with unique markers.
+These markers are akin to serial numbers,
+telling software maintainers unambiguously which configuration
+is before them.
+.PP
+RCS is designed for both production and experimental
+environments.
+In production environments,
+access controls detect update conflicts and prevent overlapping changes.
+In experimental environments, where strong controls are
+counterproductive, it is possible to loosen the controls.
+.PP
+Although RCS was originally intended for programs, it is useful for any
+text that is revised frequently and whose previous revisions must be
+preserved. RCS has been applied successfully to store the source
+text for drawings, VLSI layouts, documentation, specifications,
+test data, form letters and articles.
+.PP
+This paper discusses the practice of
+version control using RCS.
+It also introduces basic version control concepts,
+useful for clarifying current practice and designing similar systems.
+Revision groups of individual components are treated in the next three sections,
+and the extensions to configurations follow.
+Because of its size, a survey of version control tools
+appears at the end of the paper.
+.NH
+Getting started with RCS
+.PP
+Suppose a text file \fIf.c\fR is to be placed under control of RCS.
+Invoking the check-in command
+.D(
+ci f.c
+.D)
+creates a new revision group with the contents of
+\fIf.c\fR as the initial
+revision (numbered 1.1)
+and stores the group into the file \fIf.c,v\fR.
+Unless told otherwise, the command deletes \fIf.c\fR.
+It also asks for a description of the group.
+The description should state the common purpose of all revisions in the group,
+and becomes part of the group's documentation.
+All later check-in commands will ask for a log entry,
+which should summarize the changes made.
+(The first revision is assigned a default log message,
+which just records the fact that it is the initial revision.)
+.PP
+Files ending in \fI,v\fR
+are called \fIRCS files\fR (\fIv\fR stands for \fIv\fRersions);
+the others are called working files.
+To get back the working file \fIf.c\fR in the previous example,
+execute the check-out command:
+.D(
+co f.c
+.D)
+.R
+This command extracts the latest revision from
+the revision group \fIf.c,v\fR and writes
+it into \fIf.c\fR.
+The file \fIf.c\fR can now be edited and, when finished,
+checked back in with \fIci\fR:
+.D(
+ci f.c
+.D)
+\fICi\fR assigns number 1.2 to
+the new revision.
+If \fIci\fR complains with the message
+.D(
+ci error: no lock set by <login>
+.D)
+then the system administrator has decided to configure RCS for a
+production environment by enabling the `strict locking feature'.
+If this feature is enabled, all RCS files are initialized
+such that check-in operations require a lock on the previous revision
+(the one from which the current one evolved).
+Locking prevents overlapping modifications if several people work on the same file.
+If locking is required, the revision should
+have been locked during the check-out by using
+the option \fI\-l\fR:
+.D(
+co \-l f.c
+.D)
+Of course it is too late now for the check-out with locking, because
+\fIf.c\fR has already been changed; checking out the file again
+would overwrite the modifications.
+(To prevent accidental overwrites, \fIco\fR senses the presence
+of a working file and asks whether the user really intended to overwrite it.
+The overwriting check-out is sometimes useful for
+backing up to the previous revision.)
+To be able to proceed with the check-in in the present case, first execute
+.D(
+rcs \-l f.c
+.D)
+This command retroactively locks the latest revision, unless someone
+else locked it in the meantime. In this case, the two programmers
+involved have to negotiate whose
+modifications should take precedence.
+.PP
+If an RCS file is private, i.e., if only the owner of the file is expected
+to deposit revisions into it, the strict locking feature is unnecessary and
+may be disabled.
+If strict locking is disabled,
+the owner of the RCS file need not have a lock for check-in.
+For safety reasons, all others
+still do. Turning strict locking off and on is done with the commands:
+.D(
+rcs \-U f.c \fRand\fP rcs \-L f.c
+.D)
+These commands enable or disable the strict locking feature for each RCS file
+individually.
+The system administrator only decides whether strict locking is
+enabled initially.
+.PP
+To reduce the clutter in a working directory, all RCS files can be moved
+to a subdirectory with the name \fIRCS\fR.
+RCS commands look first into that directory for RCS files.
+All the commands presented above work
+with the \fIRCS\fR subdirectory without change.\(dg
+.FS \(dg
+Pairs of RCS and working files can actually be specified in 3 ways:
+a) both are given, b) only the working file is given, c) only the
+RCS file is given.
+If a pair is given, both files may have arbitrary path prefixes;
+RCS commands pair them up intelligently.
+.FE
+.PP
+It may be undesirable that \fIci\fR deletes the working file.
+For instance, sometimes one would like to save the current revision,
+but continue editing.
+Invoking
+.D(
+ci \-l f.c
+.D)
+checks in \fIf.c\fR as usual, but performs an additional
+check-out with locking afterwards. Thus, the working file does
+not disappear after the check-in.
+Similarly, the option
+\fI\-u\fR does a check-in followed by a check-out without
+locking. This option is useful if the file is needed for compilation after the check-in.
+Both options update the identification markers in the working file
+(see below).
+.PP
+Besides the operations \fIci\fR and \fIco\fR, RCS provides the following
+commands:
+.sp 0
+.nr VS 12p
+.vs 12p
+.TS
+tab(%);
+li l.
+ident%extract identification markers
+rcs%change RCS file attributes
+rcsclean%remove unchanged working files (optional)
+rcsdiff%compare revisions
+rcsfreeze%record a configuration (optional)
+rcsmerge%merge revisions
+rlog%read log messages and other information in RCS files
+.TE
+A synopsis of these commands appears in the Appendix.
+.NH 2
+Automatic Identification
+.PP
+RCS can stamp source and object code with special identification strings,
+similar to product and serial numbers.
+To obtain such identification, place the marker
+.D(
+\*sId\*s
+.D)
+into the text of a revision, for instance inside a comment.
+The check-out operation will replace this marker with a string of the form
+.D(
+\*sId: filename revisionnumber date time author state locker \*s
+.D)
+This string need never be touched, because \fIco\fR keeps it
+up to date automatically.
+To propagate the marker into object code, simply put
+it into a literal character string. In C, this is done as follows:
+.D(
+static char rcsid[] = \&"\*sId\*s\&";
+.D)
+The command \fIident\fR extracts such markers from any file, in particular from
+object code.
+\fIIdent\fR helps to find out
+which revisions of which modules were used in a given program.
+It returns a complete and unambiguous component list,
+from which a copy of the program can be reconstructed.
+This facility is invaluable for program maintenance.
+.PP
+There are several additional identification markers, one for each component
+of \*sId\*s.
+The marker
+.D(
+\*sLog\*s
+.D)
+has a similar function. It accumulates
+the log messages that are requested during check-in.
+Thus, one can maintain the complete history of a revision directly inside it,
+by enclosing it in a comment.
+Figure 1 is a partial reproduction of a log contained in revision 4.1 of
+the file \fIci.c\fR. The log appears at the beginning of the file,
+and makes it easy to determine what the recent modifications were.
+.sp
+.nr VS 12p
+.vs 12p
+.ne 18
+.nf
+.in +0.5i
+/* \*sLog: ci.c,v \*s
+ * Revision 4.1 1983/05/10 17:03:06 wft
+ * Added option \-d and \-w, and updated assignment of date, etc. to new delta.
+ * Added handling of default branches.
+ *
+ * Revision 3.9 1983/02/15 15:25:44 wft
+ * Added call to fastcopy() to copy remainder of RCS file.
+ *
+ * Revision 3.8 1983/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 1982/12/10 16:09:20 wft
+ * Corrected checking of return code from diff.
+ * An RCS file now inherits its mode during the first ci from the working file,
+ * except that write permission is removed.
+ */
+.in 0
+.ce 1
+Figure 1. Log entries produced by the marker \*sLog\*s.
+.fi
+.nr VS 18p
+.vs 18p
+.sp 0
+.LP
+Since revisions are stored in the form of differences,
+each log message is
+physically stored once,
+independent of the number of revisions present.
+Thus, the \*sLog\*s marker incurs negligible space overhead.
+.NH
+The RCS Revision Tree
+.PP
+RCS arranges revisions in an ancestral tree.
+The \fIci\fR command builds this tree; the auxiliary command \fIrcs\fR
+prunes it.
+The tree has a root revision, normally numbered 1.1, and successive revisions
+are numbered 1.2, 1.3, etc. The first field of a revision number
+is called the \fIrelease number\fR and the second one
+the \fIlevel number\fR. Unless given explicitly,
+the \fIci\fR command assigns a new revision number
+by incrementing the level number of the previous revision.
+The release number must be incremented explicitly, using the
+\fI\-r\fR option of \fIci\fR.
+Assuming there are revisions 1.1, 1.2, and 1.3 in the RCS file f.c,v, the command
+.D(
+ci \-r2.1 f.c \fRor\fP ci \-r2 f.c
+.D)
+assigns the number 2.1 to the new revision.
+Later check-ins without the \fI\-r\fR option will assign the numbers 2.2, 2.3,
+and so on.
+The release number should be incremented only at major transition points
+in the development, for instance when a new release of a software product has
+been completed.
+.NH 2
+When are branches needed?
+.PP
+A young revision tree is slender:
+It consists of only one branch, called the trunk.
+As the tree ages, side branches may form.
+Branches are needed in the following 4 situations.
+.IP "\fITemporary fixes\fR"
+.sp 0
+Suppose a tree has 5 revisions grouped in 2 releases,
+as illustrated in Figure 2.
+Revision 1.3, the last one of release 1, is in operation at customer sites,
+while release 2 is in active development.
+.ne 4
+.PS 4i
+.ps -2
+box "1.1"
+arrow
+box "1.2"
+arrow
+box "1.3"
+arrow
+box "2.1"
+arrow
+box "2.2"
+arrow dashed
+.ps +2
+.PE
+.ce 1
+Figure 2. A slender revision tree.
+.sp 0
+Now imagine a customer requesting a fix of
+a problem in revision 1.3, although actual development has moved on
+to release 2. RCS does not permit an extra
+revision to be spliced in between 1.3 and 2.1, since that would not reflect
+the actual development history. Instead, create a branch
+at revision 1.3, and check in the fix on that branch.
+The first branch starting at 1.3 has number 1.3.1, and
+the revisions on that branch are numbered 1.3.1.1, 1.3.1.2, etc.
+The double numbering is needed to allow for another
+branch at 1.3, say 1.3.2.
+Revisions on the second branch would be numbered
+1.3.2.1, 1.3.2.2, and so on.
+The following steps create
+branch 1.3.1 and add revision 1.3.1.1:
+.sp 0
+.I
+.nr VS 12p
+.vs 12p
+.TS
+tab(%);
+l l l.
+ %co \-r1.3 f.c% \*- check out revision 1.3
+ %edit f.c% \*- change it
+ %ci \-r1.3.1 f.c% \*- check it in on branch 1.3.1
+.TE
+.nr VS 18p
+.vs 18p
+.R
+This sequence of commands transforms the tree of Figure 2 into
+the one in Figure 3.
+Note that it may be necessary to incorporate the differences
+between 1.3 and 1.3.1.1
+into a revision at level 2. The operation \fIrcsmerge\fR automates this
+process (see the Appendix).
+.ne 7
+.PS 4i
+.ps -2
+ box "1.1"
+ arrow
+ box "1.2"
+ arrow
+R13: box "1.3"
+ arrow
+R21: box "2.1"
+ arrow
+R22: box "2.2"
+ arrow dashed
+ line invis down from R21.s
+RB1: box "1.3.1.1"
+ arrow dashed right from RB1.e
+ arrow from R13.s to RB1.w
+.ps +2
+.PE
+.ce 1
+Figure 3. A revision tree with one side branch
+.sp
+.IP "\fIDistributed development and customer modifications\fR"
+.sp 0
+Assume a situation as in Figure 2, where revision 1.3 is in operation
+at several customer sites,
+while release 2 is in development.
+Customer sites should use RCS to store the distributed software.
+However, customer modifications should not be placed on the same branch
+as the distributed source; instead, they should be placed on a side branch.
+When the next software distribution arrives,
+it should be appended to the trunk of
+the customer's RCS file, and the customer
+can then merge the local modifications back into the new release.
+In the above example, a
+customer's RCS file would contain the following tree, assuming
+that the customer has received revision 1.3, added his local modifications
+as revision 1.3.1.1, then received revision 2.4, and merged
+2.4 and 1.3.1.1, resulting in 2.4.1.1.
+.ne 7
+.PS 4i
+.ps -2
+R13: box "1.3"
+ line invis
+R21: box invis
+ line invis
+R22: box invis
+ line invis
+R24: box "2.4"
+ line invis
+R25: box invis
+ line invis
+ arrow from R13.e to R24.w
+ line invis down from R21.s
+RB1: box "1.3.1.1"
+ arrow from R13.s to RB1.w
+ right
+ line invis down from R25.s
+RB2: box "2.4.1.1"
+ arrow from R24.s to RB2.w
+.ps +2
+.PE
+.ce 1
+Figure 4. A customer's revision tree with local modifications.
+.sp 1
+This approach is actually practiced in the CSNET project,
+where several universities and a company cooperate
+in developing a national computer network.
+.IP "\fIParallel development\fR"
+.sp 0
+Sometimes it is desirable to explore an alternate design or
+a different implementation technique in parallel with the
+main line development. Such development
+should be carried out on a side branch.
+The experimental changes may later be moved into the main line, or abandoned.
+.IP "\fIConflicting updates\fR"
+.sp 0
+A common occurrence is that one programmer
+has checked out a revision, but cannot complete the assignment
+for some reason. In the meantime, another person
+must perform another modification
+immediately. In that case, the second person should check-out the same revision,
+modify it, and check it in on a side branch, for later merging.
+.PP
+Every node in a revision tree consists of the following attributes:
+a revision number, a check-in date and time, the author's identification,
+a log entry, a state and the actual text. All these attributes
+are determined at the time the revision is checked in.
+The state attribute indicates the status of a revision.
+It is set automatically to `experimental' during check-in.
+A revision can later be promoted to a higher status, for example
+`stable' or `released'. The set of states is user-defined.
+.NH 2
+Revisions are represented as deltas
+.PP
+For conserving space, RCS stores revisions in the form
+of deltas, i.e., as differences between revisions.
+The user interface completely hides this fact.
+.PP
+A delta is a sequence of edit commands that transforms one string
+into another. The deltas employed by RCS are line-based, which means
+that the only edit commands allowed are insertion and deletion of lines.
+If a single character in a line is changed, the
+edit scripts consider the entire line changed.
+The program \fIdiff\fR\u2\d
+produces a small, line-based delta between pairs of text files.
+A character-based edit script would take much longer to compute,
+and would not be significantly shorter.
+.PP
+Using deltas is a classical space-time tradeoff: deltas reduce the
+space consumed, but increase access time.
+However, a version control tool should impose as little delay
+as possible on programmers.
+Excessive delays discourage the use of version controls,
+or induce programmers to take shortcuts that compromise system integrity.
+To gain reasonably fast access time for both editing and compiling,
+RCS arranges deltas in the following way.
+The most recent revision on the trunk is stored intact.
+All other revisions on the trunk are stored as reverse deltas.
+A reverse delta describes how to go backward in the development history:
+it produces the desired revision if applied to the successor of that revision.
+This implementation has the advantage
+that extraction of the latest revision is a simple and fast copy
+operation.
+Adding a new revision to the trunk is also fast: \fIci\fR simply
+adds the new revision intact, replaces the previous
+revision with a reverse delta, and keeps the rest of the old deltas.
+Thus, \fIci\fR requires the computation
+of only one new delta.
+.PP
+Branches need special treatment. The naive solution would be to
+store complete copies for the tips of all branches.
+Clearly, this approach would cost too much space. Instead,
+RCS uses \fIforward\fR deltas for branches. Regenerating a revision
+on a side branch proceeds as follows. First, extract the latest revision
+on the trunk; secondly, apply reverse deltas until the fork revision for
+the branch is obtained; thirdly, apply forward deltas until the desired
+branch revision is reached. Figure 5 illustrates a tree with
+one side branch. Triangles pointing to the left and right represent
+reverse and forward deltas, respectively.
+.ne 8
+.PS 4i
+.ps -2
+define BD X [line invis $1 right .5;
+line up .3 then left .5 down .3 then right .5 down .3 then up .3] X
+
+define FD X [line invis $1 right .5;
+line left .5 down .3 then up .6 then right .5 down .3;] X
+
+right
+D11: BD(" 1.1")
+ arrow right from D11.e
+D12: BD(" 1.2")
+ arrow right from D12.e
+D13: BD(" 1.3")
+ arrow right from D13.e
+D21: BD(" 2.1")
+ arrow right from D21.e
+D22: box "2.2"
+ line invis down from D21.s
+F1: FD("1.3.1.1 ")
+ arrow from D13.se to F1.w
+ arrow from F1.e right
+ right
+F2: FD("1.3.1.2 ")
+.ps +2
+.PE
+.ce 1
+Figure 5. A revision tree with reverse and forward deltas.
+.sp 0
+.PP
+Although implementing fast check-out for the latest trunk revision,
+this arrangement has the disadvantage that generation of other revisions
+takes time proportional to the number of deltas applied. For example,
+regenerating the branch tip in Figure 5 requires application of five
+deltas (including the initial one). Since usage statistics show that
+the latest trunk revision is the one that is retrieved in 95 per cent
+of all cases (see the section on usage statistics), biasing check-out time
+in favor of that revision results in significant savings.
+However, careful implementation of the delta application process is
+necessary to provide low retrieval overhead for other revisions, in
+particular for branch tips.
+.PP
+There are several techniques for delta application.
+The naive one is to pass each delta to a general-purpose text editor.
+A prototype of RCS invoked the UNIX editor \fIed\fR both
+for applying deltas and for expanding the identification markers.
+Although easy to implement, performance was poor, owing to the
+high start-up costs and excess generality of \fIed\fR. An intermediate
+version of RCS used a special-purpose, stream-oriented editor.
+This technique reduced the cost of applying a delta to the cost of
+checking out the latest trunk revision. The reason for this behavior
+is that each delta application involves a complete pass over
+the preceding revision.
+.PP
+However, there is a much better algorithm. Note that the deltas are
+line oriented and that most of the work of a stream editor involves
+copying unchanged lines from one revision to the next. A faster
+algorithm avoids unnecessary copying of character strings by using
+a \fIpiece table\fR.
+A piece table is a one-dimensional array, specifying how a given
+revision is `pieced together' from lines in the RCS file.
+Suppose piece table \fIPT\dr\u\fR represents revision \fIr\fR.
+Then \fIPT\dr\u[i]\fR contains the starting position of line \fIi\fR
+of revision \fIr\fR.
+Application of the next delta transforms piece table \fIPT\dr\u\fR
+into \fIPT\dr+1\u\fR. For instance, a delete command removes a
+series of entries from the piece table. An insertion command inserts
+new entries, moving the entries following the insertion point further down the
+array. The inserted entries point to the text lines in the delta.
+Thus, no I/O is involved except for reading the delta itself. When all
+deltas have been applied to the piece table, a sequential pass
+through the table looks up each line in the RCS file and copies it to
+the output file, updating identification markers at the same time.
+Of course, the RCS file must permit random access, since the copied
+lines are scattered throughout that file. Figure 6 illustrates an
+RCS file with two revisions and the corresponding piece tables.
+.ne 13
+.sp 6
+.ce 1
+\fIFigure 6 is not available.\fP
+.sp 5
+.ce 1
+Figure 6. An RCS file and its piece tables
+.sp 0
+.PP
+The piece table approach has the property that the time for applying a single
+delta is roughly determined by the size of the delta, and not by the
+size of the revision. For example, if a delta is
+10 per cent of the size of a revision, then applying it takes only
+10 per cent of the time to generate the latest trunk revision. (The stream
+editor would take 100 per cent.)
+.PP
+There is an important alternative for representing deltas that affects
+performance. SCCS\u3\d,
+a precursor of RCS, uses \fIinterleaved\fR deltas.
+A file containing interleaved deltas is partitioned into blocks of lines.
+Each block has a header that specifies to which revision(s) the block
+belongs. The blocks are sorted out in such a way that a single
+pass over the file can pick up all the lines belonging to a given
+revision. Thus, the regeneration time for all revisions is the same:
+all headers must be inspected, and the associated blocks either copied
+or skipped. As the number of revisions increases, the cost of retrieving
+any revision is much higher than the cost of checking out the
+latest trunk revision with reverse deltas. A detailed comparison
+of SCCS's interleaved deltas and RCS's reverse deltas can be found
+in Reference 4.
+This reference considers the version of RCS with the
+stream editor only. The piece table method improves performance
+further, so that RCS is always faster than SCCS, except if 10
+or more deltas are applied.
+.PP
+Additional speed-up for both delta methods can be obtained by caching
+the most recently generated revision, as has been implemented in DSEE.\u5\d
+With caching, access time to frequently used revisions can approach normal file
+access time, at the cost of some additional space.
+.NH
+Locking: A Controversial Issue
+.PP
+The locking mechanism for RCS was difficult to design.
+The problem and its solution are first presented in their `pure' form,
+followed by a discussion of the complications
+caused by `real-world' considerations.
+.PP
+RCS must prevent two or more persons from depositing competing changes of the
+same revision.
+Suppose two programmers check out revision 2.4 and
+modify it. Programmer A checks in a revision before programmer B\&.
+Unfortunately, programmer B has not seen A's
+changes, so the effect is that A's changes are covered up by B's deposit.
+A's changes are not lost since all revisions
+are saved, but they are confined to a single revision.\(dd
+.FS \(dd
+Note that this problem is entirely different from the atomicity problem.
+Atomicity means that
+concurrent update operations on the same RCS file cannot be permitted,
+because that may result in inconsistent data.
+Atomic updates are essential (and implemented in RCS),
+but do not solve the conflict discussed here.
+.FE
+.PP
+This conflict is prevented in RCS by locking.
+Whenever someone intends to edit a revision (as opposed
+to reading or compiling it), the revision should be checked out
+and locked,
+using the \fI\-l\fR option on \fIco\fR. On subsequent check-in,
+\fIci\fR tests the lock and then removes it.
+At most one programmer at a time may
+lock a particular revision, and only this programmer may check in
+the succeeding revision.
+Thus, while a revision is locked, it is the exclusive responsibility
+of the locker.
+.PP
+An important maxim for software tools like RCS is that they must
+not stand in the way of making progress with a project.
+This consideration leads to several weakenings of the locking mechanism.
+First of all, even if a revision is locked, it can
+still be checked out. This is necessary if other people
+wish to compile or inspect the locked revision
+while the next one is in preparation. The only operations they
+cannot do are to lock the revision or to check in the succeeding one. Secondly,
+check-in operations on other branches in the RCS file are still possible; the
+locking of one revision does not affect any other revision.
+Thirdly, revisions are occasionally locked for a long period of time
+because a programmer is absent or otherwise unable to complete
+the assignment. If another programmer has to make a pressing change,
+there are the following three alternatives for making progress:
+a) find out who is holding the lock and ask that person to release it;
+b) check out the locked revision, modify it, check it
+in on a branch, and merge the changes later;
+c) break the lock. Breaking a lock leaves a highly visible
+trace, namely an electronic mail message that is sent automatically to the
+holder of the lock, recording the breaker and a commentary requested from him.
+Thus, breaking locks is tolerated under certain circumstances,
+but will not go unnoticed.
+Experience has shown that the automatic mail message attaches a high enough
+stigma to lock breaking,
+such that programmers break locks only in real emergencies,
+or when a co-worker resigns and leaves locked revisions behind.
+.PP
+If an RCS file is private, i.e., when a programmer owns an RCS file
+and does not expect anyone else to perform check-in operations,
+locking is an unnecessary nuisance.
+In this case,
+the `strict locking feature' discussed earlier may be disabled,
+provided that file protection
+is set such that only the owner may write the RCS file.
+This has the effect that only the owner can check-in revisions,
+and that no lock is needed for doing so.
+.PP
+As added protection,
+each RCS file contains an access list that specifies the users
+who may execute update operations. If an access list is empty,
+only normal UNIX file protection applies. Thus, the access list is
+useful for restricting the set of people who would otherwise have update
+permission. Just as with locking, the access list
+has no effect on read-only operations such as \fIco\fR. This approach
+is consistent with the UNIX philosophy of openness, which contributes
+to a productive software development environment.
+.NH
+Configuration Management
+.PP
+The preceding sections described how RCS deals with revisions of individual
+components; this section discusses how to handle configurations.
+A configuration is a set of revisions, where each revision comes
+from a different revision group, and the revisions are selected
+according to a certain criterion.
+For example,
+in order to build a functioning compiler, the `right'
+revisions from the scanner, the parser, the optimizer
+and the code generator must be combined.
+RCS, in conjunction with MAKE,
+provides a number of facilities to effect a smooth selection.
+.NH 2
+RCS Selection Functions
+.PP
+.IP "\fIDefault selection\fR"
+.sp 0
+During development, the usual selection criterion is to choose
+the latest revision of all components. The \fIco\fR command
+makes this selection by default. For example, the command
+.D(
+co *,v
+.D)
+retrieves the latest revision on the default branch of each RCS file
+in the current directory.
+The default branch is usually the trunk, but may be
+set to be a side branch.
+Side branches as defaults are needed in distributed software development,
+as discussed in the section on the RCS revision tree.
+.sp
+.IP "\fIRelease based selection\fR"
+.sp 0
+Specifying a release or branch number selects the latest revision in
+that release or branch.
+For instance,
+.D(
+co \-r2 *,v
+.D)
+retrieves the latest revision with release number 2 from each RCS file.
+This selection is convenient if a release has been completed and
+development has moved on to the next release.
+.sp
+.IP "\fIState and author based selection\fR"
+.sp 0
+If the highest level number within a given release number
+is not the desired one,
+the state attribute can help. For example,
+.D(
+co \-r2 \-sReleased *,v
+.D)
+retrieves the latest revision with release number 2 whose state attribute
+is `Released'.
+Of course, the state attribute has to be set appropriately, using the
+\fIci\fR or \fIrcs\fR commands.
+Another alternative is to select a revision by its author,
+using the \fI\-w\fR option.
+.sp
+.IP "\fIDate based selection\fR"
+.sp 0
+Revisions may also be selected by date.
+Suppose a release of an entire system was
+completed and current on March 4, at 1:00 p.m. local time. Then the command
+.D(
+co \-d'March 4, 1:00 pm LT' *,v
+.D)
+checks out all the components of that release, independent of the numbering.
+The \fI\-d\fR option specifies a `cutoff date', i.e.,
+the revision selected has a check-in date that
+is closest to, but not after the date given.
+.IP "\fIName based selection\fR"
+.sp 0
+The most powerful selection function is based on assigning symbolic
+names to revisions and branches.
+In large systems, a single release number or date is not sufficient
+to collect the appropriate revisions from all groups.
+For example, suppose one wishes to combine release 2
+of one subsystem and release 15 of another.
+Most likely, the creation dates of those releases differ also.
+Thus, a single revision number or date passed to the \fIco\fR command
+will not suffice to select the right revisions.
+Symbolic revision numbers solve this problem.
+Each RCS file may contain a set of symbolic names that are mapped
+to numeric revision numbers. For example, assume
+the symbol \fIV3\fR is bound to release number 2 in file \fIs,v\fR, and to
+revision number 15.9 in \fIt,v\fR.
+Then the single command
+.D(
+co \-rV3 s,v t,v
+.D)
+retrieves the latest revision of release 2 from \fIs,v\fR,
+and revision 15.9 from \fIt,v\fR.
+In a large system with many modules, checking out all
+revisions with one command greatly simplifies configuration management.
+.PP
+Judicious use of symbolic revision numbers helps with organizing
+large configurations.
+A special command, \fIrcsfreeze\fR,
+assigns a symbolic revision number to a selected revision
+in every RCS file.
+\fIRcsfreeze\fR effectively freezes a configuration.
+The assigned symbolic revision number selects all components
+of the configuration.
+If necessary, symbolic numbers
+may even be intermixed with numeric ones. Thus, \fIV3.5\fR in the
+above example
+would select revision 2.5 in \fIs,v\fR and branch 15.9.5 in \fIt,v\fR.
+.PP
+The options \fI\-r\fR, \fI\-s\fR, \fI\-w\fR and \fI\-d\fR
+may be combined. If a branch is given, the latest revision
+on that branch satisfying all conditions is retrieved;
+otherwise, the default branch is used.
+.NH 2
+Combining MAKE and RCS
+.PP
+MAKE\u1\d
+is a program that processes configurations.
+It is driven by configuration specifications
+recorded in a special file, called a `Makefile'.
+MAKE avoids redundant processing steps
+by comparing creation dates of source and processed objects.
+For example, when instructed to compile all
+modules of a given system, it only recompiles
+those source modules that were changed
+since they were processed last.
+.PP
+MAKE has been extended with an auto-checkout feature for RCS.*
+.FS *
+This auto-checkout extension is available only in some versions of MAKE,
+e.g. GNU MAKE.
+.FE
+When a certain file to be processed is not present,
+MAKE attempts a check-out operation.
+If successful, MAKE performs the required processing, and then deletes
+the checked out file to conserve space.
+The selection parameters discussed above can be passed to MAKE
+either as parameters, or directly embedded in the Makefile.
+MAKE has also been extended to search the subdirectory named \fIRCS\fR
+for needed files, rather than just the current working directory.
+However, if a working file is present, MAKE totally ignores the corresponding
+RCS file and uses the working file.
+(In newer versions of MAKE distributed by AT&T and others,
+auto-checkout can be
+achieved with the rule DEFAULT, instead of a special extension of MAKE.
+However, a file checked out by the rule DEFAULT
+will not be deleted after processing. \fIRcsclean\fR can be
+used for that purpose.)
+.PP
+With auto-checkout, RCS/MAKE can effect a selection rule
+especially tuned for multi-person software development and maintenance.
+In these situations,
+programmers should obtain configurations that consist of
+the revisions they have personally checked out plus the latest
+checked in revision of all other revision groups.
+This schema can be set up as follows.
+.PP
+Each programmer chooses a working directory
+and places into it a symbolic link, named \fIRCS\fR,
+to the directory containing the relevant RCS files.
+The symbolic link makes sure that \fIco\fR and \fIci\fR
+operations need only specify the working files, and that
+the Makefile need not be changed.
+The programmer then checks out the needed files and modifies them.
+If MAKE is invoked,
+it composes configurations by selecting those
+revisions that are checked out, and the rest from the
+subdirectory \fIRCS\fR.
+The latter selection may be controlled by a symbolic
+revision number or any of the other selection criteria.
+If there are several programmers editing in separate working directories,
+they are insulated from each other's changes until checking in their
+modifications.
+.PP
+Similarly, a maintainer can recreate an older configuration
+by starting to work in an empty working directory.
+During the initial MAKE invocation, all revisions are selected from RCS files.
+As the maintainer checks out files and modifies them,
+a new configuration is gradually built up.
+Every time MAKE is invoked, it substitutes the modified revisions
+into the configuration being manipulated.
+.PP
+A final application of RCS is to use it for storing Makefiles.
+Revision groups of Makefiles represent
+multiple versions of configurations.
+Whenever a configuration is baselined or distributed,
+the best approach is to unambiguously fix
+the configuration with a symbolic revision number by calling
+\fIrcsfreeze\fR,
+to embed that symbol into the Makefile, and to
+check in the Makefile (using the same symbolic revision number).
+With this approach, old configurations
+can be regenerated easily and reliably.
+.NH
+Usage Statistics
+.PP
+The following usage statistics were collected on two DEC VAX-11/780
+computers of the Purdue Computer Science Department. Both machines
+are mainly used for research purposes. Thus, the data
+reflect an environment in which the majority of projects
+involve prototyping and advanced software development,
+but relatively little long-term maintenance.
+.PP
+For the first experiment,
+the \fIci\fR and \fIco\fR operations were instrumented
+to log the number of backward and forward deltas applied.
+The data were collected during a 13 month period
+from Dec. 1982 to Dec. 1983.
+Table I summarizes the results.
+.sp 0
+.nr VS 12p
+.vs 12p
+.TS
+center,box,tab(#);
+c|c|c|c|c s|c s
+c|c|c|c|c s|c s
+l|n|n|n|n n|n n.
+Operation#Total#Total deltas#Mean deltas#Operations#Branch
+ #operations #applied#applied#with >1 delta#operations
+_
+co # 7867# 9320#1.18#509#(6%)#203#(3%)
+ci # 3468# 2207#0.64# 85#(2%)# 75#(2%)
+ci & co#11335#11527#1.02#594#(5%)#278#(2%)
+.TE
+.ce 1
+Table I. Statistics for \fIco\fR and \fIci\fR operations.
+.nr VS 18p
+.vs 18p
+.PP
+The first two lines show statistics for check-out and check-in;
+the third line shows the combination.
+Recall that \fIci\fR performs an implicit check-out to obtain
+a revision for computing the delta.
+In all measures presented, the most recent revision (stored intact)
+counts as one delta. The number of deltas applied represents
+the number of passes necessary, where the first `pass' is a copying step.
+.PP
+Note that the check-out operation is executed more than
+twice as frequently as the check-in operation.
+The fourth column gives the mean number of deltas
+applied in all three cases.
+For \fIci\fR, the mean number of deltas applied is less
+than one.
+The reasons are that the initial check-in requires no delta at all, and that
+the only time \fIci\fR requires more than one delta is for branches.
+Column 5 shows the actual number of operations that applied more than one
+delta.
+The last column indicates that branches were not used often.
+.PP
+The last three columns demonstrate that the most recent trunk revision
+is by far the most frequently accessed.
+For RCS, check-out of
+this revision is a simple copy operation, which is the absolute minimum
+given the copy-semantics of \fIco\fR.
+Access to older revisions and branches
+is more common in non-academic environments,
+yet even if access to older deltas were an order
+of magnitude more frequent,
+the combined average number of deltas applied would still be below 1.2.
+Since RCS is faster than SCCS until up to 10 delta applications,
+reverse deltas are clearly the method of choice.
+.PP
+The second experiment, conducted in March of 1984,
+involved surveying the existing RCS files
+on our two machines. The goal was to determine the mean number of
+revisions per RCS file, as well as the space consumed by them.
+Table II shows the results. (Tables I and II were produced at different
+times and are unrelated.)
+.sp 0
+.nr VS 12p
+.vs 12p
+.TS
+center,box,tab(#);
+c | c | c | c | c | c | c
+c | c | c | c | c | c | c
+l | n | n | n | n | n | n.
+ #Total RCS#Total#Mean#Mean size of#Mean size of#Overhead
+ #files#revisions#revisions#RCS files#revisions
+_
+All files #8033#11133#1.39#6156#5585#1.10
+Files with#1477# 4578#3.10#8074#6041#1.34
+\(>= 2 deltas
+.TE
+.ce 1
+Table II. Statistics for RCS files.
+.nr VS 18p
+.vs 18p
+.PP
+The mean number of revisions per RCS file is 1.39.
+Columns 5 and 6 show the mean sizes (in bytes) of an RCS file
+and of the latest revision of each RCS file, respectively.
+The `overhead' column contains the ratio of the mean sizes.
+Assuming that all revisions in an RCS file are approximately the same size,
+this ratio gives a measure of the space consumed by the extra revisions.
+.PP
+In our sample, over 80 per cent of the RCS files contained only a single revision.
+The reason is that our
+systems programmers routinely check in all source files
+on the distribution tapes, even though they may never touch them again.
+To get a better indication of how much space savings are possible
+with deltas, all measures with those files
+that contained 2 or more revisions were recomputed. Only for those files
+is RCS necessary.
+As shown in the second line, the average number of revisions for those files is
+3.10, with an overhead of 1.34. This means that the extra 2.10 deltas
+require 34 per cent extra space, or
+16 per cent per extra revision.
+Rochkind\u3\d
+measured the space consumed by SCCS, and
+reported an average of 5 revisions per group
+and an overhead of 1.37 (or about 9 per cent per extra revision).
+In a later paper, Glasser\u6\d
+observed an average of 7 revisions per group in a single, large project,
+but provided no overhead figure.
+In his paper on DSEE\u5\d,
+Leblang reported that delta storage combined with blank compression
+results in an overhead of a mere 1\-2 per cent per revision.
+Since leading blanks accounted for about 20 per cent of the surveyed Pascal
+programs, a revision group with 5\-10 members was smaller
+than a single cleartext copy.
+.PP
+The above observations demonstrate clearly that the space needed
+for extra revisions is small. With delta storage, the luxury of
+keeping multiple revisions online is certainly affordable.
+In fact, introducing a system with delta storage may reduce
+storage requirements, because programmers often save back-up copies
+anyway. Since back-up copies are stored much more efficiently with deltas,
+introducing a system such as RCS may
+actually free a considerable amount of space.
+.NH
+Survey of Version Control Tools
+.PP
+The need to keep back-up copies of software arose when
+programs and data were no longer stored on paper media, but were entered
+from terminals and stored on disk.
+Back-up copies are desirable for reliability, and many modern editors
+automatically save a back-up copy for every file touched.
+This strategy
+is valuable for short-term back-ups, but not suitable for long-term
+version control, since an existing back-up copy is overwritten whenever the
+corresponding file is edited.
+.PP
+Tape archives are suitable for long-term, offline storage.
+If all changed files are dumped on a back-up tape once per day, old revisions
+remain accessible. However, tape archives are unsatisfactory
+for version control in several ways. First, backing up the file
+system every 24 hours does not capture intermediate revisions.
+Secondly, the old revisions are not online,
+and accessing them is tedious and time-consuming.
+In particular, it is impractical to
+compare several old revisions of a group,
+because that may require mounting and searching several tapes.
+Tape archives are important fail-safe tools in the
+event of catastrophic disk failures or accidental deletions,
+but they are ill-suited for version control.
+Conversely, version control tools do not obviate the
+need for tape archives.
+.PP
+A natural technique for keeping several old revisions online is
+to never delete a file.
+Editing a file
+simply creates a new file with the same
+name, but with a different sequence number.
+This technique, available as an option in DEC's VMS operating system,
+turns out to be inadequate for version control.
+First, it is prohibitively expensive in terms of storage costs,
+especially since no data compression techniques are employed.
+Secondly, indiscriminately storing every change produces too many
+revisions, and programmers have difficulties distinguishing them.
+The proliferation of revisions forces programmers to spend much time on
+finding and deleting useless files.
+Thirdly, most of the support functions like locking, logging,
+revision selection,
+and identification described in this paper are not available.
+.PP
+An alternative approach is to separate editing from revision control.
+The user may repeatedly edit a given revision,
+until freezing it with an explicit command.
+Once a revision is frozen, it is stored permanently and can no longer be modified.
+(In RCS, freezing a revisions is done with \fIci\fR.)
+Editing a frozen revision implicitly creates a new one, which
+can again be changed repeatedly until it is frozen itself.
+This approach saves exactly those revisions that the user
+considers important, and keeps the number of revisions manageable.
+IBM's CLEAR/CASTER\u7\d,
+AT&T's SCCS\u3\d,
+CMU's SDC\u8\d
+and DEC's CMS\u9\d,
+are examples of version control systems using this approach.
+CLEAR/CASTER maintains a data base of programs, specifications,
+documentation and messages, using deltas.
+Its goal is to provide control over the development process from a
+management viewpoint.
+SCCS stores multiple revisions of source text in an ancestral tree,
+records a log entry for each revision,
+provides access control, and has facilities
+for uniquely identifying each revision.
+An efficient delta technique
+reduces the space consumed by each revision group.
+SDC is much simpler than SCCS because it stores not more than
+two revisions. However, it maintains a complete log for all old
+revisions, some of which may be on back-up tape.
+CMS, like SCCS, manages tree-structured revision groups,
+but offers no identification mechanism.
+.PP
+Tools for dealing with configurations are still in a state of flux.
+SCCS, SDC and CMS can be combined with MAKE or MAKE-like programs.
+Since flexible selection rules are missing from all these tools,
+it is sometimes difficult
+to specify precisely which revision of each group
+should be passed to MAKE for building a desired configuration.
+The Xerox Cedar system\u10\d
+provides a `System Modeller' that can rebuild
+a configuration from an arbitrary set of module revisions.
+The revisions of a module are only distinguished by creation time,
+and there is no tool for managing groups.
+Since the selection rules are primitive,
+the System Modeller appears to be somewhat tedious to use.
+Apollo's DSEE\u5\d
+is a sophisticated software engineering environment.
+It manages revision groups in a way similar to SCCS and CMS. Configurations
+are built using `configuration threads'.
+A configuration thread states which revision of each group
+named in a configuration should be chosen.
+A configuration thread may contain dynamic specifiers
+(e.g., `choose the revisions I am currently working on,
+and the most recent revisions otherwise'), which are bound
+automatically at build time.
+It also provides a notification mechanism for alerting
+maintainers about the need to rebuild a system after a change.
+.PP
+RCS is based on a general model for describing
+multi-version/multi-configuration systems\u11\d.
+The model describes systems using AND/OR graphs, where AND nodes represent
+configurations, and OR nodes represent version groups.
+The model gives rise to a suit of selection rules for
+composing configurations, almost all of which are implemented in RCS.
+The revisions selected by RCS are passed to MAKE for configuration building.
+Revision group management is modelled after SCCS.
+RCS retains SCCS's best features,
+but offers a significantly simpler user interface,
+flexible selection rules, adequate integration with MAKE
+and improved identification.
+A detailed comparison of RCS and SCCS appears in Reference 4.
+.PP
+An important component of all revision control systems
+is a program for computing deltas.
+SCCS and RCS use the program \fIdiff\fR\u2\d,
+which first computes the longest common substring of two
+revisions, and then produces the delta from that substring.
+The delta is simply an edit script consisting of deletion and
+insertion commands that generate one revision from the other.
+.PP
+A delta based on a longest common substring is not necessarily minimal,
+because it does not take advantage of crossing block moves.
+Crossing block moves arise if two or more blocks of lines
+(e.g., procedures)
+appear in a different order in two revisions.
+An edit script derived from a longest common substring
+first deletes the shorter of the two blocks, and then reinserts it.
+Heckel\u12\d
+proposed an algorithm for detecting block moves, but
+since the algorithm is based on heuristics,
+there are conditions
+under which the generated delta is far from minimal.
+DSEE uses this algorithm combined with blank compression,
+apparently with satisfactory overall results.
+A new algorithm that is guaranteed to produce a minimal delta based on
+block moves appears in Reference 13.
+A future release of RCS will use this algorithm.
+.PP
+\fIAcknowledgements\fR:
+Many people have helped make RCS a success by contributed criticisms, suggestions,
+corrections, and even whole new commands (including manual pages).
+The list of people is too long to be
+reproduced here, but my sincere thanks for their help and
+goodwill goes to all of them.
+.sp
+.nr VS 12p
+.vs 12p
+.SH
+Appendix: Synopsis of RCS Operations
+.LP
+.IP "\fIci\fP \fB\- check in revisions\fP"
+.sp 0
+\fICi\fR stores the contents of a working file into the
+corresponding RCS file as a new revision.
+If the RCS file doesn't exist, \fIci\fR creates it.
+\fICi\fR removes the working file, unless one of the options
+\fI\-u\fR or \fI\-l\fR is present.
+For each check-in, \fIci\fR asks for a commentary
+describing the changes relative to the previous revision.
+.sp 1
+\fICi\fR assigns the revision number given by the \fI\-r\fR option;
+if that option is missing, it derives the number from the
+lock held by the user; if there is no lock and locking is not strict,
+\fIci\fR increments the number of the latest revision on the trunk.
+A side branch can only be started by explicitly specifying its
+number with the \fI\-r\fR option during check-in.
+.sp 1
+\fICi\fR also determines
+whether the revision to be checked in is different from the
+previous one, and asks whether to proceed if not.
+This facility simplifies check-in operations for large systems,
+because one need not remember which files were changed.
+.sp 1
+The option \fI\-k\fR searches the checked in file for identification
+markers containing
+the attributes
+revision number, check-in date, author and state, and assigns these
+to the new revision rather than computing them. This option is
+useful for software distribution: Recipients of distributed software
+using RCS should check in updates with the \fI\-k\fR option.
+This convention guarantees that revision numbers, check-in dates,
+etc., are the same at all sites.
+.IP "\fIco\fP \fB\- check out revisions\fP"
+.sp 0
+\fICo\fR retrieves revisions according to revision number,
+date, author and state attributes. It either places the revision
+into the working file, or prints it on the standard output.
+\fICo\fR always expands the identification markers.
+.IP "\fIident\fP \fB\- extract identification markers\fP"
+.sp 0
+\fIIdent\fR extracts the identification markers expanded by \fIco\fR
+from any file and prints them.
+.IP "\fIrcs\fP \fB\- change RCS file attributes\fP"
+.sp 0
+\fIRcs\fR is an administrative operation that changes access lists,
+locks, unlocks, breaks locks, toggles the strict-locking feature,
+sets state attributes and symbolic revision numbers, changes the
+description, and deletes revisions. A revision can
+only be deleted if it is not the fork of a side branch.
+.IP "\fIrcsclean\fP \fB\- clean working directory\fP"
+.sp 0
+.ne 10
+\fIRcsclean\fR removes working files that were checked out but never changed.*
+.FS *
+The \fIrcsclean\fP and \fIrcsfreeze\fP commands
+are optional and are not always installed.
+.FE
+.IP "\fIrcsdiff\fP \fB\- compare revisions\fP"
+.sp 0
+\fIRcsdiff\fR compares two revisions and prints their
+difference, using the UNIX tool \fIdiff\fR.
+One of the revisions compared may be checked out.
+This command is useful for finding out about changes.
+.IP "\fIrcsfreeze\fP \fB\- freeze a configuration\fP"
+.sp 0
+\fIRcsfreeze\fR assigns the same symbolic revision number
+to a given revision in all RCS files.
+This command is useful for accurately recording a configuration.*
+.IP "\fIrcsmerge\fP \fB\- merge revisions\fP"
+.sp 0
+\fIRcsmerge\fR merges two revisions, \fIrev1\fR and \fIrev2\fR,
+with respect to a common ancestor.
+A 3-way file comparison determines the segments of lines that
+are (a) the same in all three revisions, or (b) the same in 2 revisions,
+or (c) different in all three. For all segments of type (b) where
+\fIrev1\fR is the differing revision,
+the segment in \fIrev1\fR replaces the corresponding segment of \fIrev2\fR.
+Type (c) indicates an overlapping change, is flagged as an error, and requires user
+intervention to select the correct alternative.
+.IP "\fIrlog\fP \fB\- read log messages\fP"
+.sp 0
+\fIRlog\fR prints the log messages and other information in an RCS file.
+.bp
+.LP
+.nr VS 12p
+.vs 12p
+.]<
+.ds [F 1
+.]-
+.ds [K FELD02
+.ds [K MakeArticle
+.ds [A Feldman, Stuart I.
+.ds [D March 1979
+.ds [T Make\*-A Program for Maintaining Computer Programs
+.ds [J Software\*-Practice & Experience
+.ds [V 9
+.ds [N 3
+.ds [P 255-265
+.nr [P 1
+.nr [T 0
+.nr [A 1
+.nr [O 0
+.][ 1 journal-article
+.ds [F 2
+.]-
+.ds [K HUNT01
+.ds [T An Algorithm for Differential File Comparison
+.ds [A Hunt, James W.
+.as [A " and McIlroy, M. D.
+.ds [I Computing Science Technical Report, Bell Laboratories
+.ds [R 41
+.ds [D June 1976
+.nr [T 0
+.nr [A 1
+.nr [O 0
+.][ 4 tech-report
+.ds [F 3
+.]-
+.ds [K SCCS
+.ds [A Rochkind, Marc J.
+.ds [D Dec. 1975
+.ds [T The Source Code Control System
+.ds [J IEEE Transactions on Software Engineering
+.ds [V SE-1
+.ds [N 4
+.ds [P 364-370
+.nr [P 1
+.nr [T 0
+.nr [A 1
+.nr [O 0
+.][ 1 journal-article
+.ds [F 4
+.]-
+.ds [K TICH08
+.ds [T Design, Implementation, and Evaluation of a Revision Control System
+.ds [A Tichy, Walter F.
+.ds [B Proceedings of the 6th International Conference on Software Engineering
+.ds [I ACM, IEEE, IPS, NBS
+.ds [D September 1982
+.ds [P 58-67
+.nr [P 1
+.nr [T 0
+.nr [A 1
+.nr [O 0
+.][ 3 article-in-book
+.ds [F 5
+.]-
+.ds [K LEBL01
+.ds [A Leblang, David B.
+.as [A " and Chase, Robert P.
+.ds [T Computer-Aided Software Engineering in a Distributed Workstation Environment
+.ds [O Proceedings of the ACM SIGSOFT/SIGPLAN Software Engineering Symposium
+.as [O " on Practical Software Development Environments.
+.ds [J SIGPLAN Notices
+.ds [V 19
+.ds [N 5
+.ds [D May 1984
+.ds [P 104-112
+.nr [P 1
+.nr [T 0
+.nr [A 1
+.nr [O 0
+.][ 1 journal-article
+.ds [F 1
+.ds [F 3
+.ds [F 6
+.]-
+.ds [K SCCSEval
+.ds [A Glasser, Alan L.
+.ds [D Nov. 1978
+.ds [T The Evolution of a Source Code Control System
+.ds [J Software Engineering Notes
+.ds [V 3
+.ds [N 5
+.ds [P 122-125
+.nr [P 1
+.ds [O Proceedings of the Software Quality and Assurance Workshop.
+.nr [T 0
+.nr [A 1
+.nr [O 1
+.][ 1 journal-article
+.ds [F 5
+.ds [F 7
+.]-
+.ds [K IBMClearCaster
+.ds [A Brown, H.B.
+.ds [D 1970
+.ds [T The Clear/Caster System
+.ds [J Nato Conference on Software Engineering, Rome
+.nr [T 0
+.nr [A 1
+.nr [O 0
+.][ 1 journal-article
+.ds [F 3
+.ds [F 8
+.]-
+.ds [K HabermannSDC
+.ds [A Habermann, A. Nico
+.ds [D Jan. 1979
+.ds [T A Software Development Control System
+.ds [I Technical Report, Carnegie-Mellon University, Department of Computer Science
+.nr [T 0
+.nr [A 0
+.nr [O 0
+.][ 2 book
+.ds [F 9
+.]-
+.ds [K CMS
+.ds [A DEC
+.ds [T Code Management System
+.ds [I Digital Equipment Corporation
+.ds [O Document No.\ EA-23134-82
+.ds [D 1982
+.nr [T 0
+.nr [A 0
+.nr [O 0
+.][ 2 book
+.ds [F 10
+.]-
+.ds [K LAMP01
+.ds [A Lampson, Butler W.
+.as [A " and Schmidt, Eric E.
+.ds [T Practical Use of a Polymorphic Applicative Language
+.ds [B Proceedings of the 10th Symposium on Principles of Programming Languages
+.ds [I ACM
+.ds [P 237-255
+.nr [P 1
+.ds [D January 1983
+.nr [T 0
+.nr [A 1
+.nr [O 0
+.][ 3 article-in-book
+.ds [F 5
+.ds [F 11
+.]-
+.ds [K TICH07
+.ds [T A Data Model for Programming Support Environments and its Application
+.ds [A Tichy, Walter F.
+.ds [B Automated Tools for Information System Design and Development
+.ds [E Hans-Jochen Schneider and Anthony I. Wasserman
+.ds [C Amsterdam
+.ds [I North-Holland Publishing Company
+.ds [D 1982
+.nr [T 0
+.nr [A 1
+.nr [O 0
+.][ 3 article-in-book
+.ds [F 4
+.ds [F 2
+.ds [F 12
+.]-
+.ds [K HECK01
+.ds [T A Technique for Isolating Differences Between Files
+.ds [A Heckel, Paul
+.ds [J Communications of the ACM
+.ds [D April 1978
+.ds [V 21
+.ds [N 4
+.ds [P 264-268
+.nr [P 1
+.nr [T 0
+.nr [A 0
+.nr [O 0
+.][ 1 journal-article
+.ds [F 13
+.]-
+.ds [K TICH11
+.ds [T The String-to-String Correction Problem with Block Moves
+.ds [A Tichy, Walter F.
+.ds [D Nov. 1984
+.ds [J ACM Transactions on Computer Systems
+.ds [V 2
+.ds [N 4
+.ds [P 309-321
+.nr [P 1
+.nr [T 0
+.nr [A 1
+.nr [O 0
+.][ 1 journal-article
+.]>
diff --git a/gnu/usr.bin/rcs/doc/rcs_func.ms b/gnu/usr.bin/rcs/doc/rcs_func.ms
new file mode 100644
index 0000000..9818086
--- /dev/null
+++ b/gnu/usr.bin/rcs/doc/rcs_func.ms
@@ -0,0 +1,95 @@
+.SH
+Functions of RCS (Revision Control System)
+.PP
+RCS manages software libraries. It greatly increases programmer productivity
+by providing the following functions.
+.IP 1.
+RCS stores and retrieves multiple revisions of program and other text.
+Thus, one can maintain one or more releases while developing the next
+release, with a minimum of space overhead. Changes no longer destroy the
+original -- previous revisions remain accessible.
+.RS
+.IP a.
+Maintains each module as a tree of revisions.
+.IP b.
+Project libraries can
+be organized centrally, decentralized, or any way you like.
+.IP c.
+RCS works for any type of text: programs, documentation, memos, papers,
+graphics, VLSI layouts, form letters, etc.
+.RE
+.IP 2.
+RCS maintains a complete history of changes.
+Thus, one can find out what happened to a module easily
+and quickly, without having to compare source listings or
+having to track down colleagues.
+.RS
+.IP a.
+RCS performs automatic record keeping.
+.IP b.
+RCS logs all changes automatically.
+.IP c.
+RCS guarantees project continuity.
+.RE
+.IP 3.
+RCS manages multiple lines of development.
+.IP 4.
+RCS can merge multiple lines of development.
+Thus, when several parallel lines of development must be consolidated
+into one line, the merging of changes is automatic.
+.IP 5.
+RCS flags coding conflicts.
+If two or more lines of development modify the same section of code,
+RCS can alert programmers about overlapping changes.
+.IP 6.
+RCS resolves access conflicts.
+When two or more programmers wish to modify the same revision,
+RCS alerts the programmers and makes sure that one modification won't wipe
+out the other one.
+.IP 7.
+RCS provides high-level retrieval functions.
+Revisions can be retrieved according to ranges of revision numbers,
+symbolic names, dates, authors, and states.
+.IP 8.
+RCS provides release and configuration control.
+Revisions can be marked as released, stable, experimental, etc.
+Configurations of modules can be described simply and directly.
+.IP 9.
+RCS performs automatic identification of modules with name, revision
+number, creation time, author, etc.
+Thus, it is always possible to determine which revisions of which
+modules make up a given configuration.
+.IP 10.
+Provides high-level management visibility.
+Thus, it is easy to track the status of a software project.
+.RS
+.IP a.
+RCS provides a complete change history.
+.IP b.
+RCS records who did what when to which revision of which module.
+.RE
+.IP 11.
+RCS is fully compatible with existing software development tools.
+RCS is unobtrusive -- its interface to the file system is such that
+all your existing software tools can be used as before.
+.IP 12.
+RCS' basic user interface is extremely simple. The novice need to learn
+only two commands. Its more sophisticated features have been
+tuned towards advanced software development environments and the
+experienced software professional.
+.IP 13.
+RCS simplifies software distribution if customers
+maintain sources with RCS also. This technique assures proper
+identification of versions and configurations, and tracking of customer
+modifications. Customer modifications can be merged into distributed
+versions locally or by the development group.
+.IP 14.
+RCS needs little extra space for the revisions (only the differences).
+If intermediate revisions are deleted, the corresponding
+differences are compressed into the shortest possible form.
+.IP 15.
+RCS is implemented with reverse deltas. This means that
+the latest revision, which is the one that is accessed most often,
+is stored intact. All others are regenerated from the latest one
+by applying reverse deltas (backward differences). This
+results in fast access time for the revision needed most often.
diff --git a/gnu/usr.bin/rcs/ident/Makefile b/gnu/usr.bin/rcs/ident/Makefile
new file mode 100644
index 0000000..1a618e5
--- /dev/null
+++ b/gnu/usr.bin/rcs/ident/Makefile
@@ -0,0 +1,7 @@
+PROG= ident
+
+SRCS= ident.c
+LDADD= -L${.CURDIR}/../lib/obj -lrcs
+CFLAGS+= -I${.CURDIR}/../lib
+
+.include <bsd.prog.mk>
diff --git a/gnu/usr.bin/rcs/ident/ident.1 b/gnu/usr.bin/rcs/ident/ident.1
new file mode 100644
index 0000000..37c8eda
--- /dev/null
+++ b/gnu/usr.bin/rcs/ident/ident.1
@@ -0,0 +1,76 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+.ds iD \\$3 \\$4 \\$5 \\$6 \\$7
+..
+.Id $Id: ident.1,v 5.0 1990/08/22 09:09:36 eggert Exp $
+.ds r \s-1RCS\s0
+.if n .ds - \%--
+.if t .ds - \(em
+.TH IDENT 1 \*(Dt GNU
+.SH NAME
+ident \- identify files
+.SH SYNOPSIS
+.B ident
+[
+.B \-q
+] [
+.I file
+\&.\|.\|. ]
+.SH DESCRIPTION
+.B ident
+searches for all occurrences of the pattern
+.BI $ keyword : .\|.\|. $
+in the named files or, if no file name appears, the standard input.
+.PP
+These patterns are normally inserted automatically by the \*r command
+.BR co (1),
+but can also be inserted manually.
+The option
+.B \-q
+suppresses
+the warning given if there are no patterns in a file.
+.PP
+.B ident
+works on text files as well as object files and dumps.
+For example, if the C program in
+.B f.c
+contains
+.IP
+\f3char rcsid[] = \&"$\&Id: f.c,v \*(iD $\&";\fP
+.LP
+and
+.B f.c
+is compiled into
+.BR f.o ,
+then the command
+.IP
+.B "ident f.c f.o"
+.LP
+will output
+.nf
+.IP
+.ft 3
+f.c:
+ $\&Id: f.c,v \*(iD $
+f.o:
+ $\&Id: f.c,v \*(iD $
+.ft
+.fi
+.SH IDENTIFICATION
+Author: Walter F. Tichy.
+.br
+Revision Number: \*(Rv; Release Date: \*(Dt.
+.br
+Copyright \(co 1982, 1988, 1989 by Walter F. Tichy.
+.br
+Copyright \(co 1990 by Paul Eggert.
+.SH "SEE ALSO"
+ci(1), co(1), rcs(1), rcsdiff(1), rcsintro(1), rcsmerge(1), rlog(1),
+rcsfile(5)
+.br
+Walter F. Tichy,
+\*r\*-A System for Version Control,
+.I "Software\*-Practice & Experience"
+.BR 15 ,
+7 (July 1985), 637-654.
diff --git a/gnu/usr.bin/rcs/ident/ident.c b/gnu/usr.bin/rcs/ident/ident.c
new file mode 100644
index 0000000..a2cc018
--- /dev/null
+++ b/gnu/usr.bin/rcs/ident/ident.c
@@ -0,0 +1,214 @@
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+/*
+ * RCS identification operation
+ */
+
+/* $Log: ident.c,v $
+ * Revision 5.3 1991/09/10 22:15:46 eggert
+ * Open files with FOPEN_R, not FOPEN_R_WORK,
+ * because they might be executables, not working files.
+ *
+ * Revision 5.2 1991/08/19 03:13:55 eggert
+ * Report read errors immediately.
+ *
+ * Revision 5.1 1991/02/25 07:12:37 eggert
+ * Don't report empty keywords. Check for I/O errors.
+ *
+ * Revision 5.0 1990/08/22 08:12:37 eggert
+ * Don't limit output to known keywords.
+ * Remove arbitrary limits and lint. Ansify and Posixate.
+ *
+ * Revision 4.5 89/05/01 15:11:54 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.4 87/10/23 17:09:57 narten
+ * added exit(0) so exit return code would be non random
+ *
+ * Revision 4.3 87/10/18 10:23:55 narten
+ * Updating version numbers. Changes relative to 1.1 are actually relative
+ * to 4.1
+ *
+ * Revision 1.3 87/07/09 09:20:52 trinkle
+ * Added check to make sure there is at least one arg before comparing argv[1]
+ * with "-q". This necessary on machines that don't allow dereferncing null
+ * pointers (i.e. Suns).
+ *
+ * Revision 1.2 87/03/27 14:21:47 jenkins
+ * Port to suns
+ *
+ * Revision 4.1 83/05/10 16:31:02 wft
+ * Added option -q and input from reading stdin.
+ * Marker matching is now done with trymatch() (independent of keywords).
+ *
+ * Revision 3.4 83/02/18 17:37:49 wft
+ * removed printing of new line after last file.
+ *
+ * Revision 3.3 82/12/04 12:48:55 wft
+ * Added LOCKER.
+ *
+ * Revision 3.2 82/11/28 18:24:17 wft
+ * removed Suffix; added ungetc to avoid skipping over trailing KDELIM.
+ *
+ * Revision 3.1 82/10/13 15:58:51 wft
+ * fixed type of variables receiving from getc() (char-->int).
+*/
+
+#include "rcsbase.h"
+
+static int match P((FILE*));
+static void scanfile P((FILE*,char const*,int));
+
+mainProg(identId, "ident", "$Id: ident.c,v 5.3 1991/09/10 22:15:46 eggert Exp $")
+/* Ident searches the named files for all occurrences
+ * of the pattern $keyword:...$, where the keywords are
+ * Author, Date, Header, Id, Log, RCSfile, Revision, Source, and State.
+ */
+
+{
+ FILE *fp;
+ int quiet;
+ int status = EXIT_SUCCESS;
+
+ if ((quiet = argc > 1 && strcmp("-q",argv[1])==0)) {
+ argc--; argv++;
+ }
+
+ if (argc<2)
+ scanfile(stdin, (char*)0, quiet);
+
+ while ( --argc > 0 ) {
+ if (!(fp = fopen(*++argv, FOPEN_R))) {
+ VOID fprintf(stderr, "%s error: can't open %s\n", cmdid, *argv);
+ status = EXIT_FAILURE;
+ } else {
+ scanfile(fp, *argv, quiet);
+ if (argc>1) VOID putchar('\n');
+ }
+ }
+ if (ferror(stdout) || fclose(stdout)!=0) {
+ VOID fprintf(stderr, "%s error: write error\n", cmdid);
+ status = EXIT_FAILURE;
+ }
+ exitmain(status);
+}
+
+#if lint
+ exiting void identExit() { _exit(EXIT_FAILURE); }
+#endif
+
+
+ static void
+scanfile(file, name, quiet)
+ register FILE *file;
+ char const *name;
+ int quiet;
+/* Function: scan an open file with descriptor file for keywords.
+ * Return false if there's a read error.
+ */
+{
+ register int c;
+
+ if (name)
+ VOID printf("%s:\n", name);
+ else
+ name = "input";
+ c = 0;
+ for (;;) {
+ if (c < 0) {
+ if (feof(file))
+ break;
+ if (ferror(file))
+ goto read_error;
+ }
+ if (c == KDELIM) {
+ if ((c = match(file)))
+ continue;
+ quiet = true;
+ }
+ c = getc(file);
+ }
+ if (!quiet)
+ VOID fprintf(stderr, "%s warning: no id keywords in %s\n", cmdid, name);
+ if (fclose(file) == 0)
+ return;
+
+ read_error:
+ VOID fprintf(stderr, "%s error: %s: read error\n", cmdid, name);
+ exit(EXIT_FAILURE);
+}
+
+
+
+ static int
+match(fp) /* group substring between two KDELIM's; then do pattern match */
+ register FILE *fp;
+{
+ char line[BUFSIZ];
+ register int c;
+ register char * tp;
+
+ tp = line;
+ while ((c = getc(fp)) != VDELIM) {
+ if (c < 0)
+ return c;
+ switch (ctab[c]) {
+ case LETTER: case Letter:
+ *tp++ = c;
+ if (tp < line+sizeof(line)-4)
+ break;
+ /* fall into */
+ default:
+ return c ? c : '\n'/* anything but 0 or KDELIM or EOF */;
+ }
+ }
+ if (tp == line)
+ return c;
+ *tp++ = c;
+ if ((c = getc(fp)) != ' ')
+ return c ? c : '\n';
+ *tp++ = c;
+ while( (c = getc(fp)) != KDELIM ) {
+ if (c < 0 && feof(fp) | ferror(fp))
+ return c;
+ switch (ctab[c]) {
+ default:
+ *tp++ = c;
+ if (tp < line+sizeof(line)-2)
+ break;
+ /* fall into */
+ case NEWLN: case UNKN:
+ return c ? c : '\n';
+ }
+ }
+ if (tp[-1] != ' ')
+ return c;
+ *tp++ = c; /*append trailing KDELIM*/
+ *tp = '\0';
+ VOID fprintf(stdout, " %c%s\n", KDELIM, line);
+ return 0;
+}
diff --git a/gnu/usr.bin/rcs/lib/Makefile b/gnu/usr.bin/rcs/lib/Makefile
new file mode 100644
index 0000000..b198e9e
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/Makefile
@@ -0,0 +1,5 @@
+LIB= rcs
+SRCS= maketime.c partime.c rcsedit.c rcsfcmp.c rcsfnms.c rcsgen.c rcskeep.c \
+ rcskeys.c rcslex.c rcsmap.c rcsrev.c rcssyn.c rcsutil.c merger.c
+
+.include <bsd.lib.mk>
diff --git a/gnu/usr.bin/rcs/lib/conf.h b/gnu/usr.bin/rcs/lib/conf.h
new file mode 100644
index 0000000..d29e511
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/conf.h
@@ -0,0 +1,495 @@
+/* RCS compile-time configuration */
+
+ /* $Id: conf.sh,v 5.14 1991/11/20 18:21:10 eggert Exp $ */
+
+/*
+ * This file is generated automatically.
+ * If you edit it by hand your changes may be lost.
+ * Instead, please try to fix conf.sh,
+ * and send your fixes to rcs-bugs@cs.purdue.edu.
+ */
+
+#define exitmain(n) return n /* how to exit from main() */
+/* #define _POSIX_SOURCE */ /* Define this if Posix + strict Standard C. */
+
+#include <errno.h>
+#include <stdio.h>
+#include <time.h>
+
+/* Comment out #include lines below that do not work. */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <utime.h>
+/* #include <vfork.h> */
+
+/* Define the following symbols to be 1 or 0. */
+#define has_sys_dir_h 1 /* Does #include <sys/dir.h> work? */
+#define has_sys_param_h 1 /* Does #include <sys/param.h> work? */
+#define has_readlink 1 /* Does readlink() work? */
+
+/* #undef NAME_MAX */ /* Uncomment this if NAME_MAX is broken. */
+
+#if !defined(NAME_MAX) && !defined(_POSIX_NAME_MAX)
+# if has_sys_dir_h
+# include <sys/dir.h>
+# endif
+# ifndef NAME_MAX
+# ifndef MAXNAMLEN
+# define MAXNAMLEN 14
+# endif
+# define NAME_MAX MAXNAMLEN
+# endif
+#endif
+#if !defined(PATH_MAX) && !defined(_POSIX_PATH_MAX)
+# if has_sys_param_h
+# include <sys/param.h>
+# define included_sys_param_h 1
+# endif
+# ifndef PATH_MAX
+# ifndef MAXPATHLEN
+# define MAXPATHLEN 1024
+# endif
+# define PATH_MAX (MAXPATHLEN-1)
+# endif
+#endif
+#if has_readlink && !defined(MAXSYMLINKS)
+# if has_sys_param_h && !included_sys_param_h
+# include <sys/param.h>
+# endif
+# ifndef MAXSYMLINKS
+# define MAXSYMLINKS 20 /* BSD; not standard yet */
+# endif
+#endif
+
+/* Comment out the keyword definitions below if the keywords work. */
+/* #define const */
+/* #define volatile */
+
+/* Comment out the typedefs below if the types are already declared. */
+/* Fix any uncommented typedefs that are wrong. */
+/* typedef int mode_t; */
+/* typedef int pid_t; */
+typedef int sig_atomic_t;
+/* typedef unsigned size_t; */
+/* typedef int ssize_t; */
+/* typedef long time_t; */
+/* typedef int uid_t; */
+
+/* Define the following symbols to be 1 or 0. */
+#define has_prototypes 1 /* Do function prototypes work? */
+#define has_stdarg 1 /* Does <stdarg.h> work? */
+#define has_varargs 0 /* Does <varargs.h> work? */
+#define va_start_args 2 /* How many args does va_start() take? */
+#if has_prototypes
+# define P(params) params
+#else
+# define P(params) ()
+#endif
+#if has_stdarg
+# include <stdarg.h>
+#else
+# if has_varargs
+# include <varargs.h>
+# else
+ typedef char *va_list;
+# define va_dcl int va_alist;
+# define va_start(ap) ((ap) = (va_list)&va_alist)
+# define va_arg(ap,t) (((t*) ((ap)+=sizeof(t))) [-1])
+# define va_end(ap)
+# endif
+#endif
+#if va_start_args == 2
+# define vararg_start va_start
+#else
+# define vararg_start(ap,p) va_start(ap)
+#endif
+
+#define text_equals_binary_stdio 1 /* Does stdio treat text like binary? */
+#define text_work_stdio 0 /* Text i/o for working file, binary for RCS file? */
+#if text_equals_binary_stdio
+ /* Text and binary i/o behave the same, or binary i/o does not work. */
+# define FOPEN_R "r"
+# define FOPEN_W "w"
+# define FOPEN_WPLUS "w+"
+#else
+ /* Text and binary i/o behave differently. */
+ /* This is incompatible with Posix and Unix. */
+# define FOPEN_R "rb"
+# define FOPEN_W "wb"
+# define FOPEN_WPLUS "w+b"
+#endif
+#if text_work_stdio
+# define FOPEN_R_WORK "r"
+# define FOPEN_W_WORK "w"
+# define FOPEN_WPLUS_WORK "w+"
+#else
+# define FOPEN_R_WORK FOPEN_R
+# define FOPEN_W_WORK FOPEN_W
+# define FOPEN_WPLUS_WORK FOPEN_WPLUS
+#endif
+
+/* Define or comment out the following symbols as needed. */
+#define bad_fopen_wplus 0 /* Does fopen(f,FOPEN_WPLUS) fail to truncate f? */
+#define getlogin_is_secure 0 /* Is getlogin() secure? Usually it's not. */
+#define has_dirent 1 /* Do opendir(), readdir(), closedir() work? */
+#define has_fchmod 0 /* Does fchmod() work? */
+#define has_fputs 0 /* Does fputs() work? */
+#define has_ftruncate 1 /* Does ftruncate() work? */
+#define has_getuid 1 /* Does getuid() work? */
+#define has_getpwuid 1 /* Does getpwuid() work? */
+#define has_link 1 /* Does link() work? */
+#define has_memcmp 1 /* Does memcmp() work? */
+#define has_memcpy 1 /* Does memcpy() work? */
+#define has_memmove 1 /* Does memmove() work? */
+#define has_madvise 0 /* Does madvise() work? */
+#define has_mmap 0 /* Does mmap() work on regular files? */
+#define has_rename 1 /* Does rename() work? */
+#define bad_a_rename 0 /* Does rename(A,B) fail if A is unwritable? */
+#define bad_b_rename 0 /* Does rename(A,B) fail if B is unwritable? */
+#define VOID (void) /* 'VOID e;' discards the value of an expression 'e'. */
+#define has_seteuid 0 /* Does seteuid() work? See README. */
+#define has_setuid 1 /* Does setuid() exist? */
+#define has_signal 1 /* Does signal() work? */
+#define signal_args P((int)) /* arguments of signal handlers */
+#define signal_type void /* type returned by signal handlers */
+#define sig_zaps_handler 0 /* Must a signal handler reinvoke signal()? */
+#define has_sigaction 1 /* Does struct sigaction work? */
+/* #define has_sigblock ? */ /* Does sigblock() work? */
+/* #define sigmask(s) (1 << ((s)-1)) */ /* Yield mask for signal number. */
+#define has_sys_siglist 0 /* Does sys_siglist[] work? */
+typedef ssize_t fread_type; /* type returned by fread() and fwrite() */
+typedef size_t freadarg_type; /* type of their size arguments */
+typedef void *malloc_type; /* type returned by malloc() */
+#define has_getcwd 1 /* Does getcwd() work? */
+/* #define has_getwd ? */ /* Does getwd() work? */
+#define has_mktemp 1 /* Does mktemp() work? */
+#define has_NFS 1 /* Might NFS be used? */
+/* #define strchr index */ /* Use old-fashioned name for strchr()? */
+/* #define strrchr rindex */ /* Use old-fashioned name for strrchr()? */
+#define bad_unlink 0 /* Does unlink() fail on unwritable files? */
+#define has_vfork 0 /* Does vfork() work? */
+#define has_fork 1 /* Does fork() work? */
+#define has_spawn 0 /* Does spawn*() work? */
+#define has_wait 1 /* Does wait() work? */
+#define has_waitpid 0 /* Does waitpid() work? */
+#define RCS_SHELL "/bin/sh" /* shell to run RCS subprograms */
+#define has_vfprintf 1 /* Does vfprintf() work? */
+/* #define has__doprintf ? */ /* Does _doprintf() work? */
+/* #define has__doprnt ? */ /* Does _doprnt() work? */
+/* #undef EXIT_FAILURE */ /* Uncomment this if EXIT_FAILURE is broken. */
+#define large_memory 0 /* Can main memory hold entire RCS files? */
+/* #undef ULONG_MAX */ /* Uncomment this if ULONG_MAX is broken (e.g. < 0). */
+/* struct utimbuf { time_t actime, modtime; }; */ /* Uncomment this if needed. */
+#define CO "/usr/bin/co" /* name of 'co' program */
+#define COMPAT2 0 /* Are version 2 files supported? */
+#define DATEFORM "%.2d.%.2d.%.2d.%.2d.%.2d.%.2d" /* e.g. 01.01.01.01.01.01 */
+#define DIFF "/usr/bin/diff" /* name of 'diff' program */
+#define DIFF3 "/usr/bin/diff3" /* name of 'diff3' program */
+#define DIFF3_BIN 1 /* Is diff3 user-visible (not the /usr/lib auxiliary)? */
+#define DIFF_FLAGS , "-an" /* Make diff output suitable for RCS. */
+#define DIFF_L 1 /* Does diff -L work? */
+#define DIFF_SUCCESS 0 /* DIFF status if no differences are found */
+#define DIFF_FAILURE 1 /* DIFF status if differences are found */
+#define DIFF_TROUBLE 2 /* DIFF status if trouble */
+#define ED "/bin/ed" /* name of 'ed' program (used only if !DIFF3_BIN) */
+#define MERGE "/usr/bin/merge" /* name of 'merge' program */
+#define TMPDIR "/tmp" /* default directory for temporary files */
+#define SLASH '/' /* principal pathname separator */
+#define SLASHes '/' /* `case SLASHes:' labels all pathname separators */
+#define isSLASH(c) ((c) == SLASH) /* Is arg a pathname separator? */
+#define ROOTPATH(p) isSLASH((p)[0]) /* Is p an absolute pathname? */
+#define X_DEFAULT ",v/" /* default value for -x option */
+#define DIFF_ABSOLUTE 1 /* Is ROOTPATH(DIFF) true? */
+#define ALL_ABSOLUTE 1 /* Are all subprograms absolute pathnames? */
+#define SENDMAIL "/usr/bin/mail" /* how to send mail */
+#define TZ_must_be_set 0 /* Must TZ be set for gmtime() to work? */
+
+
+
+/* Adjust the following declarations as needed. */
+
+
+#if __GNUC__ && !__STRICT_ANSI__
+# define exiting volatile /* GCC extension: function cannot return */
+#else
+# define exiting
+#endif
+
+#if has_ftruncate
+ int ftruncate P((int,off_t));
+#endif
+
+/* <sys/mman.h> */
+#if has_madvise
+ int madvise P((caddr_t,size_t,int));
+#endif
+#if has_mmap
+ caddr_t mmap P((caddr_t,size_t,int,int,int,off_t));
+ int munmap P((caddr_t,size_t));
+#endif
+
+
+/* Posix (ISO/IEC 9945-1: 1990 / IEEE Std 1003.1-1990) */
+/* These definitions are for the benefit of non-Posix hosts, and */
+/* Posix hosts that have Standard C compilers but traditional include files. */
+/* Unfortunately, mixed-up hosts are all too common. */
+
+/* <fcntl.h> */
+#ifdef F_DUPFD
+ int fcntl P((int,int,...));
+#else
+ int dup2 P((int,int));
+#endif
+#ifndef O_BINARY /* some non-Posix hosts need O_BINARY */
+# define O_BINARY 0 /* no effect on Posix */
+#endif
+#ifdef O_CREAT
+# define open_can_creat 1
+#else
+# define open_can_creat 0
+# define O_RDONLY 0
+# define O_WRONLY 1
+# define O_RDWR 2
+# define O_CREAT 01000
+# define O_TRUNC 02000
+ int creat P((char const*,mode_t));
+#endif
+#ifndef O_EXCL
+# define O_EXCL 0
+#endif
+
+/* <pwd.h> */
+#if has_getpwuid
+ struct passwd *getpwuid P((uid_t));
+#endif
+
+/* <signal.h> */
+#if has_sigaction
+ int sigaction P((int,struct sigaction const*,struct sigaction*));
+ int sigaddset P((sigset_t*,int));
+ int sigemptyset P((sigset_t*));
+#else
+#if has_sigblock
+ /* BSD */
+ int sigblock P((int));
+ int sigmask P((int));
+ int sigsetmask P((int));
+#endif
+#endif
+
+/* <stdio.h> */
+FILE *fdopen P((int,char const*));
+int fileno P((FILE*));
+
+/* <sys/stat.h> */
+int chmod P((char const*,mode_t));
+int fstat P((int,struct stat*));
+int stat P((char const*,struct stat*));
+mode_t umask P((mode_t));
+#if has_fchmod
+ int fchmod P((int,mode_t));
+#endif
+#ifndef S_IRUSR
+# ifdef S_IREAD
+# define S_IRUSR S_IREAD
+# else
+# define S_IRUSR 0400
+# endif
+# ifdef S_IWRITE
+# define S_IWUSR S_IWRITE
+# else
+# define S_IWUSR (S_IRUSR/2)
+# endif
+#endif
+#ifndef S_IRGRP
+# if has_getuid
+# define S_IRGRP (S_IRUSR / 0010)
+# define S_IWGRP (S_IWUSR / 0010)
+# define S_IROTH (S_IRUSR / 0100)
+# define S_IWOTH (S_IWUSR / 0100)
+# else
+ /* single user OS -- not Posix or Unix */
+# define S_IRGRP 0
+# define S_IWGRP 0
+# define S_IROTH 0
+# define S_IWOTH 0
+# endif
+#endif
+#ifndef S_ISREG
+# define S_ISREG(n) (((n) & S_IFMT) == S_IFREG)
+#endif
+
+/* <sys/wait.h> */
+#if has_wait
+ pid_t wait P((int*));
+#endif
+#ifndef WEXITSTATUS
+# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
+# undef WIFEXITED /* Avoid 4.3BSD incompatibility with Posix. */
+#endif
+#ifndef WIFEXITED
+# define WIFEXITED(stat_val) (!((stat_val) & 255))
+#endif
+
+/* <unistd.h> */
+char *getlogin P((void));
+int close P((int));
+int isatty P((int));
+int link P((char const*,char const*));
+int open P((char const*,int,...));
+int unlink P((char const*));
+int _filbuf P((FILE*)); /* keeps lint quiet in traditional C */
+int _flsbuf P((int,FILE*)); /* keeps lint quiet in traditional C */
+long pathconf P((char const*,int));
+ssize_t write P((int,void const*,size_t));
+#ifndef STDIN_FILENO
+# define STDIN_FILENO 0
+# define STDOUT_FILENO 1
+# define STDERR_FILENO 2
+#endif
+#if has_fork
+# if !has_vfork
+# undef vfork
+# define vfork fork
+# endif
+ pid_t vfork P((void)); /* vfork is nonstandard but faster */
+#endif
+#if has_getcwd || !has_getwd
+ char *getcwd P((char*,size_t));
+#else
+ char *getwd P((char*));
+#endif
+#if has_getuid
+ uid_t getuid P((void));
+#endif
+#if has_readlink
+/* ssize_t readlink P((char const*,char*,size_t)); *//* BSD; not standard yet */
+#endif
+#if has_setuid
+# if !has_seteuid
+# undef seteuid
+# define seteuid setuid
+# endif
+ int seteuid P((uid_t));
+ uid_t geteuid P((void));
+#endif
+#if has_spawn
+ int spawnv P((int,char const*,char*const*));
+# if ALL_ABSOLUTE
+# define spawn_RCS spawnv
+# else
+# define spawn_RCS spawnvp
+ int spawnvp P((int,char const*,char*const*));
+# endif
+#else
+ int execv P((char const*,char*const*));
+# if ALL_ABSOLUTE
+# define exec_RCS execv
+# else
+# define exec_RCS execvp
+ int execvp P((char const*,char*const*));
+# endif
+#endif
+
+/* utime.h */
+int utime P((char const*,struct utimbuf const*));
+
+
+/* Standard C library */
+/* These definitions are for the benefit of hosts that have */
+/* traditional C include files, possibly with Standard C compilers. */
+/* Unfortunately, mixed-up hosts are all too common. */
+
+/* <errno.h> */
+extern int errno;
+
+/* <limits.h> */
+#ifndef ULONG_MAX
+ /* This does not work in #ifs, but it's good enough for us. */
+# define ULONG_MAX ((unsigned long)-1)
+#endif
+
+/* <signal.h> */
+#if has_signal
+ signal_type (*signal P((int,signal_type(*)signal_args)))signal_args;
+#endif
+
+/* <stdio.h> */
+FILE *fopen P((char const*,char const*));
+fread_type fread P((void*,freadarg_type,freadarg_type,FILE*));
+fread_type fwrite P((void const*,freadarg_type,freadarg_type,FILE*));
+int fclose P((FILE*));
+int feof P((FILE*));
+int ferror P((FILE*));
+int fflush P((FILE*));
+int fprintf P((FILE*,char const*,...));
+int fputs P((char const*,FILE*));
+int fseek P((FILE*,long,int));
+int printf P((char const*,...));
+int rename P((char const*,char const*));
+int sprintf P((char*,char const*,...));
+/* long ftell P((FILE*)); */
+void clearerr P((FILE*));
+void perror P((char const*));
+#ifndef L_tmpnam
+# define L_tmpnam 32 /* power of 2 > sizeof("/usr/tmp/xxxxxxxxxxxxxxx") */
+#endif
+#ifndef SEEK_SET
+# define SEEK_SET 0
+#endif
+#if has_mktemp
+ char *mktemp P((char*)); /* traditional */
+#else
+ char *tmpnam P((char*));
+#endif
+#if has_vfprintf
+ int vfprintf P((FILE*,char const*,va_list));
+#else
+#if has__doprintf
+ void _doprintf P((FILE*,char const*,va_list)); /* Minix */
+#else
+ void _doprnt P((char const*,va_list,FILE*)); /* BSD */
+#endif
+#endif
+
+/* <stdlib.h> */
+char *getenv P((char const*));
+exiting void _exit P((int));
+exiting void exit P((int));
+malloc_type malloc P((size_t));
+malloc_type realloc P((malloc_type,size_t));
+void free P((malloc_type));
+#ifndef EXIT_FAILURE
+# define EXIT_FAILURE 1
+#endif
+#ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+#endif
+#if !has_fork && !has_spawn
+ int system P((char const*));
+#endif
+
+/* <string.h> */
+char *strcpy P((char*,char const*));
+char *strchr P((char const*,int));
+char *strrchr P((char const*,int));
+int memcmp P((void const*,void const*,size_t));
+int strcmp P((char const*,char const*));
+size_t strlen P((char const*));
+void *memcpy P((void*,void const*,size_t));
+#if has_memmove
+ void *memmove P((void*,void const*,size_t));
+#endif
+
+/* <time.h> */
+time_t time P((time_t*));
diff --git a/gnu/usr.bin/rcs/lib/maketime.c b/gnu/usr.bin/rcs/lib/maketime.c
new file mode 100644
index 0000000..c95c9f0
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/maketime.c
@@ -0,0 +1,344 @@
+#
+/*
+ * MAKETIME derive 32-bit time value from TM structure.
+ *
+ * Usage:
+ * int zone; Minutes west of GMT, or
+ * 48*60 for localtime
+ * time_t t;
+ * struct tm *tp; Pointer to TM structure from <time.h>
+ * t = maketime(tp,zone);
+ *
+ * Returns:
+ * -1 if failure; parameter out of range or nonsensical.
+ * else time-value.
+ * Notes:
+ * This code is quasi-public; it may be used freely in like software.
+ * It is not to be sold, nor used in licensed software without
+ * permission of the author.
+ * For everyone's benefit, please report bugs and improvements!
+ * Copyright 1981 by Ken Harrenstien, SRI International.
+ * (ARPANET: KLH @ SRI)
+ */
+/* $Log: maketime.c,v $
+ * Revision 5.3 1991/08/19 03:13:55 eggert
+ * Add setfiledate, str2time, TZ_must_be_set.
+ *
+ * Revision 5.2 1990/11/01 05:03:30 eggert
+ * Remove lint.
+ *
+ * Revision 5.1 1990/10/04 06:30:13 eggert
+ * Calculate the GMT offset of 'xxx LT' as of xxx, not as of now.
+ * Don't assume time_t is 32 bits. Fix bugs near epoch and near end of time.
+ *
+ * Revision 5.0 1990/08/22 08:12:38 eggert
+ * Switch to GMT and fix the bugs exposed thereby.
+ * Permit dates past 1999/12/31. Ansify and Posixate.
+ *
+ * Revision 1.8 88/11/08 13:54:53 narten
+ * allow negative timezones (-24h <= x <= 24h)
+ *
+ * Revision 1.7 88/08/28 14:47:52 eggert
+ * Allow cc -R. Remove unportable "#endif XXX"s.
+ *
+ * Revision 1.6 87/12/18 17:05:58 narten
+ * include rcsparam.h
+ *
+ * Revision 1.5 87/12/18 11:35:51 narten
+ * maketime.c: fixed USG code - you have tgo call "tzset" in order to have
+ * "timezone" set. ("localtime" calls it, but it's probably better not to
+ * count on "localtime" having been called.)
+ *
+ * Revision 1.4 87/10/18 10:26:57 narten
+ * Updating version numbers. Changes relative to 1.0 are actually
+ * relative to 1.2
+ *
+ * Revision 1.3 87/09/24 13:58:45 narten
+ * Sources now pass through lint (if you ignore printf/sprintf/fprintf
+ * warnings)
+ *
+ * Revision 1.2 87/03/27 14:21:48 jenkins
+ * Port to suns
+ *
+ * Revision 1.2 83/12/05 10:12:56 wft
+ * added cond. compilation for USG Unix; long timezone;
+ *
+ * Revision 1.1 82/05/06 11:38:00 wft
+ * Initial revision
+ *
+ */
+
+
+#include "rcsbase.h"
+
+libId(maketId, "$Id: maketime.c,v 5.3 1991/08/19 03:13:55 eggert Exp $")
+
+static struct tm const *time2tm P((time_t));
+
+#define given(v) (0 <= (v)) /* Negative values are unspecified. */
+
+static int const daytb[] = {
+ /* # days in year thus far, indexed by month (0-12!!) */
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
+};
+
+ static time_t
+maketime(atm,zone)
+ struct tm const *atm;
+ int zone;
+{
+ register struct tm const *tp;
+ register int i;
+ int year, yday, mon, day, hour, min, sec, leap, localzone;
+ int attempts;
+ time_t t, tres;
+
+ attempts = 2;
+ localzone = zone==48*60;
+ tres = -1;
+ year = mon = day = 0; /* Keep lint happy. */
+
+ do {
+
+ if (localzone || !given(atm->tm_year)) {
+ if (tres == -1)
+ if ((tres = time((time_t*)0)) == -1)
+ return -1;
+ tp = time2tm(tres);
+ /* Get breakdowns of default time, adjusting to zone. */
+ year = tp->tm_year; /* Use to set up defaults */
+ yday = tp->tm_yday;
+ mon = tp->tm_mon;
+ day = tp->tm_mday;
+ hour = tp->tm_hour;
+ min = tp->tm_min;
+ if (localzone) {
+ tp = localtime(&tres);
+ zone =
+ min - tp->tm_min + 60*(
+ hour - tp->tm_hour + 24*(
+ /* If years differ, it's by one day. */
+ year - tp->tm_year
+ ? year - tp->tm_year
+ : yday - tp->tm_yday));
+ }
+ /* Adjust the default day, month and year according to zone. */
+ if ((min -= zone) < 0) {
+ if (hour-(59-min)/60 < 0 && --day <= 0) {
+ if (--mon < 0) {
+ --year;
+ mon = 11;
+ }
+ day = daytb[mon+1] - daytb[mon] + (mon==1&&!(year&3));
+ }
+ } else
+ if (
+ 24 <= hour+min/60 &&
+ daytb[mon+1] - daytb[mon] + (mon==1&&!(year&3)) < ++day
+ ) {
+ if (11 < ++mon) {
+ ++year;
+ mon = 0;
+ }
+ day = 1;
+ }
+ }
+ if (zone < -24*60 || 24*60 < zone)
+ return -1;
+
+
+#ifdef DEBUG
+printf("first YMD: %d %d %d\n",year,mon,day);
+#endif
+ tp = atm;
+
+ /* First must find date, using specified year, month, day.
+ * If one of these is unspecified, it defaults either to the
+ * current date (if no more global spec was given) or to the
+ * zero-value for that spec (i.e. a more global spec was seen).
+ * Reject times that do not fit in time_t,
+ * without assuming that time_t is 32 bits or is signed.
+ */
+ if (given(tp->tm_year))
+ {
+ year = tp->tm_year;
+ mon = 0; /* Since year was given, default */
+ day = 1; /* for remaining specs is zero */
+ }
+ if (year < 69) /* 1969/12/31 OK in some timezones. */
+ return -1; /* ERR: year out of range */
+ leap = !(year&3) && (year%100 || !((year+300)%400));
+ year -= 70; /* UNIX time starts at 1970 */
+
+ /*
+ * Find day of year.
+ */
+ {
+ if (given(tp->tm_mon))
+ { mon = tp->tm_mon; /* Month was specified */
+ day = 1; /* so set remaining default */
+ }
+ if (11 < (unsigned)mon)
+ return -1; /* ERR: bad month */
+ if (given(tp->tm_mday)) day = tp->tm_mday;
+ if(day < 1
+ || (((daytb[mon+1]-daytb[mon]) < day)
+ && (day!=29 || mon!=1 || !leap) ))
+ return -1; /* ERR: bad day */
+ yday = daytb[mon] /* Add # of days in months so far */
+ + ((leap /* Leap year, and past Feb? If */
+ && mon>1)? 1:0) /* so, add leap day for this year */
+ + day-1; /* And finally add # days this mon */
+
+ }
+ if (leap+365 <= (unsigned)yday)
+ return -1; /* ERR: bad YDAY */
+
+ if (year < 0) {
+ if (yday != 364)
+ return -1; /* ERR: too early */
+ t = -1;
+ } else {
+ tres = year*365; /* Get # days of years so far */
+ if (tres/365 != year)
+ return -1; /* ERR: overflow */
+ t = tres
+ + ((year+1)>>2) /* plus # of leap days since 1970 */
+ + yday; /* and finally add # days this year */
+ if (t+4 < tres)
+ return -1; /* ERR: overflow */
+ }
+ tres = t;
+
+ if (given(i = tp->tm_wday)) /* Check WDAY if present */
+ if (i != (tres+4)%7) /* 1970/01/01 was Thu = 4 */
+ return -1; /* ERR: bad WDAY */
+
+#ifdef DEBUG
+printf("YMD: %d %d %d, T=%ld\n",year,mon,day,tres);
+#endif
+ /*
+ * Now determine time. If not given, default to zeros
+ * (since time is always the least global spec)
+ */
+ tres *= 86400L; /* Get # seconds (24*60*60) */
+ if (tres/86400L != t)
+ return -1; /* ERR: overflow */
+ hour = min = sec = 0;
+ if (given(tp->tm_hour)) hour = tp->tm_hour;
+ if (given(tp->tm_min )) min = tp->tm_min;
+ if (given(tp->tm_sec )) sec = tp->tm_sec;
+ if (60 <= (unsigned)min || 60 < (unsigned)sec)
+ return -1; /* ERR: MS out of range */
+ if (24 <= (unsigned)hour)
+ if(hour != 24 || (min+sec) !=0) /* Allow 24:00 */
+ return -1; /* ERR: H out of range */
+
+ t = tres;
+ tres += sec + 60L*(zone + min + 60*hour);
+
+#ifdef DEBUG
+printf("HMS: %d %d %d T=%ld\n",hour,min,sec,tres);
+#endif
+
+ if (!localzone) /* check for overflow */
+ return (year<0 ? (tres<0||86400L<=tres) : tres<t) ? -1 : tres;
+
+ /* Check results; LT may have had a different GMT offset back then. */
+ tp = localtime(&tres);
+ if (given(atm->tm_sec) && atm->tm_sec != tp->tm_sec)
+ return -1; /* If seconds don't match, we're in trouble. */
+ if (!(
+ given(atm->tm_min) && atm->tm_min != tp->tm_min ||
+ given(atm->tm_hour) && atm->tm_hour != tp->tm_hour ||
+ given(atm->tm_mday) && atm->tm_mday != tp->tm_mday ||
+ given(atm->tm_mon) && atm->tm_mon != tp->tm_mon ||
+ given(atm->tm_year) && atm->tm_year != tp->tm_year
+ ))
+ return tres; /* Everything matches. */
+
+ } while (--attempts);
+
+ return -1;
+}
+
+/*
+* Convert Unix time to struct tm format.
+* Use Coordinated Universal Time (UTC) if version 5 or newer;
+* use local time otherwise.
+*/
+ static struct tm const *
+time2tm(unixtime)
+ time_t unixtime;
+{
+ struct tm const *tm;
+# if TZ_must_be_set
+ static char const *TZ;
+ if (!TZ && !(TZ = getenv("TZ")))
+ faterror("TZ is not set");
+# endif
+ if (!(tm = (RCSversion<VERSION(5) ? localtime : gmtime)(&unixtime)))
+ faterror("UTC is not available; perhaps TZ is not set?");
+ return tm;
+}
+
+/*
+* Convert Unix time to RCS format.
+* For compatibility with older versions of RCS,
+* dates before AD 2000 are stored without the leading "19".
+*/
+ void
+time2date(unixtime,date)
+ time_t unixtime;
+ char date[datesize];
+{
+ register struct tm const *tm = time2tm(unixtime);
+ VOID sprintf(date, DATEFORM,
+ tm->tm_year + (tm->tm_year<100 ? 0 : 1900),
+ tm->tm_mon+1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec
+ );
+}
+
+
+
+ static time_t
+str2time(source)
+ char const *source;
+/* Parse a free-format date in SOURCE, yielding a Unix format time. */
+{
+ int zone;
+ time_t unixtime;
+ struct tm parseddate;
+
+ if (!partime(source, &parseddate, &zone))
+ faterror("can't parse date/time: %s", source);
+ if ((unixtime = maketime(&parseddate, zone)) == -1)
+ faterror("bad date/time: %s", source);
+ return unixtime;
+}
+
+ void
+str2date(source, target)
+ char const *source;
+ char target[datesize];
+/* Parse a free-format date in SOURCE, convert it
+ * into RCS internal format, and store the result into TARGET.
+ */
+{
+ time2date(str2time(source), target);
+}
+
+ int
+setfiledate(file, date)
+ char const *file, date[datesize];
+/* Set the access and modification time of FILE to DATE. */
+{
+ static struct utimbuf times; /* static so unused fields are zero */
+ char datebuf[datesize];
+
+ if (!date)
+ return 0;
+ times.actime = times.modtime = str2time(date2str(date, datebuf));
+ return utime(file, &times);
+}
diff --git a/gnu/usr.bin/rcs/lib/merger.c b/gnu/usr.bin/rcs/lib/merger.c
new file mode 100644
index 0000000..7162ffa
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/merger.c
@@ -0,0 +1,139 @@
+/* merger - three-way file merge internals */
+
+/* Copyright 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+#include "rcsbase.h"
+
+libId(mergerId, "$Id: merger.c,v 1.3 1991/08/20 23:05:00 eggert Exp $")
+
+ static char const *
+normalize_arg(s, b)
+ char const *s;
+ char **b;
+/*
+ * If S looks like an option, prepend ./ to it. Yield the result.
+ * Set *B to the address of any storage that was allocated..
+ */
+{
+ char *t;
+ switch (*s) {
+ case '-': case '+':
+ *b = t = testalloc(strlen(s) + 3);
+ VOID sprintf(t, ".%c%s", SLASH, s);
+ return t;
+ default:
+ *b = 0;
+ return s;
+ }
+}
+
+ int
+merge(tostdout, label, argv)
+ int tostdout;
+ char const *const label[2];
+ char const *const argv[3];
+/*
+ * Do `merge [-p] -L l0 -L l1 a0 a1 a2',
+ * where TOSTDOUT specifies whether -p is present,
+ * LABEL gives l0 and l1, and ARGV gives a0, a1, and a2.
+ * Yield DIFF_SUCCESS or DIFF_FAILURE.
+ */
+{
+ register int i;
+ FILE *f;
+ RILE *rt;
+ char const *a[3], *t;
+ char *b[3];
+ int s;
+#if !DIFF3_BIN
+ char const *d[2];
+#endif
+
+ for (i=3; 0<=--i; )
+ a[i] = normalize_arg(argv[i], &b[i]);
+
+#if DIFF3_BIN
+ t = 0;
+ if (!tostdout)
+ t = maketemp(0);
+ s = run(
+ (char*)0, t,
+ DIFF3, "-am", "-L", label[0], "-L", label[1],
+ a[0], a[1], a[2], (char*)0
+ );
+ switch (s) {
+ case DIFF_SUCCESS:
+ break;
+ case DIFF_FAILURE:
+ if (!quietflag)
+ warn("overlaps during merge");
+ break;
+ default:
+ exiterr();
+ }
+ if (t) {
+ if (!(f = fopen(argv[0], FOPEN_W)))
+ efaterror(argv[0]);
+ if (!(rt = Iopen(t, FOPEN_R, (struct stat*)0)))
+ efaterror(t);
+ fastcopy(rt, f);
+ Ifclose(rt);
+ Ofclose(f);
+ }
+#else
+ for (i=0; i<2; i++)
+ switch (run(
+ (char*)0, d[i]=maketemp(i),
+ DIFF, a[i], a[2], (char*)0
+ )) {
+ case DIFF_FAILURE: case DIFF_SUCCESS: break;
+ default: exiterr();
+ }
+ t = maketemp(2);
+ s = run(
+ (char*)0, t,
+ DIFF3, "-E", d[0], d[1], a[0], a[1], a[2],
+ label[0], label[1], (char*)0
+ );
+ if (s != DIFF_SUCCESS) {
+ s = DIFF_FAILURE;
+ if (!quietflag)
+ warn("overlaps or other problems during merge");
+ }
+ if (!(f = fopen(t, "a")))
+ efaterror(t);
+ aputs(tostdout ? "1,$p\n" : "w\n", f);
+ Ofclose(f);
+ if (run(t, (char*)0, ED, "-", a[0], (char*)0))
+ exiterr();
+#endif
+
+ tempunlink();
+ for (i=3; 0<=--i; )
+ if (b[i])
+ tfree(b[i]);
+ return s;
+}
diff --git a/gnu/usr.bin/rcs/lib/partime.c b/gnu/usr.bin/rcs/lib/partime.c
new file mode 100644
index 0000000..4751fc5
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/partime.c
@@ -0,0 +1,639 @@
+/*
+ * PARTIME parse date/time string into a TM structure
+ *
+ * Returns:
+ * 0 if parsing failed
+ * else time values in specified TM structure and zone (unspecified values
+ * set to TMNULL)
+ * Notes:
+ * This code is quasi-public; it may be used freely in like software.
+ * It is not to be sold, nor used in licensed software without
+ * permission of the author.
+ * For everyone's benefit, please report bugs and improvements!
+ * Copyright 1980 by Ken Harrenstien, SRI International.
+ * (ARPANET: KLH @ SRI)
+ */
+
+/* Hacknotes:
+ * If parsing changed so that no backup needed, could perhaps modify
+ * to use a FILE input stream. Need terminator, though.
+ * Perhaps should return 0 on success, else a non-zero error val?
+ */
+
+/* $Log: partime.c,v $
+ * Revision 5.6 1991/08/19 03:13:55 eggert
+ * Update timezones.
+ *
+ * Revision 5.5 1991/04/21 11:58:18 eggert
+ * Don't put , just before } in initializer.
+ *
+ * Revision 5.4 1990/10/04 06:30:15 eggert
+ * Remove date vs time heuristics that fail between 2000 and 2400.
+ * Check for overflow when lexing an integer.
+ * Parse 'Jan 10 LT' as 'Jan 10, LT', not 'Jan, 10 LT'.
+ *
+ * Revision 5.3 1990/09/24 18:56:31 eggert
+ * Update timezones.
+ *
+ * Revision 5.2 1990/09/04 08:02:16 eggert
+ * Don't parse two-digit years, because it won't work after 1999/12/31.
+ * Don't permit 'Aug Aug'.
+ *
+ * Revision 5.1 1990/08/29 07:13:49 eggert
+ * Be able to parse our own date format. Don't assume year<10000.
+ *
+ * Revision 5.0 1990/08/22 08:12:40 eggert
+ * Switch to GMT and fix the bugs exposed thereby. Update timezones.
+ * Ansify and Posixate. Fix peekahead and int-size bugs.
+ *
+ * Revision 1.4 89/05/01 14:48:46 narten
+ * fixed #ifdef DEBUG construct
+ *
+ * Revision 1.3 88/08/28 14:53:40 eggert
+ * Remove unportable "#endif XXX"s.
+ *
+ * Revision 1.2 87/03/27 14:21:53 jenkins
+ * Port to suns
+ *
+ * Revision 1.1 82/05/06 11:38:26 wft
+ * Initial revision
+ *
+ */
+
+#include "rcsbase.h"
+
+libId(partId, "$Id: partime.c,v 5.6 1991/08/19 03:13:55 eggert Exp $")
+
+#define given(v) (0 <= (v))
+#define TMNULL (-1) /* Items not given are given this value */
+#define TZ_OFFSET (24*60) /* TMNULL < zone_offset - TZ_OFFSET */
+
+struct tmwent {
+ char const *went;
+ short wval;
+ char wflgs;
+ char wtype;
+};
+ /* wflgs */
+#define TWTIME 02 /* Word is a time value (absence implies date) */
+#define TWDST 04 /* Word is a DST-type timezone */
+ /* wtype */
+#define TM_MON 1 /* month name */
+#define TM_WDAY 2 /* weekday name */
+#define TM_ZON 3 /* time zone name */
+#define TM_LT 4 /* local time */
+#define TM_DST 5 /* daylight savings time */
+#define TM_12 6 /* AM, PM, NOON, or MIDNIGHT */
+ /* wval (for wtype==TM_12) */
+#define T12_AM 1
+#define T12_PM 2
+#define T12_NOON 12
+#define T12_MIDNIGHT 0
+
+static struct tmwent const tmwords [] = {
+ {"january", 0, 0, TM_MON},
+ {"february", 1, 0, TM_MON},
+ {"march", 2, 0, TM_MON},
+ {"april", 3, 0, TM_MON},
+ {"may", 4, 0, TM_MON},
+ {"june", 5, 0, TM_MON},
+ {"july", 6, 0, TM_MON},
+ {"august", 7, 0, TM_MON},
+ {"september", 8, 0, TM_MON},
+ {"october", 9, 0, TM_MON},
+ {"november", 10, 0, TM_MON},
+ {"december", 11, 0, TM_MON},
+
+ {"sunday", 0, 0, TM_WDAY},
+ {"monday", 1, 0, TM_WDAY},
+ {"tuesday", 2, 0, TM_WDAY},
+ {"wednesday", 3, 0, TM_WDAY},
+ {"thursday", 4, 0, TM_WDAY},
+ {"friday", 5, 0, TM_WDAY},
+ {"saturday", 6, 0, TM_WDAY},
+
+ {"gmt", 0*60, TWTIME, TM_ZON}, /* Greenwich */
+ {"utc", 0*60, TWTIME, TM_ZON},
+ {"ut", 0*60, TWTIME, TM_ZON},
+ {"cut", 0*60, TWTIME, TM_ZON},
+
+ {"nzst", -12*60, TWTIME, TM_ZON}, /* New Zealand */
+ {"jst", -9*60, TWTIME, TM_ZON}, /* Japan */
+ {"kst", -9*60, TWTIME, TM_ZON}, /* Korea */
+ {"ist", -5*60-30, TWTIME, TM_ZON},/* India */
+ {"eet", -2*60, TWTIME, TM_ZON}, /* Eastern Europe */
+ {"cet", -1*60, TWTIME, TM_ZON}, /* Central Europe */
+ {"met", -1*60, TWTIME, TM_ZON}, /* Middle Europe */
+ {"wet", 0*60, TWTIME, TM_ZON}, /* Western Europe */
+ {"nst", 3*60+30, TWTIME, TM_ZON},/* Newfoundland */
+ {"ast", 4*60, TWTIME, TM_ZON}, /* Atlantic */
+ {"est", 5*60, TWTIME, TM_ZON}, /* Eastern */
+ {"cst", 6*60, TWTIME, TM_ZON}, /* Central */
+ {"mst", 7*60, TWTIME, TM_ZON}, /* Mountain */
+ {"pst", 8*60, TWTIME, TM_ZON}, /* Pacific */
+ {"akst", 9*60, TWTIME, TM_ZON}, /* Alaska */
+ {"hast", 10*60, TWTIME, TM_ZON}, /* Hawaii-Aleutian */
+ {"hst", 10*60, TWTIME, TM_ZON}, /* Hawaii */
+ {"sst", 11*60, TWTIME, TM_ZON}, /* Samoa */
+
+ {"nzdt", -12*60, TWTIME+TWDST, TM_ZON}, /* New Zealand */
+ {"kdt", -9*60, TWTIME+TWDST, TM_ZON}, /* Korea */
+ {"bst", 0*60, TWTIME+TWDST, TM_ZON}, /* Britain */
+ {"ndt", 3*60+30, TWTIME+TWDST, TM_ZON}, /* Newfoundland */
+ {"adt", 4*60, TWTIME+TWDST, TM_ZON}, /* Atlantic */
+ {"edt", 5*60, TWTIME+TWDST, TM_ZON}, /* Eastern */
+ {"cdt", 6*60, TWTIME+TWDST, TM_ZON}, /* Central */
+ {"mdt", 7*60, TWTIME+TWDST, TM_ZON}, /* Mountain */
+ {"pdt", 8*60, TWTIME+TWDST, TM_ZON}, /* Pacific */
+ {"akdt", 9*60, TWTIME+TWDST, TM_ZON}, /* Alaska */
+ {"hadt", 10*60, TWTIME+TWDST, TM_ZON}, /* Hawaii-Aleutian */
+
+#if 0
+ /*
+ * The following names are duplicates or are not well attested.
+ * A standard is needed.
+ */
+ {"east", -10*60, TWTIME, TM_ZON}, /* Eastern Australia */
+ {"cast", -9*60-30, TWTIME, TM_ZON},/* Central Australia */
+ {"cst", -8*60, TWTIME, TM_ZON}, /* China */
+ {"hkt", -8*60, TWTIME, TM_ZON}, /* Hong Kong */
+ {"sst", -8*60, TWTIME, TM_ZON}, /* Singapore */
+ {"wast", -8*60, TWTIME, TM_ZON}, /* Western Australia */
+ {"?", -6*60-30, TWTIME, TM_ZON},/* Burma */
+ {"?", -4*60-30, TWTIME, TM_ZON},/* Afghanistan */
+ {"it", -3*60-30, TWTIME, TM_ZON},/* Iran */
+ {"ist", -2*60, TWTIME, TM_ZON}, /* Israel */
+ {"mez", -1*60, TWTIME, TM_ZON}, /* Mittel-Europaeische Zeit */
+ {"ast", 1*60, TWTIME, TM_ZON}, /* Azores */
+ {"fst", 2*60, TWTIME, TM_ZON}, /* Fernando de Noronha */
+ {"bst", 3*60, TWTIME, TM_ZON}, /* Brazil */
+ {"wst", 4*60, TWTIME, TM_ZON}, /* Western Brazil */
+ {"ast", 5*60, TWTIME, TM_ZON}, /* Acre Brazil */
+ {"?", 9*60+30, TWTIME, TM_ZON},/* Marquesas */
+ {"?", 12*60, TWTIME, TM_ZON}, /* Kwajalein */
+
+ {"eadt", -10*60, TWTIME+TWDST, TM_ZON}, /* Eastern Australia */
+ {"cadt", -9*60-30, TWTIME+TWDST, TM_ZON}, /* Central Australia */
+ {"cdt", -8*60, TWTIME+TWDST, TM_ZON}, /* China */
+ {"wadt", -8*60, TWTIME+TWDST, TM_ZON}, /* Western Australia */
+ {"idt", -2*60, TWTIME+TWDST, TM_ZON}, /* Israel */
+ {"eest", -2*60, TWTIME+TWDST, TM_ZON}, /* Eastern Europe */
+ {"cest", -1*60, TWTIME+TWDST, TM_ZON}, /* Central Europe */
+ {"mest", -1*60, TWTIME+TWDST, TM_ZON}, /* Middle Europe */
+ {"mesz", -1*60, TWTIME+TWDST, TM_ZON}, /* Mittel-Europaeische Sommerzeit */
+ {"west", 0*60, TWTIME+TWDST, TM_ZON}, /* Western Europe */
+ {"adt", 1*60, TWTIME+TWDST, TM_ZON}, /* Azores */
+ {"fdt", 2*60, TWTIME+TWDST, TM_ZON}, /* Fernando de Noronha */
+ {"edt", 3*60, TWTIME+TWDST, TM_ZON}, /* Eastern Brazil */
+ {"wdt", 4*60, TWTIME+TWDST, TM_ZON}, /* Western Brazil */
+ {"adt", 5*60, TWTIME+TWDST, TM_ZON}, /* Acre Brazil */
+#endif
+
+ {"lt", 0, TWTIME, TM_LT}, /* local time */
+ {"dst", 1*60, TWTIME, TM_DST}, /* daylight savings time */
+ {"ddst", 2*60, TWTIME, TM_DST}, /* double dst */
+
+ {"am", T12_AM, TWTIME, TM_12},
+ {"pm", T12_PM, TWTIME, TM_12},
+ {"noon", T12_NOON, TWTIME, TM_12},
+ {"midnight", T12_MIDNIGHT, TWTIME, TM_12},
+
+ {0, 0, 0, 0} /* Zero entry to terminate searches */
+};
+
+struct token {
+ char const *tcp;/* pointer to string */
+ int tcnt; /* # chars */
+ char tbrk; /* "break" char */
+ char tbrkl; /* last break char */
+ char tflg; /* 0 = alpha, 1 = numeric */
+ union { /* Resulting value; */
+ int tnum;/* either a #, or */
+ struct tmwent const *ttmw;/* a ptr to a tmwent. */
+ } tval;
+};
+
+static struct tmwent const*ptmatchstr P((char const*,int,struct tmwent const*));
+static int pt12hack P((struct tm *,int));
+static int ptitoken P((struct token *));
+static int ptstash P((int *,int));
+static int pttoken P((struct token *));
+
+ static int
+goodzone(t, offset, am)
+ register struct token const *t;
+ int offset;
+ int *am;
+{
+ register int m;
+ if (
+ t->tflg &&
+ t->tcnt == 4+offset &&
+ (m = t->tval.tnum) <= 2400 &&
+ isdigit(t->tcp[offset]) &&
+ (m%=100) < 60
+ ) {
+ m += t->tval.tnum/100 * 60;
+ if (t->tcp[offset-1]=='+')
+ m = -m;
+ *am = m;
+ return 1;
+ }
+ return 0;
+}
+
+ int
+partime(astr, atm, zone)
+char const *astr;
+register struct tm *atm;
+int *zone;
+{
+ register int i;
+ struct token btoken, atoken;
+ int zone_offset; /* minutes west of GMT, plus TZ_OFFSET */
+ register char const *cp;
+ register char ch;
+ int ord, midnoon;
+ int *atmfield, dst, m;
+ int got1 = 0;
+
+ atm->tm_sec = TMNULL;
+ atm->tm_min = TMNULL;
+ atm->tm_hour = TMNULL;
+ atm->tm_mday = TMNULL;
+ atm->tm_mon = TMNULL;
+ atm->tm_year = TMNULL;
+ atm->tm_wday = TMNULL;
+ atm->tm_yday = TMNULL;
+ midnoon = TMNULL; /* and our own temp stuff */
+ zone_offset = TMNULL;
+ dst = TMNULL;
+ btoken.tcnt = btoken.tbrk = 0;
+ btoken.tcp = astr;
+
+ for (;; got1=1) {
+ if (!ptitoken(&btoken)) /* Get a token */
+ { if(btoken.tval.tnum) return(0); /* Read error? */
+ if (given(midnoon)) /* EOF, wrap up */
+ if (!pt12hack(atm, midnoon))
+ return 0;
+ if (!given(atm->tm_min))
+ atm->tm_min = 0;
+ *zone =
+ (given(zone_offset) ? zone_offset-TZ_OFFSET : 0)
+ - (given(dst) ? dst : 0);
+ return got1;
+ }
+ if(btoken.tflg == 0) /* Alpha? */
+ { i = btoken.tval.ttmw->wval;
+ switch (btoken.tval.ttmw->wtype) {
+ default:
+ return 0;
+ case TM_MON:
+ atmfield = &atm->tm_mon;
+ break;
+ case TM_WDAY:
+ atmfield = &atm->tm_wday;
+ break;
+ case TM_DST:
+ atmfield = &dst;
+ break;
+ case TM_LT:
+ if (ptstash(&dst, 0))
+ return 0;
+ i = 48*60; /* local time magic number -- see maketime() */
+ /* fall into */
+ case TM_ZON:
+ i += TZ_OFFSET;
+ if (btoken.tval.ttmw->wflgs & TWDST)
+ if (ptstash(&dst, 60))
+ return 0;
+ /* Peek ahead for offset immediately afterwards. */
+ if (
+ (btoken.tbrk=='-' || btoken.tbrk=='+') &&
+ (atoken=btoken, ++atoken.tcnt, ptitoken(&atoken)) &&
+ goodzone(&atoken, 0, &m)
+ ) {
+ i += m;
+ btoken = atoken;
+ }
+ atmfield = &zone_offset;
+ break;
+ case TM_12:
+ atmfield = &midnoon;
+ }
+ if (ptstash(atmfield, i))
+ return(0); /* ERR: val already set */
+ continue;
+ }
+
+ /* Token is number. Lots of hairy heuristics. */
+ if (!isdigit(*btoken.tcp)) {
+ if (!goodzone(&btoken, 1, &m))
+ return 0;
+ zone_offset = TZ_OFFSET + m;
+ continue;
+ }
+
+ i = btoken.tval.tnum; /* Value now known to be valid; get it. */
+ if (btoken.tcnt == 3) /* 3 digits = HMM */
+ {
+hhmm4: if (ptstash(&atm->tm_min, i%100))
+ return(0); /* ERR: min conflict */
+ i /= 100;
+hh2: if (ptstash(&atm->tm_hour, i))
+ return(0); /* ERR: hour conflict */
+ continue;
+ }
+
+ if (4 < btoken.tcnt)
+ goto year4; /* far in the future */
+ if(btoken.tcnt == 4) /* 4 digits = YEAR or HHMM */
+ { if (given(atm->tm_year)) goto hhmm4; /* Already got yr? */
+ if (given(atm->tm_hour)) goto year4; /* Already got hr? */
+ if(btoken.tbrk == ':') /* HHMM:SS ? */
+ if ( ptstash(&atm->tm_hour, i/100)
+ || ptstash(&atm->tm_min, i%100))
+ return(0); /* ERR: hr/min clash */
+ else goto coltm2; /* Go handle SS */
+ if(btoken.tbrk != ',' && btoken.tbrk != '/'
+ && (atoken=btoken, ptitoken(&atoken)) /* Peek */
+ && ( atoken.tflg
+ ? !isdigit(*atoken.tcp)
+ : atoken.tval.ttmw->wflgs & TWTIME)) /* HHMM-ZON */
+ goto hhmm4;
+ goto year4; /* Give up, assume year. */
+ }
+
+ /* From this point on, assume tcnt == 1 or 2 */
+ /* 2 digits = MM, DD, or HH (MM and SS caught at coltime) */
+ if(btoken.tbrk == ':') /* HH:MM[:SS] */
+ goto coltime; /* must be part of time. */
+ if (31 < i)
+ return 0;
+
+ /* Check for numerical-format date */
+ for (cp = "/-."; ch = *cp++;)
+ { ord = (ch == '.' ? 0 : 1); /* n/m = D/M or M/D */
+ if(btoken.tbrk == ch) /* "NN-" */
+ { if(btoken.tbrkl != ch)
+ {
+ atoken = btoken;
+ atoken.tcnt++;
+ if (ptitoken(&atoken)
+ && atoken.tflg == 0
+ && atoken.tval.ttmw->wtype == TM_MON)
+ goto dd2;
+ if(ord)goto mm2; else goto dd2; /* "NN-" */
+ } /* "-NN-" */
+ if (!given(atm->tm_mday)
+ && given(atm->tm_year)) /* If "YYYY-NN-" */
+ goto mm2; /* then always MM */
+ if(ord)goto dd2; else goto mm2;
+ }
+ if(btoken.tbrkl == ch /* "-NN" */
+ && given(ord ? atm->tm_mon : atm->tm_mday))
+ if (!given(ord ? atm->tm_mday : atm->tm_mon)) /* MM/DD */
+ if(ord)goto dd2; else goto mm2;
+ }
+
+ /* Now reduced to choice between HH and DD */
+ if (given(atm->tm_hour)) goto dd2; /* Have hour? Assume day. */
+ if (given(atm->tm_mday)) goto hh2; /* Have day? Assume hour. */
+ if (given(atm->tm_mon)) goto dd2; /* Have month? Assume day. */
+ if(i > 24) goto dd2; /* Impossible HH means DD */
+ atoken = btoken;
+ if (!ptitoken(&atoken)) /* Read ahead! */
+ if(atoken.tval.tnum) return(0); /* ERR: bad token */
+ else goto dd2; /* EOF, assume day. */
+ if ( atoken.tflg
+ ? !isdigit(*atoken.tcp)
+ : atoken.tval.ttmw->wflgs & TWTIME)
+ /* If next token is a time spec, assume hour */
+ goto hh2; /* e.g. "3 PM", "11-EDT" */
+
+dd2: if (ptstash(&atm->tm_mday, i)) /* Store day (1 based) */
+ return(0);
+ continue;
+
+mm2: if (ptstash(&atm->tm_mon, i-1)) /* Store month (make zero based) */
+ return(0);
+ continue;
+
+year4: if ((i-=1900) < 0 || ptstash(&atm->tm_year, i)) /* Store year-1900 */
+ return(0); /* ERR: year conflict */
+ continue;
+
+ /* Hack HH:MM[[:]SS] */
+coltime:
+ if (ptstash(&atm->tm_hour, i)) return 0;
+ if (!ptitoken(&btoken))
+ return(!btoken.tval.tnum);
+ if(!btoken.tflg) return(0); /* ERR: HH:<alpha> */
+ if(btoken.tcnt == 4) /* MMSS */
+ if (ptstash(&atm->tm_min, btoken.tval.tnum/100)
+ || ptstash(&atm->tm_sec, btoken.tval.tnum%100))
+ return(0);
+ else continue;
+ if(btoken.tcnt != 2
+ || ptstash(&atm->tm_min, btoken.tval.tnum))
+ return(0); /* ERR: MM bad */
+ if (btoken.tbrk != ':') continue; /* Seconds follow? */
+coltm2: if (!ptitoken(&btoken))
+ return(!btoken.tval.tnum);
+ if(!btoken.tflg || btoken.tcnt != 2 /* Verify SS */
+ || ptstash(&atm->tm_sec, btoken.tval.tnum))
+ return(0); /* ERR: SS bad */
+ }
+}
+
+/* Store date/time value, return 0 if successful.
+ * Fail if entry is already set.
+ */
+ static int
+ptstash(adr,val)
+int *adr;
+int val;
+{ register int *a;
+ if (given(*(a=adr)))
+ return 1;
+ *a = val;
+ return(0);
+}
+
+/* This subroutine is invoked for AM, PM, NOON and MIDNIGHT when wrapping up
+ * just prior to returning from partime.
+ */
+ static int
+pt12hack(tm, aval)
+register struct tm *tm;
+register int aval;
+{ register int h = tm->tm_hour;
+ switch (aval) {
+ case T12_AM:
+ case T12_PM:
+ if (h > 12)
+ return 0;
+ if (h == 12)
+ tm->tm_hour = 0;
+ if (aval == T12_PM)
+ tm->tm_hour += 12;
+ break;
+ default:
+ if (0 < tm->tm_min || 0 < tm->tm_sec)
+ return 0;
+ if (!given(h) || h==12)
+ tm->tm_hour = aval;
+ else if (aval==T12_MIDNIGHT && (h==0 || h==24))
+ return 0;
+ }
+ return 1;
+}
+
+/* Get a token and identify it to some degree.
+ * Returns 0 on failure; token.tval will be 0 for normal EOF, otherwise
+ * hit error of some sort
+ */
+
+ static int
+ptitoken(tkp)
+register struct token *tkp;
+{
+ register char const *cp;
+ register int i, j, k;
+
+ if (!pttoken(tkp))
+#ifdef DEBUG
+ {
+ VOID printf("EOF\n");
+ return(0);
+ }
+#else
+ return(0);
+#endif
+ cp = tkp->tcp;
+
+#ifdef DEBUG
+ VOID printf("Token: \"%.*s\" ", tkp->tcnt, cp);
+#endif
+
+ if (tkp->tflg) {
+ i = tkp->tcnt;
+ if (*cp == '+' || *cp == '-') {
+ cp++;
+ i--;
+ }
+ while (0 <= --i) {
+ j = tkp->tval.tnum*10;
+ k = j + (*cp++ - '0');
+ if (j/10 != tkp->tval.tnum || k < j) {
+ /* arithmetic overflow */
+ tkp->tval.tnum = 1;
+ return 0;
+ }
+ tkp->tval.tnum = k;
+ }
+ } else if (!(tkp->tval.ttmw = ptmatchstr(cp, tkp->tcnt, tmwords)))
+ {
+#ifdef DEBUG
+ VOID printf("Not found!\n");
+#endif
+ tkp->tval.tnum = 1;
+ return 0;
+ }
+
+#ifdef DEBUG
+ if(tkp->tflg)
+ VOID printf("Val: %d.\n",tkp->tval.tnum);
+ else VOID printf("Found: \"%s\", val: %d, type %d\n",
+ tkp->tval.ttmw->went,tkp->tval.ttmw->wval,tkp->tval.ttmw->wtype);
+#endif
+
+ return(1);
+}
+
+/* Read token from input string into token structure */
+ static int
+pttoken(tkp)
+register struct token *tkp;
+{
+ register char const *cp;
+ register int c;
+ char const *astr;
+
+ tkp->tcp = astr = cp = tkp->tcp + tkp->tcnt;
+ tkp->tbrkl = tkp->tbrk; /* Set "last break" */
+ tkp->tcnt = tkp->tbrk = tkp->tflg = 0;
+ tkp->tval.tnum = 0;
+
+ while(c = *cp++)
+ { switch(c)
+ { case ' ': case '\t': /* Flush all whitespace */
+ case '\r': case '\n':
+ case '\v': case '\f':
+ if (!tkp->tcnt) { /* If no token yet */
+ tkp->tcp = cp; /* ignore the brk */
+ continue; /* and go on. */
+ }
+ /* fall into */
+ case '(': case ')': /* Perhaps any non-alphanum */
+ case '-': case ',': /* shd qualify as break? */
+ case '+':
+ case '/': case ':': case '.': /* Break chars */
+ if(tkp->tcnt == 0) /* If no token yet */
+ { tkp->tcp = cp; /* ignore the brk */
+ tkp->tbrkl = c;
+ continue; /* and go on. */
+ }
+ tkp->tbrk = c;
+ return(tkp->tcnt);
+ }
+ if (!tkp->tcnt++) { /* If first char of token, */
+ if (isdigit(c)) {
+ tkp->tflg = 1;
+ if (astr<cp-2 && (cp[-2]=='-'||cp[-2]=='+')) {
+ /* timezone is break+sign+digit */
+ tkp->tcp--;
+ tkp->tcnt++;
+ }
+ }
+ } else if ((isdigit(c)!=0) != tkp->tflg) { /* else check type */
+ tkp->tbrk = c;
+ return --tkp->tcnt; /* Wrong type, back up */
+ }
+ }
+ return(tkp->tcnt); /* When hit EOF */
+}
+
+
+ static struct tmwent const *
+ptmatchstr(astr,cnt,astruc)
+ char const *astr;
+ int cnt;
+ struct tmwent const *astruc;
+{
+ register char const *cp, *mp;
+ register int c;
+ struct tmwent const *lastptr;
+ int i;
+
+ lastptr = 0;
+ for(;mp = astruc->went; astruc += 1)
+ { cp = astr;
+ for(i = cnt; i > 0; i--)
+ {
+ switch (*cp++ - (c = *mp++))
+ { case 0: continue; /* Exact match */
+ case 'A'-'a':
+ if (ctab[c] == Letter)
+ continue;
+ }
+ break;
+ }
+ if(i==0)
+ if (!*mp) return astruc; /* Exact match */
+ else if(lastptr) return(0); /* Ambiguous */
+ else lastptr = astruc; /* 1st ambig */
+ }
+ return lastptr;
+}
diff --git a/gnu/usr.bin/rcs/lib/rcsbase.h b/gnu/usr.bin/rcs/lib/rcsbase.h
new file mode 100644
index 0000000..c0904bb
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/rcsbase.h
@@ -0,0 +1,677 @@
+
+/*
+ * RCS common definitions and data structures
+ */
+#define RCSBASE "$Id: rcsbase.h,v 5.11 1991/10/07 17:32:46 eggert Exp $"
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+
+/*****************************************************************************
+ * INSTRUCTIONS:
+ * =============
+ * See the Makefile for how to define C preprocessor symbols.
+ * If you need to change the comment leaders, update the table comtable[]
+ * in rcsfnms.c. (This can wait until you know what a comment leader is.)
+ *****************************************************************************
+ */
+
+
+/* $Log: rcsbase.h,v $
+ * Revision 5.11 1991/10/07 17:32:46 eggert
+ * Support piece tables even if !has_mmap.
+ *
+ * Revision 5.10 1991/09/24 00:28:39 eggert
+ * Remove unexported functions.
+ *
+ * Revision 5.9 1991/08/19 03:13:55 eggert
+ * Add piece tables and other tuneups, and NFS workarounds.
+ *
+ * Revision 5.8 1991/04/21 11:58:20 eggert
+ * Add -x, RCSINIT, MS-DOS support.
+ *
+ * Revision 5.7 1991/02/28 19:18:50 eggert
+ * Try setuid() if seteuid() doesn't work.
+ *
+ * Revision 5.6 1991/02/26 17:48:37 eggert
+ * Support new link behavior. Move ANSI C / Posix declarations into conf.sh.
+ *
+ * Revision 5.5 1990/12/04 05:18:43 eggert
+ * Use -I for prompts and -q for diagnostics.
+ *
+ * Revision 5.4 1990/11/01 05:03:35 eggert
+ * Don't assume that builtins are functions; they may be macros.
+ * Permit arbitrary data in logs.
+ *
+ * Revision 5.3 1990/09/26 23:36:58 eggert
+ * Port wait() to non-Posix ANSI C hosts.
+ *
+ * Revision 5.2 1990/09/04 08:02:20 eggert
+ * Don't redefine NAME_MAX, PATH_MAX.
+ * Improve incomplete line handling. Standardize yes-or-no procedure.
+ *
+ * Revision 5.1 1990/08/29 07:13:53 eggert
+ * Add -kkvl. Fix type typos exposed by porting. Clean old log messages too.
+ *
+ * Revision 5.0 1990/08/22 08:12:44 eggert
+ * Adjust ANSI C / Posix support. Add -k, -V, setuid. Don't call access().
+ * Remove compile-time limits; use malloc instead.
+ * Ansify and Posixate. Add support for ISO 8859.
+ * Remove snoop and v2 support.
+ *
+ * Revision 4.9 89/05/01 15:17:14 narten
+ * botched previous USG fix
+ *
+ * Revision 4.8 89/05/01 14:53:05 narten
+ * changed #include <strings.h> -> string.h for USG systems.
+ *
+ * Revision 4.7 88/11/08 15:58:45 narten
+ * removed defs for functions loaded from libraries
+ *
+ * Revision 4.6 88/08/09 19:12:36 eggert
+ * Shrink stdio code size; remove lint; permit -Dhshsize=nn.
+ *
+ * Revision 4.5 87/12/18 17:06:41 narten
+ * made removed BSD ifdef, now uses V4_2BSD
+ *
+ * Revision 4.4 87/10/18 10:29:49 narten
+ * Updating version numbers
+ * Changes relative to 1.1 are actually relative to 4.2
+ *
+ * Revision 1.3 87/09/24 14:02:25 narten
+ * changes for lint
+ *
+ * Revision 1.2 87/03/27 14:22:02 jenkins
+ * Port to suns
+ *
+ * Revision 4.2 83/12/20 16:04:20 wft
+ * merged 3.6.1.1 and 4.1 (SMALLOG, logsize).
+ * moved setting of STRICT_LOCKING to Makefile.
+ * changed DOLLAR to UNKN (conflict with KDELIM).
+ *
+ * Revision 4.1 83/05/04 09:12:41 wft
+ * Added markers Id and RCSfile.
+ * Added Dbranch for default branches.
+ *
+ * Revision 3.6.1.1 83/12/02 21:56:22 wft
+ * Increased logsize, added macro SMALLOG.
+ *
+ * Revision 3.6 83/01/15 16:43:28 wft
+ * 4.2 prerelease
+ *
+ * Revision 3.6 83/01/15 16:43:28 wft
+ * Replaced dbm.h with BYTESIZ, fixed definition of rindex().
+ * Added variants of NCPFN and NCPPN for bsd 4.2, selected by defining V4_2BSD.
+ * Added macro DELNUMFORM to have uniform format for printing delta text nodes.
+ * Added macro DELETE to mark deleted deltas.
+ *
+ * Revision 3.5 82/12/10 12:16:56 wft
+ * Added two forms of DATEFORM, one using %02d, the other %.2d.
+ *
+ * Revision 3.4 82/12/04 20:01:25 wft
+ * added LOCKER, Locker, and USG (redefinition of rindex).
+ *
+ * Revision 3.3 82/12/03 12:22:04 wft
+ * Added dbm.h, stdio.h, RCSBASE, RCSSEP, RCSSUF, WORKMODE, TMPFILE3,
+ * PRINTDATE, PRINTTIME, map, and ctab; removed Suffix. Redefined keyvallength
+ * using NCPPN. Changed putc() to abort on write error.
+ *
+ * Revision 3.2 82/10/18 15:03:52 wft
+ * added macro STRICT_LOCKING, removed RCSUMASK.
+ * renamed JOINFILE[1,2] to JOINFIL[1,2].
+ *
+ * Revision 3.1 82/10/11 19:41:17 wft
+ * removed NBPW, NBPC, NCPW.
+ * added typdef int void to aid compiling
+ */
+
+
+#include "conf.h"
+
+
+#define EXIT_TROUBLE DIFF_TROUBLE
+
+#ifdef PATH_MAX
+# define SIZEABLE_PATH PATH_MAX /* size of a large path; not a hard limit */
+#else
+# define SIZEABLE_PATH _POSIX_PATH_MAX
+#endif
+
+/* for traditional C hosts with unusual size arguments */
+#define Fread(p,s,n,f) fread(p, (freadarg_type)(s), (freadarg_type)(n), f)
+#define Fwrite(p,s,n,f) fwrite(p, (freadarg_type)(s), (freadarg_type)(n), f)
+
+
+/*
+ * Parameters
+ */
+
+/* backwards compatibility with old versions of RCS */
+#define VERSION_min 3 /* old output RCS format supported */
+#define VERSION_max 5 /* newest output RCS format supported */
+#ifndef VERSION_DEFAULT /* default RCS output format */
+# define VERSION_DEFAULT VERSION_max
+#endif
+#define VERSION(n) ((n) - VERSION_DEFAULT) /* internally, 0 is the default */
+
+#ifndef STRICT_LOCKING
+#define STRICT_LOCKING 1
+#endif
+ /* 0 sets the default locking to non-strict; */
+ /* used in experimental environments. */
+ /* 1 sets the default locking to strict; */
+ /* used in production environments. */
+
+#define yearlength 16 /* (good through AD 9,999,999,999,999,999) */
+#define datesize (yearlength+16) /* size of output of DATEFORM */
+#define joinlength 20 /* number of joined revisions permitted */
+#define RCSTMPPREFIX '_' /* prefix for temp files in working dir */
+#define KDELIM '$' /* delimiter for keywords */
+#define VDELIM ':' /* separates keywords from values */
+#define DEFAULTSTATE "Exp" /* default state of revisions */
+
+
+
+#define true 1
+#define false 0
+#define nil 0
+
+
+/*
+ * RILE - readonly file
+ * declarecache; - declares local cache for RILE variable(s)
+ * setupcache - sets up the local RILE cache, but does not initialize it
+ * cache, uncache - caches and uncaches the local RILE;
+ * (uncache,cache) is needed around functions that advance the RILE pointer
+ * Igeteof(f,c,s) - get a char c from f, executing statement s at EOF
+ * cachegeteof(c,s) - Igeteof applied to the local RILE
+ * Iget(f,c) - like Igeteof, except EOF is an error
+ * cacheget(c) - Iget applied to the local RILE
+ * Ifileno, Irewind, Iseek, Itell - analogs to stdio routines
+ */
+
+#if large_memory
+ typedef unsigned char const *Iptr_type;
+ typedef struct {
+ Iptr_type ptr, lim;
+ unsigned char *base; /* for lint, not Iptr_type even if has_mmap */
+# if has_mmap
+# define Ifileno(f) ((f)->fd)
+ int fd;
+# else
+# define Ifileno(f) fileno((f)->stream)
+ FILE *stream;
+ unsigned char *readlim;
+# endif
+ } RILE;
+# if has_mmap
+# define declarecache register Iptr_type ptr, lim
+# define setupcache(f) (lim = (f)->lim)
+# define Igeteof(f,c,s) if ((f)->ptr==(f)->lim) s else (c)= *(f)->ptr++
+# define cachegeteof(c,s) if (ptr==lim) s else (c)= *ptr++
+# else
+# define declarecache register Iptr_type ptr; register RILE *rRILE
+# define setupcache(f) (rRILE = (f))
+# define Igeteof(f,c,s) if ((f)->ptr==(f)->readlim && !Igetmore(f)) s else (c)= *(f)->ptr++
+# define cachegeteof(c,s) if (ptr==rRILE->readlim && !Igetmore(rRILE)) s else (c)= *ptr++
+# endif
+# define uncache(f) ((f)->ptr = ptr)
+# define cache(f) (ptr = (f)->ptr)
+# define Iget(f,c) Igeteof(f,c,Ieof();)
+# define cacheget(c) cachegeteof(c,Ieof();)
+# define Itell(f) ((f)->ptr)
+# define Iseek(f,p) ((f)->ptr = (p))
+# define Irewind(f) Iseek(f, (f)->base)
+# define cachetell() ptr
+#else
+# define RILE FILE
+# define declarecache register FILE *ptr
+# define setupcache(f) (ptr = (f))
+# define uncache(f)
+# define cache(f)
+# define Igeteof(f,c,s) if(((c)=getc(f))<0){testIerror(f);if(feof(f))s}else
+# define cachegeteof(c,s) Igeteof(ptr,c,s)
+# define Iget(f,c) if (((c)=getc(f))<0) testIeof(f); else
+# define cacheget(c) Iget(ptr,c)
+# define Ifileno(f) fileno(f)
+#endif
+
+/* Print a char, but abort on write error. */
+#define aputc(c,o) if (putc(c,o)<0) testOerror(o); else
+
+/* Get a character from an RCS file, perhaps copying to a new RCS file. */
+#define GETCeof(o,c,s) { cachegeteof(c,s); if (o) aputc(c,o); }
+#define GETC(o,c) { cacheget(c); if (o) aputc(c,o); }
+
+
+#define WORKMODE(RCSmode, writable) ((RCSmode)&~(S_IWUSR|S_IWGRP|S_IWOTH) | ((writable)?S_IWUSR:0))
+/* computes mode of working file: same as RCSmode, but write permission */
+/* determined by writable */
+
+
+/* character classes and token codes */
+enum tokens {
+/* classes */ DELIM, DIGIT, IDCHAR, NEWLN, LETTER, Letter,
+ PERIOD, SBEGIN, SPACE, UNKN,
+/* tokens */ COLON, ID, NUM, SEMI, STRING
+};
+
+#define SDELIM '@' /* the actual character is needed for string handling*/
+/* SDELIM must be consistent with ctab[], so that ctab[SDELIM]==SBEGIN.
+ * there should be no overlap among SDELIM, KDELIM, and VDELIM
+ */
+
+#define isdigit(c) ((unsigned)((c)-'0') <= 9) /* faster than ctab[c]==DIGIT */
+
+
+
+
+
+/***************************************
+ * Data structures for the symbol table
+ ***************************************/
+
+/* Buffer of arbitrary data */
+struct buf {
+ char *string;
+ size_t size;
+};
+struct cbuf {
+ char const *string;
+ size_t size;
+};
+
+/* Hash table entry */
+struct hshentry {
+ char const * num; /* pointer to revision number (ASCIZ) */
+ char const * date; /* pointer to date of checkin */
+ char const * author; /* login of person checking in */
+ char const * lockedby; /* who locks the revision */
+ char const * state; /* state of revision (Exp by default) */
+ struct cbuf log; /* log message requested at checkin */
+ struct branchhead * branches; /* list of first revisions on branches*/
+ struct cbuf ig; /* ignored phrases of revision */
+ struct hshentry * next; /* next revision on same branch */
+ struct hshentry * nexthsh; /* next revision with same hash value */
+ unsigned long insertlns;/* lines inserted (computed by rlog) */
+ unsigned long deletelns;/* lines deleted (computed by rlog) */
+ char selector; /* true if selected, false if deleted */
+};
+
+/* list of hash entries */
+struct hshentries {
+ struct hshentries *rest;
+ struct hshentry *first;
+};
+
+/* list element for branch lists */
+struct branchhead {
+ struct hshentry * hsh;
+ struct branchhead * nextbranch;
+};
+
+/* accesslist element */
+struct access {
+ char const * login;
+ struct access * nextaccess;
+};
+
+/* list element for locks */
+struct lock {
+ char const * login;
+ struct hshentry * delta;
+ struct lock * nextlock;
+};
+
+/* list element for symbolic names */
+struct assoc {
+ char const * symbol;
+ char const * num;
+ struct assoc * nextassoc;
+};
+
+
+#define mainArgs (argc,argv) int argc; char **argv;
+
+#if lint
+# define libId(name,rcsid)
+# define mainProg(name,cmd,rcsid) int name mainArgs
+#else
+# define libId(name,rcsid) char const name[] = rcsid;
+# define mainProg(name,cmd,rcsid) char const copyright[] = "Copyright 1982,1988,1989 by Walter F. Tichy\nPurdue CS\nCopyright 1990,1991 by Paul Eggert", rcsbaseId[] = RCSBASE, cmdid[] = cmd; libId(name,rcsid) int main mainArgs
+#endif
+
+/*
+ * Markers for keyword expansion (used in co and ident)
+ * Every byte must have class LETTER or Letter.
+ */
+#define AUTHOR "Author"
+#define DATE "Date"
+#define HEADER "Header"
+#define IDH "Id"
+#define LOCKER "Locker"
+#define LOG "Log"
+#define RCSFILE "RCSfile"
+#define REVISION "Revision"
+#define SOURCE "Source"
+#define STATE "State"
+#define keylength 8 /* max length of any of the above keywords */
+
+enum markers { Nomatch, Author, Date, Header, Id,
+ Locker, Log, RCSfile, Revision, Source, State };
+ /* This must be in the same order as rcskeys.c's Keyword[] array. */
+
+#define DELNUMFORM "\n\n%s\n%s\n"
+/* used by putdtext and scanlogtext */
+
+#define EMPTYLOG "*** empty log message ***" /* used by ci and rlog */
+
+/* main program */
+extern char const cmdid[];
+exiting void exiterr P((void));
+
+/* maketime */
+int setfiledate P((char const*,char const[datesize]));
+void str2date P((char const*,char[datesize]));
+void time2date P((time_t,char[datesize]));
+
+/* merge */
+int merge P((int,char const*const[2],char const*const[3]));
+
+/* partime */
+int partime P((char const*,struct tm*,int*));
+
+/* rcsedit */
+#define ciklogsize 23 /* sizeof("checked in with -k by ") */
+extern FILE *fcopy;
+extern char const *resultfile;
+extern char const ciklog[ciklogsize];
+extern int locker_expansion;
+extern struct buf dirtfname[];
+#define newRCSfilename (dirtfname[0].string)
+RILE *rcswriteopen P((struct buf*,struct stat*,int));
+char const *makedirtemp P((char const*,int));
+char const *getcaller P((void));
+int addlock P((struct hshentry*));
+int addsymbol P((char const*,char const*,int));
+int checkaccesslist P((void));
+int chnamemod P((FILE**,char const*,char const*,mode_t));
+int donerewrite P((int));
+int dorewrite P((int,int));
+int expandline P((RILE*,FILE*,struct hshentry const*,int,FILE*));
+int findlock P((int,struct hshentry**));
+void aflush P((FILE*));
+void copystring P((void));
+void dirtempunlink P((void));
+void enterstring P((void));
+void finishedit P((struct hshentry const*,FILE*,int));
+void keepdirtemp P((char const*));
+void openfcopy P((FILE*));
+void snapshotedit P((FILE*));
+void xpandstring P((struct hshentry const*));
+#if has_NFS || bad_unlink
+ int un_link P((char const*));
+#else
+# define un_link(s) unlink(s)
+#endif
+#if large_memory
+ void edit_string P((void));
+# define editstring(delta) edit_string()
+#else
+ void editstring P((struct hshentry const*));
+#endif
+
+/* rcsfcmp */
+int rcsfcmp P((RILE*,struct stat const*,char const*,struct hshentry const*));
+
+/* rcsfnms */
+#define bufautobegin(b) ((void) ((b)->string = 0, (b)->size = 0))
+extern FILE *workstdout;
+extern char *workfilename;
+extern char const *RCSfilename;
+extern char const *suffixes;
+extern struct stat RCSstat;
+RILE *rcsreadopen P((struct buf*,struct stat*,int));
+char *bufenlarge P((struct buf*,char const**));
+char const *basename P((char const*));
+char const *getfullRCSname P((void));
+char const *maketemp P((int));
+char const *rcssuffix P((char const*));
+int pairfilenames P((int,char**,RILE*(*)P((struct buf*,struct stat*,int)),int,int));
+size_t dirlen P((char const*));
+struct cbuf bufremember P((struct buf*,size_t));
+void bufalloc P((struct buf*,size_t));
+void bufautoend P((struct buf*));
+void bufrealloc P((struct buf*,size_t));
+void bufscat P((struct buf*,char const*));
+void bufscpy P((struct buf*,char const*));
+void tempunlink P((void));
+
+/* rcsgen */
+extern int interactiveflag;
+extern struct buf curlogbuf;
+char const *buildrevision P((struct hshentries const*,struct hshentry*,FILE*,int));
+int getcstdin P((void));
+int ttystdin P((void));
+int yesorno P((int,char const*,...));
+struct cbuf cleanlogmsg P((char*,size_t));
+struct cbuf getsstdin P((char const*,char const*,char const*,struct buf*));
+void putdesc P((int,char*));
+
+/* rcskeep */
+extern int prevkeys;
+extern struct buf prevauthor, prevdate, prevrev, prevstate;
+int getoldkeys P((RILE*));
+
+/* rcskeys */
+extern char const *const Keyword[];
+enum markers trymatch P((char const*));
+
+/* rcslex */
+extern FILE *foutptr;
+extern FILE *frewrite;
+extern RILE *finptr;
+extern char const *NextString;
+extern enum tokens nexttok;
+extern int hshenter;
+extern int nerror;
+extern int nextc;
+extern int quietflag;
+extern unsigned long rcsline;
+char const *getid P((void));
+exiting void efaterror P((char const*));
+exiting void enfaterror P((int,char const*));
+exiting void faterror P((char const*,...));
+exiting void fatserror P((char const*,...));
+exiting void Ieof P((void));
+exiting void Ierror P((void));
+exiting void Oerror P((void));
+char *checkid P((char*,int));
+int eoflex P((void));
+int getkeyopt P((char const*));
+int getlex P((enum tokens));
+struct cbuf getphrases P((char const*));
+struct cbuf savestring P((struct buf*));
+struct hshentry *getnum P((void));
+void Ifclose P((RILE*));
+void Izclose P((RILE**));
+void Lexinit P((void));
+void Ofclose P((FILE*));
+void Ozclose P((FILE**));
+void afputc P((int,FILE*));
+void aprintf P((FILE*,char const*,...));
+void aputs P((char const*,FILE*));
+void checksid P((char*));
+void diagnose P((char const*,...));
+void eerror P((char const*));
+void eflush P((void));
+void enerror P((int,char const*));
+void error P((char const*,...));
+void fvfprintf P((FILE*,char const*,va_list));
+void getkey P((char const*));
+void getkeystring P((char const*));
+void nextlex P((void));
+void oflush P((void));
+void printstring P((void));
+void readstring P((void));
+void redefined P((int));
+void testIerror P((FILE*));
+void testOerror P((FILE*));
+void warn P((char const*,...));
+void warnignore P((void));
+#if has_madvise && has_mmap && large_memory
+ void advise_access P((RILE*,int));
+# define if_advise_access(p,f,advice) if (p) advise_access(f,advice)
+#else
+# define advise_access(f,advice)
+# define if_advise_access(p,f,advice)
+#endif
+#if has_mmap && large_memory
+ RILE *I_open P((char const*,struct stat*));
+# define Iopen(f,m,s) I_open(f,s)
+#else
+ RILE *Iopen P((char const*,char const*,struct stat*));
+#endif
+#if !large_memory
+ void testIeof P((FILE*));
+ void Irewind P((RILE*));
+#endif
+
+/* rcsmap */
+extern const enum tokens ctab[];
+
+/* rcsrev */
+char *partialno P((struct buf*,char const*,unsigned));
+char const *tiprev P((void));
+int cmpnum P((char const*,char const*));
+int cmpnumfld P((char const*,char const*,unsigned));
+int compartial P((char const*,char const*,unsigned));
+int expandsym P((char const*,struct buf*));
+int fexpandsym P((char const*,struct buf*,RILE*));
+struct hshentry *genrevs P((char const*,char const*,char const*,char const*,struct hshentries**));
+unsigned countnumflds P((char const*));
+void getbranchno P((char const*,struct buf*));
+
+/* rcssyn */
+/* These expand modes must agree with Expand_names[] in rcssyn.c. */
+#define KEYVAL_EXPAND 0 /* -kkv `$Keyword: value $' */
+#define KEYVALLOCK_EXPAND 1 /* -kkvl `$Keyword: value locker $' */
+#define KEY_EXPAND 2 /* -kk `$Keyword$' */
+#define VAL_EXPAND 3 /* -kv `value' */
+#define OLD_EXPAND 4 /* -ko use old string, omitting expansion */
+struct diffcmd {
+ unsigned long
+ line1, /* number of first line */
+ nlines, /* number of lines affected */
+ adprev, /* previous 'a' line1+1 or 'd' line1 */
+ dafter; /* sum of previous 'd' line1 and previous 'd' nlines */
+};
+extern char const * Dbranch;
+extern struct access * AccessList;
+extern struct assoc * Symbols;
+extern struct cbuf Comment;
+extern struct lock * Locks;
+extern struct hshentry * Head;
+extern int Expand;
+extern int StrictLocks;
+extern unsigned TotalDeltas;
+extern char const *const expand_names[];
+extern char const Kdesc[];
+extern char const Klog[];
+extern char const Ktext[];
+int getdiffcmd P((RILE*,int,FILE*,struct diffcmd*));
+int putdftext P((char const*,struct cbuf,RILE*,FILE*,int));
+int putdtext P((char const*,struct cbuf,char const*,FILE*,int));
+int str2expmode P((char const*));
+void getadmin P((void));
+void getdesc P((int));
+void gettree P((void));
+void ignorephrase P((void));
+void initdiffcmd P((struct diffcmd*));
+void putadmin P((FILE*));
+void putstring P((FILE*,int,struct cbuf,int));
+void puttree P((struct hshentry const*,FILE*));
+
+/* rcsutil */
+extern int RCSversion;
+char *cgetenv P((char const*));
+char *fstr_save P((char const*));
+char *str_save P((char const*));
+char const *date2str P((char const[datesize],char[datesize]));
+char const *getusername P((int));
+int getRCSINIT P((int,char**,char***));
+int run P((char const*,char const*,...));
+int runv P((char const**));
+malloc_type fremember P((malloc_type));
+malloc_type ftestalloc P((size_t));
+malloc_type testalloc P((size_t));
+malloc_type testrealloc P((malloc_type,size_t));
+#define ftalloc(T) ftnalloc(T,1)
+#define talloc(T) tnalloc(T,1)
+#if lint
+ extern malloc_type lintalloc;
+# define ftnalloc(T,n) (lintalloc = ftestalloc(sizeof(T)*(n)), (T*)0)
+# define tnalloc(T,n) (lintalloc = testalloc(sizeof(T)*(n)), (T*)0)
+# define trealloc(T,p,n) (lintalloc = testrealloc((malloc_type)0, sizeof(T)*(n)), p)
+# define tfree(p)
+#else
+# define ftnalloc(T,n) ((T*) ftestalloc(sizeof(T)*(n)))
+# define tnalloc(T,n) ((T*) testalloc(sizeof(T)*(n)))
+# define trealloc(T,p,n) ((T*) testrealloc((malloc_type)(p), sizeof(T)*(n)))
+# define tfree(p) free((malloc_type)(p))
+#endif
+void awrite P((char const*,size_t,FILE*));
+void fastcopy P((RILE*,FILE*));
+void ffree P((void));
+void ffree1 P((char const*));
+void setRCSversion P((char const*));
+#if has_signal
+ void catchints P((void));
+ void ignoreints P((void));
+ void restoreints P((void));
+#else
+# define catchints()
+# define ignoreints()
+# define restoreints()
+#endif
+#if has_getuid
+ uid_t ruid P((void));
+# define myself(u) ((u) == ruid())
+#else
+# define myself(u) true
+#endif
+#if has_setuid
+ uid_t euid P((void));
+ void nosetid P((void));
+ void seteid P((void));
+ void setrid P((void));
+#else
+# define nosetid()
+# define seteid()
+# define setrid()
+#endif
diff --git a/gnu/usr.bin/rcs/lib/rcsedit.c b/gnu/usr.bin/rcs/lib/rcsedit.c
new file mode 100644
index 0000000..fab4f62
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/rcsedit.c
@@ -0,0 +1,1656 @@
+/*
+ * RCS stream editor
+ */
+/**********************************************************************************
+ * edits the input file according to a
+ * script from stdin, generated by diff -n
+ * performs keyword expansion
+ **********************************************************************************
+ */
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+/* $Log: rcsedit.c,v $
+ * Revision 5.11 1991/11/03 01:11:44 eggert
+ * Move the warning about link breaking to where they're actually being broken.
+ *
+ * Revision 5.10 1991/10/07 17:32:46 eggert
+ * Support piece tables even if !has_mmap. Fix rare NFS bugs.
+ *
+ * Revision 5.9 1991/09/17 19:07:40 eggert
+ * SGI readlink() yields ENXIO, not EINVAL, for nonlinks.
+ *
+ * Revision 5.8 1991/08/19 03:13:55 eggert
+ * Add piece tables, NFS bug workarounds. Catch odd filenames. Tune.
+ *
+ * Revision 5.7 1991/04/21 11:58:21 eggert
+ * Fix errno bugs. Add -x, RCSINIT, MS-DOS support.
+ *
+ * Revision 5.6 1991/02/25 07:12:40 eggert
+ * Fix setuid bug. Support new link behavior. Work around broken "w+" fopen.
+ *
+ * Revision 5.5 1990/12/30 05:07:35 eggert
+ * Fix report of busy RCS files when !defined(O_CREAT) | !defined(O_EXCL).
+ *
+ * Revision 5.4 1990/11/01 05:03:40 eggert
+ * Permit arbitrary data in comment leaders.
+ *
+ * Revision 5.3 1990/09/11 02:41:13 eggert
+ * Tune expandline().
+ *
+ * Revision 5.2 1990/09/04 08:02:21 eggert
+ * Count RCS lines better. Improve incomplete line handling.
+ *
+ * Revision 5.1 1990/08/29 07:13:56 eggert
+ * Add -kkvl.
+ * Fix bug when getting revisions to files ending in incomplete lines.
+ * Fix bug in comment leader expansion.
+ *
+ * Revision 5.0 1990/08/22 08:12:47 eggert
+ * Don't require final newline.
+ * Don't append "checked in with -k by " to logs,
+ * so that checking in a program with -k doesn't change it.
+ * Don't generate trailing white space for empty comment leader.
+ * Remove compile-time limits; use malloc instead. Add -k, -V.
+ * Permit dates past 1999/12/31. Make lock and temp files faster and safer.
+ * Ansify and Posixate. Check diff's output.
+ *
+ * Revision 4.8 89/05/01 15:12:35 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.7 88/11/08 13:54:14 narten
+ * misplaced semicolon caused infinite loop
+ *
+ * Revision 4.6 88/08/09 19:12:45 eggert
+ * Shrink stdio code size; allow cc -R.
+ *
+ * Revision 4.5 87/12/18 11:38:46 narten
+ * Changes from the 43. version. Don't know the significance of the
+ * first change involving "rewind". Also, additional "lint" cleanup.
+ * (Guy Harris)
+ *
+ * Revision 4.4 87/10/18 10:32:21 narten
+ * Updating version numbers. Changes relative to version 1.1 actually
+ * relative to 4.1
+ *
+ * Revision 1.4 87/09/24 13:59:29 narten
+ * Sources now pass through lint (if you ignore printf/sprintf/fprintf
+ * warnings)
+ *
+ * Revision 1.3 87/09/15 16:39:39 shepler
+ * added an initializatin of the variables editline and linecorr
+ * this will be done each time a file is processed.
+ * (there was an obscure bug where if co was used to retrieve multiple files
+ * it would dump)
+ * fix attributed to Roy Morris @FileNet Corp ...!felix!roy
+ *
+ * Revision 1.2 87/03/27 14:22:17 jenkins
+ * Port to suns
+ *
+ * Revision 4.1 83/05/12 13:10:30 wft
+ * Added new markers Id and RCSfile; added locker to Header and Id.
+ * Overhauled expandline completely() (problem with $01234567890123456789@).
+ * Moved trymatch() and marker table to rcskeys.c.
+ *
+ * Revision 3.7 83/05/12 13:04:39 wft
+ * Added retry to expandline to resume after failed match which ended in $.
+ * Fixed truncation problem for $19chars followed by@@.
+ * Log no longer expands full path of RCS file.
+ *
+ * Revision 3.6 83/05/11 16:06:30 wft
+ * added retry to expandline to resume after failed match which ended in $.
+ * Fixed truncation problem for $19chars followed by@@.
+ *
+ * Revision 3.5 82/12/04 13:20:56 wft
+ * Added expansion of keyword Locker.
+ *
+ * Revision 3.4 82/12/03 12:26:54 wft
+ * Added line number correction in case editing does not start at the
+ * beginning of the file.
+ * Changed keyword expansion to always print a space before closing KDELIM;
+ * Expansion for Header shortened.
+ *
+ * Revision 3.3 82/11/14 14:49:30 wft
+ * removed Suffix from keyword expansion. Replaced fclose with ffclose.
+ * keyreplace() gets log message from delta, not from curlogmsg.
+ * fixed expression overflow in while(c=putc(GETC....
+ * checked nil printing.
+ *
+ * Revision 3.2 82/10/18 21:13:39 wft
+ * I added checks for write errors during the co process, and renamed
+ * expandstring() to xpandstring().
+ *
+ * Revision 3.1 82/10/13 15:52:55 wft
+ * changed type of result of getc() from char to int.
+ * made keyword expansion loop in expandline() portable to machines
+ * without sign-extension.
+ */
+
+
+#include "rcsbase.h"
+
+libId(editId, "$Id: rcsedit.c,v 5.11 1991/11/03 01:11:44 eggert Exp $")
+
+static void keyreplace P((enum markers,struct hshentry const*,FILE*));
+
+
+FILE *fcopy; /* result file descriptor */
+char const *resultfile; /* result file name */
+int locker_expansion; /* should the locker name be appended to Id val? */
+#if !large_memory
+ static RILE *fedit; /* edit file descriptor */
+ static char const *editfile; /* edit pathname */
+#endif
+static unsigned long editline; /* edit line counter; #lines before cursor */
+static long linecorr; /* #adds - #deletes in each edit run. */
+ /*used to correct editline in case file is not rewound after */
+ /* applying one delta */
+
+#define DIRTEMPNAMES 2
+enum maker {notmade, real, effective};
+struct buf dirtfname[DIRTEMPNAMES]; /* unlink these when done */
+static enum maker volatile dirtfmaker[DIRTEMPNAMES]; /* if these are set */
+
+
+#if has_NFS || bad_unlink
+ int
+un_link(s)
+ char const *s;
+/*
+ * Remove S, even if it is unwritable.
+ * Ignore unlink() ENOENT failures; NFS generates bogus ones.
+ */
+{
+# if bad_unlink
+ int e;
+ if (unlink(s) == 0)
+ return 0;
+ e = errno;
+# if has_NFS
+ if (e == ENOENT)
+ return 0;
+# endif
+ if (chmod(s, S_IWUSR) != 0) {
+ errno = e;
+ return -1;
+ }
+# endif
+# if has_NFS
+ return unlink(s)==0 || errno==ENOENT ? 0 : -1;
+# else
+ return unlink(s);
+# endif
+}
+#endif
+
+#if !has_rename
+# if !has_NFS
+# define do_link(s,t) link(s,t)
+# else
+ static int
+do_link(s, t)
+ char const *s, *t;
+/* Link S to T, ignoring bogus EEXIST problems due to NFS failures. */
+{
+ struct stat sb, tb;
+
+ if (link(s,t) == 0)
+ return 0;
+ if (errno != EEXIST)
+ return -1;
+ if (
+ stat(s, &sb) == 0 &&
+ stat(t, &tb) == 0 &&
+ sb.st_ino == tb.st_ino &&
+ sb.st_dev == tb.st_dev
+ )
+ return 0;
+ errno = EEXIST;
+ return -1;
+}
+# endif
+#endif
+
+
+ static exiting void
+editEndsPrematurely()
+{
+ fatserror("edit script ends prematurely");
+}
+
+ static exiting void
+editLineNumberOverflow()
+{
+ fatserror("edit script refers to line past end of file");
+}
+
+
+#if large_memory
+
+#if has_memmove
+# define movelines(s1, s2, n) VOID memmove(s1, s2, (n)*sizeof(Iptr_type))
+#else
+ static void
+movelines(s1, s2, n)
+ register Iptr_type *s1;
+ register Iptr_type const *s2;
+ register unsigned long n;
+{
+ if (s1 < s2)
+ do {
+ *s1++ = *s2++;
+ } while (--n);
+ else {
+ s1 += n;
+ s2 += n;
+ do {
+ *--s1 = *--s2;
+ } while (--n);
+ }
+}
+#endif
+
+/*
+ * `line' contains pointers to the lines in the currently `edited' file.
+ * It is a 0-origin array that represents linelim-gapsize lines.
+ * line[0..gap-1] and line[gap+gapsize..linelim-1] contain pointers to lines.
+ * line[gap..gap+gapsize-1] contains garbage.
+ *
+ * Any @s in lines are duplicated.
+ * Lines are terminated by \n, or (for a last partial line only) by single @.
+ */
+static Iptr_type *line;
+static unsigned long gap, gapsize, linelim;
+
+
+ static void
+insertline(n, l)
+ unsigned long n;
+ Iptr_type l;
+/* Before line N, insert line L. N is 0-origin. */
+{
+ if (linelim-gapsize < n)
+ editLineNumberOverflow();
+ if (!gapsize)
+ line =
+ !linelim ?
+ tnalloc(Iptr_type, linelim = gapsize = 1024)
+ : (
+ gap = gapsize = linelim,
+ trealloc(Iptr_type, line, linelim <<= 1)
+ );
+ if (n < gap)
+ movelines(line+n+gapsize, line+n, gap-n);
+ else if (gap < n)
+ movelines(line+gap, line+gap+gapsize, n-gap);
+
+ line[n] = l;
+ gap = n + 1;
+ gapsize--;
+}
+
+ static void
+deletelines(n, nlines)
+ unsigned long n, nlines;
+/* Delete lines N through N+NLINES-1. N is 0-origin. */
+{
+ unsigned long l = n + nlines;
+ if (linelim-gapsize < l || l < n)
+ editLineNumberOverflow();
+ if (l < gap)
+ movelines(line+l+gapsize, line+l, gap-l);
+ else if (gap < n)
+ movelines(line+gap, line+gap+gapsize, n-gap);
+
+ gap = n;
+ gapsize += nlines;
+}
+
+ static void
+snapshotline(f, l)
+ register FILE *f;
+ register Iptr_type l;
+{
+ register int c;
+ do {
+ if ((c = *l++) == SDELIM && *l++ != SDELIM)
+ return;
+ aputc(c, f);
+ } while (c != '\n');
+}
+
+ void
+snapshotedit(f)
+ FILE *f;
+/* Copy the current state of the edits to F. */
+{
+ register Iptr_type *p, *lim, *l=line;
+ for (p=l, lim=l+gap; p<lim; )
+ snapshotline(f, *p++);
+ for (p+=gapsize, lim=l+linelim; p<lim; )
+ snapshotline(f, *p++);
+}
+
+ static void
+finisheditline(fin, fout, l, delta)
+ RILE *fin;
+ FILE *fout;
+ Iptr_type l;
+ struct hshentry const *delta;
+{
+ Iseek(fin, l);
+ if (expandline(fin, fout, delta, true, (FILE*)0) < 0)
+ faterror("finisheditline internal error");
+}
+
+ void
+finishedit(delta, outfile, done)
+ struct hshentry const *delta;
+ FILE *outfile;
+ int done;
+/*
+ * Doing expansion if DELTA is set, output the state of the edits to OUTFILE.
+ * But do nothing unless DONE is set (which means we are on the last pass).
+ */
+{
+ if (done) {
+ openfcopy(outfile);
+ outfile = fcopy;
+ if (!delta)
+ snapshotedit(outfile);
+ else {
+ register Iptr_type *p, *lim, *l = line;
+ register RILE *fin = finptr;
+ Iptr_type here = Itell(fin);
+ for (p=l, lim=l+gap; p<lim; )
+ finisheditline(fin, outfile, *p++, delta);
+ for (p+=gapsize, lim=l+linelim; p<lim; )
+ finisheditline(fin, outfile, *p++, delta);
+ Iseek(fin, here);
+ }
+ }
+}
+
+/* Open a temporary FILENAME for output, truncating any previous contents. */
+# define fopen_update_truncate(filename) fopen(filename, FOPEN_W_WORK)
+#else /* !large_memory */
+ static FILE *
+fopen_update_truncate(filename)
+ char const *filename;
+{
+# if bad_fopen_wplus
+ if (un_link(filename) != 0)
+ efaterror(filename);
+# endif
+ return fopen(filename, FOPEN_WPLUS_WORK);
+}
+#endif
+
+
+ void
+openfcopy(f)
+ FILE *f;
+{
+ if (!(fcopy = f)) {
+ if (!resultfile)
+ resultfile = maketemp(2);
+ if (!(fcopy = fopen_update_truncate(resultfile)))
+ efaterror(resultfile);
+ }
+}
+
+
+#if !large_memory
+
+ static void
+swapeditfiles(outfile)
+ FILE *outfile;
+/* Function: swaps resultfile and editfile, assigns fedit=fcopy,
+ * and rewinds fedit for reading. Set fcopy to outfile if nonnull;
+ * otherwise, set fcopy to be resultfile opened for reading and writing.
+ */
+{
+ char const *tmpptr;
+
+ editline = 0; linecorr = 0;
+ if (fseek(fcopy, 0L, SEEK_SET) != 0)
+ Oerror();
+ fedit = fcopy;
+ tmpptr=editfile; editfile=resultfile; resultfile=tmpptr;
+ openfcopy(outfile);
+}
+
+ void
+snapshotedit(f)
+ FILE *f;
+/* Copy the current state of the edits to F. */
+{
+ finishedit((struct hshentry *)nil, (FILE*)0, false);
+ fastcopy(fedit, f);
+ Irewind(fedit);
+}
+
+ void
+finishedit(delta, outfile, done)
+ struct hshentry const *delta;
+ FILE *outfile;
+ int done;
+/* copy the rest of the edit file and close it (if it exists).
+ * if delta!=nil, perform keyword substitution at the same time.
+ * If DONE is set, we are finishing the last pass.
+ */
+{
+ register RILE *fe;
+ register FILE *fc;
+
+ fe = fedit;
+ if (fe) {
+ fc = fcopy;
+ if (delta!=nil) {
+ while (1 < expandline(fe,fc,delta,false,(FILE*)0))
+ ;
+ } else {
+ fastcopy(fe,fc);
+ }
+ Ifclose(fe);
+ }
+ if (!done)
+ swapeditfiles(outfile);
+}
+#endif
+
+
+
+#if large_memory
+# define copylines(upto,delta) (editline = (upto))
+#else
+ static void
+copylines(upto,delta)
+ register unsigned long upto;
+ struct hshentry const *delta;
+/*
+ * Copy input lines editline+1..upto from fedit to fcopy.
+ * If delta != nil, keyword expansion is done simultaneously.
+ * editline is updated. Rewinds a file only if necessary.
+ */
+{
+ register int c;
+ declarecache;
+ register FILE *fc;
+ register RILE *fe;
+
+ if (upto < editline) {
+ /* swap files */
+ finishedit((struct hshentry *)nil, (FILE*)0, false);
+ /* assumes edit only during last pass, from the beginning*/
+ }
+ fe = fedit;
+ fc = fcopy;
+ if (editline < upto)
+ if (delta)
+ do {
+ if (expandline(fe,fc,delta,false,(FILE*)0) <= 1)
+ editLineNumberOverflow();
+ } while (++editline < upto);
+ else {
+ setupcache(fe); cache(fe);
+ do {
+ do {
+ cachegeteof(c, editLineNumberOverflow(););
+ aputc(c, fc);
+ } while (c != '\n');
+ } while (++editline < upto);
+ uncache(fe);
+ }
+}
+#endif
+
+
+
+ void
+xpandstring(delta)
+ struct hshentry const *delta;
+/* Function: Reads a string terminated by SDELIM from finptr and writes it
+ * to fcopy. Double SDELIM is replaced with single SDELIM.
+ * Keyword expansion is performed with data from delta.
+ * If foutptr is nonnull, the string is also copied unchanged to foutptr.
+ */
+{
+ while (1 < expandline(finptr,fcopy,delta,true,foutptr))
+ ;
+}
+
+
+ void
+copystring()
+/* Function: copies a string terminated with a single SDELIM from finptr to
+ * fcopy, replacing all double SDELIM with a single SDELIM.
+ * If foutptr is nonnull, the string also copied unchanged to foutptr.
+ * editline is incremented by the number of lines copied.
+ * Assumption: next character read is first string character.
+ */
+{ register c;
+ declarecache;
+ register FILE *frew, *fcop;
+ register int amidline;
+ register RILE *fin;
+
+ fin = finptr;
+ setupcache(fin); cache(fin);
+ frew = foutptr;
+ fcop = fcopy;
+ amidline = false;
+ for (;;) {
+ GETC(frew,c);
+ switch (c) {
+ case '\n':
+ ++editline;
+ ++rcsline;
+ amidline = false;
+ break;
+ case SDELIM:
+ GETC(frew,c);
+ if (c != SDELIM) {
+ /* end of string */
+ nextc = c;
+ editline += amidline;
+ uncache(fin);
+ return;
+ }
+ /* fall into */
+ default:
+ amidline = true;
+ break;
+ }
+ aputc(c,fcop);
+ }
+}
+
+
+ void
+enterstring()
+/* Like copystring, except the string is put into the edit data structure. */
+{
+#if !large_memory
+ editfile = 0;
+ fedit = 0;
+ editline = linecorr = 0;
+ resultfile = maketemp(1);
+ if (!(fcopy = fopen_update_truncate(resultfile)))
+ efaterror(resultfile);
+ copystring();
+#else
+ register int c;
+ declarecache;
+ register FILE *frew;
+ register unsigned long e, oe;
+ register int amidline, oamidline;
+ register Iptr_type optr;
+ register RILE *fin;
+
+ e = 0;
+ gap = 0;
+ gapsize = linelim;
+ fin = finptr;
+ setupcache(fin); cache(fin);
+ advise_access(fin, MADV_NORMAL);
+ frew = foutptr;
+ amidline = false;
+ for (;;) {
+ optr = cachetell();
+ GETC(frew,c);
+ oamidline = amidline;
+ oe = e;
+ switch (c) {
+ case '\n':
+ ++e;
+ ++rcsline;
+ amidline = false;
+ break;
+ case SDELIM:
+ GETC(frew,c);
+ if (c != SDELIM) {
+ /* end of string */
+ nextc = c;
+ editline = e + amidline;
+ linecorr = 0;
+ uncache(fin);
+ return;
+ }
+ /* fall into */
+ default:
+ amidline = true;
+ break;
+ }
+ if (!oamidline)
+ insertline(oe, optr);
+ }
+#endif
+}
+
+
+
+
+ void
+#if large_memory
+edit_string()
+#else
+ editstring(delta)
+ struct hshentry const *delta;
+#endif
+/*
+ * Read an edit script from finptr and applies it to the edit file.
+#if !large_memory
+ * The result is written to fcopy.
+ * If delta!=nil, keyword expansion is performed simultaneously.
+ * If running out of lines in fedit, fedit and fcopy are swapped.
+ * editfile is the name of the file that goes with fedit.
+#endif
+ * If foutptr is set, the edit script is also copied verbatim to foutptr.
+ * Assumes that all these files are open.
+ * resultfile is the name of the file that goes with fcopy.
+ * Assumes the next input character from finptr is the first character of
+ * the edit script. Resets nextc on exit.
+ */
+{
+ int ed; /* editor command */
+ register int c;
+ declarecache;
+ register FILE *frew;
+# if !large_memory
+ register FILE *f;
+ unsigned long line_lim = ULONG_MAX;
+ register RILE *fe;
+# endif
+ register unsigned long i;
+ register RILE *fin;
+# if large_memory
+ register unsigned long j;
+# endif
+ struct diffcmd dc;
+
+ editline += linecorr; linecorr=0; /*correct line number*/
+ frew = foutptr;
+ fin = finptr;
+ setupcache(fin);
+ initdiffcmd(&dc);
+ while (0 <= (ed = getdiffcmd(fin,true,frew,&dc)))
+#if !large_memory
+ if (line_lim <= dc.line1)
+ editLineNumberOverflow();
+ else
+#endif
+ if (!ed) {
+ copylines(dc.line1-1, delta);
+ /* skip over unwanted lines */
+ i = dc.nlines;
+ linecorr -= i;
+ editline += i;
+# if large_memory
+ deletelines(editline+linecorr, i);
+# else
+ fe = fedit;
+ do {
+ /*skip next line*/
+ do {
+ Igeteof(fe, c, { if (i!=1) editLineNumberOverflow(); line_lim = dc.dafter; break; } );
+ } while (c != '\n');
+ } while (--i);
+# endif
+ } else {
+ copylines(dc.line1, delta); /*copy only; no delete*/
+ i = dc.nlines;
+# if large_memory
+ j = editline+linecorr;
+# endif
+ linecorr += i;
+#if !large_memory
+ f = fcopy;
+ if (delta)
+ do {
+ switch (expandline(fin,f,delta,true,frew)) {
+ case 0: case 1:
+ if (i==1)
+ return;
+ /* fall into */
+ case -1:
+ editEndsPrematurely();
+ }
+ } while (--i);
+ else
+#endif
+ {
+ cache(fin);
+ do {
+# if large_memory
+ insertline(j++, cachetell());
+# endif
+ for (;;) {
+ GETC(frew, c);
+# if !large_memory
+ aputc(c, f);
+# endif
+ if (c == '\n')
+ break;
+ if (c==SDELIM) {
+ GETC(frew, c);
+ if (c!=SDELIM) {
+ if (--i)
+ editEndsPrematurely();
+ nextc = c;
+ uncache(fin);
+ return;
+ }
+ }
+ }
+ ++rcsline;
+ } while (--i);
+ uncache(fin);
+ }
+ }
+}
+
+
+
+/* The rest is for keyword expansion */
+
+
+
+ int
+expandline(infile, outfile, delta, delimstuffed, frewfile)
+ RILE *infile;
+ FILE *outfile, *frewfile;
+ struct hshentry const *delta;
+ int delimstuffed;
+/*
+ * Read a line from INFILE and write it to OUTFILE.
+ * If DELIMSTUFFED is true, double SDELIM is replaced with single SDELIM.
+ * Keyword expansion is performed with data from delta.
+ * If FREWFILE is set, copy the line unchanged to FREWFILE.
+ * DELIMSTUFFED must be true if FREWFILE is set.
+ * Yields -1 if no data is copied, 0 if an incomplete line is copied,
+ * 2 if a complete line is copied; adds 1 to yield if expansion occurred.
+ */
+{
+ register c;
+ declarecache;
+ register FILE *out, *frew;
+ register char * tp;
+ register int e, ds, r;
+ char const *tlim;
+ static struct buf keyval;
+ enum markers matchresult;
+
+ setupcache(infile); cache(infile);
+ out = outfile;
+ frew = frewfile;
+ ds = delimstuffed;
+ bufalloc(&keyval, keylength+3);
+ e = 0;
+ r = -1;
+
+ for (;;) {
+ if (ds) {
+ GETC(frew, c);
+ } else
+ cachegeteof(c, goto uncache_exit;);
+ for (;;) {
+ switch (c) {
+ case SDELIM:
+ if (ds) {
+ GETC(frew, c);
+ if (c != SDELIM) {
+ /* end of string */
+ nextc=c;
+ goto uncache_exit;
+ }
+ }
+ /* fall into */
+ default:
+ aputc(c,out);
+ r = 0;
+ break;
+
+ case '\n':
+ rcsline += ds;
+ aputc(c,out);
+ r = 2;
+ goto uncache_exit;
+
+ case KDELIM:
+ r = 0;
+ /* check for keyword */
+ /* first, copy a long enough string into keystring */
+ tp = keyval.string;
+ *tp++ = KDELIM;
+ for (;;) {
+ if (ds) {
+ GETC(frew, c);
+ } else
+ cachegeteof(c, goto keystring_eof;);
+ if (tp < keyval.string+keylength+1)
+ switch (ctab[c]) {
+ case LETTER: case Letter:
+ *tp++ = c;
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ *tp++ = c; *tp = '\0';
+ matchresult = trymatch(keyval.string+1);
+ if (matchresult==Nomatch) {
+ tp[-1] = 0;
+ aputs(keyval.string, out);
+ continue; /* last c handled properly */
+ }
+
+ /* Now we have a keyword terminated with a K/VDELIM */
+ if (c==VDELIM) {
+ /* try to find closing KDELIM, and replace value */
+ tlim = keyval.string + keyval.size;
+ for (;;) {
+ if (ds) {
+ GETC(frew, c);
+ } else
+ cachegeteof(c, goto keystring_eof;);
+ if (c=='\n' || c==KDELIM)
+ break;
+ *tp++ =c;
+ if (tlim <= tp)
+ tp = bufenlarge(&keyval, &tlim);
+ if (c==SDELIM && ds) { /*skip next SDELIM */
+ GETC(frew, c);
+ if (c != SDELIM) {
+ /* end of string before closing KDELIM or newline */
+ nextc = c;
+ goto keystring_eof;
+ }
+ }
+ }
+ if (c!=KDELIM) {
+ /* couldn't find closing KDELIM -- give up */
+ *tp = 0;
+ aputs(keyval.string, out);
+ continue; /* last c handled properly */
+ }
+ }
+ /* now put out the new keyword value */
+ keyreplace(matchresult,delta,out);
+ e = 1;
+ break;
+ }
+ break;
+ }
+ }
+
+ keystring_eof:
+ *tp = 0;
+ aputs(keyval.string, out);
+ uncache_exit:
+ uncache(infile);
+ return r + e;
+}
+
+
+char const ciklog[ciklogsize] = "checked in with -k by ";
+
+ static void
+keyreplace(marker,delta,out)
+ enum markers marker;
+ register struct hshentry const *delta;
+ register FILE *out;
+/* function: outputs the keyword value(s) corresponding to marker.
+ * Attributes are derived from delta.
+ */
+{
+ register char const *sp, *cp, *date;
+ register char c;
+ register size_t cs, cw, ls;
+ char const *sp1;
+ char datebuf[datesize];
+ int RCSv;
+
+ sp = Keyword[(int)marker];
+
+ if (Expand == KEY_EXPAND) {
+ aprintf(out, "%c%s%c", KDELIM, sp, KDELIM);
+ return;
+ }
+
+ date= delta->date;
+ RCSv = RCSversion;
+
+ if (Expand == KEYVAL_EXPAND || Expand == KEYVALLOCK_EXPAND)
+ aprintf(out, "%c%s%c%c", KDELIM, sp, VDELIM,
+ marker==Log && RCSv<VERSION(5) ? '\t' : ' '
+ );
+
+ switch (marker) {
+ case Author:
+ aputs(delta->author, out);
+ break;
+ case Date:
+ aputs(date2str(date,datebuf), out);
+ break;
+ case Id:
+ case Header:
+ aprintf(out, "%s %s %s %s %s",
+ marker==Id || RCSv<VERSION(4)
+ ? basename(RCSfilename)
+ : getfullRCSname(),
+ delta->num,
+ date2str(date, datebuf),
+ delta->author,
+ RCSv==VERSION(3) && delta->lockedby ? "Locked"
+ : delta->state
+ );
+ if (delta->lockedby!=nil)
+ if (VERSION(5) <= RCSv) {
+ if (locker_expansion || Expand==KEYVALLOCK_EXPAND)
+ aprintf(out, " %s", delta->lockedby);
+ } else if (RCSv == VERSION(4))
+ aprintf(out, " Locker: %s", delta->lockedby);
+ break;
+ case Locker:
+ if (delta->lockedby)
+ if (
+ locker_expansion
+ || Expand == KEYVALLOCK_EXPAND
+ || RCSv <= VERSION(4)
+ )
+ aputs(delta->lockedby, out);
+ break;
+ case Log:
+ case RCSfile:
+ aputs(basename(RCSfilename), out);
+ break;
+ case Revision:
+ aputs(delta->num, out);
+ break;
+ case Source:
+ aputs(getfullRCSname(), out);
+ break;
+ case State:
+ aputs(delta->state, out);
+ break;
+ default:
+ break;
+ }
+ if (Expand == KEYVAL_EXPAND || Expand == KEYVALLOCK_EXPAND) {
+ afputc(' ', out);
+ afputc(KDELIM, out);
+ }
+ if (marker == Log) {
+ sp = delta->log.string;
+ ls = delta->log.size;
+ if (sizeof(ciklog)-1<=ls && !memcmp(sp,ciklog,sizeof(ciklog)-1))
+ return;
+ afputc('\n', out);
+ cp = Comment.string;
+ cw = cs = Comment.size;
+ awrite(cp, cs, out);
+ /* oddity: 2 spaces between date and time, not 1 as usual */
+ sp1 = strchr(date2str(date,datebuf), ' ');
+ aprintf(out, "Revision %s %.*s %s %s",
+ delta->num, (int)(sp1-datebuf), datebuf, sp1, delta->author
+ );
+ /* Do not include state: it may change and is not updated. */
+ /* Comment is the comment leader. */
+ if (VERSION(5) <= RCSv)
+ for (; cw && (cp[cw-1]==' ' || cp[cw-1]=='\t'); --cw)
+ ;
+ for (;;) {
+ afputc('\n', out);
+ awrite(cp, cw, out);
+ if (!ls)
+ break;
+ --ls;
+ c = *sp++;
+ if (c != '\n') {
+ awrite(cp+cw, cs-cw, out);
+ do {
+ afputc(c,out);
+ if (!ls)
+ break;
+ --ls;
+ c = *sp++;
+ } while (c != '\n');
+ }
+ }
+ }
+}
+
+#if has_readlink
+ static int
+resolve_symlink(L)
+ struct buf *L;
+/*
+ * If L is a symbolic link, resolve it to the name that it points to.
+ * If unsuccessful, set errno and yield -1.
+ * If it points to an existing file, yield 1.
+ * Otherwise, set errno=ENOENT and yield 0.
+ */
+{
+ char *b, a[SIZEABLE_PATH];
+ int e;
+ size_t s;
+ ssize_t r;
+ struct buf bigbuf;
+ unsigned linkcount = MAXSYMLINKS + 1;
+
+ b = a;
+ s = sizeof(a);
+ bufautobegin(&bigbuf);
+ while ((r = readlink(L->string,b,s)) != -1)
+ if (r == s) {
+ bufalloc(&bigbuf, s<<1);
+ b = bigbuf.string;
+ s = bigbuf.size;
+ } else if (!--linkcount) {
+ errno = ELOOP;
+ return -1;
+ } else {
+ /* Splice symbolic link into L. */
+ b[r] = '\0';
+ L->string[ROOTPATH(b) ? (size_t)0 : dirlen(L->string)] = '\0';
+ bufscat(L, b);
+ }
+ e = errno;
+ bufautoend(&bigbuf);
+ errno = e;
+ switch (e) {
+ case ENXIO:
+ case EINVAL: return 1;
+ case ENOENT: return 0;
+ default: return -1;
+ }
+}
+#endif
+
+ RILE *
+rcswriteopen(RCSbuf, status, mustread)
+ struct buf *RCSbuf;
+ struct stat *status;
+ int mustread;
+/*
+ * Create the lock file corresponding to RCSNAME.
+ * Then try to open RCSNAME for reading and yield its FILE* descriptor.
+ * Put its status into *STATUS too.
+ * MUSTREAD is true if the file must already exist, too.
+ * If all goes well, discard any previously acquired locks,
+ * and set frewrite to the FILE* descriptor of the lock file,
+ * which will eventually turn into the new RCS file.
+ */
+{
+ register char *tp;
+ register char const *sp, *RCSname, *x;
+ RILE *f;
+ size_t l;
+ int e, exists, fdesc, previouslock, r;
+ struct buf *dirt;
+ struct stat statbuf;
+
+ previouslock = frewrite != 0;
+ exists =
+# if has_readlink
+ resolve_symlink(RCSbuf);
+# else
+ stat(RCSbuf->string, &statbuf) == 0 ? 1
+ : errno==ENOENT ? 0 : -1;
+# endif
+ if (exists < (mustread|previouslock))
+ /*
+ * There's an unusual problem with the RCS file;
+ * or the RCS file doesn't exist,
+ * and we must read or we already have a lock elsewhere.
+ */
+ return 0;
+
+ RCSname = RCSbuf->string;
+ sp = basename(RCSname);
+ l = sp - RCSname;
+ dirt = &dirtfname[previouslock];
+ bufscpy(dirt, RCSname);
+ tp = dirt->string + l;
+ x = rcssuffix(RCSname);
+# if has_readlink
+ if (!x) {
+ error("symbolic link to non RCS filename `%s'", RCSname);
+ errno = EINVAL;
+ return 0;
+ }
+# endif
+ if (*sp == *x) {
+ error("RCS filename `%s' incompatible with suffix `%s'", sp, x);
+ errno = EINVAL;
+ return 0;
+ }
+ /* Create a lock file whose name is a function of the RCS filename. */
+ if (*x) {
+ /*
+ * The suffix is nonempty.
+ * The lock filename is the first char of of the suffix,
+ * followed by the RCS filename with last char removed. E.g.:
+ * foo,v RCS filename with suffix ,v
+ * ,foo, lock filename
+ */
+ *tp++ = *x;
+ while (*sp)
+ *tp++ = *sp++;
+ *--tp = 0;
+ } else {
+ /*
+ * The suffix is empty.
+ * The lock filename is the RCS filename
+ * with last char replaced by '_'.
+ */
+ while ((*tp++ = *sp++))
+ ;
+ tp -= 2;
+ if (*tp == '_') {
+ error("RCS filename `%s' ends with `%c'", RCSname, *tp);
+ errno = EINVAL;
+ return 0;
+ }
+ *tp = '_';
+ }
+
+ sp = tp = dirt->string;
+
+ f = 0;
+
+ /*
+ * good news:
+ * open(f, O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, READONLY) is atomic
+ * according to Posix 1003.1-1990.
+ * bad news:
+ * NFS ignores O_EXCL and doesn't comply with Posix 1003.1-1990.
+ * good news:
+ * (O_TRUNC,READONLY) normally guarantees atomicity even with NFS.
+ * bad news:
+ * If you're root, (O_TRUNC,READONLY) doesn't guarantee atomicity.
+ * good news:
+ * Root-over-the-wire NFS access is rare for security reasons.
+ * This bug has never been reported in practice with RCS.
+ * So we don't worry about this bug.
+ *
+ * An even rarer NFS bug can occur when clients retry requests.
+ * Suppose client A renames the lock file ",f," to "f,v"
+ * at about the same time that client B creates ",f,",
+ * and suppose A's first rename request is delayed, so A reissues it.
+ * The sequence of events might be:
+ * A sends rename(",f,", "f,v")
+ * B sends create(",f,")
+ * A sends retry of rename(",f,", "f,v")
+ * server receives, does, and acknowledges A's first rename()
+ * A receives acknowledgment, and its RCS program exits
+ * server receives, does, and acknowledges B's create()
+ * server receives, does, and acknowledges A's retry of rename()
+ * This not only wrongly deletes B's lock, it removes the RCS file!
+ * Most NFS implementations have idempotency caches that usually prevent
+ * this scenario, but such caches are finite and can be overrun.
+ * This problem afflicts programs that use the traditional
+ * Unix method of using link() and unlink() to get and release locks,
+ * as well as RCS's method of using open() and rename().
+ * There is no easy workaround for either link-unlink or open-rename.
+ * Any new method based on lockf() seemingly would be incompatible with
+ * the old methods; besides, lockf() is notoriously buggy under NFS.
+ * Since this problem afflicts scads of Unix programs, but is so rare
+ * that nobody seems to be worried about it, we won't worry either.
+ */
+# define READONLY (S_IRUSR|S_IRGRP|S_IROTH)
+# if !open_can_creat
+# define create(f) creat(f, READONLY)
+# else
+# define create(f) open(f, O_BINARY|O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, READONLY)
+# endif
+
+ catchints();
+ ignoreints();
+
+ /*
+ * Create a lock file for an RCS file. This should be atomic, i.e.
+ * if two processes try it simultaneously, at most one should succeed.
+ */
+ seteid();
+ fdesc = create(sp);
+ e = errno;
+ setrid();
+
+ if (fdesc < 0) {
+ if (e == EACCES && stat(tp,&statbuf) == 0)
+ /* The RCS file is busy. */
+ e = EEXIST;
+ } else {
+ dirtfmaker[0] = effective;
+ e = ENOENT;
+ if (exists) {
+ f = Iopen(RCSname, FOPEN_R, status);
+ e = errno;
+ if (f && previouslock) {
+ /* Discard the previous lock in favor of this one. */
+ Ozclose(&frewrite);
+ seteid();
+ if ((r = un_link(newRCSfilename)) != 0)
+ e = errno;
+ setrid();
+ if (r != 0)
+ enfaterror(e, newRCSfilename);
+ bufscpy(&dirtfname[0], tp);
+ }
+ }
+ if (!(frewrite = fdopen(fdesc, FOPEN_W))) {
+ efaterror(newRCSfilename);
+ }
+ }
+
+ restoreints();
+
+ errno = e;
+ return f;
+}
+
+ void
+keepdirtemp(name)
+ char const *name;
+/* Do not unlink name, either because it's not there any more,
+ * or because it has already been unlinked.
+ */
+{
+ register int i;
+ for (i=DIRTEMPNAMES; 0<=--i; )
+ if (dirtfname[i].string == name) {
+ dirtfmaker[i] = notmade;
+ return;
+ }
+ faterror("keepdirtemp");
+}
+
+ char const *
+makedirtemp(name, n)
+ register char const *name;
+ int n;
+/*
+ * Have maketemp() do all the work if name is null.
+ * Otherwise, create a unique filename in name's dir using n and name
+ * and store it into the dirtfname[n].
+ * Because of storage in tfnames, dirtempunlink() can unlink the file later.
+ * Return a pointer to the filename created.
+ */
+{
+ register char *tp, *np;
+ register size_t dl;
+ register struct buf *bn;
+
+ if (!name)
+ return maketemp(n);
+ dl = dirlen(name);
+ bn = &dirtfname[n];
+ bufalloc(bn,
+# if has_mktemp
+ dl + 9
+# else
+ strlen(name) + 3
+# endif
+ );
+ bufscpy(bn, name);
+ np = tp = bn->string;
+ tp += dl;
+ *tp++ = '_';
+ *tp++ = '0'+n;
+ catchints();
+# if has_mktemp
+ VOID strcpy(tp, "XXXXXX");
+ if (!mktemp(np) || !*np)
+ faterror("can't make temporary file name `%.*s%c_%cXXXXXX'",
+ (int)dl, name, SLASH, '0'+n
+ );
+# else
+ /*
+ * Posix 1003.1-1990 has no reliable way
+ * to create a unique file in a named directory.
+ * We fudge here. If the working file name is abcde,
+ * the temp filename is _Ncde where N is a digit.
+ */
+ name += dl;
+ if (*name) name++;
+ if (*name) name++;
+ VOID strcpy(tp, name);
+# endif
+ dirtfmaker[n] = real;
+ return np;
+}
+
+ void
+dirtempunlink()
+/* Clean up makedirtemp() files. May be invoked by signal handler. */
+{
+ register int i;
+ enum maker m;
+
+ for (i = DIRTEMPNAMES; 0 <= --i; )
+ if ((m = dirtfmaker[i]) != notmade) {
+ if (m == effective)
+ seteid();
+ VOID un_link(dirtfname[i].string);
+ if (m == effective)
+ setrid();
+ dirtfmaker[i] = notmade;
+ }
+}
+
+
+ int
+#if has_prototypes
+chnamemod(FILE **fromp, char const *from, char const *to, mode_t mode)
+ /* The `#if has_prototypes' is needed because mode_t might promote to int. */
+#else
+ chnamemod(fromp,from,to,mode) FILE **fromp; char const *from,*to; mode_t mode;
+#endif
+/*
+ * Rename a file (with optional stream pointer *FROMP) from FROM to TO.
+ * FROM already exists.
+ * Change its mode to MODE, before renaming if possible.
+ * If FROMP, close and clear *FROMP before renaming it.
+ * Unlink TO if it already exists.
+ * Return -1 on error (setting errno), 0 otherwise.
+ */
+{
+# if bad_a_rename
+ /*
+ * This host is brain damaged. A race condition is possible
+ * while the lock file is temporarily writable.
+ * There doesn't seem to be a workaround.
+ */
+ mode_t mode_while_renaming = mode|S_IWUSR;
+# else
+# define mode_while_renaming mode
+# endif
+ if (fromp) {
+# if has_fchmod
+ if (fchmod(fileno(*fromp), mode_while_renaming) != 0)
+ return -1;
+# endif
+ Ozclose(fromp);
+ }
+# if has_fchmod
+ else
+# endif
+ if (chmod(from, mode_while_renaming) != 0)
+ return -1;
+
+# if !has_rename || bad_b_rename
+ VOID un_link(to);
+ /*
+ * We need not check the result;
+ * link() or rename() will catch it.
+ * No harm is done if TO does not exist.
+ * However, there's a short window of inconsistency
+ * during which TO does not exist.
+ */
+# endif
+
+ return
+# if !has_rename
+ do_link(from,to) != 0 ? -1 : un_link(from)
+# else
+ rename(from, to) != 0
+# if has_NFS
+ && errno != ENOENT
+# endif
+ ? -1
+# if bad_a_rename
+ : mode != mode_while_renaming ? chmod(to, mode)
+# endif
+ : 0
+# endif
+ ;
+
+# undef mode_while_renaming
+}
+
+
+
+ int
+findlock(delete, target)
+ int delete;
+ struct hshentry **target;
+/*
+ * Find the first lock held by caller and return a pointer
+ * to the locked delta; also removes the lock if DELETE.
+ * If one lock, put it into *TARGET.
+ * Return 0 for no locks, 1 for one, 2 for two or more.
+ */
+{
+ register struct lock *next, **trail, **found;
+
+ found = 0;
+ for (trail = &Locks; (next = *trail); trail = &next->nextlock)
+ if (strcmp(getcaller(), next->login) == 0) {
+ if (found) {
+ error("multiple revisions locked by %s; please specify one", getcaller());
+ return 2;
+ }
+ found = trail;
+ }
+ if (!found)
+ return 0;
+ next = *found;
+ *target = next->delta;
+ if (delete) {
+ next->delta->lockedby = nil;
+ *found = next->nextlock;
+ }
+ return 1;
+}
+
+ int
+addlock(delta)
+ struct hshentry * delta;
+/*
+ * Add a lock held by caller to DELTA and yield 1 if successful.
+ * Print an error message and yield -1 if no lock is added because
+ * DELTA is locked by somebody other than caller.
+ * Return 0 if the caller already holds the lock.
+ */
+{
+ register struct lock *next;
+
+ next=Locks;
+ for (next = Locks; next; next = next->nextlock)
+ if (cmpnum(delta->num, next->delta->num) == 0)
+ if (strcmp(getcaller(), next->login) == 0)
+ return 0;
+ else {
+ error("revision %s already locked by %s",
+ delta->num, next->login
+ );
+ return -1;
+ }
+ next = ftalloc(struct lock);
+ delta->lockedby = next->login = getcaller();
+ next->delta = delta;
+ next->nextlock = Locks;
+ Locks = next;
+ return 1;
+}
+
+
+ int
+addsymbol(num, name, rebind)
+ char const *num, *name;
+ int rebind;
+/*
+ * Associate with revision NUM the new symbolic NAME.
+ * If NAME already exists and REBIND is set, associate NAME with NUM;
+ * otherwise, print an error message and return false;
+ * Return true if successful.
+ */
+{
+ register struct assoc *next;
+
+ for (next = Symbols; next; next = next->nextassoc)
+ if (strcmp(name, next->symbol) == 0)
+ if (rebind || strcmp(next->num,num) == 0) {
+ next->num = num;
+ return true;
+ } else {
+ error("symbolic name %s already bound to %s",
+ name, next->num
+ );
+ return false;
+ }
+ next = ftalloc(struct assoc);
+ next->symbol = name;
+ next->num = num;
+ next->nextassoc = Symbols;
+ Symbols = next;
+ return true;
+}
+
+
+
+ char const *
+getcaller()
+/* Get the caller's login name. */
+{
+# if has_setuid
+ return getusername(euid()!=ruid());
+# else
+ return getusername(false);
+# endif
+}
+
+
+ int
+checkaccesslist()
+/*
+ * Return true if caller is the superuser, the owner of the
+ * file, the access list is empty, or caller is on the access list.
+ * Otherwise, print an error message and return false.
+ */
+{
+ register struct access const *next;
+
+ if (!AccessList || myself(RCSstat.st_uid) || strcmp(getcaller(),"root")==0)
+ return true;
+
+ next = AccessList;
+ do {
+ if (strcmp(getcaller(), next->login) == 0)
+ return true;
+ } while ((next = next->nextaccess));
+
+ error("user %s not on the access list", getcaller());
+ return false;
+}
+
+
+ int
+dorewrite(lockflag, changed)
+ int lockflag, changed;
+/*
+ * Do nothing if LOCKFLAG is zero.
+ * Prepare to rewrite an RCS file if CHANGED is positive.
+ * Stop rewriting if CHANGED is zero, because there won't be any changes.
+ * Fail if CHANGED is negative.
+ * Return true on success.
+ */
+{
+ int r, e;
+
+ if (lockflag)
+ if (changed) {
+ if (changed < 0)
+ return false;
+ putadmin(frewrite);
+ puttree(Head, frewrite);
+ aprintf(frewrite, "\n\n%s%c", Kdesc, nextc);
+ foutptr = frewrite;
+ } else {
+ Ozclose(&frewrite);
+ seteid();
+ ignoreints();
+ r = un_link(newRCSfilename);
+ e = errno;
+ keepdirtemp(newRCSfilename);
+ restoreints();
+ setrid();
+ if (r != 0) {
+ enerror(e, RCSfilename);
+ return false;
+ }
+ }
+ return true;
+}
+
+ int
+donerewrite(changed)
+ int changed;
+/*
+ * Finish rewriting an RCS file if CHANGED is nonzero.
+ * Return true on success.
+ */
+{
+ int r, e;
+
+ if (changed && !nerror) {
+ if (finptr) {
+ fastcopy(finptr, frewrite);
+ Izclose(&finptr);
+ }
+ if (1 < RCSstat.st_nlink)
+ warn("breaking hard link to %s", RCSfilename);
+ seteid();
+ ignoreints();
+ r = chnamemod(&frewrite, newRCSfilename, RCSfilename,
+ RCSstat.st_mode & ~(S_IWUSR|S_IWGRP|S_IWOTH)
+ );
+ e = errno;
+ keepdirtemp(newRCSfilename);
+ restoreints();
+ setrid();
+ if (r != 0) {
+ enerror(e, RCSfilename);
+ error("saved in %s", newRCSfilename);
+ dirtempunlink();
+ return false;
+ }
+ }
+ return true;
+}
+
+ void
+aflush(f)
+ FILE *f;
+{
+ if (fflush(f) != 0)
+ Oerror();
+}
diff --git a/gnu/usr.bin/rcs/lib/rcsfcmp.c b/gnu/usr.bin/rcs/lib/rcsfcmp.c
new file mode 100644
index 0000000..75a6bbc
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/rcsfcmp.c
@@ -0,0 +1,321 @@
+/*
+ * RCS file comparison
+ */
+/*****************************************************************************
+ * rcsfcmp()
+ * Testprogram: define FCMPTEST
+ *****************************************************************************
+ */
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+
+
+
+/* $Log: rcsfcmp.c,v $
+ * Revision 5.9 1991/10/07 17:32:46 eggert
+ * Count log lines correctly.
+ *
+ * Revision 5.8 1991/08/19 03:13:55 eggert
+ * Tune.
+ *
+ * Revision 5.7 1991/04/21 11:58:22 eggert
+ * Fix errno bug. Add MS-DOS support.
+ *
+ * Revision 5.6 1991/02/28 19:18:47 eggert
+ * Open work file at most once.
+ *
+ * Revision 5.5 1990/11/27 09:26:05 eggert
+ * Fix comment leader bug.
+ *
+ * Revision 5.4 1990/11/01 05:03:42 eggert
+ * Permit arbitrary data in logs and comment leaders.
+ *
+ * Revision 5.3 1990/09/11 02:41:15 eggert
+ * Don't ignore differences inside keyword strings if -ko is set.
+ *
+ * Revision 5.1 1990/08/29 07:13:58 eggert
+ * Clean old log messages too.
+ *
+ * Revision 5.0 1990/08/22 08:12:49 eggert
+ * Don't append "checked in with -k by " log to logs,
+ * so that checking in a program with -k doesn't change it.
+ * Ansify and Posixate. Remove lint.
+ *
+ * Revision 4.5 89/05/01 15:12:42 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.4 88/08/09 19:12:50 eggert
+ * Shrink stdio code size.
+ *
+ * Revision 4.3 87/12/18 11:40:02 narten
+ * lint cleanups (Guy Harris)
+ *
+ * Revision 4.2 87/10/18 10:33:06 narten
+ * updting version number. Changes relative to 1.1 actually relative to
+ * 4.1
+ *
+ * Revision 1.2 87/03/27 14:22:19 jenkins
+ * Port to suns
+ *
+ * Revision 4.1 83/05/10 16:24:04 wft
+ * Marker matching now uses trymatch(). Marker pattern is now
+ * checked precisely.
+ *
+ * Revision 3.1 82/12/04 13:21:40 wft
+ * Initial revision.
+ *
+ */
+
+/*
+#define FCMPTEST
+*/
+/* Testprogram; prints out whether two files are identical,
+ * except for keywords
+ */
+
+#include "rcsbase.h"
+
+libId(fcmpId, "$Id: rcsfcmp.c,v 5.9 1991/10/07 17:32:46 eggert Exp $")
+
+ static int
+discardkeyval(c, f)
+ register int c;
+ register RILE *f;
+{
+ for (;;)
+ switch (c) {
+ case KDELIM:
+ case '\n':
+ return c;
+ default:
+ Igeteof(f, c, return EOF;);
+ break;
+ }
+}
+
+ int
+rcsfcmp(xfp, xstatp, ufname, delta)
+ register RILE *xfp;
+ struct stat const *xstatp;
+ char const *ufname;
+ struct hshentry const *delta;
+/* Compare the files xfp and ufname. Return zero
+ * if xfp has the same contents as ufname and neither has keywords,
+ * otherwise -1 if they are the same ignoring keyword values,
+ * and 1 if they differ even ignoring
+ * keyword values. For the LOG-keyword, rcsfcmp skips the log message
+ * given by the parameter delta in xfp. Thus, rcsfcmp returns nonpositive
+ * if xfp contains the same as ufname, with the keywords expanded.
+ * Implementation: character-by-character comparison until $ is found.
+ * If a $ is found, read in the marker keywords; if they are real keywords
+ * and identical, read in keyword value. If value is terminated properly,
+ * disregard it and optionally skip log message; otherwise, compare value.
+ */
+{
+ register int xc, uc;
+ char xkeyword[keylength+2];
+ int eqkeyvals;
+ register RILE *ufp;
+ register int xeof, ueof;
+ register char * tp;
+ register char const *sp;
+ int result;
+ enum markers match1;
+ struct stat ustat;
+
+ if (!(ufp = Iopen(ufname, FOPEN_R_WORK, &ustat))) {
+ efaterror(ufname);
+ }
+ xeof = ueof = false;
+ if (Expand==OLD_EXPAND) {
+ if (!(result = xstatp->st_size!=ustat.st_size)) {
+# if has_mmap && large_memory
+ result = !!memcmp(xfp->base,ufp->base,(size_t)xstatp->st_size);
+# else
+ for (;;) {
+ /* get the next characters */
+ Igeteof(xfp, xc, xeof=true;);
+ Igeteof(ufp, uc, ueof=true;);
+ if (xeof | ueof)
+ goto eof;
+ if (xc != uc)
+ goto return1;
+ }
+# endif
+ }
+ } else {
+ xc = 0;
+ uc = 0; /* Keep lint happy. */
+ result = 0;
+
+ for (;;) {
+ if (xc != KDELIM) {
+ /* get the next characters */
+ Igeteof(xfp, xc, xeof=true;);
+ Igeteof(ufp, uc, ueof=true;);
+ if (xeof | ueof)
+ goto eof;
+ } else {
+ /* try to get both keywords */
+ tp = xkeyword;
+ for (;;) {
+ Igeteof(xfp, xc, xeof=true;);
+ Igeteof(ufp, uc, ueof=true;);
+ if (xeof | ueof)
+ goto eof;
+ if (xc != uc)
+ break;
+ switch (xc) {
+ default:
+ if (xkeyword+keylength <= tp)
+ break;
+ *tp++ = xc;
+ continue;
+ case '\n': case KDELIM: case VDELIM:
+ break;
+ }
+ break;
+ }
+ if (
+ (xc==KDELIM || xc==VDELIM) && (uc==KDELIM || uc==VDELIM) &&
+ (*tp = xc, (match1 = trymatch(xkeyword)) != Nomatch)
+ ) {
+#ifdef FCMPTEST
+ VOID printf("found common keyword %s\n",xkeyword);
+#endif
+ result = -1;
+ for (;;) {
+ if (xc != uc) {
+ xc = discardkeyval(xc, xfp);
+ uc = discardkeyval(uc, ufp);
+ if ((xeof = xc==EOF) | (ueof = uc==EOF))
+ goto eof;
+ eqkeyvals = false;
+ break;
+ }
+ switch (xc) {
+ default:
+ Igeteof(xfp, xc, xeof=true;);
+ Igeteof(ufp, uc, ueof=true;);
+ if (xeof | ueof)
+ goto eof;
+ continue;
+
+ case '\n': case KDELIM:
+ eqkeyvals = true;
+ break;
+ }
+ break;
+ }
+ if (xc != uc)
+ goto return1;
+ if (xc==KDELIM) {
+ /* Skip closing KDELIM. */
+ Igeteof(xfp, xc, xeof=true;);
+ Igeteof(ufp, uc, ueof=true;);
+ if (xeof | ueof)
+ goto eof;
+ /* if the keyword is LOG, also skip the log message in xfp*/
+ if (match1==Log) {
+ /* first, compute the number of line feeds in log msg */
+ unsigned lncnt;
+ size_t ls, ccnt;
+ sp = delta->log.string;
+ ls = delta->log.size;
+ if (ls<sizeof(ciklog)-1 || memcmp(sp,ciklog,sizeof(ciklog)-1)) {
+ /* This log message was inserted. */
+ lncnt = 3;
+ while (ls--) if (*sp++=='\n') lncnt++;
+ for (;;) {
+ if (xc=='\n')
+ if(--lncnt==0) break;
+ Igeteof(xfp, xc, goto returnresult;);
+ }
+ /* skip last comment leader */
+ /* Can't just skip another line here, because there may be */
+ /* additional characters on the line (after the Log....$) */
+ for (ccnt=Comment.size; ccnt--; ) {
+ Igeteof(xfp, xc, goto returnresult;);
+ if(xc=='\n') break;
+ /*
+ * Read to the end of the comment leader or '\n',
+ * whatever comes first. Some editors strip
+ * trailing white space from a leader like " * ".
+ */
+ }
+ }
+ }
+ } else {
+ /* both end in the same character, but not a KDELIM */
+ /* must compare string values.*/
+#ifdef FCMPTEST
+ VOID printf("non-terminated keywords %s, potentially different values\n",xkeyword);
+#endif
+ if (!eqkeyvals)
+ goto return1;
+ }
+ }
+ }
+ if (xc != uc)
+ goto return1;
+ }
+ }
+
+ eof:
+ if (xeof==ueof)
+ goto returnresult;
+ return1:
+ result = 1;
+ returnresult:
+ Ifclose(ufp);
+ return result;
+}
+
+
+
+#ifdef FCMPTEST
+
+char const cmdid[] = "rcsfcmp";
+
+main(argc, argv)
+int argc; char *argv[];
+/* first argument: comment leader; 2nd: log message, 3rd: expanded file,
+ * 4th: unexpanded file
+ */
+{ struct hshentry delta;
+
+ Comment.string = argv[1];
+ Comment.size = strlen(argv[1]);
+ delta.log.string = argv[2];
+ delta.log.size = strlen(argv[2]);
+ if (rcsfcmp(Iopen(argv[3], FOPEN_R_WORK, (struct stat*)0), argv[4], &delta))
+ VOID printf("files are the same\n");
+ else VOID printf("files are different\n");
+}
+#endif
diff --git a/gnu/usr.bin/rcs/lib/rcsfnms.c b/gnu/usr.bin/rcs/lib/rcsfnms.c
new file mode 100644
index 0000000..02562f0
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/rcsfnms.c
@@ -0,0 +1,1088 @@
+/*
+ * RCS file name handling
+ */
+/****************************************************************************
+ * creation and deletion of /tmp temporaries
+ * pairing of RCS file names and working file names.
+ * Testprogram: define PAIRTEST
+ ****************************************************************************
+ */
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+
+
+/* $Log: rcsfnms.c,v $
+ * Revision 5.8 1991/09/24 00:28:40 eggert
+ * Don't export bindex().
+ *
+ * Revision 5.7 1991/08/19 03:13:55 eggert
+ * Fix messages when rcswriteopen fails.
+ * Look in $TMP and $TEMP if $TMPDIR isn't set. Tune.
+ *
+ * Revision 5.6 1991/04/21 11:58:23 eggert
+ * Fix errno bugs. Add -x, RCSINIT, MS-DOS support.
+ *
+ * Revision 5.5 1991/02/26 17:48:38 eggert
+ * Fix setuid bug. Support new link behavior.
+ * Define more portable getcwd().
+ *
+ * Revision 5.4 1990/11/01 05:03:43 eggert
+ * Permit arbitrary data in comment leaders.
+ *
+ * Revision 5.3 1990/09/14 22:56:16 hammer
+ * added more filename extensions and their comment leaders
+ *
+ * Revision 5.2 1990/09/04 08:02:23 eggert
+ * Fix typo when !RCSSEP.
+ *
+ * Revision 5.1 1990/08/29 07:13:59 eggert
+ * Work around buggy compilers with defective argument promotion.
+ *
+ * Revision 5.0 1990/08/22 08:12:50 eggert
+ * Ignore signals when manipulating the semaphore file.
+ * Modernize list of file name extensions.
+ * Permit paths of arbitrary length. Beware file names beginning with "-".
+ * Remove compile-time limits; use malloc instead.
+ * Permit dates past 1999/12/31. Make lock and temp files faster and safer.
+ * Ansify and Posixate.
+ * Don't use access(). Fix test for non-regular files. Tune.
+ *
+ * Revision 4.8 89/05/01 15:09:41 narten
+ * changed getwd to not stat empty directories.
+ *
+ * Revision 4.7 88/08/09 19:12:53 eggert
+ * Fix troff macro comment leader bug; add Prolog; allow cc -R; remove lint.
+ *
+ * Revision 4.6 87/12/18 11:40:23 narten
+ * additional file types added from 4.3 BSD version, and SPARC assembler
+ * comment character added. Also, more lint cleanups. (Guy Harris)
+ *
+ * Revision 4.5 87/10/18 10:34:16 narten
+ * Updating version numbers. Changes relative to 1.1 actually relative
+ * to verion 4.3
+ *
+ * Revision 1.3 87/03/27 14:22:21 jenkins
+ * Port to suns
+ *
+ * Revision 1.2 85/06/26 07:34:28 svb
+ * Comment leader '% ' for '*.tex' files added.
+ *
+ * Revision 4.3 83/12/15 12:26:48 wft
+ * Added check for KDELIM in file names to pairfilenames().
+ *
+ * Revision 4.2 83/12/02 22:47:45 wft
+ * Added csh, red, and sl file name suffixes.
+ *
+ * Revision 4.1 83/05/11 16:23:39 wft
+ * Added initialization of Dbranch to InitAdmin(). Canged pairfilenames():
+ * 1. added copying of path from workfile to RCS file, if RCS file is omitted;
+ * 2. added getting the file status of RCS and working files;
+ * 3. added ignoring of directories.
+ *
+ * Revision 3.7 83/05/11 15:01:58 wft
+ * Added comtable[] which pairs file name suffixes with comment leaders;
+ * updated InitAdmin() accordingly.
+ *
+ * Revision 3.6 83/04/05 14:47:36 wft
+ * fixed Suffix in InitAdmin().
+ *
+ * Revision 3.5 83/01/17 18:01:04 wft
+ * Added getwd() and rename(); these can be removed by defining
+ * V4_2BSD, since they are not needed in 4.2 bsd.
+ * Changed sys/param.h to sys/types.h.
+ *
+ * Revision 3.4 82/12/08 21:55:20 wft
+ * removed unused variable.
+ *
+ * Revision 3.3 82/11/28 20:31:37 wft
+ * Changed mktempfile() to store the generated file names.
+ * Changed getfullRCSname() to store the file and pathname, and to
+ * delete leading "../" and "./".
+ *
+ * Revision 3.2 82/11/12 14:29:40 wft
+ * changed pairfilenames() to handle file.sfx,v; also deleted checkpathnosfx(),
+ * checksuffix(), checkfullpath(). Semaphore name generation updated.
+ * mktempfile() now checks for nil path; freefilename initialized properly.
+ * Added Suffix .h to InitAdmin. Added testprogram PAIRTEST.
+ * Moved rmsema, trysema, trydiraccess, getfullRCSname from rcsutil.c to here.
+ *
+ * Revision 3.1 82/10/18 14:51:28 wft
+ * InitAdmin() now initializes StrictLocks=STRICT_LOCKING (def. in rcsbase.h).
+ * renamed checkpath() to checkfullpath().
+ */
+
+
+#include "rcsbase.h"
+
+libId(fnmsId, "$Id: rcsfnms.c,v 5.8 1991/09/24 00:28:40 eggert Exp $")
+
+char const *RCSfilename;
+char *workfilename;
+FILE *workstdout;
+struct stat RCSstat;
+char const *suffixes;
+
+static char const rcsdir[] = "RCS";
+#define rcsdirlen (sizeof(rcsdir)-1)
+
+static struct buf RCSbuf, RCSb;
+static int RCSerrno;
+
+
+/* Temp file names to be unlinked when done, if they are not nil. */
+#define TEMPNAMES 5 /* must be at least DIRTEMPNAMES (see rcsedit.c) */
+static char *volatile tfnames[TEMPNAMES];
+
+
+struct compair {
+ char const *suffix, *comlead;
+};
+
+static struct compair const comtable[] = {
+/* comtable pairs each filename suffix with a comment leader. The comment */
+/* leader is placed before each line generated by the $Log keyword. This */
+/* table is used to guess the proper comment leader from the working file's */
+/* suffix during initial ci (see InitAdmin()). Comment leaders are needed */
+/* for languages without multiline comments; for others they are optional. */
+ "a", "-- ", /* Ada */
+ "ada", "-- ",
+ "asm", ";; ", /* assembler (MS-DOS) */
+ "bat", ":: ", /* batch (MS-DOS) */
+ "c", " * ", /* C */
+ "c++", "// ", /* C++ in all its infinite guises */
+ "cc", "// ",
+ "cpp", "// ",
+ "cxx", "// ",
+ "cl", ";;; ", /* Common Lisp */
+ "cmd", ":: ", /* command (OS/2) */
+ "cmf", "c ", /* CM Fortran */
+ "cs", " * ", /* C* */
+ "el", "; ", /* Emacs Lisp */
+ "f", "c ", /* Fortran */
+ "for", "c ",
+ "h", " * ", /* C-header */
+ "hpp", "// ", /* C++ header */
+ "hxx", "// ",
+ "l", " * ", /* lex NOTE: conflict between lex and franzlisp */
+ "lisp",";;; ", /* Lucid Lisp */
+ "lsp", ";; ", /* Microsoft Lisp */
+ "mac", ";; ", /* macro (DEC-10, MS-DOS, PDP-11, VMS, etc) */
+ "me", ".\\\" ",/* me-macros t/nroff*/
+ "ml", "; ", /* mocklisp */
+ "mm", ".\\\" ",/* mm-macros t/nroff*/
+ "ms", ".\\\" ",/* ms-macros t/nroff*/
+ "p", " * ", /* Pascal */
+ "pas", " * ",
+ "pl", "% ", /* Prolog */
+ "tex", "% ", /* TeX */
+ "y", " * ", /* yacc */
+ nil, "# " /* default for unknown suffix; must always be last */
+};
+
+#if has_mktemp
+ static char const *
+tmp()
+/* Yield the name of the tmp directory. */
+{
+ static char const *s;
+ if (!s
+ && !(s = cgetenv("TMPDIR")) /* Unix tradition */
+ && !(s = cgetenv("TMP")) /* DOS tradition */
+ && !(s = cgetenv("TEMP")) /* another DOS tradition */
+ )
+ s = TMPDIR;
+ return s;
+}
+#endif
+
+ char const *
+maketemp(n)
+ int n;
+/* Create a unique filename using n and the process id and store it
+ * into the nth slot in tfnames.
+ * Because of storage in tfnames, tempunlink() can unlink the file later.
+ * Returns a pointer to the filename created.
+ */
+{
+ char *p;
+ char const *t = tfnames[n];
+
+ if (t)
+ return t;
+
+ catchints();
+ {
+# if has_mktemp
+ char const *tp = tmp();
+ p = testalloc(strlen(tp) + 10);
+ VOID sprintf(p, "%s%cT%cXXXXXX", tp, SLASH, '0'+n);
+ if (!mktemp(p) || !*p)
+ faterror("can't make temporary file name `%s%cT%cXXXXXX'",
+ tp, SLASH, '0'+n
+ );
+# else
+ static char tfnamebuf[TEMPNAMES][L_tmpnam];
+ p = tfnamebuf[n];
+ if (!tmpnam(p) || !*p)
+# ifdef P_tmpdir
+ faterror("can't make temporary file name `%s...'",P_tmpdir);
+# else
+ faterror("can't make temporary file name");
+# endif
+# endif
+ }
+
+ tfnames[n] = p;
+ return p;
+}
+
+ void
+tempunlink()
+/* Clean up maketemp() files. May be invoked by signal handler.
+ */
+{
+ register int i;
+ register char *p;
+
+ for (i = TEMPNAMES; 0 <= --i; )
+ if ((p = tfnames[i])) {
+ VOID unlink(p);
+ /*
+ * We would tfree(p) here,
+ * but this might dump core if we're handing a signal.
+ * We're about to exit anyway, so we won't bother.
+ */
+ tfnames[i] = 0;
+ }
+}
+
+
+ static char const *
+bindex(sp,ch)
+ register char const *sp;
+ int ch;
+/* Function: Finds the last occurrence of character c in string sp
+ * and returns a pointer to the character just beyond it. If the
+ * character doesn't occur in the string, sp is returned.
+ */
+{
+ register char const c=ch, *r;
+ r = sp;
+ while (*sp) {
+ if (*sp++ == c) r=sp;
+ }
+ return r;
+}
+
+
+
+ static int
+suffix_matches(suffix, pattern)
+ register char const *suffix, *pattern;
+{
+ register int c;
+ if (!pattern)
+ return true;
+ for (;;)
+ switch (*suffix++ - (c = *pattern++)) {
+ case 0:
+ if (!c)
+ return true;
+ break;
+
+ case 'A'-'a':
+ if (ctab[c] == Letter)
+ break;
+ /* fall into */
+ default:
+ return false;
+ }
+}
+
+
+ static void
+InitAdmin()
+/* function: initializes an admin node */
+{
+ register char const *Suffix;
+ register int i;
+
+ Head=nil; Dbranch=nil; AccessList=nil; Symbols=nil; Locks=nil;
+ StrictLocks=STRICT_LOCKING;
+
+ /* guess the comment leader from the suffix*/
+ Suffix=bindex(workfilename, '.');
+ if (Suffix==workfilename) Suffix= ""; /* empty suffix; will get default*/
+ for (i=0; !suffix_matches(Suffix,comtable[i].suffix); i++)
+ ;
+ Comment.string = comtable[i].comlead;
+ Comment.size = strlen(comtable[i].comlead);
+ Lexinit(); /* note: if !finptr, reads nothing; only initializes */
+}
+
+
+/* 'cpp' does not like this line. It seems to be the leading '_' in the */
+/* second occurence of '_POSIX_NO_TRUNC'. It evaluates correctly with */
+/* just the first term so lets just do that for now. */
+/*#if defined(_POSIX_NO_TRUNC) && _POSIX_NO_TRUNC!=-1*/
+#if defined(_POSIX_NO_TRUNC)
+# define LONG_NAMES_MAY_BE_SILENTLY_TRUNCATED 0
+#else
+# define LONG_NAMES_MAY_BE_SILENTLY_TRUNCATED 1
+#endif
+
+#if LONG_NAMES_MAY_BE_SILENTLY_TRUNCATED
+#ifdef NAME_MAX
+# define filenametoolong(path) (NAME_MAX < strlen(basename(path)))
+#else
+ static int
+filenametoolong(path)
+ char *path;
+/* Yield true if the last file name in PATH is too long. */
+{
+ static unsigned long dot_namemax;
+
+ register size_t namelen;
+ register char *base;
+ register unsigned long namemax;
+
+ base = path + dirlen(path);
+ namelen = strlen(base);
+ if (namelen <= _POSIX_NAME_MAX) /* fast check for shorties */
+ return false;
+ if (base != path) {
+ *--base = 0;
+ namemax = pathconf(path, _PC_NAME_MAX);
+ *base = SLASH;
+ } else {
+ /* Cache the results for the working directory, for speed. */
+ if (!dot_namemax)
+ dot_namemax = pathconf(".", _PC_NAME_MAX);
+ namemax = dot_namemax;
+ }
+ /* If pathconf() yielded -1, namemax is now ULONG_MAX. */
+ return namemax<namelen;
+}
+#endif
+#endif
+
+ void
+bufalloc(b, size)
+ register struct buf *b;
+ size_t size;
+/* Ensure *B is a name buffer of at least SIZE bytes.
+ * *B's old contents can be freed; *B's new contents are undefined.
+ */
+{
+ if (b->size < size) {
+ if (b->size)
+ tfree(b->string);
+ else
+ b->size = sizeof(malloc_type);
+ while (b->size < size)
+ b->size <<= 1;
+ b->string = tnalloc(char, b->size);
+ }
+}
+
+ void
+bufrealloc(b, size)
+ register struct buf *b;
+ size_t size;
+/* like bufalloc, except *B's old contents, if any, are preserved */
+{
+ if (b->size < size) {
+ if (!b->size)
+ bufalloc(b, size);
+ else {
+ while ((b->size <<= 1) < size)
+ ;
+ b->string = trealloc(char, b->string, b->size);
+ }
+ }
+}
+
+ void
+bufautoend(b)
+ struct buf *b;
+/* Free an auto buffer at block exit. */
+{
+ if (b->size)
+ tfree(b->string);
+}
+
+ struct cbuf
+bufremember(b, s)
+ struct buf *b;
+ size_t s;
+/*
+ * Free the buffer B with used size S.
+ * Yield a cbuf with identical contents.
+ * The cbuf will be reclaimed when this input file is finished.
+ */
+{
+ struct cbuf cb;
+
+ if ((cb.size = s))
+ cb.string = fremember(trealloc(char, b->string, s));
+ else {
+ bufautoend(b); /* not really auto */
+ cb.string = "";
+ }
+ return cb;
+}
+
+ char *
+bufenlarge(b, alim)
+ register struct buf *b;
+ char const **alim;
+/* Make *B larger. Set *ALIM to its new limit, and yield the relocated value
+ * of its old limit.
+ */
+{
+ size_t s = b->size;
+ bufrealloc(b, s + 1);
+ *alim = b->string + b->size;
+ return b->string + s;
+}
+
+ void
+bufscat(b, s)
+ struct buf *b;
+ char const *s;
+/* Concatenate S to B's end. */
+{
+ size_t blen = b->string ? strlen(b->string) : 0;
+ bufrealloc(b, blen+strlen(s)+1);
+ VOID strcpy(b->string+blen, s);
+}
+
+ void
+bufscpy(b, s)
+ struct buf *b;
+ char const *s;
+/* Copy S into B. */
+{
+ bufalloc(b, strlen(s)+1);
+ VOID strcpy(b->string, s);
+}
+
+
+ char const *
+basename(p)
+ char const *p;
+/* Yield the address of the base filename of the pathname P. */
+{
+ register char const *b = p, *q = p;
+ for (;;)
+ switch (*q++) {
+ case SLASHes: b = q; break;
+ case 0: return b;
+ }
+}
+
+ size_t
+dirlen(p)
+ char const *p;
+/* Yield the length of P's directory, including its trailing SLASH. */
+{
+ return basename(p) - p;
+}
+
+
+ static size_t
+suffixlen(x)
+ char const *x;
+/* Yield the length of X, an RCS filename suffix. */
+{
+ register char const *p;
+
+ p = x;
+ for (;;)
+ switch (*p) {
+ case 0: case SLASHes:
+ return p - x;
+
+ default:
+ ++p;
+ continue;
+ }
+}
+
+ char const *
+rcssuffix(name)
+ char const *name;
+/* Yield the suffix of NAME if it is an RCS filename, 0 otherwise. */
+{
+ char const *x, *p, *nz;
+ size_t dl, nl, xl;
+
+ nl = strlen(name);
+ nz = name + nl;
+ x = suffixes;
+ do {
+ if ((xl = suffixlen(x))) {
+ if (xl <= nl && memcmp(p = nz-xl, x, xl) == 0)
+ return p;
+ } else {
+ dl = dirlen(name);
+ if (
+ rcsdirlen < dl &&
+ !memcmp(p = name+(dl-=rcsdirlen+1), rcsdir, rcsdirlen) &&
+ (!dl || isSLASH(*--p))
+ )
+ return nz;
+ }
+ x += xl;
+ } while (*x++);
+ return 0;
+}
+
+ /*ARGSUSED*/ RILE *
+rcsreadopen(RCSname, status, mustread)
+ struct buf *RCSname;
+ struct stat *status;
+ int mustread;
+/* Open RCSNAME for reading and yield its FILE* descriptor.
+ * If successful, set *STATUS to its status.
+ * Pass this routine to pairfilenames() for read-only access to the file. */
+{
+ return Iopen(RCSname->string, FOPEN_R, status);
+}
+
+ static int
+finopen(rcsopen, mustread)
+ RILE *(*rcsopen)P((struct buf*,struct stat*,int));
+ int mustread;
+/*
+ * Use RCSOPEN to open an RCS file; MUSTREAD is set if the file must be read.
+ * Set finptr to the result and yield true if successful.
+ * RCSb holds the file's name.
+ * Set RCSbuf to the best RCS name found so far, and RCSerrno to its errno.
+ * Yield true if successful or if an unusual failure.
+ */
+{
+ int interesting, preferold;
+
+ /*
+ * We prefer an old name to that of a nonexisting new RCS file,
+ * unless we tried locking the old name and failed.
+ */
+ preferold = RCSbuf.string[0] && (mustread||frewrite);
+
+ finptr = (*rcsopen)(&RCSb, &RCSstat, mustread);
+ interesting = finptr || errno!=ENOENT;
+ if (interesting || !preferold) {
+ /* Use the new name. */
+ RCSerrno = errno;
+ bufscpy(&RCSbuf, RCSb.string);
+ }
+ return interesting;
+}
+
+ static int
+fin2open(d, dlen, base, baselen, x, xlen, rcsopen, mustread)
+ char const *d, *base, *x;
+ size_t dlen, baselen, xlen;
+ RILE *(*rcsopen)P((struct buf*,struct stat*,int));
+ int mustread;
+/*
+ * D is a directory name with length DLEN (including trailing slash).
+ * BASE is a filename with length BASELEN.
+ * X is an RCS filename suffix with length XLEN.
+ * Use RCSOPEN to open an RCS file; MUSTREAD is set if the file must be read.
+ * Yield true if successful.
+ * Try dRCS/basex first; if that fails and x is nonempty, try dbasex.
+ * Put these potential names in RCSb.
+ * Set RCSbuf to the best RCS name found so far, and RCSerrno to its errno.
+ * Yield true if successful or if an unusual failure.
+ */
+{
+ register char *p;
+
+ bufalloc(&RCSb, dlen + rcsdirlen + 1 + baselen + xlen + 1);
+
+ /* Try dRCS/basex. */
+ VOID memcpy(p = RCSb.string, d, dlen);
+ VOID memcpy(p += dlen, rcsdir, rcsdirlen);
+ p += rcsdirlen;
+ *p++ = SLASH;
+ VOID memcpy(p, base, baselen);
+ VOID memcpy(p += baselen, x, xlen);
+ p[xlen] = 0;
+ if (xlen) {
+ if (finopen(rcsopen, mustread))
+ return true;
+
+ /* Try dbasex. */
+ /* Start from scratch, because finopen() may have changed RCSb. */
+ VOID memcpy(p = RCSb.string, d, dlen);
+ VOID memcpy(p += dlen, base, baselen);
+ VOID memcpy(p += baselen, x, xlen);
+ p[xlen] = 0;
+ }
+ return finopen(rcsopen, mustread);
+}
+
+ int
+pairfilenames(argc, argv, rcsopen, mustread, quiet)
+ int argc;
+ char **argv;
+ RILE *(*rcsopen)P((struct buf*,struct stat*,int));
+ int mustread, quiet;
+/* Function: Pairs the filenames pointed to by argv; argc indicates
+ * how many there are.
+ * Places a pointer to the RCS filename into RCSfilename,
+ * and a pointer to the name of the working file into workfilename.
+ * If both the workfilename and the RCS filename are given, and workstdout
+ * is set, a warning is printed.
+ *
+ * If the RCS file exists, places its status into RCSstat.
+ *
+ * If the RCS file exists, it is RCSOPENed for reading, the file pointer
+ * is placed into finptr, and the admin-node is read in; returns 1.
+ * If the RCS file does not exist and MUSTREAD,
+ * print an error unless QUIET and return 0.
+ * Otherwise, initialize the admin node and return -1.
+ *
+ * 0 is returned on all errors, e.g. files that are not regular files.
+ */
+{
+ static struct buf tempbuf;
+
+ register char *p, *arg, *RCS1;
+ char const *purefname, *pureRCSname, *x;
+ int paired;
+ size_t arglen, dlen, baselen, xlen;
+
+ if (!(arg = *argv)) return 0; /* already paired filename */
+ if (*arg == '-') {
+ error("%s option is ignored after file names", arg);
+ return 0;
+ }
+
+ purefname = basename(arg);
+
+ /* Allocate buffer temporary to hold the default paired file name. */
+ p = arg;
+ for (;;) {
+ switch (*p++) {
+ /* Beware characters that cause havoc with ci -k. */
+ case KDELIM:
+ error("RCS file name `%s' contains %c", arg, KDELIM);
+ return 0;
+ case ' ': case '\n': case '\t':
+ error("RCS file name `%s' contains white space", arg);
+ return 0;
+ default:
+ continue;
+ case 0:
+ break;
+ }
+ break;
+ }
+
+ paired = false;
+
+ /* first check suffix to see whether it is an RCS file or not */
+ if ((x = rcssuffix(arg)))
+ {
+ /* RCS file name given*/
+ RCS1 = arg;
+ pureRCSname = purefname;
+ baselen = x - purefname;
+ if (
+ 1 < argc &&
+ !rcssuffix(workfilename = p = argv[1]) &&
+ baselen <= (arglen = strlen(p)) &&
+ ((p+=arglen-baselen) == workfilename || isSLASH(p[-1])) &&
+ memcmp(purefname, p, baselen) == 0
+ ) {
+ argv[1] = 0;
+ paired = true;
+ } else {
+ bufscpy(&tempbuf, purefname);
+ workfilename = p = tempbuf.string;
+ p[baselen] = 0;
+ }
+ } else {
+ /* working file given; now try to find RCS file */
+ workfilename = arg;
+ baselen = p - purefname - 1;
+ /* derive RCS file name*/
+ if (
+ 1 < argc &&
+ (x = rcssuffix(RCS1 = argv[1])) &&
+ baselen <= x - RCS1 &&
+ ((pureRCSname=x-baselen)==RCS1 || isSLASH(pureRCSname[-1])) &&
+ memcmp(purefname, pureRCSname, baselen) == 0
+ ) {
+ argv[1] = 0;
+ paired = true;
+ } else
+ pureRCSname = RCS1 = 0;
+ }
+ /* now we have a (tentative) RCS filename in RCS1 and workfilename */
+ /* Second, try to find the right RCS file */
+ if (pureRCSname!=RCS1) {
+ /* a path for RCSfile is given; single RCS file to look for */
+ bufscpy(&RCSbuf, RCS1);
+ finptr = (*rcsopen)(&RCSbuf, &RCSstat, mustread);
+ RCSerrno = errno;
+ } else {
+ bufscpy(&RCSbuf, "");
+ if (RCS1)
+ /* RCS file name was given without path. */
+ VOID fin2open(arg, (size_t)0, pureRCSname, baselen,
+ x, strlen(x), rcsopen, mustread
+ );
+ else {
+ /* No RCS file name was given. */
+ /* Try each suffix in turn. */
+ dlen = purefname-arg;
+ x = suffixes;
+ while (! fin2open(arg, dlen, purefname, baselen,
+ x, xlen=suffixlen(x), rcsopen, mustread
+ )) {
+ x += xlen;
+ if (!*x++)
+ break;
+ }
+ }
+ }
+ RCSfilename = p = RCSbuf.string;
+ if (finptr) {
+ if (!S_ISREG(RCSstat.st_mode)) {
+ error("%s isn't a regular file -- ignored", p);
+ return 0;
+ }
+ Lexinit(); getadmin();
+ } else {
+ if (RCSerrno!=ENOENT || mustread || !frewrite) {
+ if (RCSerrno == EEXIST)
+ error("RCS file %s is in use", p);
+ else if (!quiet || RCSerrno!=ENOENT)
+ enerror(RCSerrno, p);
+ return 0;
+ }
+ InitAdmin();
+ };
+# if LONG_NAMES_MAY_BE_SILENTLY_TRUNCATED
+ if (filenametoolong(p)) {
+ error("RCS file name %s is too long", p);
+ return 0;
+ }
+# ifndef NAME_MAX
+ /*
+ * Check workfilename too, even though it cannot be longer,
+ * because it may reside on a different filesystem.
+ */
+ if (filenametoolong(workfilename)) {
+ error("working file name %s is too long", workfilename);
+ return 0;
+ }
+# endif
+# endif
+
+ if (paired && workstdout)
+ warn("Option -p is set; ignoring output file %s",workfilename);
+
+ prevkeys = false;
+ return finptr ? 1 : -1;
+}
+
+
+ char const *
+getfullRCSname()
+/* Function: returns a pointer to the full path name of the RCS file.
+ * Gets the working directory's name at most once.
+ * Removes leading "../" and "./".
+ */
+{
+ static char const *wdptr;
+ static struct buf rcsbuf, wdbuf;
+ static size_t pathlength;
+
+ register char const *realname;
+ register size_t parentdirlength;
+ register unsigned dotdotcounter;
+ register char *d;
+ register char const *wd;
+
+ if (ROOTPATH(RCSfilename)) {
+ return(RCSfilename);
+ } else {
+ if (!(wd = wdptr)) {
+ /* Get working directory for the first time. */
+ if (!(d = cgetenv("PWD"))) {
+ bufalloc(&wdbuf, SIZEABLE_PATH + 1);
+# if !has_getcwd && has_getwd
+ d = getwd(wdbuf.string);
+# else
+ while (
+ !(d = getcwd(wdbuf.string, wdbuf.size))
+ && errno==ERANGE
+ )
+ bufalloc(&wdbuf, wdbuf.size<<1);
+# endif
+ if (!d)
+ efaterror("working directory");
+ }
+ parentdirlength = strlen(d);
+ while (parentdirlength && isSLASH(d[parentdirlength-1])) {
+ d[--parentdirlength] = 0;
+ /* Check needed because some getwd implementations */
+ /* generate "/" for the root. */
+ }
+ wdptr = wd = d;
+ pathlength = parentdirlength;
+ }
+ /*the following must be redone since RCSfilename may change*/
+ /* Find how many `../'s to remove from RCSfilename. */
+ dotdotcounter =0;
+ realname = RCSfilename;
+ while (realname[0]=='.') {
+ if (isSLASH(realname[1])) {
+ /* drop leading ./ */
+ realname += 2;
+ } else if (realname[1]=='.' && isSLASH(realname[2])) {
+ /* drop leading ../ and remember */
+ dotdotcounter++;
+ realname += 3;
+ } else
+ break;
+ }
+ /* Now remove dotdotcounter trailing directories from wd. */
+ parentdirlength = pathlength;
+ while (dotdotcounter && parentdirlength) {
+ /* move pointer backwards over trailing directory */
+ if (isSLASH(wd[--parentdirlength])) {
+ dotdotcounter--;
+ }
+ }
+ /* build full path name */
+ bufalloc(&rcsbuf, parentdirlength+strlen(realname)+2);
+ d = rcsbuf.string;
+ VOID memcpy(d, wd, parentdirlength);
+ d += parentdirlength;
+ *d++ = SLASH;
+ VOID strcpy(d, realname);
+ return rcsbuf.string;
+ }
+}
+
+#ifndef isSLASH
+ int
+isSLASH(c)
+ int c;
+{
+ switch (c) {
+ case SLASHes:
+ return true;
+ default:
+ return false;
+ }
+}
+#endif
+
+
+#if !has_getcwd && !has_getwd
+
+ char *
+getcwd(path, size)
+ char *path;
+ size_t size;
+{
+ static char const usrbinpwd[] = "/usr/bin/pwd";
+# define binpwd (usrbinpwd+4)
+
+ register FILE *fp;
+ register int c;
+ register char *p, *lim;
+ int closeerrno, closeerror, e, fd[2], readerror, toolong, wstatus;
+ pid_t child;
+# if !has_waitpid
+ pid_t w;
+# endif
+
+ if (!size) {
+ errno = EINVAL;
+ return 0;
+ }
+ if (pipe(fd) != 0)
+ return 0;
+ if (!(child = vfork())) {
+ if (
+ close(fd[0]) == 0 &&
+ (fd[1] == STDOUT_FILENO ||
+# ifdef F_DUPFD
+ (VOID close(STDOUT_FILENO),
+ fcntl(fd[1], F_DUPFD, STDOUT_FILENO))
+# else
+ dup2(fd[1], STDOUT_FILENO)
+# endif
+ == STDOUT_FILENO &&
+ close(fd[1]) == 0
+ )
+ ) {
+ VOID close(STDERR_FILENO);
+ VOID execl(binpwd, binpwd, (char *)0);
+ VOID execl(usrbinpwd, usrbinpwd, (char *)0);
+ }
+ _exit(EXIT_FAILURE);
+ }
+ e = errno;
+ closeerror = close(fd[1]);
+ closeerrno = errno;
+ fp = 0;
+ readerror = toolong = wstatus = 0;
+ p = path;
+ if (0 <= child) {
+ fp = fdopen(fd[0], "r");
+ e = errno;
+ if (fp) {
+ lim = p + size;
+ for (p = path; ; *p++ = c) {
+ if ((c=getc(fp)) < 0) {
+ if (feof(fp))
+ break;
+ if (ferror(fp)) {
+ readerror = 1;
+ e = errno;
+ break;
+ }
+ }
+ if (p == lim) {
+ toolong = 1;
+ break;
+ }
+ }
+ }
+# if has_waitpid
+ if (waitpid(child, &wstatus, 0) < 0)
+ wstatus = 1;
+# else
+ do {
+ if ((w = wait(&wstatus)) < 0) {
+ wstatus = 1;
+ break;
+ }
+ } while (w != child);
+# endif
+ }
+ if (!fp) {
+ VOID close(fd[0]);
+ errno = e;
+ return 0;
+ }
+ if (fclose(fp) != 0)
+ return 0;
+ if (readerror) {
+ errno = e;
+ return 0;
+ }
+ if (closeerror) {
+ errno = closeerrno;
+ return 0;
+ }
+ if (toolong) {
+ errno = ERANGE;
+ return 0;
+ }
+ if (wstatus || p == path || *--p != '\n') {
+ errno = EACCES;
+ return 0;
+ }
+ *p = '\0';
+ return path;
+}
+#endif
+
+
+#ifdef PAIRTEST
+/* test program for pairfilenames() and getfullRCSname() */
+
+char const cmdid[] = "pair";
+
+main(argc, argv)
+int argc; char *argv[];
+{
+ int result;
+ int initflag;
+ quietflag = initflag = false;
+
+ while(--argc, ++argv, argc>=1 && ((*argv)[0] == '-')) {
+ switch ((*argv)[1]) {
+
+ case 'p': workstdout = stdout;
+ break;
+ case 'i': initflag=true;
+ break;
+ case 'q': quietflag=true;
+ break;
+ default: error("unknown option: %s", *argv);
+ break;
+ }
+ }
+
+ do {
+ RCSfilename=workfilename=nil;
+ result = pairfilenames(argc,argv,rcsreadopen,!initflag,quietflag);
+ if (result!=0) {
+ diagnose("RCS file: %s; working file: %s\nFull RCS file name: %s\n",
+ RCSfilename,workfilename,getfullRCSname()
+ );
+ }
+ switch (result) {
+ case 0: continue; /* already paired file */
+
+ case 1: if (initflag) {
+ error("RCS file %s exists already",RCSfilename);
+ } else {
+ diagnose("RCS file %s exists\n",RCSfilename);
+ }
+ Ifclose(finptr);
+ break;
+
+ case -1:diagnose("RCS file doesn't exist\n");
+ break;
+ }
+
+ } while (++argv, --argc>=1);
+
+}
+
+ exiting void
+exiterr()
+{
+ dirtempunlink();
+ tempunlink();
+ _exit(EXIT_FAILURE);
+}
+#endif
diff --git a/gnu/usr.bin/rcs/lib/rcsgen.c b/gnu/usr.bin/rcs/lib/rcsgen.c
new file mode 100644
index 0000000..9a6072e
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/rcsgen.c
@@ -0,0 +1,432 @@
+/*
+ * RCS revision generation
+ */
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+
+/* $Log: rcsgen.c,v $
+ * Revision 5.10 1991/10/07 17:32:46 eggert
+ * Fix log bugs, e.g. ci -t/dev/null when has_mmap.
+ *
+ * Revision 5.9 1991/09/10 22:15:46 eggert
+ * Fix test for redirected stdin.
+ *
+ * Revision 5.8 1991/08/19 03:13:55 eggert
+ * Add piece tables. Tune.
+ *
+ * Revision 5.7 1991/04/21 11:58:24 eggert
+ * Add MS-DOS support.
+ *
+ * Revision 5.6 1990/12/27 19:54:26 eggert
+ * Fix bug: rcs -t inserted \n, making RCS file grow.
+ *
+ * Revision 5.5 1990/12/04 05:18:45 eggert
+ * Use -I for prompts and -q for diagnostics.
+ *
+ * Revision 5.4 1990/11/01 05:03:47 eggert
+ * Add -I and new -t behavior. Permit arbitrary data in logs.
+ *
+ * Revision 5.3 1990/09/21 06:12:43 hammer
+ * made putdesc() treat stdin the same whether or not it was from a terminal
+ * by making it recognize that a single '.' was then end of the
+ * description always
+ *
+ * Revision 5.2 1990/09/04 08:02:25 eggert
+ * Fix `co -p1.1 -ko' bug. Standardize yes-or-no procedure.
+ *
+ * Revision 5.1 1990/08/29 07:14:01 eggert
+ * Clean old log messages too.
+ *
+ * Revision 5.0 1990/08/22 08:12:52 eggert
+ * Remove compile-time limits; use malloc instead.
+ * Ansify and Posixate.
+ *
+ * Revision 4.7 89/05/01 15:12:49 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.6 88/08/28 14:59:10 eggert
+ * Shrink stdio code size; allow cc -R; remove lint; isatty() -> ttystdin()
+ *
+ * Revision 4.5 87/12/18 11:43:25 narten
+ * additional lint cleanups, and a bug fix from the 4.3BSD version that
+ * keeps "ci" from sticking a '\377' into the description if you run it
+ * with a zero-length file as the description. (Guy Harris)
+ *
+ * Revision 4.4 87/10/18 10:35:10 narten
+ * Updating version numbers. Changes relative to 1.1 actually relative to
+ * 4.2
+ *
+ * Revision 1.3 87/09/24 13:59:51 narten
+ * Sources now pass through lint (if you ignore printf/sprintf/fprintf
+ * warnings)
+ *
+ * Revision 1.2 87/03/27 14:22:27 jenkins
+ * Port to suns
+ *
+ * Revision 4.2 83/12/02 23:01:39 wft
+ * merged 4.1 and 3.3.1.1 (clearerr(stdin)).
+ *
+ * Revision 4.1 83/05/10 16:03:33 wft
+ * Changed putamin() to abort if trying to reread redirected stdin.
+ * Fixed getdesc() to output a prompt on initial newline.
+ *
+ * Revision 3.3.1.1 83/10/19 04:21:51 lepreau
+ * Added clearerr(stdin) for re-reading description from stdin.
+ *
+ * Revision 3.3 82/11/28 21:36:49 wft
+ * 4.2 prerelease
+ *
+ * Revision 3.3 82/11/28 21:36:49 wft
+ * Replaced ferror() followed by fclose() with ffclose().
+ * Putdesc() now suppresses the prompts if stdin
+ * is not a terminal. A pointer to the current log message is now
+ * inserted into the corresponding delta, rather than leaving it in a
+ * global variable.
+ *
+ * Revision 3.2 82/10/18 21:11:26 wft
+ * I added checks for write errors during editing, and improved
+ * the prompt on putdesc().
+ *
+ * Revision 3.1 82/10/13 15:55:09 wft
+ * corrected type of variables assigned to by getc (char --> int)
+ */
+
+
+
+
+#include "rcsbase.h"
+
+libId(genId, "$Id: rcsgen.c,v 5.10 1991/10/07 17:32:46 eggert Exp $")
+
+int interactiveflag; /* Should we act as if stdin is a tty? */
+struct buf curlogbuf; /* buffer for current log message */
+
+enum stringwork { enter, copy, edit, expand, edit_expand };
+static void scandeltatext P((struct hshentry*,enum stringwork,int));
+
+
+
+
+ char const *
+buildrevision(deltas, target, outfile, expandflag)
+ struct hshentries const *deltas;
+ struct hshentry *target;
+ FILE *outfile;
+ int expandflag;
+/* Function: Generates the revision given by target
+ * by retrieving all deltas given by parameter deltas and combining them.
+ * If outfile is set, the revision is output to it,
+ * otherwise written into a temporary file.
+ * Temporary files are allocated by maketemp().
+ * if expandflag is set, keyword expansion is performed.
+ * Return nil if outfile is set, the name of the temporary file otherwise.
+ *
+ * Algorithm: Copy initial revision unchanged. Then edit all revisions but
+ * the last one into it, alternating input and output files (resultfile and
+ * editfile). The last revision is then edited in, performing simultaneous
+ * keyword substitution (this saves one extra pass).
+ * All this simplifies if only one revision needs to be generated,
+ * or no keyword expansion is necessary, or if output goes to stdout.
+ */
+{
+ if (deltas->first == target) {
+ /* only latest revision to generate */
+ openfcopy(outfile);
+ scandeltatext(target, expandflag?expand:copy, true);
+ if (outfile)
+ return 0;
+ else {
+ Ozclose(&fcopy);
+ return(resultfile);
+ }
+ } else {
+ /* several revisions to generate */
+ /* Get initial revision without keyword expansion. */
+ scandeltatext(deltas->first, enter, false);
+ while ((deltas=deltas->rest)->rest) {
+ /* do all deltas except last one */
+ scandeltatext(deltas->first, edit, false);
+ }
+ if (expandflag || outfile) {
+ /* first, get to beginning of file*/
+ finishedit((struct hshentry *)nil, outfile, false);
+ }
+ scandeltatext(deltas->first, expandflag?edit_expand:edit, true);
+ finishedit(
+ expandflag ? deltas->first : (struct hshentry*)nil,
+ outfile, true
+ );
+ if (outfile)
+ return 0;
+ Ozclose(&fcopy);
+ return resultfile;
+ }
+}
+
+
+
+ static void
+scandeltatext(delta, func, needlog)
+ struct hshentry * delta;
+ enum stringwork func;
+ int needlog;
+/* Function: Scans delta text nodes up to and including the one given
+ * by delta. For the one given by delta, the log message is saved into
+ * delta->log if needlog is set; func specifies how to handle the text.
+ * Assumes the initial lexeme must be read in first.
+ * Does not advance nexttok after it is finished.
+ */
+{
+ struct hshentry const *nextdelta;
+ struct cbuf cb;
+
+ for (;;) {
+ if (eoflex())
+ fatserror("can't find delta for revision %s", delta->num);
+ nextlex();
+ if (!(nextdelta=getnum())) {
+ fatserror("delta number corrupted");
+ }
+ getkeystring(Klog);
+ if (needlog && delta==nextdelta) {
+ cb = savestring(&curlogbuf);
+ delta->log = cleanlogmsg(curlogbuf.string, cb.size);
+ } else {readstring();
+ }
+ nextlex();
+ while (nexttok==ID && strcmp(NextString,Ktext)!=0)
+ ignorephrase();
+ getkeystring(Ktext);
+
+ if (delta==nextdelta)
+ break;
+ readstring(); /* skip over it */
+
+ }
+ switch (func) {
+ case enter: enterstring(); break;
+ case copy: copystring(); break;
+ case expand: xpandstring(delta); break;
+ case edit: editstring((struct hshentry *)nil); break;
+ case edit_expand: editstring(delta); break;
+ }
+}
+
+ struct cbuf
+cleanlogmsg(m, s)
+ char *m;
+ size_t s;
+{
+ register char *t = m;
+ register char const *f = t;
+ struct cbuf r;
+ while (s) {
+ --s;
+ if ((*t++ = *f++) == '\n')
+ while (m < --t)
+ if (t[-1]!=' ' && t[-1]!='\t') {
+ *t++ = '\n';
+ break;
+ }
+ }
+ while (m < t && (t[-1]==' ' || t[-1]=='\t' || t[-1]=='\n'))
+ --t;
+ r.string = m;
+ r.size = t - m;
+ return r;
+}
+
+
+int ttystdin()
+{
+ static int initialized;
+ if (!initialized) {
+ if (!interactiveflag)
+ interactiveflag = isatty(STDIN_FILENO);
+ initialized = true;
+ }
+ return interactiveflag;
+}
+
+ int
+getcstdin()
+{
+ register FILE *in;
+ register int c;
+
+ in = stdin;
+ if (feof(in) && ttystdin())
+ clearerr(in);
+ c = getc(in);
+ if (c < 0) {
+ testIerror(in);
+ if (feof(in) && ttystdin())
+ afputc('\n',stderr);
+ }
+ return c;
+}
+
+#if has_prototypes
+ int
+yesorno(int default_answer, char const *question, ...)
+#else
+ /*VARARGS2*/ int
+ yesorno(default_answer, question, va_alist)
+ int default_answer; char const *question; va_dcl
+#endif
+{
+ va_list args;
+ register int c, r;
+ if (!quietflag && ttystdin()) {
+ oflush();
+ vararg_start(args, question);
+ fvfprintf(stderr, question, args);
+ va_end(args);
+ eflush();
+ r = c = getcstdin();
+ while (c!='\n' && !feof(stdin))
+ c = getcstdin();
+ if (r=='y' || r=='Y')
+ return true;
+ if (r=='n' || r=='N')
+ return false;
+ }
+ return default_answer;
+}
+
+
+ void
+putdesc(textflag, textfile)
+ int textflag;
+ char *textfile;
+/* Function: puts the descriptive text into file frewrite.
+ * if finptr && !textflag, the text is copied from the old description.
+ * Otherwise, if the textfile!=nil, the text is read from that
+ * file, or from stdin, if textfile==nil.
+ * A textfile with a leading '-' is treated as a string, not a file name.
+ * If finptr, the old descriptive text is discarded.
+ * Always clears foutptr.
+ */
+{
+ static struct buf desc;
+ static struct cbuf desclean;
+
+ register FILE *txt;
+ register int c;
+ register FILE * frew;
+ register char *p;
+ register size_t s;
+ char const *plim;
+
+ frew = frewrite;
+ if (finptr && !textflag) {
+ /* copy old description */
+ aprintf(frew, "\n\n%s%c", Kdesc, nextc);
+ foutptr = frewrite;
+ getdesc(false);
+ foutptr = 0;
+ } else {
+ foutptr = 0;
+ /* get new description */
+ if (finptr) {
+ /*skip old description*/
+ getdesc(false);
+ }
+ aprintf(frew,"\n\n%s\n%c",Kdesc,SDELIM);
+ if (!textfile)
+ desclean = getsstdin(
+ "t-", "description",
+ "NOTE: This is NOT the log message!\n", &desc
+ );
+ else if (!desclean.string) {
+ if (*textfile == '-') {
+ p = textfile + 1;
+ s = strlen(p);
+ } else {
+ if (!(txt = fopen(textfile, "r")))
+ efaterror(textfile);
+ bufalloc(&desc, 1);
+ p = desc.string;
+ plim = p + desc.size;
+ for (;;) {
+ if ((c=getc(txt)) < 0) {
+ testIerror(txt);
+ if (feof(txt))
+ break;
+ }
+ if (plim <= p)
+ p = bufenlarge(&desc, &plim);
+ *p++ = c;
+ }
+ if (fclose(txt) != 0)
+ Ierror();
+ s = p - desc.string;
+ p = desc.string;
+ }
+ desclean = cleanlogmsg(p, s);
+ }
+ putstring(frew, false, desclean, true);
+ aputc('\n', frew);
+ }
+}
+
+ struct cbuf
+getsstdin(option, name, note, buf)
+ char const *option, *name, *note;
+ struct buf *buf;
+{
+ register int c;
+ register char *p;
+ register size_t i;
+ register int tty = ttystdin();
+
+ if (tty)
+ aprintf(stderr,
+ "enter %s, terminated with single '.' or end of file:\n%s>> ",
+ name, note
+ );
+ else if (feof(stdin))
+ faterror("can't reread redirected stdin for %s; use -%s<%s>",
+ name, option, name
+ );
+
+ for (
+ i = 0, p = 0;
+ c = getcstdin(), !feof(stdin);
+ bufrealloc(buf, i+1), p = buf->string, p[i++] = c
+ )
+ if (c == '\n')
+ if (i && p[i-1]=='.' && (i==1 || p[i-2]=='\n')) {
+ /* Remove trailing '.'. */
+ --i;
+ break;
+ } else if (tty)
+ aputs(">> ", stderr);
+ return cleanlogmsg(p, i);
+}
diff --git a/gnu/usr.bin/rcs/lib/rcskeep.c b/gnu/usr.bin/rcs/lib/rcskeep.c
new file mode 100644
index 0000000..1a0c78f
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/rcskeep.c
@@ -0,0 +1,422 @@
+/*
+ * RCS keyword extraction
+ */
+/*****************************************************************************
+ * main routine: getoldkeys()
+ * Testprogram: define KEEPTEST
+ *****************************************************************************
+ */
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+
+/* $Log: rcskeep.c,v $
+ * Revision 5.4 1991/08/19 03:13:55 eggert
+ * Tune.
+ *
+ * Revision 5.3 1991/04/21 11:58:25 eggert
+ * Shorten names to keep them distinct on shortname hosts.
+ *
+ * Revision 5.2 1990/10/04 06:30:20 eggert
+ * Parse time zone offsets; future RCS versions may output them.
+ *
+ * Revision 5.1 1990/09/20 02:38:56 eggert
+ * ci -k now checks dates more thoroughly.
+ *
+ * Revision 5.0 1990/08/22 08:12:53 eggert
+ * Retrieve old log message if there is one.
+ * Don't require final newline.
+ * Remove compile-time limits; use malloc instead. Tune.
+ * Permit dates past 1999/12/31. Ansify and Posixate.
+ *
+ * Revision 4.6 89/05/01 15:12:56 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.5 88/08/09 19:13:03 eggert
+ * Remove lint and speed up by making FILE *fp local, not global.
+ *
+ * Revision 4.4 87/12/18 11:44:21 narten
+ * more lint cleanups (Guy Harris)
+ *
+ * Revision 4.3 87/10/18 10:35:50 narten
+ * Updating version numbers. Changes relative to 1.1 actually relative
+ * to 4.1
+ *
+ * Revision 1.3 87/09/24 14:00:00 narten
+ * Sources now pass through lint (if you ignore printf/sprintf/fprintf
+ * warnings)
+ *
+ * Revision 1.2 87/03/27 14:22:29 jenkins
+ * Port to suns
+ *
+ * Revision 4.1 83/05/10 16:26:44 wft
+ * Added new markers Id and RCSfile; extraction added.
+ * Marker matching with trymatch().
+ *
+ * Revision 3.2 82/12/24 12:08:26 wft
+ * added missing #endif.
+ *
+ * Revision 3.1 82/12/04 13:22:41 wft
+ * Initial revision.
+ *
+ */
+
+/*
+#define KEEPTEST
+*/
+/* Testprogram; prints out the keyword values found. */
+
+#include "rcsbase.h"
+
+libId(keepId, "$Id: rcskeep.c,v 5.4 1991/08/19 03:13:55 eggert Exp $")
+
+static int checknum P((char const*,int));
+static int getval P((RILE*,struct buf*,int));
+static int get0val P((int,RILE*,struct buf*,int));
+static int keepdate P((RILE*));
+static int keepid P((int,RILE*,struct buf*));
+static int keeprev P((RILE*));
+
+int prevkeys;
+struct buf prevauthor, prevdate, prevrev, prevstate;
+
+ int
+getoldkeys(fp)
+ register RILE *fp;
+/* Function: Tries to read keyword values for author, date,
+ * revision number, and state out of the file fp.
+ * If FNAME is nonnull, it is opened and closed instead of using FP.
+ * The results are placed into
+ * prevauthor, prevdate, prevrev, prevstate.
+ * Aborts immediately if it finds an error and returns false.
+ * If it returns true, it doesn't mean that any of the
+ * values were found; instead, check to see whether the corresponding arrays
+ * contain the empty string.
+ */
+{
+ register int c;
+ char keyword[keylength+1];
+ register char * tp;
+ int needs_closing;
+
+ if (prevkeys)
+ return true;
+
+ needs_closing = false;
+ if (!fp) {
+ if (!(fp = Iopen(workfilename, FOPEN_R_WORK, (struct stat*)0))) {
+ eerror(workfilename);
+ return false;
+ }
+ needs_closing = true;
+ }
+
+ /* initialize to empty */
+ bufscpy(&prevauthor, "");
+ bufscpy(&prevdate, "");
+ bufscpy(&prevrev, "");
+ bufscpy(&prevstate, "");
+
+ c = '\0'; /* anything but KDELIM */
+ for (;;) {
+ if ( c==KDELIM) {
+ do {
+ /* try to get keyword */
+ tp = keyword;
+ for (;;) {
+ Igeteof(fp, c, goto ok;);
+ switch (c) {
+ default:
+ if (keyword+keylength <= tp)
+ break;
+ *tp++ = c;
+ continue;
+
+ case '\n': case KDELIM: case VDELIM:
+ break;
+ }
+ break;
+ }
+ } while (c==KDELIM);
+ if (c!=VDELIM) continue;
+ *tp = c;
+ Igeteof(fp, c, break;);
+ switch (c) {
+ case ' ': case '\t': break;
+ default: continue;
+ }
+
+ switch (trymatch(keyword)) {
+ case Author:
+ if (!keepid(0, fp, &prevauthor))
+ return false;
+ c = 0;
+ break;
+ case Date:
+ if (!(c = keepdate(fp)))
+ return false;
+ break;
+ case Header:
+ case Id:
+ if (!(
+ getval(fp, (struct buf*)nil, false) &&
+ keeprev(fp) &&
+ (c = keepdate(fp)) &&
+ keepid(c, fp, &prevauthor) &&
+ keepid(0, fp, &prevstate)
+ ))
+ return false;
+ /* Skip either ``who'' (new form) or ``Locker: who'' (old). */
+ if (getval(fp, (struct buf*)nil, true) &&
+ getval(fp, (struct buf*)nil, true))
+ c = 0;
+ else if (nerror)
+ return false;
+ else
+ c = KDELIM;
+ break;
+ case Locker:
+ case Log:
+ case RCSfile:
+ case Source:
+ if (!getval(fp, (struct buf*)nil, false))
+ return false;
+ c = 0;
+ break;
+ case Revision:
+ if (!keeprev(fp))
+ return false;
+ c = 0;
+ break;
+ case State:
+ if (!keepid(0, fp, &prevstate))
+ return false;
+ c = 0;
+ break;
+ default:
+ continue;
+ }
+ if (!c)
+ Igeteof(fp, c, c=0;);
+ if (c != KDELIM) {
+ error("closing %c missing on keyword", KDELIM);
+ return false;
+ }
+ if (*prevauthor.string && *prevdate.string && *prevrev.string && *prevstate.string) {
+ break;
+ }
+ }
+ Igeteof(fp, c, break;);
+ }
+
+ ok:
+ if (needs_closing)
+ Ifclose(fp);
+ else
+ Irewind(fp);
+ prevkeys = true;
+ return true;
+}
+
+ static int
+badly_terminated()
+{
+ error("badly terminated keyword value");
+ return false;
+}
+
+ static int
+getval(fp, target, optional)
+ register RILE *fp;
+ struct buf *target;
+ int optional;
+/* Reads a keyword value from FP into TARGET.
+ * Returns true if one is found, false otherwise.
+ * Does not modify target if it is nil.
+ * Do not report an error if OPTIONAL is set and KDELIM is found instead.
+ */
+{
+ int c;
+ Igeteof(fp, c, return badly_terminated(););
+ return get0val(c, fp, target, optional);
+}
+
+ static int
+get0val(c, fp, target, optional)
+ register int c;
+ register RILE *fp;
+ struct buf *target;
+ int optional;
+/* Reads a keyword value from C+FP into TARGET, perhaps OPTIONALly.
+ * Same as getval, except C is the lookahead character.
+ */
+{ register char * tp;
+ char const *tlim;
+ register int got1;
+
+ if (target) {
+ bufalloc(target, 1);
+ tp = target->string;
+ tlim = tp + target->size;
+ } else
+ tlim = tp = 0;
+ got1 = false;
+ for (;;) {
+ switch (c) {
+ default:
+ got1 = true;
+ if (tp) {
+ *tp++ = c;
+ if (tlim <= tp)
+ tp = bufenlarge(target, &tlim);
+ }
+ break;
+
+ case ' ':
+ case '\t':
+ if (tp) {
+ *tp = 0;
+# ifdef KEEPTEST
+ VOID printf("getval: %s\n", target);
+# endif
+ }
+ if (!got1)
+ error("too much white space in keyword value");
+ return got1;
+
+ case KDELIM:
+ if (!got1 && optional)
+ return false;
+ /* fall into */
+ case '\n':
+ case 0:
+ return badly_terminated();
+ }
+ Igeteof(fp, c, return badly_terminated(););
+ }
+}
+
+
+ static int
+keepdate(fp)
+ RILE *fp;
+/* Function: reads a date prevdate; checks format
+ * Return 0 on error, lookahead character otherwise.
+ */
+{
+ struct buf prevday, prevtime, prevzone;
+ register char const *p;
+ register int c;
+
+ c = 0;
+ bufautobegin(&prevday);
+ if (getval(fp,&prevday,false)) {
+ bufautobegin(&prevtime);
+ if (getval(fp,&prevtime,false)) {
+ bufautobegin(&prevzone);
+ bufscpy(&prevzone, "");
+ Igeteof(fp, c, c=0;);
+ if (c=='-' || c=='+')
+ if (!get0val(c,fp,&prevzone,false))
+ c = 0;
+ else
+ Igeteof(fp, c, c=0;);
+ if (c) {
+ p = prevday.string;
+ bufalloc(&prevdate, strlen(p) + strlen(prevtime.string) + strlen(prevzone.string) + 5);
+ VOID sprintf(prevdate.string, "%s%s %s %s",
+ /* Parse dates put out by old versions of RCS. */
+ isdigit(p[0]) && isdigit(p[1]) && p[2]=='/' ? "19" : "",
+ p, prevtime.string, prevzone.string
+ );
+ }
+ bufautoend(&prevzone);
+ }
+ bufautoend(&prevtime);
+ }
+ bufautoend(&prevday);
+ return c;
+}
+
+ static int
+keepid(c, fp, b)
+ int c;
+ RILE *fp;
+ struct buf *b;
+/* Get previous identifier from C+FP into B. */
+{
+ if (!c)
+ Igeteof(fp, c, return false;);
+ if (!get0val(c, fp, b, false))
+ return false;
+ checksid(b->string);
+ return true;
+}
+
+ static int
+keeprev(fp)
+ RILE *fp;
+/* Get previous revision from FP into prevrev. */
+{
+ return getval(fp,&prevrev,false) && checknum(prevrev.string,-1);
+}
+
+
+ static int
+checknum(sp,fields)
+ register char const *sp;
+ int fields;
+{ register int dotcount;
+ dotcount=0;
+ while(*sp) {
+ if (*sp=='.') dotcount++;
+ else if (!isdigit(*sp)) return false;
+ sp++;
+ }
+ return fields<0 ? dotcount&1 : dotcount==fields;
+}
+
+
+
+#ifdef KEEPTEST
+
+char const cmdid[] ="keeptest";
+
+ int
+main(argc, argv)
+int argc; char *argv[];
+{
+ while (*(++argv)) {
+ workfilename = *argv;
+ getoldkeys((RILE*)0);
+ VOID printf("%s: revision: %s, date: %s, author: %s, state: %s\n",
+ *argv, prevrev.string, prevdate.string, prevauthor.string, prevstate.string);
+ }
+ exitmain(EXIT_SUCCESS);
+}
+#endif
diff --git a/gnu/usr.bin/rcs/lib/rcskeys.c b/gnu/usr.bin/rcs/lib/rcskeys.c
new file mode 100644
index 0000000..82850a7
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/rcskeys.c
@@ -0,0 +1,102 @@
+/*
+ * RCS keyword table and match operation
+ */
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+
+/* $Log: rcskeys.c,v $
+ * Revision 5.2 1991/08/19 03:13:55 eggert
+ * Say `T const' instead of `const T'; it's less confusing for pointer types.
+ * (This change was made in other source files too.)
+ *
+ * Revision 5.1 1991/04/21 11:58:25 eggert
+ * Don't put , just before } in initializer.
+ *
+ * Revision 5.0 1990/08/22 08:12:54 eggert
+ * Add -k. Ansify and Posixate.
+ *
+ * Revision 4.3 89/05/01 15:13:02 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.2 87/10/18 10:36:33 narten
+ * Updating version numbers. Changes relative to 1.1 actuallyt
+ * relative to 4.1
+ *
+ * Revision 1.2 87/09/24 14:00:10 narten
+ * Sources now pass through lint (if you ignore printf/sprintf/fprintf
+ * warnings)
+ *
+ * Revision 4.1 83/05/04 10:06:53 wft
+ * Initial revision.
+ *
+ */
+
+
+#include "rcsbase.h"
+
+libId(keysId, "$Id: rcskeys.c,v 5.2 1991/08/19 03:13:55 eggert Exp $")
+
+
+char const *const Keyword[] = {
+ /* This must be in the same order as rcsbase.h's enum markers type. */
+ nil,
+ AUTHOR, DATE, HEADER, IDH,
+ LOCKER, LOG, RCSFILE, REVISION, SOURCE, STATE
+};
+
+
+
+ enum markers
+trymatch(string)
+ char const *string;
+/* function: Checks whether string starts with a keyword followed
+ * by a KDELIM or a VDELIM.
+ * If successful, returns the appropriate marker, otherwise Nomatch.
+ */
+{
+ register int j;
+ register char const *p, *s;
+ for (j = sizeof(Keyword)/sizeof(*Keyword); (--j); ) {
+ /* try next keyword */
+ p = Keyword[j];
+ s = string;
+ while (*p++ == *s++) {
+ if (!*p)
+ switch (*s) {
+ case KDELIM:
+ case VDELIM:
+ return (enum markers)j;
+ default:
+ return Nomatch;
+ }
+ }
+ }
+ return(Nomatch);
+}
+
diff --git a/gnu/usr.bin/rcs/lib/rcslex.c b/gnu/usr.bin/rcs/lib/rcslex.c
new file mode 100644
index 0000000..51e31f3
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/rcslex.c
@@ -0,0 +1,1241 @@
+/*
+ * RCS file input
+ */
+/*********************************************************************************
+ * Lexical Analysis.
+ * hashtable, Lexinit, nextlex, getlex, getkey,
+ * getid, getnum, readstring, printstring, savestring,
+ * checkid, fatserror, error, faterror, warn, diagnose
+ * Testprogram: define LEXDB
+ *********************************************************************************
+ */
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+
+/* $Log: rcslex.c,v $
+ * Revision 5.11 1991/11/03 03:30:44 eggert
+ * Fix porting bug to ancient hosts lacking vfprintf.
+ *
+ * Revision 5.10 1991/10/07 17:32:46 eggert
+ * Support piece tables even if !has_mmap.
+ *
+ * Revision 5.9 1991/09/24 00:28:42 eggert
+ * Don't export errsay().
+ *
+ * Revision 5.8 1991/08/19 03:13:55 eggert
+ * Add eoflex(), mmap support. Tune.
+ *
+ * Revision 5.7 1991/04/21 11:58:26 eggert
+ * Add MS-DOS support.
+ *
+ * Revision 5.6 1991/02/25 07:12:42 eggert
+ * Work around fputs bug. strsave -> str_save (DG/UX name clash)
+ *
+ * Revision 5.5 1990/12/04 05:18:47 eggert
+ * Use -I for prompts and -q for diagnostics.
+ *
+ * Revision 5.4 1990/11/19 20:05:28 hammer
+ * no longer gives warning about unknown keywords if -q is specified
+ *
+ * Revision 5.3 1990/11/01 05:03:48 eggert
+ * When ignoring unknown phrases, copy them to the output RCS file.
+ *
+ * Revision 5.2 1990/09/04 08:02:27 eggert
+ * Count RCS lines better.
+ *
+ * Revision 5.1 1990/08/29 07:14:03 eggert
+ * Work around buggy compilers with defective argument promotion.
+ *
+ * Revision 5.0 1990/08/22 08:12:55 eggert
+ * Remove compile-time limits; use malloc instead.
+ * Report errno-related errors with perror().
+ * Ansify and Posixate. Add support for ISO 8859.
+ * Use better hash function.
+ *
+ * Revision 4.6 89/05/01 15:13:07 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.5 88/08/28 15:01:12 eggert
+ * Don't loop when writing error messages to a full filesystem.
+ * Flush stderr/stdout when mixing output.
+ * Yield exit status compatible with diff(1).
+ * Shrink stdio code size; allow cc -R; remove lint.
+ *
+ * Revision 4.4 87/12/18 11:44:47 narten
+ * fixed to use "varargs" in "fprintf"; this is required if it is to
+ * work on a SPARC machine such as a Sun-4
+ *
+ * Revision 4.3 87/10/18 10:37:18 narten
+ * Updating version numbers. Changes relative to 1.1 actually relative
+ * to version 4.1
+ *
+ * Revision 1.3 87/09/24 14:00:17 narten
+ * Sources now pass through lint (if you ignore printf/sprintf/fprintf
+ * warnings)
+ *
+ * Revision 1.2 87/03/27 14:22:33 jenkins
+ * Port to suns
+ *
+ * Revision 4.1 83/03/25 18:12:51 wft
+ * Only changed $Header to $Id.
+ *
+ * Revision 3.3 82/12/10 16:22:37 wft
+ * Improved error messages, changed exit status on error to 1.
+ *
+ * Revision 3.2 82/11/28 21:27:10 wft
+ * Renamed ctab to map and included EOFILE; ctab is now a macro in rcsbase.h.
+ * Added fflsbuf(), fputs(), and fprintf(), which abort the RCS operations
+ * properly in case there is an IO-error (e.g., file system full).
+ *
+ * Revision 3.1 82/10/11 19:43:56 wft
+ * removed unused label out:;
+ * made sure all calls to getc() return into an integer, not a char.
+ */
+
+
+/*
+#define LEXDB
+*/
+/* version LEXDB is for testing the lexical analyzer. The testprogram
+ * reads a stream of lexemes, enters the revision numbers into the
+ * hashtable, and prints the recognized tokens. Keywords are recognized
+ * as identifiers.
+ */
+
+
+
+#include "rcsbase.h"
+
+libId(lexId, "$Id: rcslex.c,v 5.11 1991/11/03 03:30:44 eggert Exp $")
+
+static struct hshentry *nexthsh; /*pointer to next hash entry, set by lookup*/
+
+enum tokens nexttok; /*next token, set by nextlex */
+
+int hshenter; /*if true, next suitable lexeme will be entered */
+ /*into the symbol table. Handle with care. */
+int nextc; /*next input character, initialized by Lexinit */
+
+unsigned long rcsline; /*current line-number of input */
+int nerror; /*counter for errors */
+int quietflag; /*indicates quiet mode */
+RILE * finptr; /*input file descriptor */
+
+FILE * frewrite; /*file descriptor for echoing input */
+
+FILE * foutptr; /* copy of frewrite, but 0 to suppress echo */
+
+static struct buf tokbuf; /* token buffer */
+
+char const * NextString; /* next token */
+
+/*
+ * Our hash algorithm is h[0] = 0, h[i+1] = 4*h[i] + c,
+ * so hshsize should be odd.
+ * See B J McKenzie, R Harries & T Bell, Selecting a hashing algorithm,
+ * Software--practice & experience 20, 2 (Feb 1990), 209-224.
+ */
+#ifndef hshsize
+# define hshsize 511
+#endif
+
+static struct hshentry *hshtab[hshsize]; /*hashtable */
+
+static int ignored_phrases; /* have we ignored phrases in this RCS file? */
+
+ void
+warnignore()
+{
+ if (! (ignored_phrases|quietflag)) {
+ ignored_phrases = true;
+ warn("Unknown phrases like `%s ...;' are in the RCS file.", NextString);
+ }
+}
+
+
+
+ static void
+lookup(str)
+ char const *str;
+/* Function: Looks up the character string pointed to by str in the
+ * hashtable. If the string is not present, a new entry for it is created.
+ * In any case, the address of the corresponding hashtable entry is placed
+ * into nexthsh.
+ */
+{
+ register unsigned ihash; /* index into hashtable */
+ register char const *sp;
+ register struct hshentry *n, **p;
+
+ /* calculate hash code */
+ sp = str;
+ ihash = 0;
+ while (*sp)
+ ihash = (ihash<<2) + *sp++;
+ ihash %= hshsize;
+
+ for (p = &hshtab[ihash]; ; p = &n->nexthsh)
+ if (!(n = *p)) {
+ /* empty slot found */
+ *p = n = ftalloc(struct hshentry);
+ n->num = fstr_save(str);
+ n->nexthsh = nil;
+# ifdef LEXDB
+ VOID printf("\nEntered: %s at %u ", str, ihash);
+# endif
+ break;
+ } else if (strcmp(str, n->num) == 0)
+ /* match found */
+ break;
+ nexthsh = n;
+ NextString = n->num;
+}
+
+
+
+
+
+
+ void
+Lexinit()
+/* Function: Initialization of lexical analyzer:
+ * initializes the hashtable,
+ * initializes nextc, nexttok if finptr != 0
+ */
+{ register int c;
+
+ for (c = hshsize; 0 <= --c; ) {
+ hshtab[c] = nil;
+ }
+
+ nerror = 0;
+ if (finptr) {
+ foutptr = 0;
+ hshenter = true;
+ ignored_phrases = false;
+ rcsline = 1;
+ bufrealloc(&tokbuf, 2);
+ Iget(finptr, nextc);
+ nextlex(); /*initial token*/
+ }
+}
+
+
+
+
+
+
+
+ void
+nextlex()
+
+/* Function: Reads the next token and sets nexttok to the next token code.
+ * Only if hshenter is set, a revision number is entered into the
+ * hashtable and a pointer to it is placed into nexthsh.
+ * This is useful for avoiding that dates are placed into the hashtable.
+ * For ID's and NUM's, NextString is set to the character string.
+ * Assumption: nextc contains the next character.
+ */
+{ register c;
+ declarecache;
+ register FILE *frew;
+ register char * sp;
+ char const *limit;
+ register enum tokens d;
+ register RILE *fin;
+
+ fin=finptr; frew=foutptr;
+ setupcache(fin); cache(fin);
+ c = nextc;
+
+ for (;;) { switch ((d = ctab[c])) {
+
+ default:
+ fatserror("unknown character `%c'", c);
+ /*NOTREACHED*/
+
+ case NEWLN:
+ ++rcsline;
+# ifdef LEXDB
+ afputc('\n',stdout);
+# endif
+ /* Note: falls into next case */
+
+ case SPACE:
+ GETC(frew, c);
+ continue;
+
+ case DIGIT:
+ sp = tokbuf.string;
+ limit = sp + tokbuf.size;
+ *sp++ = c;
+ for (;;) {
+ GETC(frew, c);
+ if ((d=ctab[c])!=DIGIT && d!=PERIOD)
+ break;
+ *sp++ = c; /* 1.2. and 1.2 are different */
+ if (limit <= sp)
+ sp = bufenlarge(&tokbuf, &limit);
+ }
+ *sp = 0;
+ if (hshenter)
+ lookup(tokbuf.string);
+ else
+ NextString = fstr_save(tokbuf.string);
+ d = NUM;
+ break;
+
+
+ case LETTER:
+ case Letter:
+ sp = tokbuf.string;
+ limit = sp + tokbuf.size;
+ *sp++ = c;
+ for (;;) {
+ GETC(frew, c);
+ if ((d=ctab[c])!=LETTER && d!=Letter && d!=DIGIT && d!=IDCHAR)
+ break;
+ *sp++ = c;
+ if (limit <= sp)
+ sp = bufenlarge(&tokbuf, &limit);
+ }
+ *sp = 0;
+ NextString = fstr_save(tokbuf.string);
+ d = ID; /* may be ID or keyword */
+ break;
+
+ case SBEGIN: /* long string */
+ d = STRING;
+ /* note: only the initial SBEGIN has been read*/
+ /* read the string, and reset nextc afterwards*/
+ break;
+
+ case COLON:
+ case SEMI:
+ GETC(frew, c);
+ break;
+ } break; }
+ nextc = c;
+ nexttok = d;
+ uncache(fin);
+}
+
+ int
+eoflex()
+/*
+ * Yield true if we look ahead to the end of the input, false otherwise.
+ * nextc becomes undefined at end of file.
+ */
+{
+ register int c;
+ declarecache;
+ register FILE *fout;
+ register RILE *fin;
+
+ c = nextc;
+ fin = finptr;
+ fout = foutptr;
+ setupcache(fin); cache(fin);
+
+ for (;;) {
+ switch (ctab[c]) {
+ default:
+ nextc = c;
+ uncache(fin);
+ return false;
+
+ case NEWLN:
+ ++rcsline;
+ /* fall into */
+ case SPACE:
+ cachegeteof(c, {uncache(fin);return true;});
+ break;
+ }
+ if (fout)
+ aputc(c, fout);
+ }
+}
+
+
+int getlex(token)
+enum tokens token;
+/* Function: Checks if nexttok is the same as token. If so,
+ * advances the input by calling nextlex and returns true.
+ * otherwise returns false.
+ * Doesn't work for strings and keywords; loses the character string for ids.
+ */
+{
+ if (nexttok==token) {
+ nextlex();
+ return(true);
+ } else return(false);
+}
+
+ int
+getkeyopt(key)
+ char const *key;
+/* Function: If the current token is a keyword identical to key,
+ * advances the input by calling nextlex and returns true;
+ * otherwise returns false.
+ */
+{
+ if (nexttok==ID && strcmp(key,NextString) == 0) {
+ /* match found */
+ ffree1(NextString);
+ nextlex();
+ return(true);
+ }
+ return(false);
+}
+
+ void
+getkey(key)
+ char const *key;
+/* Check that the current input token is a keyword identical to key,
+ * and advance the input by calling nextlex.
+ */
+{
+ if (!getkeyopt(key))
+ fatserror("missing '%s' keyword", key);
+}
+
+ void
+getkeystring(key)
+ char const *key;
+/* Check that the current input token is a keyword identical to key,
+ * and advance the input by calling nextlex; then look ahead for a string.
+ */
+{
+ getkey(key);
+ if (nexttok != STRING)
+ fatserror("missing string after '%s' keyword", key);
+}
+
+
+ char const *
+getid()
+/* Function: Checks if nexttok is an identifier. If so,
+ * advances the input by calling nextlex and returns a pointer
+ * to the identifier; otherwise returns nil.
+ * Treats keywords as identifiers.
+ */
+{
+ register char const *name;
+ if (nexttok==ID) {
+ name = NextString;
+ nextlex();
+ return name;
+ } else return nil;
+}
+
+
+struct hshentry * getnum()
+/* Function: Checks if nexttok is a number. If so,
+ * advances the input by calling nextlex and returns a pointer
+ * to the hashtable entry. Otherwise returns nil.
+ * Doesn't work if hshenter is false.
+ */
+{
+ register struct hshentry * num;
+ if (nexttok==NUM) {
+ num=nexthsh;
+ nextlex();
+ return num;
+ } else return nil;
+}
+
+ struct cbuf
+getphrases(key)
+ char const *key;
+/* Get a series of phrases that do not start with KEY, yield resulting buffer.
+ * Stop when the next phrase starts with a token that is not an identifier,
+ * or is KEY.
+ * Assume !foutptr.
+ */
+{
+ declarecache;
+ register int c;
+ register char *p;
+ char const *limit;
+ register char const *ki, *kn;
+ struct cbuf r;
+ struct buf b;
+ register RILE *fin;
+
+ if (nexttok!=ID || strcmp(NextString,key) == 0) {
+ r.string = 0;
+ r.size = 0;
+ return r;
+ } else {
+ warnignore();
+ fin = finptr;
+ setupcache(fin); cache(fin);
+ bufautobegin(&b);
+ bufscpy(&b, NextString);
+ ffree1(NextString);
+ p = b.string + strlen(b.string);
+ limit = b.string + b.size;
+ c = nextc;
+ for (;;) {
+ for (;;) {
+ if (limit <= p)
+ p = bufenlarge(&b, &limit);
+ *p++ = c;
+ switch (ctab[c]) {
+ default:
+ fatserror("unknown character `%c'", c);
+ /*NOTREACHED*/
+ case NEWLN:
+ ++rcsline;
+ /* fall into */
+ case COLON: case DIGIT: case LETTER: case Letter:
+ case PERIOD: case SPACE:
+ cacheget(c);
+ continue;
+ case SBEGIN: /* long string */
+ for (;;) {
+ for (;;) {
+ if (limit <= p)
+ p = bufenlarge(&b, &limit);
+ cacheget(c);
+ *p++ = c;
+ switch (c) {
+ case '\n':
+ ++rcsline;
+ /* fall into */
+ default:
+ continue;
+
+ case SDELIM:
+ break;
+ }
+ break;
+ }
+ cacheget(c);
+ if (c != SDELIM)
+ break;
+ if (limit <= p)
+ p = bufenlarge(&b, &limit);
+ *p++ = c;
+ }
+ continue;
+ case SEMI:
+ cacheget(c);
+ if (ctab[c] == NEWLN) {
+ ++rcsline;
+ if (limit <= p)
+ p = bufenlarge(&b, &limit);
+ *p++ = c;
+ cacheget(c);
+ }
+ for (;;) {
+ switch (ctab[c]) {
+ case NEWLN:
+ ++rcsline;
+ /* fall into */
+ case SPACE:
+ cacheget(c);
+ continue;
+
+ default: break;
+ }
+ break;
+ }
+ break;
+ }
+ break;
+ }
+ switch (ctab[c]) {
+ case LETTER:
+ case Letter:
+ for (kn = key; c && *kn==c; kn++)
+ cacheget(c);
+ if (!*kn)
+ switch (ctab[c]) {
+ case DIGIT: case LETTER: case Letter:
+ break;
+ default:
+ nextc = c;
+ NextString = fstr_save(key);
+ nexttok = ID;
+ uncache(fin);
+ goto returnit;
+ }
+ for (ki=key; ki<kn; ) {
+ if (limit <= p)
+ p = bufenlarge(&b, &limit);
+ *p++ = *ki++;
+ }
+ break;
+
+ default:
+ nextc = c;
+ uncache(fin);
+ nextlex();
+ goto returnit;
+ }
+ }
+ returnit:
+ return bufremember(&b, (size_t)(p - b.string));
+ }
+}
+
+
+ void
+readstring()
+/* skip over characters until terminating single SDELIM */
+/* If foutptr is set, copy every character read to foutptr. */
+/* Does not advance nextlex at the end. */
+{ register c;
+ declarecache;
+ register FILE *frew;
+ register RILE *fin;
+ fin=finptr; frew=foutptr;
+ setupcache(fin); cache(fin);
+ for (;;) {
+ GETC(frew, c);
+ switch (c) {
+ case '\n':
+ ++rcsline;
+ break;
+
+ case SDELIM:
+ GETC(frew, c);
+ if (c != SDELIM) {
+ /* end of string */
+ nextc = c;
+ uncache(fin);
+ return;
+ }
+ break;
+ }
+ }
+}
+
+
+ void
+printstring()
+/* Function: copy a string to stdout, until terminated with a single SDELIM.
+ * Does not advance nextlex at the end.
+ */
+{
+ register c;
+ declarecache;
+ register FILE *fout;
+ register RILE *fin;
+ fin=finptr;
+ fout = stdout;
+ setupcache(fin); cache(fin);
+ for (;;) {
+ cacheget(c);
+ switch (c) {
+ case '\n':
+ ++rcsline;
+ break;
+ case SDELIM:
+ cacheget(c);
+ if (c != SDELIM) {
+ nextc=c;
+ uncache(fin);
+ return;
+ }
+ break;
+ }
+ aputc(c,fout);
+ }
+}
+
+
+
+ struct cbuf
+savestring(target)
+ struct buf *target;
+/* Copies a string terminated with SDELIM from file finptr to buffer target.
+ * Double SDELIM is replaced with SDELIM.
+ * If foutptr is set, the string is also copied unchanged to foutptr.
+ * Does not advance nextlex at the end.
+ * Yield a copy of *TARGET, except with exact length.
+ */
+{
+ register c;
+ declarecache;
+ register FILE *frew;
+ register char *tp;
+ register RILE *fin;
+ char const *limit;
+ struct cbuf r;
+
+ fin=finptr; frew=foutptr;
+ setupcache(fin); cache(fin);
+ tp = target->string; limit = tp + target->size;
+ for (;;) {
+ GETC(frew, c);
+ switch (c) {
+ case '\n':
+ ++rcsline;
+ break;
+ case SDELIM:
+ GETC(frew, c);
+ if (c != SDELIM) {
+ /* end of string */
+ nextc=c;
+ r.string = target->string;
+ r.size = tp - r.string;
+ uncache(fin);
+ return r;
+ }
+ break;
+ }
+ if (tp == limit)
+ tp = bufenlarge(target, &limit);
+ *tp++ = c;
+ }
+}
+
+
+ char *
+checkid(id, delimiter)
+ register char *id;
+ int delimiter;
+/* Function: check whether the string starting at id is an */
+/* identifier and return a pointer to the delimiter*/
+/* after the identifier. White space, delim and 0 */
+/* are legal delimiters. Aborts the program if not*/
+/* a legal identifier. Useful for checking commands*/
+/* If !delim, the only delimiter is 0. */
+{
+ register enum tokens d;
+ register char *temp;
+ register char c,tc;
+ register char delim = delimiter;
+
+ temp = id;
+ if ((d = ctab[(unsigned char)(c = *id)])==LETTER || d==Letter) {
+ while ((d = ctab[(unsigned char)(c = *++id)])==LETTER
+ || d==Letter || d==DIGIT || d==IDCHAR
+ )
+ ;
+ if (c && (!delim || c!=delim && c!=' ' && c!='\t' && c!='\n')) {
+ /* append \0 to end of id before error message */
+ tc = c;
+ while( (c=(*++id))!=' ' && c!='\t' && c!='\n' && c!='\0' && c!=delim) ;
+ *id = '\0';
+ faterror("invalid character %c in identifier `%s'",tc,temp);
+ }
+ } else {
+ /* append \0 to end of id before error message */
+ while( (c=(*++id))!=' ' && c!='\t' && c!='\n' && c!='\0' && c!=delim) ;
+ *id = '\0';
+ faterror("identifier `%s' doesn't start with letter", temp);
+ }
+ return id;
+}
+
+ void
+checksid(id)
+ char *id;
+/* Check whether the string ID is an identifier. */
+{
+ VOID checkid(id, 0);
+}
+
+
+ static RILE *
+#if has_mmap && large_memory
+fd2_RILE(fd, filename, status)
+#else
+fd2RILE(fd, filename, mode, status)
+ char const *mode;
+#endif
+ int fd;
+ char const *filename;
+ register struct stat *status;
+{
+ struct stat st;
+
+ if (!status)
+ status = &st;
+ if (fstat(fd, status) != 0)
+ efaterror(filename);
+ if (!S_ISREG(status->st_mode)) {
+ error("`%s' is not a regular file", filename);
+ VOID close(fd);
+ errno = EINVAL;
+ return 0;
+ } else {
+
+# if ! (has_mmap && large_memory)
+ FILE *stream;
+ if (!(stream = fdopen(fd, mode)))
+ efaterror(filename);
+# endif
+
+# if !large_memory
+ return stream;
+# else
+# define RILES 3
+ {
+ static RILE rilebuf[RILES];
+
+ register RILE *f;
+ size_t s = status->st_size;
+
+ if (s != status->st_size)
+ faterror("`%s' is enormous", filename);
+ for (f = rilebuf; f->base; f++)
+ if (f == rilebuf+RILES)
+ faterror("too many RILEs");
+ if (!s) {
+ static unsigned char dummy;
+ f->base = &dummy;
+ } else {
+# if has_mmap
+ if (
+ (f->base = (unsigned char *)mmap(
+ (caddr_t)0, s, PROT_READ, MAP_SHARED,
+ fd, (off_t)0
+ )) == (unsigned char *)-1
+ )
+ efaterror("mmap");
+# else
+ f->base = tnalloc(unsigned char, s);
+# endif
+ }
+ f->ptr = f->base;
+ f->lim = f->base + s;
+# if has_mmap
+ f->fd = fd;
+# else
+ f->readlim = f->base;
+ f->stream = stream;
+# endif
+ if_advise_access(s, f, MADV_SEQUENTIAL);
+ return f;
+ }
+# endif
+ }
+}
+
+#if !has_mmap && large_memory
+ int
+Igetmore(f)
+ register RILE *f;
+{
+ register fread_type r;
+ register size_t s = f->lim - f->readlim;
+
+ if (BUFSIZ < s)
+ s = BUFSIZ;
+ if (!(r = Fread(f->readlim, sizeof(*f->readlim), s, f->stream))) {
+ testIerror(f->stream);
+ f->lim = f->readlim; /* The file might have shrunk! */
+ return 0;
+ }
+ f->readlim += r;
+ return 1;
+}
+#endif
+
+#if has_madvise && has_mmap && large_memory
+ void
+advise_access(f, advice)
+ register RILE *f;
+ int advice;
+{
+ if (madvise((caddr_t)f->base, (size_t)(f->lim - f->base), advice) != 0)
+ efaterror("madvise");
+}
+#endif
+
+ RILE *
+#if has_mmap && large_memory
+I_open(filename, status)
+#else
+Iopen(filename, mode, status)
+ char const *mode;
+#endif
+ char const *filename;
+ struct stat *status;
+/* Open FILENAME for reading, yield its descriptor, and set *STATUS. */
+{
+ int fd;
+
+ if ((fd = open(filename,O_RDONLY|O_BINARY)) < 0)
+ return 0;
+# if has_mmap && large_memory
+ return fd2_RILE(fd, filename, status);
+# else
+ return fd2RILE(fd, filename, mode, status);
+# endif
+}
+
+
+#if !large_memory
+# define Iclose(f) fclose(f)
+#else
+ static int
+ Iclose(f)
+ register RILE *f;
+ {
+# if has_mmap
+ size_t s = f->lim - f->base;
+ if (s && munmap((caddr_t)f->base, s) != 0)
+ return -1;
+ f->base = 0;
+ return close(f->fd);
+# else
+ tfree(f->base);
+ f->base = 0;
+ return fclose(f->stream);
+# endif
+ }
+#endif
+
+
+static int Oerrloop;
+
+ exiting void
+Oerror()
+{
+ if (Oerrloop)
+ exiterr();
+ Oerrloop = true;
+ efaterror("output error");
+}
+
+exiting void Ieof() { fatserror("unexpected end of file"); }
+exiting void Ierror() { efaterror("input error"); }
+void testIerror(f) FILE *f; { if (ferror(f)) Ierror(); }
+void testOerror(o) FILE *o; { if (ferror(o)) Oerror(); }
+
+void Ifclose(f) RILE *f; { if (f && Iclose(f)!=0) Ierror(); }
+void Ofclose(f) FILE *f; { if (f && fclose(f)!=0) Oerror(); }
+void Izclose(p) RILE **p; { Ifclose(*p); *p = 0; }
+void Ozclose(p) FILE **p; { Ofclose(*p); *p = 0; }
+
+#if !large_memory
+ void
+testIeof(f)
+ FILE *f;
+{
+ testIerror(f);
+ if (feof(f))
+ Ieof();
+}
+void Irewind(f) FILE *f; { if (fseek(f,0L,SEEK_SET) != 0) Ierror(); }
+#endif
+
+void eflush()
+{
+ if (fflush(stderr) != 0 && !Oerrloop)
+ Oerror();
+}
+
+void oflush()
+{
+ if (fflush(workstdout ? workstdout : stdout) != 0 && !Oerrloop)
+ Oerror();
+}
+
+ static exiting void
+fatcleanup(already_newline)
+ int already_newline;
+{
+ VOID fprintf(stderr, already_newline+"\n%s aborted\n", cmdid);
+ exiterr();
+}
+
+static void errsay() { oflush(); aprintf(stderr,"%s error: ",cmdid); nerror++; }
+static void fatsay() { oflush(); VOID fprintf(stderr,"%s error: ",cmdid); }
+
+void eerror(s) char const *s; { enerror(errno,s); }
+
+ void
+enerror(e,s)
+ int e;
+ char const *s;
+{
+ errsay();
+ errno = e;
+ perror(s);
+ eflush();
+}
+
+exiting void efaterror(s) char const *s; { enfaterror(errno,s); }
+
+ exiting void
+enfaterror(e,s)
+ int e;
+ char const *s;
+{
+ fatsay();
+ errno = e;
+ perror(s);
+ fatcleanup(true);
+}
+
+#if has_prototypes
+ void
+error(char const *format,...)
+#else
+ /*VARARGS1*/ void error(format, va_alist) char const *format; va_dcl
+#endif
+/* non-fatal error */
+{
+ va_list args;
+ errsay();
+ vararg_start(args, format);
+ fvfprintf(stderr, format, args);
+ va_end(args);
+ afputc('\n',stderr);
+ eflush();
+}
+
+#if has_prototypes
+ exiting void
+fatserror(char const *format,...)
+#else
+ /*VARARGS1*/ exiting void
+ fatserror(format, va_alist) char const *format; va_dcl
+#endif
+/* fatal syntax error */
+{
+ va_list args;
+ oflush();
+ VOID fprintf(stderr, "%s: %s:%lu: ", cmdid, RCSfilename, rcsline);
+ vararg_start(args, format);
+ fvfprintf(stderr, format, args);
+ va_end(args);
+ fatcleanup(false);
+}
+
+#if has_prototypes
+ exiting void
+faterror(char const *format,...)
+#else
+ /*VARARGS1*/ exiting void faterror(format, va_alist)
+ char const *format; va_dcl
+#endif
+/* fatal error, terminates program after cleanup */
+{
+ va_list args;
+ fatsay();
+ vararg_start(args, format);
+ fvfprintf(stderr, format, args);
+ va_end(args);
+ fatcleanup(false);
+}
+
+#if has_prototypes
+ void
+warn(char const *format,...)
+#else
+ /*VARARGS1*/ void warn(format, va_alist) char const *format; va_dcl
+#endif
+/* prints a warning message */
+{
+ va_list args;
+ oflush();
+ aprintf(stderr,"%s warning: ",cmdid);
+ vararg_start(args, format);
+ fvfprintf(stderr, format, args);
+ va_end(args);
+ afputc('\n',stderr);
+ eflush();
+}
+
+ void
+redefined(c)
+ int c;
+{
+ warn("redefinition of -%c option", c);
+}
+
+#if has_prototypes
+ void
+diagnose(char const *format,...)
+#else
+ /*VARARGS1*/ void diagnose(format, va_alist) char const *format; va_dcl
+#endif
+/* prints a diagnostic message */
+/* Unlike the other routines, it does not append a newline. */
+/* This lets some callers suppress the newline, and is faster */
+/* in implementations that flush stderr just at the end of each printf. */
+{
+ va_list args;
+ if (!quietflag) {
+ oflush();
+ vararg_start(args, format);
+ fvfprintf(stderr, format, args);
+ va_end(args);
+ eflush();
+ }
+}
+
+
+
+ void
+afputc(c, f)
+/* Function: afputc(c,f) acts like aputc(c,f), but is smaller and slower.
+ */
+ int c;
+ register FILE *f;
+{
+ aputc(c,f);
+}
+
+
+ void
+aputs(s, iop)
+ char const *s;
+ FILE *iop;
+/* Function: Put string s on file iop, abort on error.
+ */
+{
+#if has_fputs
+ if (fputs(s, iop) < 0)
+ Oerror();
+#else
+ awrite(s, strlen(s), iop);
+#endif
+}
+
+
+
+ void
+#if has_prototypes
+fvfprintf(FILE *stream, char const *format, va_list args)
+#else
+ fvfprintf(stream,format,args) FILE *stream; char *format; va_list args;
+#endif
+/* like vfprintf, except abort program on error */
+{
+#if has_vfprintf
+ if (vfprintf(stream, format, args) < 0)
+#else
+# if has__doprintf
+ _doprintf(stream, format, args);
+# else
+# if has__doprnt
+ _doprnt(format, args, stream);
+# else
+ int *a = (int *)args;
+ VOID fprintf(stream, format,
+ a[0], a[1], a[2], a[3], a[4],
+ a[5], a[6], a[7], a[8], a[9]
+ );
+# endif
+# endif
+ if (ferror(stream))
+#endif
+ Oerror();
+}
+
+#if has_prototypes
+ void
+aprintf(FILE *iop, char const *fmt, ...)
+#else
+ /*VARARGS2*/ void
+aprintf(iop, fmt, va_alist)
+FILE *iop;
+char const *fmt;
+va_dcl
+#endif
+/* Function: formatted output. Same as fprintf in stdio,
+ * but aborts program on error
+ */
+{
+ va_list ap;
+ vararg_start(ap, fmt);
+ fvfprintf(iop, fmt, ap);
+ va_end(ap);
+}
+
+
+
+#ifdef LEXDB
+/* test program reading a stream of lexemes and printing the tokens.
+ */
+
+
+
+ int
+main(argc,argv)
+int argc; char * argv[];
+{
+ cmdid="lextest";
+ if (argc<2) {
+ aputs("No input file\n",stderr);
+ exitmain(EXIT_FAILURE);
+ }
+ if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
+ faterror("can't open input file %s",argv[1]);
+ }
+ Lexinit();
+ while (!eoflex()) {
+ switch (nexttok) {
+
+ case ID:
+ VOID printf("ID: %s",NextString);
+ break;
+
+ case NUM:
+ if (hshenter)
+ VOID printf("NUM: %s, index: %d",nexthsh->num, nexthsh-hshtab);
+ else
+ VOID printf("NUM, unentered: %s",NextString);
+ hshenter = !hshenter; /*alternate between dates and numbers*/
+ break;
+
+ case COLON:
+ VOID printf("COLON"); break;
+
+ case SEMI:
+ VOID printf("SEMI"); break;
+
+ case STRING:
+ readstring();
+ VOID printf("STRING"); break;
+
+ case UNKN:
+ VOID printf("UNKN"); break;
+
+ default:
+ VOID printf("DEFAULT"); break;
+ }
+ VOID printf(" | ");
+ nextlex();
+ }
+ exitmain(EXIT_SUCCESS);
+}
+
+exiting void exiterr() { _exit(EXIT_FAILURE); }
+
+
+#endif
diff --git a/gnu/usr.bin/rcs/lib/rcsmap.c b/gnu/usr.bin/rcs/lib/rcsmap.c
new file mode 100644
index 0000000..0e7b23c
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/rcsmap.c
@@ -0,0 +1,68 @@
+/* RCS map of character types */
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+#include "rcsbase.h"
+
+libId(mapId, "$Id: rcsmap.c,v 5.2 1991/08/19 03:13:55 eggert Exp $")
+
+/* map of character types */
+/* ISO 8859/1 (Latin-1) */
+enum tokens const ctab[] = {
+ UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN,
+ SPACE, SPACE, NEWLN, SPACE, SPACE, SPACE, UNKN, UNKN,
+ UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN,
+ UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN,
+ SPACE, IDCHAR, IDCHAR, IDCHAR, DELIM, IDCHAR, IDCHAR, IDCHAR,
+ IDCHAR, IDCHAR, IDCHAR, IDCHAR, DELIM, IDCHAR, PERIOD, IDCHAR,
+ DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT, DIGIT,
+ DIGIT, DIGIT, COLON, SEMI, IDCHAR, IDCHAR, IDCHAR, IDCHAR,
+ SBEGIN, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER,
+ LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER,
+ LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER,
+ LETTER, LETTER, LETTER, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR,
+ IDCHAR, Letter, Letter, Letter, Letter, Letter, Letter, Letter,
+ Letter, Letter, Letter, Letter, Letter, Letter, Letter, Letter,
+ Letter, Letter, Letter, Letter, Letter, Letter, Letter, Letter,
+ Letter, Letter, Letter, IDCHAR, IDCHAR, IDCHAR, IDCHAR, UNKN,
+ UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN,
+ UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN,
+ UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN,
+ UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN, UNKN,
+ IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR,
+ IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR,
+ IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR,
+ IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR, IDCHAR,
+ LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER,
+ LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER,
+ LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, IDCHAR,
+ LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, LETTER, Letter,
+ Letter, Letter, Letter, Letter, Letter, Letter, Letter, Letter,
+ Letter, Letter, Letter, Letter, Letter, Letter, Letter, Letter,
+ Letter, Letter, Letter, Letter, Letter, Letter, Letter, IDCHAR,
+ Letter, Letter, Letter, Letter, Letter, Letter, Letter, Letter
+};
diff --git a/gnu/usr.bin/rcs/lib/rcsrev.c b/gnu/usr.bin/rcs/lib/rcsrev.c
new file mode 100644
index 0000000..ce11f54
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/rcsrev.c
@@ -0,0 +1,790 @@
+/*
+ * RCS revision number handling
+ */
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+
+
+/* $Log: rcsrev.c,v $
+ * Revision 5.3 1991/08/19 03:13:55 eggert
+ * Add `-r$', `-rB.'. Remove botches like `<now>' from messages. Tune.
+ *
+ * Revision 5.2 1991/04/21 11:58:28 eggert
+ * Add tiprev().
+ *
+ * Revision 5.1 1991/02/25 07:12:43 eggert
+ * Avoid overflow when comparing revision numbers.
+ *
+ * Revision 5.0 1990/08/22 08:13:43 eggert
+ * Remove compile-time limits; use malloc instead.
+ * Ansify and Posixate. Tune.
+ * Remove possibility of an internal error. Remove lint.
+ *
+ * Revision 4.5 89/05/01 15:13:22 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.4 87/12/18 11:45:22 narten
+ * more lint cleanups. Also, the NOTREACHED comment is no longer necessary,
+ * since there's now a return value there with a value. (Guy Harris)
+ *
+ * Revision 4.3 87/10/18 10:38:42 narten
+ * Updating version numbers. Changes relative to version 1.1 actually
+ * relative to 4.1
+ *
+ * Revision 1.3 87/09/24 14:00:37 narten
+ * Sources now pass through lint (if you ignore printf/sprintf/fprintf
+ * warnings)
+ *
+ * Revision 1.2 87/03/27 14:22:37 jenkins
+ * Port to suns
+ *
+ * Revision 4.1 83/03/25 21:10:45 wft
+ * Only changed $Header to $Id.
+ *
+ * Revision 3.4 82/12/04 13:24:08 wft
+ * Replaced getdelta() with gettree().
+ *
+ * Revision 3.3 82/11/28 21:33:15 wft
+ * fixed compartial() and compnum() for nil-parameters; fixed nils
+ * in error messages. Testprogram output shortenend.
+ *
+ * Revision 3.2 82/10/18 21:19:47 wft
+ * renamed compnum->cmpnum, compnumfld->cmpnumfld,
+ * numericrevno->numricrevno.
+ *
+ * Revision 3.1 82/10/11 19:46:09 wft
+ * changed expandsym() to check for source==nil; returns zero length string
+ * in that case.
+ */
+
+
+
+/*
+#define REVTEST
+*/
+/* version REVTEST is for testing the routines that generate a sequence
+ * of delta numbers needed to regenerate a given delta.
+ */
+
+#include "rcsbase.h"
+
+libId(revId, "$Id: rcsrev.c,v 5.3 1991/08/19 03:13:55 eggert Exp $")
+
+static char const *branchtip P((char const*));
+static struct hshentry *genbranch P((struct hshentry const*,char const*,unsigned,char const*,char const*,char const*,struct hshentries**));
+
+
+
+ unsigned
+countnumflds(s)
+ char const *s;
+/* Given a pointer s to a dotted number (date or revision number),
+ * countnumflds returns the number of digitfields in s.
+ */
+{
+ register char const *sp;
+ register unsigned count;
+ if ((sp=s)==nil) return(0);
+ if (*sp == '\0') return(0);
+ count = 1;
+ do {
+ if (*sp++ == '.') count++;
+ } while (*sp);
+ return(count);
+}
+
+ void
+getbranchno(revno,branchno)
+ char const *revno;
+ struct buf *branchno;
+/* Given a non-nil revision number revno, getbranchno copies the number of the branch
+ * on which revno is into branchno. If revno itself is a branch number,
+ * it is copied unchanged.
+ */
+{
+ register unsigned numflds;
+ register char *tp;
+
+ bufscpy(branchno, revno);
+ numflds=countnumflds(revno);
+ if (!(numflds & 1)) {
+ tp = branchno->string;
+ while (--numflds)
+ while (*tp++ != '.')
+ ;
+ *(tp-1)='\0';
+ }
+}
+
+
+
+int cmpnum(num1, num2)
+ char const *num1, *num2;
+/* compares the two dotted numbers num1 and num2 lexicographically
+ * by field. Individual fields are compared numerically.
+ * returns <0, 0, >0 if num1<num2, num1==num2, and num1>num2, resp.
+ * omitted fields are assumed to be higher than the existing ones.
+*/
+{
+ register char const *s1, *s2;
+ register size_t d1, d2;
+ register int r;
+
+ s1=num1==nil?"":num1;
+ s2=num2==nil?"":num2;
+
+ for (;;) {
+ /* Give precedence to shorter one. */
+ if (!*s1)
+ return (unsigned char)*s2;
+ if (!*s2)
+ return -1;
+
+ /* Strip leading zeros, then find number of digits. */
+ while (*s1=='0') ++s1; for (d1=0; isdigit(s1[d1]); d1++) ;
+ while (*s2=='0') ++s2; for (d2=0; isdigit(s2[d2]); d2++) ;
+
+ /* Do not convert to integer; it might overflow! */
+ if (d1 != d2)
+ return d1<d2 ? -1 : 1;
+ if ((r = memcmp(s1, s2, d1)))
+ return r;
+ s1 += d1;
+ s2 += d1;
+
+ /* skip '.' */
+ if (*s1) s1++;
+ if (*s2) s2++;
+ }
+}
+
+
+
+int cmpnumfld(num1, num2, fld)
+ char const *num1, *num2;
+ unsigned fld;
+/* Compare the two dotted numbers at field fld.
+ * num1 and num2 must have at least fld fields.
+ * fld must be positive.
+*/
+{
+ register char const *s1, *s2;
+ register size_t d1, d2;
+
+ s1 = num1;
+ s2 = num2;
+ /* skip fld-1 fields */
+ while (--fld) {
+ while (*s1++ != '.')
+ ;
+ while (*s2++ != '.')
+ ;
+ }
+ /* Now s1 and s2 point to the beginning of the respective fields */
+ while (*s1=='0') ++s1; for (d1=0; isdigit(s1[d1]); d1++) ;
+ while (*s2=='0') ++s2; for (d2=0; isdigit(s2[d2]); d2++) ;
+
+ return d1<d2 ? -1 : d1==d2 ? memcmp(s1,s2,d1) : 1;
+}
+
+
+ static void
+cantfindbranch(revno, date, author, state)
+ char const *revno, date[datesize], *author, *state;
+{
+ char datebuf[datesize];
+
+ error("No revision on branch %s has%s%s%s%s%s%s.",
+ revno,
+ date ? " a date before " : "",
+ date ? date2str(date,datebuf) : "",
+ author ? " and author "+(date?0:4) : "",
+ author ? author : "",
+ state ? " and state "+(date||author?0:4) : "",
+ state ? state : ""
+ );
+}
+
+ static void
+absent(revno, field)
+ char const *revno;
+ unsigned field;
+{
+ struct buf t;
+ bufautobegin(&t);
+ error("%s %s absent", field&1?"revision":"branch",
+ partialno(&t,revno,field)
+ );
+ bufautoend(&t);
+}
+
+
+ int
+compartial(num1, num2, length)
+ char const *num1, *num2;
+ unsigned length;
+
+/* compare the first "length" fields of two dot numbers;
+ the omitted field is considered to be larger than any number */
+/* restriction: at least one number has length or more fields */
+
+{
+ register char const *s1, *s2;
+ register size_t d1, d2;
+ register int r;
+
+ s1 = num1; s2 = num2;
+ if (!s1) return 1;
+ if (!s2) return -1;
+
+ for (;;) {
+ if (!*s1) return 1;
+ if (!*s2) return -1;
+
+ while (*s1=='0') ++s1; for (d1=0; isdigit(s1[d1]); d1++) ;
+ while (*s2=='0') ++s2; for (d2=0; isdigit(s2[d2]); d2++) ;
+
+ if (d1 != d2)
+ return d1<d2 ? -1 : 1;
+ if ((r = memcmp(s1, s2, d1)))
+ return r;
+ s1 += d1;
+ s2 += d1;
+
+ if (*s1 == '.') s1++;
+ if (*s2 == '.') s2++;
+
+ if ( --length == 0 ) return 0;
+ }
+}
+
+
+char * partialno(rev1,rev2,length)
+ struct buf *rev1;
+ char const *rev2;
+ register unsigned length;
+/* Function: Copies length fields of revision number rev2 into rev1.
+ * Return rev1's string.
+ */
+{
+ register char *r1;
+
+ bufscpy(rev1, rev2);
+ r1 = rev1->string;
+ while (length) {
+ while (*r1!='.' && *r1)
+ ++r1;
+ ++r1;
+ length--;
+ }
+ /* eliminate last '.'*/
+ *(r1-1)='\0';
+ return rev1->string;
+}
+
+
+
+
+ static void
+store1(store, next)
+ struct hshentries ***store;
+ struct hshentry *next;
+/*
+ * Allocate a new list node that addresses NEXT.
+ * Append it to the list that **STORE is the end pointer of.
+ */
+{
+ register struct hshentries *p;
+
+ p = ftalloc(struct hshentries);
+ p->first = next;
+ **store = p;
+ *store = &p->rest;
+}
+
+struct hshentry * genrevs(revno,date,author,state,store)
+ char const *revno, *date, *author, *state;
+ struct hshentries **store;
+/* Function: finds the deltas needed for reconstructing the
+ * revision given by revno, date, author, and state, and stores pointers
+ * to these deltas into a list whose starting address is given by store.
+ * The last delta (target delta) is returned.
+ * If the proper delta could not be found, nil is returned.
+ */
+{
+ unsigned length;
+ register struct hshentry * next;
+ int result;
+ char const *branchnum;
+ struct buf t;
+ char datebuf[datesize];
+
+ bufautobegin(&t);
+
+ if (!(next = Head)) {
+ error("RCS file empty");
+ goto norev;
+ }
+
+ length = countnumflds(revno);
+
+ if (length >= 1) {
+ /* at least one field; find branch exactly */
+ while ((result=cmpnumfld(revno,next->num,1)) < 0) {
+ store1(&store, next);
+ next = next->next;
+ if (!next) {
+ error("branch number %s too low", partialno(&t,revno,1));
+ goto norev;
+ }
+ }
+
+ if (result>0) {
+ absent(revno, 1);
+ goto norev;
+ }
+ }
+ if (length<=1){
+ /* pick latest one on given branch */
+ branchnum = next->num; /* works even for empty revno*/
+ while ((next!=nil) &&
+ (cmpnumfld(branchnum,next->num,1)==0) &&
+ !(
+ (date==nil?1:(cmpnum(date,next->date)>=0)) &&
+ (author==nil?1:(strcmp(author,next->author)==0)) &&
+ (state ==nil?1:(strcmp(state, next->state) ==0))
+ )
+ )
+ {
+ store1(&store, next);
+ next=next->next;
+ }
+ if ((next==nil) ||
+ (cmpnumfld(branchnum,next->num,1)!=0))/*overshot*/ {
+ cantfindbranch(
+ length ? revno : partialno(&t,branchnum,1),
+ date, author, state
+ );
+ goto norev;
+ } else {
+ store1(&store, next);
+ }
+ *store = nil;
+ return next;
+ }
+
+ /* length >=2 */
+ /* find revision; may go low if length==2*/
+ while ((result=cmpnumfld(revno,next->num,2)) < 0 &&
+ (cmpnumfld(revno,next->num,1)==0) ) {
+ store1(&store, next);
+ next = next->next;
+ if (!next)
+ break;
+ }
+
+ if ((next==nil) || (cmpnumfld(revno,next->num,1)!=0)) {
+ error("revision number %s too low", partialno(&t,revno,2));
+ goto norev;
+ }
+ if ((length>2) && (result!=0)) {
+ absent(revno, 2);
+ goto norev;
+ }
+
+ /* print last one */
+ store1(&store, next);
+
+ if (length>2)
+ return genbranch(next,revno,length,date,author,state,store);
+ else { /* length == 2*/
+ if ((date!=nil) && (cmpnum(date,next->date)<0)){
+ error("Revision %s has date %s.",
+ next->num,
+ date2str(next->date, datebuf)
+ );
+ return nil;
+ }
+ if ((author!=nil)&&(strcmp(author,next->author)!=0)) {
+ error("Revision %s has author %s.",next->num,next->author);
+ return nil;
+ }
+ if ((state!=nil)&&(strcmp(state,next->state)!=0)) {
+ error("Revision %s has state %s.",next->num,
+ next->state==nil?"<empty>":next->state);
+ return nil;
+ }
+ *store=nil;
+ return next;
+ }
+
+ norev:
+ bufautoend(&t);
+ return nil;
+}
+
+
+
+
+ static struct hshentry *
+genbranch(bpoint, revno, length, date, author, state, store)
+ struct hshentry const *bpoint;
+ char const *revno;
+ unsigned length;
+ char const *date, *author, *state;
+ struct hshentries **store;
+/* Function: given a branchpoint, a revision number, date, author, and state,
+ * genbranch finds the deltas necessary to reconstruct the given revision
+ * from the branch point on.
+ * Pointers to the found deltas are stored in a list beginning with store.
+ * revno must be on a side branch.
+ * return nil on error
+ */
+{
+ unsigned field;
+ register struct hshentry * next, * trail;
+ register struct branchhead const *bhead;
+ int result;
+ struct buf t;
+ char datebuf[datesize];
+
+ field = 3;
+ bhead = bpoint->branches;
+
+ do {
+ if (!bhead) {
+ bufautobegin(&t);
+ error("no side branches present for %s", partialno(&t,revno,field-1));
+ bufautoend(&t);
+ return nil;
+ }
+
+ /*find branch head*/
+ /*branches are arranged in increasing order*/
+ while (0 < (result=cmpnumfld(revno,bhead->hsh->num,field))) {
+ bhead = bhead->nextbranch;
+ if (!bhead) {
+ bufautobegin(&t);
+ error("branch number %s too high",partialno(&t,revno,field));
+ bufautoend(&t);
+ return nil;
+ }
+ }
+
+ if (result<0) {
+ absent(revno, field);
+ return nil;
+ }
+
+ next = bhead->hsh;
+ if (length==field) {
+ /* pick latest one on that branch */
+ trail=nil;
+ do { if ((date==nil?1:(cmpnum(date,next->date)>=0)) &&
+ (author==nil?1:(strcmp(author,next->author)==0)) &&
+ (state ==nil?1:(strcmp(state, next->state) ==0))
+ ) trail = next;
+ next=next->next;
+ } while (next!=nil);
+
+ if (trail==nil) {
+ cantfindbranch(revno, date, author, state);
+ return nil;
+ } else { /* print up to last one suitable */
+ next = bhead->hsh;
+ while (next!=trail) {
+ store1(&store, next);
+ next=next->next;
+ }
+ store1(&store, next);
+ }
+ *store = nil;
+ return next;
+ }
+
+ /* length > field */
+ /* find revision */
+ /* check low */
+ if (cmpnumfld(revno,next->num,field+1)<0) {
+ bufautobegin(&t);
+ error("revision number %s too low", partialno(&t,revno,field+1));
+ bufautoend(&t);
+ return(nil);
+ }
+ do {
+ store1(&store, next);
+ trail = next;
+ next = next->next;
+ } while ((next!=nil) &&
+ (cmpnumfld(revno,next->num,field+1) >=0));
+
+ if ((length>field+1) && /*need exact hit */
+ (cmpnumfld(revno,trail->num,field+1) !=0)){
+ absent(revno, field+1);
+ return(nil);
+ }
+ if (length == field+1) {
+ if ((date!=nil) && (cmpnum(date,trail->date)<0)){
+ error("Revision %s has date %s.",
+ trail->num,
+ date2str(trail->date, datebuf)
+ );
+ return nil;
+ }
+ if ((author!=nil)&&(strcmp(author,trail->author)!=0)) {
+ error("Revision %s has author %s.",trail->num,trail->author);
+ return nil;
+ }
+ if ((state!=nil)&&(strcmp(state,trail->state)!=0)) {
+ error("Revision %s has state %s.",trail->num,
+ trail->state==nil?"<empty>":trail->state);
+ return nil;
+ }
+ }
+ bhead = trail->branches;
+
+ } while ((field+=2) <= length);
+ * store = nil;
+ return trail;
+}
+
+
+ static char const *
+lookupsym(id)
+ char const *id;
+/* Function: looks up id in the list of symbolic names starting
+ * with pointer SYMBOLS, and returns a pointer to the corresponding
+ * revision number. Returns nil if not present.
+ */
+{
+ register struct assoc const *next;
+ next = Symbols;
+ while (next!=nil) {
+ if (strcmp(id, next->symbol)==0)
+ return next->num;
+ else next=next->nextassoc;
+ }
+ return nil;
+}
+
+int expandsym(source, target)
+ char const *source;
+ struct buf *target;
+/* Function: Source points to a revision number. Expandsym copies
+ * the number to target, but replaces all symbolic fields in the
+ * source number with their numeric values.
+ * Expand a branch followed by `.' to the latest revision on that branch.
+ * Ignore `.' after a revision. Remove leading zeros.
+ * returns false on error;
+ */
+{
+ return fexpandsym(source, target, (RILE*)0);
+}
+
+ int
+fexpandsym(source, target, fp)
+ char const *source;
+ struct buf *target;
+ RILE *fp;
+/* Same as expandsym, except if FP is nonzero, it is used to expand KDELIM. */
+{
+ register char const *sp, *bp;
+ register char *tp;
+ char const *tlim;
+ register enum tokens d;
+ unsigned dots;
+
+ sp = source;
+ bufalloc(target, 1);
+ tp = target->string;
+ if (!sp || !*sp) { /*accept nil pointer as a legal value*/
+ *tp='\0';
+ return true;
+ }
+ if (sp[0] == KDELIM && !sp[1]) {
+ if (!getoldkeys(fp))
+ return false;
+ if (!*prevrev.string) {
+ error("working file lacks revision number");
+ return false;
+ }
+ bufscpy(target, prevrev.string);
+ return true;
+ }
+ tlim = tp + target->size;
+ dots = 0;
+
+ for (;;) {
+ switch (ctab[(unsigned char)*sp]) {
+ case DIGIT:
+ while (*sp=='0' && isdigit(sp[1]))
+ /* skip leading zeroes */
+ sp++;
+ do {
+ if (tlim <= tp)
+ tp = bufenlarge(target, &tlim);
+ } while (isdigit(*tp++ = *sp++));
+ --sp;
+ tp[-1] = '\0';
+ break;
+
+ case LETTER:
+ case Letter:
+ {
+ register char *p = tp;
+ register size_t s = tp - target->string;
+ do {
+ if (tlim <= p)
+ p = bufenlarge(target, &tlim);
+ *p++ = *sp++;
+ } while ((d=ctab[(unsigned char)*sp])==LETTER ||
+ d==Letter || d==DIGIT ||
+ (d==IDCHAR));
+ if (tlim <= p)
+ p = bufenlarge(target, &tlim);
+ *p = 0;
+ tp = target->string + s;
+ }
+ bp = lookupsym(tp);
+ if (bp==nil) {
+ error("Symbolic number %s is undefined.", tp);
+ return false;
+ }
+ do {
+ if (tlim <= tp)
+ tp = bufenlarge(target, &tlim);
+ } while ((*tp++ = *bp++));
+ break;
+
+ default:
+ goto improper;
+ }
+ switch (*sp++) {
+ case '\0': return true;
+ case '.': break;
+ default: goto improper;
+ }
+ if (!*sp) {
+ if (dots & 1)
+ goto improper;
+ if (!(bp = branchtip(target->string)))
+ return false;
+ bufscpy(target, bp);
+ return true;
+ }
+ ++dots;
+ tp[-1] = '.';
+ }
+
+ improper:
+ error("improper revision number: %s", source);
+ return false;
+}
+
+ static char const *
+branchtip(branch)
+ char const *branch;
+{
+ struct hshentry *h;
+ struct hshentries *hs;
+
+ h = genrevs(branch, (char*)0, (char*)0, (char*)0, &hs);
+ return h ? h->num : (char const*)0;
+}
+
+ char const *
+tiprev()
+{
+ return Dbranch ? branchtip(Dbranch) : Head ? Head->num : (char const*)0;
+}
+
+
+
+#ifdef REVTEST
+
+char const cmdid[] = "revtest";
+
+ int
+main(argc,argv)
+int argc; char * argv[];
+{
+ static struct buf numricrevno;
+ char symrevno[100]; /* used for input of revision numbers */
+ char author[20];
+ char state[20];
+ char date[20];
+ struct hshentries *gendeltas;
+ struct hshentry * target;
+ int i;
+
+ if (argc<2) {
+ aputs("No input file\n",stderr);
+ exitmain(EXIT_FAILURE);
+ }
+ if (!(finptr=Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
+ faterror("can't open input file %s", argv[1]);
+ }
+ Lexinit();
+ getadmin();
+
+ gettree();
+
+ getdesc(false);
+
+ do {
+ /* all output goes to stderr, to have diagnostics and */
+ /* errors in sequence. */
+ aputs("\nEnter revision number or <return> or '.': ",stderr);
+ if (!gets(symrevno)) break;
+ if (*symrevno == '.') break;
+ aprintf(stderr,"%s;\n",symrevno);
+ expandsym(symrevno,&numricrevno);
+ aprintf(stderr,"expanded number: %s; ",numricrevno.string);
+ aprintf(stderr,"Date: ");
+ gets(date); aprintf(stderr,"%s; ",date);
+ aprintf(stderr,"Author: ");
+ gets(author); aprintf(stderr,"%s; ",author);
+ aprintf(stderr,"State: ");
+ gets(state); aprintf(stderr, "%s;\n", state);
+ target = genrevs(numricrevno.string, *date?date:(char *)nil, *author?author:(char *)nil,
+ *state?state:(char*)nil, &gendeltas);
+ if (target!=nil) {
+ while (gendeltas) {
+ aprintf(stderr,"%s\n",gendeltas->first->num);
+ gendeltas = gendeltas->next;
+ }
+ }
+ } while (true);
+ aprintf(stderr,"done\n");
+ exitmain(EXIT_SUCCESS);
+}
+
+exiting void exiterr() { _exit(EXIT_FAILURE); }
+
+#endif
diff --git a/gnu/usr.bin/rcs/lib/rcssyn.c b/gnu/usr.bin/rcs/lib/rcssyn.c
new file mode 100644
index 0000000..31086c2
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/rcssyn.c
@@ -0,0 +1,857 @@
+/*
+ * RCS file input
+ */
+/*********************************************************************************
+ * Syntax Analysis.
+ * Keyword table
+ * Testprogram: define SYNTEST
+ * Compatibility with Release 2: define COMPAT2=1
+ *********************************************************************************
+ */
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+/* $Log: rcssyn.c,v $
+ * Revision 5.8 1991/08/19 03:13:55 eggert
+ * Tune.
+ *
+ * Revision 5.7 1991/04/21 11:58:29 eggert
+ * Disambiguate names on shortname hosts.
+ * Fix errno bug. Add MS-DOS support.
+ *
+ * Revision 5.6 1991/02/28 19:18:51 eggert
+ * Fix null termination bug in reporting keyword expansion.
+ *
+ * Revision 5.5 1991/02/25 07:12:44 eggert
+ * Check diff output more carefully; avoid overflow.
+ *
+ * Revision 5.4 1990/11/01 05:28:48 eggert
+ * When ignoring unknown phrases, copy them to the output RCS file.
+ * Permit arbitrary data in logs and comment leaders.
+ * Don't check for nontext on initial checkin.
+ *
+ * Revision 5.3 1990/09/20 07:58:32 eggert
+ * Remove the test for non-text bytes; it caused more pain than it cured.
+ *
+ * Revision 5.2 1990/09/04 08:02:30 eggert
+ * Parse RCS files with no revisions.
+ * Don't strip leading white space from diff commands. Count RCS lines better.
+ *
+ * Revision 5.1 1990/08/29 07:14:06 eggert
+ * Add -kkvl. Clean old log messages too.
+ *
+ * Revision 5.0 1990/08/22 08:13:44 eggert
+ * Try to parse future RCS formats without barfing.
+ * Add -k. Don't require final newline.
+ * Remove compile-time limits; use malloc instead.
+ * Don't output branch keyword if there's no default branch,
+ * because RCS version 3 doesn't understand it.
+ * Tune. Remove lint.
+ * Add support for ISO 8859. Ansify and Posixate.
+ * Check that a newly checked-in file is acceptable as input to 'diff'.
+ * Check diff's output.
+ *
+ * Revision 4.6 89/05/01 15:13:32 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.5 88/08/09 19:13:21 eggert
+ * Allow cc -R; remove lint.
+ *
+ * Revision 4.4 87/12/18 11:46:16 narten
+ * more lint cleanups (Guy Harris)
+ *
+ * Revision 4.3 87/10/18 10:39:36 narten
+ * Updating version numbers. Changes relative to 1.1 actually relative to
+ * 4.1
+ *
+ * Revision 1.3 87/09/24 14:00:49 narten
+ * Sources now pass through lint (if you ignore printf/sprintf/fprintf
+ * warnings)
+ *
+ * Revision 1.2 87/03/27 14:22:40 jenkins
+ * Port to suns
+ *
+ * Revision 4.1 83/03/28 11:38:49 wft
+ * Added parsing and printing of default branch.
+ *
+ * Revision 3.6 83/01/15 17:46:50 wft
+ * Changed readdelta() to initialize selector and log-pointer.
+ * Changed puttree to check for selector==DELETE; putdtext() uses DELNUMFORM.
+ *
+ * Revision 3.5 82/12/08 21:58:58 wft
+ * renamed Commentleader to Commleader.
+ *
+ * Revision 3.4 82/12/04 13:24:40 wft
+ * Added routine gettree(), which updates keeplock after reading the
+ * delta tree.
+ *
+ * Revision 3.3 82/11/28 21:30:11 wft
+ * Reading and printing of Suffix removed; version COMPAT2 skips the
+ * Suffix for files of release 2 format. Fixed problems with printing nil.
+ *
+ * Revision 3.2 82/10/18 21:18:25 wft
+ * renamed putdeltatext to putdtext.
+ *
+ * Revision 3.1 82/10/11 19:45:11 wft
+ * made sure getc() returns into an integer.
+ */
+
+
+
+/* version COMPAT2 reads files of the format of release 2 and 3, but
+ * generates files of release 3 format. Need not be defined if no
+ * old RCS files generated with release 2 exist.
+ */
+/* version SYNTEST inputs a RCS file and then prints out its internal
+ * data structures.
+*/
+
+#include "rcsbase.h"
+
+libId(synId, "$Id: rcssyn.c,v 5.8 1991/08/19 03:13:55 eggert Exp $")
+
+/* forward */
+static char const *getkeyval P((char const*,enum tokens,int));
+static int strn2expmode P((char const*,size_t));
+
+/* keyword table */
+
+char const
+ Kdesc[] = "desc",
+ Klog[] = "log",
+ Ktext[] = "text";
+
+static char const
+ Kaccess[] = "access",
+ Kauthor[] = "author",
+ Kbranch[] = "branch",
+ K_branches[]= "branches",
+ Kcomment[] = "comment",
+ Kdate[] = "date",
+ Kexpand[] = "expand",
+ Khead[] = "head",
+ Klocks[] = "locks",
+ Knext[] = "next",
+ Kstate[] = "state",
+ Kstrict[] = "strict",
+#if COMPAT2
+ Ksuffix[] = "suffix",
+#endif
+ Ksymbols[] = "symbols";
+
+static struct buf Commleader;
+static struct cbuf Ignored;
+struct cbuf Comment;
+struct access * AccessList;
+struct assoc * Symbols;
+struct lock * Locks;
+int Expand;
+int StrictLocks;
+struct hshentry * Head;
+char const * Dbranch;
+unsigned TotalDeltas;
+
+
+ static void
+getsemi(key)
+ char const *key;
+/* Get a semicolon to finish off a phrase started by KEY. */
+{
+ if (!getlex(SEMI))
+ fatserror("missing ';' after '%s'", key);
+}
+
+ static struct hshentry *
+getdnum()
+/* Get a delta number. */
+{
+ register struct hshentry *delta = getnum();
+ if (delta && countnumflds(delta->num)&1)
+ fatserror("%s isn't a delta number", delta->num);
+ return delta;
+}
+
+
+ void
+getadmin()
+/* Read an <admin> and initialize the appropriate global variables. */
+{
+ register char const *id;
+ struct access * newaccess;
+ struct assoc * newassoc;
+ struct lock * newlock;
+ struct hshentry * delta;
+ struct access **LastAccess;
+ struct assoc **LastSymbol;
+ struct lock **LastLock;
+ struct buf b;
+ struct cbuf cb;
+
+ TotalDeltas=0;
+
+ getkey(Khead);
+ Head = getdnum();
+ getsemi(Khead);
+
+ Dbranch = nil;
+ if (getkeyopt(Kbranch)) {
+ if ((delta = getnum()))
+ Dbranch = delta->num;
+ getsemi(Kbranch);
+ }
+
+
+#if COMPAT2
+ /* read suffix. Only in release 2 format */
+ if (getkeyopt(Ksuffix)) {
+ if (nexttok==STRING) {
+ readstring(); nextlex(); /* Throw away the suffix. */
+ } else if (nexttok==ID) {
+ nextlex();
+ }
+ getsemi(Ksuffix);
+ }
+#endif
+
+ getkey(Kaccess);
+ LastAccess = &AccessList;
+ while (id=getid()) {
+ newaccess = ftalloc(struct access);
+ newaccess->login = id;
+ *LastAccess = newaccess;
+ LastAccess = &newaccess->nextaccess;
+ }
+ *LastAccess = nil;
+ getsemi(Kaccess);
+
+ getkey(Ksymbols);
+ LastSymbol = &Symbols;
+ while (id = getid()) {
+ if (!getlex(COLON))
+ fatserror("missing ':' in symbolic name definition");
+ if (!(delta=getnum())) {
+ fatserror("missing number in symbolic name definition");
+ } else { /*add new pair to association list*/
+ newassoc = ftalloc(struct assoc);
+ newassoc->symbol=id;
+ newassoc->num = delta->num;
+ *LastSymbol = newassoc;
+ LastSymbol = &newassoc->nextassoc;
+ }
+ }
+ *LastSymbol = nil;
+ getsemi(Ksymbols);
+
+ getkey(Klocks);
+ LastLock = &Locks;
+ while (id = getid()) {
+ if (!getlex(COLON))
+ fatserror("missing ':' in lock");
+ if (!(delta=getdnum())) {
+ fatserror("missing number in lock");
+ } else { /*add new pair to lock list*/
+ newlock = ftalloc(struct lock);
+ newlock->login=id;
+ newlock->delta=delta;
+ *LastLock = newlock;
+ LastLock = &newlock->nextlock;
+ }
+ }
+ *LastLock = nil;
+ getsemi(Klocks);
+
+ if ((StrictLocks = getkeyopt(Kstrict)))
+ getsemi(Kstrict);
+
+ Comment.size = 0;
+ if (getkeyopt(Kcomment)) {
+ if (nexttok==STRING) {
+ Comment = savestring(&Commleader);
+ nextlex();
+ }
+ getsemi(Kcomment);
+ }
+
+ Expand = KEYVAL_EXPAND;
+ if (getkeyopt(Kexpand)) {
+ if (nexttok==STRING) {
+ bufautobegin(&b);
+ cb = savestring(&b);
+ if ((Expand = strn2expmode(cb.string,cb.size)) < 0)
+ fatserror("unknown expand mode %.*s",
+ (int)cb.size, cb.string
+ );
+ bufautoend(&b);
+ nextlex();
+ }
+ getsemi(Kexpand);
+ }
+ Ignored = getphrases(Kdesc);
+}
+
+char const *const expand_names[] = {
+ /* These must agree with *_EXPAND in rcsbase.h. */
+ "kv","kvl","k","v","o",
+ 0
+};
+
+ int
+str2expmode(s)
+ char const *s;
+/* Yield expand mode corresponding to S, or -1 if bad. */
+{
+ return strn2expmode(s, strlen(s));
+}
+
+ static int
+strn2expmode(s, n)
+ char const *s;
+ size_t n;
+{
+ char const *const *p;
+
+ for (p = expand_names; *p; ++p)
+ if (memcmp(*p,s,n) == 0 && !(*p)[n])
+ return p - expand_names;
+ return -1;
+}
+
+
+ void
+ignorephrase()
+/* Ignore a phrase introduced by a later version of RCS. */
+{
+ warnignore();
+ hshenter=false;
+ for (;;) {
+ switch (nexttok) {
+ case SEMI: hshenter=true; nextlex(); return;
+ case ID:
+ case NUM: ffree1(NextString); break;
+ case STRING: readstring(); break;
+ default: break;
+ }
+ nextlex();
+ }
+}
+
+
+ static int
+getdelta()
+/* Function: reads a delta block.
+ * returns false if the current block does not start with a number.
+ */
+{
+ register struct hshentry * Delta, * num;
+ struct branchhead **LastBranch, *NewBranch;
+
+ if (!(Delta = getdnum()))
+ return false;
+
+ hshenter = false; /*Don't enter dates into hashtable*/
+ Delta->date = getkeyval(Kdate, NUM, false);
+ hshenter=true; /*reset hshenter for revision numbers.*/
+
+ Delta->author = getkeyval(Kauthor, ID, false);
+
+ Delta->state = getkeyval(Kstate, ID, true);
+
+ getkey(K_branches);
+ LastBranch = &Delta->branches;
+ while ((num = getdnum())) {
+ NewBranch = ftalloc(struct branchhead);
+ NewBranch->hsh = num;
+ *LastBranch = NewBranch;
+ LastBranch = &NewBranch->nextbranch;
+ }
+ *LastBranch = nil;
+ getsemi(K_branches);
+
+ getkey(Knext);
+ Delta->next = num = getdnum();
+ getsemi(Knext);
+ Delta->lockedby = nil;
+ Delta->log.string = 0;
+ Delta->selector = true;
+ Delta->ig = getphrases(Kdesc);
+ TotalDeltas++;
+ return (true);
+}
+
+
+ void
+gettree()
+/* Function: Reads in the delta tree with getdelta(), then
+ * updates the lockedby fields.
+ */
+{
+ struct lock const *currlock;
+
+ while (getdelta());
+ currlock=Locks;
+ while (currlock) {
+ currlock->delta->lockedby = currlock->login;
+ currlock = currlock->nextlock;
+ }
+}
+
+
+ void
+getdesc(prdesc)
+int prdesc;
+/* Function: read in descriptive text
+ * nexttok is not advanced afterwards.
+ * If prdesc is set, the text is printed to stdout.
+ */
+{
+
+ getkeystring(Kdesc);
+ if (prdesc)
+ printstring(); /*echo string*/
+ else readstring(); /*skip string*/
+}
+
+
+
+
+
+
+ static char const *
+getkeyval(keyword, token, optional)
+ char const *keyword;
+ enum tokens token;
+ int optional;
+/* reads a pair of the form
+ * <keyword> <token> ;
+ * where token is one of <id> or <num>. optional indicates whether
+ * <token> is optional. A pointer to
+ * the actual character string of <id> or <num> is returned.
+ */
+{
+ register char const *val = nil;
+
+ getkey(keyword);
+ if (nexttok==token) {
+ val = NextString;
+ nextlex();
+ } else {
+ if (!optional)
+ fatserror("missing %s", keyword);
+ }
+ getsemi(keyword);
+ return(val);
+}
+
+
+
+
+ void
+putadmin(fout)
+register FILE * fout;
+/* Function: Print the <admin> node read with getadmin() to file fout.
+ * Assumption: Variables AccessList, Symbols, Locks, StrictLocks,
+ * and Head have been set.
+ */
+{
+ struct assoc const *curassoc;
+ struct lock const *curlock;
+ struct access const *curaccess;
+
+ aprintf(fout, "%s\t%s;\n", Khead, Head?Head->num:"");
+ if (Dbranch && VERSION(4)<=RCSversion)
+ aprintf(fout, "%s\t%s;\n", Kbranch, Dbranch);
+
+ aputs(Kaccess, fout);
+ curaccess = AccessList;
+ while (curaccess) {
+ aprintf(fout, "\n\t%s", curaccess->login);
+ curaccess = curaccess->nextaccess;
+ }
+ aprintf(fout, ";\n%s", Ksymbols);
+ curassoc = Symbols;
+ while (curassoc) {
+ aprintf(fout, "\n\t%s:%s", curassoc->symbol, curassoc->num);
+ curassoc = curassoc->nextassoc;
+ }
+ aprintf(fout, ";\n%s", Klocks);
+ curlock = Locks;
+ while (curlock) {
+ aprintf(fout, "\n\t%s:%s", curlock->login, curlock->delta->num);
+ curlock = curlock->nextlock;
+ }
+ if (StrictLocks) aprintf(fout, "; %s", Kstrict);
+ aprintf(fout, ";\n");
+ if (Comment.size) {
+ aprintf(fout, "%s\t", Kcomment);
+ putstring(fout, true, Comment, false);
+ aprintf(fout, ";\n");
+ }
+ if (Expand != KEYVAL_EXPAND)
+ aprintf(fout, "%s\t%c%s%c;\n",
+ Kexpand, SDELIM, expand_names[Expand], SDELIM
+ );
+ awrite(Ignored.string, Ignored.size, fout);
+ aputc('\n', fout);
+}
+
+
+
+
+ static void
+putdelta(node,fout)
+register struct hshentry const *node;
+register FILE * fout;
+/* Function: prints a <delta> node to fout;
+ */
+{
+ struct branchhead const *nextbranch;
+
+ if (node == nil) return;
+
+ aprintf(fout, "\n%s\n%s\t%s;\t%s %s;\t%s %s;\nbranches",
+ node->num,
+ Kdate, node->date,
+ Kauthor, node->author,
+ Kstate, node->state?node->state:""
+ );
+ nextbranch = node->branches;
+ while (nextbranch) {
+ aprintf(fout, "\n\t%s", nextbranch->hsh->num);
+ nextbranch = nextbranch->nextbranch;
+ }
+
+ aprintf(fout, ";\n%s\t%s;\n", Knext, node->next?node->next->num:"");
+ awrite(node->ig.string, node->ig.size, fout);
+}
+
+
+
+
+ void
+puttree(root,fout)
+struct hshentry const *root;
+register FILE * fout;
+/* Function: prints the delta tree in preorder to fout, starting with root.
+ */
+{
+ struct branchhead const *nextbranch;
+
+ if (root==nil) return;
+
+ if (root->selector)
+ putdelta(root,fout);
+
+ puttree(root->next,fout);
+
+ nextbranch = root->branches;
+ while (nextbranch) {
+ puttree(nextbranch->hsh,fout);
+ nextbranch = nextbranch->nextbranch;
+ }
+}
+
+
+ static exiting void
+unexpected_EOF()
+{
+ faterror("unexpected EOF in diff output");
+}
+
+int putdtext(num,log,srcfilename,fout,diffmt)
+ char const *num, *srcfilename;
+ struct cbuf log;
+ FILE *fout;
+ int diffmt;
+/* Function: write a deltatext-node to fout.
+ * num points to the deltanumber, log to the logmessage, and
+ * sourcefile contains the text. Doubles up all SDELIMs in both the
+ * log and the text; Makes sure the log message ends in \n.
+ * returns false on error.
+ * If diffmt is true, also checks that text is valid diff -n output.
+ */
+{
+ RILE *fin;
+ int result;
+ if (!(fin = Iopen(srcfilename, "r", (struct stat*)0))) {
+ eerror(srcfilename);
+ return false;
+ }
+ result = putdftext(num,log,fin,fout,diffmt);
+ Ifclose(fin);
+ return result;
+}
+
+ void
+putstring(out, delim, s, log)
+ register FILE *out;
+ struct cbuf s;
+ int delim, log;
+/*
+ * Output to OUT one SDELIM if DELIM, then the string S with SDELIMs doubled.
+ * If LOG is set then S is a log string; append a newline if S is nonempty.
+ */
+{
+ register char const *sp;
+ register size_t ss;
+
+ if (delim)
+ aputc(SDELIM, out);
+ sp = s.string;
+ for (ss = s.size; ss; --ss) {
+ if (*sp == SDELIM)
+ aputc(SDELIM, out);
+ aputc(*sp++, out);
+ }
+ if (s.size && log)
+ aputc('\n', out);
+ aputc(SDELIM, out);
+}
+
+ int
+putdftext(num,log,finfile,foutfile,diffmt)
+ char const *num;
+ struct cbuf log;
+ RILE *finfile;
+ FILE *foutfile;
+ int diffmt;
+/* like putdtext(), except the source file is already open */
+{
+ declarecache;
+ register FILE *fout;
+ register int c;
+ register RILE *fin;
+ int ed;
+ struct diffcmd dc;
+
+ fout = foutfile;
+ aprintf(fout,DELNUMFORM,num,Klog);
+ /* put log */
+ putstring(fout, true, log, true);
+ /* put text */
+ aprintf(fout, "\n%s\n%c", Ktext, SDELIM);
+ fin = finfile;
+ setupcache(fin);
+ if (!diffmt) {
+ /* Copy the file */
+ cache(fin);
+ for (;;) {
+ cachegeteof(c, break;);
+ if (c==SDELIM) aputc(SDELIM,fout); /*double up SDELIM*/
+ aputc(c,fout);
+ }
+ } else {
+ initdiffcmd(&dc);
+ while (0 <= (ed = getdiffcmd(fin,false,fout,&dc)))
+ if (ed) {
+ cache(fin);
+ while (dc.nlines--)
+ do {
+ cachegeteof(c, { if (!dc.nlines) goto OK_EOF; unexpected_EOF(); });
+ if (c == SDELIM)
+ aputc(SDELIM,fout);
+ aputc(c,fout);
+ } while (c != '\n');
+ uncache(fin);
+ }
+ }
+ OK_EOF:
+ aprintf(fout, "%c\n", SDELIM);
+ return true;
+}
+
+ void
+initdiffcmd(dc)
+ register struct diffcmd *dc;
+/* Initialize *dc suitably for getdiffcmd(). */
+{
+ dc->adprev = 0;
+ dc->dafter = 0;
+}
+
+ static exiting void
+badDiffOutput(buf)
+ char const *buf;
+{
+ faterror("bad diff output line: %s", buf);
+}
+
+ static exiting void
+diffLineNumberTooLarge(buf)
+ char const *buf;
+{
+ faterror("diff line number too large: %s", buf);
+}
+
+ int
+getdiffcmd(finfile, delimiter, foutfile, dc)
+ RILE *finfile;
+ FILE *foutfile;
+ int delimiter;
+ struct diffcmd *dc;
+/* Get a editing command output by 'diff -n' from fin.
+ * The input is delimited by SDELIM if delimiter is set, EOF otherwise.
+ * Copy a clean version of the command to fout (if nonnull).
+ * Yield 0 for 'd', 1 for 'a', and -1 for EOF.
+ * Store the command's line number and length into dc->line1 and dc->nlines.
+ * Keep dc->adprev and dc->dafter up to date.
+ */
+{
+ register int c;
+ declarecache;
+ register FILE *fout;
+ register char *p;
+ register RILE *fin;
+ unsigned long line1, nlines, t;
+ char buf[BUFSIZ];
+
+ fin = finfile;
+ fout = foutfile;
+ setupcache(fin); cache(fin);
+ cachegeteof(c, { if (delimiter) unexpected_EOF(); return -1; } );
+ if (delimiter) {
+ if (c==SDELIM) {
+ cacheget(c);
+ if (c==SDELIM) {
+ buf[0] = c;
+ buf[1] = 0;
+ badDiffOutput(buf);
+ }
+ uncache(fin);
+ nextc = c;
+ if (fout)
+ aprintf(fout, "%c%c", SDELIM, c);
+ return -1;
+ }
+ }
+ p = buf;
+ do {
+ if (buf+BUFSIZ-2 <= p) {
+ faterror("diff output command line too long");
+ }
+ *p++ = c;
+ cachegeteof(c, unexpected_EOF();) ;
+ } while (c != '\n');
+ uncache(fin);
+ if (delimiter)
+ ++rcsline;
+ *p = '\0';
+ for (p = buf+1; (c = *p++) == ' '; )
+ ;
+ line1 = 0;
+ while (isdigit(c)) {
+ t = line1 * 10;
+ if (
+ ULONG_MAX/10 < line1 ||
+ (line1 = t + (c - '0')) < t
+ )
+ diffLineNumberTooLarge(buf);
+ c = *p++;
+ }
+ while (c == ' ')
+ c = *p++;
+ nlines = 0;
+ while (isdigit(c)) {
+ t = nlines * 10;
+ if (
+ ULONG_MAX/10 < nlines ||
+ (nlines = t + (c - '0')) < t
+ )
+ diffLineNumberTooLarge(buf);
+ c = *p++;
+ }
+ if (c || !nlines) {
+ badDiffOutput(buf);
+ }
+ if (line1+nlines < line1)
+ diffLineNumberTooLarge(buf);
+ switch (buf[0]) {
+ case 'a':
+ if (line1 < dc->adprev) {
+ faterror("backward insertion in diff output: %s", buf);
+ }
+ dc->adprev = line1 + 1;
+ break;
+ case 'd':
+ if (line1 < dc->adprev || line1 < dc->dafter) {
+ faterror("backward deletion in diff output: %s", buf);
+ }
+ dc->adprev = line1;
+ dc->dafter = line1 + nlines;
+ break;
+ default:
+ badDiffOutput(buf);
+ }
+ if (fout) {
+ aprintf(fout, "%s\n", buf);
+ }
+ dc->line1 = line1;
+ dc->nlines = nlines;
+ return buf[0] == 'a';
+}
+
+
+
+#ifdef SYNTEST
+
+char const cmdid[] = "syntest";
+
+ int
+main(argc,argv)
+int argc; char * argv[];
+{
+
+ if (argc<2) {
+ aputs("No input file\n",stderr);
+ exitmain(EXIT_FAILURE);
+ }
+ if (!(finptr = Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
+ faterror("can't open input file %s", argv[1]);
+ }
+ Lexinit();
+ getadmin();
+ putadmin(stdout);
+
+ gettree();
+ puttree(Head,stdout);
+
+ getdesc(true);
+
+ nextlex();
+
+ if (!eoflex()) {
+ fatserror("expecting EOF");
+ }
+ exitmain(EXIT_SUCCESS);
+}
+
+
+exiting void exiterr() { _exit(EXIT_FAILURE); }
+
+
+#endif
+
diff --git a/gnu/usr.bin/rcs/lib/rcsutil.c b/gnu/usr.bin/rcs/lib/rcsutil.c
new file mode 100644
index 0000000..c523ccf1
--- /dev/null
+++ b/gnu/usr.bin/rcs/lib/rcsutil.c
@@ -0,0 +1,994 @@
+/*
+ * RCS utilities
+ */
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+
+
+/* $Log: rcsutil.c,v $
+ * Revision 5.10 1991/10/07 17:32:46 eggert
+ * Support piece tables even if !has_mmap.
+ *
+ * Revision 5.9 1991/08/19 03:13:55 eggert
+ * Add spawn() support. Explicate assumptions about getting invoker's name.
+ * Standardize user-visible dates. Tune.
+ *
+ * Revision 5.8 1991/04/21 11:58:30 eggert
+ * Plug setuid security hole.
+ *
+ * Revision 5.6 1991/02/26 17:48:39 eggert
+ * Fix setuid bug. Use fread, fwrite more portably.
+ * Support waitpid. Don't assume -1 is acceptable to W* macros.
+ * strsave -> str_save (DG/UX name clash)
+ *
+ * Revision 5.5 1990/12/04 05:18:49 eggert
+ * Don't output a blank line after a signal diagnostic.
+ * Use -I for prompts and -q for diagnostics.
+ *
+ * Revision 5.4 1990/11/01 05:03:53 eggert
+ * Remove unneeded setid check. Add awrite(), fremember().
+ *
+ * Revision 5.3 1990/10/06 00:16:45 eggert
+ * Don't fread F if feof(F).
+ *
+ * Revision 5.2 1990/09/04 08:02:31 eggert
+ * Store fread()'s result in an fread_type object.
+ *
+ * Revision 5.1 1990/08/29 07:14:07 eggert
+ * Declare getpwuid() more carefully.
+ *
+ * Revision 5.0 1990/08/22 08:13:46 eggert
+ * Add setuid support. Permit multiple locks per user.
+ * Remove compile-time limits; use malloc instead.
+ * Switch to GMT. Permit dates past 1999/12/31.
+ * Add -V. Remove snooping. Ansify and Posixate.
+ * Tune. Some USG hosts define NSIG but not sys_siglist.
+ * Don't run /bin/sh if it's hopeless.
+ * Don't leave garbage behind if the output is an empty pipe.
+ * Clean up after SIGXCPU or SIGXFSZ. Print name of signal that caused cleanup.
+ *
+ * Revision 4.6 89/05/01 15:13:40 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.5 88/11/08 16:01:02 narten
+ * corrected use of varargs routines
+ *
+ * Revision 4.4 88/08/09 19:13:24 eggert
+ * Check for memory exhaustion.
+ * Permit signal handlers to yield either 'void' or 'int'; fix oldSIGINT botch.
+ * Use execv(), not system(); yield exit status like diff(1)'s.
+ *
+ * Revision 4.3 87/10/18 10:40:22 narten
+ * Updating version numbers. Changes relative to 1.1 actually
+ * relative to 4.1
+ *
+ * Revision 1.3 87/09/24 14:01:01 narten
+ * Sources now pass through lint (if you ignore printf/sprintf/fprintf
+ * warnings)
+ *
+ * Revision 1.2 87/03/27 14:22:43 jenkins
+ * Port to suns
+ *
+ * Revision 4.1 83/05/10 15:53:13 wft
+ * Added getcaller() and findlock().
+ * Changed catchints() to check SIGINT for SIG_IGN before setting up the signal
+ * (needed for background jobs in older shells). Added restoreints().
+ * Removed printing of full RCS path from logcommand().
+ *
+ * Revision 3.8 83/02/15 15:41:49 wft
+ * Added routine fastcopy() to copy remainder of a file in blocks.
+ *
+ * Revision 3.7 82/12/24 15:25:19 wft
+ * added catchints(), ignoreints() for catching and ingnoring interrupts;
+ * fixed catchsig().
+ *
+ * Revision 3.6 82/12/08 21:52:05 wft
+ * Using DATEFORM to format dates.
+ *
+ * Revision 3.5 82/12/04 18:20:49 wft
+ * Replaced SNOOPDIR with SNOOPFILE; changed addlock() to update
+ * lockedby-field.
+ *
+ * Revision 3.4 82/12/03 17:17:43 wft
+ * Added check to addlock() ensuring only one lock per person.
+ * Addlock also returns a pointer to the lock created. Deleted fancydate().
+ *
+ * Revision 3.3 82/11/27 12:24:37 wft
+ * moved rmsema(), trysema(), trydiraccess(), getfullRCSname() to rcsfnms.c.
+ * Introduced macro SNOOP so that snoop can be placed in directory other than
+ * TARGETDIR. Changed %02d to %.2d for compatibility reasons.
+ *
+ * Revision 3.2 82/10/18 21:15:11 wft
+ * added function getfullRCSname().
+ *
+ * Revision 3.1 82/10/13 16:17:37 wft
+ * Cleanup message is now suppressed in quiet mode.
+ */
+
+
+
+
+#include "rcsbase.h"
+
+libId(utilId, "$Id: rcsutil.c,v 5.10 1991/10/07 17:32:46 eggert Exp $")
+
+#if !has_memcmp
+ int
+memcmp(s1, s2, n)
+ void const *s1, *s2;
+ size_t n;
+{
+ register unsigned char const
+ *p1 = (unsigned char const*)s1,
+ *p2 = (unsigned char const*)s2;
+ register size_t i = n;
+ register int r = 0;
+ while (i-- && !(r = (*p1++ - *p2++)))
+ ;
+ return r;
+}
+#endif
+
+#if !has_memcpy
+ void *
+memcpy(s1, s2, n)
+ void *s1;
+ void const *s2;
+ size_t n;
+{
+ register char *p1 = (char*)s1;
+ register char const *p2 = (char const*)s2;
+ while (n--)
+ *p1++ = *p2++;
+ return s1;
+}
+#endif
+
+#if lint
+ malloc_type lintalloc;
+#endif
+
+/*
+ * list of blocks allocated with ftestalloc()
+ * These blocks can be freed by ffree when we're done with the current file.
+ * We could put the free block inside struct alloclist, rather than a pointer
+ * to the free block, but that would be less portable.
+ */
+struct alloclist {
+ malloc_type alloc;
+ struct alloclist *nextalloc;
+};
+static struct alloclist *alloced;
+
+
+ static malloc_type
+okalloc(p)
+ malloc_type p;
+{
+ if (!p)
+ faterror("out of memory");
+ return p;
+}
+
+ malloc_type
+testalloc(size)
+ size_t size;
+/* Allocate a block, testing that the allocation succeeded. */
+{
+ return okalloc(malloc(size));
+}
+
+ malloc_type
+testrealloc(ptr, size)
+ malloc_type ptr;
+ size_t size;
+/* Reallocate a block, testing that the allocation succeeded. */
+{
+ return okalloc(realloc(ptr, size));
+}
+
+ malloc_type
+fremember(ptr)
+ malloc_type ptr;
+/* Remember PTR in 'alloced' so that it can be freed later. Yield PTR. */
+{
+ register struct alloclist *q = talloc(struct alloclist);
+ q->nextalloc = alloced;
+ alloced = q;
+ return q->alloc = ptr;
+}
+
+ malloc_type
+ftestalloc(size)
+ size_t size;
+/* Allocate a block, putting it in 'alloced' so it can be freed later. */
+{
+ return fremember(testalloc(size));
+}
+
+ void
+ffree()
+/* Free all blocks allocated with ftestalloc(). */
+{
+ register struct alloclist *p, *q;
+ for (p = alloced; p; p = q) {
+ q = p->nextalloc;
+ tfree(p->alloc);
+ tfree(p);
+ }
+ alloced = nil;
+}
+
+ void
+ffree1(f)
+ register char const *f;
+/* Free the block f, which was allocated by ftestalloc. */
+{
+ register struct alloclist *p, **a = &alloced;
+
+ while ((p = *a)->alloc != f)
+ a = &p->nextalloc;
+ *a = p->nextalloc;
+ tfree(p->alloc);
+ tfree(p);
+}
+
+ char *
+str_save(s)
+ char const *s;
+/* Save s in permanently allocated storage. */
+{
+ return strcpy(tnalloc(char, strlen(s)+1), s);
+}
+
+ char *
+fstr_save(s)
+ char const *s;
+/* Save s in storage that will be deallocated when we're done with this file. */
+{
+ return strcpy(ftnalloc(char, strlen(s)+1), s);
+}
+
+ char *
+cgetenv(name)
+ char const *name;
+/* Like getenv(), but yield a copy; getenv() can overwrite old results. */
+{
+ register char *p;
+
+ return (p=getenv(name)) ? str_save(p) : p;
+}
+
+ char const *
+getusername(suspicious)
+ int suspicious;
+/* Get the caller's login name. Trust only getwpuid if SUSPICIOUS. */
+{
+ static char *name;
+
+ if (!name) {
+ if (
+ /* Prefer getenv() unless suspicious; it's much faster. */
+# if getlogin_is_secure
+ (suspicious
+ ||
+ !(name = cgetenv("LOGNAME"))
+ && !(name = cgetenv("USER")))
+ && !(name = getlogin())
+# else
+ suspicious
+ ||
+ !(name = cgetenv("LOGNAME"))
+ && !(name = cgetenv("USER"))
+ && !(name = getlogin())
+# endif
+ ) {
+#if has_getuid && has_getpwuid
+ struct passwd const *pw = getpwuid(ruid());
+ if (!pw)
+ faterror("no password entry for userid %lu",
+ (unsigned long)ruid()
+ );
+ name = pw->pw_name;
+#else
+#if has_setuid
+ faterror("setuid not supported");
+#else
+ faterror("Who are you? Please set LOGNAME.");
+#endif
+#endif
+ }
+ checksid(name);
+ }
+ return name;
+}
+
+
+
+
+#if has_signal
+
+/*
+ * Signal handling
+ *
+ * Standard C places too many restrictions on signal handlers.
+ * We obey as many of them as we can.
+ * Posix places fewer restrictions, and we are Posix-compatible here.
+ */
+
+static sig_atomic_t volatile heldsignal, holdlevel;
+
+ static signal_type
+catchsig(s)
+ int s;
+{
+ char const *sname;
+ char buf[BUFSIZ];
+
+#if sig_zaps_handler
+ /* If a signal arrives before we reset the signal handler, we lose. */
+ VOID signal(s, SIG_IGN);
+#endif
+ if (holdlevel) {
+ heldsignal = s;
+ return;
+ }
+ ignoreints();
+ setrid();
+ if (!quietflag) {
+ sname = nil;
+#if has_sys_siglist && defined(NSIG)
+ if ((unsigned)s < NSIG) {
+# ifndef sys_siglist
+ extern char const *sys_siglist[];
+# endif
+ sname = sys_siglist[s];
+ }
+#else
+ switch (s) {
+#ifdef SIGHUP
+ case SIGHUP: sname = "Hangup"; break;
+#endif
+#ifdef SIGINT
+ case SIGINT: sname = "Interrupt"; break;
+#endif
+#ifdef SIGPIPE
+ case SIGPIPE: sname = "Broken pipe"; break;
+#endif
+#ifdef SIGQUIT
+ case SIGQUIT: sname = "Quit"; break;
+#endif
+#ifdef SIGTERM
+ case SIGTERM: sname = "Terminated"; break;
+#endif
+#ifdef SIGXCPU
+ case SIGXCPU: sname = "Cputime limit exceeded"; break;
+#endif
+#ifdef SIGXFSZ
+ case SIGXFSZ: sname = "Filesize limit exceeded"; break;
+#endif
+ }
+#endif
+ if (sname)
+ VOID sprintf(buf, "\nRCS: %s. Cleaning up.\n", sname);
+ else
+ VOID sprintf(buf, "\nRCS: Signal %d. Cleaning up.\n", s);
+ VOID write(STDERR_FILENO, buf, strlen(buf));
+ }
+ exiterr();
+}
+
+ void
+ignoreints()
+{
+ ++holdlevel;
+}
+
+ void
+restoreints()
+{
+ if (!--holdlevel && heldsignal)
+ VOID catchsig(heldsignal);
+}
+
+
+static int const sig[] = {
+#ifdef SIGHUP
+ SIGHUP,
+#endif
+#ifdef SIGINT
+ SIGINT,
+#endif
+#ifdef SIGPIPE
+ SIGPIPE,
+#endif
+#ifdef SIGQUIT
+ SIGQUIT,
+#endif
+#ifdef SIGTERM
+ SIGTERM,
+#endif
+#ifdef SIGXCPU
+ SIGXCPU,
+#endif
+#ifdef SIGXFSZ
+ SIGXFSZ,
+#endif
+};
+#define SIGS (sizeof(sig)/sizeof(*sig))
+
+
+#if has_sigaction
+
+ static void
+ check_sig(r)
+ int r;
+ {
+ if (r != 0)
+ efaterror("signal");
+ }
+
+ static void
+ setup_catchsig()
+ {
+ register int i;
+ sigset_t blocked;
+ struct sigaction act;
+
+ check_sig(sigemptyset(&blocked));
+ for (i=SIGS; 0<=--i; )
+ check_sig(sigaddset(&blocked, sig[i]));
+ for (i=SIGS; 0<=--i; ) {
+ check_sig(sigaction(sig[i], (struct sigaction*)nil, &act));
+ if (act.sa_handler != SIG_IGN) {
+ act.sa_handler = catchsig;
+ act.sa_mask = blocked;
+ check_sig(sigaction(sig[i], &act, (struct sigaction*)nil));
+ }
+ }
+ }
+
+#else
+#if has_sigblock
+
+ static void
+ setup_catchsig()
+ {
+ register int i;
+ int mask;
+
+ mask = 0;
+ for (i=SIGS; 0<=--i; )
+ mask |= sigmask(sig[i]);
+ mask = sigblock(mask);
+ for (i=SIGS; 0<=--i; )
+ if (
+ signal(sig[i], catchsig) == SIG_IGN &&
+ signal(sig[i], SIG_IGN) != catchsig
+ )
+ faterror("signal catcher failure");
+ VOID sigsetmask(mask);
+ }
+
+#else
+
+ static void
+ setup_catchsig()
+ {
+ register i;
+
+ for (i=SIGS; 0<=--i; )
+ if (
+ signal(sig[i], SIG_IGN) != SIG_IGN &&
+ signal(sig[i], catchsig) != SIG_IGN
+ )
+ faterror("signal catcher failure");
+ }
+
+#endif
+#endif
+
+ void
+catchints()
+{
+ static int catching_ints;
+ if (!catching_ints) {
+ catching_ints = true;
+ setup_catchsig();
+ }
+}
+
+#endif /* has_signal */
+
+
+ void
+fastcopy(inf,outf)
+ register RILE *inf;
+ FILE *outf;
+/* Function: copies the remainder of file inf to outf.
+ */
+{
+#if large_memory
+# if has_mmap
+ awrite((char const*)inf->ptr, (size_t)(inf->lim - inf->ptr), outf);
+ inf->ptr = inf->lim;
+# else
+ for (;;) {
+ awrite((char const*)inf->ptr, (size_t)(inf->readlim - inf->ptr), outf);
+ inf->ptr = inf->readlim;
+ if (inf->ptr == inf->lim)
+ break;
+ VOID Igetmore(inf);
+ }
+# endif
+#else
+ char buf[BUFSIZ*8];
+ register fread_type rcount;
+
+ /*now read the rest of the file in blocks*/
+ while (!feof(inf)) {
+ if (!(rcount = Fread(buf,sizeof(*buf),sizeof(buf),inf))) {
+ testIerror(inf);
+ return;
+ }
+ awrite(buf, (size_t)rcount, outf);
+ }
+#endif
+}
+
+#ifndef SSIZE_MAX
+ /* This does not work in #ifs, but it's good enough for us. */
+ /* Underestimating SSIZE_MAX may slow us down, but it won't break us. */
+# define SSIZE_MAX ((unsigned)-1 >> 1)
+#endif
+
+ void
+awrite(buf, chars, f)
+ char const *buf;
+ size_t chars;
+ FILE *f;
+{
+ /* Posix 1003.1-1990 ssize_t hack */
+ while (SSIZE_MAX < chars) {
+ if (Fwrite(buf, sizeof(*buf), SSIZE_MAX, f) != SSIZE_MAX)
+ Oerror();
+ buf += SSIZE_MAX;
+ chars -= SSIZE_MAX;
+ }
+
+ if (Fwrite(buf, sizeof(*buf), chars, f) != chars)
+ Oerror();
+}
+
+
+
+
+
+ static int
+movefd(old, new)
+ int old, new;
+{
+ if (old < 0 || old == new)
+ return old;
+# ifdef F_DUPFD
+ new = fcntl(old, F_DUPFD, new);
+# else
+ new = dup2(old, new);
+# endif
+ return close(old)==0 ? new : -1;
+}
+
+ static int
+fdreopen(fd, file, flags)
+ int fd;
+ char const *file;
+ int flags;
+{
+ int newfd;
+ VOID close(fd);
+ newfd =
+#if !open_can_creat
+ flags&O_CREAT ? creat(file, S_IRUSR|S_IWUSR) :
+#endif
+ open(file, flags, S_IRUSR|S_IWUSR);
+ return movefd(newfd, fd);
+}
+
+#if !has_spawn
+ static void
+tryopen(fd,file,flags)
+ int fd, flags;
+ char const *file;
+{
+ if (file && fdreopen(fd,file,flags) != fd)
+ efaterror(file);
+}
+#else
+ static int
+tryopen(fd,file,flags)
+ int fd, flags;
+ char const *file;
+{
+ int newfd = -1;
+ if (file && ((newfd=dup(fd)) < 0 || fdreopen(fd,file,flags) != fd))
+ efaterror(file);
+ return newfd;
+}
+ static void
+redirect(old, new)
+ int old, new;
+{
+ if (0 <= old && (close(new) != 0 || movefd(old,new) < 0))
+ efaterror("spawn I/O redirection");
+}
+#endif
+
+
+
+#if !has_fork && !has_spawn
+ static void
+bufargcat(b, c, s)
+ register struct buf *b;
+ int c;
+ register char const *s;
+/* Append to B a copy of C, plus a quoted copy of S. */
+{
+ register char *p;
+ register char const *t;
+ size_t bl, sl;
+
+ for (t=s, sl=0; *t; )
+ sl += 3*(*t++=='\'') + 1;
+ bl = strlen(b->string);
+ bufrealloc(b, bl + sl + 4);
+ p = b->string + bl;
+ *p++ = c;
+ *p++ = '\'';
+ while (*s) {
+ if (*s == '\'') {
+ *p++ = '\'';
+ *p++ = '\\';
+ *p++ = '\'';
+ }
+ *p++ = *s++;
+ }
+ *p++ = '\'';
+ *p = 0;
+}
+#endif
+
+/*
+* Run a command specified by the strings in 'inoutargs'.
+* inoutargs[0], if nonnil, is the name of the input file.
+* inoutargs[1], if nonnil, is the name of the output file.
+* inoutargs[2..] form the command to be run.
+*/
+ int
+runv(inoutargs)
+ char const **inoutargs;
+{
+ register char const **p;
+ int wstatus;
+
+ oflush();
+ eflush();
+ {
+#if has_spawn
+ int in, out;
+ p = inoutargs;
+ in = tryopen(STDIN_FILENO, *p++, O_BINARY|O_RDONLY);
+ out = tryopen(STDOUT_FILENO, *p++, O_BINARY|O_CREAT|O_TRUNC|O_WRONLY);
+ wstatus = spawn_RCS(0, *p, (char*const*)p);
+ if (wstatus == -1 && errno == ENOEXEC) {
+ *--p = RCS_SHELL;
+ wstatus = spawnv(0, *p, (char*const*)p);
+ }
+ redirect(in, STDIN_FILENO);
+ redirect(out, STDOUT_FILENO);
+#else
+#if has_fork
+ pid_t pid;
+# if !has_waitpid
+ pid_t w;
+# endif
+ if (!(pid = vfork())) {
+ p = inoutargs;
+ tryopen(STDIN_FILENO, *p++, O_BINARY|O_RDONLY);
+ tryopen(STDOUT_FILENO, *p++, O_BINARY|O_CREAT|O_TRUNC|O_WRONLY);
+ VOID exec_RCS(*p, (char*const*)p);
+ if (errno == ENOEXEC) {
+ *--p = RCS_SHELL;
+ VOID execv(*p, (char*const*)p);
+ }
+ VOID write(STDERR_FILENO, *p, strlen(*p));
+ VOID write(STDERR_FILENO, ": not found\n", 12);
+ _exit(EXIT_TROUBLE);
+ }
+ if (pid < 0)
+ efaterror("fork");
+# if has_waitpid
+ if (waitpid(pid, &wstatus, 0) < 0)
+ efaterror("waitpid");
+# else
+ do {
+ if ((w = wait(&wstatus)) < 0)
+ efaterror("wait");
+ } while (w != pid);
+# endif
+#else
+ static struct buf b;
+
+ /* Use system(). On many hosts system() discards signals. Yuck! */
+ p = inoutargs+2;
+ bufscpy(&b, *p);
+ while (*++p)
+ bufargcat(&b, ' ', *p);
+ if (inoutargs[0])
+ bufargcat(&b, '<', inoutargs[0]);
+ if (inoutargs[1])
+ bufargcat(&b, '>', inoutargs[1]);
+ wstatus = system(b.string);
+#endif
+#endif
+ }
+ if (!WIFEXITED(wstatus))
+ faterror("%s failed", inoutargs[2]);
+ return WEXITSTATUS(wstatus);
+}
+
+#define CARGSMAX 20
+/*
+* Run a command.
+* The first two arguments are the input and output files (if nonnil);
+* the rest specify the command and its arguments.
+*/
+ int
+#if has_prototypes
+run(char const *infile, char const *outfile, ...)
+#else
+ /*VARARGS2*/
+run(infile, outfile, va_alist)
+ char const *infile;
+ char const *outfile;
+ va_dcl
+#endif
+{
+ va_list ap;
+ char const *rgargs[CARGSMAX];
+ register i = 0;
+ rgargs[0] = infile;
+ rgargs[1] = outfile;
+ vararg_start(ap, outfile);
+ for (i = 2; (rgargs[i++] = va_arg(ap, char const*)); )
+ if (CARGSMAX <= i)
+ faterror("too many command arguments");
+ va_end(ap);
+ return runv(rgargs);
+}
+
+
+ char const *
+date2str(date, datebuf)
+ char const date[datesize];
+ char datebuf[datesize];
+/*
+* Format a user-readable form of the RCS format DATE into the buffer DATEBUF.
+* Yield DATEBUF.
+*/
+{
+ register char const *p = date;
+
+ while (*p++ != '.')
+ ;
+ VOID sprintf(datebuf,
+ "19%.*s/%.2s/%.2s %.2s:%.2s:%s" +
+ (date[2]=='.' && VERSION(5)<=RCSversion ? 0 : 2),
+ (int)(p-date-1), date,
+ p, p+3, p+6, p+9, p+12
+ );
+ return datebuf;
+}
+
+
+int RCSversion;
+
+ void
+setRCSversion(str)
+ char const *str;
+{
+ static int oldversion;
+
+ register char const *s = str + 2;
+ int v = VERSION_DEFAULT;
+
+ if (oldversion)
+ redefined('V');
+ oldversion = true;
+
+ if (*s) {
+ v = 0;
+ while (isdigit(*s))
+ v = 10*v + *s++ - '0';
+ if (*s)
+ faterror("%s isn't a number", str);
+ if (v < VERSION_min || VERSION_max < v)
+ faterror("%s out of range %d..%d", str, VERSION_min, VERSION_max);
+ }
+
+ RCSversion = VERSION(v);
+}
+
+ int
+getRCSINIT(argc, argv, newargv)
+ int argc;
+ char **argv, ***newargv;
+{
+ register char *p, *q, **pp;
+ unsigned n;
+
+ if (!(q = cgetenv("RCSINIT")))
+ *newargv = argv;
+ else {
+ n = argc + 2;
+ /*
+ * Count spaces in RCSINIT to allocate a new arg vector.
+ * This is an upper bound, but it's OK even if too large.
+ */
+ for (p = q; ; ) {
+ switch (*p++) {
+ default:
+ continue;
+
+ case ' ':
+ case '\b': case '\f': case '\n':
+ case '\r': case '\t': case '\v':
+ n++;
+ continue;
+
+ case '\0':
+ break;
+ }
+ break;
+ }
+ *newargv = pp = tnalloc(char*, n);
+ *pp++ = *argv++; /* copy program name */
+ for (p = q; ; ) {
+ for (;;) {
+ switch (*q) {
+ case '\0':
+ goto copyrest;
+
+ case ' ':
+ case '\b': case '\f': case '\n':
+ case '\r': case '\t': case '\v':
+ q++;
+ continue;
+ }
+ break;
+ }
+ *pp++ = p;
+ ++argc;
+ for (;;) {
+ switch ((*p++ = *q++)) {
+ case '\0':
+ goto copyrest;
+
+ case '\\':
+ if (!*q)
+ goto copyrest;
+ p[-1] = *q++;
+ continue;
+
+ default:
+ continue;
+
+ case ' ':
+ case '\b': case '\f': case '\n':
+ case '\r': case '\t': case '\v':
+ break;
+ }
+ break;
+ }
+ p[-1] = '\0';
+ }
+ copyrest:
+ while ((*pp++ = *argv++))
+ ;
+ }
+ return argc;
+}
+
+
+#define cacheid(E) static uid_t i; static int s; if (!s){ s=1; i=(E); } return i
+
+#if has_getuid
+ uid_t ruid() { cacheid(getuid()); }
+#endif
+#if has_setuid
+ uid_t euid() { cacheid(geteuid()); }
+#endif
+
+
+#if has_setuid
+
+/*
+ * Setuid execution really works only with Posix 1003.1a Draft 5 seteuid(),
+ * because it lets us switch back and forth between arbitrary users.
+ * If seteuid() doesn't work, we fall back on setuid(),
+ * which works if saved setuid is supported,
+ * unless the real or effective user is root.
+ * This area is such a mess that we always check switches at runtime.
+ */
+
+ static void
+set_uid_to(u)
+ uid_t u;
+/* Become user u. */
+{
+ static int looping;
+
+ if (euid() == ruid())
+ return;
+#if (has_fork||has_spawn) && DIFF_ABSOLUTE
+ if (seteuid(u) != 0)
+ efaterror("setuid");
+#endif
+ if (geteuid() != u) {
+ if (looping)
+ return;
+ looping = true;
+ faterror("root setuid not supported" + (u?5:0));
+ }
+}
+
+static int stick_with_euid;
+
+ void
+/* Ignore all calls to seteid() and setrid(). */
+nosetid()
+{
+ stick_with_euid = true;
+}
+
+ void
+seteid()
+/* Become effective user. */
+{
+ if (!stick_with_euid)
+ set_uid_to(euid());
+}
+
+ void
+setrid()
+/* Become real user. */
+{
+ if (!stick_with_euid)
+ set_uid_to(ruid());
+}
+#endif
diff --git a/gnu/usr.bin/rcs/merge/Makefile b/gnu/usr.bin/rcs/merge/Makefile
new file mode 100644
index 0000000..d14afb2
--- /dev/null
+++ b/gnu/usr.bin/rcs/merge/Makefile
@@ -0,0 +1,7 @@
+PROG= merge
+
+SRCS= merge.c
+LDADD= -L${.CURDIR}/../lib/obj -lrcs
+CFLAGS+= -I${.CURDIR}/../lib
+
+.include <bsd.prog.mk>
diff --git a/gnu/usr.bin/rcs/merge/merge.1 b/gnu/usr.bin/rcs/merge/merge.1
new file mode 100644
index 0000000..8b1957f
--- /dev/null
+++ b/gnu/usr.bin/rcs/merge/merge.1
@@ -0,0 +1,102 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+..
+.Id $Id: merge.1,v 5.3 1991/02/28 19:18:45 eggert Exp $
+.TH MERGE 1 \*(Dt GNU
+.SH NAME
+merge \- three-way file merge
+.SH SYNOPSIS
+.B merge
+[
+.B \-L
+.I label1
+[
+.B \-L
+.I label3
+] ] [
+.B \-p
+] [
+.B \-q
+]
+.I "file1 file2 file3"
+.SH DESCRIPTION
+.B merge
+incorporates all changes that lead from
+.I file2
+to
+.I file3
+into
+.IR file1 .
+The result goes to standard output if
+.B \-p
+is present, into
+.I file1
+otherwise.
+.B merge
+is useful for combining separate changes to an original. Suppose
+.I file2
+is the original, and both
+.I file1
+and
+.I file3
+are modifications of
+.IR file2 .
+Then
+.B merge
+combines both changes.
+.PP
+An overlap occurs if both
+.I file1
+and
+.I file3
+have changes in a common segment of lines.
+On a few older hosts where
+.B diff3
+does not support the
+.B \-E
+option,
+.B merge
+does not detect overlaps, and merely supplies the changed lines from
+.I file3.
+On most hosts, if overlaps occur,
+.B merge
+outputs a message (unless the
+.B \-q
+option is given),
+and includes both alternatives
+in the result. The alternatives are delimited as follows:
+.LP
+.RS
+.nf
+.BI <<<<<<< " file1"
+.I "lines in file1"
+.B "======="
+.I "lines in file3"
+.BI >>>>>>> " file3"
+.RE
+.fi
+.LP
+If there are overlaps, the user should edit the result and delete one of the
+alternatives.
+If the
+.BI \-L "\ label1"
+and
+.BI \-L "\ label3"
+options are given, the labels are output in place of the names
+.I file1
+and
+.I file3
+in overlap reports.
+.SH DIAGNOSTICS
+Exit status is 0 for no overlaps, 1 for some overlaps, 2 for trouble.
+.SH IDENTIFICATION
+Author: Walter F. Tichy.
+.br
+Revision Number: \*(Rv; Release Date: \*(Dt.
+.br
+Copyright \(co 1982, 1988, 1989 by Walter F. Tichy.
+.br
+Copyright \(co 1990, 1991 by Paul Eggert.
+.SH SEE ALSO
+diff3(1), diff(1), rcsmerge(1), co(1).
diff --git a/gnu/usr.bin/rcs/merge/merge.c b/gnu/usr.bin/rcs/merge/merge.c
new file mode 100644
index 0000000..4067c18
--- /dev/null
+++ b/gnu/usr.bin/rcs/merge/merge.c
@@ -0,0 +1,97 @@
+/* merge - three-way file merge */
+
+/* Copyright 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+#include "rcsbase.h"
+
+
+static char const usage[] =
+ "\nmerge: usage: merge [-p] [-q] [-L label1 [-L label3]] file1 file2 file3\n";
+
+ static exiting void
+badoption(a)
+ char const *a;
+{
+ faterror("unknown option: %s%s", a-2, usage);
+}
+
+
+mainProg(mergeId, "merge", "$Id: merge.c,v 1.2 1991/08/19 03:13:55 eggert Exp $")
+{
+ register char const *a;
+ char const *label[2], *arg[3];
+ int labels, tostdout;
+
+ labels = 0;
+ tostdout = false;
+
+ while ((a = *++argv) && *a++ == '-') {
+ switch (*a++) {
+ case 'p': tostdout = true; break;
+ case 'q': quietflag = true; break;
+ case 'L':
+ if (1<labels)
+ faterror("too many -L options");
+ if (!(label[labels++] = *++argv))
+ faterror("-L needs following argument");
+ --argc;
+ break;
+ default:
+ badoption(a);
+ }
+ if (*a)
+ badoption(a);
+ --argc;
+ }
+
+ if (argc != 4)
+ faterror("%s arguments%s",
+ argc<4 ? "not enough" : "too many", usage
+ );
+
+ /* This copy keeps us `const'-clean. */
+ arg[0] = argv[0];
+ arg[1] = argv[1];
+ arg[2] = argv[2];
+
+ switch (labels) {
+ case 0: label[0] = arg[0]; /* fall into */
+ case 1: label[1] = arg[2];
+ }
+
+ exitmain(merge(tostdout, label, arg));
+}
+
+
+#if lint
+# define exiterr mergeExit
+#endif
+ exiting void
+exiterr()
+{
+ tempunlink();
+ _exit(DIFF_TROUBLE);
+}
diff --git a/gnu/usr.bin/rcs/rcs/Makefile b/gnu/usr.bin/rcs/rcs/Makefile
new file mode 100644
index 0000000..d62c8d1
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcs/Makefile
@@ -0,0 +1,10 @@
+PROG= rcs
+
+SRCS= rcs.c
+LDADD= -L${.CURDIR}/../lib/obj -lrcs
+CFLAGS+= -I${.CURDIR}/../lib
+
+MAN1= rcs.0 rcsintro.0
+MAN5= rcsfile.0
+
+.include <bsd.prog.mk>
diff --git a/gnu/usr.bin/rcs/rcs/rcs.1 b/gnu/usr.bin/rcs/rcs/rcs.1
new file mode 100644
index 0000000..9866a9c
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcs/rcs.1
@@ -0,0 +1,397 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+..
+.Id $Id: rcs.1,v 5.6 1991/09/26 23:16:17 eggert Exp $
+.ds r \&\s-1RCS\s0
+.if n .ds - \%--
+.if t .ds - \(em
+.TH RCS 1 \*(Dt GNU
+.SH NAME
+rcs \- change RCS file attributes
+.SH SYNOPSIS
+.B rcs
+.RI [ " options " ] " file " .\|.\|.
+.SH DESCRIPTION
+.B rcs
+creates new \*r files or changes attributes of existing ones.
+An \*r file contains multiple revisions of text,
+an access list, a change log,
+descriptive text,
+and some control attributes.
+For
+.B rcs
+to work, the caller's login name must be on the access list,
+except if the access list is empty, the caller is the owner of the file
+or the superuser, or
+the
+.B \-i
+option is present.
+.PP
+Pathnames matching an \*r suffix denote \*r files;
+all others denote working files.
+Names are paired as explained in
+.BR ci (1).
+Revision numbers use the syntax described in
+.BR ci (1).
+.SH OPTIONS
+.TP
+.B \-i
+Create and initialize a new \*r file, but do not deposit any revision.
+If the \*r file has no path prefix, try to place it
+first into the subdirectory
+.BR ./RCS ,
+and then into the current directory.
+If the \*r file
+already exists, print an error message.
+.TP
+.BI \-a "logins"
+Append the login names appearing in the comma-separated list
+.I logins
+to the access list of the \*r file.
+.TP
+.BI \-A "oldfile"
+Append the access list of
+.I oldfile
+to the access list of the \*r file.
+.TP
+.BR \-e [\f2logins\fP]
+Erase the login names appearing in the comma-separated list
+.I logins
+from the access list of the \*r file.
+If
+.I logins
+is omitted, erase the entire access list.
+.TP
+.BR \-b [\f2rev\fP]
+Set the default branch to
+.IR rev .
+If
+.I rev
+is omitted, the default
+branch is reset to the (dynamically) highest branch on the trunk.
+.TP
+.BI \-c string
+sets the comment leader to
+.IR string .
+The comment leader
+is printed before every log message line generated by the keyword
+.B $\&Log$
+during checkout (see
+.BR co (1)).
+This is useful for programming
+languages without multi-line comments.
+An initial
+.B ci ,
+or an
+.B "rcs\ \-i"
+without
+.BR \-c ,
+guesses the comment leader from the suffix of the working file.
+.TP
+.BI \-k subst
+Set the default keyword substitution to
+.IR subst .
+The effect of keyword substitution is described in
+.BR co (1).
+Giving an explicit
+.B \-k
+option to
+.BR co ,
+.BR rcsdiff ,
+and
+.B rcsmerge
+overrides this default.
+Beware
+.BR "rcs\ \-kv",
+because
+.B \-kv
+is incompatible with
+.BR "co\ \-l".
+Use
+.B "rcs\ \-kkv"
+to restore the normal default keyword substitution.
+.TP
+.BR \-l [\f2rev\fP]
+Lock the revision with number
+.IR rev .
+If a branch is given, lock the latest revision on that branch.
+If
+.I rev
+is omitted, lock the latest revision on the default branch.
+Locking prevents overlapping changes.
+A lock is removed with
+.B ci
+or
+.B "rcs\ \-u"
+(see below).
+.TP
+.BR \-u [\f2rev\fP]
+Unlock the revision with number
+.IR rev .
+If a branch is given, unlock the latest revision on that branch.
+If
+.I rev
+is omitted, remove the latest lock held by the caller.
+Normally, only the locker of a revision may unlock it.
+Somebody else unlocking a revision breaks the lock.
+This causes a mail message to be sent to the original locker.
+The message contains a commentary solicited from the breaker.
+The commentary is terminated by end-of-file or by a line containing
+.BR \&. "\ by"
+itself.
+.TP
+.B \-L
+Set locking to
+.IR strict .
+Strict locking means that the owner
+of an \*r file is not exempt from locking for checkin.
+This option should be used for files that are shared.
+.TP
+.B \-U
+Set locking to non-strict. Non-strict locking means that the owner of
+a file need not lock a revision for checkin.
+This option should
+.I not
+be used for files that are shared.
+Whether default locking is strict is determined by your system administrator,
+but it is normally strict.
+.TP
+\f3\-m\fP\f2rev\fP\f3:\fP\f2msg\fP
+Replace revision
+.IR rev 's
+log message with
+.IR msg .
+.TP
+\f3\-n\fP\f2name\fP[\f3:\fP[\f2rev\fP]]
+Associate the symbolic name
+.I name
+with the branch or
+revision
+.IR rev .
+Delete the symbolic name if both
+.B :
+and
+.I rev
+are omitted; otherwise, print an error message if
+.I name
+is already associated with
+another number.
+If
+.I rev
+is symbolic, it is expanded before association.
+A
+.I rev
+consisting of a branch number followed by a
+.B .\&
+stands for the current latest revision in the branch.
+A
+.B :
+with an empty
+.I rev
+stands for the current latest revision on the default branch,
+normally the trunk.
+For example,
+.BI "rcs\ \-n" name ":\ RCS/*"
+associates
+.I name
+with the current latest revision of all the named \*r files;
+this contrasts with
+.BI "rcs\ \-n" name ":$\ RCS/*"
+which associates
+.I name
+with the revision numbers extracted from keyword strings
+in the corresponding working files.
+.TP
+\f3\-N\fP\f2name\fP[\f3:\fP[\f2rev\fP]]
+Act like
+.BR \-n ,
+except override any previous assignment of
+.IR name .
+.TP
+.BI \-o range
+deletes (\*(lqoutdates\*(rq) the revisions given by
+.IR range .
+A range consisting of a single revision number means that revision.
+A range consisting of a branch number means the latest revision on that
+branch.
+A range of the form
+.IB rev1 : rev2
+means
+revisions
+.I rev1
+to
+.I rev2
+on the same branch,
+.BI : rev
+means from the beginning of the branch containing
+.I rev
+up to and including
+.IR rev ,
+and
+.IB rev :
+means
+from revision
+.I rev
+to the end of the branch containing
+.IR rev .
+None of the outdated revisions may have branches or locks.
+.TP
+.B \-q
+Run quietly; do not print diagnostics.
+.TP
+.B \-I
+Run interactively, even if the standard input is not a terminal.
+.TP
+.B \-s\f2state\fP\f1[\fP:\f2rev\fP\f1]\fP
+Set the state attribute of the revision
+.I rev
+to
+.I state .
+If
+.I rev
+is a branch number, assume the latest revision on that branch.
+If
+.I rev
+is omitted, assume the latest revision on the default branch.
+Any identifier is acceptable for
+.IR state .
+A useful set of states
+is
+.B Exp
+(for experimental),
+.B Stab
+(for stable), and
+.B Rel
+(for
+released).
+By default,
+.BR ci (1)
+sets the state of a revision to
+.BR Exp .
+.TP
+.BR \-t [\f2file\fP]
+Write descriptive text from the contents of the named
+.I file
+into the \*r file, deleting the existing text.
+The
+.IR file
+pathname may not begin with
+.BR \- .
+If
+.I file
+is omitted, obtain the text from standard input,
+terminated by end-of-file or by a line containing
+.BR \&. "\ by"
+itself.
+Prompt for the text if interaction is possible; see
+.BR \-I .
+With
+.BR \-i ,
+descriptive text is obtained
+even if
+.B \-t
+is not given.
+.TP
+.BI \-t\- string
+Write descriptive text from the
+.I string
+into the \*r file, deleting the existing text.
+.TP
+.BI \-V n
+Emulate \*r version
+.IR n .
+See
+.BR co (1)
+for details.
+.TP
+.BI \-x "suffixes"
+Use
+.I suffixes
+to characterize \*r files.
+See
+.BR ci (1)
+for details.
+.SH COMPATIBILITY
+The
+.BI \-b rev
+option generates an \*r file that cannot be parsed by \*r version 3 or earlier.
+.PP
+The
+.BI \-k subst
+options (except
+.BR \-kkv )
+generate an \*r file that cannot be parsed by \*r version 4 or earlier.
+.PP
+Use
+.BI "rcs \-V" n
+to make an \*r file acceptable to \*r version
+.I n
+by discarding information that would confuse version
+.IR n .
+.PP
+\*r version 5.5 and earlier does not support the
+.B \-x
+option, and requires a
+.B ,v
+suffix on an \*r pathname.
+.SH FILES
+.B rcs
+accesses files much as
+.BR ci (1)
+does,
+except that it uses the effective user for all accesses,
+it does not write the working file or its directory,
+and it does not even read the working file unless a revision number of
+.B $
+is specified.
+.SH ENVIRONMENT
+.TP
+.B \s-1RCSINIT\s0
+options prepended to the argument list, separated by spaces.
+See
+.BR ci (1)
+for details.
+.SH DIAGNOSTICS
+The \*r pathname and the revisions outdated are written to
+the diagnostic output.
+The exit status is zero if and only if all operations were successful.
+.SH IDENTIFICATION
+Author: Walter F. Tichy.
+.br
+Revision Number: \*(Rv; Release Date: \*(Dt.
+.br
+Copyright \(co 1982, 1988, 1989 by Walter F. Tichy.
+.br
+Copyright \(co 1990, 1991 by Paul Eggert.
+.SH "SEE ALSO"
+co(1), ci(1), ident(1), rcsdiff(1), rcsintro(1), rcsmerge(1), rlog(1),
+rcsfile(5)
+.br
+Walter F. Tichy,
+\*r\*-A System for Version Control,
+.I "Software\*-Practice & Experience"
+.BR 15 ,
+7 (July 1985), 637-654.
+.SH BUGS
+The separator for revision ranges in the
+.B \-o
+option used to be
+.B \-
+instead of
+.BR : ,
+but this leads to confusion when symbolic names contain
+.BR \- .
+For backwards compatibility
+.B "rcs \-o"
+still supports the old
+.B \-
+separator, but it warns about this obsolete use.
+.PP
+Symbolic names need not refer to existing revisions or branches.
+For example, the
+.B \-o
+option does not remove symbolic names for the outdated revisions; you must use
+.B \-n
+to remove the names.
+.br
diff --git a/gnu/usr.bin/rcs/rcs/rcs.c b/gnu/usr.bin/rcs/rcs/rcs.c
new file mode 100644
index 0000000..70e7ffc
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcs/rcs.c
@@ -0,0 +1,1554 @@
+/*
+ * RCS create/change operation
+ */
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+
+
+/* $Log: rcs.c,v $
+ * Revision 5.12 1991/11/20 17:58:08 eggert
+ * Don't read the delta tree from a nonexistent RCS file.
+ *
+ * Revision 5.11 1991/10/07 17:32:46 eggert
+ * Remove lint.
+ *
+ * Revision 5.10 1991/08/19 23:17:54 eggert
+ * Add -m, -r$, piece tables. Revision separator is `:', not `-'. Tune.
+ *
+ * Revision 5.9 1991/04/21 11:58:18 eggert
+ * Add -x, RCSINIT, MS-DOS support.
+ *
+ * Revision 5.8 1991/02/25 07:12:38 eggert
+ * strsave -> str_save (DG/UX name clash)
+ * 0444 -> S_IRUSR|S_IRGRP|S_IROTH for portability
+ *
+ * Revision 5.7 1990/12/18 17:19:21 eggert
+ * Fix bug with multiple -n and -N options.
+ *
+ * Revision 5.6 1990/12/04 05:18:40 eggert
+ * Use -I for prompts and -q for diagnostics.
+ *
+ * Revision 5.5 1990/11/11 00:06:35 eggert
+ * Fix `rcs -e' core dump.
+ *
+ * Revision 5.4 1990/11/01 05:03:33 eggert
+ * Add -I and new -t behavior. Permit arbitrary data in logs.
+ *
+ * Revision 5.3 1990/10/04 06:30:16 eggert
+ * Accumulate exit status across files.
+ *
+ * Revision 5.2 1990/09/04 08:02:17 eggert
+ * Standardize yes-or-no procedure.
+ *
+ * Revision 5.1 1990/08/29 07:13:51 eggert
+ * Remove unused setuid support. Clean old log messages too.
+ *
+ * Revision 5.0 1990/08/22 08:12:42 eggert
+ * Don't lose names when applying -a option to multiple files.
+ * Remove compile-time limits; use malloc instead. Add setuid support.
+ * Permit dates past 1999/12/31. Make lock and temp files faster and safer.
+ * Ansify and Posixate. Add -V. Fix umask bug. Make linting easier. Tune.
+ * Yield proper exit status. Check diff's output.
+ *
+ * Revision 4.11 89/05/01 15:12:06 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.10 88/11/08 16:01:54 narten
+ * didn't install previous patch correctly
+ *
+ * Revision 4.9 88/11/08 13:56:01 narten
+ * removed include <sysexits.h> (not needed)
+ * minor fix for -A option
+ *
+ * Revision 4.8 88/08/09 19:12:27 eggert
+ * Don't access freed storage.
+ * Use execv(), not system(); yield proper exit status; remove lint.
+ *
+ * Revision 4.7 87/12/18 11:37:17 narten
+ * lint cleanups (Guy Harris)
+ *
+ * Revision 4.6 87/10/18 10:28:48 narten
+ * Updating verison numbers. Changes relative to 1.1 are actually
+ * relative to 4.3
+ *
+ * Revision 1.4 87/09/24 13:58:52 narten
+ * Sources now pass through lint (if you ignore printf/sprintf/fprintf
+ * warnings)
+ *
+ * Revision 1.3 87/03/27 14:21:55 jenkins
+ * Port to suns
+ *
+ * Revision 1.2 85/12/17 13:59:09 albitz
+ * Changed setstate to rcs_setstate because of conflict with random.o.
+ *
+ * Revision 4.3 83/12/15 12:27:33 wft
+ * rcs -u now breaks most recent lock if it can't find a lock by the caller.
+ *
+ * Revision 4.2 83/12/05 10:18:20 wft
+ * Added conditional compilation for sending mail.
+ * Alternatives: V4_2BSD, V6, USG, and other.
+ *
+ * Revision 4.1 83/05/10 16:43:02 wft
+ * Simplified breaklock(); added calls to findlock() and getcaller().
+ * Added option -b (default branch). Updated -s and -w for -b.
+ * Removed calls to stat(); now done by pairfilenames().
+ * Replaced most catchints() calls with restoreints().
+ * Removed check for exit status of delivermail().
+ * Directed all interactive output to stderr.
+ *
+ * Revision 3.9.1.1 83/12/02 22:08:51 wft
+ * Added conditional compilation for 4.2 sendmail and 4.1 delivermail.
+ *
+ * Revision 3.9 83/02/15 15:38:39 wft
+ * Added call to fastcopy() to copy remainder of RCS file.
+ *
+ * Revision 3.8 83/01/18 17:37:51 wft
+ * Changed sendmail(): now uses delivermail, and asks whether to break the lock.
+ *
+ * Revision 3.7 83/01/15 18:04:25 wft
+ * Removed putree(); replaced with puttree() in rcssyn.c.
+ * Combined putdellog() and scanlogtext(); deleted putdellog().
+ * Cleaned up diagnostics and error messages. Fixed problem with
+ * mutilated files in case of deletions in 2 files in a single command.
+ * Changed marking of selector from 'D' to DELETE.
+ *
+ * Revision 3.6 83/01/14 15:37:31 wft
+ * Added ignoring of interrupts while new RCS file is renamed;
+ * Avoids deletion of RCS files by interrupts.
+ *
+ * Revision 3.5 82/12/10 21:11:39 wft
+ * Removed unused variables, fixed checking of return code from diff,
+ * introduced variant COMPAT2 for skipping Suffix on -A files.
+ *
+ * Revision 3.4 82/12/04 13:18:20 wft
+ * Replaced getdelta() with gettree(), changed breaklock to update
+ * field lockedby, added some diagnostics.
+ *
+ * Revision 3.3 82/12/03 17:08:04 wft
+ * Replaced getlogin() with getpwuid(), flcose() with ffclose(),
+ * /usr/ucb/Mail with macro MAIL. Removed handling of Suffix (-x).
+ * fixed -u for missing revno. Disambiguated structure members.
+ *
+ * Revision 3.2 82/10/18 21:05:07 wft
+ * rcs -i now generates a file mode given by the umask minus write permission;
+ * otherwise, rcs keeps the mode, but removes write permission.
+ * I added a check for write error, fixed call to getlogin(), replaced
+ * curdir() with getfullRCSname(), cleaned up handling -U/L, and changed
+ * conflicting, long identifiers.
+ *
+ * Revision 3.1 82/10/13 16:11:07 wft
+ * fixed type of variables receiving from getc() (char -> int).
+ */
+
+
+#include "rcsbase.h"
+
+struct Lockrev {
+ char const *revno;
+ struct Lockrev * nextrev;
+};
+
+struct Symrev {
+ char const *revno;
+ char const *ssymbol;
+ int override;
+ struct Symrev * nextsym;
+};
+
+struct Message {
+ char const *revno;
+ struct cbuf message;
+ struct Message *nextmessage;
+};
+
+struct Status {
+ char const *revno;
+ char const *status;
+ struct Status * nextstatus;
+};
+
+enum changeaccess {append, erase};
+struct chaccess {
+ char const *login;
+ enum changeaccess command;
+ struct chaccess *nextchaccess;
+};
+
+struct delrevpair {
+ char const *strt;
+ char const *end;
+ int code;
+};
+
+static int buildeltatext P((struct hshentries const*));
+static int removerevs P((void));
+static int sendmail P((char const*,char const*));
+static struct Lockrev *rmnewlocklst P((struct Lockrev const*));
+static void breaklock P((struct hshentry const*));
+static void buildtree P((void));
+static void cleanup P((void));
+static void doaccess P((void));
+static void doassoc P((void));
+static void dolocks P((void));
+static void domessages P((void));
+static void getaccessor P((char*,enum changeaccess));
+static void getassoclst P((int,char*));
+static void getchaccess P((char const*,enum changeaccess));
+static void getdelrev P((char*));
+static void getmessage P((char*));
+static void getstates P((char*));
+static void rcs_setstate P((char const*,char const*));
+static void scanlogtext P((struct hshentry*,int));
+static void setlock P((char const*));
+
+static struct buf numrev;
+static char const *headstate;
+static int chgheadstate, exitstatus, lockhead, unlockcaller;
+static struct Lockrev *newlocklst, *rmvlocklst;
+static struct Message *messagelst, *lastmessage;
+static struct Status *statelst, *laststate;
+static struct Symrev *assoclst, *lastassoc;
+static struct chaccess *chaccess, **nextchaccess;
+static struct delrevpair delrev;
+static struct hshentry *cuthead, *cuttail, *delstrt;
+static struct hshentries *gendeltas;
+
+mainProg(rcsId, "rcs", "$Id: rcs.c,v 5.12 1991/11/20 17:58:08 eggert Exp $")
+{
+ static char const cmdusage[] =
+ "\nrcs usage: rcs -{ae}logins -Afile -{blu}[rev] -cstring -{iLU} -{nNs}name[:rev] -orange -t[file] -Vn file ...";
+
+ char *a, **newargv, *textfile;
+ char const *branchsym, *commsyml;
+ int branchflag, expmode, initflag;
+ int e, r, strictlock, strict_selected, textflag;
+ mode_t defaultRCSmode; /* default mode for new RCS files */
+ mode_t RCSmode;
+ struct buf branchnum;
+ struct stat workstat;
+ struct Lockrev *curlock, * rmvlock, *lockpt;
+ struct Status * curstate;
+
+ nosetid();
+
+ nextchaccess = &chaccess;
+ branchsym = commsyml = textfile = nil;
+ branchflag = strictlock = false;
+ bufautobegin(&branchnum);
+ curlock = rmvlock = nil;
+ defaultRCSmode = 0;
+ expmode = -1;
+ suffixes = X_DEFAULT;
+ initflag= textflag = false;
+ strict_selected = 0;
+
+ /* preprocessing command options */
+ argc = getRCSINIT(argc, argv, &newargv);
+ argv = newargv;
+ while (a = *++argv, 0<--argc && *a++=='-') {
+ switch (*a++) {
+
+ case 'i': /* initial version */
+ initflag = true;
+ break;
+
+ case 'b': /* change default branch */
+ if (branchflag) redefined('b');
+ branchflag= true;
+ branchsym = a;
+ break;
+
+ case 'c': /* change comment symbol */
+ if (commsyml) redefined('c');
+ commsyml = a;
+ break;
+
+ case 'a': /* add new accessor */
+ getaccessor(*argv+1, append);
+ break;
+
+ case 'A': /* append access list according to accessfile */
+ if (!*a) {
+ error("missing file name after -A");
+ break;
+ }
+ *argv = a;
+ if (0 < pairfilenames(1,argv,rcsreadopen,true,false)) {
+ while (AccessList) {
+ getchaccess(str_save(AccessList->login),append);
+ AccessList = AccessList->nextaccess;
+ }
+ Izclose(&finptr);
+ }
+ break;
+
+ case 'e': /* remove accessors */
+ getaccessor(*argv+1, erase);
+ break;
+
+ case 'l': /* lock a revision if it is unlocked */
+ if (!*a) {
+ /* Lock head or default branch. */
+ lockhead = true;
+ break;
+ }
+ lockpt = talloc(struct Lockrev);
+ lockpt->revno = a;
+ lockpt->nextrev = nil;
+ if ( curlock )
+ curlock->nextrev = lockpt;
+ else
+ newlocklst = lockpt;
+ curlock = lockpt;
+ break;
+
+ case 'u': /* release lock of a locked revision */
+ if (!*a) {
+ unlockcaller=true;
+ break;
+ }
+ lockpt = talloc(struct Lockrev);
+ lockpt->revno = a;
+ lockpt->nextrev = nil;
+ if (rmvlock)
+ rmvlock->nextrev = lockpt;
+ else
+ rmvlocklst = lockpt;
+ rmvlock = lockpt;
+
+ curlock = rmnewlocklst(lockpt);
+ break;
+
+ case 'L': /* set strict locking */
+ if (strict_selected++) { /* Already selected L or U? */
+ if (!strictlock) /* Already selected -U? */
+ warn("-L overrides -U.");
+ }
+ strictlock = true;
+ break;
+
+ case 'U': /* release strict locking */
+ if (strict_selected++) { /* Already selected L or U? */
+ if (strictlock) /* Already selected -L? */
+ warn("-L overrides -U.");
+ }
+ else
+ strictlock = false;
+ break;
+
+ case 'n': /* add new association: error, if name exists */
+ if (!*a) {
+ error("missing symbolic name after -n");
+ break;
+ }
+ getassoclst(false, (*argv)+1);
+ break;
+
+ case 'N': /* add or change association */
+ if (!*a) {
+ error("missing symbolic name after -N");
+ break;
+ }
+ getassoclst(true, (*argv)+1);
+ break;
+
+ case 'm': /* change log message */
+ getmessage(a);
+ break;
+
+ case 'o': /* delete revisions */
+ if (delrev.strt) redefined('o');
+ if (!*a) {
+ error("missing revision range after -o");
+ break;
+ }
+ getdelrev( (*argv)+1 );
+ break;
+
+ case 's': /* change state attribute of a revision */
+ if (!*a) {
+ error("state missing after -s");
+ break;
+ }
+ getstates( (*argv)+1);
+ break;
+
+ case 't': /* change descriptive text */
+ textflag=true;
+ if (*a) {
+ if (textfile) redefined('t');
+ textfile = a;
+ }
+ break;
+
+ case 'I':
+ interactiveflag = true;
+ break;
+
+ case 'q':
+ quietflag = true;
+ break;
+
+ case 'x':
+ suffixes = a;
+ break;
+
+ case 'V':
+ setRCSversion(*argv);
+ break;
+
+ case 'k': /* set keyword expand mode */
+ if (0 <= expmode) redefined('k');
+ if (0 <= (expmode = str2expmode(a)))
+ break;
+ /* fall into */
+ default:
+ faterror("unknown option: %s%s", *argv, cmdusage);
+ };
+ } /* end processing of options */
+
+ if (argc<1) faterror("no input file%s", cmdusage);
+ if (nerror) {
+ diagnose("%s aborted\n",cmdid);
+ exitmain(EXIT_FAILURE);
+ }
+ if (initflag) {
+ defaultRCSmode = umask((mode_t)0);
+ VOID umask(defaultRCSmode);
+ defaultRCSmode = (S_IRUSR|S_IRGRP|S_IROTH) & ~defaultRCSmode;
+ }
+
+ /* now handle all filenames */
+ do {
+ ffree();
+
+ if ( initflag ) {
+ switch (pairfilenames(argc, argv, rcswriteopen, false, false)) {
+ case -1: break; /* not exist; ok */
+ case 0: continue; /* error */
+ case 1: error("file %s exists already", RCSfilename);
+ continue;
+ }
+ }
+ else {
+ switch (pairfilenames(argc, argv, rcswriteopen, true, false)) {
+ case -1: continue; /* not exist */
+ case 0: continue; /* errors */
+ case 1: break; /* file exists; ok*/
+ }
+ }
+
+
+ /* now RCSfilename contains the name of the RCS file, and
+ * workfilename contains the name of the working file.
+ * if !initflag, finptr contains the file descriptor for the
+ * RCS file. The admin node is initialized.
+ */
+
+ diagnose("RCS file: %s\n", RCSfilename);
+
+ RCSmode = defaultRCSmode;
+ if (initflag) {
+ if (stat(workfilename, &workstat) == 0)
+ RCSmode = workstat.st_mode;
+ } else {
+ if (!checkaccesslist()) continue;
+ gettree(); /* Read the delta tree. */
+ RCSmode = RCSstat.st_mode;
+ }
+ RCSmode &= ~(S_IWUSR|S_IWGRP|S_IWOTH);
+
+ /* update admin. node */
+ if (strict_selected) StrictLocks = strictlock;
+ if (commsyml) {
+ Comment.string = commsyml;
+ Comment.size = strlen(commsyml);
+ }
+ if (0 <= expmode) Expand = expmode;
+
+ /* update default branch */
+ if (branchflag && expandsym(branchsym, &branchnum)) {
+ if (countnumflds(branchnum.string)) {
+ Dbranch = branchnum.string;
+ } else
+ Dbranch = nil;
+ }
+
+ doaccess(); /* Update access list. */
+
+ doassoc(); /* Update association list. */
+
+ dolocks(); /* Update locks. */
+
+ domessages(); /* Update log messages. */
+
+ /* update state attribution */
+ if (chgheadstate) {
+ /* change state of default branch or head */
+ if (Dbranch==nil) {
+ if (Head==nil)
+ warn("can't change states in an empty tree");
+ else Head->state = headstate;
+ } else {
+ rcs_setstate(Dbranch,headstate); /* Can't set directly */
+ }
+ }
+ curstate = statelst;
+ while( curstate ) {
+ rcs_setstate(curstate->revno,curstate->status);
+ curstate = curstate->nextstatus;
+ }
+
+ cuthead = cuttail = nil;
+ if (delrev.strt && removerevs()) {
+ /* rebuild delta tree if some deltas are deleted */
+ if ( cuttail )
+ VOID genrevs(cuttail->num, (char *)nil,(char *)nil,
+ (char *)nil, &gendeltas);
+ buildtree();
+ }
+
+ if (nerror)
+ continue;
+
+ putadmin(frewrite);
+ if ( Head )
+ puttree(Head, frewrite);
+ putdesc(textflag,textfile);
+
+ if ( Head) {
+ if (!delrev.strt && !messagelst) {
+ /* No revision was deleted and no message was changed. */
+ fastcopy(finptr, frewrite);
+ } else {
+ if (!cuttail || buildeltatext(gendeltas)) {
+ advise_access(finptr, MADV_SEQUENTIAL);
+ scanlogtext((struct hshentry *)nil, false);
+ /* copy rest of delta text nodes that are not deleted */
+ }
+ }
+ }
+ Izclose(&finptr);
+ if ( ! nerror ) { /* move temporary file to RCS file if no error */
+ /* update mode */
+ ignoreints();
+ r = chnamemod(&frewrite, newRCSfilename, RCSfilename, RCSmode);
+ e = errno;
+ keepdirtemp(newRCSfilename);
+ restoreints();
+ if (r != 0) {
+ enerror(e, RCSfilename);
+ error("saved in %s", newRCSfilename);
+ dirtempunlink();
+ break;
+ }
+ diagnose("done\n");
+ } else {
+ diagnose("%s aborted; %s unchanged.\n",cmdid,RCSfilename);
+ }
+ } while (cleanup(),
+ ++argv, --argc >=1);
+
+ tempunlink();
+ exitmain(exitstatus);
+} /* end of main (rcs) */
+
+ static void
+cleanup()
+{
+ if (nerror) exitstatus = EXIT_FAILURE;
+ Izclose(&finptr);
+ Ozclose(&fcopy);
+ Ozclose(&frewrite);
+ dirtempunlink();
+}
+
+ exiting void
+exiterr()
+{
+ dirtempunlink();
+ tempunlink();
+ _exit(EXIT_FAILURE);
+}
+
+
+ static void
+getassoclst(flag, sp)
+int flag;
+char * sp;
+/* Function: associate a symbolic name to a revision or branch, */
+/* and store in assoclst */
+
+{
+ struct Symrev * pt;
+ char const *temp;
+ int c;
+
+ while( (c=(*++sp)) == ' ' || c == '\t' || c =='\n') ;
+ temp = sp;
+ sp = checkid(sp, ':'); /* check for invalid symbolic name */
+ c = *sp; *sp = '\0';
+ while( c == ' ' || c == '\t' || c == '\n') c = *++sp;
+
+ if ( c != ':' && c != '\0') {
+ error("invalid string %s after option -n or -N",sp);
+ return;
+ }
+
+ pt = talloc(struct Symrev);
+ pt->ssymbol = temp;
+ pt->override = flag;
+ if (c == '\0') /* delete symbol */
+ pt->revno = nil;
+ else {
+ while( (c = *++sp) == ' ' || c == '\n' || c == '\t') ;
+ pt->revno = sp;
+ }
+ pt->nextsym = nil;
+ if (lastassoc)
+ lastassoc->nextsym = pt;
+ else
+ assoclst = pt;
+ lastassoc = pt;
+ return;
+}
+
+
+ static void
+getchaccess(login, command)
+ char const *login;
+ enum changeaccess command;
+{
+ register struct chaccess *pt;
+
+ *nextchaccess = pt = talloc(struct chaccess);
+ pt->login = login;
+ pt->command = command;
+ pt->nextchaccess = nil;
+ nextchaccess = &pt->nextchaccess;
+}
+
+
+
+ static void
+getaccessor(opt, command)
+ char *opt;
+ enum changeaccess command;
+/* Function: get the accessor list of options -e and -a, */
+/* and store in chaccess */
+
+
+{
+ register c;
+ register char *sp;
+
+ sp = opt;
+ while( ( c = *++sp) == ' ' || c == '\n' || c == '\t' || c == ',') ;
+ if ( c == '\0') {
+ if (command == erase && sp-opt == 1) {
+ getchaccess((char const*)nil, command);
+ return;
+ }
+ error("missing login name after option -a or -e");
+ return;
+ }
+
+ while( c != '\0') {
+ getchaccess(sp, command);
+ sp = checkid(sp,',');
+ c = *sp; *sp = '\0';
+ while( c == ' ' || c == '\n' || c == '\t'|| c == ',')c =(*++sp);
+ }
+}
+
+
+ static void
+getmessage(option)
+ char *option;
+{
+ struct Message *pt;
+ struct cbuf cb;
+ char *m;
+
+ if (!(m = strchr(option, ':'))) {
+ error("-m option lacks revision number");
+ return;
+ }
+ *m++ = 0;
+ cb = cleanlogmsg(m, strlen(m));
+ if (!cb.size) {
+ error("-m option lacks log message");
+ return;
+ }
+ pt = talloc(struct Message);
+ pt->revno = option;
+ pt->message = cb;
+ pt->nextmessage = 0;
+ if (lastmessage)
+ lastmessage->nextmessage = pt;
+ else
+ messagelst = pt;
+ lastmessage = pt;
+}
+
+
+ static void
+getstates(sp)
+char *sp;
+/* Function: get one state attribute and the corresponding */
+/* revision and store in statelst */
+
+{
+ char const *temp;
+ struct Status *pt;
+ register c;
+
+ while( (c=(*++sp)) ==' ' || c == '\t' || c == '\n') ;
+ temp = sp;
+ sp = checkid(sp,':'); /* check for invalid state attribute */
+ c = *sp; *sp = '\0';
+ while( c == ' ' || c == '\t' || c == '\n' ) c = *++sp;
+
+ if ( c == '\0' ) { /* change state of def. branch or Head */
+ chgheadstate = true;
+ headstate = temp;
+ return;
+ }
+ else if ( c != ':' ) {
+ error("missing ':' after state in option -s");
+ return;
+ }
+
+ while( (c = *++sp) == ' ' || c == '\t' || c == '\n') ;
+ pt = talloc(struct Status);
+ pt->status = temp;
+ pt->revno = sp;
+ pt->nextstatus = nil;
+ if (laststate)
+ laststate->nextstatus = pt;
+ else
+ statelst = pt;
+ laststate = pt;
+}
+
+
+
+ static void
+getdelrev(sp)
+char *sp;
+/* Function: get revision range or branch to be deleted, */
+/* and place in delrev */
+{
+ int c;
+ struct delrevpair *pt;
+ int separator;
+
+ pt = &delrev;
+ while((c = (*++sp)) == ' ' || c == '\n' || c == '\t') ;
+
+ /* Support old ambiguous '-' syntax; this will go away. */
+ if (strchr(sp,':'))
+ separator = ':';
+ else {
+ if (strchr(sp,'-') && VERSION(5) <= RCSversion)
+ warn("`-' is obsolete in `-o%s'; use `:' instead", sp);
+ separator = '-';
+ }
+
+ if (c == separator) { /* -o:rev */
+ while( (c = (*++sp)) == ' ' || c == '\n' || c == '\t') ;
+ pt->strt = sp; pt->code = 1;
+ while( c != ' ' && c != '\n' && c != '\t' && c != '\0') c =(*++sp);
+ *sp = '\0';
+ pt->end = nil;
+ return;
+ }
+ else {
+ pt->strt = sp;
+ while( c != ' ' && c != '\n' && c != '\t' && c != '\0'
+ && c != separator ) c = *++sp;
+ *sp = '\0';
+ while( c == ' ' || c == '\n' || c == '\t' ) c = *++sp;
+ if ( c == '\0' ) { /* -o rev or branch */
+ pt->end = nil; pt->code = 0;
+ return;
+ }
+ if (c != separator) {
+ faterror("invalid range %s %s after -o", pt->strt, sp);
+ }
+ while( (c = *++sp) == ' ' || c == '\n' || c == '\t') ;
+ if (!c) { /* -orev: */
+ pt->end = nil; pt->code = 2;
+ return;
+ }
+ }
+ /* -orev1:rev2 */
+ pt->end = sp; pt->code = 3;
+ while( c!= ' ' && c != '\n' && c != '\t' && c != '\0') c = *++sp;
+ *sp = '\0';
+}
+
+
+
+
+ static void
+scanlogtext(delta,edit)
+ struct hshentry *delta;
+ int edit;
+/* Function: Scans delta text nodes up to and including the one given
+ * by delta, or up to last one present, if delta==nil.
+ * For the one given by delta (if delta!=nil), the log message is saved into
+ * delta->log if delta==cuttail; the text is edited if EDIT is set, else copied.
+ * Assumes the initial lexeme must be read in first.
+ * Does not advance nexttok after it is finished, except if delta==nil.
+ */
+{
+ struct hshentry const *nextdelta;
+ struct cbuf cb;
+
+ for (;;) {
+ foutptr = 0;
+ if (eoflex()) {
+ if(delta)
+ faterror("can't find delta for revision %s", delta->num);
+ return; /* no more delta text nodes */
+ }
+ nextlex();
+ if (!(nextdelta=getnum()))
+ faterror("delta number corrupted");
+ if (nextdelta->selector) {
+ foutptr = frewrite;
+ aprintf(frewrite,DELNUMFORM,nextdelta->num,Klog);
+ }
+ getkeystring(Klog);
+ if (nextdelta == cuttail) {
+ cb = savestring(&curlogbuf);
+ if (!delta->log.string)
+ delta->log = cleanlogmsg(curlogbuf.string, cb.size);
+ } else if (nextdelta->log.string && nextdelta->selector) {
+ foutptr = 0;
+ readstring();
+ foutptr = frewrite;
+ putstring(foutptr, false, nextdelta->log, true);
+ afputc(nextc, foutptr);
+ } else {readstring();
+ }
+ nextlex();
+ while (nexttok==ID && strcmp(NextString,Ktext)!=0)
+ ignorephrase();
+ getkeystring(Ktext);
+
+ if (delta==nextdelta)
+ break;
+ readstring(); /* skip over it */
+
+ }
+ /* got the one we're looking for */
+ if (edit)
+ editstring((struct hshentry *)nil);
+ else
+ enterstring();
+}
+
+
+
+ static struct Lockrev *
+rmnewlocklst(which)
+ struct Lockrev const *which;
+/* Function: remove lock to revision which->revno from newlocklst */
+
+{
+ struct Lockrev * pt, *pre;
+
+ while( newlocklst && (! strcmp(newlocklst->revno, which->revno))){
+ struct Lockrev *pn = newlocklst->nextrev;
+ tfree(newlocklst);
+ newlocklst = pn;
+ }
+
+ pt = pre = newlocklst;
+ while( pt ) {
+ if ( ! strcmp(pt->revno, which->revno) ) {
+ pre->nextrev = pt->nextrev;
+ tfree(pt);
+ pt = pre->nextrev;
+ }
+ else {
+ pre = pt;
+ pt = pt->nextrev;
+ }
+ }
+ return pre;
+}
+
+
+
+ static void
+doaccess()
+{
+ register struct chaccess *ch;
+ register struct access **p, *t;
+
+ for (ch = chaccess; ch; ch = ch->nextchaccess) {
+ switch (ch->command) {
+ case erase:
+ if (!ch->login)
+ AccessList = nil;
+ else
+ for (p = &AccessList; (t = *p); )
+ if (strcmp(ch->login, t->login) == 0)
+ *p = t->nextaccess;
+ else
+ p = &t->nextaccess;
+ break;
+ case append:
+ for (p = &AccessList; ; p = &t->nextaccess)
+ if (!(t = *p)) {
+ *p = t = ftalloc(struct access);
+ t->login = ch->login;
+ t->nextaccess = nil;
+ break;
+ } else if (strcmp(ch->login, t->login) == 0)
+ break;
+ break;
+ }
+ }
+}
+
+
+ static int
+sendmail(Delta, who)
+ char const *Delta, *who;
+/* Function: mail to who, informing him that his lock on delta was
+ * broken by caller. Ask first whether to go ahead. Return false on
+ * error or if user decides not to break the lock.
+ */
+{
+#ifdef SENDMAIL
+ char const *messagefile;
+ int old1, old2, c;
+ FILE * mailmess;
+#endif
+
+
+ aprintf(stderr, "Revision %s is already locked by %s.\n", Delta, who);
+ if (!yesorno(false, "Do you want to break the lock? [ny](n): "))
+ return false;
+
+ /* go ahead with breaking */
+#ifdef SENDMAIL
+ messagefile = maketemp(0);
+ if (!(mailmess = fopen(messagefile, "w"))) {
+ efaterror(messagefile);
+ }
+
+ aprintf(mailmess, "Subject: Broken lock on %s\n\nYour lock on revision %s of file %s\nhas been broken by %s for the following reason:\n",
+ basename(RCSfilename), Delta, getfullRCSname(), getcaller()
+ );
+ aputs("State the reason for breaking the lock:\n(terminate with single '.' or end of file)\n>> ", stderr);
+
+ old1 = '\n'; old2 = ' ';
+ for (; ;) {
+ c = getcstdin();
+ if (feof(stdin)) {
+ aprintf(mailmess, "%c\n", old1);
+ break;
+ }
+ else if ( c == '\n' && old1 == '.' && old2 == '\n')
+ break;
+ else {
+ afputc(old1, mailmess);
+ old2 = old1; old1 = c;
+ if (c=='\n') aputs(">> ", stderr);
+ }
+ }
+ Ozclose(&mailmess);
+
+ if (run(messagefile, (char*)nil, SENDMAIL, who, (char*)nil))
+ warn("Mail may have failed."),
+#else
+ warn("Mail notification of broken locks is not available."),
+#endif
+ warn("Please tell `%s' why you broke the lock.", who);
+ return(true);
+}
+
+
+
+ static void
+breaklock(delta)
+ struct hshentry const *delta;
+/* function: Finds the lock held by caller on delta,
+ * and removes it.
+ * Sends mail if a lock different from the caller's is broken.
+ * Prints an error message if there is no such lock or error.
+ */
+{
+ register struct lock * next, * trail;
+ char const *num;
+ struct lock dummy;
+
+ num=delta->num;
+ dummy.nextlock=next=Locks;
+ trail = &dummy;
+ while (next!=nil) {
+ if (strcmp(num, next->delta->num) == 0) {
+ if (
+ strcmp(getcaller(),next->login) != 0
+ && !sendmail(num, next->login)
+ ) {
+ error("%s still locked by %s", num, next->login);
+ return;
+ }
+ break; /* exact match */
+ }
+ trail=next;
+ next=next->nextlock;
+ }
+ if (next!=nil) {
+ /*found one */
+ diagnose("%s unlocked\n",next->delta->num);
+ trail->nextlock=next->nextlock;
+ next->delta->lockedby=nil;
+ Locks=dummy.nextlock;
+ } else {
+ error("no lock set on revision %s", num);
+ }
+}
+
+
+
+ static struct hshentry *
+searchcutpt(object, length, store)
+ char const *object;
+ unsigned length;
+ struct hshentries *store;
+/* Function: Search store and return entry with number being object. */
+/* cuttail = nil, if the entry is Head; otherwise, cuttail */
+/* is the entry point to the one with number being object */
+
+{
+ cuthead = nil;
+ while (compartial(store->first->num, object, length)) {
+ cuthead = store->first;
+ store = store->rest;
+ }
+ return store->first;
+}
+
+
+
+ static int
+branchpoint(strt, tail)
+struct hshentry *strt, *tail;
+/* Function: check whether the deltas between strt and tail */
+/* are locked or branch point, return 1 if any is */
+/* locked or branch point; otherwise, return 0 and */
+/* mark deleted */
+
+{
+ struct hshentry *pt;
+ struct lock const *lockpt;
+ int flag;
+
+
+ pt = strt;
+ flag = false;
+ while( pt != tail) {
+ if ( pt->branches ){ /* a branch point */
+ flag = true;
+ error("can't remove branch point %s", pt->num);
+ }
+ lockpt = Locks;
+ while(lockpt && lockpt->delta != pt)
+ lockpt = lockpt->nextlock;
+ if ( lockpt ) {
+ flag = true;
+ error("can't remove locked revision %s",pt->num);
+ }
+ pt = pt->next;
+ }
+
+ if ( ! flag ) {
+ pt = strt;
+ while( pt != tail ) {
+ pt->selector = false;
+ diagnose("deleting revision %s\n",pt->num);
+ pt = pt->next;
+ }
+ }
+ return flag;
+}
+
+
+
+ static int
+removerevs()
+/* Function: get the revision range to be removed, and place the */
+/* first revision removed in delstrt, the revision before */
+/* delstrt in cuthead( nil, if delstrt is head), and the */
+/* revision after the last removed revision in cuttail(nil */
+/* if the last is a leaf */
+
+{
+ struct hshentry *target, *target2, *temp;
+ unsigned length;
+ int flag;
+
+ flag = false;
+ if (!expandsym(delrev.strt, &numrev)) return 0;
+ target = genrevs(numrev.string, (char*)nil, (char*)nil, (char*)nil, &gendeltas);
+ if ( ! target ) return 0;
+ if (cmpnum(target->num, numrev.string)) flag = true;
+ length = countnumflds(numrev.string);
+
+ if (delrev.code == 0) { /* -o rev or -o branch */
+ if (length & 1)
+ temp=searchcutpt(target->num,length+1,gendeltas);
+ else if (flag) {
+ error("Revision %s doesn't exist.", numrev.string);
+ return 0;
+ }
+ else
+ temp = searchcutpt(numrev.string, length, gendeltas);
+ cuttail = target->next;
+ if ( branchpoint(temp, cuttail) ) {
+ cuttail = nil;
+ return 0;
+ }
+ delstrt = temp; /* first revision to be removed */
+ return 1;
+ }
+
+ if (length & 1) { /* invalid branch after -o */
+ error("invalid branch range %s after -o", numrev.string);
+ return 0;
+ }
+
+ if (delrev.code == 1) { /* -o -rev */
+ if ( length > 2 ) {
+ temp = searchcutpt( target->num, length-1, gendeltas);
+ cuttail = target->next;
+ }
+ else {
+ temp = searchcutpt(target->num, length, gendeltas);
+ cuttail = target;
+ while( cuttail && ! cmpnumfld(target->num,cuttail->num,1) )
+ cuttail = cuttail->next;
+ }
+ if ( branchpoint(temp, cuttail) ){
+ cuttail = nil;
+ return 0;
+ }
+ delstrt = temp;
+ return 1;
+ }
+
+ if (delrev.code == 2) { /* -o rev- */
+ if ( length == 2 ) {
+ temp = searchcutpt(target->num, 1,gendeltas);
+ if ( flag)
+ cuttail = target;
+ else
+ cuttail = target->next;
+ }
+ else {
+ if ( flag){
+ cuthead = target;
+ if ( !(temp = target->next) ) return 0;
+ }
+ else
+ temp = searchcutpt(target->num, length, gendeltas);
+ getbranchno(temp->num, &numrev); /* get branch number */
+ target = genrevs(numrev.string, (char*)nil, (char*)nil, (char*)nil, &gendeltas);
+ }
+ if ( branchpoint( temp, cuttail ) ) {
+ cuttail = nil;
+ return 0;
+ }
+ delstrt = temp;
+ return 1;
+ }
+
+ /* -o rev1-rev2 */
+ if (!expandsym(delrev.end, &numrev)) return 0;
+ if (
+ length != countnumflds(numrev.string)
+ || length>2 && compartial(numrev.string, target->num, length-1)
+ ) {
+ error("invalid revision range %s-%s", target->num, numrev.string);
+ return 0;
+ }
+
+ target2 = genrevs(numrev.string,(char*)nil,(char*)nil,(char*)nil,&gendeltas);
+ if ( ! target2 ) return 0;
+
+ if ( length > 2) { /* delete revisions on branches */
+ if ( cmpnum(target->num, target2->num) > 0) {
+ if (cmpnum(target2->num, numrev.string))
+ flag = true;
+ else
+ flag = false;
+ temp = target;
+ target = target2;
+ target2 = temp;
+ }
+ if ( flag ) {
+ if ( ! cmpnum(target->num, target2->num) ) {
+ error("Revisions %s-%s don't exist.", delrev.strt,delrev.end);
+ return 0;
+ }
+ cuthead = target;
+ temp = target->next;
+ }
+ else
+ temp = searchcutpt(target->num, length, gendeltas);
+ cuttail = target2->next;
+ }
+ else { /* delete revisions on trunk */
+ if ( cmpnum( target->num, target2->num) < 0 ) {
+ temp = target;
+ target = target2;
+ target2 = temp;
+ }
+ else
+ if (cmpnum(target2->num, numrev.string))
+ flag = true;
+ else
+ flag = false;
+ if ( flag ) {
+ if ( ! cmpnum(target->num, target2->num) ) {
+ error("Revisions %s-%s don't exist.", delrev.strt, delrev.end);
+ return 0;
+ }
+ cuttail = target2;
+ }
+ else
+ cuttail = target2->next;
+ temp = searchcutpt(target->num, length, gendeltas);
+ }
+ if ( branchpoint(temp, cuttail) ) {
+ cuttail = nil;
+ return 0;
+ }
+ delstrt = temp;
+ return 1;
+}
+
+
+
+ static void
+doassoc()
+/* Function: add or delete(if revno is nil) association */
+/* which is stored in assoclst */
+
+{
+ char const *p;
+ struct Symrev const *curassoc;
+ struct assoc * pre, * pt;
+
+ /* add new associations */
+ curassoc = assoclst;
+ while( curassoc ) {
+ if ( curassoc->revno == nil ) { /* delete symbol */
+ pre = pt = Symbols;
+ while( pt && strcmp(pt->symbol,curassoc->ssymbol) ) {
+ pre = pt;
+ pt = pt->nextassoc;
+ }
+ if ( pt )
+ if ( pre == pt )
+ Symbols = pt->nextassoc;
+ else
+ pre->nextassoc = pt->nextassoc;
+ else
+ warn("can't delete nonexisting symbol %s",curassoc->ssymbol);
+ }
+ else {
+ if (curassoc->revno[0]) {
+ p = 0;
+ if (expandsym(curassoc->revno, &numrev))
+ p = fstr_save(numrev.string);
+ } else if (!(p = tiprev()))
+ error("no latest revision to associate with symbol %s",
+ curassoc->ssymbol
+ );
+ if (p)
+ VOID addsymbol(p, curassoc->ssymbol, curassoc->override);
+ }
+ curassoc = curassoc->nextsym;
+ }
+
+}
+
+
+
+ static void
+dolocks()
+/* Function: remove lock for caller or first lock if unlockcaller is set;
+ * remove locks which are stored in rmvlocklst,
+ * add new locks which are stored in newlocklst,
+ * add lock for Dbranch or Head if lockhead is set.
+ */
+{
+ struct Lockrev const *lockpt;
+ struct hshentry *target;
+
+ if (unlockcaller) { /* find lock for caller */
+ if ( Head ) {
+ if (Locks) {
+ switch (findlock(true, &target)) {
+ case 0:
+ breaklock(Locks->delta); /* remove most recent lock */
+ break;
+ case 1:
+ diagnose("%s unlocked\n",target->num);
+ break;
+ }
+ } else {
+ warn("No locks are set.");
+ }
+ } else {
+ warn("can't unlock an empty tree");
+ }
+ }
+
+ /* remove locks which are stored in rmvlocklst */
+ lockpt = rmvlocklst;
+ while( lockpt ) {
+ if (expandsym(lockpt->revno, &numrev)) {
+ target = genrevs(numrev.string, (char *)nil, (char *)nil, (char *)nil, &gendeltas);
+ if ( target )
+ if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
+ error("can't unlock nonexisting revision %s",lockpt->revno);
+ else
+ breaklock(target);
+ /* breaklock does its own diagnose */
+ }
+ lockpt = lockpt->nextrev;
+ }
+
+ /* add new locks which stored in newlocklst */
+ lockpt = newlocklst;
+ while( lockpt ) {
+ setlock(lockpt->revno);
+ lockpt = lockpt->nextrev;
+ }
+
+ if (lockhead) { /* lock default branch or head */
+ if (Dbranch) {
+ setlock(Dbranch);
+ } else if (Head) {
+ if (0 <= addlock(Head))
+ diagnose("%s locked\n",Head->num);
+ } else {
+ warn("can't lock an empty tree");
+ }
+ }
+
+}
+
+
+
+ static void
+setlock(rev)
+ char const *rev;
+/* Function: Given a revision or branch number, finds the corresponding
+ * delta and locks it for caller.
+ */
+{
+ struct hshentry *target;
+
+ if (expandsym(rev, &numrev)) {
+ target = genrevs(numrev.string, (char*)nil, (char*)nil,
+ (char*)nil, &gendeltas);
+ if ( target )
+ if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
+ error("can't lock nonexisting revision %s", numrev.string);
+ else
+ if (0 <= addlock(target))
+ diagnose("%s locked\n", target->num);
+ }
+}
+
+
+ static void
+domessages()
+{
+ struct hshentry *target;
+ struct Message *p;
+
+ for (p = messagelst; p; p = p->nextmessage)
+ if (
+ expandsym(p->revno, &numrev) &&
+ (target = genrevs(
+ numrev.string, (char*)0, (char*)0, (char*)0, &gendeltas
+ ))
+ )
+ target->log = p->message;
+}
+
+
+ static void
+rcs_setstate(rev,status)
+ char const *rev, *status;
+/* Function: Given a revision or branch number, finds the corresponding delta
+ * and sets its state to status.
+ */
+{
+ struct hshentry *target;
+
+ if (expandsym(rev, &numrev)) {
+ target = genrevs(numrev.string, (char*)nil, (char*)nil,
+ (char*)nil, &gendeltas);
+ if ( target )
+ if (!(countnumflds(numrev.string)&1) && cmpnum(target->num,numrev.string))
+ error("can't set state of nonexisting revision %s to %s",
+ numrev.string, status);
+ else
+ target->state = status;
+ }
+}
+
+
+
+
+
+ static int
+buildeltatext(deltas)
+ struct hshentries const *deltas;
+/* Function: put the delta text on frewrite and make necessary */
+/* change to delta text */
+{
+ register FILE *fcut; /* temporary file to rebuild delta tree */
+ char const *cutfilename, *diffilename;
+
+ cutfilename = nil;
+ cuttail->selector = false;
+ scanlogtext(deltas->first, false);
+ if ( cuthead ) {
+ cutfilename = maketemp(3);
+ if (!(fcut = fopen(cutfilename, FOPEN_W_WORK))) {
+ efaterror(cutfilename);
+ }
+
+ while (deltas->first != cuthead) {
+ deltas = deltas->rest;
+ scanlogtext(deltas->first, true);
+ }
+
+ snapshotedit(fcut);
+ Ofclose(fcut);
+ }
+
+ while (deltas->first != cuttail)
+ scanlogtext((deltas = deltas->rest)->first, true);
+ finishedit((struct hshentry *)nil, (FILE*)0, true);
+ Ozclose(&fcopy);
+
+ if ( cuthead ) {
+ diffilename = maketemp(0);
+ switch (run((char*)nil,diffilename,
+ DIFF DIFF_FLAGS, cutfilename, resultfile, (char*)nil
+ )) {
+ case DIFF_FAILURE: case DIFF_SUCCESS: break;
+ default: faterror ("diff failed");
+ }
+ return putdtext(cuttail->num,cuttail->log,diffilename,frewrite,true);
+ } else
+ return putdtext(cuttail->num,cuttail->log,resultfile,frewrite,false);
+}
+
+
+
+ static void
+buildtree()
+/* Function: actually removes revisions whose selector field */
+/* is false, and rebuilds the linkage of deltas. */
+/* asks for reconfirmation if deleting last revision*/
+{
+ struct hshentry * Delta;
+ struct branchhead *pt, *pre;
+
+ if ( cuthead )
+ if ( cuthead->next == delstrt )
+ cuthead->next = cuttail;
+ else {
+ pre = pt = cuthead->branches;
+ while( pt && pt->hsh != delstrt ) {
+ pre = pt;
+ pt = pt->nextbranch;
+ }
+ if ( cuttail )
+ pt->hsh = cuttail;
+ else if ( pt == pre )
+ cuthead->branches = pt->nextbranch;
+ else
+ pre->nextbranch = pt->nextbranch;
+ }
+ else {
+ if ( cuttail == nil && !quietflag) {
+ if (!yesorno(false, "Do you really want to delete all revisions? [ny](n): ")) {
+ error("No revision deleted");
+ Delta = delstrt;
+ while( Delta) {
+ Delta->selector = true;
+ Delta = Delta->next;
+ }
+ return;
+ }
+ }
+ Head = cuttail;
+ }
+ return;
+}
+
+#if lint
+/* This lets us lint everything all at once. */
+
+char const cmdid[] = "";
+
+#define go(p,e) {int p P((int,char**)); void e P((void)); if(*argv)return p(argc,argv);if(*argv[1])e();}
+
+ int
+main(argc, argv)
+ int argc;
+ char **argv;
+{
+ go(ciId, ciExit);
+ go(coId, coExit);
+ go(identId, identExit);
+ go(mergeId, mergeExit);
+ go(rcsId, exiterr);
+ go(rcscleanId, rcscleanExit);
+ go(rcsdiffId, rdiffExit);
+ go(rcsmergeId, rmergeExit);
+ go(rlogId, rlogExit);
+ return 0;
+}
+#endif
diff --git a/gnu/usr.bin/rcs/rcs/rcsfile.5 b/gnu/usr.bin/rcs/rcs/rcsfile.5
new file mode 100644
index 0000000..d0dbbb8
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcs/rcsfile.5
@@ -0,0 +1,224 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+..
+.Id $Id: rcsfile.5,v 5.1 1991/08/19 03:13:55 eggert Exp $
+.ds r \s-1RCS\s0
+.if n .ds - \%--
+.if t .ds - \(em
+.TH RCSFILE 5 \*(Dt GNU
+.SH NAME
+rcsfile \- format of RCS file
+.SH DESCRIPTION
+An \*r file's
+contents are described by the grammar
+below.
+.PP
+The text is free format: space, backspace, tab, newline, vertical
+tab, form feed, and carriage return (collectively,
+.IR "white space")
+have no significance except in strings.
+However, an \*r file must end in a newline character.
+.PP
+Strings are enclosed by
+.BR @ .
+If a string contains a
+.BR @ ,
+it must be doubled;
+otherwise, strings may contain arbitrary binary data.
+.PP
+The meta syntax uses the following conventions: `|' (bar) separates
+alternatives; `{' and `}' enclose optional phrases; `{' and `}*' enclose
+phrases that may be repeated zero or more times;
+`{' and '}+' enclose phrases that must appear at least once and may be
+repeated;
+Terminal symbols are in
+.BR boldface ;
+nonterminal symbols are in
+.IR italics .
+.LP
+.nr x \w'\f3branches\fP'
+.nr y \w'{ \f3comment\fP'
+.if \nx<\ny .nr x \ny
+.nr y \w'\f3{ branch\fP'
+.if \nx<\ny .nr x \ny
+.ta \w'\f2deltatext\fP 'u +\w'::= 'u +\nxu+\w' 'u
+.fc ~
+.nf
+\f2rcstext\fP ::= \f2admin\fP {\f2delta\fP}* \f2desc\fP {\f2deltatext\fP}*
+.LP
+\f2admin\fP ::= \f3head\fP {\f2num\fP}\f3;\fP
+ { \f3branch\fP {\f2num\fP}\f3;\fP }
+ \f3access\fP {\f2id\fP}*\f3;\fP
+ \f3symbols\fP {\f2id\fP \f3:\fP \f2num\fP}*\f3;\fP
+ \f3locks\fP {\f2id\fP \f3:\fP \f2num\fP}*\f3;\fP {\f3strict ;\fP}
+ { \f3comment\fP {\f2string\fP}\f3;\fP }
+ { \f3expand\fP {\f2string\fP}\f3;\fP }
+ { \f2newphrase\fP }*
+.LP
+\f2delta\fP ::= \f2num\fP
+ \f3date\fP \f2num\fP\f3;\fP
+ \f3author\fP \f2id\fP\f3;\fP
+ \f3state\fP {\f2id\fP}\f3;\fP
+ \f3branches\fP {\f2num\fP}*\f3;\fP
+ \f3next\fP {\f2num\fP}\f3;\fP
+ { \f2newphrase\fP }*
+.LP
+\f2desc\fP ::= \f3desc\fP \f2string\fP
+.LP
+\f2deltatext\fP ::= \f2num\fP
+ \f3log\fP \f2string\fP
+ { \f2newphrase\fP }*
+ \f3text\fP \f2string\fP
+.LP
+\f2num\fP ::= {\f2digit\fP{\f3.\fP}}+
+.LP
+\f2digit\fP ::= \f30\fP | \f31\fP | .\|.\|. | \f39\fP
+.LP
+\f2id\fP ::= \f2letter\fP{\f2idchar\fP}*
+.LP
+\f2letter\fP ::= any letter
+.LP
+\f2idchar\fP ::= any visible graphic character except \f2special\fP
+.LP
+\f2special\fP ::= \f3$\fP | \f3,\fP | \f3.\fP | \f3:\fP | \f3;\fP | \f3@\fP
+.LP
+\f2string\fP ::= \f3@\fP{any character, with \f3@\fP doubled}*\f3@\fP
+.LP
+\f2newphrase\fP ::= \f2id\fP \f2word\fP* \f3;\fP
+.LP
+\f2word\fP ::= \f2id\fP | \f2num\fP | \f2string\fP | \f3:\fP
+.fi
+.PP
+Identifiers are case sensitive. Keywords are in lower case only.
+The sets of keywords and identifiers may overlap.
+In most environments RCS uses the ISO 8859/1 encoding:
+letters are octal codes 101\-132, 141\-172, 300\-326, 330\-366 and 370-377,
+visible graphic characters are codes 041\-176 and 240\-377,
+and white space characters are codes 010\-015 and 040.
+.PP
+The
+.I newphrase
+productions in the grammar are reserved for future extensions
+to the format of \*r files.
+No
+.I newphrase
+will begin with any keyword already in use.
+.PP
+The
+.I delta
+nodes form a tree. All nodes whose numbers
+consist of a single pair
+(e.g., 2.3, 2.1, 1.3, etc.)
+are on the trunk, and are linked through the
+.B next
+field in order of decreasing numbers.
+The
+.B head
+field in the
+.I admin
+node points to the head of that sequence (i.e., contains
+the highest pair).
+The
+.B branch
+node in the admin node indicates the default
+branch (or revision) for most \*r operations.
+If empty, the default
+branch is the highest branch on the trunk.
+.PP
+All
+.I delta
+nodes whose numbers consist of
+.RI 2 n
+fields
+.RI ( n >=2)
+(e.g., 3.1.1.1, 2.1.2.2, etc.)
+are linked as follows.
+All nodes whose first
+.RI 2 n \-1
+number fields are identical are linked through the
+.B next
+field in order of increasing numbers.
+For each such sequence,
+the
+.I delta
+node whose number is identical to the first
+.RI 2 n \-2
+number fields of the deltas on that sequence is called the branchpoint.
+The
+.B branches
+field of a node contains a list of the
+numbers of the first nodes of all sequences for which it is a branchpoint.
+This list is ordered in increasing numbers.
+.LP
+.nf
+.vs 12
+.ne 38
+Example:
+.if t .in +0.5i
+.cs 1 20
+.eo
+
+ Head
+ |
+ |
+ v / \
+ --------- / \
+ / \ / \ | | / \ / \
+ / \ / \ | 2.1 | / \ / \
+ / \ / \ | | / \ / \
+/1.2.1.3\ /1.3.1.1\ | | /1.2.2.2\ /1.2.2.1.1.1\
+--------- --------- --------- --------- -------------
+ ^ ^ | ^ ^
+ | | | | |
+ | | v | |
+ / \ | --------- / \ |
+ / \ | \ 1.3 / / \ |
+ / \ ---------\ / / \-----------
+/1.2.1.1\ \ / /1.2.2.1\
+--------- \ / ---------
+ ^ | ^
+ | | |
+ | v |
+ | --------- |
+ | \ 1.2 / |
+ ----------------------\ /---------
+ \ /
+ \ /
+ |
+ |
+ v
+ ---------
+ \ 1.1 /
+ \ /
+ \ /
+ \ /
+
+.ec
+.if t .in
+.cs 1
+.ce
+Fig. 1: A revision tree
+.vs
+.fi
+.PP
+.SH IDENTIFICATION
+.de VL
+\\$2
+..
+Author: Walter F. Tichy,
+Purdue University, West Lafayette, IN, 47907.
+.br
+Revision Number: \*(Rv; Release Date: \*(Dt.
+.br
+Copyright \(co 1982, 1988, 1989 by Walter F. Tichy.
+.br
+Copyright \(co 1990, 1991 by Paul Eggert.
+.SH SEE ALSO
+ci(1), co(1), ident(1), rcs(1), rcsdiff(1), rcsmerge(1), rlog(1),
+.br
+Walter F. Tichy,
+\*r\*-A System for Version Control,
+.I "Software\*-Practice & Experience"
+.BR 15 ,
+7 (July 1985), 637-654.
diff --git a/gnu/usr.bin/rcs/rcs/rcsintro.1 b/gnu/usr.bin/rcs/rcs/rcsintro.1
new file mode 100644
index 0000000..a76caa0
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcs/rcsintro.1
@@ -0,0 +1,292 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+..
+.Id $Id: rcsintro.1,v 5.1 1991/04/21 12:00:46 eggert Exp $
+.ds r \&\s-1RCS\s0
+.if n .ds - \%--
+.if t .ds - \(em
+.am SS
+.LP
+..
+.TH RCSINTRO 1 \*(Dt GNU
+.SH NAME
+rcsintro \- introduction to RCS commands
+.SH DESCRIPTION
+The Revision Control System (\*r) manages multiple revisions of files.
+\*r automates the storing, retrieval, logging, identification, and merging
+of revisions. \*r is useful for text that is revised frequently, for example
+programs, documentation, graphics, papers, and form letters.
+.PP
+The basic user interface is extremely simple. The novice only needs
+to learn two commands:
+.BR ci (1)
+and
+.BR co (1).
+.BR ci ,
+short for \*(lqcheck in\*(rq, deposits the contents of a
+file into an archival file called an \*r file. An \*r file
+contains all revisions of a particular file.
+.BR co ,
+short for \*(lqcheck out\*(rq, retrieves revisions from an \*r file.
+.SS "Functions of \*r"
+.IP \(bu
+Store and retrieve multiple revisions of text. \*r saves all old
+revisions in a space efficient way.
+Changes no longer destroy the original, because the
+previous revisions remain accessible. Revisions can be retrieved according to
+ranges of revision numbers, symbolic names, dates, authors, and
+states.
+.IP \(bu
+Maintain a complete history of changes.
+\*r logs all changes automatically.
+Besides the text of each revision, \*r stores the author, the date and time of
+check-in, and a log message summarizing the change.
+The logging makes it easy to find out
+what happened to a module, without having to compare
+source listings or having to track down colleagues.
+.IP \(bu
+Resolve access conflicts. When two or more programmers wish to
+modify the same revision, \*r alerts the programmers and prevents one
+modification from corrupting the other.
+.IP \(bu
+Maintain a tree of revisions. \*r can maintain separate lines of development
+for each module. It stores a tree structure that represents the
+ancestral relationships among revisions.
+.IP \(bu
+Merge revisions and resolve conflicts.
+Two separate lines of development of a module can be coalesced by merging.
+If the revisions to be merged affect the same sections of code, \*r alerts the
+user about the overlapping changes.
+.IP \(bu
+Control releases and configurations.
+Revisions can be assigned symbolic names
+and marked as released, stable, experimental, etc.
+With these facilities, configurations of modules can be
+described simply and directly.
+.IP \(bu
+Automatically identify each revision with name, revision number,
+creation time, author, etc.
+The identification is like a stamp that can be embedded at an appropriate place
+in the text of a revision.
+The identification makes it simple to determine which
+revisions of which modules make up a given configuration.
+.IP \(bu
+Minimize secondary storage. \*r needs little extra space for
+the revisions (only the differences). If intermediate revisions are
+deleted, the corresponding deltas are compressed accordingly.
+.SS "Getting Started with \*r"
+Suppose you have a file
+.B f.c
+that you wish to put under control of \*r.
+If you have not already done so, make an \*r directory with the command
+.IP
+.B "mkdir RCS"
+.LP
+Then invoke the check-in command
+.IP
+.B "ci f.c"
+.LP
+This command creates an \*r file in the
+.B RCS
+directory,
+stores
+.B f.c
+into it as revision 1.1, and
+deletes
+.BR f.c .
+It also asks you for a description. The description
+should be a synopsis of the contents of the file. All later check-in
+commands will ask you for a log entry, which should summarize the
+changes that you made.
+.PP
+Files in the \*r directory are called \*r files;
+the others are called working files.
+To get back the working file
+.B f.c
+in the previous example, use the check-out
+command
+.IP
+.B "co f.c"
+.LP
+This command extracts the latest revision from the \*r file
+and writes
+it into
+.BR f.c .
+If you want to edit
+.BR f.c ,
+you must lock it as you check it out with the command
+.IP
+.B "co \-l f.c"
+.LP
+You can now edit
+.BR f.c .
+.PP
+Suppose after some editing you want to know what changes that you have made.
+The command
+.IP
+.B "rcsdiff f.c"
+.LP
+tells you the difference between the most recently checked-in version
+and the working file.
+You can check the file back in by invoking
+.IP
+.B "ci f.c"
+.LP
+This increments the revision number properly.
+.PP
+If
+.B ci
+complains with the message
+.IP
+.BI "ci error: no lock set by " "your name"
+.LP
+then you have tried to check in a file even though you did not
+lock it when you checked it out.
+Of course, it is too late now to do the check-out with locking, because
+another check-out would
+overwrite your modifications. Instead, invoke
+.IP
+.B "rcs \-l f.c"
+.LP
+This command will lock the latest revision for you, unless somebody
+else got ahead of you already. In this case, you'll have to negotiate with
+that person.
+.PP
+Locking assures that you, and only you, can check in the next update, and
+avoids nasty problems if several people work on the same file.
+Even if a revision is locked, it can still be checked out for
+reading, compiling, etc. All that locking
+prevents is a
+.I "check-in"
+by anybody but the locker.
+.PP
+If your \*r file is private, i.e., if you are the only person who is going
+to deposit revisions into it, strict locking is not needed and you
+can turn it off.
+If strict locking is turned off,
+the owner of the \*r file need not have a lock for check-in; all others
+still do. Turning strict locking off and on is done with the commands
+.IP
+.BR "rcs \-U f.c" " and " "rcs \-L f.c"
+.LP
+If you don't want to clutter your working directory with \*r files, create
+a subdirectory called
+.B RCS
+in your working directory, and move all your \*r
+files there. \*r commands will look first into that directory to find
+needed files. All the commands discussed above will still work, without any
+modification.
+(Actually, pairs of \*r and working files can be specified in three ways:
+(a) both are given, (b) only the working file is given, (c) only the
+\*r file is given. Both \*r and working files may have arbitrary path prefixes;
+\*r commands pair them up intelligently.)
+.PP
+To avoid the deletion of the working file during check-in (in case you want to
+continue editing or compiling), invoke
+.IP
+.BR "ci \-l f.c" " or " "ci \-u f.c"
+.LP
+These commands check in
+.B f.c
+as usual, but perform an implicit
+check-out. The first form also locks the checked in revision, the second one
+doesn't. Thus, these options save you one check-out operation.
+The first form is useful if you want to continue editing,
+the second one if you just want to read the file.
+Both update the identification markers in your working file (see below).
+.PP
+You can give
+.B ci
+the number you want assigned to a checked in
+revision. Assume all your revisions were numbered 1.1, 1.2, 1.3, etc.,
+and you would like to start release 2.
+The command
+.IP
+.BR "ci \-r2 f.c" " or " "ci \-r2.1 f.c"
+.LP
+assigns the number 2.1 to the new revision.
+From then on,
+.B ci
+will number the subsequent revisions
+with 2.2, 2.3, etc. The corresponding
+.B co
+commands
+.IP
+.BR "co \-r2 f.c" " and " "co \-r2.1 f.c"
+.PP
+retrieve the latest revision numbered
+.RI 2. x
+and the revision 2.1,
+respectively.
+.B co
+without a revision number selects
+the latest revision on the
+.IR trunk ,
+i.e. the highest
+revision with a number consisting of two fields. Numbers with more than two
+fields are needed for branches.
+For example, to start a branch at revision 1.3, invoke
+.IP
+.B "ci \-r1.3.1 f.c"
+.LP
+This command starts a branch numbered 1 at revision 1.3, and assigns
+the number 1.3.1.1 to the new revision. For more information about
+branches, see
+.BR rcsfile (5).
+.SS "Automatic Identification"
+\*r can put special strings for identification into your source and object
+code. To obtain such identification, place the marker
+.IP
+.B "$\&Id$"
+.LP
+into your text, for instance inside a comment.
+\*r will replace this marker with a string of the form
+.IP
+.BI $\&Id: " filename revision date time author state " $
+.LP
+With such a marker on the first page of each module, you can
+always see with which revision you are working.
+\*r keeps the markers up to date automatically.
+To propagate the markers into your object code, simply put
+them into literal character strings. In C, this is done as follows:
+.IP
+.ft 3
+static char rcsid[] = \&"$\&Id$\&";
+.ft
+.LP
+The command
+.B ident
+extracts such markers from any file, even object code
+and dumps.
+Thus,
+.B ident
+lets you find out
+which revisions of which modules were used in a given program.
+.PP
+You may also find it useful to put the marker
+.B $\&Log$
+into your text, inside a comment. This marker accumulates
+the log messages that are requested during check-in.
+Thus, you can maintain the complete history of your file directly inside it.
+There are several additional identification markers; see
+.BR co (1)
+for
+details.
+.SH IDENTIFICATION
+Author: Walter F. Tichy.
+.br
+Revision Number: \*(Rv; Release Date: \*(Dt.
+.br
+Copyright \(co 1982, 1988, 1989 by Walter F. Tichy.
+.br
+Copyright \(co 1990, 1991 by Paul Eggert.
+.SH "SEE ALSO"
+ci(1), co(1), ident(1), rcs(1), rcsdiff(1), rcsintro(1), rcsmerge(1), rlog(1)
+.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/rcsclean/Makefile b/gnu/usr.bin/rcs/rcsclean/Makefile
new file mode 100644
index 0000000..fc0c626
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcsclean/Makefile
@@ -0,0 +1,7 @@
+PROG= rcsclean
+
+SRCS= rcsclean.c
+LDADD= -L${.CURDIR}/../lib/obj -lrcs
+CFLAGS+= -I${.CURDIR}/../lib
+
+.include <bsd.prog.mk>
diff --git a/gnu/usr.bin/rcs/rcsclean/rcsclean.1 b/gnu/usr.bin/rcs/rcsclean/rcsclean.1
new file mode 100644
index 0000000..07ed722
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcsclean/rcsclean.1
@@ -0,0 +1,177 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+..
+.Id $Id: rcsclean.1,v 1.8 1991/11/03 01:09:19 eggert Exp $
+.ds r \&\s-1RCS\s0
+.if n .ds - \%--
+.if t .ds - \(em
+.TH RCSCLEAN 1 \*(Dt GNU
+.SH NAME
+rcsclean \- clean up working files
+.SH SYNOPSIS
+.B rcsclean
+.RI [ options "] [ " file " .\|.\|. ]"
+.SH DESCRIPTION
+.B rcsclean
+removes working files that were checked out and never modified.
+For each
+.I file
+given,
+.B rcsclean
+compares the working file and a revision in the corresponding
+\*r file. If it finds a difference, it does nothing.
+Otherwise, it first unlocks the revision if the
+.B \-u
+option is given,
+and then removes the working file
+unless the working file is writable and the revision is locked.
+It logs its actions by outputting the corresponding
+.B "rcs \-u"
+and
+.B "rm \-f"
+commands on the standard output.
+.PP
+If no
+.I file
+is given, all working files in the current directory are cleaned.
+Pathnames matching an \*r suffix denote \*r files;
+all others denote working files.
+Names are paired as explained in
+.BR ci (1).
+.PP
+The number of the revision to which the working file is compared
+may be attached to any of the options
+.BR \-n ,
+.BR \-q ,
+.BR \-r ,
+or
+.BR \-u .
+If no revision number is specified, then if the
+.B \-u
+option is given and the caller has one revision locked,
+.B rcsclean
+uses that revision; otherwise
+.B rcsclean
+uses the latest revision on the default branch, normally the root.
+.PP
+.B rcsclean
+is useful for
+.B clean
+targets in Makefiles.
+See also
+.BR rcsdiff (1),
+which prints out the differences,
+and
+.BR ci (1),
+which
+normally asks whether to check in a file
+if it was not changed.
+.SH OPTIONS
+.TP
+.BI \-k subst
+Use
+.I subst
+style keyword substitution when retrieving the revision for comparison.
+See
+.BR co (1)
+for details.
+.TP
+.BR \-n [\f2rev\fP]
+Do not actually remove any files or unlock any revisions.
+Using this option will tell you what
+.B rcsclean
+would do without actually doing it.
+.TP
+.BR \-q [\f2rev\fP]
+Do not log the actions taken on standard output.
+.TP
+.BR \-r [\f2rev\fP]
+This option has no effect other than specifying the revision for comparison.
+.TP
+.BR \-u [\f2rev\fP]
+Unlock the revision if it is locked and no difference is found.
+.TP
+.BI \-V n
+Emulate \*r version
+.IR n .
+See
+.BR co (1)
+for details.
+.TP
+.BI \-x "suffixes"
+Use
+.I suffixes
+to characterize \*r files.
+See
+.BR ci (1)
+for details.
+.SH EXAMPLES
+.LP
+.RS
+.ft 3
+rcsclean *.c *.h
+.ft
+.RE
+.LP
+removes all working files ending in
+.B .c
+or
+.B .h
+that were not changed
+since their checkout.
+.LP
+.RS
+.ft 3
+rcsclean
+.ft
+.RE
+.LP
+removes all working files in the current directory
+that were not changed since their checkout.
+.SH FILES
+.B rcsclean
+accesses files much as
+.BR ci (1)
+does.
+.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 ,
+and
+.BR \-x .
+.SH DIAGNOSTICS
+The exit status is zero if and only if all operations were successful.
+Missing working files and \*r files are silently ignored.
+.SH IDENTIFICATION
+Author: Walter F. Tichy.
+.br
+Revision Number: \*(Rv; Release Date: \*(Dt.
+.br
+Copyright \(co 1982, 1988, 1989 by Walter F. Tichy.
+.br
+Copyright \(co 1990, 1991 by Paul Eggert.
+.SH "SEE ALSO"
+ci(1), co(1), ident(1), rcs(1), rcsdiff(1), rcsintro(1), rcsmerge(1), rlog(1),
+rcsfile(5)
+.br
+Walter F. Tichy,
+\*r\*-A System for Version Control,
+.I "Software\*-Practice & Experience"
+.BR 15 ,
+7 (July 1985), 637-654.
+.SH BUGS
+At least one
+.I file
+must be given in older Unix versions that
+do not provide the needed directory scanning operations.
+.br
diff --git a/gnu/usr.bin/rcs/rcsclean/rcsclean.c b/gnu/usr.bin/rcs/rcsclean/rcsclean.c
new file mode 100644
index 0000000..ba24ab7
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcsclean/rcsclean.c
@@ -0,0 +1,297 @@
+/* rcsclean - clean up working files */
+
+/* Copyright 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+#include "rcsbase.h"
+
+#if has_dirent
+ static int get_directory P((char const*,char***));
+#endif
+
+static int unlock P((struct hshentry *));
+static void cleanup P((void));
+
+static RILE *workptr;
+static int exitstatus;
+
+mainProg(rcscleanId, "rcsclean", "$Id: rcsclean.c,v 5.1 1991/11/03 01:11:44 eggert Exp $")
+{
+ static char const usage[] =
+ "\nrcsclean: usage: rcsclean [-ksubst] [-{nqru}[rev]] [-Vn] [-xsuffixes] [file ...]";
+
+ static struct buf revision;
+
+ char *a, **newargv;
+ char const *rev, *p;
+ int changelock, expmode, perform, unlocked, unlockflag, waslocked;
+ struct hshentries *deltas;
+ struct hshentry *delta;
+ struct stat workstat;
+
+ setrid();
+
+ expmode = -1;
+ rev = nil;
+ suffixes = X_DEFAULT;
+ perform = true;
+ unlockflag = false;
+
+ argc = getRCSINIT(argc, argv, &newargv);
+ argv = newargv;
+ for (;;) {
+ if (--argc <= 0) {
+# if has_dirent
+ argc = get_directory(".", &newargv);
+ argv = newargv;
+ break;
+# else
+ faterror("no file names specified");
+# endif
+ }
+ a = *++argv;
+ if (*a++ != '-')
+ break;
+ switch (*a++) {
+ case 'k':
+ if (0 <= expmode)
+ redefined('k');
+ if ((expmode = str2expmode(a)) < 0)
+ goto unknown;
+ break;
+
+ case 'n':
+ perform = false;
+ goto handle_revision;
+
+ case 'q':
+ quietflag = true;
+ /* fall into */
+ case 'r':
+ handle_revision:
+ if (*a) {
+ if (rev)
+ warn("redefinition of revision number");
+ rev = a;
+ }
+ break;
+
+ case 'u':
+ unlockflag = true;
+ goto handle_revision;
+
+ case 'V':
+ setRCSversion(*argv);
+ break;
+
+ case 'x':
+ suffixes = a;
+ break;
+
+ default:
+ unknown:
+ faterror("unknown option: %s%s", *argv, usage);
+ }
+ }
+
+ do {
+ ffree();
+
+ if (!(
+ 0 < pairfilenames(
+ argc, argv,
+ unlockflag&perform ? rcswriteopen : rcsreadopen,
+ true, true
+ ) &&
+ (workptr = Iopen(workfilename,FOPEN_R_WORK,&workstat))
+ ))
+ continue;
+
+ gettree();
+
+ p = 0;
+ if (rev) {
+ if (!fexpandsym(rev, &revision, workptr))
+ continue;
+ p = revision.string;
+ } else if (Head)
+ switch (unlockflag ? findlock(false,&delta) : 0) {
+ default:
+ continue;
+ case 0:
+ p = Dbranch ? Dbranch : "";
+ break;
+ case 1:
+ p = delta->num;
+ break;
+ }
+ delta = 0;
+ deltas = 0; /* Keep lint happy. */
+ if (p && !(delta = genrevs(p,(char*)0,(char*)0,(char*)0,&deltas)))
+ continue;
+
+ waslocked = delta && delta->lockedby;
+ locker_expansion = unlock(delta);
+ unlocked = locker_expansion & unlockflag;
+ changelock = unlocked & perform;
+ if (unlocked<waslocked && workstat.st_mode&(S_IWUSR|S_IWGRP|S_IWOTH))
+ continue;
+
+ if (!dorewrite(unlockflag, changelock))
+ continue;
+
+ if (0 <= expmode)
+ Expand = expmode;
+ else if (
+ waslocked &&
+ Expand == KEYVAL_EXPAND &&
+ WORKMODE(RCSstat.st_mode,true) == workstat.st_mode
+ )
+ Expand = KEYVALLOCK_EXPAND;
+
+ getdesc(false);
+
+ if (
+ !delta ? workstat.st_size!=0 :
+ 0 < rcsfcmp(
+ workptr, &workstat,
+ buildrevision(deltas, delta, (FILE*)0, false),
+ delta
+ )
+ )
+ continue;
+
+ if (quietflag < unlocked)
+ aprintf(stdout, "rcs -u%s %s\n", delta->num, RCSfilename);
+
+ if_advise_access(changelock && deltas->first != delta,
+ finptr, MADV_SEQUENTIAL
+ );
+ if (!donerewrite(changelock))
+ continue;
+
+ if (!quietflag)
+ aprintf(stdout, "rm -f %s\n", workfilename);
+ Izclose(&workptr);
+ if (perform && un_link(workfilename) != 0)
+ eerror(workfilename);
+
+ } while (cleanup(), ++argv, 0 < --argc);
+
+ tempunlink();
+ if (!quietflag)
+ Ofclose(stdout);
+ exitmain(exitstatus);
+}
+
+ static void
+cleanup()
+{
+ if (nerror) exitstatus = EXIT_FAILURE;
+ Izclose(&finptr);
+ Izclose(&workptr);
+ Ozclose(&fcopy);
+ Ozclose(&frewrite);
+ dirtempunlink();
+}
+
+#if lint
+# define exiterr rcscleanExit
+#endif
+ exiting void
+exiterr()
+{
+ dirtempunlink();
+ tempunlink();
+ _exit(EXIT_FAILURE);
+}
+
+ static int
+unlock(delta)
+ struct hshentry *delta;
+{
+ register struct lock **al, *l;
+
+ if (delta && delta->lockedby && strcmp(getcaller(),delta->lockedby)==0)
+ for (al = &Locks; (l = *al); al = &l->nextlock)
+ if (l->delta == delta) {
+ *al = l->nextlock;
+ delta->lockedby = 0;
+ return true;
+ }
+ return false;
+}
+
+#if has_dirent
+ static int
+get_directory(dirname, aargv)
+ char const *dirname;
+ char ***aargv;
+/*
+ * Put a vector of all DIRNAME's directory entries names into *AARGV.
+ * Ignore names of RCS files.
+ * Yield the number of entries found. Terminate the vector with 0.
+ * Allocate the storage for the vector and entry names.
+ * Do not sort the names. Do not include '.' and '..'.
+ */
+{
+ int i, entries = 0, entries_max = 64;
+ size_t chars = 0, chars_max = 1024;
+ size_t *offset = tnalloc(size_t, entries_max);
+ char *a = tnalloc(char, chars_max), **p;
+ DIR *d;
+ struct dirent *e;
+
+ if (!(d = opendir(dirname)))
+ efaterror(dirname);
+ while ((errno = 0, e = readdir(d))) {
+ char const *en = e->d_name;
+ size_t s = strlen(en) + 1;
+ if (en[0]=='.' && (!en[1] || en[1]=='.' && !en[2]))
+ continue;
+ if (rcssuffix(en))
+ continue;
+ while (chars_max < s + chars)
+ a = trealloc(char, a, chars_max<<=1);
+ if (entries == entries_max)
+ offset = trealloc(size_t, offset, entries_max<<=1);
+ offset[entries++] = chars;
+ VOID strcpy(a+chars, en);
+ chars += s;
+ }
+ if (errno || closedir(d) != 0)
+ efaterror(dirname);
+ if (chars)
+ a = trealloc(char, a, chars);
+ else
+ tfree(a);
+ *aargv = p = tnalloc(char*, entries+1);
+ for (i=0; i<entries; i++)
+ *p++ = a + offset[i];
+ *p = 0;
+ tfree(offset);
+ return entries;
+}
+#endif
diff --git a/gnu/usr.bin/rcs/rcsdiff/Makefile b/gnu/usr.bin/rcs/rcsdiff/Makefile
new file mode 100644
index 0000000..837c241
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcsdiff/Makefile
@@ -0,0 +1,7 @@
+PROG= rcsdiff
+
+SRCS= rcsdiff.c
+LDADD= -L${.CURDIR}/../lib/obj -lrcs
+CFLAGS+= -I${.CURDIR}/../lib
+
+.include <bsd.prog.mk>
diff --git a/gnu/usr.bin/rcs/rcsdiff/rcsdiff.1 b/gnu/usr.bin/rcs/rcsdiff/rcsdiff.1
new file mode 100644
index 0000000..b78bbdd
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcsdiff/rcsdiff.1
@@ -0,0 +1,152 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+..
+.Id $Id: rcsdiff.1,v 5.3 1991/04/21 12:00:46 eggert Exp $
+.ds r \&\s-1RCS\s0
+.if n .ds - \%--
+.if t .ds - \(em
+.TH RCSDIFF 1 \*(Dt GNU
+.SH NAME
+rcsdiff \- compare RCS revisions
+.SH SYNOPSIS
+.B rcsdiff
+[
+.BI \-k subst
+] [
+.B \-q
+] [
+.BI \-r rev1
+[
+.BI \-r rev2
+] ] [
+.BI \-V n
+] [
+.BI \-x suffixes
+] [
+.I "diff options"
+]
+.I "file .\|.\|."
+.SH DESCRIPTION
+.B rcsdiff
+runs
+.BR diff (1)
+to compare two revisions of each \*r file given.
+.PP
+Pathnames matching an \*r suffix denote \*r files;
+all others denote working files.
+Names are paired as explained in
+.BR ci (1).
+.PP
+The option
+.B \-q
+suppresses diagnostic output.
+Zero, one, or two revisions may be specified with
+.BR \-r .
+The option
+.BI \-k subst
+affects keyword substitution when extracting
+revisions, as described in
+.BR co (1);
+for example,
+.B "\-kk\ \-r1.1\ \-r1.2"
+ignores differences in keyword values when comparing revisions
+.B 1.1
+and
+.BR 1.2 .
+To avoid excess output from locker name substitution,
+.B \-kkvl
+is assumed if (1) at most one revision option is given,
+(2) no
+.B \-k
+option is given, (3)
+.B \-kkv
+is the default keyword substitution, and
+(4) the working file's mode would be produced by
+.BR "co\ \-l".
+See
+.BR co (1)
+for details
+about
+.B \-V
+and
+.BR \-x .
+Otherwise, all options of
+.BR diff (1)
+that apply to regular files are accepted, with the same meaning as for
+.BR diff .
+.PP
+If both
+.I rev1
+and
+.I rev2
+are omitted,
+.B rcsdiff
+compares the latest revision on the
+default branch (by default the trunk)
+with the contents of the corresponding working file. This is useful
+for determining what you changed since the last checkin.
+.PP
+If
+.I rev1
+is given, but
+.I rev2
+is omitted,
+.B rcsdiff
+compares revision
+.I rev1
+of the \*r file with
+the contents of the corresponding working file.
+.PP
+If both
+.I rev1
+and
+.I rev2
+are given,
+.B rcsdiff
+compares revisions
+.I rev1
+and
+.I rev2
+of the \*r file.
+.PP
+Both
+.I rev1
+and
+.I rev2
+may be given numerically or symbolically.
+.SH EXAMPLE
+The command
+.LP
+.B " rcsdiff f.c"
+.LP
+compares the latest revision on the default branch of the \*r file
+to the contents of the working file
+.BR f.c .
+.SH ENVIRONMENT
+.TP
+.B \s-1RCSINIT\s0
+options prepended to the argument list, separated by spaces.
+See
+.BR ci (1)
+for details.
+.SH DIAGNOSTICS
+Exit status is 0 for no differences during any comparison,
+1 for some differences, 2 for trouble.
+.SH IDENTIFICATION
+Author: Walter F. Tichy.
+.br
+Revision Number: \*(Rv; Release Date: \*(Dt.
+.br
+Copyright \(co 1982, 1988, 1989 by Walter F. Tichy.
+.br
+Copyright \(co 1990, 1991 by Paul Eggert.
+.SH "SEE ALSO"
+ci(1), co(1), diff(1), ident(1), rcs(1), rcsintro(1), rcsmerge(1), rlog(1)
+.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/rcsdiff/rcsdiff.c b/gnu/usr.bin/rcs/rcsdiff/rcsdiff.c
new file mode 100644
index 0000000..7155c8d
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcsdiff/rcsdiff.c
@@ -0,0 +1,422 @@
+/*
+ * RCS rcsdiff operation
+ */
+/*****************************************************************************
+ * generate difference between RCS revisions
+ *****************************************************************************
+ */
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+
+
+/* $Log: rcsdiff.c,v $
+ * Revision 5.10 1991/10/07 17:32:46 eggert
+ * Remove lint.
+ *
+ * Revision 5.9 1991/08/19 03:13:55 eggert
+ * Add RCSINIT, -r$. Tune.
+ *
+ * Revision 5.8 1991/04/21 11:58:21 eggert
+ * Add -x, RCSINIT, MS-DOS support.
+ *
+ * Revision 5.7 1990/12/13 06:54:07 eggert
+ * GNU diff 1.15 has -u.
+ *
+ * Revision 5.6 1990/11/01 05:03:39 eggert
+ * Remove unneeded setid check.
+ *
+ * Revision 5.5 1990/10/04 06:30:19 eggert
+ * Accumulate exit status across files.
+ *
+ * Revision 5.4 1990/09/27 01:31:43 eggert
+ * Yield 1, not EXIT_FAILURE, when diffs are found.
+ *
+ * Revision 5.3 1990/09/11 02:41:11 eggert
+ * Simplify -kkvl test.
+ *
+ * Revision 5.2 1990/09/04 17:07:19 eggert
+ * Diff's argv was too small by 1.
+ *
+ * Revision 5.1 1990/08/29 07:13:55 eggert
+ * Add -kkvl.
+ *
+ * Revision 5.0 1990/08/22 08:12:46 eggert
+ * Add -k, -V. Don't use access(). Add setuid support.
+ * Remove compile-time limits; use malloc instead.
+ * Don't pass arguments with leading '+' to diff; GNU DIFF treats them as options.
+ * Add GNU diff's flags. Make lock and temp files faster and safer.
+ * Ansify and Posixate.
+ *
+ * Revision 4.6 89/05/01 15:12:27 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.5 88/08/09 19:12:41 eggert
+ * Use execv(), not system(); yield exit status like diff(1)s; allow cc -R.
+ *
+ * Revision 4.4 87/12/18 11:37:46 narten
+ * changes Jay Lepreau made in the 4.3 BSD version, to add support for
+ * "-i", "-w", and "-t" flags and to permit flags to be bundled together,
+ * merged in.
+ *
+ * Revision 4.3 87/10/18 10:31:42 narten
+ * Updating version numbers. Changes relative to 1.1 actually
+ * relative to 4.1
+ *
+ * Revision 1.3 87/09/24 13:59:21 narten
+ * Sources now pass through lint (if you ignore printf/sprintf/fprintf
+ * warnings)
+ *
+ * Revision 1.2 87/03/27 14:22:15 jenkins
+ * Port to suns
+ *
+ * Revision 4.1 83/05/03 22:13:19 wft
+ * Added default branch, option -q, exit status like diff.
+ * Added fterror() to replace faterror().
+ *
+ * Revision 3.6 83/01/15 17:52:40 wft
+ * Expanded mainprogram to handle multiple RCS files.
+ *
+ * Revision 3.5 83/01/06 09:33:45 wft
+ * Fixed passing of -c (context) option to diff.
+ *
+ * Revision 3.4 82/12/24 15:28:38 wft
+ * Added call to catchsig().
+ *
+ * Revision 3.3 82/12/10 16:08:17 wft
+ * Corrected checking of return code from diff; improved error msgs.
+ *
+ * Revision 3.2 82/12/04 13:20:09 wft
+ * replaced getdelta() with gettree(). Changed diagnostics.
+ *
+ * Revision 3.1 82/11/28 19:25:04 wft
+ * Initial revision.
+ *
+ */
+#include "rcsbase.h"
+
+#if DIFF_L
+static char const *setup_label P((struct buf*,char const*,char const[datesize]));
+#endif
+static void cleanup P((void));
+
+static int exitstatus;
+static RILE *workptr;
+static struct stat workstat;
+
+mainProg(rcsdiffId, "rcsdiff", "$Id: rcsdiff.c,v 5.10 1991/10/07 17:32:46 eggert Exp $")
+{
+ static char const cmdusage[] =
+ "\nrcsdiff usage: rcsdiff [-q] [-rrev1 [-rrev2]] [-Vn] [diff options] file ...";
+
+ int revnums; /* counter for revision numbers given */
+ char const *rev1, *rev2; /* revision numbers from command line */
+ char const *xrev1, *xrev2; /* expanded revision numbers */
+ char const *expandarg, *lexpandarg, *versionarg;
+#if DIFF_L
+ static struct buf labelbuf[2];
+ int file_labels;
+ char const **diff_label1, **diff_label2;
+ char date2[datesize];
+#endif
+ char const *cov[9];
+ char const **diffv, **diffp; /* argv for subsidiary diff */
+ char const **pp, *p, *diffvstr;
+ struct buf commarg;
+ struct buf numericrev; /* expanded revision number */
+ struct hshentries *gendeltas; /* deltas to be generated */
+ struct hshentry * target;
+ char *a, *dcp, **newargv;
+ register c;
+
+ exitstatus = DIFF_SUCCESS;
+
+ bufautobegin(&commarg);
+ bufautobegin(&numericrev);
+ revnums = 0;
+ rev1 = rev2 = xrev2 = nil;
+#if DIFF_L
+ file_labels = 0;
+#endif
+ expandarg = versionarg = 0;
+ suffixes = X_DEFAULT;
+
+ /* Room for args + 2 i/o [+ 2 labels] + 1 file + 1 trailing null. */
+ diffp = diffv = tnalloc(char const*, argc + 4 + 2*DIFF_L);
+ *diffp++ = nil;
+ *diffp++ = nil;
+ *diffp++ = DIFF;
+
+ argc = getRCSINIT(argc, argv, &newargv);
+ argv = newargv;
+ while (a = *++argv, 0<--argc && *a++=='-') {
+ dcp = a;
+ while (c = *a++) switch (c) {
+ case 'r':
+ switch (++revnums) {
+ case 1: rev1=a; break;
+ case 2: rev2=a; break;
+ default: faterror("too many revision numbers");
+ }
+ goto option_handled;
+#if DIFF_L
+ case 'L':
+ if (++file_labels == 2)
+ faterror("too many -L options");
+ /* fall into */
+#endif
+ case 'C': case 'D': case 'F': case 'I':
+ *dcp++ = c;
+ if (*a)
+ do *dcp++ = *a++;
+ while (*a);
+ else {
+ if (!--argc)
+ faterror("-%c needs following argument%s",
+ c, cmdusage
+ );
+ *diffp++ = *argv++;
+ }
+ break;
+ case 'B': case 'H': case 'T':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ case 'h': case 'i': case 'n': case 'p':
+ case 't': case 'u': case 'w':
+ *dcp++ = c;
+ break;
+ case 'q':
+ quietflag=true;
+ break;
+ case 'x':
+ suffixes = *argv + 2;
+ goto option_handled;
+ case 'V':
+ versionarg = *argv;
+ setRCSversion(versionarg);
+ goto option_handled;
+ case 'k':
+ expandarg = *argv;
+ if (0 <= str2expmode(expandarg+2))
+ goto option_handled;
+ /* fall into */
+ default:
+ faterror("unknown option: %s%s", *argv, cmdusage);
+ };
+ option_handled:
+ if (dcp != *argv+1) {
+ *dcp = 0;
+ *diffp++ = *argv;
+ }
+ } /* end of option processing */
+
+ if (argc<1) faterror("no input file%s", cmdusage);
+
+ for (pp = diffv+3, c = 0; pp<diffp; )
+ c += strlen(*pp++) + 1;
+ diffvstr = a = tnalloc(char, c + 1);
+ for (pp = diffv+3; pp<diffp; ) {
+ p = *pp++;
+ *a++ = ' ';
+ while ((*a = *p++))
+ a++;
+ }
+ *a = 0;
+
+#if DIFF_L
+ diff_label1 = diff_label2 = nil;
+ if (file_labels < 2) {
+ if (!file_labels)
+ diff_label1 = diffp++;
+ diff_label2 = diffp++;
+ }
+#endif
+ diffp[2] = nil;
+
+ cov[0] = 0;
+ cov[2] = CO;
+ cov[3] = "-q";
+
+ /* now handle all filenames */
+ do {
+ ffree();
+
+ if (pairfilenames(argc, argv, rcsreadopen, true, false) <= 0)
+ continue;
+ diagnose("===================================================================\nRCS file: %s\n",RCSfilename);
+ if (!rev2) {
+ /* Make sure work file is readable, and get its status. */
+ if (!(workptr = Iopen(workfilename,FOPEN_R_WORK,&workstat))) {
+ eerror(workfilename);
+ continue;
+ }
+ }
+
+
+ gettree(); /* reads in the delta tree */
+
+ if (Head==nil) {
+ error("no revisions present");
+ continue;
+ }
+ if (revnums==0 || !*rev1)
+ rev1 = Dbranch ? Dbranch : Head->num;
+
+ if (!fexpandsym(rev1, &numericrev, workptr)) continue;
+ if (!(target=genrevs(numericrev.string,(char *)nil,(char *)nil,(char *)nil,&gendeltas))) continue;
+ xrev1=target->num;
+#if DIFF_L
+ if (diff_label1)
+ *diff_label1 = setup_label(&labelbuf[0], target->num, target->date);
+#endif
+
+ lexpandarg = expandarg;
+ if (revnums==2) {
+ if (!fexpandsym(
+ *rev2 ? rev2 : Dbranch ? Dbranch : Head->num,
+ &numericrev,
+ workptr
+ ))
+ continue;
+ if (!(target=genrevs(numericrev.string,(char *)nil,(char *)nil,(char *)nil,&gendeltas))) continue;
+ xrev2=target->num;
+ } else if (
+ target->lockedby
+ && !lexpandarg
+ && Expand == KEYVAL_EXPAND
+ && WORKMODE(RCSstat.st_mode,true) == workstat.st_mode
+ )
+ lexpandarg = "-kkvl";
+ Izclose(&workptr);
+#if DIFF_L
+ if (diff_label2)
+ if (revnums == 2)
+ *diff_label2 = setup_label(&labelbuf[1], target->num, target->date);
+ else {
+ time2date(workstat.st_mtime, date2);
+ *diff_label2 = setup_label(&labelbuf[1], workfilename, date2);
+ }
+#endif
+
+ diagnose("retrieving revision %s\n", xrev1);
+ bufscpy(&commarg, "-p");
+ bufscat(&commarg, xrev1);
+
+ cov[1] = diffp[0] = maketemp(0);
+ pp = &cov[4];
+ *pp++ = commarg.string;
+ if (lexpandarg)
+ *pp++ = lexpandarg;
+ if (versionarg)
+ *pp++ = versionarg;
+ *pp++ = RCSfilename;
+ *pp = 0;
+
+ if (runv(cov)) {
+ error("co failed");
+ continue;
+ }
+ if (!rev2) {
+ diffp[1] = workfilename;
+ if (workfilename[0] == '+') {
+ /* Some diffs have options with leading '+'. */
+ char *dp = ftnalloc(char, strlen(workfilename)+3);
+ diffp[1] = dp;
+ *dp++ = '.';
+ *dp++ = SLASH;
+ VOID strcpy(dp, workfilename);
+ }
+ } else {
+ diagnose("retrieving revision %s\n",xrev2);
+ bufscpy(&commarg, "-p");
+ bufscat(&commarg, xrev2);
+ cov[1] = diffp[1] = maketemp(1);
+ cov[4] = commarg.string;
+ if (runv(cov)) {
+ error("co failed");
+ continue;
+ }
+ }
+ if (!rev2)
+ diagnose("diff%s -r%s %s\n", diffvstr, xrev1, workfilename);
+ else
+ diagnose("diff%s -r%s -r%s\n", diffvstr, xrev1, xrev2);
+
+ switch (runv(diffv)) {
+ case DIFF_SUCCESS:
+ break;
+ case DIFF_FAILURE:
+ if (exitstatus == DIFF_SUCCESS)
+ exitstatus = DIFF_FAILURE;
+ break;
+ default:
+ error("diff failed");
+ }
+ } while (cleanup(),
+ ++argv, --argc >=1);
+
+
+ tempunlink();
+ exitmain(exitstatus);
+}
+
+ static void
+cleanup()
+{
+ if (nerror) exitstatus = DIFF_TROUBLE;
+ Izclose(&finptr);
+ Izclose(&workptr);
+}
+
+#if lint
+# define exiterr rdiffExit
+#endif
+ exiting void
+exiterr()
+{
+ tempunlink();
+ _exit(DIFF_TROUBLE);
+}
+
+#if DIFF_L
+ static char const *
+setup_label(b, name, date)
+ struct buf *b;
+ char const *name;
+ char const date[datesize];
+{
+ char *p;
+ size_t l = strlen(name) + 3;
+ bufalloc(b, l+datesize);
+ p = b->string;
+ VOID sprintf(p, "-L%s\t", name);
+ VOID date2str(date, p+l);
+ return p;
+}
+#endif
diff --git a/gnu/usr.bin/rcs/rcsfreeze/Makefile b/gnu/usr.bin/rcs/rcsfreeze/Makefile
new file mode 100644
index 0000000..825d4bf
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcsfreeze/Makefile
@@ -0,0 +1,7 @@
+# Do nothing for the following
+obj clean cleandir depend rcsfreeze all:
+ @echo No need to make $@ for rcsfreeze\; ignored
+
+install:
+ install -c -o bin -g bin -m 555 rcsfreeze.sh /usr/bin/rcsfreeze
+ install -c -o bin -g bin -m 444 rcsfreeze.1 /usr/share/man/man1
diff --git a/gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.1 b/gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.1
new file mode 100644
index 0000000..be669a9
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.1
@@ -0,0 +1,68 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+..
+.Id $Id: rcsfreeze.1,v 4.4 1990/11/13 15:43:42 hammer Exp $
+.ds r \s-1RCS\s0
+.TH RCSFREEZE 1 \*(Dt GNU
+.SH NAME
+rcsfreeze \- freeze a configuration of sources checked in under RCS
+.SH SYNOPSIS
+.B rcsfreeze
+.RI [ "name" ]
+.SH DESCRIPTION
+.B rcsfreeze
+assigns a symbolic revision
+number to a set of \*r files that form a valid configuration.
+.PP
+The idea is to run
+.B rcsfreeze
+each time a new version is checked
+in. A unique symbolic name (\c
+.BI C_ number,
+where
+.I number
+is increased each time
+.B rcsfreeze
+is run) is then assigned to the most
+recent revision of each \*r file of the main trunk.
+.PP
+An optional
+.I name
+argument to
+.B rcsfreeze
+gives a symbolic name to the configuration.
+The unique identifier is still generated
+and is listed in the log file but it will not appear as
+part of the symbolic revision name in the actual \*r files.
+.PP
+A log message is requested from the user for future reference.
+.PP
+The shell script works only on all \*r files at one time.
+All changed files must be checked in already.
+Run
+.IR rcsclean (1)
+first and see whether any sources remain in the current directory.
+.SH FILES
+.TP
+.B RCS/.rcsfreeze.ver
+version number
+.TP
+.B RCS/.rcsfreeze.log
+log messages, most recent first
+.SH AUTHOR
+Stephan v. Bechtolsheim
+.SH "SEE ALSO"
+co(1), rcs(1), rcsclean(1), rlog(1)
+.SH BUGS
+.B rcsfreeze
+does not check whether any sources are checked out and modified.
+.PP
+Although both source file names and RCS file names are accepted,
+they are not paired as usual with RCS commands.
+.PP
+Error checking is rudimentary.
+.PP
+.B rcsfreeze
+is just an optional example shell script, and should not be taken too seriously.
+See \s-1CVS\s0 for a more complete solution.
diff --git a/gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.sh b/gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.sh
new file mode 100644
index 0000000..4219979
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcsfreeze/rcsfreeze.sh
@@ -0,0 +1,100 @@
+#! /bin/sh
+
+# rcsfreeze - assign a symbolic revision number to a configuration of RCS files
+
+# $Id: rcsfreeze.sh,v 4.4 1991/04/21 11:58:24 eggert Exp $
+
+# The idea is to run rcsfreeze each time a new version is checked
+# in. A unique symbolic revision number (C_[number], where number
+# is increased each time rcsfreeze is run) is then assigned to the most
+# recent revision of each RCS file of the main trunk.
+#
+# If the command is invoked with an argument, then this
+# argument is used as the symbolic name to freeze a configuration.
+# The unique identifier is still generated
+# and is listed in the log file but it will not appear as
+# part of the symbolic revision name in the actual RCS file.
+#
+# A log message is requested from the user which is saved for future
+# references.
+#
+# The shell script works only on all RCS files at one time.
+# It is important that all changed files are checked in (there are
+# no precautions against any error in this respect).
+# file names:
+# {RCS/}.rcsfreeze.ver version number
+# {RCS/}.rscfreeze.log log messages, most recent first
+
+PATH=/usr/gnu/bin:/usr/local/bin:/bin:/usr/bin:/usr/ucb:$PATH
+export PATH
+
+DATE=`date` || exit
+# Check whether we have an RCS subdirectory, so we can have the right
+# prefix for our paths.
+if [ -d RCS ]
+then RCSDIR=RCS/
+else RCSDIR=
+fi
+
+# Version number stuff, log message file
+VERSIONFILE=${RCSDIR}.rcsfreeze.ver
+LOGFILE=${RCSDIR}.rcsfreeze.log
+# Initialize, rcsfreeze never run before in the current directory
+[ -r $VERSIONFILE ] || { echo 0 >$VERSIONFILE && >>$LOGFILE; } || exit
+
+# Get Version number, increase it, write back to file.
+VERSIONNUMBER=`cat $VERSIONFILE` &&
+VERSIONNUMBER=`expr $VERSIONNUMBER + 1` &&
+echo $VERSIONNUMBER >$VERSIONFILE || exit
+
+# Symbolic Revision Number
+SYMREV=C_$VERSIONNUMBER
+# Allow the user to give a meaningful symbolic name to the revision.
+SYMREVNAME=${1-$SYMREV}
+echo >&2 "rcsfreeze: symbolic revision number computed: \"${SYMREV}\"
+rcsfreeze: symbolic revision number used: \"${SYMREVNAME}\"
+rcsfreeze: the two differ only when rcsfreeze invoked with argument
+rcsfreeze: give log message, summarizing changes (end with EOF or single '.')" \
+ || exit
+
+# Stamp the logfile. Because we order the logfile the most recent
+# first we will have to save everything right now in a temporary file.
+TMPLOG=/tmp/rcsfrz$$
+trap 'rm -f $TMPLOG; exit 1' 1 2 13 15
+# Now ask for a log message, continously add to the log file
+(
+ echo "Version: $SYMREVNAME($SYMREV), Date: $DATE
+-----------" || exit
+ while read MESS
+ do
+ case $MESS in
+ .) break
+ esac
+ echo " $MESS" || exit
+ done
+ echo "-----------
+" &&
+ cat $LOGFILE
+) >$TMPLOG &&
+
+# combine old and new logfiles
+cp $TMPLOG $LOGFILE &&
+rm -f $TMPLOG || exit
+trap 1 2 13 15
+
+# Now the real work begins by assigning a symbolic revision number
+# to each rcs file. Take the most recent version of the main trunk.
+
+status=
+
+for FILE in ${RCSDIR}*
+do
+# get the revision number of the most recent revision
+ HEAD=`rlog -h $FILE` &&
+ REV=`echo "$HEAD" | sed -n 's/^head:[ ]*//p'` &&
+# assign symbolic name to it.
+ echo >&2 "rcsfreeze: $REV $FILE" &&
+ rcs -q -n$SYMREVNAME:$REV $FILE || status=$?
+done
+
+exit $status
diff --git a/gnu/usr.bin/rcs/rcsmerge/Makefile b/gnu/usr.bin/rcs/rcsmerge/Makefile
new file mode 100644
index 0000000..0c1f643
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcsmerge/Makefile
@@ -0,0 +1,7 @@
+PROG= rcsmerge
+
+SRCS= rcsmerge.c
+LDADD= -L${.CURDIR}/../lib/obj -lrcs
+CFLAGS+= -I${.CURDIR}/../lib
+
+.include <bsd.prog.mk>
diff --git a/gnu/usr.bin/rcs/rcsmerge/rcsmerge.1 b/gnu/usr.bin/rcs/rcsmerge/rcsmerge.1
new file mode 100644
index 0000000..82871b0
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcsmerge/rcsmerge.1
@@ -0,0 +1,140 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+..
+.Id $Id: rcsmerge.1,v 5.3 1991/08/19 03:13:55 eggert Exp $
+.ds r \&\s-1RCS\s0
+.if n .ds - \%--
+.if t .ds - \(em
+.TH RCSMERGE 1 \*(Dt GNU
+.SH NAME
+rcsmerge \- merge RCS revisions
+.SH SYNOPSIS
+.B rcsmerge
+.RI [ options ] " file"
+.SH DESCRIPTION
+.B rcsmerge
+incorporates the changes between two revisions
+of an \*r file into the corresponding working file.
+.PP
+Pathnames matching an \*r suffix denote \*r files;
+all others denote working files.
+Names are paired as explained in
+.BR ci (1).
+.PP
+At least one revision must be specified with one of the options
+described below, usually
+.BR \-r .
+At most two revisions may be specified.
+If only one revision is specified, the latest
+revision on the default branch (normally the highest branch on the trunk)
+is assumed for the second revision.
+Revisions may be specified numerically or symbolically.
+.PP
+.B rcsmerge
+prints a warning if there are overlaps, and delimits
+the overlapping regions as explained in
+.BR merge (1).
+The command is useful for incorporating changes into a checked-out revision.
+.SH OPTIONS
+.TP
+.BI \-k subst
+Use
+.I subst
+style keyword substitution.
+See
+.BR co (1)
+for details.
+For example,
+.B "\-kk\ \-r1.1\ \-r1.2"
+ignores differences in keyword values when merging the changes from
+.B 1.1
+to
+.BR 1.2 .
+.TP
+.BR \-p [\f2rev\fP]
+Send the result to standard output instead of overwriting the working file.
+.TP
+.BR \-q [\f2rev\fP]
+Run quietly; do not print diagnostics.
+.TP
+.BR \-r [\f2rev\fP]
+Merge with respect to revision
+.IR rev .
+Here an empty
+.I rev
+stands for the latest revision on the default branch, normally the head.
+.TP
+.BI \-V n
+Emulate \*r version
+.IR n .
+See
+.BR co (1)
+for details.
+.TP
+.BI \-x "suffixes"
+Use
+.I suffixes
+to characterize \*r files.
+See
+.BR ci (1)
+for details.
+.SH EXAMPLES
+Suppose you have released revision 2.8 of
+.BR f.c .
+Assume
+furthermore that after you complete an unreleased revision 3.4, you receive
+updates to release 2.8 from someone else.
+To combine the updates to 2.8 and your changes between 2.8 and 3.4,
+put the updates to 2.8 into file f.c and execute
+.LP
+.B " rcsmerge \-p \-r2.8 \-r3.4 f.c >f.merged.c"
+.PP
+Then examine
+.BR f.merged.c .
+Alternatively, if you want to save the updates to 2.8 in the \*r file,
+check them in as revision 2.8.1.1 and execute
+.BR "co \-j":
+.LP
+.B " ci \-r2.8.1.1 f.c"
+.br
+.B " co \-r3.4 \-j2.8:2.8.1.1 f.c"
+.PP
+As another example, the following command undoes the changes
+between revision 2.4 and 2.8 in your currently checked out revision
+in
+.BR f.c .
+.LP
+.B " rcsmerge \-r2.8 \-r2.4 f.c"
+.PP
+Note the order of the arguments, and that
+.B f.c
+will be
+overwritten.
+.SH ENVIRONMENT
+.TP
+.B \s-1RCSINIT\s0
+options prepended to the argument list, separated by spaces.
+See
+.BR ci (1)
+for details.
+.SH DIAGNOSTICS
+Exit status is 0 for no overlaps, 1 for some overlaps, 2 for trouble.
+.SH IDENTIFICATION
+Author: Walter F. Tichy.
+.br
+Revision Number: \*(Rv; Release Date: \*(Dt.
+.br
+Copyright \(co 1982, 1988, 1989 by Walter F. Tichy.
+.br
+Copyright \(co 1990, 1991 by Paul Eggert.
+.SH "SEE ALSO"
+ci(1), co(1), ident(1), merge(1), rcs(1), rcsdiff(1), rcsintro(1), rlog(1),
+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/rcsmerge/rcsmerge.c b/gnu/usr.bin/rcs/rcsmerge/rcsmerge.c
new file mode 100644
index 0000000..e5d4394
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcsmerge/rcsmerge.c
@@ -0,0 +1,252 @@
+/*
+ * rcsmerge operation
+ */
+/*****************************************************************************
+ * join 2 revisions with respect to a third
+ *****************************************************************************
+ */
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+
+/* $Log: rcsmerge.c,v $
+ * Revision 5.7 1991/11/20 17:58:09 eggert
+ * Don't Iopen(f, "r+"); it's not portable.
+ *
+ * Revision 5.6 1991/08/19 03:13:55 eggert
+ * Add -r$. Tune.
+ *
+ * Revision 5.5 1991/04/21 11:58:27 eggert
+ * Add -x, RCSINIT, MS-DOS support.
+ *
+ * Revision 5.4 1991/02/25 07:12:43 eggert
+ * Merging a revision to itself is no longer an error.
+ *
+ * Revision 5.3 1990/11/01 05:03:50 eggert
+ * Remove unneeded setid check.
+ *
+ * Revision 5.2 1990/09/04 08:02:28 eggert
+ * Check for I/O error when reading working file.
+ *
+ * Revision 5.1 1990/08/29 07:14:04 eggert
+ * Add -q. Pass -L options to merge.
+ *
+ * Revision 5.0 1990/08/22 08:13:41 eggert
+ * Propagate merge's exit status.
+ * Remove compile-time limits; use malloc instead.
+ * Make lock and temp files faster and safer. Ansify and Posixate. Add -V.
+ * Don't use access(). Tune.
+ *
+ * Revision 4.5 89/05/01 15:13:16 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.4 88/08/09 19:13:13 eggert
+ * Beware merging into a readonly file.
+ * Beware merging a revision to itself (no change).
+ * Use execv(), not system(); yield exit status like diff(1)'s.
+ *
+ * Revision 4.3 87/10/18 10:38:02 narten
+ * Updating version numbers. Changes relative to version 1.1
+ * actually relative to 4.1
+ *
+ * Revision 1.3 87/09/24 14:00:31 narten
+ * Sources now pass through lint (if you ignore printf/sprintf/fprintf
+ * warnings)
+ *
+ * Revision 1.2 87/03/27 14:22:36 jenkins
+ * Port to suns
+ *
+ * Revision 4.1 83/03/28 11:14:57 wft
+ * Added handling of default branch.
+ *
+ * Revision 3.3 82/12/24 15:29:00 wft
+ * Added call to catchsig().
+ *
+ * Revision 3.2 82/12/10 21:32:02 wft
+ * Replaced getdelta() with gettree(); improved error messages.
+ *
+ * Revision 3.1 82/11/28 19:27:44 wft
+ * Initial revision.
+ *
+ */
+#include "rcsbase.h"
+
+static char const co[] = CO;
+
+mainProg(rcsmergeId, "rcsmerge", "$Id: rcsmerge.c,v 5.7 1991/11/20 17:58:09 eggert Exp $")
+{
+ static char const cmdusage[] =
+ "\nrcsmerge usage: rcsmerge -rrev1 [-rrev2] [-p] [-Vn] file";
+ static char const quietarg[] = "-q";
+
+ register int i;
+ char *a, **newargv;
+ char const *arg[3];
+ char const *rev[2]; /*revision numbers*/
+ char const *expandarg, *versionarg;
+ int tostdout;
+ int status;
+ RILE *workptr;
+ struct buf commarg;
+ struct buf numericrev; /* holds expanded revision number */
+ struct hshentries *gendeltas; /* deltas to be generated */
+ struct hshentry * target;
+
+ bufautobegin(&commarg);
+ bufautobegin(&numericrev);
+ rev[0] = rev[1] = nil;
+ status = 0; /* Keep lint happy. */
+ tostdout = false;
+ expandarg = versionarg = quietarg; /* i.e. a no-op */
+ suffixes = X_DEFAULT;
+
+ argc = getRCSINIT(argc, argv, &newargv);
+ argv = newargv;
+ while (a = *++argv, 0<--argc && *a++=='-') {
+ switch (*a++) {
+ case 'p':
+ tostdout=true;
+ goto revno;
+
+ case 'q':
+ quietflag = true;
+ revno:
+ if (!*a)
+ break;
+ /* falls into -r */
+ case 'r':
+ if (!rev[0])
+ rev[0] = a;
+ else if (!rev[1])
+ rev[1] = a;
+ else
+ faterror("too many revision numbers");
+ break;
+ case 'x':
+ suffixes = a;
+ break;
+ case 'V':
+ versionarg = *argv;
+ setRCSversion(versionarg);
+ break;
+
+ case 'k':
+ expandarg = *argv;
+ if (0 <= str2expmode(expandarg+2))
+ break;
+ /* fall into */
+ default:
+ faterror("unknown option: %s%s", *argv, cmdusage);
+ };
+ } /* end of option processing */
+
+ if (argc<1) faterror("no input file%s", cmdusage);
+ if (!rev[0]) faterror("no base revision number given");
+
+ /* now handle all filenames */
+
+ if (0 < pairfilenames(argc, argv, rcsreadopen, true, false)) {
+
+ if (argc>2 || (argc==2&&argv[1]!=nil))
+ warn("too many arguments");
+ diagnose("RCS file: %s\n", RCSfilename);
+ if (!(workptr = Iopen(workfilename,
+ FOPEN_R_WORK,
+ (struct stat*)0
+ )))
+ efaterror(workfilename);
+
+ gettree(); /* reads in the delta tree */
+
+ if (Head==nil) faterror("no revisions present");
+
+ if (!*rev[0])
+ rev[0] = Dbranch ? Dbranch : Head->num;
+ if (!fexpandsym(rev[0], &numericrev, workptr))
+ goto end;
+ if (!(target=genrevs(numericrev.string, (char *)nil, (char *)nil, (char *)nil,&gendeltas))) goto end;
+ rev[0] = target->num;
+ if (!rev[1] || !*rev[1])
+ rev[1] = Dbranch ? Dbranch : Head->num;
+ if (!fexpandsym(rev[1], &numericrev, workptr))
+ goto end;
+ if (!(target=genrevs(numericrev.string, (char *)nil, (char *)nil, (char *)nil,&gendeltas))) goto end;
+ rev[1] = target->num;
+
+ if (strcmp(rev[0],rev[1]) == 0) {
+ if (tostdout) {
+ FILE *o;
+# if text_equals_binary_stdio || text_work_stdio
+ o = stdout;
+# else
+ if (!(o=fdopen(STDOUT_FILENO,FOPEN_W_WORK)))
+ efaterror("stdout");
+# endif
+ fastcopy(workptr,o);
+ Ofclose(o);
+ }
+ goto end;
+ }
+ Izclose(&workptr);
+
+ for (i=0; i<2; i++) {
+ diagnose("retrieving revision %s\n", rev[i]);
+ bufscpy(&commarg, "-p");
+ bufscat(&commarg, rev[i]);
+ if (run(
+ (char*)0,
+ /* Do not collide with merger.c maketemp(). */
+ arg[i+1] = maketemp(i+3),
+ co, quietarg, commarg.string, expandarg,
+ versionarg, RCSfilename, (char*)0
+ ))
+ faterror("co failed");
+ }
+ diagnose("Merging differences between %s and %s into %s%s\n",
+ rev[0], rev[1], workfilename,
+ tostdout?"; result to stdout":"");
+
+ arg[0] = rev[0] = workfilename;
+ status = merge(tostdout, rev, arg);
+ }
+
+end:
+ Izclose(&workptr);
+ tempunlink();
+ exitmain(nerror ? DIFF_TROUBLE : status);
+}
+
+#if lint
+# define exiterr rmergeExit
+#endif
+ exiting void
+exiterr()
+{
+ tempunlink();
+ _exit(DIFF_TROUBLE);
+}
diff --git a/gnu/usr.bin/rcs/rcstest b/gnu/usr.bin/rcs/rcstest
new file mode 100755
index 0000000..e0b6c82
--- /dev/null
+++ b/gnu/usr.bin/rcs/rcstest
@@ -0,0 +1,397 @@
+#!/bin/sh
+
+# Test RCS's functions.
+# The RCS commands are searched for in the PATH as usual;
+# to test the working directory's commands, prepend . to your PATH.
+
+# Test RCS by creating files RCS/a.* and RCS/a.c.
+# If all goes well, output nothing, and remove the temporary files.
+# Otherwise, send a message to standard output.
+# Exit status is 0 if OK, 1 if an RCS bug is found, and 2 if scaffolding fails.
+# With the -v option, output more debugging info.
+
+# If diff outputs `No differences encountered' when comparing identical files,
+# then rcstest may also output these noise lines; ignore them.
+
+# The current directory and ./RCS must be readable, writable, and searchable.
+
+# $Id: rcstest,v 5.8 1991/11/20 17:58:10 eggert Exp $
+
+
+# Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# Report problems and direct all questions to:
+#
+# rcs-bugs@cs.purdue.edu
+
+RCSINIT=-x
+export RCSINIT
+
+SLASH=/
+RCSfile=RCS${SLASH}a.c
+RCS_alt=RCS${SLASH}a.d
+lockfile=RCS${SLASH}a._
+
+case $1 in
+-v) q=; set -x;;
+'') q=-q;;
+*) echo >&2 "$0: usage: $0 [-v]"; exit 2
+esac
+
+test -d RCS || {
+ echo >&2 "$0: RCS: not a directory; please \`mkdir RCS' first."
+ exit 1
+}
+
+rm -f a.* $RCSfile $RCS_alt $lockfile &&
+echo 1.1 >a.11 &&
+echo 1.1.1.1 >a.3x1 &&
+echo 1.2 >a.12 || { echo "#initialization failed"; exit 2; }
+
+case `diff -c a.11 a.3x1` in
+*'! 1.1.1.1')
+ diff='diff -c';;
+*)
+ echo "#warning: diff -c does not work, so diagnostics may be cryptic"
+ diff=diff
+esac
+
+rcs -i -L -ta.11 $q a.c &&
+<$RCSfile || {
+ echo "#rcs -i -L failed; perhaps RCS is not properly installed."
+ exit 1
+}
+
+rlog a.c >/dev/null || { echo "#rlog failed on empty RCS file"; exit 1; }
+rm -f $RCSfile || exit 2
+
+cp a.11 a.c &&
+ci -ta.11 -mm $q a.c &&
+<$RCSfile &&
+rcs -L $q a.c || { echo "#ci+rcs -L failed"; exit 1; }
+test ! -f a.c || { echo "#ci did not remove working file"; exit 1; }
+for l in '' '-l'
+do
+ co $l $q a.c &&
+ test -f a.c || { echo '#co' $l did not create working file; exit 1; }
+ $diff a.11 a.c || { echo '#ci' followed by co $l is not a no-op; exit 1; }
+done
+
+cp a.12 a.c &&
+ci -mm $q a.c &&
+co $q a.c &&
+$diff a.12 a.c || { echo "#ci+co failed"; exit 1; }
+
+co -r1.1 $q a.c &&
+$diff a.11 a.c || { echo "#can't retrieve first revision"; exit 1; }
+
+rm -f a.c &&
+cp a.3x1 a.c &&
+ci -r1.1.1 -mm $q a.c &&
+co -r1.1.1.1 $q a.c &&
+$diff a.3x1 a.c || { echo "#branches failed"; exit 1; }
+
+co -l $q a.c &&
+ci -f -mm $q a.c &&
+co -r1.3 $q a.c &&
+$diff a.12 a.c || { echo "#(co -l; ci -f) failed"; exit 1; }
+
+co -l $q a.c &&
+echo 1.4 >a.c &&
+ci -l -mm $q a.c &&
+echo error >a.c &&
+ci -mm $q a.c || { echo "#ci -l failed"; exit 1; }
+
+co -l $q a.c &&
+echo 1.5 >a.c &&
+ci -u -mm $q a.c &&
+<a.c || { echo "#ci -u didn't create a working file"; exit 1; }
+rm -f a.c &&
+echo error >a.c || exit 2
+ci -mm $q a.c 2>/dev/null && { echo "#ci -u didn't unlock the file"; exit 1; }
+
+rm -f a.c &&
+rcs -l $q a.c &&
+co -u $q a.c || { echo "#rcs -l + co -u failed"; exit 1; }
+rm -f a.c &&
+echo error >a.c || exit 2
+ci -mm $q a.c 2>/dev/null && { echo "#co -u didn't unlock the file"; exit 1; }
+
+rm -f a.c &&
+cp a.11 a.c &&
+co -f $q a.c || { echo "#co -f failed"; exit 1; }
+$diff a.11 a.c >/dev/null && { echo "#co -f had no effect"; exit 1; }
+
+co -p1.1 $q a.c >a.t &&
+$diff a.11 a.t || { echo "#co -p failed"; exit 1; }
+
+for n in n N
+do
+ rm -f a.c &&
+ co -l $q a.c &&
+ echo $n >a.$n &&
+ cp a.$n a.c &&
+ ci -${n}n -mm $q a.c &&
+ co -rn $q a.c &&
+ $diff a.$n a.c || { echo "#ci -$n failed"; exit 1; }
+done
+
+case $LOGNAME in
+?*) me=$LOGNAME;;
+*)
+ case $USER in
+ ?*) me=$USER;;
+ *)
+ me=`who am i` || exit 2
+ me=`echo "$me" | sed -e 's/ .*//' -e 's/.*!//'`
+ case $me in
+ '') echo >&2 "$0: cannot deduce user name"; exit 2
+ esac
+ esac
+esac
+date=`date -u 2>/dev/null` ||
+date=`TZ=GMT0 date 2>/dev/null` ||
+date=`TZ= date` || exit 2
+set $date
+case $2 in
+Jan) m=01;; Feb) m=02;; Mar) m=03;; Apr) m=04;; May) m=05;; Jun) m=06;;
+Jul) m=07;; Aug) m=08;; Sep) m=09;; Oct) m=10;; Nov) m=11;; Dec) m=12;;
+*) echo >&2 "$0: $2: unknown month name"; exit 2
+esac
+case $3 in
+?) d=0$3;;
+*) d=$3
+esac
+case $6 in
+[0-9][0-9][0-9][0-9]*) D=$6/$m/$d;;
+*)
+ case $5 in
+ [0-9][0-9][0-9][0-9]*) D=$5/$m/$d;;
+ *) echo >&2 "$0: bad date format: $date"; exit 2
+ esac
+esac
+T=$4
+case $PWD in
+'') PWD=`pwd`
+esac &&
+co -l $q a.c &&
+sed 's/@/$/g' >a.kv <<EOF
+@Author: w @
+@Date: $D $T @
+@Header: $PWD$SLASH$RCSfile 2.1 $D $T w s @
+@Id: a.c 2.1 $D $T w s @
+@Locker: @
+@Log: a.c @
+ * Revision 2.1 $D $T w
+ * m
+ *
+@RCSfile: a.c @
+@Revision: 2.1 @
+@Source: $PWD$SLASH$RCSfile @
+@State: s @
+EOF
+test $? = 0 &&
+sed 's/:.*\$/$/' a.kv >a.k &&
+sed -e 's/w s [$]/w s '"$me"' $/' -e 's/[$]Locker: /&'"$me/" a.kv >a.kvl &&
+sed -e '/^\$/!d' -e 's/\$$/: old $/' a.k >a.o &&
+sed -e 's/\$[^ ]*: //' -e 's/ \$//' a.kv >a.v &&
+cp a.o a.c &&
+ci -d"$date" -ss -ww -u2.1 -mm $q a.c &&
+$diff a.kv a.c || { echo "#keyword expansion failed"; exit 1; }
+co -p -ko $q a.c >a.oo &&
+$diff a.o a.oo || { echo "#co -p -ko failed"; exit 1; }
+cp a.kv a.o || exit 2
+rcs -o2.1 $q a.c &&
+rcs -l $q a.c &&
+ci -k -u $q a.c &&
+$diff a.kv a.c || { echo "#ci -k failed"; exit 1; }
+sed '/^[^$]/d' a.kv >a.i &&
+ident a.c >a.i1 &&
+sed -e 1d -e 's/^[ ]*//' a.i1 >a.i2 &&
+$diff a.i a.i2 || { echo "#ident failed"; exit 1; }
+
+rcs -i $q a.c 2>/dev/null && { echo "#rcs -i permitted existing file"; exit 1; }
+
+co -l $q a.c &&
+echo 2.2 >a.c &&
+ci -mm $q a.c &&
+echo 1.1.1.2 >a.c &&
+rcs -l1.1.1 $q a.c &&
+ci -r1.1.1.2 -mm $q a.c &&
+rcs -b1.1.1 $q a.c &&
+test " `co -p $q a.c`" = ' 1.1.1.2' || { echo "#rcs -b1.1.1 failed"; exit 1; }
+rcs -b $q a.c &&
+test " `co -p $q a.c`" = ' 2.2' || { echo "#rcs -b failed"; exit 1; }
+
+echo 2.3 >a.c || exit 2
+rcs -U $q a.c || { echo "#rcs -U failed"; exit 1; }
+ci -mm $q a.c || { echo "#rcs -U didn't unset strict locking"; exit 1; }
+rcs -L $q a.c || { echo "#rcs -L failed"; exit 1; }
+echo error >a.c || exit 2
+ci -mm $q a.c 2>/dev/null && { echo "#ci retest failed"; exit 1; }
+
+rm -f a.c &&
+log0=`rlog -h a.c` &&
+co -l $q a.c &&
+ci -mm $q a.c &&
+log1=`rlog -h a.c` &&
+test " $log0" = " $log1" || { echo "#unchanged ci didn't revert"; exit 1; }
+
+rm -f a.c &&
+rcs -nN:1.1 $q a.c &&
+co -rN $q a.c &&
+$diff a.11 a.c || { echo "#rcs -n failed"; exit 1; }
+
+rcs -NN:2.1 $q a.c &&
+co -rN $q a.c &&
+$diff a.kv a.c || { echo "#rcs -N failed"; exit 1; }
+
+co -l $q a.c &&
+rcs -c':::' $q a.c &&
+echo '$''Log$' >a.c &&
+ci -u -mm $q a.c &&
+test " `sed '$!d' a.c`" = ' :::' || { echo "#rcs -c failed"; exit 1; }
+
+rcs -o2.2: $q a.c &&
+co $q a.c &&
+$diff a.kv a.c || { echo "#rcs -o failed"; exit 1; }
+
+rcsdiff -r1.1 -r2.1 $q a.c >a.0
+case $? in
+1) ;;
+*) echo "#rcsdiff bad status"; exit 1
+esac
+diff a.11 a.kv >a.1
+$diff a.0 a.1 || { echo "#rcsdiff failed"; exit 1; }
+
+rcs -l2.1 $q a.c || { echo "#rcs -l2.1 failed"; exit 1; }
+for i in k kv kvl o v
+do
+ rm -f a.c &&
+ cp a.$i a.c &&
+ rcsdiff -k$i $q a.c || { echo "#rcsdiff -k$i failed"; exit 1; }
+done
+co -p1.1 -ko $q a.c >a.t &&
+$diff a.11 a.t || { echo "#co -p1.1 -ko failed"; exit 1; }
+rcs -u2.1 $q a.c || { echo "#rcs -u2.1 failed"; exit 1; }
+
+rm -f a.c &&
+co -l $q a.c &&
+cat >a.c <<'EOF'
+2.2
+a
+b
+c
+d
+EOF
+test $? = 0 &&
+ci -l -mm $q a.c &&
+co -p2.2 $q a.c | sed -e s/2.2/2.3/ -e s/b/b1/ >a.c &&
+ci -l -mm $q a.c &&
+co -p2.2 $q a.c | sed -e s/2.2/new/ -e s/d/d1/ >a.c || exit 2
+cat >a.0 <<'EOF'
+2.3
+a
+b1
+c
+d1
+EOF
+cat >a.1 <<'EOF'
+<<<<<<< a.c
+new
+=======
+2.3
+>>>>>>> 2.3
+a
+b1
+c
+d1
+EOF
+rcsmerge -r2.2 -r2.3 $q a.c
+case $? in
+0)
+ if $diff a.0 a.c >/dev/null
+ then echo "#warning: diff3 -E does not work, " \
+ "so merge and rcsmerge ignore overlaps and suppress overlap lines."
+ else
+ $diff a.1 a.c || { echo "#rcsmerge failed (status 0)"; exit 1; }
+ echo "#warning: The diff3 lib program exit status ignores overlaps," \
+ "so rcsmerge does not warn about overlap lines that it generates."
+ fi
+ ;;
+1)
+ $diff a.1 a.c || { echo "#rcsmerge failed (status 1)"; exit 1; }
+ ;;
+*)
+ echo "#rcsmerge bad status"; exit 1
+esac
+
+nl='
+'
+{
+ co -p $q a.c | tr "$nl" '\200' >a.24 &&
+ cp a.24 a.c &&
+ ciOut=`(ci -l -mm $q a.c 2>&1)` &&
+ case $ciOut in
+ ?*) echo >&2 "$ciOut"
+ esac &&
+ co -p $q a.c | tr '\200' "$nl" >a.c &&
+ rcsdiff -r2.3 $q a.c >/dev/null &&
+
+ echo 2.5 >a.c &&
+ ci -l -mm $q a.c &&
+ cp a.24 a.c &&
+ rcsdiff -r2.4 $q a.c >/dev/null
+} || echo "#warning: Traditional diff is used, so RCS is limited to text files."
+
+rcs -u -o2.4: $q a.c || { echo "#rcs -u -o failed"; exit 1; }
+
+rcs -i -Aa.c -t- $q a.d || { echo "#rcs -i -A failed"; exit 1; }
+
+rlog -r2.1 a.c >a.t &&
+grep '^checked in with -k' a.t >/dev/null &&
+sed '/^checked in with -k/d' a.t >a.u &&
+$diff - a.u <<EOF
+
+RCS file: $RCSfile
+Working file: a.c
+head: 2.3
+branch:
+locks: strict
+access list:
+symbolic names:
+ N: 2.1
+ n: 1.8
+comment leader: ":::"
+keyword substitution: kv
+total revisions: 13; selected revisions: 1
+description:
+1.1
+----------------------------
+revision 2.1
+date: $D $T; author: w; state: s; lines: +13 -1
+=============================================================================
+EOF
+test $? = 0 || { echo "#rlog failed"; exit 1; }
+
+
+test ! -f $lockfile || { echo "#lock file not removed"; exit 1; }
+
+exec rm -f a.* $RCSfile $RCS_alt
diff --git a/gnu/usr.bin/rcs/rlog/Makefile b/gnu/usr.bin/rcs/rlog/Makefile
new file mode 100644
index 0000000..b6a1268
--- /dev/null
+++ b/gnu/usr.bin/rcs/rlog/Makefile
@@ -0,0 +1,7 @@
+PROG= rlog
+
+SRCS= rlog.c
+LDADD= -L${.CURDIR}/../lib/obj -lrcs
+CFLAGS+= -I${.CURDIR}/../lib
+
+.include <bsd.prog.mk>
diff --git a/gnu/usr.bin/rcs/rlog/rlog.1 b/gnu/usr.bin/rcs/rlog/rlog.1
new file mode 100644
index 0000000..fa627ff
--- /dev/null
+++ b/gnu/usr.bin/rcs/rlog/rlog.1
@@ -0,0 +1,260 @@
+.de Id
+.ds Rv \\$3
+.ds Dt \\$4
+..
+.Id $Id: rlog.1,v 5.3 1991/08/22 06:50:48 eggert Exp $
+.ds g \&\s-1UTC\s0
+.ds r \&\s-1RCS\s0
+.if n .ds - \%--
+.if t .ds - \(em
+.TH RLOG 1 \*(Dt GNU
+.SH NAME
+rlog \- print log messages and other information about RCS files
+.SH SYNOPSIS
+.B rlog
+.RI [ " options " ] " file " .\|.\|.
+.SH DESCRIPTION
+.B rlog
+prints information about \*r files.
+.PP
+Pathnames matching an \*r suffix denote \*r files;
+all others denote working files.
+Names are paired as explained in
+.BR ci (1).
+.PP
+.B rlog
+prints the following information for each
+\*r file: \*r pathname, working pathname, head (i.e., the number
+of the latest revision on the trunk), default branch, access list, locks,
+symbolic names, suffix, total number of revisions,
+number of revisions selected for printing, and
+descriptive text. This is followed by entries for the selected revisions in
+reverse chronological order for each branch. For each revision,
+.B rlog
+prints revision number, author, date/time, state, number of
+lines added/deleted (with respect to the previous revision),
+locker of the revision (if any), and log message.
+All times are displayed in Coordinated Universal Time (\*g).
+Without options,
+.B rlog
+prints complete information.
+The options below restrict this output.
+.nr n \w'\f3\-V\fP\f2n\fP '+1n-1/1n
+.TP \nn
+.B \-L
+Ignore \*r files that have no locks set.
+This is convenient in combination with
+.BR \-h ,
+.BR \-l ,
+and
+.BR \-R .
+.TP
+.B \-R
+Print only the name of the \*r file.
+This is convenient for translating a
+working pathname into an \*r pathname.
+.TP
+.B \-h
+Print only the \*r pathname, working pathname, head,
+default branch, access list, locks,
+symbolic names, and suffix.
+.TP
+.B \-t
+Print the same as
+.BR \-h ,
+plus the descriptive text.
+.TP
+.B \-b
+Print information about the revisions on the default branch, normally
+the highest branch on the trunk.
+.TP
+.BI \-d "dates"
+Print information about revisions with a checkin date/time in the ranges given by
+the semicolon-separated list of
+.IR dates .
+A range of the form
+.IB d1 < d2
+or
+.IB d2 > d1
+selects the revisions that were deposited between
+.I d1
+and
+.I d2
+inclusive.
+A range of the form
+.BI < d
+or
+.IB d >
+selects
+all revisions dated
+.I d
+or earlier.
+A range of the form
+.IB d <
+or
+.BI > d
+selects
+all revisions dated
+.I d
+or later.
+A range of the form
+.I d
+selects the single, latest revision dated
+.I d
+or earlier.
+The date/time strings
+.IR d ,
+.IR d1 ,
+and
+.I d2
+are in the free format explained in
+.BR co (1).
+Quoting is normally necessary, especially for
+.B <
+and
+.BR > .
+Note that the separator is
+a semicolon.
+.TP
+.BR \-l [\f2lockers\fP]
+Print information about locked revisions only.
+In addition, if the comma-separated list
+.I lockers
+of login names is given,
+ignore all locks other than those held by the
+.IR lockers .
+For example,
+.B "rlog\ \-L\ \-R\ \-lwft\ RCS/*"
+prints the name of \*r files locked by the user
+.BR wft .
+.TP
+.BR \-r [\f2revisions\fP]
+prints information about revisions given in the comma-separated list
+.I revisions
+of revisions and ranges.
+A range
+.IB rev1 : rev2
+means revisions
+.I rev1
+to
+.I rev2
+on the same branch,
+.BI : rev
+means revisions from the beginning of the branch up to and including
+.IR rev ,
+and
+.IB rev :
+means revisions starting with
+.I rev
+to the end of the branch containing
+.IR rev .
+An argument that is a branch means all
+revisions on that branch.
+A range of branches means all revisions
+on the branches in that range.
+A branch followed by a
+.B .\&
+means the latest revision in that branch.
+A bare
+.B \-r
+with no
+.I revisions
+means the latest revision on the default branch, normally the trunk.
+.TP
+.BI \-s states
+prints information about revisions whose state attributes match one of the
+states given in the comma-separated list
+.IR states .
+.TP
+.BR \-w [\f2logins\fP]
+prints information about revisions checked in by users with
+login names appearing in the comma-separated list
+.IR logins .
+If
+.I logins
+is omitted, the user's login is assumed.
+.TP
+.BI \-V n
+Emulate \*r version
+.I n
+when generating logs.
+See
+.BR co (1)
+for more.
+.TP
+.BI \-x "suffixes"
+Use
+.I suffixes
+to characterize \*r files.
+See
+.BR ci (1)
+for details.
+.PP
+.B rlog
+prints the intersection of the revisions selected with
+the options
+.BR \-d ,
+.BR \-l ,
+.BR \-s ,
+and
+.BR \-w ,
+intersected
+with the union of the revisions selected by
+.B \-b
+and
+.BR \-r .
+.SH EXAMPLES
+.LP
+.nf
+.B " rlog \-L \-R RCS/*"
+.B " rlog \-L \-h RCS/*"
+.B " rlog \-L \-l RCS/*"
+.B " rlog RCS/*"
+.fi
+.LP
+The first command prints the names of all \*r files in the subdirectory
+.B RCS
+that have locks. The second command prints the headers of those files,
+and the third prints the headers plus the log messages of the locked revisions.
+The last command prints complete information.
+.SH ENVIRONMENT
+.TP
+.B \s-1RCSINIT\s0
+options prepended to the argument list, separated by spaces.
+See
+.BR ci (1)
+for details.
+.SH DIAGNOSTICS
+The exit status is zero if and only if all operations were successful.
+.SH IDENTIFICATION
+Author: Walter F. Tichy.
+.br
+Revision Number: \*(Rv; Release Date: \*(Dt.
+.br
+Copyright \(co 1982, 1988, 1989 by Walter F. Tichy.
+.br
+Copyright \(co 1990, 1991 by Paul Eggert.
+.SH "SEE ALSO"
+ci(1), co(1), ident(1), rcs(1), rcsdiff(1), rcsintro(1), rcsmerge(1),
+rcsfile(5)
+.br
+Walter F. Tichy,
+\*r\*-A System for Version Control,
+.I "Software\*-Practice & Experience"
+.BR 15 ,
+7 (July 1985), 637-654.
+.SH BUGS
+The separator for revision ranges in the
+.B \-r
+option used to be
+.B \-
+instead of
+.BR : ,
+but this leads to confusion when symbolic names contain
+.BR \- .
+For backwards compatibility
+.B "rlog \-r"
+still supports the old
+.B \-
+separator, but it warns about this obsolete use.
+.br
diff --git a/gnu/usr.bin/rcs/rlog/rlog.c b/gnu/usr.bin/rcs/rlog/rlog.c
new file mode 100644
index 0000000..b18b0c9
--- /dev/null
+++ b/gnu/usr.bin/rcs/rlog/rlog.c
@@ -0,0 +1,1204 @@
+/*
+ * RLOG operation
+ */
+/*****************************************************************************
+ * print contents of RCS files
+ *****************************************************************************
+ */
+
+/* Copyright (C) 1982, 1988, 1989 Walter Tichy
+ Copyright 1990, 1991 by 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, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Report problems and direct all questions to:
+
+ rcs-bugs@cs.purdue.edu
+
+*/
+
+
+
+
+/* $Log: rlog.c,v $
+ * Revision 5.9 1991/09/17 19:07:40 eggert
+ * Getscript() didn't uncache partial lines.
+ *
+ * Revision 5.8 1991/08/19 03:13:55 eggert
+ * Revision separator is `:', not `-'.
+ * Check for missing and duplicate logs. Tune.
+ * Permit log messages that do not end in newline (including empty logs).
+ *
+ * Revision 5.7 1991/04/21 11:58:31 eggert
+ * Add -x, RCSINIT, MS-DOS support.
+ *
+ * Revision 5.6 1991/02/26 17:07:17 eggert
+ * Survive RCS files with missing logs.
+ * strsave -> str_save (DG/UX name clash)
+ *
+ * Revision 5.5 1990/11/01 05:03:55 eggert
+ * Permit arbitrary data in logs and comment leaders.
+ *
+ * Revision 5.4 1990/10/04 06:30:22 eggert
+ * Accumulate exit status across files.
+ *
+ * Revision 5.3 1990/09/11 02:41:16 eggert
+ * Plug memory leak.
+ *
+ * Revision 5.2 1990/09/04 08:02:33 eggert
+ * Count RCS lines better.
+ *
+ * Revision 5.0 1990/08/22 08:13:48 eggert
+ * Remove compile-time limits; use malloc instead. Add setuid support.
+ * Switch to GMT.
+ * Report dates in long form, to warn about dates past 1999/12/31.
+ * Change "added/del" message to make room for the longer dates.
+ * Don't generate trailing white space. Add -V. Ansify and Posixate.
+ *
+ * Revision 4.7 89/05/01 15:13:48 narten
+ * changed copyright header to reflect current distribution rules
+ *
+ * Revision 4.6 88/08/09 19:13:28 eggert
+ * Check for memory exhaustion; don't access freed storage.
+ * Shrink stdio code size; remove lint.
+ *
+ * Revision 4.5 87/12/18 11:46:38 narten
+ * more lint cleanups (Guy Harris)
+ *
+ * Revision 4.4 87/10/18 10:41:12 narten
+ * Updating version numbers
+ * Changes relative to 1.1 actually relative to 4.2
+ *
+ * Revision 1.3 87/09/24 14:01:10 narten
+ * Sources now pass through lint (if you ignore printf/sprintf/fprintf
+ * warnings)
+ *
+ * Revision 1.2 87/03/27 14:22:45 jenkins
+ * Port to suns
+ *
+ * Revision 4.2 83/12/05 09:18:09 wft
+ * changed rewriteflag to external.
+ *
+ * Revision 4.1 83/05/11 16:16:55 wft
+ * Added -b, updated getnumericrev() accordingly.
+ * Replaced getpwuid() with getcaller().
+ *
+ * Revision 3.7 83/05/11 14:24:13 wft
+ * Added options -L and -R;
+ * Fixed selection bug with -l on multiple files.
+ * Fixed error on dates of the form -d'>date' (rewrote getdatepair()).
+ *
+ * Revision 3.6 82/12/24 15:57:53 wft
+ * shortened output format.
+ *
+ * Revision 3.5 82/12/08 21:45:26 wft
+ * removed call to checkaccesslist(); used DATEFORM to format all dates;
+ * removed unused variables.
+ *
+ * Revision 3.4 82/12/04 13:26:25 wft
+ * Replaced getdelta() with gettree(); removed updating of field lockedby.
+ *
+ * Revision 3.3 82/12/03 14:08:20 wft
+ * Replaced getlogin with getpwuid(), %02d with %.2d, fancydate with PRINTDATE.
+ * Fixed printing of nil, removed printing of Suffix,
+ * added shortcut if no revisions are printed, disambiguated struct members.
+ *
+ * Revision 3.2 82/10/18 21:09:06 wft
+ * call to curdir replaced with getfullRCSname(),
+ * fixed call to getlogin(), cosmetic changes on output,
+ * changed conflicting long identifiers.
+ *
+ * Revision 3.1 82/10/13 16:07:56 wft
+ * fixed type of variables receiving from getc() (char -> int).
+ */
+
+
+
+#include "rcsbase.h"
+
+struct lockers { /* lockers in locker option; stored */
+ char const * login; /* lockerlist */
+ struct lockers * lockerlink;
+ } ;
+
+struct stateattri { /* states in state option; stored in */
+ char const * status; /* statelist */
+ struct stateattri * nextstate;
+ } ;
+
+struct authors { /* login names in author option; */
+ char const * login; /* stored in authorlist */
+ struct authors * nextauthor;
+ } ;
+
+struct Revpairs{ /* revision or branch range in -r */
+ unsigned numfld; /* option; stored in revlist */
+ char const * strtrev;
+ char const * endrev;
+ struct Revpairs * rnext;
+ } ;
+
+struct Datepairs{ /* date range in -d option; stored in */
+ char strtdate[datesize]; /* duelst and datelist */
+ char enddate[datesize];
+ struct Datepairs * dnext;
+ };
+
+static char extractdelta P((struct hshentry const*));
+static int checkrevpair P((char const*,char const*));
+static struct hshentry const *readdeltalog P((void));
+static unsigned extdate P((struct hshentry*));
+static void cleanup P((void));
+static void exttree P((struct hshentry*));
+static void getauthor P((char*));
+static void getdatepair P((char*));
+static void getlocker P((char*));
+static void getnumericrev P((void));
+static void getrevpairs P((char*));
+static void getscript P((struct hshentry*));
+static void getstate P((char*));
+static void putabranch P((struct hshentry const*));
+static void putadelta P((struct hshentry const*,struct hshentry const*,int));
+static void putforest P((struct branchhead const*));
+static void putree P((struct hshentry const*));
+static void putrunk P((void));
+static void recentdate P((struct hshentry const*,struct Datepairs*));
+static void trunclocks P((void));
+
+static char const *insDelFormat;
+static int branchflag; /*set on -b */
+static int exitstatus;
+static int lockflag;
+static struct Datepairs *datelist, *duelst;
+static struct Revpairs *revlist, *Revlst;
+static struct authors *authorlist;
+static struct lockers *lockerlist;
+static struct stateattri *statelist;
+
+
+mainProg(rlogId, "rlog", "$Id: rlog.c,v 5.9 1991/09/17 19:07:40 eggert Exp $")
+{
+ static char const cmdusage[] =
+ "\nrlog usage: rlog -{bhLRt} -ddates -l[lockers] -rrevs -sstates -w[logins] -Vn file ...";
+
+ register FILE *out;
+ char *a, **newargv;
+ struct Datepairs *currdate;
+ char const *accessListString, *accessFormat, *commentFormat;
+ char const *headFormat, *symbolFormat;
+ struct access const *curaccess;
+ struct assoc const *curassoc;
+ struct hshentry const *delta;
+ struct lock const *currlock;
+ int descflag, selectflag;
+ int onlylockflag; /* print only files with locks */
+ int onlyRCSflag; /* print only RCS file name */
+ unsigned revno;
+
+ descflag = selectflag = true;
+ onlylockflag = onlyRCSflag = false;
+ out = stdout;
+ suffixes = X_DEFAULT;
+
+ argc = getRCSINIT(argc, argv, &newargv);
+ argv = newargv;
+ while (a = *++argv, 0<--argc && *a++=='-') {
+ switch (*a++) {
+
+ case 'L':
+ onlylockflag = true;
+ break;
+
+ case 'R':
+ onlyRCSflag =true;
+ break;
+
+ case 'l':
+ lockflag = true;
+ getlocker(a);
+ break;
+
+ case 'b':
+ branchflag = true;
+ break;
+
+ case 'r':
+ getrevpairs(a);
+ break;
+
+ case 'd':
+ getdatepair(a);
+ break;
+
+ case 's':
+ getstate(a);
+ break;
+
+ case 'w':
+ getauthor(a);
+ break;
+
+ case 'h':
+ descflag = false;
+ break;
+
+ case 't':
+ selectflag = false;
+ break;
+
+ case 'q':
+ /* This has no effect; it's here for consistency. */
+ quietflag = true;
+ break;
+
+ case 'x':
+ suffixes = a;
+ break;
+
+ case 'V':
+ setRCSversion(*argv);
+ break;
+
+ default:
+ faterror("unknown option: %s%s", *argv, cmdusage);
+
+ };
+ } /* end of option processing */
+
+ if (argc<1) faterror("no input file%s", cmdusage);
+
+ if (! (descflag|selectflag)) {
+ warn("-t overrides -h.");
+ descflag = true;
+ }
+
+ if (RCSversion < VERSION(5)) {
+ accessListString = "\naccess list: ";
+ accessFormat = " %s";
+ commentFormat = "\ncomment leader: \"";
+ headFormat = "\nRCS file: %s; Working file: %s\nhead: %s%s\nbranch: %s%s\nlocks: ";
+ insDelFormat = " lines added/del: %lu/%lu";
+ symbolFormat = " %s: %s;";
+ } else {
+ accessListString = "\naccess list:";
+ accessFormat = "\n\t%s";
+ commentFormat = "\ncomment leader: \"";
+ headFormat = "\nRCS file: %s\nWorking file: %s\nhead:%s%s\nbranch:%s%s\nlocks:%s";
+ insDelFormat = " lines: +%lu -%lu";
+ symbolFormat = "\n\t%s: %s";
+ }
+
+ /* now handle all filenames */
+ do {
+ ffree();
+
+ if (pairfilenames(argc, argv, rcsreadopen, true, false) <= 0)
+ continue;
+
+ /* now RCSfilename contains the name of the RCS file, and finptr
+ * the file descriptor. Workfilename contains the name of the
+ * working file.
+ */
+
+ /* Keep only those locks given by -l. */
+ if (lockflag)
+ trunclocks();
+
+ /* do nothing if -L is given and there are no locks*/
+ if (onlylockflag && !Locks)
+ continue;
+
+ if ( onlyRCSflag ) {
+ aprintf(out, "%s\n", RCSfilename);
+ continue;
+ }
+ /* print RCS filename , working filename and optional
+ administrative information */
+ /* could use getfullRCSname() here, but that is very slow */
+ aprintf(out, headFormat, RCSfilename, workfilename,
+ Head ? " " : "", Head ? Head->num : "",
+ Dbranch ? " " : "", Dbranch ? Dbranch : "",
+ StrictLocks ? " strict" : ""
+ );
+ currlock = Locks;
+ while( currlock ) {
+ aprintf(out, symbolFormat, currlock->login,
+ currlock->delta->num);
+ currlock = currlock->nextlock;
+ }
+ if (StrictLocks && RCSversion<VERSION(5))
+ aputs(" strict", out);
+
+ aputs(accessListString, out); /* print access list */
+ curaccess = AccessList;
+ while(curaccess) {
+ aprintf(out, accessFormat, curaccess->login);
+ curaccess = curaccess->nextaccess;
+ }
+
+ aputs("\nsymbolic names:", out); /* print symbolic names */
+ for (curassoc=Symbols; curassoc; curassoc=curassoc->nextassoc)
+ aprintf(out, symbolFormat, curassoc->symbol, curassoc->num);
+ aputs(commentFormat, out);
+ awrite(Comment.string, Comment.size, out);
+ aputs("\"\n", out);
+ if (VERSION(5)<=RCSversion || Expand != KEYVAL_EXPAND)
+ aprintf(out, "keyword substitution: %s\n",
+ expand_names[Expand]
+ );
+
+ gettree();
+
+ aprintf(out, "total revisions: %u", TotalDeltas);
+
+ revno = 0;
+
+ if (Head && selectflag & descflag) {
+
+ getnumericrev(); /* get numeric revision or branch names */
+
+ exttree(Head);
+
+ /* get most recently date of the dates pointed by duelst */
+ currdate = duelst;
+ while( currdate) {
+ VOID sprintf(currdate->strtdate,DATEFORM,0,0,0,0,0,0);
+ recentdate(Head, currdate);
+ currdate = currdate->dnext;
+ }
+
+ revno = extdate(Head);
+
+ aprintf(out, ";\tselected revisions: %u", revno);
+ }
+
+ afputc('\n',out);
+ if (descflag) {
+ aputs("description:\n", out);
+ getdesc(true);
+ }
+ if (revno) {
+ while (! (delta = readdeltalog())->selector || --revno)
+ ;
+ if (delta->next && countnumflds(delta->num)==2)
+ /* Read through delta->next to get its insertlns. */
+ while (readdeltalog() != delta->next)
+ ;
+ putrunk();
+ putree(Head);
+ }
+ aputs("=============================================================================\n",out);
+ } while (cleanup(),
+ ++argv, --argc >= 1);
+ Ofclose(out);
+ exitmain(exitstatus);
+}
+
+ static void
+cleanup()
+{
+ if (nerror) exitstatus = EXIT_FAILURE;
+ Izclose(&finptr);
+}
+
+#if lint
+# define exiterr rlogExit
+#endif
+ exiting void
+exiterr()
+{
+ _exit(EXIT_FAILURE);
+}
+
+
+
+ static void
+putrunk()
+/* function: print revisions chosen, which are in trunk */
+
+{
+ register struct hshentry const *ptr;
+
+ for (ptr = Head; ptr; ptr = ptr->next)
+ putadelta(ptr, ptr->next, true);
+}
+
+
+
+ static void
+putree(root)
+ struct hshentry const *root;
+/* function: print delta tree (not including trunk) in reverse
+ order on each branch */
+
+{
+ if ( root == nil ) return;
+
+ putree(root->next);
+
+ putforest(root->branches);
+}
+
+
+
+
+ static void
+putforest(branchroot)
+ struct branchhead const *branchroot;
+/* function: print branches that has the same direct ancestor */
+{
+
+ if ( branchroot == nil ) return;
+
+ putforest(branchroot->nextbranch);
+
+ putabranch(branchroot->hsh);
+ putree(branchroot->hsh);
+}
+
+
+
+
+ static void
+putabranch(root)
+ struct hshentry const *root;
+/* function : print one branch */
+
+{
+
+ if ( root == nil) return;
+
+ putabranch(root->next);
+
+ putadelta(root, root, false);
+}
+
+
+
+
+
+ static void
+putadelta(node,editscript,trunk)
+ register struct hshentry const *node, *editscript;
+ int trunk;
+/* function: Print delta node if node->selector is set. */
+/* editscript indicates where the editscript is stored */
+/* trunk indicated whether this node is in trunk */
+{
+ static char emptych[] = EMPTYLOG;
+
+ register FILE *out;
+ char const *s;
+ size_t n;
+ struct branchhead const *newbranch;
+ struct buf branchnum;
+ char datebuf[datesize];
+
+ if (!node->selector)
+ return;
+
+ out = stdout;
+ aprintf(out,
+ "----------------------------\nrevision %s", node->num
+ );
+ if ( node->lockedby )
+ aprintf(out, "\tlocked by: %s;", node->lockedby);
+
+ aprintf(out, "\ndate: %s; author: %s; state: %s;",
+ date2str(node->date, datebuf),
+ node->author, node->state
+ );
+
+ if ( editscript )
+ if(trunk)
+ aprintf(out, insDelFormat,
+ editscript->deletelns, editscript->insertlns);
+ else
+ aprintf(out, insDelFormat,
+ editscript->insertlns, editscript->deletelns);
+
+ newbranch = node->branches;
+ if ( newbranch ) {
+ bufautobegin(&branchnum);
+ aputs("\nbranches:", out);
+ while( newbranch ) {
+ getbranchno(newbranch->hsh->num, &branchnum);
+ aprintf(out, " %s;", branchnum.string);
+ newbranch = newbranch->nextbranch;
+ }
+ bufautoend(&branchnum);
+ }
+
+ afputc('\n', out);
+ s = node->log.string;
+ if (!(n = node->log.size)) {
+ s = emptych;
+ n = sizeof(emptych)-1;
+ }
+ awrite(s, n, out);
+ if (s[n-1] != '\n')
+ afputc('\n', out);
+}
+
+
+
+
+
+ static struct hshentry const *
+readdeltalog()
+/* Function : get the log message and skip the text of a deltatext node.
+ * Return the delta found.
+ * Assumes the current lexeme is not yet in nexttok; does not
+ * advance nexttok.
+ */
+{
+ register struct hshentry * Delta;
+ struct buf logbuf;
+ struct cbuf cb;
+
+ if (eoflex())
+ fatserror("missing delta log");
+ nextlex();
+ if (!(Delta = getnum()))
+ fatserror("delta number corrupted");
+ getkeystring(Klog);
+ if (Delta->log.string)
+ fatserror("duplicate delta log");
+ bufautobegin(&logbuf);
+ cb = savestring(&logbuf);
+ Delta->log = bufremember(&logbuf, cb.size);
+
+ nextlex();
+ while (nexttok==ID && strcmp(NextString,Ktext)!=0)
+ ignorephrase();
+ getkeystring(Ktext);
+ Delta->insertlns = Delta->deletelns = 0;
+ if ( Delta != Head)
+ getscript(Delta);
+ else
+ readstring();
+ return Delta;
+}
+
+
+ static void
+getscript(Delta)
+struct hshentry * Delta;
+/* function: read edit script of Delta and count how many lines added */
+/* and deleted in the script */
+
+{
+ int ed; /* editor command */
+ declarecache;
+ register RILE *fin;
+ register int c;
+ register unsigned long i;
+ struct diffcmd dc;
+
+ fin = finptr;
+ setupcache(fin);
+ initdiffcmd(&dc);
+ while (0 <= (ed = getdiffcmd(fin,true,(FILE *)0,&dc)))
+ if (!ed)
+ Delta->deletelns += dc.nlines;
+ else {
+ /* skip scripted lines */
+ i = dc.nlines;
+ Delta->insertlns += i;
+ cache(fin);
+ do {
+ for (;;) {
+ cacheget(c);
+ switch (c) {
+ default:
+ continue;
+ case SDELIM:
+ cacheget(c);
+ if (c == SDELIM)
+ continue;
+ if (--i)
+ fatserror("unexpected end to edit script");
+ nextc = c;
+ uncache(fin);
+ return;
+ case '\n':
+ break;
+ }
+ break;
+ }
+ ++rcsline;
+ } while (--i);
+ uncache(fin);
+ }
+}
+
+
+
+
+
+
+
+ static void
+exttree(root)
+struct hshentry *root;
+/* function: select revisions , starting with root */
+
+{
+ struct branchhead const *newbranch;
+
+ if (root == nil) return;
+
+ root->selector = extractdelta(root);
+ root->log.string = nil;
+ exttree(root->next);
+
+ newbranch = root->branches;
+ while( newbranch ) {
+ exttree(newbranch->hsh);
+ newbranch = newbranch->nextbranch;
+ }
+}
+
+
+
+
+ static void
+getlocker(argv)
+char * argv;
+/* function : get the login names of lockers from command line */
+/* and store in lockerlist. */
+
+{
+ register char c;
+ struct lockers * newlocker;
+ argv--;
+ while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
+ c == '\n' || c == ';') ;
+ if ( c == '\0') {
+ lockerlist=nil;
+ return;
+ }
+
+ while( c != '\0' ) {
+ newlocker = talloc(struct lockers);
+ newlocker->lockerlink = lockerlist;
+ newlocker->login = argv;
+ lockerlist = newlocker;
+ while ( ( c = (*++argv)) != ',' && c != '\0' && c != ' '
+ && c != '\t' && c != '\n' && c != ';') ;
+ *argv = '\0';
+ if ( c == '\0' ) return;
+ while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
+ c == '\n' || c == ';') ;
+ }
+}
+
+
+
+ static void
+getauthor(argv)
+char *argv;
+/* function: get the author's name from command line */
+/* and store in authorlist */
+
+{
+ register c;
+ struct authors * newauthor;
+
+ argv--;
+ while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
+ c == '\n' || c == ';') ;
+ if ( c == '\0' ) {
+ authorlist = talloc(struct authors);
+ authorlist->login = getusername(false);
+ authorlist->nextauthor = nil;
+ return;
+ }
+
+ while( c != '\0' ) {
+ newauthor = talloc(struct authors);
+ newauthor->nextauthor = authorlist;
+ newauthor->login = argv;
+ authorlist = newauthor;
+ while( ( c = *++argv) != ',' && c != '\0' && c != ' '
+ && c != '\t' && c != '\n' && c != ';') ;
+ * argv = '\0';
+ if ( c == '\0') return;
+ while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
+ c == '\n' || c == ';') ;
+ }
+}
+
+
+
+
+ static void
+getstate(argv)
+char * argv;
+/* function : get the states of revisions from command line */
+/* and store in statelist */
+
+{
+ register char c;
+ struct stateattri *newstate;
+
+ argv--;
+ while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
+ c == '\n' || c == ';') ;
+ if ( c == '\0'){
+ warn("missing state attributes after -s options");
+ return;
+ }
+
+ while( c != '\0' ) {
+ newstate = talloc(struct stateattri);
+ newstate->nextstate = statelist;
+ newstate->status = argv;
+ statelist = newstate;
+ while( (c = (*++argv)) != ',' && c != '\0' && c != ' '
+ && c != '\t' && c != '\n' && c != ';') ;
+ *argv = '\0';
+ if ( c == '\0' ) return;
+ while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
+ c == '\n' || c == ';') ;
+ }
+}
+
+
+
+ static void
+trunclocks()
+/* Function: Truncate the list of locks to those that are held by the */
+/* id's on lockerlist. Do not truncate if lockerlist empty. */
+
+{
+ struct lockers const *plocker;
+ struct lock * plocked, * nextlocked;
+
+ if ( (lockerlist == nil) || (Locks == nil)) return;
+
+ /* shorten Locks to those contained in lockerlist */
+ plocked = Locks;
+ Locks = nil;
+ while( plocked != nil) {
+ plocker = lockerlist;
+ while((plocker != nil) && ( strcmp(plocker->login, plocked->login)!=0))
+ plocker = plocker->lockerlink;
+ nextlocked = plocked->nextlock;
+ if ( plocker != nil) {
+ plocked->nextlock = Locks;
+ Locks = plocked;
+ }
+ plocked = nextlocked;
+ }
+}
+
+
+
+ static void
+recentdate(root, pd)
+ struct hshentry const *root;
+ struct Datepairs *pd;
+/* function: Finds the delta that is closest to the cutoff date given by */
+/* pd among the revisions selected by exttree. */
+/* Successively narrows down the interval given by pd, */
+/* and sets the strtdate of pd to the date of the selected delta */
+{
+ struct branchhead const *newbranch;
+
+ if ( root == nil) return;
+ if (root->selector) {
+ if ( cmpnum(root->date, pd->strtdate) >= 0 &&
+ cmpnum(root->date, pd->enddate) <= 0)
+ VOID strcpy(pd->strtdate, root->date);
+ }
+
+ recentdate(root->next, pd);
+ newbranch = root->branches;
+ while( newbranch) {
+ recentdate(newbranch->hsh, pd);
+ newbranch = newbranch->nextbranch;
+ }
+}
+
+
+
+
+
+
+ static unsigned
+extdate(root)
+struct hshentry * root;
+/* function: select revisions which are in the date range specified */
+/* in duelst and datelist, start at root */
+/* Yield number of revisions selected, including those already selected. */
+{
+ struct branchhead const *newbranch;
+ struct Datepairs const *pdate;
+ unsigned revno;
+
+ if (!root)
+ return 0;
+
+ if ( datelist || duelst) {
+ pdate = datelist;
+ while( pdate ) {
+ if ( (pdate->strtdate)[0] == '\0' || cmpnum(root->date,pdate->strtdate) >= 0){
+ if ((pdate->enddate)[0] == '\0' || cmpnum(pdate->enddate,root->date) >= 0)
+ break;
+ }
+ pdate = pdate->dnext;
+ }
+ if ( pdate == nil) {
+ pdate = duelst;
+ for (;;) {
+ if (!pdate) {
+ root->selector = false;
+ break;
+ }
+ if ( cmpnum(root->date, pdate->strtdate) == 0)
+ break;
+ pdate = pdate->dnext;
+ }
+ }
+ }
+ revno = root->selector + extdate(root->next);
+
+ newbranch = root->branches;
+ while( newbranch ) {
+ revno += extdate(newbranch->hsh);
+ newbranch = newbranch->nextbranch;
+ }
+ return revno;
+}
+
+
+
+ static char
+extractdelta(pdelta)
+ struct hshentry const *pdelta;
+/* function: compare information of pdelta to the authorlist, lockerlist,*/
+/* statelist, revlist and yield true if pdelta is selected. */
+
+{
+ struct lock const *plock;
+ struct stateattri const *pstate;
+ struct authors const *pauthor;
+ struct Revpairs const *prevision;
+ unsigned length;
+
+ if ((pauthor = authorlist)) /* only certain authors wanted */
+ while (strcmp(pauthor->login, pdelta->author) != 0)
+ if (!(pauthor = pauthor->nextauthor))
+ return false;
+ if ((pstate = statelist)) /* only certain states wanted */
+ while (strcmp(pstate->status, pdelta->state) != 0)
+ if (!(pstate = pstate->nextstate))
+ return false;
+ if (lockflag) /* only locked revisions wanted */
+ for (plock = Locks; ; plock = plock->nextlock)
+ if (!plock)
+ return false;
+ else if (plock->delta == pdelta)
+ break;
+ if ((prevision = Revlst)) /* only certain revs or branches wanted */
+ for (;;) {
+ length = prevision->numfld;
+ if (
+ countnumflds(pdelta->num) == length+(length&1) &&
+ 0 <= compartial(pdelta->num, prevision->strtrev, length) &&
+ 0 <= compartial(prevision->endrev, pdelta->num, length)
+ )
+ break;
+ if (!(prevision = prevision->rnext))
+ return false;
+ }
+ return true;
+}
+
+
+
+ static void
+getdatepair(argv)
+ char * argv;
+/* function: get time range from command line and store in datelist if */
+/* a time range specified or in duelst if a time spot specified */
+
+{
+ register char c;
+ struct Datepairs * nextdate;
+ char const * rawdate;
+ int switchflag;
+
+ argv--;
+ while( ( c = (*++argv)) == ',' || c == ' ' || c == '\t' ||
+ c == '\n' || c == ';') ;
+ if ( c == '\0' ) {
+ warn("missing date/time after -d");
+ return;
+ }
+
+ while( c != '\0' ) {
+ switchflag = false;
+ nextdate = talloc(struct Datepairs);
+ if ( c == '<' ) { /* case: -d <date */
+ c = *++argv;
+ (nextdate->strtdate)[0] = '\0';
+ } else if (c == '>') { /* case: -d'>date' */
+ c = *++argv;
+ (nextdate->enddate)[0] = '\0';
+ switchflag = true;
+ } else {
+ rawdate = argv;
+ while( c != '<' && c != '>' && c != ';' && c != '\0')
+ c = *++argv;
+ *argv = '\0';
+ if ( c == '>' ) switchflag=true;
+ str2date(rawdate,
+ switchflag ? nextdate->enddate : nextdate->strtdate);
+ if ( c == ';' || c == '\0') { /* case: -d date */
+ VOID strcpy(nextdate->enddate,nextdate->strtdate);
+ nextdate->dnext = duelst;
+ duelst = nextdate;
+ goto end;
+ } else {
+ /* case: -d date< or -d date>; see switchflag */
+ while ( (c= *++argv) == ' ' || c=='\t' || c=='\n');
+ if ( c == ';' || c == '\0') {
+ /* second date missing */
+ if (switchflag)
+ *nextdate->strtdate= '\0';
+ else
+ *nextdate->enddate= '\0';
+ nextdate->dnext = datelist;
+ datelist = nextdate;
+ goto end;
+ }
+ }
+ }
+ rawdate = argv;
+ while( c != '>' && c != '<' && c != ';' && c != '\0')
+ c = *++argv;
+ *argv = '\0';
+ str2date(rawdate,
+ switchflag ? nextdate->strtdate : nextdate->enddate);
+ nextdate->dnext = datelist;
+ datelist = nextdate;
+ end:
+ if ( c == '\0') return;
+ while( (c = *++argv) == ';' || c == ' ' || c == '\t' || c =='\n');
+ }
+}
+
+
+
+ static void
+getnumericrev()
+/* function: get the numeric name of revisions which stored in revlist */
+/* and then stored the numeric names in Revlst */
+/* if branchflag, also add default branch */
+
+{
+ struct Revpairs * ptr, *pt;
+ unsigned n;
+ struct buf s, e;
+ char const *lrev;
+ struct buf const *rstart, *rend;
+
+ Revlst = nil;
+ ptr = revlist;
+ bufautobegin(&s);
+ bufautobegin(&e);
+ while( ptr ) {
+ n = 0;
+ rstart = &s;
+ rend = &e;
+
+ switch (ptr->numfld) {
+
+ case 1: /* -r rev */
+ if (expandsym(ptr->strtrev, &s)) {
+ rend = &s;
+ n = countnumflds(s.string);
+ if (!n && (lrev = tiprev())) {
+ bufscpy(&s, lrev);
+ n = countnumflds(lrev);
+ }
+ }
+ break;
+
+ case 2: /* -r rev- */
+ if (expandsym(ptr->strtrev, &s)) {
+ bufscpy(&e, s.string);
+ n = countnumflds(s.string);
+ (n<2 ? e.string : strrchr(e.string,'.'))[0] = 0;
+ }
+ break;
+
+ case 3: /* -r -rev */
+ if (expandsym(ptr->endrev, &e)) {
+ if ((n = countnumflds(e.string)) < 2)
+ bufscpy(&s, ".1");
+ else {
+ bufscpy(&s, e.string);
+ VOID strcpy(strrchr(s.string,'.'), ".1");
+ }
+ }
+ break;
+
+ default: /* -r rev1-rev2 */
+ if (
+ expandsym(ptr->strtrev, &s)
+ && expandsym(ptr->endrev, &e)
+ && checkrevpair(s.string, e.string)
+ ) {
+ n = countnumflds(s.string);
+ /* Swap if out of order. */
+ if (compartial(s.string,e.string,n) > 0) {
+ rstart = &e;
+ rend = &s;
+ }
+ }
+ break;
+ }
+
+ if (n) {
+ pt = ftalloc(struct Revpairs);
+ pt->numfld = n;
+ pt->strtrev = fstr_save(rstart->string);
+ pt->endrev = fstr_save(rend->string);
+ pt->rnext = Revlst;
+ Revlst = pt;
+ }
+ ptr = ptr->rnext;
+ }
+ /* Now take care of branchflag */
+ if (branchflag && (Dbranch||Head)) {
+ pt = ftalloc(struct Revpairs);
+ pt->strtrev = pt->endrev =
+ Dbranch ? Dbranch : fstr_save(partialno(&s,Head->num,1));
+ pt->rnext=Revlst; Revlst=pt;
+ pt->numfld = countnumflds(pt->strtrev);
+ }
+ bufautoend(&s);
+ bufautoend(&e);
+}
+
+
+
+ static int
+checkrevpair(num1,num2)
+ char const *num1, *num2;
+/* function: check whether num1, num2 are legal pair,i.e.
+ only the last field are different and have same number of
+ fields( if length <= 2, may be different if first field) */
+
+{
+ unsigned length = countnumflds(num1);
+
+ if (
+ countnumflds(num2) != length
+ || 2 < length && compartial(num1, num2, length-1) != 0
+ ) {
+ error("invalid branch or revision pair %s : %s", num1, num2);
+ return false;
+ }
+
+ return true;
+}
+
+
+
+ static void
+getrevpairs(argv)
+register char * argv;
+/* function: get revision or branch range from command line, and */
+/* store in revlist */
+
+{
+ register char c;
+ struct Revpairs * nextrevpair;
+ int separator;
+
+ c = *argv;
+
+ /* Support old ambiguous '-' syntax; this will go away. */
+ if (strchr(argv,':'))
+ separator = ':';
+ else {
+ if (strchr(argv,'-') && VERSION(5) <= RCSversion)
+ warn("`-' is obsolete in `-r%s'; use `:' instead", argv);
+ separator = '-';
+ }
+
+ for (;;) {
+ while (c==' ' || c=='\t' || c=='\n')
+ c = *++argv;
+ nextrevpair = talloc(struct Revpairs);
+ nextrevpair->rnext = revlist;
+ revlist = nextrevpair;
+ nextrevpair->numfld = 1;
+ nextrevpair->strtrev = argv;
+ for (;; c = *++argv) {
+ switch (c) {
+ default:
+ continue;
+ case '\0': case ' ': case '\t': case '\n':
+ case ',': case ';':
+ break;
+ case ':': case '-':
+ if (c == separator)
+ break;
+ continue;
+ }
+ break;
+ }
+ *argv = '\0';
+ while (c==' ' || c=='\t' || c=='\n')
+ c = *++argv;
+ if (c == separator) {
+ while( (c =(*++argv)) == ' ' || c == '\t' || c =='\n') ;
+ nextrevpair->endrev = argv;
+ for (;; c = *++argv) {
+ switch (c) {
+ default:
+ continue;
+ case '\0': case ' ': case '\t': case '\n':
+ case ',': case ';':
+ break;
+ case ':': case '-':
+ if (c == separator)
+ continue;
+ break;
+ }
+ break;
+ }
+ *argv = '\0';
+ while (c==' ' || c=='\t' || c =='\n')
+ c = *++argv;
+ nextrevpair->numfld =
+ !nextrevpair->endrev[0] ? 2 /* -rrev- */ :
+ !nextrevpair->strtrev[0] ? 3 /* -r-rev */ :
+ 4 /* -rrev1-rev2 */;
+ }
+ if (!c)
+ break;
+ if (c!=',' && c!=';')
+ error("missing `,' near `%c%s'", c, argv+1);
+ }
+}
OpenPOWER on IntegriCloud