summaryrefslogtreecommitdiffstats
path: root/usr.sbin/ctm
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/ctm')
-rw-r--r--usr.sbin/ctm/Makefile5
-rw-r--r--usr.sbin/ctm/Makefile.inc5
-rw-r--r--usr.sbin/ctm/README85
-rw-r--r--usr.sbin/ctm/ctm/Makefile24
-rw-r--r--usr.sbin/ctm/ctm/Makefile.depend19
-rw-r--r--usr.sbin/ctm/ctm/ctm.1335
-rw-r--r--usr.sbin/ctm/ctm/ctm.5182
-rw-r--r--usr.sbin/ctm/ctm/ctm.c331
-rw-r--r--usr.sbin/ctm/ctm/ctm.h163
-rw-r--r--usr.sbin/ctm/ctm/ctm_ed.c114
-rw-r--r--usr.sbin/ctm/ctm/ctm_input.c134
-rw-r--r--usr.sbin/ctm/ctm/ctm_pass1.c250
-rw-r--r--usr.sbin/ctm/ctm/ctm_pass2.c301
-rw-r--r--usr.sbin/ctm/ctm/ctm_pass3.c295
-rw-r--r--usr.sbin/ctm/ctm/ctm_passb.c142
-rw-r--r--usr.sbin/ctm/ctm/ctm_syntax.c67
-rw-r--r--usr.sbin/ctm/ctm_dequeue/Makefile13
-rw-r--r--usr.sbin/ctm/ctm_dequeue/Makefile.depend18
-rw-r--r--usr.sbin/ctm/ctm_dequeue/ctm_dequeue.c209
-rw-r--r--usr.sbin/ctm/ctm_rmail/Makefile10
-rw-r--r--usr.sbin/ctm/ctm_rmail/Makefile.depend18
-rw-r--r--usr.sbin/ctm/ctm_rmail/ctm_rmail.1510
-rw-r--r--usr.sbin/ctm/ctm_rmail/ctm_rmail.c672
-rw-r--r--usr.sbin/ctm/ctm_rmail/error.c102
-rw-r--r--usr.sbin/ctm/ctm_rmail/error.h5
-rw-r--r--usr.sbin/ctm/ctm_rmail/options.h139
-rw-r--r--usr.sbin/ctm/ctm_smail/Makefile13
-rw-r--r--usr.sbin/ctm/ctm_smail/Makefile.depend18
-rw-r--r--usr.sbin/ctm/ctm_smail/ctm_smail.c497
-rw-r--r--usr.sbin/ctm/mkCTM/Makefile25
-rw-r--r--usr.sbin/ctm/mkCTM/ctm_conf.cvs-cur9
-rw-r--r--usr.sbin/ctm/mkCTM/ctm_conf.ports-cur7
-rw-r--r--usr.sbin/ctm/mkCTM/ctm_conf.smp-cur7
-rw-r--r--usr.sbin/ctm/mkCTM/ctm_conf.src-cur9
-rw-r--r--usr.sbin/ctm/mkCTM/ctm_conf.src-special9
-rwxr-xr-xusr.sbin/ctm/mkCTM/dequeue6
-rw-r--r--usr.sbin/ctm/mkCTM/mkCTM188
-rw-r--r--usr.sbin/ctm/mkCTM/mkctm.c597
38 files changed, 5533 insertions, 0 deletions
diff --git a/usr.sbin/ctm/Makefile b/usr.sbin/ctm/Makefile
new file mode 100644
index 0000000..630aab0
--- /dev/null
+++ b/usr.sbin/ctm/Makefile
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+SUBDIR= ctm ctm_rmail ctm_smail ctm_dequeue
+
+.include <bsd.subdir.mk>
diff --git a/usr.sbin/ctm/Makefile.inc b/usr.sbin/ctm/Makefile.inc
new file mode 100644
index 0000000..c6c2c5a
--- /dev/null
+++ b/usr.sbin/ctm/Makefile.inc
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+.if exists(${.CURDIR}/../../Makefile.inc)
+.include "${.CURDIR}/../../Makefile.inc"
+.endif
diff --git a/usr.sbin/ctm/README b/usr.sbin/ctm/README
new file mode 100644
index 0000000..94c779a
--- /dev/null
+++ b/usr.sbin/ctm/README
@@ -0,0 +1,85 @@
+# ----------------------------------------------------------------------------
+# "THE BEER-WARE LICENSE" (Revision 42):
+# <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
+# can do whatever you want with this stuff. If we meet some day, and you think
+# this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+# ----------------------------------------------------------------------------
+#
+# $FreeBSD$
+#
+
+What will I not find in this file ?
+-----------------------------------
+Instructions on how to obtain FreeBSD via CTM.
+Contact <CTM@FreeBSD.org> for that.
+
+What is CTM ?
+-------------
+CTM was originally "CVS Through eMail", but has since changed scope to be
+much more general.
+CTM is now meant to be the definitive way to make and apply a delta between
+two versions of a directory tree.
+There are two parts to this, making the delta and applying it. These are two
+entirely different things. CTM concentrates the computation-burden on the
+generation of the deltas, as a delta very often is applied more times than
+it is made. Second CTM tries to make the minimal size delta.
+
+Why not use diff/patch ?
+------------------------
+Good question. Primarily because diff and patch doesn't do their job very
+well. They don't deal with binary files (in this case files with '\0' or
+'\0377' characters in them or files that doesn't end in '\n') which isn't
+a big surprise: they were made to deal with text-files only. As a second
+gripe, with patch you send the entire file to delete it. Not particular
+efficient.
+
+So what does CTM do exactly ?
+-----------------------------
+CTM will produce a file, (a delta) containing the instructions and data needed
+to take another copy of the tree from the old to the new status. CTM means to
+do this in the exact sense, and therefore the delta contains MD5 checksums to
+verify that the tree it is applied to is indeed in the state CTM expects.
+
+This means that if you have modified the tree locally, CTM might not be able
+to upgrade your copy.
+
+How do I make a CTM-delta ?
+---------------------------
+
+Read the source, and be prepared to have 2 copies of the tree; One is
+the reference ("From") tree, and the other is the delta ("To") tree.
+The mkCTM script will create the CTM diff of the differences between
+the reference tree and the delta tree. A lot of scratch space is
+required, and your machine will work hard.
+
+How do I apply a CTM-delta ?
+----------------------------
+You pass it to the 'ctm' command. You can pass a CTM-delta on stdin, or
+you can give the filename as an argument. If you do the latter, you make
+life a lot easier for your self, since the program can accept gzip'ed files
+and since it will not have to make a temporary copy of your file. You can
+specify multiple deltas at one time, they will be processed one at a time.
+
+The ctm command runs in a number of passes. It will process the entire
+input file in each pass, before commencing with the next pass.
+
+Pass 1 will validate that the input file is OK. The syntax, the data and
+the global MD5 checksum will be checked. If any of these fail, ctm will
+never be able to do anything with the file, so it will simply reject it.
+
+Pass 2 will validate that the directory tree is in the state expected by
+the CTM-delta. This is done by looking for files and directories which
+should/should not exists and by checking the MD5 checksums of files.
+
+Pass 3 will actually apply the delta.
+
+Should I delete the delta when I have applied it ?
+--------------------------------------------------
+No. You might want to selectively reconstruct a file latter on.
+
+Why is CTM not being maintained?
+--------------------------------
+Because CVSUP has improved on the concept quite a bit, and is now
+the method of choice.
+
+Poul-Henning
diff --git a/usr.sbin/ctm/ctm/Makefile b/usr.sbin/ctm/ctm/Makefile
new file mode 100644
index 0000000..dd27ed2
--- /dev/null
+++ b/usr.sbin/ctm/ctm/Makefile
@@ -0,0 +1,24 @@
+# ----------------------------------------------------------------------------
+# "THE BEER-WARE LICENSE" (Revision 42):
+# <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
+# can do whatever you want with this stuff. If we meet some day, and you think
+# this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+# ----------------------------------------------------------------------------
+#
+# $FreeBSD$
+
+PROG= ctm
+MAN= ctm.1 ctm.5
+SRCS= ctm.c ctm_input.c ctm_pass1.c ctm_pass2.c ctm_pass3.c \
+ ctm_passb.c ctm_syntax.c ctm_ed.c
+
+NOTYET= ctm_ed.c
+
+LIBADD= md
+
+WARNS?= 2
+
+.if exists(${.CURDIR}/../../Makefile.inc)
+.include "${.CURDIR}/../../Makefile.inc"
+.endif
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ctm/ctm/Makefile.depend b/usr.sbin/ctm/ctm/Makefile.depend
new file mode 100644
index 0000000..064e492
--- /dev/null
+++ b/usr.sbin/ctm/ctm/Makefile.depend
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libmd \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ctm/ctm/ctm.1 b/usr.sbin/ctm/ctm/ctm.1
new file mode 100644
index 0000000..6ba35c9
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm.1
@@ -0,0 +1,335 @@
+.\" ----------------------------------------------------------------------------
+.\" "THE BEER-WARE LICENSE" (Revision 42):
+.\" <joerg@FreeBSD.org> wrote this file. As long as you retain this notice you
+.\" can do whatever you want with this stuff. If we meet some day, and you think
+.\" this stuff is worth it, you can buy me a beer in return. Joerg Wunsch
+.\" ----------------------------------------------------------------------------
+.\"
+.\" This manual page is partially obtained from Poul-Hennings CTM README
+.\" file.
+.\"
+.\" CTM and ctm(1) by <phk@FreeBSD.org>
+.\"
+.\" $FreeBSD$
+.\"
+.Dd December 14, 2015
+.Dt CTM 1
+.Os
+.Sh NAME
+.Nm ctm
+.Nd source code mirror program
+.Sh SYNOPSIS
+.Nm
+.Op Fl cFklquv
+.Op Fl b Ar basedir
+.Op Fl B Ar backup-file
+.Op Fl e Ar include-regex
+.Op Fl t Ar tar-command
+.Op Fl T Ar tmpdir
+.Op Fl V Ar level
+.Op Fl x Ar exclude-regex
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility was originally
+.Dq Cvs Through eMail ,
+but now instead it seems more fitting to call it
+.Dq Current Through eMail .
+.Pp
+The
+.Nm
+utility is now meant to be the definitive way to make and apply a delta between
+two versions of a directory tree.
+.Pp
+There are two parts to this, making the delta and applying it.
+These are two
+entirely different things.
+.Ss Usage
+To apply a CTM delta, you pass it to the
+.Nm
+command.
+You can pass a CTM delta on stdin, or you can give the
+filename as an argument.
+If you do the latter, you make life a lot
+easier for your self, since the program can accept gzip'ed files and
+since it will not have to make a temporary copy of your file.
+You can
+specify multiple deltas at one time, they will be processed one at a
+time.
+Deltas that are already applied will be ignored.
+.Pp
+The
+.Nm
+command runs in a number of passes.
+It will process the entire
+input file in each pass, before commencing with the next pass.
+.Pp
+Before working on a file
+.Ar name
+.Nm
+first checks for the existence of the file
+.Ar name.ctm .
+If this file exists,
+.Nm
+works on it instead.
+.Pp
+Pass 1 will verify that the input file is OK.
+The syntax, the data
+and the global MD5 checksum will be checked.
+If any of these fail,
+.Nm
+will simply reject the input file.
+.Pp
+Pass 2 will validate that the directory tree is in the state expected by
+the CTM delta.
+This is done by looking for files and directories which
+should/should not exist and by checking the MD5 checksums of files.
+.Pp
+If a
+.Ar backup-file
+had been specified using the
+.Fl B
+option, all files that would be modified by this
+.Nm
+invocation are backed up
+to this file using the archiver command specified by the
+.Fl t
+option.
+The default archiver command is
+.Nm "tar -rf %s -T -" .
+.Pp
+Pass 3 will actually apply the delta.
+.Pp
+The list of files that would be modified by
+.Nm
+is subject to filtering regular expressions specified
+using the
+.Fl e
+and
+.Fl x
+options.
+The
+.Fl e
+and
+.Fl x
+options are applied in order of appearance on the command line.
+The last
+filter that matched a given file name determines whether the file would be
+operated on or left alone by
+.Nm .
+.Pp
+The
+.Nm
+utility
+will extract the file hierarchy below its working directory.
+Absolute
+filenames or filenames containing references through
+.Sq Pa .\&
+and
+.Sq Pa ..\&
+are explicitly prohibited as a security measure.
+.Ss Options
+.Bl -tag -width indent
+.It Fl b Ar basedir
+Prepend the path
+.Ar basedir
+to every filename.
+.It Fl B Ar backup-file
+Backup all files that would be modified by this CTM run to
+.Ar backup-file .
+If any filters are specified using the
+.Fl e
+and
+.Fl x
+options, then the final set of files backed up are those that would be
+modified by CTM after the filters are applied.
+.It Fl c
+Check it out, do not do anything.
+.It Fl e Ar regular_expression
+Match each name in the CTM file against
+.Ar regular_expression ,
+and if it matches process the file, otherwise leave it alone.
+There may be
+any number of these options.
+Use of this option disables the
+.Pa .ctm_status
+sequence number checks.
+For example, the expression
+.Ic ^usr.sbin/ctm
+for example, will select the
+.Pa usr.sbin/ctm
+source directory and all pathnames under it.
+.Pp
+Pathnames can be disabled from being considered by CTM using the
+.Fl x
+option.
+.It Fl F
+Force.
+.It Fl k
+Keep files and directories and do not remove them even if the CTM file
+specifies they are to be removed.
+If the
+.Fl B
+option is specified, these files and directories will not be backed up.
+.It Fl l
+List files that would be modified by this invocation of CTM and the
+actions that would be performed on them.
+Use of the
+.Fl l
+option disables the
+.Pa .ctm_status
+checks and integrity checks on the source tree being operated on.
+The
+.Fl l
+option can be combined with the
+.Fl e
+and
+.Fl x
+options to determine which files would be modified by the given set of
+command line options.
+.It Fl q
+Tell us less.
+.It Fl t Ar tar-command
+Use
+.Ar tar-command
+instead of the default archiver
+.Nm tar .
+This option takes effect only if a backup file had been specified using the
+.Fl B
+option.
+A %s in the tar command will be replaced by the name of the backup
+file.
+.It Fl T Ar tmpdir
+Put temporary files under
+.Ar tmpdir .
+.It Fl u
+Set modification time of created and modified files to the CTM delta
+creation time.
+.It Fl v
+Tell us more.
+.It Fl V Ar level
+Tell us more.
+.Ar Level
+is the level of verbosity.
+.It Fl x Ar regular_expression
+Match each name in the CTM file against
+.Ar regular_expression
+and if it matches, leave the file alone.
+There may be any number of these
+options.
+Use of this option disables the
+.Pa .ctm_status
+sequence number checks.
+.Pp
+Pathnames can be selected for CTM's consideration using the
+.Fl e
+option.
+.El
+.Sh SECURITY
+On its own, CTM is an insecure protocol
+- there is no authentication performed that the
+changes applied to the source code were sent by a
+trusted party, and so care should be taken if the
+CTM deltas are obtained via an unauthenticated
+medium such as regular email.
+It is a relatively simple matter for an attacker
+to forge a CTM delta to replace or precede the
+legitimate one and insert malicious code into your
+source tree.
+If the legitimate delta is somehow prevented from
+arriving, this will go unnoticed until a later
+delta attempts to touch the same file, at which
+point the MD5 checksum will fail.
+.Pp
+To remedy this insecurity, CTM pieces generated by
+FreeBSD.org are cryptographically signed in a
+format compatible with the GNU Privacy Guard
+utility, available in /usr/ports/security/gpg, and
+the Pretty Good Privacy v5 utility,
+/usr/ports/security/pgp5.
+The relevant public key can be obtained by
+fingering ctm@FreeBSD.org.
+.Pp
+CTM deltas which are thus signed cannot be
+undetectably altered by an attacker.
+Therefore it is recommended that you make use of
+GPG or PGP5 to verify the signatures if you
+receive your CTM deltas via email.
+.Sh ENVIRONMENT
+.Ev TMPDIR ,
+if set to a pathname, will cause ctm to use that pathname
+as the location of temporary file.
+See
+.Xr tempnam 3 ,
+for more details on this.
+The same effect may be achieved with the
+.Fl T
+flag.
+.Sh FILES
+.Pa .ctm_status
+contains the sequence number of the last CTM delta applied.
+Changing
+or removing this file will greatly confuse
+.Nm .
+.Pp
+Using the
+.Fl e
+and
+.Fl x
+options can update a partial subset of the source tree and causes sources
+to be in an inconsistent state.
+It is assumed that you know what you are
+doing when you use these options.
+.Sh EXAMPLES
+.Bd -literal
+cd ~cvs
+/usr/sbin/ctm ~ctm/cvs-*
+.Ed
+.Pp
+To extract and patch all sources under `lib'
+.Bd -literal
+cd ~/lib-srcs
+/usr/sbin/ctm -e '^lib' ~ctm/src-cur*
+.Ed
+.Sh DIAGNOSTICS
+Numerous messages, hopefully self-explanatory.
+The
+.Dq noise level
+can be adjusted with the
+.Fl q ,
+.Fl v
+and
+.Fl V
+options.
+.Sh SEE ALSO
+.Xr ctm_dequeue 1 ,
+.Xr ctm_rmail 1 ,
+.Xr ctm_smail 1 ,
+.Xr ctm 5
+.Rs
+.%B "The FreeBSD Handbook"
+.%T "Using CTM"
+.%U http://www.FreeBSD.org/doc/en_US.ISO8859-1/books/handbook/ctm.html
+.Re
+.Rs
+.%T "Miscellaneous CTM on FreeBSD Resources"
+.%U http://ctm.berklix.org
+.Re
+.Sh HISTORY
+Initial trials were run during the work on
+.Fx 1.1.5 ,
+and many bugs and
+methods were hashed out.
+.Pp
+The
+.Nm
+command appeared in
+.Fx 2.1 .
+.Sh AUTHORS
+.An -nosplit
+The CTM system has been designed and implemented by
+.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org .
+.Pp
+.An Joerg Wunsch Aq Mt joerg@FreeBSD.org
+wrote this man-page.
diff --git a/usr.sbin/ctm/ctm/ctm.5 b/usr.sbin/ctm/ctm/ctm.5
new file mode 100644
index 0000000..e48b09f
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm.5
@@ -0,0 +1,182 @@
+.\" ----------------------------------------------------------------------------
+.\" "THE BEER-WARE LICENSE" (Revision 42):
+.\" <joerg@FreeBSD.org> wrote this file. As long as you retain this notice you
+.\" can do whatever you want with this stuff. If we meet some day, and you think
+.\" this stuff is worth it, you can buy me a beer in return. Joerg Wunsch
+.\" ----------------------------------------------------------------------------
+.\"
+.\" This manual page is partially obtained from Poul-Hennings CTM README
+.\" file.
+.\"
+.\" CTM and ctm(1) by <phk@FreeBSD.org>
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 25, 1995
+.Dt CTM 5
+.Os
+.Sh NAME
+.Nm ctm
+.Nd source code mirror system
+.Sh DESCRIPTION
+The
+.Nm
+transfers data in a specific file format, called a CTM delta.
+.Pp
+CTM deltas consist of control lines and data chunks.
+Each control
+line starts with the letters
+.Dq CTM ,
+followed by a CTM statement and control data, and ends with a '\en'
+character.
+.Pp
+Data chunks always belong to the preceding control line, and the
+last field on that control line is the number of bytes in the data
+chunk.
+A trailing newline '\en' character follows each data chunk, this
+newline is not part of the chunk and is not included in the count.
+.Pp
+The CTM statements are as follows.
+.Bl -tag -width indent
+.It _BEGIN Ar version name number timestamp prefix
+This is the overall begin of a CTM delta file.
+The
+.Ar version
+field must match the program version
+(currently 2.0).
+.Ar Name
+is the name and
+.Ar number
+the sequence number of the CTM service, it is matched against the file
+.Pa .ctm_status
+to see if the delta has already been applied.
+.Ar Timestamp
+contains the year, month, day, hour, minute, and second of the
+time of delta creation for reference
+(followed by the letter
+.Sq Z
+meaning this is a UTC timestamp).
+The
+.Ar prefix
+field is currently not implemented.
+.It _END Ar md5
+This statement ends the CTM delta, the global
+.Ar md5
+checksum is matched against the MD5 checksum of the entire delta, up to
+and including the space (0x20) character following ``_END''.
+.It \&FM Ar name uid gid mode md5 count
+Make the file
+.Ar name ,
+the original file had the uid
+.Ar uid
+(numerical, decimal),
+the gid
+.Ar gid
+(numerical, decimal),
+mode
+.Ar mode
+(numerical, octal),
+and the MD5 checksum
+.Ar md5 .
+.Pp
+The following
+.Ar count
+bytes data are the contents of the new file.
+.It \&FS Ar name uid gid mode md5before md5after count
+Substitute the contents of file
+.Ar name ,
+the original file had the new uid
+.Ar uid
+(numerical, decimal),
+the new gid
+.Ar gid
+(numerical, decimal),
+new mode
+.Ar mode
+(numerical, octal),
+the old MD5 checksum
+.Ar md5before ,
+and the new MD5 checksum
+.Ar md5after .
+.Pp
+The following
+.Ar count
+bytes data are the contents of the new file.
+.Pp
+File substitution is used if the commands to edit a file would exceed
+the total file length, so substituting it is more efficient.
+.It \&FN Ar name uid gid mode md5before md5after count
+Edit the file
+.Ar name .
+The arguments are as above, but the data sections contains an
+.Xr diff 1
+-n script which should be applied to the file in question.
+.It \&FR Ar name md5
+Remove the file
+.Ar name ,
+which must match the MD5 checksum
+.Ar md5 .
+.It \&AS Ar name uid gid mode
+The original file
+.Ar name
+changed its owner to
+.Ar uid ,
+its group to
+.Ar gid ,
+and/or its mode to
+.Ar mode .
+.It \&DM Ar name uid gid mode
+The directory
+.Ar name
+is to be created, it had originally the owner
+.Ar uid ,
+group
+.Ar gid ,
+and mode
+.Ar mode .
+.It \&DR Ar name
+The directory
+.Ar name
+is to be removed.
+.El
+.Sh EXAMPLES
+In the following example, long lines have been folded to make them
+printable
+(marked by backslashes).
+.Bd -literal
+CTM_BEGIN 2.0 cvs-cur 485 19950324214652Z .
+CTMFR src/sys/gnu/i386/isa/scd.c,v 5225f13aa3c7e458f9dd0d4bb637b18d
+CTMFR src/sys/gnu/i386/isa/scdreg.h,v e5af42b8a06f2c8030b93a7d71afb223
+CTMDM src/sys/gnu/i386/isa/Attic 0 552 775
+CTMFS .ctm_status 545 552 664 d9ccd2a84a9dbb8db56ba85663adebf0 \\
+e2a10c6f66428981782a0a18a789ee2e 12
+cvs-cur 485
+
+CTMFN CVSROOT/commitlogs/gnu 545 552 664 \\
+5d7bc3549140d860bd9641b5782c002d 7fb04ed84b48160c9b8eea84b4c0b6e3 394
+a6936 21
+ache 95/03/24 09:59:50
+
+ Modified: gnu/lib/libdialog kernel.c prgbox.c
+ Log:
+[...]
+CTM_END 74ddd298d76215ae45a077a4b6a74e9c
+.Ed
+.Sh SEE ALSO
+.Xr ctm 1 ,
+.Xr ctm_rmail 1 ,
+.Xr ed 1
+.Sh HISTORY
+Initial trials ran during the
+.Fx 1.1.5 ,
+and many bugs and
+methods were hashed out.
+The CTM system has been made publicly available in
+.Fx 2.1 .
+.Sh AUTHORS
+.An -nosplit
+The CTM system has been designed and implemented by
+.An Poul-Henning Kamp Aq Mt phk@FreeBSD.org .
+.Pp
+.An Joerg Wunsch Aq Mt joerg@FreeBSD.org
+wrote this man-page.
diff --git a/usr.sbin/ctm/ctm/ctm.c b/usr.sbin/ctm/ctm/ctm.c
new file mode 100644
index 0000000..0ea4895
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm.c
@@ -0,0 +1,331 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ *
+ * $FreeBSD$
+ *
+ * This is the client program of 'CTM'. It will apply a CTM-patch to a
+ * collection of files.
+ *
+ * Options we'd like to see:
+ *
+ * -a Attempt best effort.
+ * -d <int> Debug TBD.
+ * -m <mail-addr> Email me instead.
+ * -r <name> Reconstruct file.
+ * -R <file> Read list of files to reconstruct.
+ *
+ * Options we have:
+ * -b <dir> Base-dir
+ * -B <file> Backup to tar-file.
+ * -t Tar command (default as in TARCMD).
+ * -c Check it out, don't do anything.
+ * -F Force
+ * -q Tell us less.
+ * -T <tmpdir>. Temporary files.
+ * -u Set all file modification times to the timestamp
+ * -v Tell us more.
+ * -V <level> Tell us more level = number of -v
+ * -k Keep files and directories that would have been removed.
+ * -l List actions.
+ *
+ * Options we don't actually use:
+ * -p Less paranoid.
+ * -P Paranoid.
+ */
+
+#define EXTERN /* */
+#include <paths.h>
+#include "ctm.h"
+
+#define CTM_STATUS ".ctm_status"
+
+extern int Proc(char *, unsigned applied);
+
+int
+main(int argc, char **argv)
+{
+ int stat=0, err=0;
+ int c;
+ unsigned applied = 0;
+ FILE *statfile;
+ struct CTM_Filter *nfilter = NULL; /* new filter */
+ u_char * basedir;
+
+ basedir = NULL;
+ Verbose = 1;
+ Paranoid = 1;
+ SetTime = 0;
+ KeepIt = 0;
+ ListIt = 0;
+ BackupFile = NULL;
+ TarCmd = TARCMD;
+ LastFilter = FilterList = NULL;
+ TmpDir = getenv("TMPDIR");
+ if (TmpDir == NULL)
+ TmpDir = strdup(_PATH_TMP);
+ setbuf(stderr,0);
+ setbuf(stdout,0);
+
+ while((c=getopt(argc,argv,"ab:B:cd:e:Fklm:pPqr:R:t:T:uV:vx:")) != -1) {
+ switch (c) {
+ case 'b': basedir = optarg; break; /* Base Directory */
+ case 'B': BackupFile = optarg; break;
+ case 'c': CheckIt++; break; /* Only check it */
+ case 'F': Force = 1; break;
+ case 'k': KeepIt++; break; /* Don't do removes */
+ case 'l': ListIt++; break; /* Only list actions and files */
+ case 'p': Paranoid--; break; /* Less Paranoid */
+ case 'P': Paranoid++; break; /* More Paranoid */
+ case 'q': Verbose--; break; /* Quiet */
+ case 't': TarCmd = optarg; break; /* archiver command */
+ case 'T': TmpDir = optarg; break; /* set temporary directory */
+ case 'u': SetTime++; break; /* Set timestamp on files */
+ case 'v': Verbose++; break; /* Verbose */
+ case 'V': sscanf(optarg,"%d", &c); /* Verbose */
+ Verbose += c;
+ break;
+ case 'e': /* filter expressions */
+ case 'x':
+ if (NULL == (nfilter = Malloc(sizeof(struct CTM_Filter)))) {
+ warnx("out of memory for expressions: \"%s\"", optarg);
+ stat++;
+ break;
+ }
+
+ (void) memset(nfilter, 0, sizeof(struct CTM_Filter));
+
+ if (0 != (err =
+ regcomp(&nfilter->CompiledRegex, optarg, REG_NOSUB))) {
+
+ char errmsg[128];
+
+ regerror(err, &nfilter->CompiledRegex, errmsg,
+ sizeof(errmsg));
+ warnx("regular expression: \"%s\"", errmsg);
+ stat++;
+ break;
+ }
+
+ /* note whether the filter enables or disables on match */
+ nfilter->Action =
+ (('e' == c) ? CTM_FILTER_ENABLE : CTM_FILTER_DISABLE);
+
+ /* link in the expression into the list */
+ nfilter->Next = NULL;
+ if (NULL == FilterList) {
+ LastFilter = FilterList = nfilter; /* init head and tail */
+ } else { /* place at tail */
+ LastFilter->Next = nfilter;
+ LastFilter = nfilter;
+ }
+ break;
+ case ':':
+ warnx("option '%c' requires an argument",optopt);
+ stat++;
+ break;
+ case '?':
+ warnx("option '%c' not supported",optopt);
+ stat++;
+ break;
+ default:
+ warnx("option '%c' not yet implemented",optopt);
+ break;
+ }
+ }
+
+ if(stat) {
+ warnx("%d errors during option processing",stat);
+ return Exit_Pilot;
+ }
+ stat = Exit_Done;
+ argc -= optind;
+ argv += optind;
+
+ if (basedir == NULL) {
+ Buffer = (u_char *)Malloc(BUFSIZ + strlen(SUBSUFF) +1);
+ CatPtr = Buffer;
+ *Buffer = '\0';
+ } else {
+ Buffer = (u_char *)Malloc(strlen(basedir)+ BUFSIZ + strlen(SUBSUFF) +1);
+ strcpy(Buffer, basedir);
+ CatPtr = Buffer + strlen(basedir);
+ if (CatPtr[-1] != '/') {
+ strcat(Buffer, "/");
+ CatPtr++;
+ }
+ }
+ strcat(Buffer, CTM_STATUS);
+
+ if(ListIt)
+ applied = 0;
+ else
+ if((statfile = fopen(Buffer, "r")) == NULL) {
+ if (Verbose > 0)
+ warnx("warning: %s not found", Buffer);
+ } else {
+ fscanf(statfile, "%*s %u", &applied);
+ fclose(statfile);
+ }
+
+ if(!argc)
+ stat |= Proc("-", applied);
+
+ while(argc-- && stat == Exit_Done) {
+ stat |= Proc(*argv++, applied);
+ stat &= ~(Exit_Version | Exit_NoMatch);
+ }
+
+ if(stat == Exit_Done)
+ stat = Exit_OK;
+
+ if(Verbose > 0)
+ warnx("exit(%d)",stat);
+
+ if (FilterList)
+ for (nfilter = FilterList; nfilter; ) {
+ struct CTM_Filter *tmp = nfilter->Next;
+ Free(nfilter);
+ nfilter = tmp;
+ }
+ return stat;
+}
+
+int
+Proc(char *filename, unsigned applied)
+{
+ FILE *f;
+ int i;
+ char *p = strrchr(filename,'.');
+
+ if(!strcmp(filename,"-")) {
+ p = 0;
+ f = stdin;
+ } else if(p && (!strcmp(p,".gz") || !strcmp(p,".Z"))) {
+ p = alloca(20 + strlen(filename));
+ strcpy(p,"gunzip < ");
+ strcat(p,filename);
+ f = popen(p,"r");
+ if(!f) { warn("%s", p); return Exit_Garbage; }
+ } else {
+ p = 0;
+ f = fopen(filename,"r");
+ }
+ if(!f) {
+ warn("%s", filename);
+ return Exit_Garbage;
+ }
+
+ if(Verbose > 1)
+ fprintf(stderr,"Working on <%s>\n",filename);
+
+ Delete(FileName);
+ FileName = String(filename);
+
+ /* If we cannot seek, we're doomed, so copy to a tmp-file in that case */
+ if(!p && -1 == fseek(f,0,SEEK_END)) {
+ char *fn;
+ FILE *f2;
+ int fd;
+
+ if (asprintf(&fn, "%s/CTMclient.XXXXXXXXXX", TmpDir) == -1) {
+ fprintf(stderr, "Cannot allocate memory\n");
+ fclose(f);
+ return Exit_Broke;
+ }
+ if ((fd = mkstemp(fn)) == -1 || (f2 = fdopen(fd, "w+")) == NULL) {
+ warn("%s", fn);
+ free(fn);
+ if (fd != -1)
+ close(fd);
+ fclose(f);
+ return Exit_Broke;
+ }
+ unlink(fn);
+ if (Verbose > 0)
+ fprintf(stderr,"Writing tmp-file \"%s\"\n",fn);
+ free(fn);
+ while(EOF != (i=getc(f)))
+ if(EOF == putc(i,f2)) {
+ fclose(f2);
+ return Exit_Broke;
+ }
+ fclose(f);
+ f = f2;
+ }
+
+ if(!p)
+ rewind(f);
+
+ if((i=Pass1(f, applied)))
+ goto exit_and_close;
+
+ if(ListIt) {
+ i = Exit_Done;
+ goto exit_and_close;
+ }
+
+ if(!p) {
+ rewind(f);
+ } else {
+ pclose(f);
+ f = popen(p,"r");
+ if(!f) { warn("%s", p); return Exit_Broke; }
+ }
+
+ i=Pass2(f);
+
+ if(!p) {
+ rewind(f);
+ } else {
+ pclose(f);
+ f = popen(p,"r");
+ if(!f) { warn("%s", p); return Exit_Broke; }
+ }
+
+ if(i) {
+ if((!Force) || (i & ~Exit_Forcible))
+ goto exit_and_close;
+ }
+
+ if(CheckIt) {
+ if (Verbose > 0)
+ fprintf(stderr,"All checks out ok.\n");
+ i = Exit_Done;
+ goto exit_and_close;
+ }
+
+ /* backup files if requested */
+ if(BackupFile) {
+
+ i = PassB(f);
+
+ if(!p) {
+ rewind(f);
+ } else {
+ pclose(f);
+ f = popen(p,"r");
+ if(!f) { warn("%s", p); return Exit_Broke; }
+ }
+ }
+
+ i=Pass3(f);
+
+exit_and_close:
+ if(!p)
+ fclose(f);
+ else
+ pclose(f);
+
+ if(i)
+ return i;
+
+ if (Verbose > 0)
+ fprintf(stderr,"All done ok\n");
+
+ return Exit_Done;
+}
diff --git a/usr.sbin/ctm/ctm/ctm.h b/usr.sbin/ctm/ctm/ctm.h
new file mode 100644
index 0000000..b733539
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm.h
@@ -0,0 +1,163 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <md5.h>
+#include <regex.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/time.h>
+
+#define VERSION "2.0"
+
+#define SUBSUFF ".ctm"
+#define TMPSUFF ".ctmtmp"
+#define TARCMD "tar -rf %s -T -"
+
+/* The fields... */
+#define CTM_F_MASK 0xff
+#define CTM_F_Name 0x01
+#define CTM_F_Uid 0x02
+#define CTM_F_Gid 0x03
+#define CTM_F_Mode 0x04
+#define CTM_F_MD5 0x05
+#define CTM_F_Count 0x06
+#define CTM_F_Bytes 0x07
+
+/* The qualifiers... */
+#define CTM_Q_MASK 0xff00
+#define CTM_Q_Name_File 0x0100
+#define CTM_Q_Name_Dir 0x0200
+#define CTM_Q_Name_New 0x0400
+#define CTM_Q_Name_Subst 0x0800
+#define CTM_Q_MD5_After 0x0100
+#define CTM_Q_MD5_Before 0x0200
+#define CTM_Q_MD5_Chunk 0x0400
+#define CTM_Q_MD5_Force 0x0800
+
+struct CTM_Syntax {
+ char *Key; /* CTM key for operation */
+ int *List; /* List of operations */
+ };
+
+extern struct CTM_Syntax Syntax[];
+
+struct CTM_Filter {
+ struct CTM_Filter *Next; /* next filter in the list */
+ int Action; /* enable or disable */
+ regex_t CompiledRegex; /* compiled regex */
+};
+
+#define CTM_FILTER_DISABLE 0
+#define CTM_FILTER_ENABLE 1
+
+#define Malloc malloc
+#define Free free
+#define Delete(foo) if (!foo) ; else {Free(foo); foo = 0; }
+#define String(foo) strdup(foo)
+
+#ifndef EXTERN
+# define EXTERN extern
+#endif
+EXTERN u_char *Version;
+EXTERN u_char *Name;
+EXTERN u_char *Nbr;
+EXTERN u_char *TimeStamp;
+EXTERN u_char *Prefix;
+EXTERN u_char *FileName;
+EXTERN u_char *TmpDir;
+EXTERN u_char *CatPtr;
+EXTERN u_char *Buffer;
+EXTERN u_char *BackupFile;
+EXTERN u_char *TarCmd;
+
+/*
+ * Paranoid -- Just in case they should be after us...
+ * 0 not at all.
+ * 1 normal.
+ * 2 somewhat.
+ * 3 you bet!.
+ *
+ * Verbose -- What to tell mom...
+ * 0 Nothing which wouldn't surprise.
+ * 1 Normal.
+ * 2 Show progress '.'.
+ * 3 Show progress names, and actions.
+ * 4 even more...
+ * and so on
+ *
+ * ExitCode -- our Epitaph
+ * 0 Perfect, all input digested, no problems
+ * 1 Bad input, no point in retrying.
+ * 2 Pilot error, commandline problem &c
+ * 4 Out of resources.
+ * 8 Destination-tree not correct.
+ * 16 Destination-tree not correct, can force.
+ * 32 Internal problems.
+ *
+ */
+
+EXTERN int Paranoid;
+EXTERN int Verbose;
+EXTERN int Exit;
+EXTERN int Force;
+EXTERN int CheckIt;
+EXTERN int KeepIt;
+EXTERN int ListIt;
+EXTERN int SetTime;
+EXTERN struct timeval Times[2];
+EXTERN struct CTM_Filter *FilterList;
+EXTERN struct CTM_Filter *LastFilter;
+
+#define Exit_OK 0
+#define Exit_Garbage 1
+#define Exit_Pilot 2
+#define Exit_Broke 4
+#define Exit_NotOK 8
+#define Exit_Forcible 16
+#define Exit_Mess 32
+#define Exit_Done 64
+#define Exit_Version 128
+#define Exit_NoMatch 256
+
+void Fatal_(int ln, char *fn, char *kind);
+#define Fatal(foo) Fatal_(__LINE__,__FILE__,foo)
+#define Assert() Fatal_(__LINE__,__FILE__,"Assert failed.")
+#define WRONG {Assert(); return Exit_Mess;}
+
+u_char * Ffield(FILE *fd, MD5_CTX *ctx,u_char term);
+u_char * Fname(FILE *fd, MD5_CTX *ctx,u_char term,int qual, int verbose);
+
+int Fbytecnt(FILE *fd, MD5_CTX *ctx, u_char term);
+
+u_char * Fdata(FILE *fd, int u_chars, MD5_CTX *ctx);
+
+#define GETFIELD(p,q) if(!((p)=Ffield(fd,&ctx,(q)))) return BADREAD
+#define GETFIELDCOPY(p,q) if(!((p)=Ffield(fd,&ctx,(q)))) return BADREAD; else p=String(p)
+#define GETBYTECNT(p,q) if(0 >((p)= Fbytecnt(fd,&ctx,(q)))) return BADREAD
+#define GETDATA(p,q) if(!((p) = Fdata(fd,(q),&ctx))) return BADREAD
+#define GETNAMECOPY(p,q,r,v) if(!((p)=Fname(fd,&ctx,(q),(r),(v)))) return BADREAD; else p=String(p)
+
+int Pass1(FILE *fd, unsigned applied);
+int Pass2(FILE *fd);
+int PassB(FILE *fd);
+int Pass3(FILE *fd);
+
+int ctm_edit(u_char *script, int length, char *filein, char *fileout);
diff --git a/usr.sbin/ctm/ctm/ctm_ed.c b/usr.sbin/ctm/ctm/ctm_ed.c
new file mode 100644
index 0000000..e3ec464
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm_ed.c
@@ -0,0 +1,114 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include "ctm.h"
+
+int
+ctm_edit(u_char *script, int length, char *filein, char *fileout)
+{
+ u_char *ep, cmd;
+ int ln, ln2, iln, ret=0, c;
+ FILE *fi=0,*fo=0;
+
+ fi = fopen(filein,"r");
+ if(!fi) {
+ warn("%s", filein);
+ return 8;
+ }
+
+ fo = fopen(fileout,"w");
+ if(!fo) {
+ warn("%s", fileout);
+ fclose(fi);
+ return 4;
+ }
+ iln = 1;
+ for(ep=script;ep < script+length;) {
+ cmd = *ep++;
+ if(cmd != 'a' && cmd != 'd') { ret = 1; goto bye; }
+ ln = 0;
+ while(isdigit(*ep)) {
+ ln *= 10;
+ ln += (*ep++ - '0');
+ }
+ if(*ep++ != ' ') { ret = 1; goto bye; }
+ ln2 = 0;
+ while(isdigit(*ep)) {
+ ln2 *= 10;
+ ln2 += (*ep++ - '0');
+ }
+ if(*ep++ != '\n') { ret = 1; goto bye; }
+
+
+ if(cmd == 'd') {
+ while(iln < ln) {
+ c = getc(fi);
+ if(c == EOF) { ret = 1; goto bye; }
+ putc(c,fo);
+ if(c == '\n')
+ iln++;
+ }
+ while(ln2) {
+ c = getc(fi);
+ if(c == EOF) { ret = 1; goto bye; }
+ if(c != '\n')
+ continue;
+ ln2--;
+ iln++;
+ }
+ continue;
+ }
+ if(cmd == 'a') {
+ while(iln <= ln) {
+ c = getc(fi);
+ if(c == EOF) { ret = 1; goto bye; }
+ putc(c,fo);
+ if(c == '\n')
+ iln++;
+ }
+ while(ln2) {
+ c = *ep++;
+ putc(c,fo);
+ if(c != '\n')
+ continue;
+ ln2--;
+ }
+ continue;
+ }
+ ret = 1;
+ goto bye;
+ }
+ while(1) {
+ c = getc(fi);
+ if(c == EOF) break;
+ putc(c,fo);
+ }
+ ret = 0;
+bye:
+ if(fi) {
+ if(fclose(fi) != 0) {
+ warn("%s", filein);
+ ret = 1;
+ }
+ }
+ if(fo) {
+ if(fflush(fo) != 0) {
+ warn("%s", fileout);
+ ret = 1;
+ }
+ if(fclose(fo) != 0) {
+ warn("%s", fileout);
+ ret = 1;
+ }
+ }
+ return ret;
+}
diff --git a/usr.sbin/ctm/ctm/ctm_input.c b/usr.sbin/ctm/ctm/ctm_input.c
new file mode 100644
index 0000000..32166d3
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm_input.c
@@ -0,0 +1,134 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include "ctm.h"
+
+/*---------------------------------------------------------------------------*/
+void
+Fatal_(int ln, char *fn, char *kind)
+{
+ if(Verbose > 2)
+ fprintf(stderr,"Fatal error. (%s:%d)\n",fn,ln);
+ fprintf(stderr,"%s Fatal error: %s\n",FileName, kind);
+}
+#define Fatal(foo) Fatal_(__LINE__,__FILE__,foo)
+#define Assert() Fatal_(__LINE__,__FILE__,"Assert failed.")
+
+/*---------------------------------------------------------------------------*/
+/* get next field, check that the terminating whitespace is what we expect */
+u_char *
+Ffield(FILE *fd, MD5_CTX *ctx,u_char term)
+{
+ static u_char buf[BUFSIZ];
+ int i,l;
+
+ for(l=0;;) {
+ if((i=getc(fd)) == EOF) {
+ Fatal("Truncated patch.");
+ return 0;
+ }
+ buf[l++] = i;
+ if(isspace(i))
+ break;
+ if(l >= sizeof buf) {
+ Fatal("Corrupt patch.");
+ printf("Token is too long.\n");
+ return 0;
+ }
+ }
+ buf[l] = '\0';
+ MD5Update(ctx,buf,l);
+ if(buf[l-1] != term) {
+ Fatal("Corrupt patch.");
+ fprintf(stderr,"Expected \"%s\" but didn't find it {%02x}.\n",
+ term == '\n' ? "\\n" : " ",buf[l-1]);
+ if(Verbose > 4)
+ fprintf(stderr,"{%s}\n",buf);
+ return 0;
+ }
+ buf[--l] = '\0';
+ if(Verbose > 4)
+ fprintf(stderr,"<%s>\n",buf);
+ return buf;
+}
+
+int
+Fbytecnt(FILE *fd, MD5_CTX *ctx, u_char term)
+{
+ u_char *p,*q;
+ int u_chars=0;
+
+ p = Ffield(fd,ctx,term);
+ if(!p) return -1;
+ for(q=p;*q;q++) {
+ if(!isdigit(*q)) {
+ Fatal("Bytecount contains non-digit.");
+ return -1;
+ }
+ u_chars *= 10;
+ u_chars += (*q - '0');
+ }
+ return u_chars;
+}
+
+u_char *
+Fdata(FILE *fd, int u_chars, MD5_CTX *ctx)
+{
+ u_char *p = Malloc(u_chars+1);
+
+ if(u_chars+1 != fread(p,1,u_chars+1,fd)) {
+ Fatal("Truncated patch.");
+ return 0;
+ }
+ MD5Update(ctx,p,u_chars+1);
+ if(p[u_chars] != '\n') {
+ if(Verbose > 3)
+ printf("FileData wasn't followed by a newline.\n");
+ Fatal("Corrupt patch.");
+ return 0;
+ }
+ p[u_chars] = '\0';
+ return p;
+}
+
+/*---------------------------------------------------------------------------*/
+/* get the filename in the next field, prepend BaseDir and give back the result
+ strings. The sustitute filename is return (the one with the suffix SUBSUFF)
+ if it exists and the qualifier contains CTM_Q_Name_Subst
+ NOTA: Buffer is already initialize with BaseDir, CatPtr is the insertion
+ point on this buffer + the length test in Ffield() is enough for Fname() */
+
+u_char *
+Fname(FILE *fd, MD5_CTX *ctx,u_char term,int qual, int verbose)
+{
+ u_char * p;
+ struct stat st;
+
+ if ((p = Ffield(fd,ctx,term)) == NULL) return(NULL);
+
+ strcpy(CatPtr, p);
+
+ if (!(qual & CTM_Q_Name_Subst)) return(Buffer);
+
+ p = Buffer + strlen(Buffer);
+
+ strcat(Buffer, SUBSUFF);
+
+ if ( -1 == stat(Buffer, &st) ) {
+ *p = '\0';
+ } else {
+ if(verbose > 2)
+ fprintf(stderr,"Using %s as substitute file\n", Buffer);
+ }
+
+ return (Buffer);
+}
diff --git a/usr.sbin/ctm/ctm/ctm_pass1.c b/usr.sbin/ctm/ctm/ctm_pass1.c
new file mode 100644
index 0000000..026a320
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm_pass1.c
@@ -0,0 +1,250 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include "ctm.h"
+#define BADREAD 1
+
+/*---------------------------------------------------------------------------*/
+/* Pass1 -- Validate the incoming CTM-file.
+ */
+
+int
+Pass1(FILE *fd, unsigned applied)
+{
+ u_char *p,*q;
+ MD5_CTX ctx;
+ int i,j,sep,cnt;
+ u_char *md5=0,*name=0,*trash=0;
+ struct CTM_Syntax *sp;
+ int slashwarn=0, match=0, total_matches=0;
+ unsigned current;
+ char md5_1[33];
+
+ if(Verbose>3)
+ printf("Pass1 -- Checking integrity of incoming CTM-patch\n");
+ MD5Init (&ctx);
+
+ GETFIELD(p,' '); /* CTM_BEGIN */
+ if(strcmp(p,"CTM_BEGIN")) {
+ Fatal("Probably not a CTM-patch at all.");
+ if(Verbose>3)
+ fprintf(stderr,"Expected \"CTM_BEGIN\" got \"%s\".\n",p);
+ return 1;
+ }
+
+ GETFIELDCOPY(Version,' '); /* <Version> */
+ if(strcmp(Version,VERSION)) {
+ Fatal("CTM-patch is wrong version.");
+ if(Verbose>3)
+ fprintf(stderr,"Expected \"%s\" got \"%s\".\n",VERSION,p);
+ return 1;
+ }
+
+ GETFIELDCOPY(Name,' '); /* <Name> */
+ GETFIELDCOPY(Nbr,' '); /* <Nbr> */
+ GETFIELDCOPY(TimeStamp,' '); /* <TimeStamp> */
+ GETFIELDCOPY(Prefix,'\n'); /* <Prefix> */
+
+ sscanf(Nbr, "%u", &current);
+ if (FilterList || ListIt)
+ current = 0; /* ignore if -l or if filters are present */
+ if(current && current <= applied) {
+ if(Verbose > 0)
+ fprintf(stderr,"Delta number %u is already applied; ignoring.\n",
+ current);
+ return Exit_Version;
+ }
+
+ for(;;) {
+ Delete(md5);
+ Delete(name);
+ Delete(trash);
+ cnt = -1;
+ /* if a filter list is defined we assume that all pathnames require
+ an action opposite to that requested by the first filter in the
+ list.
+ If no filter is defined, all pathnames are assumed to match. */
+ match = (FilterList ? !(FilterList->Action) : CTM_FILTER_ENABLE);
+
+ GETFIELD(p,' '); /* CTM_something */
+
+ if (p[0] != 'C' || p[1] != 'T' || p[2] != 'M') {
+ Fatal("Expected CTM keyword.");
+ fprintf(stderr,"Got [%s]\n",p);
+ return 1;
+ }
+
+ if(!strcmp(p+3,"_END"))
+ break;
+
+ for(sp=Syntax;sp->Key;sp++)
+ if(!strcmp(p+3,sp->Key))
+ goto found;
+ Fatal("Expected CTM keyword.");
+ fprintf(stderr,"Got [%s]\n",p);
+ return 1;
+ found:
+ if(Verbose > 5)
+ fprintf(stderr,"%s ",sp->Key);
+ for(i=0;(j = sp->List[i]);i++) {
+ if (sp->List[i+1] && (sp->List[i+1] & CTM_F_MASK) != CTM_F_Bytes)
+ sep = ' ';
+ else
+ sep = '\n';
+
+ if(Verbose > 5)
+ fprintf(stderr," %x(%d)",sp->List[i],sep);
+
+ switch (j & CTM_F_MASK) {
+ case CTM_F_Name: /* XXX check for garbage and .. */
+ GETFIELDCOPY(name,sep);
+ j = strlen(name);
+ if(name[j-1] == '/' && !slashwarn) {
+ fprintf(stderr,"Warning: contains trailing slash\n");
+ slashwarn++;
+ }
+ if (name[0] == '/') {
+ Fatal("Absolute paths are illegal.");
+ return Exit_Mess;
+ }
+ q = name;
+ for (;;) {
+ if (q[0] == '.' && q[1] == '.')
+ if (q[2] == '/' || q[2] == '\0') {
+ Fatal("Paths containing '..' are illegal.");
+ return Exit_Mess;
+ }
+ if ((q = strchr(q, '/')) == NULL)
+ break;
+ q++;
+ }
+
+ /* if we have been asked to `keep' files then skip
+ removes; i.e. we don't match these entries at
+ all. */
+ if (KeepIt &&
+ (!strcmp(sp->Key,"DR") || !strcmp(sp->Key,"FR"))) {
+ match = CTM_FILTER_DISABLE;
+ break;
+ }
+
+ /* If filter expression have been defined, match the
+ path name against the expression list. */
+
+ if (FilterList) {
+ struct CTM_Filter *filter;
+
+ for (filter = FilterList; filter;
+ filter = filter->Next) {
+ if (0 == regexec(&filter->CompiledRegex, name,
+ 0, 0, 0))
+ /* if the name matches, adopt the
+ action */
+ match = filter->Action;
+ }
+ }
+
+ /* Add up the total number of matches */
+ total_matches += match;
+ break;
+ case CTM_F_Uid:
+ GETFIELD(p,sep);
+ while(*p) {
+ if(!isdigit(*p)) {
+ Fatal("Non-digit in uid.");
+ return 32;
+ }
+ p++;
+ }
+ break;
+ case CTM_F_Gid:
+ GETFIELD(p,sep);
+ while(*p) {
+ if(!isdigit(*p)) {
+ Fatal("Non-digit in gid.");
+ return 32;
+ }
+ p++;
+ }
+ break;
+ case CTM_F_Mode:
+ GETFIELD(p,sep);
+ while(*p) {
+ if(!isdigit(*p)) {
+ Fatal("Non-digit in mode.");
+ return 32;
+ }
+ p++;
+ }
+ break;
+ case CTM_F_MD5:
+ if(j & CTM_Q_MD5_Chunk) {
+ GETFIELDCOPY(md5,sep); /* XXX check for garbage */
+ } else if(j & CTM_Q_MD5_Before) {
+ GETFIELD(p,sep); /* XXX check for garbage */
+ } else if(j & CTM_Q_MD5_After) {
+ GETFIELD(p,sep); /* XXX check for garbage */
+ } else {
+ fprintf(stderr,"List = 0x%x\n",j);
+ Fatal("Unqualified MD5.");
+ return 32;
+ }
+ break;
+ case CTM_F_Count:
+ GETBYTECNT(cnt,sep);
+ break;
+ case CTM_F_Bytes:
+ if(cnt < 0) WRONG
+ GETDATA(trash,cnt);
+ p = MD5Data(trash,cnt,md5_1);
+ if(md5 && strcmp(md5,p)) {
+ Fatal("Internal MD5 failed.");
+ return Exit_Garbage;
+ default:
+ fprintf(stderr,"List = 0x%x\n",j);
+ Fatal("List had garbage.");
+ return Exit_Garbage;
+ }
+ }
+ }
+ if(Verbose > 5)
+ putc('\n',stderr);
+ if(ListIt && match)
+ printf("> %s %s\n", sp->Key, name);
+ }
+
+ Delete(md5);
+ Delete(name);
+ Delete(trash);
+
+ q = MD5End (&ctx,md5_1);
+ if(Verbose > 2)
+ printf("Expecting Global MD5 <%s>\n",q);
+ GETFIELD(p,'\n'); /* <MD5> */
+ if(Verbose > 2)
+ printf("Reference Global MD5 <%s>\n",p);
+ if(strcmp(q,p)) {
+ Fatal("MD5 sum doesn't match.");
+ fprintf(stderr,"\tI have:<%s>\n",q);
+ fprintf(stderr,"\tShould have been:<%s>\n",p);
+ return Exit_Garbage;
+ }
+ if (-1 != getc(fd)) {
+ if(!Force) {
+ Fatal("Trailing junk in CTM-file. Can Force with -F.");
+ return 16;
+ }
+ }
+ if ((Verbose > 1) && (0 == total_matches))
+ printf("No matches in \"%s\"\n", FileName);
+ return (total_matches ? Exit_OK : Exit_NoMatch);
+}
diff --git a/usr.sbin/ctm/ctm/ctm_pass2.c b/usr.sbin/ctm/ctm/ctm_pass2.c
new file mode 100644
index 0000000..a0b857a
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm_pass2.c
@@ -0,0 +1,301 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include "ctm.h"
+#define BADREAD 32
+
+/*---------------------------------------------------------------------------*/
+/* Pass2 -- Validate the incoming CTM-file.
+ */
+
+int
+Pass2(FILE *fd)
+{
+ u_char *p,*q,*md5=0;
+ MD5_CTX ctx;
+ int i,j,sep,cnt,fdesc;
+ u_char *trash=0,*name=0;
+ struct CTM_Syntax *sp;
+ struct stat st;
+ int ret = 0;
+ int match = 0;
+ char md5_1[33];
+ struct CTM_Filter *filter;
+ FILE *ed = NULL;
+ static char *template = NULL;
+
+ if(Verbose>3)
+ printf("Pass2 -- Checking if CTM-patch will apply\n");
+ MD5Init (&ctx);
+
+ GETFIELD(p,' '); if(strcmp("CTM_BEGIN",p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Version,p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Name,p)) WRONG
+ /* XXX Lookup name in /etc/ctm,conf, read stuff */
+ GETFIELD(p,' '); if(strcmp(Nbr,p)) WRONG
+ /* XXX Verify that this is the next patch to apply */
+ GETFIELD(p,' '); if(strcmp(TimeStamp,p)) WRONG
+ GETFIELD(p,'\n'); if(strcmp(Prefix,p)) WRONG
+ /* XXX drop or use ? */
+
+ for(;;) {
+ Delete(trash);
+ Delete(name);
+ Delete(md5);
+ cnt = -1;
+
+ /* if a filter list was specified, check file name against
+ the filters specified
+ if no filter was given operate on all files. */
+ match = (FilterList ?
+ !(FilterList->Action) : CTM_FILTER_ENABLE);
+
+ GETFIELD(p,' ');
+
+ if (p[0] != 'C' || p[1] != 'T' || p[2] != 'M') WRONG
+
+ if(!strcmp(p+3,"_END"))
+ break;
+
+ for(sp=Syntax;sp->Key;sp++)
+ if(!strcmp(p+3,sp->Key))
+ goto found;
+ WRONG
+ found:
+ for(i=0;(j = sp->List[i]);i++) {
+ if (sp->List[i+1] && (sp->List[i+1] & CTM_F_MASK) != CTM_F_Bytes)
+ sep = ' ';
+ else
+ sep = '\n';
+
+ switch (j & CTM_F_MASK) {
+ case CTM_F_Name:
+ GETNAMECOPY(name,sep,j,0);
+ /* If `keep' was specified, we won't remove any files,
+ so don't check if the file exists */
+ if (KeepIt &&
+ (!strcmp(sp->Key,"FR") || !strcmp(sp->Key,"DR"))) {
+ match = CTM_FILTER_DISABLE;
+ break;
+ }
+
+ for (filter = FilterList; filter; filter = filter->Next) if (0 == regexec(&filter->CompiledRegex, name,
+ 0, 0, 0)) {
+ match = filter->Action;
+ }
+
+ if (CTM_FILTER_DISABLE == match)
+ break; /* should ignore this file */
+
+ /* XXX Check DR DM rec's for parent-dir */
+ if(j & CTM_Q_Name_New) {
+ /* XXX Check DR FR rec's for item */
+ if(-1 != stat(name,&st)) {
+ fprintf(stderr," %s: %s exists.\n",
+ sp->Key,name);
+ ret |= Exit_Forcible;
+ }
+ break;
+ }
+ if(-1 == stat(name,&st)) {
+ fprintf(stderr," %s: %s doesn't exist.\n",
+ sp->Key,name);
+ if (sp->Key[1] == 'R')
+ ret |= Exit_Forcible;
+ else
+ ret |= Exit_NotOK;
+ break;
+ }
+ if (SetTime && getuid() && (getuid() != st.st_uid)) {
+ fprintf(stderr,
+ " %s: %s not mine, cannot set time.\n",
+ sp->Key,name);
+ ret |= Exit_NotOK;
+ }
+ if (j & CTM_Q_Name_Dir) {
+ if((st.st_mode & S_IFMT) != S_IFDIR) {
+ fprintf(stderr,
+ " %s: %s exist, but isn't dir.\n",
+ sp->Key,name);
+ ret |= Exit_NotOK;
+ }
+ break;
+ }
+ if (j & CTM_Q_Name_File) {
+ if((st.st_mode & S_IFMT) != S_IFREG) {
+ fprintf(stderr,
+ " %s: %s exist, but isn't file.\n",
+ sp->Key,name);
+ ret |= Exit_NotOK;
+ }
+ break;
+ }
+ break;
+ case CTM_F_Uid:
+ case CTM_F_Gid:
+ case CTM_F_Mode:
+ GETFIELD(p,sep);
+ break;
+ case CTM_F_MD5:
+ if(!name) WRONG
+ if(j & CTM_Q_MD5_Before) {
+ char *tmp;
+ GETFIELD(p,sep);
+ if(match && (st.st_mode & S_IFMT) == S_IFREG &&
+ (tmp = MD5File(name,md5_1)) != NULL &&
+ strcmp(tmp,p)) {
+ fprintf(stderr," %s: %s md5 mismatch.\n",
+ sp->Key,name);
+ GETFIELDCOPY(md5,sep);
+ if(md5 != NULL && strcmp(tmp,md5) == 0) {
+ fprintf(stderr," %s: %s already applied.\n",
+ sp->Key,name);
+ match = CTM_FILTER_DISABLE;
+ } else if(j & CTM_Q_MD5_Force) {
+ if(Force)
+ fprintf(stderr," Can and will force.\n");
+ else
+ fprintf(stderr," Could have forced.\n");
+ ret |= Exit_Forcible;
+ } else {
+ ret |= Exit_NotOK;
+ }
+ }
+ break;
+ } else if(j & CTM_Q_MD5_After) {
+ if(md5 == NULL) {
+ GETFIELDCOPY(md5,sep);
+ }
+ break;
+ }
+ /* Unqualified MD5 */
+ WRONG
+ break;
+ case CTM_F_Count:
+ GETBYTECNT(cnt,sep);
+ break;
+ case CTM_F_Bytes:
+ if(cnt < 0) WRONG
+ GETDATA(trash,cnt);
+ if (!match)
+ break;
+ if (!template) {
+ if (asprintf(&template, "%s/CTMclientXXXXXX",
+ TmpDir) == -1) {
+ fprintf(stderr, " %s: malloc failed.\n",
+ sp->Key);
+ ret |= Exit_Mess;
+ return ret;
+ }
+ }
+ if(!strcmp(sp->Key,"FN")) {
+ if ((p = strdup(template)) == NULL) {
+ fprintf(stderr, " %s: malloc failed.\n",
+ sp->Key);
+ ret |= Exit_Mess;
+ return ret;
+ }
+ if ((fdesc = mkstemp(p)) == -1) {
+ fprintf(stderr, " %s: mkstemp failed.\n",
+ sp->Key);
+ ret |= Exit_Mess;
+ Free(p);
+ return ret;
+ }
+ if (close(fdesc) == -1) {
+ fprintf(stderr, " %s: close failed.\n",
+ sp->Key);
+ ret |= Exit_Mess;
+ unlink(p);
+ Free(p);
+ return ret;
+ }
+ j = ctm_edit(trash,cnt,name,p);
+ if(j) {
+ fprintf(stderr," %s: %s edit returned %d.\n",
+ sp->Key,name,j);
+ ret |= j;
+ unlink(p);
+ Free(p);
+ return ret;
+ } else if(strcmp(md5,MD5File(p,md5_1))) {
+ fprintf(stderr," %s: %s edit fails.\n",
+ sp->Key,name);
+ ret |= Exit_Mess;
+ unlink(p);
+ Free(p);
+ return ret;
+ }
+ unlink(p);
+ Free(p);
+ } else if (!strcmp(sp->Key,"FE")) {
+ if ((p = strdup(template)) == NULL) {
+ fprintf(stderr, " %s: malloc failed.\n",
+ sp->Key);
+ ret |= Exit_Mess;
+ return ret;
+ }
+ if ((fdesc = mkstemp(p)) == -1) {
+ fprintf(stderr, " %s: mkstemp failed.\n",
+ sp->Key);
+ ret |= Exit_Mess;
+ Free(p);
+ return ret;
+ }
+ if (close(fdesc) == -1) {
+ fprintf(stderr, " %s: close failed.\n",
+ sp->Key);
+ ret |= Exit_Mess;
+ unlink(p);
+ Free(p);
+ return ret;
+ }
+ ed = popen("ed","w");
+ if (!ed) {
+ WRONG
+ }
+ fprintf(ed,"e %s\n", name);
+ if (cnt != fwrite(trash,1,cnt,ed)) {
+ warn("%s", name);
+ pclose(ed);
+ WRONG
+ }
+ fprintf(ed,"w %s\n",p);
+ if (pclose(ed)) {
+ warn("%s", p);
+ WRONG
+ }
+ if(strcmp(md5,MD5File(p,md5_1))) {
+ fprintf(stderr,"%s %s MD5 didn't come out right\n",
+ sp->Key, name);
+ WRONG
+ }
+ unlink(p);
+ Free(p);
+ }
+
+ break;
+ default: WRONG
+ }
+ }
+ }
+
+ Delete(trash);
+ Delete(name);
+ Delete(md5);
+
+ q = MD5End (&ctx,md5_1);
+ GETFIELD(p,'\n'); /* <MD5> */
+ if(strcmp(q,p)) WRONG
+ if (-1 != getc(fd)) WRONG
+ return ret;
+}
diff --git a/usr.sbin/ctm/ctm/ctm_pass3.c b/usr.sbin/ctm/ctm/ctm_pass3.c
new file mode 100644
index 0000000..34c6d87c1d
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm_pass3.c
@@ -0,0 +1,295 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include "ctm.h"
+#define BADREAD 32
+
+/*---------------------------------------------------------------------------*/
+/* Pass3 -- Validate the incoming CTM-file.
+ */
+
+int
+settime(const char *name, const struct timeval *times)
+{
+ if (SetTime)
+ if (utimes(name,times)) {
+ warn("utimes(): %s", name);
+ return -1;
+ }
+ return 0;
+}
+
+int
+Pass3(FILE *fd)
+{
+ u_char *p,*q,buf[BUFSIZ];
+ MD5_CTX ctx;
+ int i,j,sep,cnt;
+ u_char *md5=0,*md5before=0,*trash=0,*name=0,*uid=0,*gid=0,*mode=0;
+ struct CTM_Syntax *sp;
+ FILE *ed=0;
+ struct stat st;
+ char md5_1[33];
+ int match=0;
+ struct timeval times[2];
+ struct CTM_Filter *filter = NULL;
+ if(Verbose>3)
+ printf("Pass3 -- Applying the CTM-patch\n");
+ MD5Init (&ctx);
+
+ GETFIELD(p,' '); if(strcmp("CTM_BEGIN",p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Version,p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Name,p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Nbr,p)) WRONG
+ GETFIELD(p,' '); if(strcmp(TimeStamp,p)) WRONG
+ GETFIELD(p,'\n'); if(strcmp(Prefix,p)) WRONG
+
+ /*
+ * This would be cleaner if mktime() worked in UTC rather than
+ * local time.
+ */
+ if (SetTime) {
+ struct tm tm;
+ char *tz;
+ char buf[5];
+ int i;
+
+#define SUBSTR(off,len) strncpy(buf, &TimeStamp[off], len), buf[len] = '\0'
+#define WRONGDATE { fprintf(stderr, " %s failed date validation\n",\
+ TimeStamp); WRONG}
+
+ if (strlen(TimeStamp) != 15 || TimeStamp[14] != 'Z') WRONGDATE
+ for (i = 0; i < 14; i++)
+ if (!isdigit(TimeStamp[i])) WRONGDATE
+
+ tz = getenv("TZ");
+ if (setenv("TZ", "UTC", 1) < 0) WRONG
+ tzset();
+
+ tm.tm_isdst = tm.tm_gmtoff = 0;
+
+ SUBSTR(0, 4);
+ tm.tm_year = atoi(buf) - 1900;
+ SUBSTR(4, 2);
+ tm.tm_mon = atoi(buf) - 1;
+ if (tm.tm_mon < 0 || tm.tm_mon > 11) WRONGDATE
+ SUBSTR(6, 2);
+ tm.tm_mday = atoi(buf);
+ if (tm.tm_mday < 1 || tm.tm_mday > 31) WRONG;
+ SUBSTR(8, 2);
+ tm.tm_hour = atoi(buf);
+ if (tm.tm_hour > 24) WRONGDATE
+ SUBSTR(10, 2);
+ tm.tm_min = atoi(buf);
+ if (tm.tm_min > 59) WRONGDATE
+ SUBSTR(12, 2);
+ tm.tm_sec = atoi(buf);
+ if (tm.tm_min > 62) WRONGDATE /* allow leap seconds */
+
+ times[0].tv_sec = times[1].tv_sec = mktime(&tm);
+ if (times[0].tv_sec == -1) WRONGDATE
+ times[0].tv_usec = times[1].tv_usec = 0;
+
+ if (tz) {
+ if (setenv("TZ", tz, 1) < 0) WRONGDATE
+ } else {
+ unsetenv("TZ");
+ }
+ }
+
+ for(;;) {
+ Delete(md5);
+ Delete(uid);
+ Delete(gid);
+ Delete(mode);
+ Delete(md5before);
+ Delete(trash);
+ Delete(name);
+ cnt = -1;
+
+ GETFIELD(p,' ');
+
+ if (p[0] != 'C' || p[1] != 'T' || p[2] != 'M') WRONG
+
+ if(!strcmp(p+3,"_END"))
+ break;
+
+ for(sp=Syntax;sp->Key;sp++)
+ if(!strcmp(p+3,sp->Key))
+ goto found;
+ WRONG
+ found:
+ for(i=0;(j = sp->List[i]);i++) {
+ if (sp->List[i+1] && (sp->List[i+1] & CTM_F_MASK) != CTM_F_Bytes)
+ sep = ' ';
+ else
+ sep = '\n';
+
+ switch (j & CTM_F_MASK) {
+ case CTM_F_Name: GETNAMECOPY(name,sep,j, Verbose); break;
+ case CTM_F_Uid: GETFIELDCOPY(uid,sep); break;
+ case CTM_F_Gid: GETFIELDCOPY(gid,sep); break;
+ case CTM_F_Mode: GETFIELDCOPY(mode,sep); break;
+ case CTM_F_MD5:
+ if(j & CTM_Q_MD5_Before)
+ GETFIELDCOPY(md5before,sep);
+ else
+ GETFIELDCOPY(md5,sep);
+ break;
+ case CTM_F_Count: GETBYTECNT(cnt,sep); break;
+ case CTM_F_Bytes: GETDATA(trash,cnt); break;
+ default: WRONG
+ }
+ }
+ /* XXX This should go away. Disallow trailing '/' */
+ j = strlen(name)-1;
+ if(name[j] == '/') name[j] = '\0';
+
+ /*
+ * If a filter list is specified, run thru the filter list and
+ * match `name' against filters. If the name matches, set the
+ * required action to that specified in the filter.
+ * The default action if no filterlist is given is to match
+ * everything.
+ */
+
+ match = (FilterList ? !(FilterList->Action) : CTM_FILTER_ENABLE);
+ for (filter = FilterList; filter; filter = filter->Next) {
+ if (0 == regexec(&filter->CompiledRegex, name,
+ 0, 0, 0)) {
+ match = filter->Action;
+ }
+ }
+
+ if (CTM_FILTER_DISABLE == match) /* skip file if disabled */
+ continue;
+
+ if (Verbose > 0)
+ fprintf(stderr,"> %s %s\n",sp->Key,name);
+ if(!strcmp(sp->Key,"FM") || !strcmp(sp->Key, "FS")) {
+ i = open(name,O_WRONLY|O_CREAT|O_TRUNC,0666);
+ if(i < 0) {
+ warn("%s", name);
+ WRONG
+ }
+ if(cnt != write(i,trash,cnt)) {
+ warn("%s", name);
+ WRONG
+ }
+ close(i);
+ if(strcmp(md5,MD5File(name,md5_1))) {
+ fprintf(stderr," %s %s MD5 didn't come out right\n",
+ sp->Key,name);
+ WRONG
+ }
+ if (settime(name,times)) WRONG
+ continue;
+ }
+ if(!strcmp(sp->Key,"FE")) {
+ ed = popen("ed","w");
+ if(!ed) {
+ WRONG
+ }
+ fprintf(ed,"e %s\n",name);
+ if(cnt != fwrite(trash,1,cnt,ed)) {
+ warn("%s", name);
+ pclose(ed);
+ WRONG
+ }
+ fprintf(ed,"w %s\n",name);
+ if(pclose(ed)) {
+ warn("ed");
+ WRONG
+ }
+ if(strcmp(md5,MD5File(name,md5_1))) {
+ fprintf(stderr," %s %s MD5 didn't come out right\n",
+ sp->Key,name);
+ WRONG
+ }
+ if (settime(name,times)) WRONG
+ continue;
+ }
+ if(!strcmp(sp->Key,"FN")) {
+ strcpy(buf,name);
+ strcat(buf,TMPSUFF);
+ i = ctm_edit(trash,cnt,name,buf);
+ if(i) {
+ fprintf(stderr," %s %s Edit failed with code %d.\n",
+ sp->Key,name,i);
+ WRONG
+ }
+ if(strcmp(md5,MD5File(buf,md5_1))) {
+ fprintf(stderr," %s %s Edit failed MD5 check.\n",
+ sp->Key,name);
+ WRONG
+ }
+ if (rename(buf,name) == -1)
+ WRONG
+ if (settime(name,times)) WRONG
+ continue;
+ }
+ if(!strcmp(sp->Key,"DM")) {
+ if(0 > mkdir(name,0777)) {
+ sprintf(buf,"mkdir -p %s",name);
+ system(buf);
+ }
+ if(0 > stat(name,&st) || ((st.st_mode & S_IFMT) != S_IFDIR)) {
+ fprintf(stderr,"<%s> mkdir failed\n",name);
+ WRONG
+ }
+ if (settime(name,times)) WRONG
+ continue;
+ }
+ if(!strcmp(sp->Key,"FR")) {
+ if (KeepIt) {
+ if (Verbose > 1)
+ printf("<%s> not removed\n", name);
+ }
+ else if (0 != unlink(name)) {
+ fprintf(stderr,"<%s> unlink failed\n",name);
+ if (!Force)
+ WRONG
+ }
+ continue;
+ }
+ if(!strcmp(sp->Key,"DR")) {
+ /*
+ * We cannot use rmdir() because we do not get the directories
+ * in '-depth' order (cvs-cur.0018.gz for examples)
+ */
+ if (KeepIt) {
+ if (Verbose > 1) {
+ printf("<%s> not removed\n", name);
+ }
+ } else {
+ sprintf(buf,"rm -rf %s",name);
+ system(buf);
+ }
+ continue;
+ }
+ WRONG
+ }
+
+ Delete(md5);
+ Delete(uid);
+ Delete(gid);
+ Delete(mode);
+ Delete(md5before);
+ Delete(trash);
+ Delete(name);
+
+ q = MD5End (&ctx,md5_1);
+ GETFIELD(p,'\n');
+ if(strcmp(q,p)) WRONG
+ if (-1 != getc(fd)) WRONG
+ return 0;
+}
diff --git a/usr.sbin/ctm/ctm/ctm_passb.c b/usr.sbin/ctm/ctm/ctm_passb.c
new file mode 100644
index 0000000..ee3a69c5f
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm_passb.c
@@ -0,0 +1,142 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <koshy@india.hp.com> wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return. Joseph Koshy
+ * ----------------------------------------------------------------------------
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include "ctm.h"
+#define BADREAD 32
+
+/*---------------------------------------------------------------------------*/
+/* PassB -- Backup modified files.
+ */
+
+int
+PassB(FILE *fd)
+{
+ u_char *p,*q;
+ MD5_CTX ctx;
+ int i,j,sep,cnt;
+ u_char *md5=0,*md5before=0,*trash=0,*name=0,*uid=0,*gid=0,*mode=0;
+ struct CTM_Syntax *sp;
+ FILE *b = 0; /* backup command */
+ u_char buf[BUFSIZ];
+ char md5_1[33];
+ int ret = 0;
+ int match = 0;
+ struct CTM_Filter *filter = NULL;
+
+ if(Verbose>3)
+ printf("PassB -- Backing up files which would be changed.\n");
+
+ MD5Init (&ctx);
+ snprintf(buf, sizeof(buf), fmtcheck(TarCmd, TARCMD), BackupFile);
+ b=popen(buf, "w");
+ if(!b) { warn("%s", buf); return Exit_Garbage; }
+
+ GETFIELD(p,' '); if(strcmp("CTM_BEGIN",p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Version,p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Name,p)) WRONG
+ GETFIELD(p,' '); if(strcmp(Nbr,p)) WRONG
+ GETFIELD(p,' '); if(strcmp(TimeStamp,p)) WRONG
+ GETFIELD(p,'\n'); if(strcmp(Prefix,p)) WRONG
+
+ for(;;) {
+ Delete(md5);
+ Delete(uid);
+ Delete(gid);
+ Delete(mode);
+ Delete(md5before);
+ Delete(trash);
+ Delete(name);
+ cnt = -1;
+
+ GETFIELD(p,' ');
+
+ if (p[0] != 'C' || p[1] != 'T' || p[2] != 'M') WRONG
+
+ if(!strcmp(p+3,"_END"))
+ break;
+
+ for(sp=Syntax;sp->Key;sp++)
+ if(!strcmp(p+3,sp->Key))
+ goto found;
+ WRONG
+ found:
+ for(i=0;(j = sp->List[i]);i++) {
+ if (sp->List[i+1] && (sp->List[i+1] & CTM_F_MASK) != CTM_F_Bytes)
+ sep = ' ';
+ else
+ sep = '\n';
+
+ switch (j & CTM_F_MASK) {
+ case CTM_F_Name: GETNAMECOPY(name,sep,j, Verbose); break;
+ case CTM_F_Uid: GETFIELDCOPY(uid,sep); break;
+ case CTM_F_Gid: GETFIELDCOPY(gid,sep); break;
+ case CTM_F_Mode: GETFIELDCOPY(mode,sep); break;
+ case CTM_F_MD5:
+ if(j & CTM_Q_MD5_Before)
+ GETFIELDCOPY(md5before,sep);
+ else
+ GETFIELDCOPY(md5,sep);
+ break;
+ case CTM_F_Count: GETBYTECNT(cnt,sep); break;
+ case CTM_F_Bytes: GETDATA(trash,cnt); break;
+ default: WRONG
+ }
+ }
+ /* XXX This should go away. Disallow trailing '/' */
+ j = strlen(name)-1;
+ if(name[j] == '/') name[j] = '\0';
+
+ if (KeepIt &&
+ (!strcmp(sp->Key,"DR") || !strcmp(sp->Key,"FR")))
+ continue;
+
+ /* match the name against the elements of the filter list. The
+ action associated with the last matched filter determines whether
+ this file should be ignored or backed up. */
+ match = (FilterList ? !(FilterList->Action) : CTM_FILTER_ENABLE);
+ for (filter = FilterList; filter; filter = filter->Next) {
+ if (0 == regexec(&filter->CompiledRegex, name, 0, 0, 0))
+ match = filter->Action;
+ }
+
+ if (CTM_FILTER_DISABLE == match)
+ continue;
+
+ if (!strcmp(sp->Key,"FS") || !strcmp(sp->Key,"FN") ||
+ !strcmp(sp->Key,"AS") || !strcmp(sp->Key,"DR") ||
+ !strcmp(sp->Key,"FR")) {
+ /* send name to the archiver for a backup */
+ cnt = strlen(name);
+ if (cnt != fwrite(name,1,cnt,b) || EOF == fputc('\n',b)) {
+ warn("%s", name);
+ pclose(b);
+ WRONG;
+ }
+ }
+ }
+
+ ret = pclose(b);
+
+ Delete(md5);
+ Delete(uid);
+ Delete(gid);
+ Delete(mode);
+ Delete(md5before);
+ Delete(trash);
+ Delete(name);
+
+ q = MD5End (&ctx,md5_1);
+ GETFIELD(p,'\n'); /* <MD5> */
+ if(strcmp(q,p)) WRONG
+ if (-1 != getc(fd)) WRONG
+ return ret;
+}
diff --git a/usr.sbin/ctm/ctm/ctm_syntax.c b/usr.sbin/ctm/ctm/ctm_syntax.c
new file mode 100644
index 0000000..6638943
--- /dev/null
+++ b/usr.sbin/ctm/ctm/ctm_syntax.c
@@ -0,0 +1,67 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ *
+ * $FreeBSD$
+ *
+ */
+
+#include "ctm.h"
+
+/* The fields... */
+#define Name CTM_F_Name
+#define Uid CTM_F_Uid
+#define Gid CTM_F_Gid
+#define Mode CTM_F_Mode
+#define MD5 CTM_F_MD5
+#define Count CTM_F_Count
+#define Bytes CTM_F_Bytes
+
+/* The qualifiers... */
+#define File CTM_Q_Name_File
+#define Dir CTM_Q_Name_Dir
+#define New CTM_Q_Name_New
+#define Subst CTM_Q_Name_Subst
+#define After CTM_Q_MD5_After
+#define Before CTM_Q_MD5_Before
+#define Chunk CTM_Q_MD5_Chunk
+#define Force CTM_Q_MD5_Force
+
+static int ctmFM[] = /* File Make */
+ { Name|File|New|Subst, Uid, Gid, Mode,
+ MD5|After|Chunk, Count, Bytes,0 };
+
+static int ctmFS[] = /* File Substitute */
+ { Name|File|Subst, Uid, Gid, Mode,
+ MD5|Before|Force, MD5|After|Chunk, Count, Bytes,0 };
+
+static int ctmFE[] = /* File Edit */
+ { Name|File|Subst, Uid, Gid, Mode,
+ MD5|Before, MD5|After, Count, Bytes,0 };
+
+static int ctmFR[] = /* File Remove */
+ { Name|File|Subst, MD5|Before, 0 };
+
+static int ctmAS[] = /* Attribute Substitute */
+ { Name|Subst, Uid, Gid, Mode, 0 };
+
+static int ctmDM[] = /* Directory Make */
+ { Name|Dir|New , Uid, Gid, Mode, 0 };
+
+static int ctmDR[] = /* Directory Remove */
+ { Name|Dir, 0 };
+
+struct CTM_Syntax Syntax[] = {
+ { "FM", ctmFM },
+ { "FS", ctmFS },
+ { "FE", ctmFE },
+ { "FN", ctmFE },
+ { "FR", ctmFR },
+ { "AS", ctmAS },
+ { "DM", ctmDM },
+ { "DR", ctmDR },
+ { 0, 0} };
diff --git a/usr.sbin/ctm/ctm_dequeue/Makefile b/usr.sbin/ctm/ctm_dequeue/Makefile
new file mode 100644
index 0000000..d2f5648
--- /dev/null
+++ b/usr.sbin/ctm/ctm_dequeue/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../ctm_rmail
+
+PROG= ctm_dequeue
+MAN=
+SRCS= ctm_dequeue.c error.c
+
+CFLAGS+= -I${.CURDIR}/../ctm_rmail
+
+WARNS?= 1
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ctm/ctm_dequeue/Makefile.depend b/usr.sbin/ctm/ctm_dequeue/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/ctm/ctm_dequeue/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ctm/ctm_dequeue/ctm_dequeue.c b/usr.sbin/ctm/ctm_dequeue/ctm_dequeue.c
new file mode 100644
index 0000000..2dd6e86
--- /dev/null
+++ b/usr.sbin/ctm/ctm_dequeue/ctm_dequeue.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 1996, Gary J. Palmer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer,
+ * verbatim and that no modifications are made prior to this
+ * point in the file.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * ctm_dequeue: Dequeue queued delta pieces and mail them.
+ *
+ * The pieces have already been packaged up as mail messages by ctm_smail,
+ * and will be simply passed to sendmail in alphabetical order.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fts.h>
+#include <limits.h>
+#include <errno.h>
+#include <paths.h>
+#include "error.h"
+#include "options.h"
+
+#define DEFAULT_NUM 1 /* Default number of pieces mailed per run. */
+
+int fts_sort(const FTSENT * const *, const FTSENT * const *);
+int run_sendmail(int ifd);
+
+int
+main(int argc, char **argv)
+{
+ char *log_file = NULL;
+ char *queue_dir = NULL;
+ char *list[2];
+ int num_to_send = DEFAULT_NUM, chunk;
+ int fd;
+ FTS *fts;
+ FTSENT *ftsent;
+ int piece, npieces;
+ char filename[PATH_MAX];
+
+ err_prog_name(argv[0]);
+
+ OPTIONS("[-l log] [-n num] queuedir")
+ NUMBER('n', num_to_send)
+ STRING('l', log_file)
+ ENDOPTS
+
+ if (argc != 2)
+ usage();
+
+ if (log_file)
+ err_set_log(log_file);
+
+ queue_dir = argv[1];
+ list[0] = queue_dir;
+ list[1] = NULL;
+
+ fts = fts_open(list, FTS_PHYSICAL|FTS_COMFOLLOW, fts_sort);
+ if (fts == NULL)
+ {
+ err("fts failed on `%s'", queue_dir);
+ exit(1);
+ }
+
+ ftsent = fts_read(fts);
+ if (ftsent == NULL || ftsent->fts_info != FTS_D)
+ {
+ err("not a directory: %s", queue_dir);
+ exit(1);
+ }
+
+ ftsent = fts_children(fts, 0);
+ if (ftsent == NULL && errno)
+ {
+ err("*ftschildren failed");
+ exit(1);
+ }
+
+ for (chunk = 1; ftsent != NULL; ftsent = ftsent->fts_link)
+ {
+ /*
+ * Skip non-files and ctm_smail tmp files (ones starting with `.')
+ */
+ if (ftsent->fts_info != FTS_F || ftsent->fts_name[0] == '.')
+ continue;
+
+ sprintf(filename, "%s/%s", queue_dir, ftsent->fts_name);
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ {
+ err("*open: %s", filename);
+ exit(1);
+ }
+
+ if (run_sendmail(fd))
+ exit(1);
+
+ close(fd);
+
+ if (unlink(filename) < 0)
+ {
+ err("*unlink: %s", filename);
+ exit(1);
+ }
+
+ /*
+ * Deduce the delta, piece number, and number of pieces from
+ * the name of the file in the queue. Ideally, we should be
+ * able to get the mail alias name too.
+ *
+ * NOTE: This depends intimately on the queue name used in ctm_smail.
+ */
+ npieces = atoi(&ftsent->fts_name[ftsent->fts_namelen-3]);
+ piece = atoi(&ftsent->fts_name[ftsent->fts_namelen-7]);
+ err("%.*s %d/%d sent", (int)(ftsent->fts_namelen-8), ftsent->fts_name,
+ piece, npieces);
+
+ if (chunk++ == num_to_send)
+ break;
+ }
+
+ fts_close(fts);
+
+ return(0);
+}
+
+int
+fts_sort(const FTSENT * const * a, const FTSENT * const * b)
+{
+ if ((*a)->fts_info != FTS_F)
+ return(0);
+ if ((*a)->fts_info != FTS_F)
+ return(0);
+
+ return (strcmp((*a)->fts_name, (*b)->fts_name));
+}
+
+/*
+ * Run sendmail with the given file descriptor as standard input.
+ * Sendmail will decode the destination from the message contents.
+ * Returns 0 on success, 1 on failure.
+ */
+int
+run_sendmail(int ifd)
+{
+ pid_t child, pid;
+ int status;
+
+ switch (child = fork())
+ {
+ case -1:
+ err("*fork");
+ return(1);
+
+ case 0: /* Child */
+ dup2(ifd, 0);
+ execl(_PATH_SENDMAIL, _PATH_SENDMAIL, "-odq", "-t", (char *)NULL);
+ err("*exec: %s", _PATH_SENDMAIL);
+ _exit(1);
+
+ default: /* Parent */
+ while ((pid = wait(&status)) != child)
+ {
+ if (pid == -1 && errno != EINTR)
+ {
+ err("*wait");
+ return(1);
+ }
+ }
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ {
+ err("sendmail failed");
+ return(1);
+ }
+ }
+
+ return(0);
+}
diff --git a/usr.sbin/ctm/ctm_rmail/Makefile b/usr.sbin/ctm/ctm_rmail/Makefile
new file mode 100644
index 0000000..1278ab4
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+PROG= ctm_rmail
+MLINKS= ctm_rmail.1 ctm_smail.1 \
+ ctm_rmail.1 ctm_dequeue.1
+SRCS= ctm_rmail.c error.c
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ctm/ctm_rmail/Makefile.depend b/usr.sbin/ctm/ctm_rmail/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ctm/ctm_rmail/ctm_rmail.1 b/usr.sbin/ctm/ctm_rmail/ctm_rmail.1
new file mode 100644
index 0000000..232f646
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/ctm_rmail.1
@@ -0,0 +1,510 @@
+.\" NOTICE: This is free documentation. I hope you get some use from these
+.\" words. In return you should think about all the nice people who sweat
+.\" blood to document their free software. Maybe you should write some
+.\" documentation and give it away. Maybe with a free program attached!
+.\"
+.\" Author: Stephen McKay
+.\"
+.\" $FreeBSD$
+.\"
+.Dd January 24, 1995
+.Dt CTM_MAIL 1
+.Os
+.Sh NAME
+.Nm ctm_smail ,
+.Nm ctm_dequeue ,
+.Nm ctm_rmail
+.Nd send and receive
+.Xr ctm 1
+deltas via mail
+.Sh SYNOPSIS
+.Nm ctm_smail
+.Op Fl l Ar log
+.Op Fl m Ar maxmsgsize
+.Op Fl c Ar maxctmsize
+.Op Fl q Ar queue-dir
+.Ar ctm-delta
+.Ar mail-alias
+.Nm ctm_dequeue
+.Op Fl l Ar log
+.Op Fl n Ar numchunks
+.Ar queue-dir
+.Nm ctm_rmail
+.Op Fl Dfuv
+.Op Fl l Ar log
+.Op Fl p Ar piecedir
+.Op Fl d Ar deltadir
+.Op Fl b Ar basedir
+.Op Ar
+.Sh DESCRIPTION
+In conjunction with the
+.Xr ctm 1
+command,
+.Nm ctm_smail ,
+.Nm ctm_dequeue
+and
+.Nm ctm_rmail
+are used to distribute changes to a source tree via email.
+The
+.Nm ctm_smail
+utility is given a compressed
+.Xr ctm
+delta, and a mailing list to send it to.
+It splits the delta into manageable
+pieces, encodes them as mail messages and sends them to the mailing list
+(optionally queued to spread the mail load).
+Each recipient uses
+.Nm ctm_rmail
+(either manually or automatically) to decode and reassemble the delta, and
+optionally call
+.Xr ctm
+to apply it to the source tree.
+At the moment,
+several source trees are distributed, and by several sites.
+These include
+the
+.Fx Ns -current
+source and CVS trees, distributed by
+.Li freefall.FreeBSD.org .
+.Pp
+Command line arguments for
+.Nm ctm_smail :
+.Bl -tag -width indent
+.It Fl l Ar log
+Instead of appearing on
+.Em stderr ,
+error diagnostics and informational messages (other than command line errors)
+are time stamped and written to the file
+.Em log .
+.It Fl m Ar maxmsgsize
+Limit the maximum size mail message that
+.Nm ctm_smail
+is allowed to send.
+It is approximate since mail headers and other niceties
+are not counted in this limit.
+If not specified, it will default to 64000
+bytes, leaving room for 1535 bytes of headers before the rumoured 64k mail
+limit.
+.It Fl c Ar maxctmsize
+Limit the maximum size delta that will be sent.
+Deltas bigger that this
+limit will cause an apology mail message to be sent to the mailing list.
+This is to prevent massive changes overwhelming users' mail boxes.
+Note that
+this is the size before encoding.
+Encoding causes a 4/3 size increase before
+mail headers are added.
+If not specified, there is no limit.
+.It Fl q Ar queue-dir
+Instead of mailing the delta pieces now, store them in the given directory
+to be mailed later using
+.Nm ctm_dequeue .
+This feature allows the mailing of large deltas to be spread out over
+hours or even days to limit the impact on recipients with limited network
+bandwidth or small mail spool areas.
+.El
+.Pp
+.Ar ctm-delta
+is the delta to be sent, and
+.Ar mail-alias
+is the mailing list to send the delta to.
+The mail messages are sent using
+.Xr sendmail 8 .
+.Pp
+Command line arguments for
+.Nm ctm_dequeue :
+.Bl -tag -width indent
+.It Fl l Ar log
+Instead of appearing on
+.Em stderr ,
+error diagnostics and informational messages (other than command line errors)
+are time stamped and written to the file
+.Em log .
+.It Fl n Ar numchunks
+Limit the number of mail messages that
+.Nm ctm_dequeue
+will send per run.
+By default,
+.Nm ctm_dequeue
+will send one mail message per run.
+.El
+.Pp
+.Ar queuedir
+is the directory containing the mail messages stored by
+.Nm ctm_smail .
+Up to
+.Ar numchunks
+mail messages will be sent in each run.
+The recipient mailing list is already
+encoded in the queued files.
+.Pp
+It is safe to run
+.Nm ctm_dequeue
+while
+.Nm ctm_smail
+is adding entries to the queue, or even to run
+.Nm ctm_smail
+multiple times concurrently, but a separate queue directory should be used
+for each tree being distributed.
+This is because entries are served in
+alphabetical order, and one tree will be unfairly serviced before any others,
+based on the delta names, not delta creation times.
+.Pp
+Command line arguments for
+.Nm ctm_rmail :
+.Bl -tag -width indent
+.It Fl l Ar log
+Instead of appearing on
+.Em stderr ,
+error diagnostics and informational messages (other than command line errors)
+are time stamped and written to the file
+.Em log .
+.It Fl p Ar piecedir
+Collect pieces of deltas in this directory.
+Each piece corresponds to a
+single mail message.
+Pieces are removed when complete deltas are built.
+If this flag is not given, no input files will be read, but completed
+deltas may still be applied with
+.Xr ctm
+if the
+.Fl b
+flag is given.
+.It Fl d Ar deltadir
+Collect completed deltas in this directory.
+Deltas are built from one or
+more pieces when all pieces are present.
+.It Fl b Ar basedir
+Apply any completed deltas to this source tree.
+If this flag is not given,
+deltas will be stored, but not applied.
+The user may then apply the deltas
+manually, or by using
+.Nm ctm_rmail
+without the
+.Fl p
+flag.
+Deltas will not be applied if they do not match the
+.Li .ctm_status
+file in
+.Ar basedir
+(or if
+.Li .ctm_status
+does not exist).
+.It Fl D
+Delete deltas after successful application by
+.Xr ctm .
+It is probably a good idea to avoid this flag (and keep all the deltas) as
+.Xr ctm
+has the ability to recover small groups of files from a full set of deltas.
+.It Fl f
+Fork and execute in the background while applying deltas with
+.Xr ctm .
+This is useful when automatically invoking
+.Nm ctm_rmail
+from
+.Xr sendmail
+because
+.Xr ctm
+can take a very long time to complete, causing other people's mail to
+be delayed, and can in theory cause spurious
+mail retransmission due to the remote
+.Xr sendmail
+timing out, or even termination of
+.Nm ctm_rmail
+by mail filters such as
+.Xr "MH's"
+.Xr slocal .
+Do not worry about zillions of background
+.Xr ctm
+processes loading your machine, since locking is used to prevent more than one
+.Xr ctm
+invocation at a time.
+.It Fl u
+Pass the
+.Fl u
+flag to the
+.Xr ctm
+command when applying the complete deltas, causing it to set the modification
+time of created and modified files to the CTM delta creation time.
+.It Fl v
+Pass the
+.Fl v
+flag to the
+.Xr ctm
+command when applying the complete deltas, causing a more informative
+output.
+All
+.Xr ctm
+output appears in the
+.Nm ctm_rmail
+log file.
+.El
+.Pp
+The file arguments (or
+.Em stdin ,
+if there are none) are scanned for delta pieces.
+Multiple delta pieces
+can be read from a single file, so an entire maildrop can be scanned
+and processed with a single command.
+.Pp
+It is safe to invoke
+.Nm ctm_rmail
+multiple times concurrently (with different input files),
+as might happen when
+.Xr sendmail
+is delivering mail asynchronously.
+This is because locking is used to
+keep things orderly.
+.Sh FILE FORMAT
+Following are the important parts of an actual (very small) delta piece:
+.Bd -literal
+From: owner-src-cur
+To: src-cur
+Subject: ctm-mail src-cur.0003.gz 1/4
+
+CTM_MAIL BEGIN src-cur.0003.gz 1 4
+H4sIAAAAAAACA3VU72/bNhD9bP0VByQoEiyRSZEUSQP9kKTeYCR2gDTdsGFAwB/HRogtG5K8NCj6
+v4+UZSdtUQh6Rz0eee/xaF/dzx8up3/MFlDkBNrGnbttAwyo1pxoRgoiBNX/QJ5d3c9/X8DcPGGo
+lggkPiXngE4W1gUjKPJCYyk5MZRbIqmNW/ASglIFcdwIzTUxaAqhnCPcBqloKEkJVNDMF0Azk+Bo
+dDzzk0Ods/+A5gXv9YyJHjMCtJwQNeESNma7hOmXDRxn
+CTM_MAIL END 61065
+.Ed
+.Pp
+The subject of the message always begins with
+.Dq ctm-mail
+followed by the name of the delta, which piece this is, and how many total
+pieces there are.
+The data are bracketed by
+.Dq CTM_MAIL BEGIN
+and
+.Dq CTM_MAIL END
+lines, duplicating the information in the subject line, plus a simple checksum.
+.Pp
+If the delta exceeds
+.Ar maxctmsize ,
+then a message like this will be received instead:
+.Bd -literal
+From: owner-src-cur
+To: src-cur
+Subject: ctm-notice src-cur.0999.gz
+
+src-cur.0999.gz is 792843 bytes. The limit is 300000 bytes.
+
+You can retrieve this delta via ftp.
+.Ed
+.Pp
+You are then on your own!
+.Sh ENVIRONMENT
+If deltas are to be applied then
+.Xr ctm 1
+and
+.Xr gunzip 1
+must be in your
+.Ev PATH .
+.Sh FILES
+.Bl -tag -width indent
+.It Pa QUEUEDIR/*
+Pieces of deltas encoded as mail messages waiting to be sent to the
+mailing list.
+.It Pa PIECEDIR/*
+Pieces of deltas waiting for the rest to arrive.
+.It Pa DELTADIR/*
+Completed deltas.
+.It Pa BASEDIR/.ctm_status
+File containing the name and number of the next delta to be applied to this
+source tree.
+.El
+.Sh EXIT STATUS
+The
+.Nm ctm_smail ,
+.Nm ctm_dequeue
+and
+.Nm ctm_rmail
+utilities return exit status 0 for success, and 1 for various failures.
+The
+.Nm ctm_rmail
+utility is expected to be called from a mail transfer program, and thus signals
+failure only when the input mail message should be bounced (preferably into
+your regular maildrop, not back to the sender).
+In short, failure to
+apply a completed delta with
+.Xr ctm
+is not considered an error important enough to bounce the mail, and
+.Nm ctm_rmail
+returns an exit status of 0.
+.Sh EXAMPLES
+To send delta 32 of
+.Em src-cur
+to a group of wonderful code hackers known to
+.Xr sendmail
+as
+.Em src-guys ,
+limiting the mail size to roughly 60000 bytes, you could use:
+.Bd -literal -offset indent
+ctm_smail -m 60000 /wherever/it/is/src-cur.0032.gz src-guys
+.Ed
+.Pp
+To decode every
+.Nm ctm-mail
+message in your mailbox, assemble them into complete deltas, then apply
+any deltas built or lying around, you could use:
+.Bd -literal -offset indent
+ctm_rmail -p ~/pieces -d ~/deltas -b /usr/ctm-src-cur $MAIL
+.Ed
+.Pp
+(Note that no messages are deleted by
+.Nm ctm_rmail .
+Any mail reader could be used for that purpose.)
+.Pp
+To create a mail alias called
+.Em receiver-dude
+that will automatically decode and assemble deltas, but not apply them,
+you could put the following lines in your
+.Pa /etc/mail/aliases
+file (assuming the
+.Pa /ctm/tmp
+and
+.Pa /ctm/deltas
+directories and
+.Pa /ctm/log
+file are writable by user
+.Em daemon
+or group
+.Em wheel ) :
+.Bd -literal -offset indent
+receiver-dude: "|ctm_rmail -p /ctm/tmp -d /ctm/deltas -l /ctm/log"
+owner-receiver-dude: real_dude@wherever.you.like
+.Ed
+.Pp
+The second line will catch failures and drop them into your regular mailbox,
+or wherever else you like.
+.Pp
+To apply all the deltas collected, and delete those applied, you could use:
+.Bd -literal -offset indent
+ctm_rmail -D -d /ctm/deltas -b /ctm/src-cur -l /ctm/apply.log
+.Ed
+.Pp
+For maximum flexibility, consider this excerpt from a
+.Xr procmail
+script:
+.Bd -literal -offset indent
+PATH=$HOME/bin:$PATH
+
+:0 w
+* ^Subject: ctm-mail cvs-cur
+| ctm_incoming
+.Ed
+.Pp
+together with the
+shell script
+.Pa ~/bin/ctm_incoming :
+.Bd -literal -offset indent
+#! /bin/sh
+PATH="$HOME/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin"
+export PATH
+
+cd $HOME/ctm && ctm_rmail -f -p pieces -d deltas -l log -b /ctm
+.Ed
+.Pp
+which will deposit all
+.Xr ctm
+deltas in
+.Pa ~/ctm/deltas ,
+apply them to the tree in
+.Pa /ctm ,
+and drop any failures into your regular mail box.
+Note the
+.Ev PATH
+manipulation in
+.Pa ctm_incoming
+which allows
+.Nm ctm_rmail
+to execute
+.Xr ctm 1
+on the
+.Pq non- Ns Fx
+machine that this example was taken from.
+.Sh SECURITY
+On its own, CTM is an insecure protocol
+- there is no authentication performed that the
+changes applied to the source code were sent by a
+trusted party, and so care should be taken if the
+CTM deltas are obtained via an unauthenticated
+medium such as regular email.
+It is a relatively simple matter for an attacker
+to forge a CTM delta to replace or precede the
+legitimate one and insert malicious code into your
+source tree.
+If the legitimate delta is somehow prevented from
+arriving, this will go unnoticed until a later
+delta attempts to touch the same file, at which
+point the MD5 checksum will fail.
+.Pp
+To remedy this insecurity, CTM delta pieces generated by
+FreeBSD.org are cryptographically signed in a
+format compatible with the GNU Privacy Guard
+utility, available in /usr/ports/security/gpg, and
+the Pretty Good Privacy v5 utility,
+/usr/ports/security/pgp5.
+The relevant public key can be obtained by
+fingering ctm@FreeBSD.org.
+.Pp
+CTM deltas which are thus signed cannot be
+undetectably altered by an attacker.
+Therefore it is recommended that you make use of
+GPG or PGP5 to verify the signatures if you
+receive your CTM deltas via email.
+.Sh DIAGNOSTICS
+In normal operation,
+.Nm ctm_smail
+will report messages like:
+.Bd -literal -offset indent
+ctm_smail: src-cur.0250.gz 1/2 sent to src-guys
+.Ed
+.Pp
+or, if queueing,
+.Bd -literal -offset indent
+ctm_smail: src-cur.0250.gz 1/2 queued for src-guys
+.Ed
+.Pp
+The
+.Nm ctm_dequeue
+utility will report messages like:
+.Bd -literal -offset indent
+ctm_dequeue: src-cur.0250.gz 1/2 sent
+.Ed
+.Pp
+The
+.Nm ctm_rmail
+utility will report messages like:
+.Bd -literal -offset indent
+ctm_rmail: src-cur.0250.gz 1/2 stored
+ctm_rmail: src-cur.0250.gz 2/2 stored
+ctm_rmail: src-cur.0250.gz complete
+.Ed
+.Pp
+If any of the input files do not contain a valid delta piece,
+.Nm ctm_rmail
+will report:
+.Bd -literal -offset indent
+ctm_rmail: message contains no delta
+.Ed
+.Pp
+and return an exit status of 1.
+You can use this to redirect wayward messages
+back into your real mailbox if your mail filter goes wonky.
+.Pp
+These messages go to
+.Em stderr
+or to the log file.
+Messages from
+.Xr ctm 1
+turn up here too.
+Error messages should be self explanatory.
+.Sh SEE ALSO
+.Xr ctm 1 ,
+.Xr ctm 5
+.\" .Sh HISTORY
+.Sh AUTHORS
+.An Stephen McKay Aq Mt mckay@FreeBSD.org
diff --git a/usr.sbin/ctm/ctm_rmail/ctm_rmail.c b/usr.sbin/ctm/ctm_rmail/ctm_rmail.c
new file mode 100644
index 0000000..a46a58aa
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/ctm_rmail.c
@@ -0,0 +1,672 @@
+/*
+ * Accept one (or more) ASCII encoded chunks that together make a compressed
+ * CTM delta. Decode them and reconstruct the deltas. Any completed
+ * deltas may be passed to ctm for unpacking.
+ *
+ * Author: Stephen McKay
+ *
+ * NOTICE: This is free software. I hope you get some use from this program.
+ * In return you should think about all the nice people who give away software.
+ * Maybe you should write some free software too.
+ *
+ * $FreeBSD$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+#include "error.h"
+#include "options.h"
+
+#define CTM_STATUS ".ctm_status"
+
+char *piece_dir = NULL; /* Where to store pieces of deltas. */
+char *delta_dir = NULL; /* Where to store completed deltas. */
+char *base_dir = NULL; /* The tree to apply deltas to. */
+int delete_after = 0; /* Delete deltas after ctm applies them. */
+int apply_verbose = 0; /* Run with '-v' */
+int set_time = 0; /* Set the time of the files that is changed. */
+int mask = 0; /* The current umask */
+
+void apply_complete(void);
+int read_piece(char *input_file);
+int combine_if_complete(char *delta, int pce, int npieces);
+int combine(char *delta, int npieces, char *dname, char *pname, char *tname);
+int decode_line(char *line, char *out_buf);
+int lock_file(char *name);
+
+/*
+ * If given a '-p' flag, read encoded delta pieces from stdin or file
+ * arguments, decode them and assemble any completed deltas. If given
+ * a '-b' flag, pass any completed deltas to 'ctm' for application to
+ * the source tree. The '-d' flag is mandatory, but either of '-p' or
+ * '-b' can be omitted. If given the '-l' flag, notes and errors will
+ * be timestamped and written to the given file.
+ *
+ * Exit status is 0 for success or 1 for indigestible input. That is,
+ * 0 means the encode input pieces were decoded and stored, and 1 means
+ * some input was discarded. If a delta fails to apply, this won't be
+ * reflected in the exit status. In this case, the delta is left in
+ * 'deltadir'.
+ */
+int
+main(int argc, char **argv)
+ {
+ char *log_file = NULL;
+ int status = 0;
+ int fork_ctm = 0;
+
+ mask = umask(0);
+ umask(mask);
+
+ err_prog_name(argv[0]);
+
+ OPTIONS("[-Dfuv] [-p piecedir] [-d deltadir] [-b basedir] [-l log] [file ...]")
+ FLAG('D', delete_after)
+ FLAG('f', fork_ctm)
+ FLAG('u', set_time)
+ FLAG('v', apply_verbose)
+ STRING('p', piece_dir)
+ STRING('d', delta_dir)
+ STRING('b', base_dir)
+ STRING('l', log_file)
+ ENDOPTS
+
+ if (delta_dir == NULL)
+ usage();
+
+ if (piece_dir == NULL && (base_dir == NULL || argc > 1))
+ usage();
+
+ if (log_file != NULL)
+ err_set_log(log_file);
+
+ /*
+ * Digest each file in turn, or just stdin if no files were given.
+ */
+ if (argc <= 1)
+ {
+ if (piece_dir != NULL)
+ status = read_piece(NULL);
+ }
+ else
+ {
+ while (*++argv != NULL)
+ status |= read_piece(*argv);
+ }
+
+ /*
+ * Maybe it's time to look for and apply completed deltas with ctm.
+ *
+ * Shall we report back to sendmail immediately, and let a child do
+ * the work? Sendmail will be waiting for us to complete, delaying
+ * other mail, and possibly some intermediate process (like MH slocal)
+ * will terminate us if we take too long!
+ *
+ * If fork() fails, it's unlikely we'll be able to run ctm, so give up.
+ * Also, the child exit status is unimportant.
+ */
+ if (base_dir != NULL)
+ if (!fork_ctm || fork() == 0)
+ apply_complete();
+
+ return status;
+ }
+
+
+/*
+ * Construct the file name of a piece of a delta.
+ */
+#define mk_piece_name(fn,d,p,n) \
+ sprintf((fn), "%s/%s+%03d-%03d", piece_dir, (d), (p), (n))
+
+/*
+ * Construct the file name of an assembled delta.
+ */
+#define mk_delta_name(fn,d) \
+ sprintf((fn), "%s/%s", delta_dir, (d))
+
+/*
+ * If the next required delta is now present, let ctm lunch on it and any
+ * contiguous deltas.
+ */
+void
+apply_complete()
+ {
+ int i, dn;
+ int lfd;
+ FILE *fp, *ctm;
+ struct stat sb;
+ char class[20];
+ char delta[30];
+ char junk[2];
+ char fname[PATH_MAX];
+ char here[PATH_MAX];
+ char buf[PATH_MAX*2];
+
+ /*
+ * Grab a lock on the ctm mutex file so that we can be sure we are
+ * working alone, not fighting another ctm_rmail!
+ */
+ strcpy(fname, delta_dir);
+ strcat(fname, "/.mutex_apply");
+ if ((lfd = lock_file(fname)) < 0)
+ return;
+
+ /*
+ * Find out which delta ctm needs next.
+ */
+ sprintf(fname, "%s/%s", base_dir, CTM_STATUS);
+ if ((fp = fopen(fname, "r")) == NULL)
+ {
+ close(lfd);
+ return;
+ }
+
+ i = fscanf(fp, "%19s %d %c", class, &dn, junk);
+ fclose(fp);
+ if (i != 2)
+ {
+ close(lfd);
+ return;
+ }
+
+ /*
+ * We might need to convert the delta filename to an absolute pathname.
+ */
+ here[0] = '\0';
+ if (delta_dir[0] != '/')
+ {
+ getcwd(here, sizeof(here)-1);
+ i = strlen(here) - 1;
+ if (i >= 0 && here[i] != '/')
+ {
+ here[++i] = '/';
+ here[++i] = '\0';
+ }
+ }
+
+ /*
+ * Keep applying deltas until we run out or something bad happens.
+ */
+ for (;;)
+ {
+ sprintf(delta, "%s.%04d.gz", class, ++dn);
+ mk_delta_name(fname, delta);
+
+ if (stat(fname, &sb) < 0)
+ break;
+
+ sprintf(buf, "(cd %s && ctm %s%s%s%s) 2>&1", base_dir,
+ set_time ? "-u " : "",
+ apply_verbose ? "-v " : "", here, fname);
+ if ((ctm = popen(buf, "r")) == NULL)
+ {
+ err("ctm failed to apply %s", delta);
+ break;
+ }
+
+ while (fgets(buf, sizeof(buf), ctm) != NULL)
+ {
+ i = strlen(buf) - 1;
+ if (i >= 0 && buf[i] == '\n')
+ buf[i] = '\0';
+ err("ctm: %s", buf);
+ }
+
+ if (pclose(ctm) != 0)
+ {
+ err("ctm failed to apply %s", delta);
+ break;
+ }
+
+ if (delete_after)
+ unlink(fname);
+
+ err("%s applied%s", delta, delete_after ? " and deleted" : "");
+ }
+
+ /*
+ * Closing the lock file clears the lock.
+ */
+ close(lfd);
+ }
+
+
+/*
+ * This cheap plastic checksum effectively rotates our checksum-so-far
+ * left one, then adds the character. We only want 16 bits of it, and
+ * don't care what happens to the rest. It ain't much, but it's small.
+ */
+#define add_ck(sum,x) \
+ ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
+
+
+/*
+ * Decode the data between BEGIN and END, and stash it in the staging area.
+ * Multiple pieces can be present in a single file, bracketed by BEGIN/END.
+ * If we have all pieces of a delta, combine them. Returns 0 on success,
+ * and 1 for any sort of failure.
+ */
+int
+read_piece(char *input_file)
+ {
+ int status = 0;
+ FILE *ifp, *ofp = 0;
+ int decoding = 0;
+ int got_one = 0;
+ int line_no = 0;
+ int i, n;
+ int pce, npieces;
+ unsigned claimed_cksum;
+ unsigned short cksum = 0;
+ char out_buf[200];
+ char line[200];
+ char delta[30];
+ char pname[PATH_MAX];
+ char tname[PATH_MAX];
+ char junk[2];
+
+ ifp = stdin;
+ if (input_file != NULL && (ifp = fopen(input_file, "r")) == NULL)
+ {
+ err("cannot open '%s' for reading", input_file);
+ return 1;
+ }
+
+ while (fgets(line, sizeof(line), ifp) != NULL)
+ {
+ line_no++;
+
+ /*
+ * Remove all trailing white space.
+ */
+ i = strlen(line) - 1;
+ while (i > 0 && isspace(line[i]))
+ line[i--] = '\0';
+
+ /*
+ * Look for the beginning of an encoded piece.
+ */
+ if (!decoding)
+ {
+ char *s;
+ int fd = -1;
+
+ if (sscanf(line, "CTM_MAIL BEGIN %29s %d %d %c",
+ delta, &pce, &npieces, junk) != 3)
+ continue;
+
+ while ((s = strchr(delta, '/')) != NULL)
+ *s = '_';
+
+ got_one++;
+ strcpy(tname, piece_dir);
+ strcat(tname, "/p.XXXXXXXXXX");
+ if ((fd = mkstemp(tname)) == -1 ||
+ (ofp = fdopen(fd, "w")) == NULL)
+ {
+ if (fd != -1) {
+ err("cannot open '%s' for writing", tname);
+ close(fd);
+ }
+ else
+ err("*mkstemp: '%s'", tname);
+ status++;
+ continue;
+ }
+
+ cksum = 0xffff;
+ decoding++;
+ continue;
+ }
+
+ /*
+ * We are decoding. Stop if we see the end flag.
+ */
+ if (sscanf(line, "CTM_MAIL END %d %c", &claimed_cksum, junk) == 1)
+ {
+ int e;
+
+ decoding = 0;
+
+ fflush(ofp);
+ e = ferror(ofp);
+ fclose(ofp);
+
+ if (e)
+ err("error writing %s", tname);
+
+ if (cksum != claimed_cksum)
+ err("checksum: read %d, calculated %d", claimed_cksum, cksum);
+
+ if (e || cksum != claimed_cksum)
+ {
+ err("%s %d/%d discarded", delta, pce, npieces);
+ unlink(tname);
+ status++;
+ continue;
+ }
+
+ mk_piece_name(pname, delta, pce, npieces);
+ if (rename(tname, pname) < 0)
+ {
+ err("*rename: '%s' to '%s'", tname, pname);
+ err("%s %d/%d lost!", delta, pce, npieces);
+ unlink(tname);
+ status++;
+ continue;
+ }
+
+ err("%s %d/%d stored", delta, pce, npieces);
+
+ if (!combine_if_complete(delta, pce, npieces))
+ status++;
+ continue;
+ }
+
+ /*
+ * Must be a line of encoded data. Decode it, sum it, and save it.
+ */
+ n = decode_line(line, out_buf);
+ if (n <= 0)
+ {
+ err("line %d: illegal character: '%c'", line_no, line[-n]);
+ err("%s %d/%d discarded", delta, pce, npieces);
+
+ fclose(ofp);
+ unlink(tname);
+
+ status++;
+ decoding = 0;
+ continue;
+ }
+
+ for (i = 0; i < n; i++)
+ add_ck(cksum, out_buf[i]);
+
+ fwrite(out_buf, sizeof(char), n, ofp);
+ }
+
+ if (decoding)
+ {
+ err("truncated file");
+ err("%s %d/%d discarded", delta, pce, npieces);
+
+ fclose(ofp);
+ unlink(tname);
+
+ status++;
+ }
+
+ if (ferror(ifp))
+ {
+ err("error reading %s", input_file == NULL ? "stdin" : input_file);
+ status++;
+ }
+
+ if (input_file != NULL)
+ fclose(ifp);
+
+ if (!got_one)
+ {
+ err("message contains no delta");
+ status++;
+ }
+
+ return (status != 0);
+ }
+
+
+/*
+ * Put the pieces together to form a delta, if they are all present.
+ * Returns 1 on success (even if we didn't do anything), and 0 on failure.
+ */
+int
+combine_if_complete(char *delta, int pce, int npieces)
+ {
+ int i, e;
+ int lfd;
+ struct stat sb;
+ char pname[PATH_MAX];
+ char dname[PATH_MAX];
+ char tname[PATH_MAX];
+
+ /*
+ * We can probably just rename() it into place if it is a small delta.
+ */
+ if (npieces == 1)
+ {
+ mk_delta_name(dname, delta);
+ mk_piece_name(pname, delta, 1, 1);
+ if (rename(pname, dname) == 0)
+ {
+ chmod(dname, 0666 & ~mask);
+ err("%s complete", delta);
+ return 1;
+ }
+ }
+
+ /*
+ * Grab a lock on the reassembly mutex file so that we can be sure we are
+ * working alone, not fighting another ctm_rmail!
+ */
+ strcpy(tname, delta_dir);
+ strcat(tname, "/.mutex_build");
+ if ((lfd = lock_file(tname)) < 0)
+ return 0;
+
+ /*
+ * Are all of the pieces present? Of course the current one is,
+ * unless all pieces are missing because another ctm_rmail has
+ * processed them already.
+ */
+ for (i = 1; i <= npieces; i++)
+ {
+ if (i == pce)
+ continue;
+ mk_piece_name(pname, delta, i, npieces);
+ if (stat(pname, &sb) < 0)
+ {
+ close(lfd);
+ return 1;
+ }
+ }
+
+ /*
+ * Stick them together. Let combine() use our file name buffers, since
+ * we're such good buddies. :-)
+ */
+ e = combine(delta, npieces, dname, pname, tname);
+ close(lfd);
+ return e;
+ }
+
+
+/*
+ * Put the pieces together to form a delta.
+ * Returns 1 on success, and 0 on failure.
+ * Note: dname, pname, and tname are room for some file names that just
+ * happened to by lying around in the calling routine. Waste not, want not!
+ */
+int
+combine(char *delta, int npieces, char *dname, char *pname, char *tname)
+ {
+ FILE *dfp, *pfp;
+ int i, n, e;
+ char buf[BUFSIZ];
+ int fd = -1;
+
+ strcpy(tname, delta_dir);
+ strcat(tname, "/d.XXXXXXXXXX");
+ if ((fd = mkstemp(tname)) == -1 ||
+ (dfp = fdopen(fd, "w")) == NULL)
+ {
+ if (fd != -1) {
+ close(fd);
+ err("cannot open '%s' for writing", tname);
+ }
+ else
+ err("*mkstemp: '%s'", tname);
+ return 0;
+ }
+
+ /*
+ * Reconstruct the delta by reading each piece in order.
+ */
+ for (i = 1; i <= npieces; i++)
+ {
+ mk_piece_name(pname, delta, i, npieces);
+ if ((pfp = fopen(pname, "r")) == NULL)
+ {
+ err("cannot open '%s' for reading", pname);
+ fclose(dfp);
+ unlink(tname);
+ return 0;
+ }
+ while ((n = fread(buf, sizeof(char), sizeof(buf), pfp)) != 0)
+ fwrite(buf, sizeof(char), n, dfp);
+ e = ferror(pfp);
+ fclose(pfp);
+ if (e)
+ {
+ err("error reading '%s'", pname);
+ fclose(dfp);
+ unlink(tname);
+ return 0;
+ }
+ }
+ fflush(dfp);
+ e = ferror(dfp);
+ fclose(dfp);
+ if (e)
+ {
+ err("error writing '%s'", tname);
+ unlink(tname);
+ return 0;
+ }
+
+ mk_delta_name(dname, delta);
+ if (rename(tname, dname) < 0)
+ {
+ err("*rename: '%s' to '%s'", tname, dname);
+ unlink(tname);
+ return 0;
+ }
+ chmod(dname, 0666 & ~mask);
+
+ /*
+ * Throw the pieces away.
+ */
+ for (i = 1; i <= npieces; i++)
+ {
+ mk_piece_name(pname, delta, i, npieces);
+ if (unlink(pname) < 0)
+ err("*unlink: '%s'", pname);
+ }
+
+ err("%s complete", delta);
+ return 1;
+ }
+
+
+/*
+ * MIME BASE64 decode table.
+ */
+static unsigned char from_b64[0x80] =
+ {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
+ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+ 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+ 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+ 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+ 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
+ 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
+ };
+
+
+/*
+ * Decode a line of ASCII into binary. Returns the number of bytes in
+ * the output buffer, or < 0 on indigestable input. Error output is
+ * the negative of the index of the inedible character.
+ */
+int
+decode_line(char *line, char *out_buf)
+ {
+ unsigned char *ip = (unsigned char *)line;
+ unsigned char *op = (unsigned char *)out_buf;
+ unsigned long bits;
+ unsigned x;
+
+ for (;;)
+ {
+ if (*ip >= 0x80 || (x = from_b64[*ip]) >= 0x40)
+ break;
+ bits = x << 18;
+ ip++;
+ if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
+ {
+ bits |= x << 12;
+ *op++ = bits >> 16;
+ ip++;
+ if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
+ {
+ bits |= x << 6;
+ *op++ = bits >> 8;
+ ip++;
+ if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
+ {
+ bits |= x;
+ *op++ = bits;
+ ip++;
+ }
+ }
+ }
+ }
+
+ if (*ip == '\0' || *ip == '\n')
+ return op - (unsigned char *)out_buf;
+ else
+ return -(ip - (unsigned char *)line);
+ }
+
+
+/*
+ * Create and lock the given file.
+ *
+ * Clearing the lock is as simple as closing the file descriptor we return.
+ */
+int
+lock_file(char *name)
+ {
+ int lfd;
+
+ if ((lfd = open(name, O_WRONLY|O_CREAT, 0600)) < 0)
+ {
+ err("*open: '%s'", name);
+ return -1;
+ }
+ if (flock(lfd, LOCK_EX) < 0)
+ {
+ close(lfd);
+ err("*flock: '%s'", name);
+ return -1;
+ }
+ return lfd;
+ }
diff --git a/usr.sbin/ctm/ctm_rmail/error.c b/usr.sbin/ctm/ctm_rmail/error.c
new file mode 100644
index 0000000..56d3dc6
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/error.c
@@ -0,0 +1,102 @@
+/*
+ * Routines for logging error messages or other informative messages.
+ *
+ * Log messages can easily contain the program name, a time stamp, system
+ * error messages, and arbitrary printf-style strings, and can be directed
+ * to stderr or a log file.
+ *
+ * Author: Stephen McKay
+ *
+ * NOTICE: This is free software. I hope you get some use from this program.
+ * In return you should think about all the nice people who give away software.
+ * Maybe you should write some free software too.
+ */
+
+#ifndef lint
+static const char rcsid[] =
+ "$FreeBSD$";
+#endif /* not lint */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+#include <errno.h>
+#include "error.h"
+
+static FILE *error_fp = NULL;
+static char *prog = NULL;
+
+
+/*
+ * Log errors to the given file.
+ */
+void
+err_set_log(char *log_file)
+ {
+ FILE *fp;
+
+ if ((fp = fopen(log_file, "a")) == NULL)
+ err("cannot log to '%s'", log_file);
+ else
+ error_fp = fp;
+ }
+
+
+/*
+ * Set the error prefix if not logging to a file.
+ */
+void
+err_prog_name(char *name)
+ {
+ if ((prog = strrchr(name, '/')) == NULL)
+ prog = name;
+ else
+ prog++;
+ }
+
+
+/*
+ * Log an error.
+ *
+ * A leading '*' in the message format means we want the system errno
+ * decoded and appended.
+ */
+void
+err(const char *fmt, ...)
+ {
+ va_list ap;
+ time_t now;
+ struct tm *tm;
+ FILE *fp;
+ int x = errno;
+ int want_errno;
+
+ if ((fp = error_fp) == NULL)
+ {
+ fp = stderr;
+ if (prog != NULL)
+ fprintf(fp, "%s: ", prog);
+ }
+ else
+ {
+ time(&now);
+ tm = localtime(&now);
+ fprintf(fp, "%04d-%02d-%02d %02d:%02d ", tm->tm_year+1900,
+ tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min);
+ }
+
+ want_errno = 0;
+ if (*fmt == '*')
+ want_errno++, fmt++;
+
+ va_start(ap, fmt);
+ vfprintf(fp, fmt, ap);
+ va_end(ap);
+
+ if (want_errno)
+ fprintf(fp, ": %s", strerror(x));
+
+ fprintf(fp, "\n");
+ fflush(fp);
+ }
diff --git a/usr.sbin/ctm/ctm_rmail/error.h b/usr.sbin/ctm/ctm_rmail/error.h
new file mode 100644
index 0000000..c631b67
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/error.h
@@ -0,0 +1,5 @@
+/* $FreeBSD$ */
+
+extern void err_set_log(char *log_file);
+extern void err_prog_name(char *name);
+extern void err(const char *fmt, ...) __printflike(1, 2);
diff --git a/usr.sbin/ctm/ctm_rmail/options.h b/usr.sbin/ctm/ctm_rmail/options.h
new file mode 100644
index 0000000..86c1247
--- /dev/null
+++ b/usr.sbin/ctm/ctm_rmail/options.h
@@ -0,0 +1,139 @@
+/*
+ * Macros for processing command arguments.
+ *
+ * Conforms closely to the command option requirements of intro(1) in System V
+ * and intro(C) in Xenix.
+ *
+ * A command consists of: cmdname [ options ] [ cmdarguments ]
+ *
+ * Options consist of a leading dash '-' and a flag letter. An argument may
+ * follow optionally preceded by white space.
+ * Options without arguments may be grouped behind a single dash.
+ * A dash on its own is interpreted as the end of the options and is retained
+ * as a command argument.
+ * A double dash '--' is interpreted as the end of the options and is discarded.
+ *
+ * For example:
+ * zap -xz -f flame -q34 -- -x
+ *
+ * where zap.c contains the following in main():
+ *
+ * OPTIONS("[-xz] [-q queue-id] [-f dump-file] user")
+ * FLAG('x', xecute)
+ * FLAG('z', zot)
+ * STRING('f', file)
+ * fp = fopen(file, "w");
+ * NUMBER('q', queue)
+ * ENDOPTS
+ *
+ * Results in:
+ * xecute = 1
+ * zot = 1
+ * file = "flame"
+ * fp = fopen("flame", "w")
+ * queue = 34
+ * argc = 2
+ * argv[0] = "zap"
+ * argv[1] = "-x"
+ *
+ * Should the user enter unknown flags or leave out required arguments,
+ * the message:
+ *
+ * Usage: zap [-xz] [-q queue-id] [-f dump-file] user
+ *
+ * will be printed. This message can be printed by calling pusage(), or
+ * usage(). usage() will also cause program termination with exit code 1.
+ *
+ * Author: Stephen McKay, February 1991
+ *
+ * Based on recollection of the original options.h produced at the University
+ * of Queensland by Ross Patterson (and possibly others).
+ *
+ * $FreeBSD$
+ */
+
+static char *O_usage;
+static char *O_name;
+extern long atol();
+
+void
+pusage()
+ {
+ /*
+ * Avoid gratuitously loading stdio.
+ */
+ write(STDERR_FILENO, "usage: ", 7);
+ write(STDERR_FILENO, O_name, strlen(O_name));
+ write(STDERR_FILENO, " ", 1);
+ write(STDERR_FILENO, O_usage, strlen(O_usage));
+ write(STDERR_FILENO, "\n", 1);
+ }
+
+#define usage() (pusage(), exit(1))
+
+#define OPTIONS(usage_msg) \
+ { \
+ char O_cont; \
+ O_usage = (usage_msg); \
+ O_name = argv[0]; \
+ while (*++argv && **argv == '-') \
+ { \
+ if ((*argv)[1] == '\0') \
+ break; \
+ argc--; \
+ if ((*argv)[1] == '-' && (*argv)[2] == '\0') \
+ { \
+ argv++; \
+ break; \
+ } \
+ O_cont = 1; \
+ while (O_cont) \
+ switch (*++*argv) \
+ { \
+ default: \
+ case '-': \
+ usage(); \
+ case '\0': \
+ O_cont = 0;
+
+#define FLAG(x,flag) \
+ break; \
+ case (x): \
+ (flag) = 1;
+
+#define CHAR(x,ch) \
+ break; \
+ case (x): \
+ O_cont = 0; \
+ if (*++*argv == '\0' && (--argc, *++argv == 0)) \
+ usage(); \
+ (ch) = **argv;
+
+#define NUMBER(x,n) \
+ break; \
+ case (x): \
+ O_cont = 0; \
+ if (*++*argv == '\0' && (--argc, *++argv == 0)) \
+ usage(); \
+ (n) = atol(*argv);
+
+#define STRING(x,str) \
+ break; \
+ case (x): \
+ O_cont = 0; \
+ if (*++*argv == '\0' && (--argc, *++argv == 0)) \
+ usage(); \
+ (str) = *argv;
+
+#define SUFFIX(x,str) \
+ break; \
+ case (x): \
+ (str) = ++*argv; \
+ O_cont = 0;
+
+#define ENDOPTS \
+ break; \
+ } \
+ } \
+ *--argv = O_name; \
+ }
diff --git a/usr.sbin/ctm/ctm_smail/Makefile b/usr.sbin/ctm/ctm_smail/Makefile
new file mode 100644
index 0000000..4c98d31
--- /dev/null
+++ b/usr.sbin/ctm/ctm_smail/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../ctm_rmail
+
+PROG= ctm_smail
+MAN=
+SRCS= ctm_smail.c error.c
+
+CFLAGS+= -I${.CURDIR}/../ctm_rmail
+
+WARNS?= 2
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ctm/ctm_smail/Makefile.depend b/usr.sbin/ctm/ctm_smail/Makefile.depend
new file mode 100644
index 0000000..3646e2e
--- /dev/null
+++ b/usr.sbin/ctm/ctm_smail/Makefile.depend
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/ctm/ctm_smail/ctm_smail.c b/usr.sbin/ctm/ctm_smail/ctm_smail.c
new file mode 100644
index 0000000..c90fa2f
--- /dev/null
+++ b/usr.sbin/ctm/ctm_smail/ctm_smail.c
@@ -0,0 +1,497 @@
+/*
+ * Send a compressed CTM delta to a recipient mailing list by encoding it
+ * in safe ASCII characters, in mailer-friendly chunks, and passing them
+ * to sendmail. Optionally, the chunks can be queued to be sent later by
+ * ctm_dequeue in controlled bursts. The encoding is almost the same as
+ * MIME BASE64, and is protected by a simple checksum.
+ *
+ * Author: Stephen McKay
+ *
+ * NOTICE: This is free software. I hope you get some use from this program.
+ * In return you should think about all the nice people who give away software.
+ * Maybe you should write some free software too.
+ *
+ * $FreeBSD$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <paths.h>
+#include <limits.h>
+#include "error.h"
+#include "options.h"
+
+#define DEF_MAX_MSG 64000 /* Default maximum mail msg minus headers. */
+
+#define LINE_LENGTH 72 /* Chars per encoded line. Divisible by 4. */
+
+int chop_and_send_or_queue(FILE *dfp, char *delta, off_t ctm_size,
+ long max_msg_size, char *mail_alias, char *queue_dir);
+int chop_and_send(FILE *dfp, char *delta, long msg_size, int npieces,
+ char *mail_alias);
+int chop_and_queue(FILE *dfp, char *delta, long msg_size, int npieces,
+ char *mail_alias, char *queue_dir);
+void clean_up_queue(char *queue_dir);
+int encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size, unsigned *sum);
+void write_header(FILE *sfp, char *mail_alias, char *delta, int pce,
+ int npieces);
+void write_trailer(FILE *sfp, unsigned sum);
+int apologise(char *delta, off_t ctm_size, long max_ctm_size,
+ char *mail_alias, char *queue_dir);
+FILE *open_sendmail(void);
+int close_sendmail(FILE *fp);
+
+int
+main(int argc, char **argv)
+ {
+ int status = 0;
+ char *delta_file;
+ char *mail_alias;
+ long max_msg_size = DEF_MAX_MSG;
+ long max_ctm_size = 0;
+ char *log_file = NULL;
+ char *queue_dir = NULL;
+ char *delta;
+ FILE *dfp;
+ struct stat sb;
+
+ err_prog_name(argv[0]);
+
+ OPTIONS("[-l log] [-m maxmsgsize] [-c maxctmsize] [-q queuedir] ctm-delta mail-alias")
+ NUMBER('m', max_msg_size)
+ NUMBER('c', max_ctm_size)
+ STRING('l', log_file)
+ STRING('q', queue_dir)
+ ENDOPTS
+
+ if (argc != 3)
+ usage();
+
+ if (log_file != NULL)
+ err_set_log(log_file);
+
+ delta_file = argv[1];
+ mail_alias = argv[2];
+
+ if ((delta = strrchr(delta_file, '/')) == NULL)
+ delta = delta_file;
+ else
+ delta++;
+
+ if ((dfp = fopen(delta_file, "r")) == NULL || fstat(fileno(dfp), &sb) < 0)
+ {
+ err("*%s", delta_file);
+ exit(1);
+ }
+
+ if (max_ctm_size != 0 && sb.st_size > max_ctm_size)
+ status = apologise(delta, sb.st_size, max_ctm_size, mail_alias,
+ queue_dir);
+ else
+ status = chop_and_send_or_queue(dfp, delta, sb.st_size, max_msg_size,
+ mail_alias, queue_dir);
+
+ fclose(dfp);
+
+ return status;
+ }
+
+
+/*
+ * Carve our CTM delta into pieces, encode them, and send or queue them.
+ * Returns 0 on success, and 1 on failure.
+ */
+int
+chop_and_send_or_queue(FILE *dfp, char *delta, off_t ctm_size,
+ long max_msg_size, char *mail_alias, char *queue_dir)
+ {
+ int npieces;
+ long msg_size;
+ long exp_size;
+ int status;
+
+#undef howmany
+#define howmany(x,y) (((x)+((y)-1)) / (y))
+
+ /*
+ * Work out how many pieces we need, bearing in mind that each piece
+ * grows by 4/3 when encoded. We count the newlines too, but ignore
+ * all mail headers and piece headers. They are a "small" (almost
+ * constant) per message overhead that we make the user worry about. :-)
+ */
+ exp_size = ctm_size * 4 / 3;
+ exp_size += howmany(exp_size, LINE_LENGTH);
+ npieces = howmany(exp_size, max_msg_size);
+ msg_size = howmany(ctm_size, npieces);
+
+#undef howmany
+
+ if (queue_dir == NULL)
+ status = chop_and_send(dfp, delta, msg_size, npieces, mail_alias);
+ else
+ {
+ status = chop_and_queue(dfp, delta, msg_size, npieces, mail_alias,
+ queue_dir);
+ if (status)
+ clean_up_queue(queue_dir);
+ }
+
+ return status;
+ }
+
+
+/*
+ * Carve our CTM delta into pieces, encode them, and send them.
+ * Returns 0 on success, and 1 on failure.
+ */
+int
+chop_and_send(FILE *dfp, char *delta, long msg_size, int npieces,
+ char *mail_alias)
+ {
+ int pce;
+ FILE *sfp;
+ unsigned sum;
+
+ /*
+ * Send each chunk directly to sendmail as it is generated.
+ * No temporary files necessary. If things turn ugly, we just
+ * have to live with the fact the we have sent only part of
+ * the delta.
+ */
+ for (pce = 1; pce <= npieces; pce++)
+ {
+ int read_error;
+
+ if ((sfp = open_sendmail()) == NULL)
+ return 1;
+
+ write_header(sfp, mail_alias, delta, pce, npieces);
+ read_error = encode_body(sfp, dfp, msg_size, &sum);
+ if (!read_error)
+ write_trailer(sfp, sum);
+
+ if (!close_sendmail(sfp) || read_error)
+ return 1;
+
+ err("%s %d/%d sent to %s", delta, pce, npieces, mail_alias);
+ }
+
+ return 0;
+ }
+
+
+/*
+ * Construct the tmp queue file name of a delta piece.
+ */
+#define mk_tmp_name(fn,qd,p) \
+ sprintf((fn), "%s/.%08ld.%03d", (qd), (long)getpid(), (p))
+
+/*
+ * Construct the final queue file name of a delta piece.
+ */
+#define mk_queue_name(fn,qd,d,p,n) \
+ sprintf((fn), "%s/%s+%03d-%03d", (qd), (d), (p), (n))
+
+/*
+ * Carve our CTM delta into pieces, encode them, and queue them.
+ * Returns 0 on success, and 1 on failure.
+ */
+int
+chop_and_queue(FILE *dfp, char *delta, long msg_size, int npieces,
+ char *mail_alias, char *queue_dir)
+ {
+ int pce;
+ FILE *qfp;
+ unsigned sum;
+ char tname[PATH_MAX];
+ char qname[PATH_MAX];
+
+ /*
+ * Store each piece in the queue directory, but under temporary names,
+ * so that they can be deleted without unpleasant consequences if
+ * anything goes wrong. We could easily fill up a disk, for example.
+ */
+ for (pce = 1; pce <= npieces; pce++)
+ {
+ int write_error;
+
+ mk_tmp_name(tname, queue_dir, pce);
+ if ((qfp = fopen(tname, "w")) == NULL)
+ {
+ err("cannot open '%s' for writing", tname);
+ return 1;
+ }
+
+ write_header(qfp, mail_alias, delta, pce, npieces);
+ if (encode_body(qfp, dfp, msg_size, &sum))
+ return 1;
+ write_trailer(qfp, sum);
+
+ fflush(qfp);
+ write_error = ferror(qfp);
+ fclose(qfp);
+ if (write_error)
+ {
+ err("error writing '%s'", tname);
+ return 1;
+ }
+
+ /*
+ * Give the warm success message now, instead of all in a rush
+ * during the rename phase.
+ */
+ err("%s %d/%d queued for %s", delta, pce, npieces, mail_alias);
+ }
+
+ /*
+ * Rename the pieces into place. If an error occurs now, we are
+ * stuffed, but there is no neat way to back out. rename() should
+ * only fail now under extreme circumstances.
+ */
+ for (pce = 1; pce <= npieces; pce++)
+ {
+ mk_tmp_name(tname, queue_dir, pce);
+ mk_queue_name(qname, queue_dir, delta, pce, npieces);
+ if (rename(tname, qname) < 0)
+ {
+ err("*rename: '%s' to '%s'", tname, qname);
+ unlink(tname);
+ }
+ }
+
+ return 0;
+ }
+
+
+/*
+ * There may be temporary files cluttering up the queue directory.
+ */
+void
+clean_up_queue(char *queue_dir)
+ {
+ int pce;
+ char tname[PATH_MAX];
+
+ err("discarding queued delta pieces");
+ for (pce = 1; ; pce++)
+ {
+ mk_tmp_name(tname, queue_dir, pce);
+ if (unlink(tname) < 0)
+ break;
+ }
+ }
+
+
+/*
+ * MIME BASE64 encode table.
+ */
+static char to_b64[0x40] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/*
+ * This cheap plastic checksum effectively rotates our checksum-so-far
+ * left one, then adds the character. We only want 16 bits of it, and
+ * don't care what happens to the rest. It ain't much, but it's small.
+ */
+#define add_ck(sum,x) \
+ ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
+
+/*
+ * Encode the body. Use an encoding almost the same as MIME BASE64.
+ *
+ * Characters are read from delta_fp and encoded characters are written
+ * to sm_fp. At most 'msg_size' characters should be read from delta_fp.
+ *
+ * The body consists of lines of up to LINE_LENGTH characters. Each group
+ * of 4 characters encodes 3 input characters. Each output character encodes
+ * 6 bits. Thus 64 different characters are needed in this representation.
+ */
+int
+encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size, unsigned *sum)
+ {
+ unsigned short cksum = 0xffff;
+ unsigned char *ip;
+ char *op;
+ int want, n, i;
+ unsigned char inbuf[LINE_LENGTH*3/4];
+ char outbuf[LINE_LENGTH+1];
+
+ /*
+ * Round up to the nearest line boundary, for the tiniest of gains,
+ * and lots of neatness. :-)
+ */
+ msg_size += (LINE_LENGTH*3/4) - 1;
+ msg_size -= msg_size % (LINE_LENGTH*3/4);
+
+ while (msg_size > 0)
+ {
+ want = (msg_size < sizeof(inbuf)) ? msg_size : sizeof(inbuf);
+ if ((n = fread(inbuf, sizeof(char), want, delta_fp)) == 0)
+ break;
+ msg_size -= n;
+
+ for (i = 0; i < n; i++)
+ add_ck(cksum, inbuf[i]);
+
+ /*
+ * Produce a line of encoded data. Every line length will be a
+ * multiple of 4, except for, perhaps, the last line.
+ */
+ ip = inbuf;
+ op = outbuf;
+ while (n >= 3)
+ {
+ *op++ = to_b64[ip[0] >> 2];
+ *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4];
+ *op++ = to_b64[(ip[1] << 2 & 0x3f) | ip[2] >> 6];
+ *op++ = to_b64[ip[2] & 0x3f];
+ ip += 3;
+ n -= 3;
+ }
+ if (n > 0)
+ {
+ *op++ = to_b64[ip[0] >> 2];
+ *op++ = to_b64[(ip[0] << 4 & 0x3f) | ip[1] >> 4];
+ if (n >= 2)
+ *op++ = to_b64[ip[1] << 2 & 0x3f];
+ }
+ *op++ = '\n';
+ fwrite(outbuf, sizeof(char), op - outbuf, sm_fp);
+ }
+
+ if (ferror(delta_fp))
+ {
+ err("error reading input file.");
+ return 1;
+ }
+
+ *sum = cksum;
+
+ return 0;
+ }
+
+
+/*
+ * Write the mail header and data header.
+ */
+void
+write_header(FILE *sfp, char *mail_alias, char *delta, int pce, int npieces)
+ {
+ fprintf(sfp, "From: owner-%s\n", mail_alias);
+ fprintf(sfp, "To: %s\n", mail_alias);
+ fprintf(sfp, "Subject: ctm-mail %s %d/%d\n\n", delta, pce, npieces);
+
+ fprintf(sfp, "CTM_MAIL BEGIN %s %d %d\n", delta, pce, npieces);
+ }
+
+
+/*
+ * Write the data trailer.
+ */
+void
+write_trailer(FILE *sfp, unsigned sum)
+ {
+ fprintf(sfp, "CTM_MAIL END %ld\n", (long)sum);
+ }
+
+
+/*
+ * We're terribly sorry, but the delta is too big to send.
+ * Returns 0 on success, 1 on failure.
+ */
+int
+apologise(char *delta, off_t ctm_size, long max_ctm_size, char *mail_alias,
+ char *queue_dir)
+ {
+ FILE *sfp;
+ char qname[PATH_MAX];
+
+ if (queue_dir == NULL)
+ {
+ sfp = open_sendmail();
+ if (sfp == NULL)
+ return 1;
+ }
+ else
+ {
+ mk_queue_name(qname, queue_dir, delta, 1, 1);
+ sfp = fopen(qname, "w");
+ if (sfp == NULL)
+ {
+ err("cannot open '%s' for writing", qname);
+ return 1;
+ }
+ }
+
+
+ fprintf(sfp, "From: owner-%s\n", mail_alias);
+ fprintf(sfp, "To: %s\n", mail_alias);
+ fprintf(sfp, "Subject: ctm-notice %s\n\n", delta);
+
+ fprintf(sfp, "%s is %ld bytes. The limit is %ld bytes.\n\n", delta,
+ (long)ctm_size, max_ctm_size);
+ fprintf(sfp, "You can retrieve this delta via ftp.\n");
+
+ if (queue_dir == NULL)
+ {
+ if (!close_sendmail(sfp))
+ return 1;
+ }
+ else
+ {
+ if (fclose(sfp)!=0)
+ {
+ err("error writing '%s'", qname);
+ unlink(qname);
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
+
+/*
+ * Start a pipe to sendmail. Sendmail will decode the destination
+ * from the message contents.
+ */
+FILE *
+open_sendmail()
+ {
+ FILE *fp;
+ char buf[100];
+
+ sprintf(buf, "%s -odq -t", _PATH_SENDMAIL);
+ if ((fp = popen(buf, "w")) == NULL)
+ err("cannot start sendmail");
+ return fp;
+ }
+
+
+/*
+ * Close a pipe to sendmail. Sendmail will then do its bit.
+ * Return 1 on success, 0 on failure.
+ */
+int
+close_sendmail(FILE *fp)
+ {
+ int status;
+
+ fflush(fp);
+ if (ferror(fp))
+ {
+ err("error writing to sendmail");
+ return 0;
+ }
+
+ if ((status = pclose(fp)) != 0)
+ err("sendmail failed with status %d", status);
+
+ return (status == 0);
+ }
diff --git a/usr.sbin/ctm/mkCTM/Makefile b/usr.sbin/ctm/mkCTM/Makefile
new file mode 100644
index 0000000..8194dd7
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/Makefile
@@ -0,0 +1,25 @@
+# $FreeBSD$
+
+PROG= mkctm
+MAN=
+
+LIBADD= md
+
+test: mkctm
+ rm -f tst.out*
+ time ./mkctm -v -v /3c/210src /a/r1/usr/src \
+ 2>a | md5 -p > /a/tst.out
+ ls -l /a/tst.out
+ gzip -9 -v /a/tst.out
+ ls -l /a/tst.out.gz
+ # cd /usr/src/release && ctm -c -v -v ${.CURDIR}/tst.out
+
+test1: mkctm
+ rm -f tst.out*
+ time ./mkctm -v -v /3c/210src /home/ncvs/src \
+ 2> b | md5 -p > /a/tst2.out
+ ls -l /a/tst2.out
+ gzip -9 -v /a/tst2.out
+ ls -l /a/tst2.out.gz
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.cvs-cur b/usr.sbin/ctm/mkCTM/ctm_conf.cvs-cur
new file mode 100644
index 0000000..fbb5bf2
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/ctm_conf.cvs-cur
@@ -0,0 +1,9 @@
+#!/usr/local/bin/tclsh
+
+set CTMname cvs-cur
+set CTMref /home/ncvs
+set CTMignore {^src/secure|^src/eBones|^src/kerberosIV|^CVSROOT/val-tags$|CVSROOT/\\.#}
+set CTMbogus {\\.core$|/#cvs|/\\.#}
+set CTMmail ctm-cvs-cur-fast@freebsd.org
+set CTMqueuemail ctm-cvs-cur@freebsd.org
+set CTMqueue /home/ctm/queue/ctm-cvs-cur
diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.ports-cur b/usr.sbin/ctm/mkCTM/ctm_conf.ports-cur
new file mode 100644
index 0000000..b9d3d13
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/ctm_conf.ports-cur
@@ -0,0 +1,7 @@
+#!/usr/local/bin/tclsh
+
+set CTMname ports-cur
+set CTMref /usr/ports
+set CTMignore {/CVS$|/CVS/|^distfiles}
+set CTMbogus {\\.core$|/#cvs|/\\.#}
+set CTMmail ctm-ports-cur@freebsd.org
diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.smp-cur b/usr.sbin/ctm/mkCTM/ctm_conf.smp-cur
new file mode 100644
index 0000000..5c3e1d1
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/ctm_conf.smp-cur
@@ -0,0 +1,7 @@
+#!/usr/local/bin/tclsh
+
+set CTMname smp-cur
+set CTMref /home/smp
+set CTMignore {^CVSROOT/history.*$|^CVSROOT/val-tags$|^CVSROOT/\\.#}
+set CTMbogus {\\.core$|/#cvs|/\\.#}
+set CTMmail smp-cvs-cur@freebsd.org
diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.src-cur b/usr.sbin/ctm/mkCTM/ctm_conf.src-cur
new file mode 100644
index 0000000..8589d04
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/ctm_conf.src-cur
@@ -0,0 +1,9 @@
+#!/usr/local/bin/tclsh
+
+set CTMname src-cur
+set CTMref /c/src
+set CTMignore {/CVS$|/CVS/|^/secure|^/eBones}
+set CTMbogus {\\.core$|/#cvs|/\\.#}
+set CTMmail ctm-src-cur-fast@freebsd.org
+set CTMqueue /home/ctm/queue/ctm-src-cur
+set CTMqueuemail ctm-src-cur@freebsd.org
diff --git a/usr.sbin/ctm/mkCTM/ctm_conf.src-special b/usr.sbin/ctm/mkCTM/ctm_conf.src-special
new file mode 100644
index 0000000..2a8ca70
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/ctm_conf.src-special
@@ -0,0 +1,9 @@
+#!/usr/local/bin/tclsh
+
+set CTMname src-cur
+set CTMref $CTMSW/../$CTMname
+set CTMcopy /c/phk/20R/usr/src
+set CTMdont {\.core$|/CVS$|/CVS/|^/secure|^/eBones|/#cvs|/\.#}
+set CTMtest 1
+set CTMspecial 1
+set CTMsuff R20
diff --git a/usr.sbin/ctm/mkCTM/dequeue b/usr.sbin/ctm/mkCTM/dequeue
new file mode 100755
index 0000000..697ec0b
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/dequeue
@@ -0,0 +1,6 @@
+#! /bin/sh
+# $FreeBSD$
+
+L=/home/ctm/log.dequeue
+/usr/sbin/ctm_dequeue -n 1 -l $L /home/ctm/queue/ctm-cvs-cur
+/usr/sbin/ctm_dequeue -n 1 -l $L /home/ctm/queue/ctm-src-cur
diff --git a/usr.sbin/ctm/mkCTM/mkCTM b/usr.sbin/ctm/mkCTM/mkCTM
new file mode 100644
index 0000000..02b1544
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/mkCTM
@@ -0,0 +1,188 @@
+#!/usr/local/bin/tclsh7.4
+#
+# $FreeBSD$
+
+#############################################################################
+### Do we already have this delta ?
+#############################################################################
+
+proc find_delta {nbr} {
+ global CTMname CTMdest
+ if {[file exists [format "%s/$CTMname.%04d" $CTMdest $nbr]]} { return 1 }
+ if {[file exists [format "%s/$CTMname.%04d.gz" $CTMdest $nbr]]} { return 1 }
+ return 0
+}
+
+#############################################################################
+### The top level code...
+#############################################################################
+
+set CTMSW /home/ctm/SW
+
+cd $CTMSW
+
+# Defaults...
+set CTMapply 1
+set CTMignore {^///}
+set CTMbogus {\.core$}
+set CTMmail {}
+set CTMqueue {}
+set CTMqueuemail {}
+set CTMmaxctm 10000000
+set CTMmaxmsg 100000
+set CTMsuff {}
+set CTMdate [exec date -u +%Y%m%d%H%M%SZ]
+set CTMtmp {}
+set CTMcopy {}
+set CTMdest {}
+set CTMprefix .
+set CTMtest 0
+set CTMspecial 0
+set CTMscan .
+set CTMfirst 0
+set max_damage 100
+
+set damage 0
+set changes 0
+
+source $argv
+exec sh -c "date -u '+%Y%m%d%H%M%S $argv'" >> ${CTMSW}/log
+
+if {$CTMtmp == ""} {
+ set CTMtmp $CTMSW/../tmp/${CTMname}_${CTMsuff}
+}
+if {$CTMcopy == ""} {
+ set CTMcopy $CTMSW/../$CTMname
+}
+if {$CTMdest == ""} {
+ set CTMdest $CTMSW/../CTM-pub/$CTMname
+}
+
+# Make sure we only run one at a time...
+
+set CTMlock Lck.${CTMname}.${CTMdate}.[pid]
+exec rm -f ${CTMlock}
+exec echo starting > ${CTMlock}
+if {[catch "exec ln $CTMlock LCK.$CTMname" a]} {
+ puts "Not going, lock exists..."
+ exec rm -f $CTMlock
+ exit 1
+}
+exec rm -f $CTMlock
+set CTMlock LCK.$CTMname
+
+set CTMscratch ${CTMtmp}.tmp
+
+while 1 {
+ if { ! $CTMspecial} {
+ if {$CTMfirst} {
+ set CTMnbr 0
+ } else {
+ set CTMnbr [lindex [exec cat $CTMcopy/.ctm_status] 1]
+ }
+
+ if {$CTMnbr > 0 && ![find_delta $CTMnbr]} {
+ puts "$CTMname delta $CTMnbr doesn't exist..."
+ exec rm -f $CTMlock
+ exit 1
+ }
+
+ incr CTMnbr
+
+ if {[find_delta $CTMnbr]} {
+ puts "$CTMname delta $CTMnbr does already exist..."
+ exec rm -f $CTMlock
+ exit 1
+ }
+
+ set fo [open $CTMref/.ctm_status w]
+ puts $fo "$CTMname $CTMnbr"
+ close $fo
+ incr changes -1
+
+ } else {
+ set CTMnbr [lindex [exec cat $CTMref/.ctm_status] 1]
+ }
+
+ puts "Doing CTMname $CTMname CTMnbr $CTMnbr$CTMsuff CTMdate $CTMdate"
+ flush stdout
+ exec sh -c "rm -f ${CTMtmp}.* ${CTMtmp}:*" >&@ stdout
+
+ set nm [format "%s.%04d%s" $CTMname $CTMnbr $CTMsuff]
+
+ set x1 $CTMcopy
+ if {$x1 == ""} {
+ exec mkdir ${CTMtmp}.dir
+ set x1 ${CTMtmp}.dir
+ }
+ set r1 [catch "exec ${CTMSW}/mkctm -I ${CTMignore} -B ${CTMbogus} -l ${CTMtmp}.log -D $max_damage $CTMname $CTMnbr $CTMdate . $x1 $CTMref | md5 -p | gzip -9 > ${CTMtmp}:${nm}.gz 2>@ stderr" r2]
+
+ if {$r1} {
+ if {[lindex $errorCode 2] == 4} {
+ puts "No changes, stopping."
+ exec rm -f $CTMlock
+ exit 0
+ }
+ puts "problems, stopping now."
+ puts "errorCode $errorCode"
+ puts "$r2"
+ exec rm -f $CTMlock
+ exit 1
+ }
+
+ puts "mkctm done"
+
+ if {$CTMtest} {
+ puts "testing, stopping now."
+ exec rm -f $CTMlock
+ exit 0
+ }
+ if {$CTMapply} {
+ puts "Applying delta"
+ flush stdout
+ exec echo now applying > $CTMlock
+ exec sh -e -c "cd $CTMcopy ; $CTMSW/ctm -v -v -v ${CTMtmp}:${nm}.gz" >& ${CTMtmp}.apply
+ exec echo did apply > $CTMlock
+ }
+ puts "Moving delta"
+ flush stdout
+ exec mv ${CTMtmp}:${nm}.gz $CTMdest/.CTMtmp_${nm}.gz >&@ stdout
+ exec mv $CTMdest/.CTMtmp_${nm}.gz $CTMdest/${nm}.gz >&@ stdout
+ exec echo moved > $CTMlock
+
+ exec sh -c "rm -rf ${CTMtmp}.*" >&@ stdout
+
+ if {$CTMmail != ""} {
+ puts "Mailing delta"
+ flush stdout
+ exec $CTMSW/ctm_smail -m $CTMmaxmsg -c $CTMmaxctm $CTMdest/${nm}.gz $CTMmail >&@ stdout
+ if {$CTMqueue != "" && $CTMqueuemail != ""} {
+ puts "Queueing delta"
+ flush stdout
+ exec $CTMSW/ctm_smail -m $CTMmaxmsg -c $CTMmaxctm -q $CTMqueue $CTMdest/${nm}.gz $CTMqueuemail >&@ stdout
+ puts "Sending initial two deltas"
+ flush stdout
+ exec $CTMSW/ctm_dequeue -n 2 $CTMqueue >&@ stdout
+ }
+ }
+ exec echo mailed > $CTMlock
+
+ # If we did an absolute delta: stop.
+ if {$CTMsuff != ""} break
+
+ # Make an absolute delta (!) every 100 deltas
+ if {$CTMnbr == 0 || ($CTMnbr % 100)} break
+
+ # Make an absolute delta too...
+ set CTMref $CTMcopy
+ set CTMsuff A
+ set CTMcopy ""
+ set CTMmail ""
+ set CTMqueue ""
+ set CTMqueuemail ""
+ set CTMapply 0
+ set CTMspecial 1
+ exec rm -f $CTMlock
+}
+puts "done."
+exec rm -f $CTMlock
diff --git a/usr.sbin/ctm/mkCTM/mkctm.c b/usr.sbin/ctm/mkCTM/mkctm.c
new file mode 100644
index 0000000..d2c73e2
--- /dev/null
+++ b/usr.sbin/ctm/mkCTM/mkctm.c
@@ -0,0 +1,597 @@
+/* $FreeBSD$ */
+
+/* Still missing:
+ *
+ * mkctm
+ * -B regex Bogus
+ * -I regex Ignore
+ * -D int Damage
+ * -q decrease verbosity
+ * -v increase verbosity
+ * -l file logfile
+ * name cvs-cur
+ * prefix src/secure
+ * dir1 "Soll"
+ * dir2 "Ist"
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <regex.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <md5.h>
+#include <err.h>
+#include <paths.h>
+#include <signal.h>
+
+#define DEFAULT_IGNORE "/CVS$|/\\.#|00_TRANS\\.TBL$"
+#define DEFAULT_BOGUS "\\.core$|\\.orig$|\\.rej$|\\.o$"
+regex_t reg_ignore, reg_bogus;
+int flag_ignore, flag_bogus;
+
+int verbose;
+int damage, damage_limit;
+int change;
+
+FILE *logf;
+
+u_long s1_ignored, s2_ignored;
+u_long s1_bogus, s2_bogus;
+u_long s1_wrong, s2_wrong;
+u_long s_new_dirs, s_new_files, s_new_bytes;
+u_long s_del_dirs, s_del_files, s_del_bytes;
+u_long s_files_chg, s_bytes_add, s_bytes_del;
+u_long s_same_dirs, s_same_files, s_same_bytes;
+u_long s_edit_files, s_edit_bytes, s_edit_saves;
+u_long s_sub_files, s_sub_bytes;
+
+void
+Usage(void)
+{
+ fprintf(stderr,
+ "usage: mkctm [-options] name number timestamp prefix dir1 dir2\n");
+ fprintf(stderr, "options:\n");
+ fprintf(stderr, "\t\t-B bogus_regexp\n");
+ fprintf(stderr, "\t\t-D damage_limit\n");
+ fprintf(stderr, "\t\t-I ignore_regexp\n");
+ fprintf(stderr, "\t\t-q\n");
+ fprintf(stderr, "\t\t-v\n");
+}
+
+void
+print_stat(FILE *fd, char *pre)
+{
+ fprintf(fd, "%sNames:\n", pre);
+ fprintf(fd, "%s ignore: %5lu ref %5lu target\n",
+ pre, s1_ignored, s2_ignored);
+ fprintf(fd, "%s bogus: %5lu ref %5lu target\n",
+ pre, s1_bogus, s2_bogus);
+ fprintf(fd, "%s wrong: %5lu ref %5lu target\n",
+ pre, s1_wrong, s2_wrong);
+ fprintf(fd, "%sDelta:\n", pre);
+ fprintf(fd, "%s new: %5lu dirs %5lu files %9lu plus\n",
+ pre, s_new_dirs, s_new_files, s_new_bytes);
+ fprintf(fd, "%s del: %5lu dirs %5lu files %9lu minus\n",
+ pre, s_del_dirs, s_del_files, s_del_bytes);
+ fprintf(fd, "%s chg: %5lu files %9lu plus %9lu minus\n",
+ pre, s_files_chg, s_bytes_add, s_bytes_del);
+ fprintf(fd, "%s same: %5lu dirs %5lu files %9lu bytes\n",
+ pre, s_same_dirs, s_same_files, s_same_bytes);
+ fprintf(fd, "%sMethod:\n", pre);
+ fprintf(fd, "%s edit: %5lu files %9lu bytes %9lu saved\n",
+ pre, s_edit_files, s_edit_bytes, s_edit_saves);
+ fprintf(fd, "%s sub: %5lu files %9lu bytes\n",
+ pre, s_sub_files, s_sub_bytes);
+
+}
+
+void
+stat_info(int foo)
+{
+ signal(SIGINFO, stat_info);
+ print_stat(stderr, "INFO: ");
+}
+
+void DoDir(const char *dir1, const char *dir2, const char *name);
+
+static struct stat st;
+static __inline struct stat *
+StatFile(char *name)
+{
+ if (lstat(name, &st) < 0)
+ err(1, "couldn't stat %s", name);
+ return &st;
+}
+
+int
+dirselect(struct dirent *de)
+{
+ if (!strcmp(de->d_name, ".")) return 0;
+ if (!strcmp(de->d_name, "..")) return 0;
+ return 1;
+}
+
+void
+name_stat(const char *pfx, const char *dir, const char *name, struct dirent *de)
+{
+ char *buf = alloca(strlen(dir) + strlen(name) +
+ strlen(de->d_name) + 3);
+ struct stat *st;
+
+ strcpy(buf, dir);
+ strcat(buf, "/"); strcat(buf, name);
+ strcat(buf, "/"); strcat(buf, de->d_name);
+ st = StatFile(buf);
+ printf("%s %s%s %u %u %o",
+ pfx, name, de->d_name,
+ st->st_uid, st->st_gid, st->st_mode & ~S_IFMT);
+ fprintf(logf, "%s %s%s\n", pfx, name, de->d_name);
+ if (verbose > 1) {
+ fprintf(stderr, "%s %s%s\n", pfx, name, de->d_name);
+ }
+}
+
+void
+Equ(const char *dir1, const char *dir2, const char *name, struct dirent *de)
+{
+ if (de->d_type == DT_DIR) {
+ char *p = alloca(strlen(name)+strlen(de->d_name)+2);
+
+ strcpy(p, name); strcat(p, de->d_name); strcat(p, "/");
+ DoDir(dir1, dir2, p);
+ s_same_dirs++;
+ } else {
+ char *buf1 = alloca(strlen(dir1) + strlen(name) +
+ strlen(de->d_name) + 3);
+ char *buf2 = alloca(strlen(dir2) + strlen(name) +
+ strlen(de->d_name) + 3);
+ char *m1, md5_1[33], *m2, md5_2[33];
+ u_char *p1, *p2;
+ int fd1, fd2;
+ struct stat s1, s2;
+
+ strcpy(buf1, dir1);
+ strcat(buf1, "/"); strcat(buf1, name);
+ strcat(buf1, "/"); strcat(buf1, de->d_name);
+ fd1 = open(buf1, O_RDONLY);
+ if(fd1 < 0) { err(3, "%s", buf1); }
+ fstat(fd1, &s1);
+ strcpy(buf2, dir2);
+ strcat(buf2, "/"); strcat(buf2, name);
+ strcat(buf2, "/"); strcat(buf2, de->d_name);
+ fd2 = open(buf2, O_RDONLY);
+ if(fd2 < 0) { err(3, "%s", buf2); }
+ fstat(fd2, &s2);
+#if 0
+ /* XXX if we could just trust the size to change... */
+ if (s1.st_size == s2.st_size) {
+ s_same_files++;
+ s_same_bytes += s1.st_size;
+ close(fd1);
+ close(fd2);
+ goto finish;
+ }
+#endif
+ p1=mmap(0, s1.st_size, PROT_READ, MAP_PRIVATE, fd1, 0);
+ if (p1 == (u_char *)MAP_FAILED) { err(3, "%s", buf1); }
+ close(fd1);
+
+ p2=mmap(0, s2.st_size, PROT_READ, MAP_PRIVATE, fd2, 0);
+ if (p2 == (u_char *)MAP_FAILED) { err(3, "%s", buf2); }
+ close(fd2);
+
+ /* If identical, we're done. */
+ if((s1.st_size == s2.st_size) && !memcmp(p1, p2, s1.st_size)) {
+ s_same_files++;
+ s_same_bytes += s1.st_size;
+ goto finish;
+ }
+
+ s_files_chg++;
+ change++;
+ if (s1.st_size > s2.st_size)
+ s_bytes_del += (s1.st_size - s2.st_size);
+ else
+ s_bytes_add += (s2.st_size - s1.st_size);
+
+ m1 = MD5Data(p1, s1.st_size, md5_1);
+ m2 = MD5Data(p2, s2.st_size, md5_2);
+
+ /* Just a curiosity... */
+ if(!strcmp(m1, m2)) {
+ if (s1.st_size != s2.st_size)
+ fprintf(stderr,
+ "Notice: MD5 same for files of diffent size:\n\t%s\n\t%s\n",
+ buf1, buf2);
+ goto finish;
+ }
+
+ {
+ u_long l = s2.st_size + 2;
+ u_char *cmd = alloca(strlen(buf1)+strlen(buf2)+100);
+ u_char *ob = malloc(l), *p;
+ int j;
+ FILE *F;
+
+ if (s1.st_size && p1[s1.st_size-1] != '\n') {
+ if (verbose > 0)
+ fprintf(stderr,
+ "last char != \\n in %s\n",
+ buf1);
+ goto subst;
+ }
+
+ if (s2.st_size && p2[s2.st_size-1] != '\n') {
+ if (verbose > 0)
+ fprintf(stderr,
+ "last char != \\n in %s\n",
+ buf2);
+ goto subst;
+ }
+
+ for (p=p1; p<p1+s1.st_size; p++)
+ if (!*p) {
+ if (verbose > 0)
+ fprintf(stderr,
+ "NULL char in %s\n",
+ buf1);
+ goto subst;
+ }
+
+ for (p=p2; p<p2+s2.st_size; p++)
+ if (!*p) {
+ if (verbose > 0)
+ fprintf(stderr,
+ "NULL char in %s\n",
+ buf2);
+ goto subst;
+ }
+
+ strcpy(cmd, "diff -n ");
+ strcat(cmd, buf1);
+ strcat(cmd, " ");
+ strcat(cmd, buf2);
+ F = popen(cmd, "r");
+ for (j = 1, l = 0; l < s2.st_size; ) {
+ j = fread(ob+l, 1, s2.st_size - l, F);
+ if (j < 1)
+ break;
+ l += j;
+ continue;
+ }
+ if (j) {
+ l = 0;
+ while (EOF != fgetc(F))
+ continue;
+ }
+ pclose(F);
+
+ if (l && l < s2.st_size) {
+ name_stat("CTMFN", dir2, name, de);
+ printf(" %s %s %d\n", m1, m2, (unsigned)l);
+ fwrite(ob, 1, l, stdout);
+ putchar('\n');
+ s_edit_files++;
+ s_edit_bytes += l;
+ s_edit_saves += (s2.st_size - l);
+ } else {
+ subst:
+ name_stat("CTMFS", dir2, name, de);
+ printf(" %s %s %u\n", m1, m2, (unsigned)s2.st_size);
+ fwrite(p2, 1, s2.st_size, stdout);
+ putchar('\n');
+ s_sub_files++;
+ s_sub_bytes += s2.st_size;
+ }
+ free(ob);
+ }
+ finish:
+ munmap(p1, s1.st_size);
+ munmap(p2, s2.st_size);
+ }
+}
+
+void
+Add(const char *dir1, const char *dir2, const char *name, struct dirent *de)
+{
+ change++;
+ if (de->d_type == DT_DIR) {
+ char *p = alloca(strlen(name)+strlen(de->d_name)+2);
+ strcpy(p, name); strcat(p, de->d_name); strcat(p, "/");
+ name_stat("CTMDM", dir2, name, de);
+ putchar('\n');
+ s_new_dirs++;
+ DoDir(dir1, dir2, p);
+ } else if (de->d_type == DT_REG) {
+ char *buf2 = alloca(strlen(dir2) + strlen(name) +
+ strlen(de->d_name) + 3);
+ char *m2, md5_2[33];
+ u_char *p1;
+ struct stat st;
+ int fd1;
+
+ strcpy(buf2, dir2);
+ strcat(buf2, "/"); strcat(buf2, name);
+ strcat(buf2, "/"); strcat(buf2, de->d_name);
+ fd1 = open(buf2, O_RDONLY);
+ if (fd1 < 0) { err(3, "%s", buf2); }
+ fstat(fd1, &st);
+ p1=mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd1, 0);
+ if (p1 == (u_char *)MAP_FAILED) { err(3, "%s", buf2); }
+ close(fd1);
+ m2 = MD5Data(p1, st.st_size, md5_2);
+ name_stat("CTMFM", dir2, name, de);
+ printf(" %s %u\n", m2, (unsigned)st.st_size);
+ fwrite(p1, 1, st.st_size, stdout);
+ putchar('\n');
+ munmap(p1, st.st_size);
+ s_new_files++;
+ s_new_bytes += st.st_size;
+ }
+}
+
+void
+Del (const char *dir1, const char *dir2, const char *name, struct dirent *de)
+{
+ damage++;
+ change++;
+ if (de->d_type == DT_DIR) {
+ char *p = alloca(strlen(name)+strlen(de->d_name)+2);
+ strcpy(p, name); strcat(p, de->d_name); strcat(p, "/");
+ DoDir(dir1, dir2, p);
+ printf("CTMDR %s%s\n", name, de->d_name);
+ fprintf(logf, "CTMDR %s%s\n", name, de->d_name);
+ if (verbose > 1) {
+ fprintf(stderr, "CTMDR %s%s\n", name, de->d_name);
+ }
+ s_del_dirs++;
+ } else if (de->d_type == DT_REG) {
+ char *buf1 = alloca(strlen(dir1) + strlen(name) +
+ strlen(de->d_name) + 3);
+ char *m1, md5_1[33];
+ strcpy(buf1, dir1);
+ strcat(buf1, "/"); strcat(buf1, name);
+ strcat(buf1, "/"); strcat(buf1, de->d_name);
+ m1 = MD5File(buf1, md5_1);
+ printf("CTMFR %s%s %s\n", name, de->d_name, m1);
+ fprintf(logf, "CTMFR %s%s %s\n", name, de->d_name, m1);
+ if (verbose > 1) {
+ fprintf(stderr, "CTMFR %s%s\n", name, de->d_name);
+ }
+ s_del_files++;
+ s_del_bytes += StatFile(buf1)->st_size;
+ }
+}
+
+void
+GetNext(int *i, int *n, struct dirent **nl, const char *dir, const char *name, u_long *ignored, u_long *bogus, u_long *wrong)
+{
+ char buf[BUFSIZ];
+ char buf1[BUFSIZ];
+
+ for (;;) {
+ for (;;) {
+ (*i)++;
+ if (*i >= *n)
+ return;
+ strcpy(buf1, name);
+ if (buf1[strlen(buf1)-1] != '/')
+ strcat(buf1, "/");
+ strcat(buf1, nl[*i]->d_name);
+ if (flag_ignore &&
+ !regexec(&reg_ignore, buf1, 0, 0, 0)) {
+ (*ignored)++;
+ fprintf(logf, "Ignore %s\n", buf1);
+ if (verbose > 2) {
+ fprintf(stderr, "Ignore %s\n", buf1);
+ }
+ } else if (flag_bogus &&
+ !regexec(&reg_bogus, buf1, 0, 0, 0)) {
+ (*bogus)++;
+ fprintf(logf, "Bogus %s\n", buf1);
+ fprintf(stderr, "Bogus %s\n", buf1);
+ damage++;
+ } else {
+ *buf = 0;
+ if (*dir != '/')
+ strcat(buf, "/");
+ strcat(buf, dir);
+ if (buf[strlen(buf)-1] != '/')
+ strcat(buf, "/");
+ strcat(buf, buf1);
+ break;
+ }
+ free(nl[*i]); nl[*i] = 0;
+ }
+ /* If the filesystem didn't tell us, find type */
+ if (nl[*i]->d_type == DT_UNKNOWN)
+ nl[*i]->d_type = IFTODT(StatFile(buf)->st_mode);
+ if (nl[*i]->d_type == DT_REG || nl[*i]->d_type == DT_DIR)
+ break;
+ (*wrong)++;
+ if (verbose > 0)
+ fprintf(stderr, "Wrong %s\n", buf);
+ free(nl[*i]); nl[*i] = 0;
+ }
+}
+
+void
+DoDir(const char *dir1, const char *dir2, const char *name)
+{
+ int i1, i2, n1, n2, i;
+ struct dirent **nl1, **nl2;
+ char *buf1 = alloca(strlen(dir1) + strlen(name) + 4);
+ char *buf2 = alloca(strlen(dir2) + strlen(name) + 4);
+
+ strcpy(buf1, dir1); strcat(buf1, "/"); strcat(buf1, name);
+ strcpy(buf2, dir2); strcat(buf2, "/"); strcat(buf2, name);
+ n1 = scandir(buf1, &nl1, dirselect, alphasort);
+ n2 = scandir(buf2, &nl2, dirselect, alphasort);
+ i1 = i2 = -1;
+ GetNext(&i1, &n1, nl1, dir1, name, &s1_ignored, &s1_bogus, &s1_wrong);
+ GetNext(&i2, &n2, nl2, dir2, name, &s2_ignored, &s2_bogus, &s2_wrong);
+ for (;i1 < n1 || i2 < n2;) {
+
+ if (damage_limit && damage > damage_limit)
+ break;
+
+ /* Get next item from list 1 */
+ if (i1 < n1 && !nl1[i1])
+ GetNext(&i1, &n1, nl1, dir1, name,
+ &s1_ignored, &s1_bogus, &s1_wrong);
+
+ /* Get next item from list 2 */
+ if (i2 < n2 && !nl2[i2])
+ GetNext(&i2, &n2, nl2, dir2, name,
+ &s2_ignored, &s2_bogus, &s2_wrong);
+
+ if (i1 >= n1 && i2 >= n2) {
+ /* Done */
+ break;
+ } else if (i1 >= n1 && i2 < n2) {
+ /* end of list 1, add anything left on list 2 */
+ Add(dir1, dir2, name, nl2[i2]);
+ free(nl2[i2]); nl2[i2] = 0;
+ } else if (i1 < n1 && i2 >= n2) {
+ /* end of list 2, delete anything left on list 1 */
+ Del(dir1, dir2, name, nl1[i1]);
+ free(nl1[i1]); nl1[i1] = 0;
+ } else if (!(i = strcmp(nl1[i1]->d_name, nl2[i2]->d_name))) {
+ /* Identical names */
+ if (nl1[i1]->d_type == nl2[i2]->d_type) {
+ /* same type */
+ Equ(dir1, dir2, name, nl1[i1]);
+ } else {
+ /* different types */
+ Del(dir1, dir2, name, nl1[i1]);
+ Add(dir1, dir2, name, nl2[i2]);
+ }
+ free(nl1[i1]); nl1[i1] = 0;
+ free(nl2[i2]); nl2[i2] = 0;
+ } else if (i < 0) {
+ /* Something extra in list 1, delete it */
+ Del(dir1, dir2, name, nl1[i1]);
+ free(nl1[i1]); nl1[i1] = 0;
+ } else {
+ /* Something extra in list 2, add it */
+ Add(dir1, dir2, name, nl2[i2]);
+ free(nl2[i2]); nl2[i2] = 0;
+ }
+ }
+ if (n1 >= 0)
+ free(nl1);
+ if (n2 >= 0)
+ free(nl2);
+}
+
+int
+main(int argc, char **argv)
+{
+ int i;
+
+ setbuf(stderr, NULL);
+
+#if 0
+ if (regcomp(&reg_bogus, DEFAULT_BOGUS, REG_EXTENDED | REG_NEWLINE))
+ /* XXX use regerror to explain it */
+ errx(1, "default regular expression argument to -B is botched");
+ flag_bogus = 1;
+
+ if (regcomp(&reg_ignore, DEFAULT_IGNORE, REG_EXTENDED | REG_NEWLINE))
+ /* XXX use regerror to explain it */
+ errx(1, "default regular expression argument to -I is botched");
+ flag_ignore = 1;
+#endif
+
+ while ((i = getopt(argc, argv, "D:I:B:l:qv")) != -1)
+ switch (i) {
+ case 'D':
+ damage_limit = strtol(optarg, 0, 0);
+ if (damage_limit < 0)
+ errx(1, "damage limit must be positive");
+ break;
+ case 'I':
+ if (flag_ignore)
+ regfree(&reg_ignore);
+ flag_ignore = 0;
+ if (!*optarg)
+ break;
+ if (regcomp(&reg_ignore, optarg,
+ REG_EXTENDED | REG_NEWLINE))
+ /* XXX use regerror to explain it */
+ errx(1, "regular expression argument to -I is botched");
+ flag_ignore = 1;
+ break;
+ case 'B':
+ if (flag_bogus)
+ regfree(&reg_bogus);
+ flag_bogus = 0;
+ if (!*optarg)
+ break;
+ if (regcomp(&reg_bogus, optarg,
+ REG_EXTENDED | REG_NEWLINE))
+ /* XXX use regerror to explain it */
+ errx(1, "regular expression argument to -B is botched");
+ flag_bogus = 1;
+ break;
+ case 'l':
+ logf = fopen(optarg, "w");
+ if (!logf)
+ err(1, "%s", optarg);
+ setlinebuf(logf);
+ break;
+ case 'q':
+ verbose--;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case '?':
+ default:
+ Usage();
+ return (1);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!logf)
+ logf = fopen(_PATH_DEVNULL, "w");
+
+ setbuf(stdout, 0);
+
+ if (argc != 6) {
+ Usage();
+ return (1);
+ }
+
+ signal(SIGINFO, stat_info);
+
+ fprintf(stderr, "CTM_BEGIN 2.0 %s %s %s %s\n",
+ argv[0], argv[1], argv[2], argv[3]);
+ fprintf(logf, "CTM_BEGIN 2.0 %s %s %s %s\n",
+ argv[0], argv[1], argv[2], argv[3]);
+ printf("CTM_BEGIN 2.0 %s %s %s %s\n",
+ argv[0], argv[1], argv[2], argv[3]);
+ DoDir(argv[4], argv[5], "");
+ if (damage_limit && damage > damage_limit) {
+ print_stat(stderr, "DAMAGE: ");
+ errx(1, "damage of %d would exceed %d files",
+ damage, damage_limit);
+ } else if (change < 2) {
+ errx(4, "no changes");
+ } else {
+ printf("CTM_END ");
+ fprintf(logf, "CTM_END\n");
+ print_stat(stderr, "END: ");
+ }
+ exit(0);
+}
OpenPOWER on IntegriCloud