mkctm.c revision 15702
1/* Still missing:
2 *
3 * Damage counter
4 * Change counter
5 * Time stamp
6 * prefix
7 * cmd-line args
8 * %100 deltas
9 * delta and Add are different. delta -> Equ.
10 *
11 * mkctm
12 *	-B regex	Bogus
13 *	-I regex	Ignore
14 *	-D int		Damage
15 *	-v		increase verbosity
16 *	-l str		control logging.
17 *	name		cvs-cur
18 *	prefix		src/secure
19 *	dir1		"Soll"
20 *	dir2		"Ist"
21 *
22 */
23
24#include <sys/types.h>
25#include <sys/stat.h>
26#include <sys/mman.h>
27#include <sys/wait.h>
28#include <dirent.h>
29#include <regex.h>
30#include <stdio.h>
31#include <fcntl.h>
32#include <string.h>
33#include <stdlib.h>
34#include <unistd.h>
35#include <md5.h>
36#include <err.h>
37#include <signal.h>
38
39#define DEFAULT_IGNORE	"/CVS$|/\\.#"
40#define DEFAULT_BOGUS	"\\.core$"
41regex_t reg_ignore,  reg_bogus;
42int	flag_ignore, flag_bogus;
43
44u_long s1_ignored,	s2_ignored;
45u_long s1_bogus,	s2_bogus;
46u_long s1_wrong,	s2_wrong;
47u_long s_same_dirs,	s_same_files,	s_same_bytes;
48u_long 			s_files_chg,	s_bytes_add,	s_bytes_del;
49u_long s_new_dirs,	s_new_files,	s_new_bytes;
50u_long s_del_dirs,	s_del_files,	s_del_bytes;
51u_long            	s_chg_files,	s_chg_bytes;
52u_long 			s_edit_files,	s_edit_bytes,	s_edit_saves;
53u_long 			s_sub_files,	s_sub_bytes;
54
55void
56print_stat(FILE *fd, char *pre)
57{
58    fprintf(fd,"%sAvoided:\n",pre);
59    fprintf(fd,"%s  ignore:  %5lu old   %5lu new\n",
60	    pre, s1_ignored, s2_ignored);
61    fprintf(fd,"%s  bogus:   %5lu old   %5lu new\n",
62	    pre, s1_bogus, s2_bogus);
63    fprintf(fd,"%s  wrong:   %5lu old   %5lu new\n",
64	    pre, s1_wrong, s2_wrong);
65    fprintf(fd,"%sDelta:\n",pre);
66    fprintf(fd,"%s  new:     %5lu dirs  %5lu files  %9lu plus\n",
67	    pre, s_new_dirs, s_new_files, s_new_bytes);
68    fprintf(fd,"%s  del:     %5lu dirs  %5lu files                   %9lu minus\n",
69	    pre, s_del_dirs, s_del_files, s_del_bytes);
70    fprintf(fd,"%s  chg:                 %5lu files  %9lu plus   %9lu minus\n",
71	    pre, s_files_chg, s_bytes_add, s_bytes_del);
72    fprintf(fd,"%s  same:    %5lu dirs  %5lu files  %9lu bytes\n",
73	    pre, s_same_dirs, s_same_files, s_same_bytes);
74    fprintf(fd,"%sMethod:\n",pre);
75    fprintf(fd,"%s  edit:                %5lu files  %9lu bytes  %9lu saved\n",
76	    pre, s_edit_files, s_edit_bytes, s_edit_saves);
77    fprintf(fd,"%s  sub:                 %5lu files  %9lu bytes\n",
78	    pre, s_sub_files, s_sub_bytes);
79}
80
81void
82stat_info(int foo)
83{
84	signal(SIGINFO,stat_info);
85	print_stat(stderr,"INFO: ");
86}
87
88void DoDir(const char *dir1, const char *dir2, const char *name);
89
90static struct stat st;
91static __inline struct stat *
92StatFile(char *name)
93{
94	if (lstat(name,&st) < 0)
95		err(1,"Couldn't stat %s\n",name);
96	return &st;
97}
98
99int
100dirselect(struct dirent *de)
101{
102	if (!strcmp(de->d_name,"."))	return 0;
103	if (!strcmp(de->d_name,".."))	return 0;
104	return 1;
105}
106
107void
108name_stat(const char *pfx, const char *dir, const char *name, struct dirent *de)
109{
110	char *buf = alloca(strlen(dir) + strlen(name) +
111		strlen(de->d_name) + 3);
112	struct stat *st;
113
114	strcpy(buf,dir);
115		strcat(buf,"/"); strcat(buf,name);
116		strcat(buf,"/"); strcat(buf,de->d_name);
117	st = StatFile(buf);
118	printf("%s %s%s %lu %lu %o",
119	    pfx, name, de->d_name,
120	    st->st_uid, st->st_gid, st->st_mode & ~S_IFMT);
121}
122
123void
124Equ(const char *dir1, const char *dir2, const char *name, struct dirent *de)
125{
126	if (de->d_type == DT_DIR) {
127		char *p = alloca(strlen(name)+strlen(de->d_name)+2);
128
129		strcpy(p,name);  strcat(p,de->d_name); strcat(p, "/");
130		DoDir(dir1,dir2,p);
131		s_same_dirs++;
132	} else {
133		char *buf1 = alloca(strlen(dir1) + strlen(name) +
134			strlen(de->d_name) + 3);
135		char *buf2 = alloca(strlen(dir2) + strlen(name) +
136			strlen(de->d_name) + 3);
137		char *m1,md5_1[33],*m2, md5_2[33];
138		void *p1,*p2;
139		int fd1,fd2;
140		struct stat s1,s2;
141
142		strcpy(buf1,dir1);
143			strcat(buf1,"/"); strcat(buf1,name);
144			strcat(buf1,"/"); strcat(buf1,de->d_name);
145		fd1 = open(buf1,O_RDONLY);
146		if(fd1 < 0) { perror(buf1); exit(3); }
147		fstat(fd1,&s1);
148		strcpy(buf2,dir2);
149			strcat(buf2,"/"); strcat(buf2,name);
150			strcat(buf2,"/"); strcat(buf2,de->d_name);
151		fd2 = open(buf2,O_RDONLY);
152		if(fd2 < 0) { perror(buf2); exit(3); }
153		fstat(fd2,&s2);
154#if 1
155		if (s1.st_size == s2.st_size) {
156			s_same_files++;
157			s_same_bytes += s1.st_size;
158			close(fd1);
159			close(fd2);
160			goto finish;
161		}
162#endif
163		p1=mmap(0,s1.st_size,PROT_READ,MAP_PRIVATE,fd1,0);
164		if ((int)p1 == -1) { perror(buf1); exit(3); }
165		close(fd1);
166
167		p2=mmap(0,s2.st_size,PROT_READ,MAP_PRIVATE,fd2,0);
168		if ((int)p2 == -1) { perror(buf2); exit(3); }
169		close(fd2);
170
171		/* If identical, we're done. */
172		if((s1.st_size == s2.st_size) && !memcmp(p1,p2,s1.st_size)) {
173			s_same_files++;
174			s_same_bytes += s1.st_size;
175			goto finish;
176		}
177
178		s_files_chg++;
179		if (s1.st_size > s2.st_size)
180			s_bytes_del += (s1.st_size - s2.st_size);
181		else
182			s_bytes_add += (s2.st_size - s1.st_size);
183
184		m1 = MD5Data(p1, s1.st_size, md5_1);
185		m2 = MD5Data(p2, s2.st_size, md5_2);
186
187		/* Just a curiosity... */
188		if(!strcmp(m1,m2)) {
189			if (s1.st_size != s2.st_size)
190				fprintf(stderr,
191		"Notice: MD5 same for files of diffent size:\n\t%s\n\t%s\n",
192					buf1,buf2);
193			goto finish;
194		}
195
196		{
197			u_long l = s1.st_size + s2.st_size;
198			u_char *ob = alloca(l);
199			Dissect(p1,p1+s1.st_size,p2,p2+s2.st_size,ob,&l);
200			if (l && l < s2.st_size) {
201				name_stat("CTMFB",dir2,name,de);
202				printf(" %s %s %d\n",m1,m2,(unsigned)s1.st_size);
203				s_edit_files++;
204				s_edit_bytes += l;
205				s_edit_saves += (s2.st_size - l);
206				fwrite(ob,1,l,stdout);
207				putchar('\n');
208			} else {
209				name_stat("CTMFS",dir2,name,de);
210				printf(" %s %s %u\n",m1,m2,(unsigned)s2.st_size);
211				fwrite(p2,1,s2.st_size,stdout);
212				s_sub_files++;
213				s_sub_bytes += s2.st_size;
214			}
215		}
216	    finish:
217		munmap(p1,s1.st_size);
218		munmap(p2,s2.st_size);
219	}
220}
221
222void
223Add(const char *dir1, const char *dir2, const char *name, struct dirent *de)
224{
225	if (de->d_type == DT_DIR) {
226		char *p = alloca(strlen(name)+strlen(de->d_name)+2);
227		strcpy(p,name);  strcat(p,de->d_name); strcat(p, "/");
228		name_stat("CTMDM",dir2,name,de);
229		putchar('\n');
230		s_new_dirs++;
231		DoDir(dir1,dir2,p);
232	} else if (de->d_type == DT_REG) {
233		char *buf2 = alloca(strlen(dir2) + strlen(name) +
234			strlen(de->d_name) + 3);
235		char *m2, md5_2[33];
236		u_char *p1;
237		struct stat st;
238		int fd1;
239
240		strcpy(buf2,dir2);
241			strcat(buf2,"/"); strcat(buf2,name);
242			strcat(buf2,"/"); strcat(buf2,de->d_name);
243		fd1 = open(buf2,O_RDONLY);
244		if (fd1 < 0) {perror(buf2); exit (3); }
245		fstat(fd1,&st);
246		p1=mmap(0,st.st_size,PROT_READ,MAP_PRIVATE,fd1,0);
247		if ((int)p1 == -1) { perror(buf2); exit(3); }
248		close(fd1);
249		m2 = MD5Data(p1, st.st_size, md5_2);
250		name_stat("CTMFM",dir2,name,de);
251		printf(" %s %u\n",m2,(unsigned)st.st_size);
252		fwrite(p1,1,st.st_size,stdout);
253		putchar('\n');
254		munmap(p1,st.st_size);
255		s_new_files++;
256		s_new_bytes += st.st_size;
257	}
258}
259
260void
261Del (const char *dir1, const char *dir2, const char *name, struct dirent *de)
262{
263	if (de->d_type == DT_DIR) {
264		char *p = alloca(strlen(name)+strlen(de->d_name)+2);
265		strcpy(p,name);  strcat(p,de->d_name); strcat(p, "/");
266		DoDir(dir1,dir2,p);
267		printf("CTMDR %s%s\n",name,de->d_name);
268		s_del_dirs++;
269	} else if (de->d_type == DT_REG) {
270		char *buf1 = alloca(strlen(dir1) + strlen(name) +
271			strlen(de->d_name) + 3);
272		char *m1, md5_1[33];
273		strcpy(buf1,dir1);
274			strcat(buf1,"/"); strcat(buf1,name);
275			strcat(buf1,"/"); strcat(buf1,de->d_name);
276		m1 = MD5File(buf1, md5_1);
277		printf("CTMFR %s%s %s\n",name,de->d_name,m1);
278		s_del_files++;
279		s_del_bytes += StatFile(buf1)->st_size;
280	}
281}
282
283void
284GetNext(int *i, int *n, struct dirent **nl, const char *dir, const char *name, u_long *ignored, u_long *bogus, u_long *wrong)
285{
286	char buf[BUFSIZ];
287
288	for (;;) {
289		for (;;) {
290			(*i)++;
291			if (*i >= *n)
292				return;
293			*buf = 0;
294			if (*dir != '/')
295				strcat(buf,"/");
296			strcat(buf,dir);
297			if (buf[strlen(buf)-1] != '/')
298				strcat(buf,"/");
299			strcat(buf,name);
300			if (buf[strlen(buf)-1] != '/')
301				strcat(buf,"/");
302			fprintf(stderr,">%d<%s>\n",strlen(nl[*i]->d_name),nl[*i]->d_name);
303			fflush(stderr);
304			strcat(buf,nl[*i]->d_name);
305			if (flag_ignore && !regexec(&reg_ignore,buf,0,0,0))
306				(*ignored)++;
307			else if (flag_bogus && !regexec(&reg_bogus,buf,0,0,0))
308				(*bogus)++;
309			else
310				break;
311			free(nl[*i]); nl[*i] = 0;
312		}
313		/* If the filesystem didn't tell us, find type */
314		if (nl[*i]->d_type == DT_UNKNOWN)
315			nl[*i]->d_type = IFTODT(StatFile(buf)->st_mode);
316		if (nl[*i]->d_type == DT_REG || nl[*i]->d_type == DT_DIR)
317			break;
318		(*wrong)++;
319		free(nl[*i]); nl[*i] = 0;
320	}
321}
322
323void
324DoDir(const char *dir1, const char *dir2, const char *name)
325{
326	int i1,i2,n1,n2,i;
327	struct dirent **nl1,**nl2;
328	char *buf1 = alloca(strlen(dir1) + strlen(name) + 4);
329	char *buf2 = alloca(strlen(dir2) + strlen(name) + 4);
330
331	strcpy(buf1,dir1); strcat(buf1,"/"); strcat(buf1,name);
332	strcpy(buf2,dir2); strcat(buf2,"/"); strcat(buf2,name);
333	n1 = scandir(buf1, &nl1, dirselect, alphasort);
334	n2 = scandir(buf2, &nl2, dirselect, alphasort);
335	i1 = i2 = -1;
336	GetNext(&i1, &n1, nl1, dir1, name, &s1_ignored, &s1_bogus, &s1_wrong);
337	GetNext(&i2, &n2, nl2, dir2, name, &s2_ignored, &s2_bogus, &s2_wrong);
338	for (;i1 < n1 || i2 < n2;) {
339
340		/* Get next item from list 1 */
341		if (i1 < n1 && !nl1[i1])
342			GetNext(&i1, &n1, nl1, dir1, name,
343				&s1_ignored, &s1_bogus, &s1_wrong);
344
345		/* Get next item from list 2 */
346		if (i2 < n2 && !nl2[i2])
347			GetNext(&i2, &n2, nl2, dir2, name,
348				&s2_ignored, &s2_bogus, &s2_wrong);
349
350		if (i1 >= n1 && i2 >= n2) {
351			/* Done */
352			break;
353		} else if (i1 >= n1 && i2 < n2) {
354			/* end of list 1, add anything left on list 2 */
355			Add(dir1,dir2,name,nl2[i2]);
356			free(nl2[i2]); nl2[i2] = 0;
357		} else if (i1 < n1 && i2 >= n2) {
358			/* end of list 2, delete anything left on list 1 */
359			Del(dir1,dir2,name,nl1[i1]);
360			free(nl1[i1]); nl1[i1] = 0;
361		} else if (!(i = strcmp(nl1[i1]->d_name, nl2[i2]->d_name))) {
362			/* Identical names */
363			if (nl1[i1]->d_type == nl2[i2]->d_type) {
364				/* same type */
365				Equ(dir1,dir2,name,nl1[i1]);
366			} else {
367				/* different types */
368				Del(dir1,dir2,name,nl1[i1]);
369				Add(dir1,dir2,name,nl2[i2]);
370			}
371			free(nl1[i1]); nl1[i1] = 0;
372			free(nl2[i2]); nl2[i2] = 0;
373		} else if (i < 0) {
374			/* Something extra in list 1, delete it */
375			Del(dir1,dir2,name,nl1[i1]);
376			free(nl1[i1]); nl1[i1] = 0;
377		} else {
378			/* Something extra in list 2, add it */
379			Add(dir1,dir2,name,nl2[i2]);
380			free(nl2[i2]); nl2[i2] = 0;
381		}
382	}
383	if (n1 >= 0)
384		free(nl1);
385	if (n2 >= 0)
386		free(nl2);
387}
388
389int
390main(int argc, char **argv)
391{
392	int i;
393	extern char *optarg;
394	extern int optind;
395
396	if (regcomp(&reg_bogus,DEFAULT_BOGUS, REG_EXTENDED | REG_NEWLINE))
397		/* XXX use regerror to explain it */
398		err(1,"Default regular expression argument to -B is botched");
399	flag_bogus = 1;
400
401	if (regcomp(&reg_ignore,DEFAULT_IGNORE, REG_EXTENDED | REG_NEWLINE))
402		/* XXX use regerror to explain it */
403		err(1,"Default regular expression argument to -I is botched");
404	flag_ignore = 1;
405
406	while ((i = getopt(argc,argv,"I:B:")) != EOF)
407		switch (i) {
408		case 'I':
409			if (flag_ignore)
410				regfree(&reg_ignore);
411			flag_ignore = 0;
412			if (!*optarg)
413				break;
414			if (regcomp(&reg_ignore,optarg,
415			    REG_EXTENDED | REG_NEWLINE))
416				/* XXX use regerror to explain it */
417				err(1,"Regular expression argument to -I is botched");
418			flag_ignore = 1;
419			break;
420		case 'B':
421			if (flag_bogus)
422				regfree(&reg_bogus);
423			flag_bogus = 0;
424			if (!*optarg)
425				break;
426			if (regcomp(&reg_bogus,optarg,
427			    REG_EXTENDED | REG_NEWLINE))
428				/* XXX use regerror to explain it */
429				err(1,"Regular expression argument to -B is botched");
430			flag_bogus = 1;
431			break;
432		case '?':
433		default:
434			fprintf(stderr,"Usage:\n\t%s: %s\n", argv[0],
435"[-I ignore_re] [-B bogus_re]");
436			return (1);
437		}
438	argc -= optind;
439	argv += optind;
440
441	setbuf(stdout,0);
442
443	signal(SIGINFO,stat_info);
444	printf("CTM_BEGIN 2.0 tst 0 950326022230Z .\n");
445	DoDir(argv[0],argv[1],"");
446	printf("CTM_END ");
447	print_stat(stderr,"");
448	exit(0);
449}
450