Remove CTM from 13-CURRENT after the release of FreeBSD-12.0.

The removal (and creation of a port) has been pre-announced in UPDATING
1 month ago. Packages are available for all supported FreeBSD vesions.

I did not think that another entry in UPDATING is required to note the
actual removal.

No MFC is planned - CTM shall be kept in base for all releases up to 12.x.

Reviewed by:	rgrimes
Approved by:	imp, bcr (manpages)
Relnotes:	yes
Differential Revision:	https://reviews.freebsd.org/D17935
This commit is contained in:
Stefan Eßer 2018-12-15 16:53:15 +00:00
parent 855acb84ca
commit 385e98080c
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=342126
42 changed files with 0 additions and 5579 deletions

View File

@ -479,10 +479,6 @@ is set explicitly)
Set to compile with CTF (Compact C Type Format) data.
CTF data encapsulates a reduced form of debugging information
similar to DWARF and the venerable stabs and is required for DTrace.
.It Va WITHOUT_CTM
Set to not build
.Xr ctm 1
and related utilities.
.It Va WITHOUT_CUSE
Set to not build CUSE-related programs and libraries.
.It Va WITHOUT_CXGBETOOL

View File

@ -1551,18 +1551,6 @@ OLD_FILES+=usr/bin/cpp
OLD_FILES+=usr/share/man/man1/cpp.1.gz
.endif
.if ${MK_CTM} == no
OLD_FILES+=usr/sbin/ctm
OLD_FILES+=usr/sbin/ctm_dequeue
OLD_FILES+=usr/sbin/ctm_rmail
OLD_FILES+=usr/sbin/ctm_smail
OLD_FILES+=usr/share/man/man1/ctm.1.gz
OLD_FILES+=usr/share/man/man1/ctm_dequeue.1.gz
OLD_FILES+=usr/share/man/man1/ctm_rmail.1.gz
OLD_FILES+=usr/share/man/man1/ctm_smail.1.gz
OLD_FILES+=usr/share/man/man5/ctm.5.gz
.endif
.if ${MK_CUSE} == no
OLD_FILES+=usr/include/fs/cuse/cuse_defs.h
OLD_FILES+=usr/include/fs/cuse/cuse_ioctl.h

View File

@ -1,4 +0,0 @@
.\" $FreeBSD$
Set to not build
.Xr ctm 1
and related utilities.

View File

@ -123,7 +123,6 @@ SUBDIR.${MK_BLUETOOTH}+= bluetooth
SUBDIR.${MK_BOOTPARAMD}+= bootparamd
SUBDIR.${MK_BSDINSTALL}+= bsdinstall
SUBDIR.${MK_BSNMP}+= bsnmpd
SUBDIR.${MK_CTM}+= ctm
SUBDIR.${MK_CXGBETOOL}+= cxgbetool
SUBDIR.${MK_DIALOG}+= bsdconfig
SUBDIR.${MK_EFI}+= efivar efidp efibootmgr

View File

@ -1,5 +0,0 @@
# $FreeBSD$
SUBDIR= ctm ctm_rmail ctm_smail ctm_dequeue
.include <bsd.subdir.mk>

View File

@ -1,5 +0,0 @@
# $FreeBSD$
.if exists(${.CURDIR:H:H}/Makefile.inc)
.include "${.CURDIR:H:H}/Makefile.inc"
.endif

View File

@ -1,89 +0,0 @@
# ----------------------------------------------------------------------------
# "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.
Is there a pointer to this whole glorious saga?
-----------------------------------------------
You bet! http://phk.freebsd.dk/sagas/ctm.html
Poul-Henning

View File

@ -1,24 +0,0 @@
# ----------------------------------------------------------------------------
# "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:H:H}/Makefile.inc)
.include "${.CURDIR:H:H}/Makefile.inc"
.endif
.include <bsd.prog.mk>

View File

@ -1,18 +0,0 @@
# $FreeBSD$
# Autogenerated - do NOT edit!
DIRDEPS = \
gnu/lib/csu \
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

View File

@ -1,336 +0,0 @@
.\" ----------------------------------------------------------------------------
.\" "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 November 15, 2018
.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
.%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 .
.Pp
The latest
.Nm
code has been made available as a port (misc/ctm) in preparation of removal
from base in
.Fx 13.0 .
.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.

View File

@ -1,182 +0,0 @@
.\" ----------------------------------------------------------------------------
.\" "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.

View File

@ -1,336 +0,0 @@
/*-
* SPDX-License-Identifier: Beerware
*
* ----------------------------------------------------------------------------
* "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;
}
fprintf(stderr, "CTM will be removed from FreeBSD-13, and will be "
"provided as a port (misc/ctm) or package (ctm).\n\n");
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;
}

View File

@ -1,163 +0,0 @@
/*
* ----------------------------------------------------------------------------
* "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);

View File

@ -1,116 +0,0 @@
/*-
* SPDX-License-Identifier: Beerware
*
* ----------------------------------------------------------------------------
* "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;
}

View File

@ -1,136 +0,0 @@
/*-
* SPDX-License-Identifier: Beerware
*
* ----------------------------------------------------------------------------
* "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);
}

View File

@ -1,254 +0,0 @@
/*-
* SPDX-License-Identifier: Beerware
*
* ----------------------------------------------------------------------------
* "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.");
Delete(name);
return Exit_Mess;
}
q = name;
for (;;) {
if (q[0] == '.' && q[1] == '.')
if (q[2] == '/' || q[2] == '\0') {
Fatal("Paths containing '..' are illegal.");
Delete(name);
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);
}

View File

@ -1,303 +0,0 @@
/*-
* SPDX-License-Identifier: Beerware
*
* ----------------------------------------------------------------------------
* "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;
}

View File

@ -1,297 +0,0 @@
/*-
* SPDX-License-Identifier: Beerware
*
* ----------------------------------------------------------------------------
* "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;
}

View File

@ -1,144 +0,0 @@
/*-
* SPDX-License-Identifier: Beerware
*
* ----------------------------------------------------------------------------
* "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;
}

View File

@ -1,69 +0,0 @@
/*-
* SPDX-License-Identifier: Beerware
*
* ----------------------------------------------------------------------------
* "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} };

View File

@ -1,13 +0,0 @@
# $FreeBSD$
.PATH: ${.CURDIR:H}/ctm_rmail
PROG= ctm_dequeue
MAN=
SRCS= ctm_dequeue.c error.c
CFLAGS+= -I${.CURDIR:H}/ctm_rmail
WARNS?= 1
.include <bsd.prog.mk>

View File

@ -1,17 +0,0 @@
# $FreeBSD$
# Autogenerated - do NOT edit!
DIRDEPS = \
gnu/lib/csu \
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

View File

@ -1,212 +0,0 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* 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;
snprintf(filename, sizeof(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);
}

View File

@ -1,10 +0,0 @@
# $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>

View File

@ -1,17 +0,0 @@
# $FreeBSD$
# Autogenerated - do NOT edit!
DIRDEPS = \
gnu/lib/csu \
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

View File

@ -1,510 +0,0 @@
.\" 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

View File

@ -1,672 +0,0 @@
/*
* 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;
}

View File

@ -1,102 +0,0 @@
/*
* 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);
}

View File

@ -1,5 +0,0 @@
/* $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);

View File

@ -1,139 +0,0 @@
/*
* 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; \
}

View File

@ -1,13 +0,0 @@
# $FreeBSD$
.PATH: ${.CURDIR:H}/ctm_rmail
PROG= ctm_smail
MAN=
SRCS= ctm_smail.c error.c
CFLAGS+= -I${.CURDIR:H}/ctm_rmail
WARNS?= 2
.include <bsd.prog.mk>

View File

@ -1,17 +0,0 @@
# $FreeBSD$
# Autogenerated - do NOT edit!
DIRDEPS = \
gnu/lib/csu \
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

View File

@ -1,497 +0,0 @@
/*
* 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) \
snprintf((fn), sizeof(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) \
snprintf((fn), sizeof(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);
}

View File

@ -1,25 +0,0 @@
# $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>

View File

@ -1,9 +0,0 @@
#!/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

View File

@ -1,7 +0,0 @@
#!/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

View File

@ -1,7 +0,0 @@
#!/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

View File

@ -1,9 +0,0 @@
#!/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

View File

@ -1,9 +0,0 @@
#!/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

View File

@ -1,6 +0,0 @@
#! /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

View File

@ -1,188 +0,0 @@
#!/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

View File

@ -1,597 +0,0 @@
/* $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);
}