diff options
29 files changed, 5152 insertions, 0 deletions
diff --git a/usr.sbin/ctm/Makefile b/usr.sbin/ctm/Makefile new file mode 100644 index 00000000000..4f57309a01e --- /dev/null +++ b/usr.sbin/ctm/Makefile @@ -0,0 +1,5 @@ +# $Id: Makefile,v 1.1 1996/10/30 17:32:57 graichen Exp $ + +SUBDIR= ctm ctm_scan ctm_rmail ctm_smail ctm_dequeue + +.include <bsd.subdir.mk> diff --git a/usr.sbin/ctm/Makefile.inc b/usr.sbin/ctm/Makefile.inc new file mode 100644 index 00000000000..197ef9f0d03 --- /dev/null +++ b/usr.sbin/ctm/Makefile.inc @@ -0,0 +1,5 @@ +# $Id: Makefile.inc,v 1.1 1996/10/30 17:32:57 graichen Exp $ + +.if exists(${.CURDIR}/../../Makefile.inc) +.include "${.CURDIR}/../../Makefile.inc" +.endif diff --git a/usr.sbin/ctm/README b/usr.sbin/ctm/README new file mode 100644 index 00000000000..47f87756d55 --- /dev/null +++ b/usr.sbin/ctm/README @@ -0,0 +1,97 @@ +# ---------------------------------------------------------------------------- +# "THE BEER-WARE LICENSE" (Revision 42): +# <phk@login.dknet.dk> 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 +# ---------------------------------------------------------------------------- +# +# $Id: README,v 1.1 1996/10/30 17:32:57 graichen Exp $ +# + +What will I not find in this file ? +----------------------------------- +Instructions on how to obtain FreeBSD via CTM. +Contact <phk@freefall.cdrom.com> 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 og 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 ? +--------------------------- +Don't. Send me email before you even try. This is yet not quite as trivial +as I would like. This is not to discourage you from using CTM, it is merely +to warn you that it is slightly tedious and takes much diskspace. + +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 proccessed 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. + +What features are are planned ? +------------------------------- +This list isn't exhaustive, and it isn't sorted in priority. + Reconstruct subset of tree. + Make tar-copy of things which will be affected. + Verify. + Internal editor instead of ed(1) + Support for mode + Support for uid/gid + Support for hardlinks + Support for symlinks + +Isn't this a bit thin yet ? +--------------------------- +Yes. + +Can I say something ? +--------------------- +Yes, email me: <phk@freefall.cdrom.com> + +Poul-Henning diff --git a/usr.sbin/ctm/ctm/Makefile b/usr.sbin/ctm/ctm/Makefile new file mode 100644 index 00000000000..4cde30a3ba7 --- /dev/null +++ b/usr.sbin/ctm/ctm/Makefile @@ -0,0 +1,22 @@ +# +# ---------------------------------------------------------------------------- +# "THE BEER-WARE LICENSE" (Revision 42): +# <phk@login.dknet.dk> 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 +# ---------------------------------------------------------------------------- +# +# $Id: Makefile,v 1.1 1996/10/30 17:32:57 graichen Exp $ +# + +PROG= ctm +NOTYET= ctm_ed.c +SRCS= ctm.c ctm_input.c ctm_pass1.c ctm_pass2.c ctm_pass3.c \ + ctm_syntax.c ctm_ed.c +MAN= ctm.1 ctm.5 +CFLAGS+= -Wall + +.if exists(${.CURDIR}/../../Makefile.inc) +.include "${.CURDIR}/../../Makefile.inc" +.endif +.include <bsd.prog.mk> diff --git a/usr.sbin/ctm/ctm/ctm.1 b/usr.sbin/ctm/ctm/ctm.1 new file mode 100644 index 00000000000..9b0acab048f --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm.1 @@ -0,0 +1,186 @@ +.\"---------------------------------------------------------------------------- +.\""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@login.dknet.dk> +.\" +.\" $Id: ctm.1,v 1.1 1996/10/30 17:32:57 graichen Exp $ +.\" +.Dd Mar 25, 1995 +.Os +.Dt CTM 1 +.Sh NAME +.Nm ctm +.Nd source code mirror program +.Sh SYNOPSIS +.Nm ctm +.Op Fl cFpPquv +.Op Fl b Ar basedir +.Op Fl T Ar tmpdir +.Op Fl V Ar level +.Ar file Op ... +.Sh DESCRIPTION +.Nm Ctm +was originally +.Dq Cvs Through eMail , +but now instead it seems more fitting to call it +.Dq Current Through eMail . + +.Nm 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. + +.Ss Usage + +To apply a CTM delta, you pass it to the +.Nm 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 proccessed one at a +time. Deltas that are already applied will be ignored. + +The +.Nm ctm +command runs in a number of passes. It will process the entire +input file in each pass, before commencing with the next pass. + +Before working one a file +.Ar name +.Nm ctm +first checks for the existence of the file +.Ar name.ctm . +If this file exists, +.Nm ctm +works on it instead. + +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, +.Nm 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. + +.Nm Ctm +will extract the file hierarchy below its working directory. Absolute +filenames or filenames containing references through +.Sq \&. +and +.Sq \&.\&. +are explicitly prohibited as a security measure. + +.Ss Options + +.Bl -tag -width indent -compact + +.It Fl b Ar basedir +Prepend the path +.Ar basedir +on every filename. + +.It Fl c +Check it out, don't do anything. + +.It Fl F +Force. + +.It Fl p +Less paranoid. + +.It Fl P +Paranoid. + +.It Fl q +Tell us less. + +.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. + +.El + +.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 ctm . + +.Sh EXAMPLES + +.Bd -literal + +cd ~cvs +/usr/sbin/ctm ~ctm/cvs-* + +.Ed + +.Sh DIAGNOSTICS + +Numerous messages, hopefully self-explaining. The +.Dq noise level +can be adjusted with the +.Fl q +and +.Fl v +options. + +.Sh SEE ALSO +.Xr ctm 5 , +.Xr ctm_rmail 1 + +.Sh HISTORY + +Initial trials ran during the FreeBSD 1.1.5, and many bugs and +methods were hashed out. + +The +.Nm ctm +command appeared in FreeBSD 2.1. + +.Sh AUTHORS + +The CTM system has been designed and implemented by +Poul-Henning Kamp +.Aq phk@FreeBSD.org . + +Joerg Wunsch wrote this man-page. +.Aq joerg@FreeBSD.org . diff --git a/usr.sbin/ctm/ctm/ctm.5 b/usr.sbin/ctm/ctm/ctm.5 new file mode 100644 index 00000000000..ef6bf1aa6bb --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm.5 @@ -0,0 +1,210 @@ +.\"---------------------------------------------------------------------------- +.\""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@login.dknet.dk> +.\" +.\" $Id: ctm.5,v 1.1 1996/10/30 17:32:57 graichen Exp $ +.\" +.Dd March 25, 1995 +.Os +.Dt CTM 5 +.Sh NAME +.Nm ctm +.Nd source code mirror system + +.Sh DESCRIPTION +The +.Nm ctm +transfers data in a specific file format, called a CTM delta. + +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. + +Data chunks always belong to the preceeding 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 isn't included in the count. + +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 +.Pq 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 +.Po +followed by the letter +.Sq Z +meaning this is a UTC timestamp +.Pc . +The +.Ar prefix +This 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 +.Pq numerical, decimal , +the gid +.Ar gid +.Pq numerical, decimal , +mode +.Ar mode +.Pq numerical, octal , +and the MD5 checksum +.Ar md5 . + +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 +.Pq numerical, decimal , +the new gid +.Ar gid +.Pq numerical, decimal , +new mode +.Ar mode +.Pq numerical, octal , +the old MD5 checksum +.Ar md5before , +and the new MD5 checksum +.Ar md5after . + +The following +.Ar count +bytes data are the contents of the new file. + +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 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 +.Pq 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 FreeBSD 1.1.5, and many bugs and +methods were hashed out. +The CTM system has been made publically available in FreeBSD 2.1. + +.Sh AUTHORS + +The CTM system has been designed and implemented by +Poul-Henning Kamp +.Aq phk@FreeBSD.org . + +Joerg Wunsch wrote this man-page. +.Aq joerg@FreeBSD.org . diff --git a/usr.sbin/ctm/ctm/ctm.c b/usr.sbin/ctm/ctm/ctm.c new file mode 100644 index 00000000000..4d02ca0dc13 --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm.c @@ -0,0 +1,237 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@login.dknet.dk> 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 + * ---------------------------------------------------------------------------- + * + * $Id: ctm.c,v 1.1 1996/10/30 17:32:58 graichen Exp $ + * + * 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. + * -B <file> Backup to tar-file. + * -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 + * -c Check it out, don't do anything. + * -F Force + * -p Less paranoid. + * -P Paranoid. + * -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 + * + */ + +#define EXTERN /* */ +#include "ctm.h" + +#define CTM_STATUS ".ctm_status" + +extern int Proc(char *, unsigned applied); + +int +main(int argc, char **argv) +{ + int stat=0; + int c; + extern int optopt,optind; + extern char * optarg; + unsigned applied = 0; + FILE *statfile; + u_char * basedir; + + basedir = NULL; + Verbose = 1; + Paranoid = 1; + SetTime = 0; + setbuf(stderr,0); + setbuf(stdout,0); + + while((c=getopt(argc,argv,"ab:B:cd:Fm:pPqr:R:T:uV:v")) != -1) { + switch (c) { + case 'b': basedir = optarg; break; /* Base Directory */ + case 'c': CheckIt++; break; /* Only check it */ + case 'p': Paranoid--; break; /* Less Paranoid */ + case 'P': Paranoid++; break; /* More Paranoid */ + case 'q': Verbose--; break; /* Quiet */ + case 'v': Verbose++; break; /* Verbose */ + case 'T': TmpDir = optarg; break; + case 'F': Force = 1; break; + case 'u': SetTime++; break; /* Set timestamp on files */ + case 'V': sscanf(optarg,"%d", &c); /* Verbose */ + Verbose += c; + break; + case ':': + fprintf(stderr,"Option '%c' requires an argument.\n",optopt); + stat++; + break; + case '?': + fprintf(stderr,"Option '%c' not supported.\n",optopt); + stat++; + break; + default: + fprintf(stderr,"Option '%c' not yet implemented.\n",optopt); + break; + } + } + + if(stat) { + fprintf(stderr,"%d errors during option processing\n",stat); + return Exit_Pilot; + } + stat = Exit_Done; + argc -= optind; + argv += optind; + + if (basedir == NULL) { + Buffer = (u_char *)Malloc(BUFSIZ + strlen(SUBSUFF) +1); + CatPtr = Buffer; + *Buffer = '\0'; + } else { + Buffer = (u_char *)Malloc(strlen(basedir)+ BUFSIZ + strlen(SUBSUFF) +1); + strcpy(Buffer, basedir); + CatPtr = Buffer + strlen(basedir); + if (CatPtr[-1] != '/') { + strcat(Buffer, "/"); + CatPtr++; + } + } + strcat(Buffer, CTM_STATUS); + + if((statfile = fopen(Buffer, "r")) == NULL) + fprintf(stderr, "Warning: %s not found.\n", 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; + } + + if(stat == Exit_Done) + stat = Exit_OK; + + if(Verbose) + fprintf(stderr,"Exit(%d)\n",stat); + 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) { perror(p); return Exit_Garbage; } + } else { + p = 0; + f = fopen(filename,"r"); + } + if(!f) { + perror(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 = tempnam(TmpDir,"CTMclient"); + FILE *f2 = fopen(fn,"w+"); + int i; + + if(!f2) { + perror(fn); + fclose(f); + return Exit_Broke; + } + unlink(fn); + fprintf(stderr,"Writing tmp-file \"%s\"\n",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(!p) { + rewind(f); + } else { + pclose(f); + f = popen(p,"r"); + if(!f) { perror(p); return Exit_Broke; } + } + + i=Pass2(f); + + if(!p) { + rewind(f); + } else { + pclose(f); + f = popen(p,"r"); + if(!f) { perror(p); return Exit_Broke; } + } + + if(i) { + if((!Force) || (i & ~Exit_Forcible)) + goto exit_and_close; + } + + if(CheckIt) { + fprintf(stderr,"All checks out ok.\n"); + i = Exit_Done; + goto exit_and_close; + } + + i=Pass3(f); + +exit_and_close: + if(!p) + fclose(f); + else + pclose(f); + + if(i) + return i; + + fprintf(stderr,"All done ok\n"); + return Exit_Done; +} diff --git a/usr.sbin/ctm/ctm/ctm.h b/usr.sbin/ctm/ctm/ctm.h new file mode 100644 index 00000000000..b1ffc9db2c3 --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm.h @@ -0,0 +1,144 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@login.dknet.dk> 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 + * ---------------------------------------------------------------------------- + * + * $Id: ctm.h,v 1.1 1996/10/30 17:32:58 graichen Exp $ + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <md5.h> +#include <ctype.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <sys/time.h> + +#define VERSION "2.0" +#define MAXSIZE (1024*1024*10) + +#define SUBSUFF ".ctm" +#define TMPSUFF ".ctmtmp" + +/* 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; + int *List; + }; + +extern struct CTM_Syntax Syntax[]; + +#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; + +/* + * 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 SetTime; +EXTERN struct timeval Times[2]; + +#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 + +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 Pass3(FILE *fd); + +int ctm_edit(u_char *script, int length, char *filein, char *fileout); diff --git a/usr.sbin/ctm/ctm/ctm_ed.c b/usr.sbin/ctm/ctm/ctm_ed.c new file mode 100644 index 00000000000..f6ec9e93717 --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm_ed.c @@ -0,0 +1,114 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@login.dknet.dk> 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 + * ---------------------------------------------------------------------------- + * + * $Id: ctm_ed.c,v 1.1 1996/10/30 17:32:58 graichen Exp $ + * + */ + +#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) { + perror(filein); + return 8; + } + + fo = fopen(fileout,"w"); + if(!fo) { + perror(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) { + perror(filein); + ret = 1; + } + } + if(fo) { + if(fflush(fo) != 0) { + perror(fileout); + ret = 1; + } + if(fclose(fo) != 0) { + perror(fileout); + ret = 1; + } + } + return ret; +} diff --git a/usr.sbin/ctm/ctm/ctm_input.c b/usr.sbin/ctm/ctm/ctm_input.c new file mode 100644 index 00000000000..d5eb3f368d7 --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm_input.c @@ -0,0 +1,138 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@login.dknet.dk> 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 + * ---------------------------------------------------------------------------- + * + * $Id: ctm_input.c,v 1.1 1996/10/30 17:32:58 graichen Exp $ + * + */ + +#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'); + } + if(u_chars > MAXSIZE) { + Fatal("Bytecount too large."); + return -1; + } + return u_chars; +} + +u_char * +Fdata(FILE *fd, int u_chars, MD5_CTX *ctx) +{ + u_char *p = Malloc(u_chars+1); + + if(u_chars+1 != fread(p,1,u_chars+1,fd)) { + Fatal("Truncated patch."); + return 0; + } + MD5Update(ctx,p,u_chars+1); + if(p[u_chars] != '\n') { + if(Verbose > 3) + printf("FileData wasn't followed by a newline.\n"); + Fatal("Corrupt patch."); + return 0; + } + p[u_chars] = '\0'; + return p; +} + +/*---------------------------------------------------------------------------*/ +/* get the filename in the next field, prepend BaseDir and give back the result + strings. The sustitute filename is return (the one with the suffix SUBSUFF) + if it exists and the qualifier contains CTM_Q_Name_Subst + NOTA: Buffer is already initialize with BaseDir, CatPtr is the insertion + point on this buffer + the length test in Ffield() is enough for Fname() */ + +u_char * +Fname(FILE *fd, MD5_CTX *ctx,u_char term,int qual, int verbose) +{ + u_char * p; + struct stat st; + + if ((p = Ffield(fd,ctx,term)) == NULL) return(NULL); + + strcpy(CatPtr, p); + + if (!(qual & CTM_Q_Name_Subst)) return(Buffer); + + p = Buffer + strlen(Buffer); + + strcat(Buffer, SUBSUFF); + + if ( -1 == stat(Buffer, &st) ) { + *p = '\0'; + } else { + if(verbose > 2) + fprintf(stderr,"Using %s as substitute file\n", Buffer); + } + + return (Buffer); +} diff --git a/usr.sbin/ctm/ctm/ctm_pass1.c b/usr.sbin/ctm/ctm/ctm_pass1.c new file mode 100644 index 00000000000..880df5698bf --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm_pass1.c @@ -0,0 +1,210 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@login.dknet.dk> 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 + * ---------------------------------------------------------------------------- + * + * $Id: ctm_pass1.c,v 1.1 1996/10/30 17:32:58 graichen Exp $ + * + */ + +#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,*trash=0; + struct CTM_Syntax *sp; + int slashwarn=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", ¤t); + if(current && current <= applied) { + if(Verbose) + fprintf(stderr,"Delta number %u is already applied; ignoring.\n", + current); + return Exit_Version; + } + + for(;;) { + Delete(md5); + Delete(trash); + cnt = -1; + + 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 .. */ + GETFIELD(p,sep); + j = strlen(p); + if(p[j-1] == '/' && !slashwarn) { + fprintf(stderr,"Warning: contains trailing slash\n"); + slashwarn++; + } + if (p[0] == '/') { + Fatal("Absolute paths are illegal."); + return Exit_Mess; + } + for (;;) { + if (p[0] == '.' && p[1] == '.') + if (p[2] == '/' || p[2] == '\0') { + Fatal("Paths containing '..' are illegal."); + return Exit_Mess; + } + if ((p = strchr(p, '/')) == NULL) + break; + p++; + } + 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 1; + default: + fprintf(stderr,"List = 0x%x\n",j); + Fatal("List had garbage."); + return 1; + + } + + } + } + if(Verbose > 5) + putc('\n',stderr); + continue; + } + + Delete(md5); + 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 1; + } + if (-1 != getc(fd)) { + if(!Force) { + Fatal("Trailing junk in CTM-file. Can Force with -F."); + return 16; + } + } + return 0; +} diff --git a/usr.sbin/ctm/ctm/ctm_pass2.c b/usr.sbin/ctm/ctm/ctm_pass2.c new file mode 100644 index 00000000000..3ca7beaf754 --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm_pass2.c @@ -0,0 +1,194 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@login.dknet.dk> 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 + * ---------------------------------------------------------------------------- + * + * $Id: ctm_pass2.c,v 1.1 1996/10/30 17:32:58 graichen Exp $ + * + */ + +#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; + u_char *trash=0,*name=0; + struct CTM_Syntax *sp; + struct stat st; + int ret = 0; + char md5_1[33]; + + 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; + + 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); + /* 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((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); + 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; + } + if(j & CTM_Q_MD5_After) { + 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(!strcmp(sp->Key,"FN")) { + p = tempnam(TmpDir,"CTMclient"); + 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); + } + + break; + default: WRONG + } + } + } + + Delete(trash); + Delete(name); + Delete(md5); + + q = MD5End (&ctx,md5_1); + GETFIELD(p,'\n'); /* <MD5> */ + if(strcmp(q,p)) WRONG + if (-1 != getc(fd)) WRONG + return ret; +} diff --git a/usr.sbin/ctm/ctm/ctm_pass3.c b/usr.sbin/ctm/ctm/ctm_pass3.c new file mode 100644 index 00000000000..11e1432a55a --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm_pass3.c @@ -0,0 +1,263 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@login.dknet.dk> 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 + * ---------------------------------------------------------------------------- + * + * $Id: ctm_pass3.c,v 1.1 1996/10/30 17:32:58 graichen Exp $ + * + */ + +#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)) { + fprintf(stderr, " utimes(): %s: %s\n", name, strerror(errno)); + 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]; + struct timeval times[2]; + + 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'; + + 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) { + perror(name); + WRONG + } + if(cnt != write(i,trash,cnt)) { + perror(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)) { + perror(name); + pclose(ed); + WRONG + } + fprintf(ed,"w %s\n",name); + if(pclose(ed)) { + perror("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 + } + rename(buf,name); + if(strcmp(md5,MD5File(name,md5_1))) { + fprintf(stderr," %s %s Edit failed MD5 check.\n", + sp->Key,name); + 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 (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) + */ + sprintf(buf,"rm -rf %s",name); + system(buf); + continue; + } + WRONG + } + + Delete(md5); + Delete(uid); + Delete(gid); + Delete(mode); + Delete(md5before); + Delete(trash); + Delete(name); + + q = MD5End (&ctx,md5_1); + GETFIELD(p,'\n'); + if(strcmp(q,p)) WRONG + if (-1 != getc(fd)) WRONG + return 0; +} diff --git a/usr.sbin/ctm/ctm/ctm_syntax.c b/usr.sbin/ctm/ctm/ctm_syntax.c new file mode 100644 index 00000000000..39de06ef940 --- /dev/null +++ b/usr.sbin/ctm/ctm/ctm_syntax.c @@ -0,0 +1,67 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@login.dknet.dk> 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 + * ---------------------------------------------------------------------------- + * + * $Id: ctm_syntax.c,v 1.1 1996/10/30 17:32:58 graichen Exp $ + * + */ + +#include "ctm.h" + +/* The fields... */ +#define Name CTM_F_Name +#define Uid CTM_F_Uid +#define Gid CTM_F_Gid +#define Mode CTM_F_Mode +#define MD5 CTM_F_MD5 +#define Count CTM_F_Count +#define Bytes CTM_F_Bytes + +/* The qualifiers... */ +#define File CTM_Q_Name_File +#define Dir CTM_Q_Name_Dir +#define New CTM_Q_Name_New +#define Subst CTM_Q_Name_Subst +#define After CTM_Q_MD5_After +#define Before CTM_Q_MD5_Before +#define Chunk CTM_Q_MD5_Chunk +#define Force CTM_Q_MD5_Force + +static int ctmFM[] = /* File Make */ + { Name|File|New|Subst, Uid, Gid, Mode, + MD5|After|Chunk, Count, Bytes,0 }; + +static int ctmFS[] = /* File Substitute */ + { Name|File|Subst, Uid, Gid, Mode, + MD5|Before|Force, MD5|After|Chunk, Count, Bytes,0 }; + +static int ctmFE[] = /* File Edit */ + { Name|File|Subst, Uid, Gid, Mode, + MD5|Before, MD5|After, Count, Bytes,0 }; + +static int ctmFR[] = /* File Remove */ + { Name|File|Subst, MD5|Before, 0 }; + +static int ctmAS[] = /* Attribute Substitute */ + { Name|Subst, Uid, Gid, Mode, 0 }; + +static int ctmDM[] = /* Directory Make */ + { Name|Dir|New , Uid, Gid, Mode, 0 }; + +static int ctmDR[] = /* Directory Remove */ + { Name|Dir, 0 }; + +struct CTM_Syntax Syntax[] = { + { "FM", ctmFM }, + { "FS", ctmFS }, + { "FE", ctmFE }, + { "FN", ctmFE }, + { "FR", ctmFR }, + { "AS", ctmAS }, + { "DM", ctmDM }, + { "DR", ctmDR }, + { 0, 0} }; diff --git a/usr.sbin/ctm/ctm_dequeue/Makefile b/usr.sbin/ctm/ctm_dequeue/Makefile new file mode 100644 index 00000000000..543e77e3e96 --- /dev/null +++ b/usr.sbin/ctm/ctm_dequeue/Makefile @@ -0,0 +1,8 @@ +PROG= ctm_dequeue +SRCS= ctm_dequeue.c error.c +NOMAN= yes +CFLAGS+= -Wall -I${.CURDIR}/../ctm_rmail + +.PATH: ${.CURDIR}/../ctm_rmail + +.include <bsd.prog.mk> diff --git a/usr.sbin/ctm/ctm_dequeue/ctm_dequeue.c b/usr.sbin/ctm/ctm_dequeue/ctm_dequeue.c new file mode 100644 index 00000000000..0ddd3aa869f --- /dev/null +++ b/usr.sbin/ctm/ctm_dequeue/ctm_dequeue.c @@ -0,0 +1,233 @@ +/* + * 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. + * + * $Id: ctm_dequeue.c,v 1.1 1996/10/30 17:32:58 graichen Exp $ + */ + +/* + * Change this if you want to alter how many files it sends out by + * default + */ + +#define DEFAULT_NUM 2 + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fts.h> +#include <sys/mman.h> +#include <errno.h> +#include <paths.h> +#include "error.h" +#include "options.h" + +int fts_sort(const FTSENT **, const FTSENT **); +FILE *open_sendmail(void); +int close_sendmail(FILE *fp); + +int +main(int argc, char **argv) +{ + char *log_file = NULL; + char *queue_dir = NULL; + char *list[2]; + char *buffer, *filename; + int num_to_send = DEFAULT_NUM, piece, fp, len; + FTS *fts; + FTSENT *ftsent; + FILE *sfp; + + 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(); + + queue_dir = argv[1]; + list[0] = queue_dir; + list[1] = NULL; + + fts = fts_open(list, FTS_PHYSICAL, fts_sort); + if (fts == NULL) + { + err("fts failed on `%s'", queue_dir); + exit(1); + } + + (void) fts_read(fts); + + ftsent = fts_children(fts, 0); + if (ftsent == NULL) + { + err("ftschildren failed"); + exit(1); + } + + /* assumption :-( */ + len = strlen(queue_dir) + 40; + filename = malloc(len); + if (filename == NULL) + { + err("malloc failed"); + exit(1); + } + + for (piece = 0; piece < num_to_send ; piece++) + { + /* Skip non-files and files we should ignore (ones starting with `.') */ + +#define ISFILE ((ftsent->fts_info & FTS_F) == FTS_F) +#define IGNORE (ftsent->fts_name[0] == '.') +#define HASNEXT (ftsent->fts_link != NULL) + + while(((!ISFILE) || (IGNORE)) && (HASNEXT)) + ftsent = ftsent->fts_link; + + if ((!ISFILE) || (IGNORE)) + { + err("No more chunks to mail"); + exit(0); + } + +#undef ISFILE +#undef IGNORE +#undef HASNEXT + + if (snprintf(filename, len, "%s/%s", queue_dir, ftsent->fts_name) > len) + err("snprintf(filename) longer than buffer"); + + fp = open(filename, O_RDONLY, 0); + if (fp < 0) + { + err("open(`%s') failed, errno = %d", filename, errno); + exit(1); + } + + buffer = mmap(0, ftsent->fts_statp->st_size, PROT_READ, MAP_PRIVATE, fp, 0); + if (((int) buffer) <= 0) + { + err("mmap failed, errno = %d", errno); + exit(1); + } + + sfp = open_sendmail(); + if (sfp == NULL) + exit(1); + + if (fwrite(buffer, ftsent->fts_statp->st_size, 1, sfp) < 1) + { + err("fwrite failed: errno = %d", errno); + close_sendmail(sfp); + exit(1); + } + + if (!close_sendmail(sfp)) + exit(1); + + munmap(buffer, ftsent->fts_statp->st_size); + close(fp); + + if (unlink(filename) < 0) + { + err("unlink of `%s' failed", filename); + exit(1); + } + + err("sent file `%s'", ftsent->fts_name); + + if (ftsent->fts_link != NULL) + ftsent = ftsent->fts_link; + else + break; + } + + err("exiting normally"); + return(0); +} + +int +fts_sort(const FTSENT ** a, const FTSENT ** b) +{ + int a_info, b_info; + + a_info = (*a)->fts_info; + if (a_info == FTS_ERR) + return (0); + b_info = (*b)->fts_info; + if (b_info == FTS_ERR) + return (0); + + return (strcmp((*a)->fts_name, (*b)->fts_name)); +} + +/* + * 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 -t", _PATH_SENDMAIL); + if ((fp = popen(buf, "w")) == NULL) + err("cannot start sendmail"); + return fp; +} + + +/* + * Close a pipe to sendmail. Sendmail will then do its bit. + * Return 1 on success, 0 on failure. + */ +int +close_sendmail(FILE *fp) +{ + int status; + + fflush(fp); + if (ferror(fp)) + { + err("error writing to sendmail"); + return 0; + } + + if ((status = pclose(fp)) != 0) + err("sendmail failed with status %d", status); + + return (status == 0); +} diff --git a/usr.sbin/ctm/ctm_rmail/Makefile b/usr.sbin/ctm/ctm_rmail/Makefile new file mode 100644 index 00000000000..cb672f2c17c --- /dev/null +++ b/usr.sbin/ctm/ctm_rmail/Makefile @@ -0,0 +1,6 @@ +PROG= ctm_rmail +SRCS= ctm_rmail.c error.c +CFLAGS+= -Wall +MLINKS+= ctm_rmail.1 ctm_smail.1 + +.include <bsd.prog.mk> diff --git a/usr.sbin/ctm/ctm_rmail/ctm_rmail.1 b/usr.sbin/ctm/ctm_rmail/ctm_rmail.1 new file mode 100644 index 00000000000..d52b02deaf6 --- /dev/null +++ b/usr.sbin/ctm/ctm_rmail/ctm_rmail.1 @@ -0,0 +1,368 @@ +.\" 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 +.\" +.Dd January 24, 1995 +.Dt CTM_MAIL 1 +.Os +.Sh NAME +.Nm ctm_smail, ctm_rmail +.Nd send and receive +.Nm ctm +deltas via mail +.Sh SYNOPSIS +.Nm ctm_smail +.Op Fl l Ar log +.Op Fl m Ar maxmsgsize +.Op Fl c Ar maxctmsize +.Ar ctm-delta +.Ar mail-alias +.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 conjuction with the +.Xr ctm 1 +command, +.Nm ctm_smail +and +.Nm ctm_rmail +are used to distribute changes to a source tree via email. +.Nm ctm_smail +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. +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 FreeBSD-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. +.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_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 one of the possible future enhancements to +.Xr ctm +is 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 . +Don't 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. Note that you need to make your own arrangements to capture it. +.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 +.nh +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 is 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 ftpmail, or your good mate at the university. +.Ed +.Pp +You are then on your own! +.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/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 +.Sh SECURITY +If you automatically take your mail and pass it to a file tree patcher, you +might think you are handing the keys to your system to the hackers! Happily, +the window for mischief is quite small. +.Nm ctm_rmail +is careful to write only to the directories given to it (by not believing any +.Dq / +characters in the delta name), and the latest +.Xr ctm +disallows absolute pathnames and +.Dq \&\.\. +in files it manipulates, so the worst you +could lose are a few source tree files (recoverable from your deltas). +Since +.Xr ctm +requires that a +.Xr md5 +checksum match before it touches a file, only fellow +source recipients would be able to generate a fake delta, and they're such +nice folk that they wouldn't even think of it! :-) +.Pp +Even this possibility could be removed by using cryptographic signatures. +A possible future enhancement would be to use +.Nm PGP +to provide a secure wrapper. +.\" This next request is for sections 1, 6, 7 & 8 only +.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 PIECEDIR/* +Pieces of deltas waiting for the rest. +.It Pa DELTADIR/* +Completed deltas. +.It Pa BASEDIR/.ctm_status +File containing name and number of the next delta to be applied to this +source tree. +.\" This next request is for sections 1, 6, 7 & 8 only +.\" (command return values (to shell) and fprintf/stderr type diagnostics) +.Sh DIAGNOSTICS +.Nm ctm_smail +and +.Nm ctm_rmail +return exit status 0 for success, and 1 for various failures. +.Nm ctm_rmail +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. +.Pp +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 +.Nm ctm_rmail +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 +.sp \n(Ppu +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 +turn up here too. Error messages should be self explanatory. +.\" The next request is for sections 2 and 3 error and signal handling only. +.\" .Sh ERRORS +.Sh SEE ALSO +.Xr ctm 1 , +.Xr ctm 5 +.\" .Sh STANDARDS +.\" .Sh HISTORY +.Sh AUTHOR +Stephen McKay <syssgm@devetir.qld.gov.au> +.\" .Sh BUGS diff --git a/usr.sbin/ctm/ctm_rmail/ctm_rmail.c b/usr.sbin/ctm/ctm_rmail/ctm_rmail.c new file mode 100644 index 00000000000..6b019221bd5 --- /dev/null +++ b/usr.sbin/ctm/ctm_rmail/ctm_rmail.c @@ -0,0 +1,661 @@ +/* + * 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <strings.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. */ + +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; + + 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, "%s %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; + + if (sscanf(line, "CTM_MAIL BEGIN %s %d %d %c", + delta, &pce, &npieces, junk) != 3) + continue; + + while ((s = strchr(delta, '/')) != NULL) + *s = '_'; + + got_one++; + strcpy(tname, piece_dir); + strcat(tname, "/p.XXXXXX"); + if (mktemp(tname) == NULL) + { + err("*mktemp: '%s'", tname); + status++; + continue; + } + if ((ofp = fopen(tname, "w")) == NULL) + { + err("cannot open '%s' for writing", 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) + { + 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]; + + strcpy(tname, delta_dir); + strcat(tname, "/d.XXXXXX"); + if (mktemp(tname) == NULL) + { + err("*mktemp: '%s'", tname); + return 0; + } + if ((dfp = fopen(tname, "w")) == NULL) + { + err("cannot open '%s' for writing", 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; + } + + /* + * Throw the pieces away. + */ + for (i = 1; i <= npieces; i++) + { + mk_piece_name(pname, delta, i, npieces); + if (unlink(pname) < 0) + err("*unlink: '%s'", pname); + } + + err("%s complete", delta); + return 1; + } + + +/* + * MIME BASE64 decode table. + */ +static unsigned char from_b64[0x80] = + { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + +/* + * Decode a line of ASCII into binary. Returns the number of bytes in + * the output buffer, or < 0 on indigestable input. Error output is + * the negative of the index of the inedible character. + */ +int +decode_line(char *line, char *out_buf) + { + unsigned char *ip = (unsigned char *)line; + unsigned char *op = (unsigned char *)out_buf; + unsigned long bits; + unsigned x; + + for (;;) + { + if (*ip >= 0x80 || (x = from_b64[*ip]) >= 0x40) + break; + bits = x << 18; + ip++; + if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40) + { + bits |= x << 12; + *op++ = bits >> 16; + ip++; + if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40) + { + bits |= x << 6; + *op++ = bits >> 8; + ip++; + if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40) + { + bits |= x; + *op++ = bits; + ip++; + } + } + } + } + + if (*ip == '\0' || *ip == '\n') + return op - (unsigned char *)out_buf; + else + return -(ip - (unsigned char *)line); + } + + +/* + * Create and lock the given file. + * + * Clearing the lock is as simple as closing the file descriptor we return. + */ +int +lock_file(char *name) + { + int lfd; + + if ((lfd = open(name, O_WRONLY|O_CREAT, 0600)) < 0) + { + err("*open: '%s'", name); + return -1; + } + if (flock(lfd, LOCK_EX) < 0) + { + close(lfd); + err("*flock: '%s'", name); + return -1; + } + return lfd; + } diff --git a/usr.sbin/ctm/ctm_rmail/error.c b/usr.sbin/ctm/ctm_rmail/error.c new file mode 100644 index 00000000000..724b117184a --- /dev/null +++ b/usr.sbin/ctm/ctm_rmail/error.c @@ -0,0 +1,97 @@ +/* + * 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. + */ + +#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(char *fmt, ...) + { + va_list ap; + time_t now; + struct tm *tm; + FILE *fp; + int x = errno; + int want_errno; + + if ((fp = error_fp) == NULL) + { + fp = stderr; + if (prog != NULL) + fprintf(fp, "%s: ", prog); + } + else + { + time(&now); + tm = localtime(&now); + fprintf(fp, "%04d-%02d-%02d %02d:%02d ", tm->tm_year+1900, + tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min); + } + + want_errno = 0; + if (*fmt == '*') + want_errno++, fmt++; + + va_start(ap, fmt); + vfprintf(fp, fmt, ap); + va_end(ap); + + if (want_errno) + fprintf(fp, ": %s", strerror(x)); + + fprintf(fp, "\n"); + fflush(fp); + } diff --git a/usr.sbin/ctm/ctm_rmail/error.h b/usr.sbin/ctm/ctm_rmail/error.h new file mode 100644 index 00000000000..b8bc4521e10 --- /dev/null +++ b/usr.sbin/ctm/ctm_rmail/error.h @@ -0,0 +1,3 @@ +extern void err_set_log(char *log_file); +extern void err_prog_name(char *name); +extern void err(char *fmt, ...); diff --git a/usr.sbin/ctm/ctm_rmail/options.h b/usr.sbin/ctm/ctm_rmail/options.h new file mode 100644 index 00000000000..18b844cebf5 --- /dev/null +++ b/usr.sbin/ctm/ctm_rmail/options.h @@ -0,0 +1,137 @@ +/* + * 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). + */ + +static char *O_usage; +static char *O_name; +extern long atol(); + +void +pusage() + { + /* + * Avoid gratuitously loading stdio. + */ + write(2, "Usage: ", 7); + write(2, O_name, strlen(O_name)); + write(2, " ", 1); + write(2, O_usage, strlen(O_usage)); + write(2, "\n", 1); + } + +#define usage() (pusage(), exit(1)) + +#define OPTIONS(usage_msg) \ + { \ + char O_cont; \ + O_usage = (usage_msg); \ + O_name = argv[0]; \ + while (*++argv && **argv == '-') \ + { \ + if ((*argv)[1] == '\0') \ + break; \ + argc--; \ + if ((*argv)[1] == '-' && (*argv)[2] == '\0') \ + { \ + argv++; \ + break; \ + } \ + O_cont = 1; \ + while (O_cont) \ + switch (*++*argv) \ + { \ + default: \ + case '-': \ + usage(); \ + case '\0': \ + O_cont = 0; + +#define FLAG(x,flag) \ + break; \ + case (x): \ + (flag) = 1; + +#define CHAR(x,ch) \ + break; \ + case (x): \ + O_cont = 0; \ + if (*++*argv == '\0' && (--argc, *++argv == 0)) \ + usage(); \ + (ch) = **argv; + +#define NUMBER(x,n) \ + break; \ + case (x): \ + O_cont = 0; \ + if (*++*argv == '\0' && (--argc, *++argv == 0)) \ + usage(); \ + (n) = atol(*argv); + +#define STRING(x,str) \ + break; \ + case (x): \ + O_cont = 0; \ + if (*++*argv == '\0' && (--argc, *++argv == 0)) \ + usage(); \ + (str) = *argv; + +#define SUFFIX(x,str) \ + break; \ + case (x): \ + (str) = ++*argv; \ + O_cont = 0; + +#define ENDOPTS \ + break; \ + } \ + } \ + *--argv = O_name; \ + } diff --git a/usr.sbin/ctm/ctm_scan/Makefile b/usr.sbin/ctm/ctm_scan/Makefile new file mode 100644 index 00000000000..a52c5d62393 --- /dev/null +++ b/usr.sbin/ctm/ctm_scan/Makefile @@ -0,0 +1,16 @@ +# +# ---------------------------------------------------------------------------- +# "THE BEER-WARE LICENSE" (Revision 42): +# <phk@login.dkuug.dk> 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 +# ---------------------------------------------------------------------------- +# +# $Id: Makefile,v 1.1 1996/10/30 17:32:59 graichen Exp $ +# +PROG= ctm_scan +NOMAN= 1 +CFLAGS+= -Wall + +.include "../../Makefile.inc" +.include <bsd.prog.mk> diff --git a/usr.sbin/ctm/ctm_scan/ctm_scan.c b/usr.sbin/ctm/ctm_scan/ctm_scan.c new file mode 100644 index 00000000000..323880b1d75 --- /dev/null +++ b/usr.sbin/ctm/ctm_scan/ctm_scan.c @@ -0,0 +1,187 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@login.dkuug.dk> 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 + * ---------------------------------------------------------------------------- + * + * $Id: ctm_scan.c,v 1.1 1996/10/30 17:32:59 graichen Exp $ + * + */ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <dirent.h> +#include <md5.h> + +int barf[256]; +int CheckMode = 0; + +int +pstrcmp(const void *pp, const void *qq) +{ + return strcmp(*(char **)pp,*(char **)qq); +} + +int +Do(char *path) +{ + DIR *d; + struct dirent *de; + struct stat st; + int ret=0; + u_char buf[BUFSIZ]; + u_char data[BUFSIZ],*q; + int bufp; + MD5_CTX ctx; + int fd,i,j,k,l,npde,nde=0; + char **pde, md5[33]; + + npde = 1; + pde = malloc(sizeof *pde * (npde+1)); + d = opendir(path); + if(!d) { perror(path); return 2; } + if(!strcmp(path,".")) { + *buf = 0; + } else { + strcpy(buf,path); + if(buf[strlen(buf)-1] != '/') + strcat(buf,"/"); + } + bufp = strlen(buf); + while((de=readdir(d))) { + if(!strcmp(de->d_name,".")) continue; + if(!strcmp(de->d_name,"..")) continue; + if(nde >= npde) { + npde *= 2; + pde = realloc(pde,sizeof *pde * (npde+1)); + } + strcpy(buf+bufp,de->d_name); + if(stat(buf,&st)) { + ret |= 1; + continue; + } + pde[nde] = malloc(strlen(buf+bufp)+1); + strcpy(pde[nde++],buf+bufp); + } + closedir(d); + if(!nde) return 0; + qsort(pde,nde,sizeof *pde, pstrcmp); + for(k=0;k<nde;k++) { + strcpy(buf+bufp,pde[k]); + free(pde[k]); + if(stat(buf,&st)) { + ret |= 1; + continue; + } + switch(st.st_mode & S_IFMT) { + case S_IFDIR: + if(!CheckMode) { + i = printf("d %s %o %u %u - - -\n", + buf,st.st_mode & (~S_IFMT),st.st_uid,st.st_gid); + if(!i) + exit(-1); + } + ret |= Do(buf); + break; + case S_IFREG: + fd = open(buf,O_RDONLY); + if(fd < 0) { + ret |= 1; + continue; + } + MD5Init(&ctx); + l = 1; + j = 0; + while(0 < (i = read(fd,data,sizeof data))) { + l = (data[i-1] == '\n'); + if(!CheckMode) + MD5Update(&ctx,data,i); + for(q=data;i && !j;i--) + if(barf[*q++]) + j=1; + } + close(fd); + if(CheckMode) { + if(j || !l) { + i = printf("%s",buf); + if(!i) exit(-1); + if(j) printf(" Illegal characters."); + if(!l) printf(" No final newline."); + i = printf(".\n"); + if(!i) exit(-1); + } + } else { + if(!l) + j=2; + i = printf("f %s %o %u %u %u %lu %s\n", + buf,st.st_mode & (~S_IFMT),st.st_uid,st.st_gid, + j,(u_long)st.st_size,MD5End(&ctx,md5)); + if(!i) exit(-1); + } + break; + default: + fprintf(stderr,"%s: type 0%o\n",buf, st.st_mode & S_IFMT); + ret |= 4; + break; + } + } + free(pde); + return ret; +} + +int +main(int argc, char **argv) +{ + int i; + + /* + * Initialize barf[], characters diff/patch will not appreciate. + */ + + barf[0x00] = 1; + barf[0x7f] = 1; + barf[0x80] = 1; + barf[0xff] = 1; + + /* + * -c is CheckMode + */ + if (argc > 1 && !strcmp(argv[1],"-c")) { + CheckMode=1; + argc--; + argv++; + } + + /* + * First argument, if any, is where to do the work. + */ + if (argc > 1) { + if(chdir(argv[1])) { + perror(argv[1]); + return 2; + } + argc--; + argv++; + } + + /* + * Scan the directories recursively. + */ + if (argc > 1) { + while (argc > 1) { + i = Do(argv[1]); + argc--; + argv++; + if (i) + return i; + } + return i; + } else + return Do("."); +} diff --git a/usr.sbin/ctm/ctm_smail/Makefile b/usr.sbin/ctm/ctm_smail/Makefile new file mode 100644 index 00000000000..e9eb84680ba --- /dev/null +++ b/usr.sbin/ctm/ctm_smail/Makefile @@ -0,0 +1,7 @@ +PROG= ctm_smail +SRCS= ctm_smail.c error.c +NOMAN= 1 +CFLAGS+= -Wall -I${.CURDIR}/../ctm_rmail +.PATH: ${.CURDIR}/../ctm_rmail + +.include <bsd.prog.mk> diff --git a/usr.sbin/ctm/ctm_smail/ctm_smail.c b/usr.sbin/ctm/ctm_smail/ctm_smail.c new file mode 100644 index 00000000000..c995572ae61 --- /dev/null +++ b/usr.sbin/ctm/ctm_smail/ctm_smail.c @@ -0,0 +1,591 @@ +/* + * Send a compressed CTM delta to a recipient mailing list by encoding it + * in safe ASCII characters, in mailer-friendly chunks, and passing it + * to sendmail. 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. + * + * $Id: ctm_smail.c,v 1.1 1996/10/30 17:32:59 graichen Exp $ + */ + +#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 "error.h" +#include "options.h" + +#define DEF_MAX_MSG 64000 /* Default maximum mail msg minus headers. */ + +#define LINE_LENGTH 76 /* Chars per encode line. Divisible by 4. */ + +void chop_and_send(char *delta, off_t ctm_size, long max_msg_size, + char *mail_alias); +void chop_and_queue(char *delta, off_t ctm_size, long max_msg_size, + char *queue_dir, char *mail_alias); +unsigned encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size); +void write_header(FILE *sfp, char *mail_alias, char *delta, int pce, + int npieces); +void write_trailer(FILE *sfp, unsigned sum); +void apologise(char *delta, off_t ctm_size, long max_ctm_size, + char *mail_alias); +FILE *open_sendmail(void); +int close_sendmail(FILE *fp); +int lock_queuedir(char *queue_dir); +void free_lock(int lockf, char *queue_dir); +void add_to_queue(char *queue_dir, char *mail_alias, char *delta, int npieces, char **tempnames); + +int +main(int argc, char **argv) + { + 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; + 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 (stat(delta_file, &sb) < 0) + { + err("%s: %s", delta_file, strerror(errno)); + exit(1); + } + + if (max_ctm_size != 0 && sb.st_size > max_ctm_size) + apologise(delta_file, sb.st_size, max_ctm_size, mail_alias); + else if (queue_dir == NULL) + chop_and_send(delta_file, sb.st_size, max_msg_size, mail_alias); + else + chop_and_queue(delta_file, sb.st_size, max_msg_size, queue_dir, mail_alias); + + return 0; + } + + +/* + * Carve our CTM delta into pieces, encode them, and send them. + */ +void +chop_and_send(char *delta, off_t ctm_size, long max_msg_size, char *mail_alias) + { + int npieces; + long msg_size; + long exp_size; + int pce; + FILE *sfp; + FILE *dfp; + unsigned sum; + +#ifdef howmany +#undef howmany +#endif + +#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 ((dfp = fopen(delta, "r")) == NULL) + { + err("cannot open '%s' for reading.", delta); + exit(1); + } + + for (pce = 1; pce <= npieces; pce++) + { + sfp = open_sendmail(); + if (sfp == NULL) + exit(1); + write_header(sfp, mail_alias, delta, pce, npieces); + sum = encode_body(sfp, dfp, msg_size); + write_trailer(sfp, sum); + if (!close_sendmail(sfp)) + exit(1); + err("%s %d/%d sent to %s", delta, pce, npieces, mail_alias); + } + + fclose(dfp); + } + +/* + * Carve our CTM delta into pieces, encode them, and drop them in the + * queue dir. + * + * Basic algorythm: + * + * - for (each piece) + * - gen. temp. file name (one which the de-queuer will ignore) + * - record in array + * - open temp. file + * - encode delta (including headers) into the temp file + * - close temp. file + * - end + * - lock queue directory + * - foreach (temp. file) + * - rename to the proper filename + * - end + * - unlock queue directory + * + * This is probably overkill, but it means that incomplete deltas + * don't get mailed, and also reduces the window for lock races + * between ctm_smail and the de-queueing process. + */ + +void +chop_and_queue(char *delta, off_t ctm_size, long max_msg_size, char *queue_dir, char *mail_alias) +{ + int npieces, pce, len; + long msg_size, exp_size; + FILE *sfp, *dfp; + unsigned sum; + char **tempnames, *tempnam, *sn; + +#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 + + /* + * allocate space for the array of filenames. Try to be portable + * by not assuming anything to do with sizeof(char *) + */ + tempnames = malloc(npieces * sizeof(char *)); + if (tempnames == NULL) + { + err("malloc for tempnames failed"); + exit(1); + } + + len = strlen(queue_dir) + 16; + tempnam = malloc(len); + if (tempnam == NULL) + { + err("malloc for tempnames failed"); + exit(1); + } + + if ((dfp = fopen(delta, "r")) == NULL) + { + err("cannot open '%s' for reading.", delta); + exit(1); + } + + if ((sn = strrchr(delta, '/')) == NULL) + sn = delta; + else + sn++; + + for (pce = 1; pce <= npieces; pce++) + { + if (snprintf(tempnam, len, "%s/.%08d-%03d", queue_dir, getpid(), pce) >= len) + err("Whoops! tempnam isn't long enough"); + + tempnames[pce - 1] = strdup(tempnam); + if (tempnames[pce - 1] == NULL) + { + err("strdup failed for temp. filename"); + exit(1); + } + + sfp = fopen(tempnam, "w"); + if (sfp == NULL) + exit(1); + + write_header(sfp, mail_alias, delta, pce, npieces); + sum = encode_body(sfp, dfp, msg_size); + write_trailer(sfp, sum); + + if (fclose(sfp) != 0) + exit(1); + + err("%s %d/%d created succesfully", sn, pce, npieces); + } + + add_to_queue(queue_dir, mail_alias, delta, npieces, tempnames); + + fclose(dfp); + +} + + +/* + * 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. + */ +unsigned +encode_body(FILE *sm_fp, FILE *delta_fp, long msg_size) + { + 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."); + exit(1); + } + + if (ferror(sm_fp)) + { + err("error writing encoded file"); + exit(1); + } + + return cksum; + } + + +/* + * Write the mail header and data header. + */ +void +write_header(FILE *sfp, char *mail_alias, char *delta, int pce, int npieces) + { + char *sn; + + if ((sn = strrchr(delta, '/')) == NULL) + sn = delta; + else + sn++; + + 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", sn, pce, npieces); + + fprintf(sfp, "CTM_MAIL BEGIN %s %d %d\n", sn, 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. + */ +void +apologise(char *delta, off_t ctm_size, long max_ctm_size, char *mail_alias) + { + FILE *sfp; + char *sn; + + sfp = open_sendmail(); + if (sfp == NULL) + exit(1); + + if ((sn = strrchr(delta, '/')) == NULL) + sn = delta; + else + sn++; + + fprintf(sfp, "From: %s-owner\n", mail_alias); + fprintf(sfp, "To: %s\n", mail_alias); + fprintf(sfp, "Subject: ctm-notice %s\n\n", sn); + + fprintf(sfp, "%s is %ld bytes. The limit is %ld bytes.\n\n", sn, + (long)ctm_size, max_ctm_size); + fprintf(sfp, "You can retrieve this delta via ftpmail, or your good mate at the university.\n"); + + if (!close_sendmail(sfp)) + exit(1); + } + + +/* + * 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 -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); + } + +/* + * Lock the queuedir so we're the only guy messing about in there. + */ +int +lock_queuedir(char *queue_dir) +{ + int fp, len; + char *buffer; + struct stat sb; + + len = strlen(queue_dir) + 8; + + buffer = malloc(len); + if (buffer == NULL) + { + err("malloc failed in lock_queuedir"); + exit(1); + } + + if (snprintf(buffer, len, "%s/.lock", queue_dir) >= len) + err("Whoops. lock buffer too small in lock_queuedir"); + + /* + * We do our own lockfile scanning to avoid unlink races. 60 + * seconds should be enough to ensure that we won't get more races + * happening between the stat and the open/flock. + */ + + while (stat(buffer, &sb) == 0) + sleep(60); + + if ((fp = open(buffer, O_WRONLY | O_CREAT | O_EXLOCK, 0600)) < 0) + { + err("can't open `%s' in lock_queuedir", buffer); + exit(1); + } + +#ifdef __OpenBSD__ + snprintf(buffer, len, "%8d", getpid()); +#else + snprintf(buffer, len, "%8ld", getpid()); +#endif + write(fp, buffer, 8); + + free(buffer); + + return(fp); +} + +/* + * Lock the queuedir so we're the only guy messing about in there. + */ +void +free_lock(int lockf, char *queue_dir) +{ + int len; + char *path; + + /* + * Most important: free the lock before we do anything else! + */ + + close(lockf); + + len = strlen(queue_dir) + 7; + + path = malloc(len); + if (path == NULL) + { + err("malloc failed in free_lock"); + exit(1); + } + + if (snprintf(path, len, "%s/.lock", queue_dir) >= len) + err("lock path buffer too small in free_lock"); + + if (unlink(path) != 0) + { + err("can't unlink lockfile `%s'", path); + exit(1); + } + + free(path); +} + +/* move everything into the queue directory. */ + +void +add_to_queue(char *queue_dir, char *mail_alias, char *delta, int npieces, char **tempnames) +{ + char *queuefile, *sn; + int pce, len, lockf; + + if ((sn = strrchr(delta, '/')) == NULL) + sn = delta; + else + sn++; + + /* try to malloc all we need BEFORE entering the lock loop */ + + len = strlen(queue_dir) + strlen(sn) + 7; + queuefile = malloc(len); + if (queuefile == NULL) + { + err("can't malloc for queuefile"); + exit(1); + } + + /* + * We should be the only process mucking around in the queue + * directory while we add the new queue files ... it could be + * awkward if the de-queue process starts it's job while we're + * adding files ... + */ + + lockf = lock_queuedir(queue_dir); + for (pce = 0; pce < npieces; pce++) + { + struct stat sb; + + if (snprintf(queuefile, len, "%s/%s+%03d", queue_dir, sn, pce + 1) >= len) + err("whoops, queuefile buffer is too small"); + + if (stat(queuefile, &sb) == 0) + { + err("WOAH! Queue file `%s' already exists! Bailing out.", queuefile); + free_lock(lockf, queue_dir); + exit(1); + } + + rename(tempnames[pce], queuefile); + err("Queue file %s now exists", queuefile); + } + + free_lock(lockf, queue_dir); + + free(queuefile); +} diff --git a/usr.sbin/ctm/mkCTM/Makefile b/usr.sbin/ctm/mkCTM/Makefile new file mode 100644 index 00000000000..9e052a94b7e --- /dev/null +++ b/usr.sbin/ctm/mkCTM/Makefile @@ -0,0 +1,24 @@ + +PROG= mkctm +SRCS= mkctm.c +LDADD= -lmd +CFLAGS= -g -Wall + +test: mkctm + rm -f tst.out* + time ./mkctm -v -v /3c/210src /a/r1/usr/src \ + 2>a | md5 -p > /a/tst.out + ls -l /a/tst.out + gzip -9 -v /a/tst.out + ls -l /a/tst.out.gz + # cd /usr/src/release && ctm -c -v -v ${.CURDIR}/tst.out + +test1: mkctm + rm -f tst.out* + time ./mkctm -v -v /3c/210src /home/ncvs/src \ + 2> b | md5 -p > /a/tst2.out + ls -l /a/tst2.out + gzip -9 -v /a/tst2.out + ls -l /a/tst2.out.gz + +.include <bsd.prog.mk> diff --git a/usr.sbin/ctm/mkCTM/mkCTM b/usr.sbin/ctm/mkCTM/mkCTM new file mode 100644 index 00000000000..3f884c13899 --- /dev/null +++ b/usr.sbin/ctm/mkCTM/mkCTM @@ -0,0 +1,350 @@ +#!/usr/local/bin/tclsh7.4 + +############################################################################# +### Add something +############################################################################# +# Type Name Mode User Group Barf Size Hash + +proc CTMadd {t n m u g b s h} { + global fo_files fo_mkdir changes CTMref + + puts stderr "A $b $t $n" + incr changes + + if {$t == "d"} { + puts $fo_mkdir "CTMDM $n $u $g $m" + } elseif {$t == "f"} { + puts $fo_files "CTMFM $n $u $g $m $h $s" + flush $fo_files + exec cat $CTMref/$n >@ $fo_files + puts $fo_files "" + } else { + puts "confused in CTMadd" + exit 0 + } +} + +############################################################################# +### Delete something +############################################################################# +# Type Name Mode User Group Barf Size Hash + +proc CTMdel {t n m u g b s h} { + global fo_del fo_rmdir changes damage max_damage CTMlock + + puts stderr "D $b $t $n" + incr damage + incr changes + + if {$damage > $max_damage} { + exec rm -f $CTMlock + return + } + if {$t == "d"} { + puts $fo_rmdir "CTMDR $n" + } elseif {$t == "f"} { + puts $fo_del "CTMFR $n $h" + } else { + puts "confused in CTMdel" + exit 0 + } +} + +############################################################################# +### Change something +############################################################################# +# Type Name Mode User Group Barf Size Hash + +proc CTMchg {t1 n1 m1 u1 g1 b1 s1 h1 t2 n2 m2 u2 g2 b2 s2 h2} { + global fo_files CTMref CTMcopy changes damage CTMscratch + + # Ignore attribute changes for directories + if {$t1 == "d" && $t2 == "d"} return + + # turn file into dir or vice versa... + if {$t1 != $t2} { + CTMdel $t1 $n1 $m1 $u1 $g1 $b1 $s1 $h1 + CTMadd $t2 $n2 $m2 $u2 $g2 $b2 $s2 $h2 + return + } + + # only files allowed past this poing... + if {$t1 != "f" && $t2 != "f"} { + puts "confused in CTMchg" + exit 0 + } + + # Ignore attribute changes for files + if {"x$h1" == "x$h2" && $s1 == $s2} return + + if {$s2 == 0} { incr damage } + incr changes + + # If diff will deal with it... + if {$b1 == "0" && $b2 == "0"} { + set i [catch "exec diff -n $CTMcopy/$n1 $CTMref/$n2 > $CTMscratch" j] + set s [file size $CTMscratch] + if {$s < $s2} { + puts stderr "E $b1$b2 $t1$t2 $n1" + puts $fo_files "CTMFN $n1 $u2 $g2 $m2 $h1 $h2 $s" + flush $fo_files + exec cat $CTMscratch >@ $fo_files + puts $fo_files "" + return + } + } + puts stderr "R $b1$b2 $t1$t2 $n1" + puts $fo_files "CTMFS $n2 $u2 $g2 $m2 $h1 $h2 $s2" + flush $fo_files + exec cat $CTMref/$n2 >@ $fo_files + puts $fo_files "" +} + +############################################################################# +### 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 CTMdont {^///} +set CTMmail {} +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 1200 + +set damage 0 +set changes 0 + +exec sh -c "date -u '+%y%m%d%H%M%S $argv'" >> /home/ctm/log +source $argv + +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 0 +} +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 0 + } + + incr CTMnbr + + if {[find_delta $CTMnbr]} { + puts "$CTMname delta $CTMnbr does already exist..." + exec rm -f $CTMlock + exit 0 + } + + 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] + } + + if {"$CTMcopy" == "" } { + set f1 [open /dev/null] + } else { + set f1 [open "| ./ctm_scan $CTMcopy $CTMscan"] + } + + puts "Doing CTMname $CTMname CTMnbr $CTMnbr$CTMsuff CTMdate $CTMdate" + flush stdout + exec sh -c "rm -f ${CTMtmp}.* ${CTMtmp}:*" >&@ stdout + + set f2 [open "| ./ctm_scan $CTMref $CTMscan"] + + set fo_del [open $CTMtmp.del w] + set fo_rmdir [open $CTMtmp.rmdir w] + set fo_mkdir [open $CTMtmp.mkdir w] + set fo_files [open $CTMtmp.files w] + + set l1 "" + set l2 "" + + while 1 { + + if {$l1 == ""} {gets $f1 l1} + + if {$l2 == ""} {gets $f2 l2} + + if {$l1 == "" && $l2 == ""} break + + set n1 [lindex $l1 1] + set n2 [lindex $l2 1] + + if {[regexp $CTMdont /$n1]} { set l1 "" ; continue } + if {[regexp $CTMdont /$n2]} { set l2 "" ; continue } + + # they're all the same... + if {$l1 == $l2} { set l1 "" ; set l2 "" ; continue } + + if {$l1 == "" } { eval CTMadd $l2 ; set l2 "" ; continue } + + if {$l2 == "" } { eval CTMdel $l1 ; set l1 "" ; continue } + + # if the name is the same we're safe... + if {$n1 == $n2} { + eval CTMchg $l1 $l2 + set l1 "" + set l2 "" + continue + } + + # To avoid this anomaly: + # A - d src/gnu/lib/libreadline/readline/Attic + # A 0 f src/gnu/lib/libreadline/readline/Attic/readline.h,v + # A 0 f src/gnu/lib/libreadline/readline.c,v + # D 0 f src/gnu/lib/libreadline/readline/readline.h,v + # D 0 f src/gnu/lib/libreadline/readline.c,v + # we have to make things somewhat complicated... + + # if they have the same number of components... + set ll1 [llength [split $n1 /]] + set ll2 [llength [split $n2 /]] + if {$ll1 == $ll2} { + if {$n1 < $n2 } { + eval CTMdel $l1 ; set l1 "" ; continue + } else { + eval CTMadd $l2 ; set l2 "" ; continue + } + } + if {$ll1 < $ll2} { + eval CTMadd $l2 ; set l2 "" ; continue + } else { + eval CTMdel $l1 ; set l1 "" ; continue + } + } + + close $fo_del + close $fo_rmdir + close $fo_mkdir + close $fo_files + + if {$damage > $max_damage} { + puts "Too much damage: $damage deletes" + exec sh -c "rm -f ${CTMtmp}.*" + exec rm -f $CTMlock + exit 0 + } + + if {!$changes} { + puts "no changes" + exec sh -c "rm -f ${CTMtmp}.*" + exec rm -f $CTMlock + exit 0 + } + + exec echo CTM_BEGIN 2.0 $CTMname $CTMnbr $CTMdate $CTMprefix > $CTMtmp.begin + + puts "Assembling delta" + flush stdout + set nm [format "%s.%04d%s" $CTMname $CTMnbr $CTMsuff] + + set fdout [open "| /sbin/md5 -p | gzip -9 > ${CTMtmp}:${nm}.gz" w] + + foreach i {begin del rmdir mkdir files} { + exec cat $CTMtmp.$i >@$fdout + } + puts $fdout "CTM_END " nonewline + close $fdout ; unset fdout + + exec sh -c "rm -f ${CTMtmp}.*" >&@ stdout + + 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" >&@ stdout + 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 + + if {$CTMmail != ""} { + puts "Mailing delta" + flush stdout + exec $CTMSW/ctm_smail -m 100000 -c 3000000 $CTMdest/${nm}.gz $CTMmail >&@ stdout + } + exec echo mailed > $CTMlock + + # If we did an absolute delta: stop. + if {$CTMsuff != ""} break + + # Make a 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 CTMapply 0 + set CTMspecial 1 + exec rm -f $CTMlock +} +puts "done." +exec rm -f $CTMlock diff --git a/usr.sbin/ctm/mkCTM/mkctm.c b/usr.sbin/ctm/mkCTM/mkctm.c new file mode 100644 index 00000000000..f9dfcb7e88f --- /dev/null +++ b/usr.sbin/ctm/mkCTM/mkctm.c @@ -0,0 +1,572 @@ +/* Still missing: + * + * Damage counter + * Change counter + * Time stamp + * prefix + * cmd-line args + * %100 deltas + * delta and Add are different. delta -> Equ. + * + * mkctm + * -B regex Bogus + * -I regex Ignore + * -D int Damage + * -q decrease verbosity + * -v increase verbosity + * (-l str control logging.) + * name cvs-cur + * prefix src/secure + * dir1 "Soll" + * dir2 "Ist" + * + */ + +#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 <signal.h> + +#define DEFAULT_IGNORE "/CVS$|/\\.#|00_TRANS\\.TBL$" +#define DEFAULT_BOGUS "\\.core$|\\.orig$|\\.rej$" +regex_t reg_ignore, reg_bogus; +int flag_ignore, flag_bogus; + +int verbose; +int damage, damage_limit; +int change; + +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:\n"); + fprintf(stderr, "\tmkctm [-options] name number timestamp prefix"); + fprintf(stderr, " dir1 dir2"); + 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,"%sAvoided:\n",pre); + fprintf(fd,"%s ignore: %5lu old %5lu new\n", + pre, s1_ignored, s2_ignored); + fprintf(fd,"%s bogus: %5lu old %5lu new\n", + pre, s1_bogus, s2_bogus); + fprintf(fd,"%s wrong: %5lu old %5lu new\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\n",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 %lu %lu %o", + pfx, name, de->d_name, + st->st_uid, st->st_gid, st->st_mode & ~S_IFMT); + 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) { perror(buf1); exit(3); } + 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) { perror(buf2); exit(3); } + 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 ((int)p1 == -1) { perror(buf1); exit(3); } + close(fd1); + + p2=mmap(0,s2.st_size,PROT_READ,MAP_PRIVATE,fd2,0); + if ((int)p2 == -1) { perror(buf2); exit(3); } + 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++; + damage++; + 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 = alloca(l), *p; + int j; + FILE *F; + + if (p1[s1.st_size-1] != '\n') { + if (verbose > 0) + fprintf(stderr, + "last char != \\n in %s\n", + buf1); + goto subst; + } + + if (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; + } + } + 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) {perror(buf2); exit (3); } + fstat(fd1,&st); + p1=mmap(0,st.st_size,PROT_READ,MAP_PRIVATE,fd1,0); + if ((int)p1 == -1) { perror(buf2); exit(3); } + 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); + 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); + 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]; + + for (;;) { + for (;;) { + (*i)++; + if (*i >= *n) + return; + *buf = 0; + if (*dir != '/') + strcat(buf,"/"); + strcat(buf,dir); + if (buf[strlen(buf)-1] != '/') + strcat(buf,"/"); + strcat(buf,name); + if (buf[strlen(buf)-1] != '/') + strcat(buf,"/"); + strcat(buf,nl[*i]->d_name); + if (flag_ignore && + !regexec(®_ignore,buf,0,0,0)) { + (*ignored)++; + if (verbose > 2) { + fprintf(stderr,"Ignore %s\n",buf); + } + } else if (flag_bogus && + !regexec(®_bogus,buf,0,0,0)) { + (*bogus)++; + if (verbose > 0) { + fprintf(stderr,"Bogus %s\n",buf); + } + } else { + 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; + extern char *optarg; + extern int optind; + + setbuf(stderr, NULL); + + if (regcomp(®_bogus,DEFAULT_BOGUS, REG_EXTENDED | REG_NEWLINE)) + /* XXX use regerror to explain it */ + err(1,"Default regular expression argument to -B is botched"); + flag_bogus = 1; + + if (regcomp(®_ignore,DEFAULT_IGNORE, REG_EXTENDED | REG_NEWLINE)) + /* XXX use regerror to explain it */ + err(1,"Default regular expression argument to -I is botched"); + flag_ignore = 1; + + while ((i = getopt(argc,argv,"D:I:B:qv")) != EOF) + switch (i) { + case 'D': + damage_limit = strtol(optarg,0,0); + if (damage_limit < 0) + err(1,"Damage limit must be positive"); + break; + case 'I': + if (flag_ignore) + regfree(®_ignore); + flag_ignore = 0; + if (!*optarg) + break; + if (regcomp(®_ignore,optarg, + REG_EXTENDED | REG_NEWLINE)) + /* XXX use regerror to explain it */ + err(1,"Regular expression argument to -I is botched"); + flag_ignore = 1; + break; + case 'B': + if (flag_bogus) + regfree(®_bogus); + flag_bogus = 0; + if (!*optarg) + break; + if (regcomp(®_bogus,optarg, + REG_EXTENDED | REG_NEWLINE)) + /* XXX use regerror to explain it */ + err(1,"Regular expression argument to -B is botched"); + flag_bogus = 1; + break; + case 'q': + verbose--; + break; + case 'v': + verbose++; + break; + case '?': + default: + Usage(); + return (1); + } + argc -= optind; + argv += optind; + + setbuf(stdout,0); + + if (argc != 6) { + Usage(); + return (1); + } + + signal(SIGINFO,stat_info); + + 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,""); + err(1,"Damage would exceede %d files", damage_limit); + } else if (!change) { + err(1,"No changes"); + } else { + printf("CTM_END "); + print_stat(stderr,""); + } + exit(0); +} |