1 /* @(#)sccslog.c	1.69 20/08/23 Copyright 1997-2020 J. Schilling */
2 #include <schily/mconfig.h>
3 #ifndef lint
4 static	UConst char sccsid[] =
5 	"@(#)sccslog.c	1.69 20/08/23 Copyright 1997-2020 J. Schilling";
6 #endif
7 /*
8  *	Copyright (c) 1997-2020 J. Schilling
9  */
10 /*
11  * The contents of this file are subject to the terms of the
12  * Common Development and Distribution License, Version 1.0 only
13  * (the "License").  You may not use this file except in compliance
14  * with the License.
15  *
16  * See the file CDDL.Schily.txt in this distribution for details.
17  * A copy of the CDDL is also available via the Internet at
18  * http://www.opensource.org/licenses/cddl1.txt
19  *
20  * When distributing Covered Code, include this CDDL HEADER in each
21  * file and include the License file CDDL.Schily.txt from this distribution.
22  */
23 
24 #define	SCCS_MAIN			/* define global vars */
25 #include <defines.h>
26 #include <schily/stdio.h>
27 #include <schily/stdlib.h>
28 #include <schily/unistd.h>
29 #include <schily/standard.h>
30 #include <schily/string.h>
31 #include <schily/time.h>
32 #include <schily/utypes.h>
33 #include <schily/stat.h>
34 #include <schily/dirent.h>
35 #include <schily/maxpath.h>
36 #include <schily/getargs.h>
37 #include <schily/schily.h>
38 #include <schily/wait.h>
39 #include <version.h>
40 #include <i18n.h>
41 
42 #ifdef	INS_BASE
43 #if defined(__STDC__) || defined(PROTOTYPES)
44 #define	PROGPATH(name)	INS_BASE "/" SCCS_BIN_PRE "bin/" #name
45 #else
46 /*
47  * XXX With a K&R compiler, you need to edit the following string in case
48  * XXX you like to change the install path.
49  */
50 #define	PROGPATH(name) "/usr/ccs/bin/name"	/* place to find binaries */
51 #endif
52 #endif
53 
54 #define	streql(s1, s2)	(strcmp((s1), (s2)) == 0)
55 #undef	fgetline			/* May be #defined by schily.h */
56 #define	fgetline	log_fgetline
57 
58 #define	DAY	(24*60*60)
59 
60 struct filedata {
61 	urand_t	urand;			/* Unified random number for file */
62 	char	*init_path;		/* Initial path name from SCCSv6 */
63 };
64 
65 struct xtime {
66 	time_t	xt;			/* time_t value from mk{gm}time() */
67 	Llong	xlt;			/* Time with larger time range	  */
68 	long	xns;			/* Nanoseconds for xt / struct tm */
69 	struct tm xtm;			/* Struct tm from textual parsing */
70 	int	xgmtoff;		/* GMT offset for struct tm	  */
71 };
72 
73 struct delt {
74 	time_t	time;			/* The time the way UNIX counts */
75 	long	nsec;			/* Nanoseconds if available	*/
76 	Llong	ltime;			/* Time with larger time range	*/
77 	int	gmtoff;			/* GMT offset when in SCCSv6 mode */
78 	struct tm tm;			/* Struct tm as read from delta	*/
79 	char	*user;			/* User name from delta		*/
80 	size_t	userlen;		/* strlen(user)			*/
81 	char	*file;			/* Filename for this delta	*/
82 	char	*vers;			/* Version string for this delta */
83 	char	*comment;		/* Comment for this delta	*/
84 	size_t	commentlen;		/* strlen(comment)		*/
85 	int	flags;			/* Flags like PRINTED		*/
86 	int	ghash;			/* Delta specific checksum	*/
87 	char	type;
88 	struct filedata *fdata;
89 };
90 
91 #define	PRINTED	0x01
92 
93 struct author {
94 	char	*user;			/* The user's login name	*/
95 	char	*mail;			/* The user's realname and mail */
96 };
97 
98 LOCAL	struct	delt	*list;
99 LOCAL	int		listmax;
100 LOCAL	int		listsize;
101 
102 LOCAL	char		*Cwd;
103 LOCAL	char		*SccsPath = "";
104 LOCAL	char		*usermapfile;
105 LOCAL	BOOL		reverse = FALSE;
106 LOCAL	BOOL		multfile = FALSE;
107 LOCAL	BOOL		extended = FALSE;
108 LOCAL	BOOL		changeset = FALSE;
109 LOCAL	int		nopooling = 0;
110 LOCAL	time_t		maxdelta = DAY;
111 LOCAL	Nparms		N;			/* Keep -N parameters	*/
112 LOCAL	Xparms		X;			/* Keep -X parameters	*/
113 LOCAL	FILE		*Cs;
114 LOCAL	char		csname[30];
115 
116 LOCAL	int	deltcmp		__PR((const void *vp1, const void *vp2));
117 LOCAL	int	rrcmp		__PR((const void *vp1, const void *vp2));
118 LOCAL	char *	mapuser		__PR((char *name));
119 LOCAL	void	usage		__PR((int exitcode));
120 EXPORT	int	main		__PR((int ac, char *av[]));
121 LOCAL	void	dodir		__PR((char *name));
122 LOCAL	void	dofile		__PR((char *name));
123 LOCAL	int	fgetline	__PR((FILE *, char *, int));
124 LOCAL	void	handle_created_msg __PR((char *));
125 LOCAL	int	getN		__PR((const char *, void *));
126 LOCAL	int	getX		__PR((const char *, void *));
127 LOCAL	void	print_changeset	__PR((FILE *, struct delt *));
128 LOCAL	Llong	find_changeset	__PR((int i, struct xtime *xtp, BOOL printit));
129 LOCAL	void	commit_changeset __PR((int i, struct xtime *xtp));
130 
131 /*
132  * XXX With SCCS v6 local time + GMT off, we should not compare struct tm
133  * XXX but time_t or better Llong ltime.
134  */
135 LOCAL int
deltcmp(vp1,vp2)136 deltcmp(vp1, vp2)
137 	const void	*vp1;
138 	const void	*vp2;
139 {
140 	const struct delt *p1 = vp1;
141 	const struct delt *p2 = vp2;
142 	const struct tm	*tm1;
143 	const struct tm	*tm2;
144 
145 	tm1 = &(p1)->tm;
146 	tm2 = &(p2)->tm;
147 
148 	if (tm1->tm_year < tm2->tm_year)
149 		return (1);
150 	else if (tm1->tm_year > tm2->tm_year)
151 		return (-1);
152 	else if (tm1->tm_mon < tm2->tm_mon)
153 		return (1);
154 	else if (tm1->tm_mon > tm2->tm_mon)
155 		return (-1);
156 	else if (tm1->tm_mday < tm2->tm_mday)
157 		return (1);
158 	else if (tm1->tm_mday > tm2->tm_mday)
159 		return (-1);
160 	else if (tm1->tm_hour < tm2->tm_hour)
161 		return (1);
162 	else if (tm1->tm_hour > tm2->tm_hour)
163 		return (-1);
164 	else if (tm1->tm_min < tm2->tm_min)
165 		return (1);
166 	else if (tm1->tm_min > tm2->tm_min)
167 		return (-1);
168 	else if (tm1->tm_sec < tm2->tm_sec)
169 		return (1);
170 	else if (tm1->tm_sec > tm2->tm_sec)
171 		return (-1);
172 	return (0);
173 
174 #ifdef	OLD
175 	if ((p1)->time < (p2)->time)
176 		return (1);
177 	if ((p1)->time > (p2)->time)
178 		return (-1);
179 	return (0);
180 #endif
181 }
182 
183 LOCAL int
rrcmp(vp1,vp2)184 rrcmp(vp1, vp2)
185 	const void	*vp1;
186 	const void	*vp2;
187 {
188 	return (deltcmp(vp1, vp2) * -1);
189 }
190 
191 LOCAL char *
mapuser(name)192 mapuser(name)
193 	char	*name;
194 {
195 static	char	nbuf[1024];
196 static	FILE	*f = NULL;
197 static	int	cannot = 0;
198 static	char	*lastname = NULL;
199 static	char	*lastuser = NULL;
200 static struct author	*auth = NULL;
201 static size_t		authsize = 0;
202 static size_t		authused = 0;
203 	int	len;
204 
205 	if (cannot)
206 		return (name);
207 
208 	if (lastname && streql(lastname, name))
209 		return (lastuser);
210 
211 	if (auth) {
212 		int	i;
213 
214 		for (i = 0; i < authused; i++) {
215 			if (streql(auth[i].user, name)) {
216 				lastname = name;
217 				lastuser = auth[i].mail;
218 				return (lastuser);
219 			}
220 		}
221 	}
222 
223 	if (f == NULL) {
224 		char	*home = getenv("HOME");
225 
226 		if (home == NULL)
227 			home = ".";
228 		js_snprintf(nbuf, sizeof (nbuf), "%s/.sccs/usermap", home);
229 		if (usermapfile)
230 			f = fopen(usermapfile, "r");
231 		else
232 			f = fopen(nbuf, "r");
233 		if (f == NULL) {
234 			cannot = 1;
235 			return (name);
236 		}
237 		lastname = lastuser = NULL;
238 	}
239 	rewind(f);
240 	while ((len = fgetline(f, nbuf, sizeof (nbuf))) >= 0) {
241 		char	*p;
242 
243 		if (len == 0)
244 			continue;
245 		p = strchr(nbuf, '\t');
246 		if (p == NULL)
247 			p = strchr(nbuf, ' ');
248 		if (p == NULL || p == nbuf)
249 			continue;
250 		*p++ = '\0';
251 		if (!streql(nbuf, name))
252 			continue;
253 		while (*p == ' ' || *p == '\t')
254 			p++;
255 		lastname = name;
256 		lastuser = p;
257 		if (authsize <= authused) {
258 			authsize += 128;
259 			if (auth == NULL) {
260 				auth = malloc(authsize * sizeof (*auth));
261 			} else {
262 				auth = realloc(auth, authsize * sizeof (*auth));
263 			}
264 			if (auth == NULL)
265 				comerr("No memory.\n");
266 		}
267 		auth[authused].user = strdup(name);
268 		if (auth[authused].user == NULL)
269 			comerr("No memory.\n");
270 		auth[authused].mail = strdup(p);
271 		if (auth[authused].mail == NULL)
272 			comerr("No memory.\n");
273 		authused++;
274 		return (p);
275 	}
276 	lastname = lastuser = NULL;
277 	return (name);
278 }
279 
280 LOCAL void
usage(exitcode)281 usage(exitcode)
282 	int	exitcode;
283 {
284 	fprintf(stderr, _("Usage: sccslog [options] s.file1 .. s.filen\n"));
285 	fprintf(stderr, _("	-help		Print this help.\n"));
286 	fprintf(stderr, _("	-version	Print version number.\n"));
287 	fprintf(stderr, _("	-a		Print all deltas with times differing > 60s separately.\n"));
288 	fprintf(stderr, _("	-aa		Print all deltas with different times separately.\n"));
289 	fprintf(stderr, _("	-Cdir		Base dir for printed filenames.\n"));
290 	fprintf(stderr, _("	maxdelta=#	Set maximum time delta for a commit (default one day).\n"));
291 	fprintf(stderr, _("	-multfile	Allow multiple versions of the same file in a commit.\n"));
292 	fprintf(stderr, _("	-p subdir	Define SCCS subdir.\n"));
293 	fprintf(stderr, _("	-R		Reverse sorting: oldest entries first.\n"));
294 	fprintf(stderr, _("	usermap=file	Specify user map file.\n"));
295 	fprintf(stderr, _("	-x		Include all comment, even SCCSv6 metadata.\n"));
296 	fprintf(stderr, _("	-Nbulk-spec	Processes a bulk of SCCS history files.\n"));
297 	fprintf(stderr, _("	-Xxopts		Processes SCCS extended files.\n"));
298 	exit(exitcode);
299 }
300 
301 EXPORT int
main(ac,av)302 main(ac, av)
303 	int	ac;
304 	char	*av[];
305 {
306 	int	cac;
307 	char	* const *cav;
308 	char	*opts = "help,V,version,a+,R,reverse,changeset,multfile,x,C*,p*,maxdelta*,usermap*,N&_,X&_";
309 	BOOL	help = FALSE;
310 	BOOL	pversion = FALSE;
311 	char	*maxdelt = NULL;
312 	int	i;
313 	int	j;
314 
315 	save_args(ac, av);
316 
317 	/*
318 	 * Set locale for all categories.
319 	 */
320 	setlocale(LC_ALL, "");
321 
322 	sccs_setinsbase(INS_BASE);
323 
324 	/*
325 	 * Set directory to search for general l10n SCCS messages.
326 	 */
327 #ifdef	PROTOTYPES
328 	(void) bindtextdomain(NOGETTEXT("SUNW_SPRO_SCCS"),
329 	    NOGETTEXT(INS_BASE "/" SCCS_BIN_PRE "lib/locale/"));
330 #else
331 	(void) bindtextdomain(NOGETTEXT("SUNW_SPRO_SCCS"),
332 	    NOGETTEXT("/usr/ccs/lib/locale/"));
333 #endif
334 
335 	(void) textdomain(NOGETTEXT("SUNW_SPRO_SCCS"));
336 
337 	Fflags = FTLEXIT | FTLMSG | FTLCLN;
338 #ifdef	SCCS_FATALHELP
339 	Fflags |= FTLFUNC;
340 	Ffunc = sccsfatalhelp;
341 #endif
342 	cac = --ac;
343 	cav = ++av;
344 
345 	if (getallargs(&cac, &cav, opts,
346 			&help, &pversion, &pversion,
347 			&nopooling,
348 			&reverse, &reverse,
349 			&changeset,
350 			&multfile,
351 			&extended,
352 			&Cwd, &SccsPath,
353 			&maxdelt,
354 			&usermapfile,
355 			getN, &N,
356 			getX, &X) < 0) {
357 		errmsgno(EX_BAD, "Bad flag: %s.\n", cav[0]);
358 		usage(EX_BAD);
359 	}
360 	if (help)
361 		usage(0);
362 	if (pversion) {
363 		printf(_(
364 		"sccslog %s-SCCS version %s %s (%s-%s-%s) Copyright (C) 1997-2020 J�rg Schilling\n"),
365 			PROVIDER,
366 			VERSION,
367 			VDATE,
368 			HOST_CPU, HOST_VENDOR, HOST_OS);
369 		exit(0);
370 	}
371 	if (maxdelt) {
372 		i = gettnum(maxdelt, &maxdelta);
373 		if (i < 0 || maxdelta == 0)
374 			comerrno(EX_BAD,
375 				_("Bad time delta specification '%s'.\n"),
376 				maxdelt);
377 		nopooling = 0;
378 	}
379 	if (changeset) {
380 		reverse = TRUE;
381 		snprintf(csname, sizeof (csname), "/tmp/cs.%d", (int)getpid());
382 	}
383 
384 	if (N.n_parm) {					/* Parse -N args  */
385 		parseN(&N);
386 	}
387 
388 	xsethome(NULL);
389 	if (N.n_parm && N.n_sdot && (sethomestat & SETHOME_OFFTREE))
390 		fatal(gettext("-Ns. not supported in off-tree project mode"));
391 	Fflags &= ~FTLEXIT;
392 	Fflags |= FTLJMP;
393 
394 	cac = ac;
395 	cav = av;
396 
397 	i = 0;
398 	while (getfiles(&cac, &cav, opts) > 0) {
399 		struct stat	sb;
400 
401 		if (cav[0][0] == '-' && cav[0][1] == '\0')
402 			do_file("-", dofile, 0, N.n_sdot, &X);
403 		else if (stat(cav[0], &sb) >= 0 && S_ISDIR(sb.st_mode))
404 			dodir(cav[0]);
405 		else
406 			dofile(cav[0]);
407 		i++;
408 		cac--;
409 		cav++;
410 	}
411 	/*
412 	 * Make sure that "sccs -R log" results in useful output.
413 	 */
414 	if (i == 0 && *SccsPath)
415 		dodir(SccsPath);
416 
417 	qsort(list, listsize, sizeof (struct delt), reverse?rrcmp:deltcmp);
418 
419 #ifdef	SCCSLOG_DEBUG
420 	printf("%d Eintr�ge\n", listsize);
421 #endif
422 	for (i = 0; i < listsize; i++) {
423 		int	k;
424 		int	l;
425 		Llong	xlt;
426 		Llong	nlt;
427 		struct xtime xtime;
428 		struct xtime xntime;
429 
430 		if (list[i].flags & PRINTED)
431 			continue;
432 
433 		/*
434 		 * First, retrieve latest time stamp for this changeset.
435 		 */
436 		nlt = find_changeset(i, &xtime, FALSE);
437 
438 		/*
439 		 * Now look for overlapping changesets that need to be first.
440 		 */
441 		do {
442 			xlt = nlt;
443 			for (j = k = i+1; j < listsize; j++) {
444 				Llong	lt;
445 
446 				if (list[j].flags & PRINTED)
447 					continue;
448 
449 				if (list[j].comment[0]) {
450 					/*
451 					 * First skip all entries with the same
452 					 * commit message as previous ones.
453 					 * They are not new ones.
454 					 */
455 					for (l = i; l < j; l++) {
456 						if (list[l].flags & PRINTED)
457 							continue;
458 						if (list[l].commentlen ==
459 						    list[j].commentlen &&
460 						    streql(list[l].comment,
461 							    list[j].comment) &&
462 						    list[l].userlen ==
463 						    list[j].userlen &&
464 						    streql(list[l].user,
465 							    list[j].user)) {
466 							goto next;
467 						}
468 					}
469 				}
470 
471 				if (reverse && (list[j].ltime > nlt))
472 					break;
473 				if (!reverse && (nlt > list[j].ltime))
474 					break;
475 
476 				lt = find_changeset(j, &xntime, FALSE);
477 
478 				if (lt == LLONG_MAX)
479 					goto next;
480 
481 				if (reverse && lt < xlt) {
482 					xlt = lt;
483 					k = j;
484 				}
485 				if (!reverse && lt > xlt) {
486 					xlt = lt;
487 					k = j;
488 				}
489 			next:
490 				;
491 			}
492 			if (nlt != xlt) {
493 				(void) find_changeset(k, &xntime, TRUE);
494 			}
495 		} while (nlt != xlt);
496 		(void) find_changeset(i, &xtime, TRUE);
497 	}
498 	return (0);
499 }
500 
501 LOCAL void
dodir(name)502 dodir(name)
503 	char	*name;
504 {
505 	DIR		*dp = opendir(name);
506 	struct dirent	*d;
507 	char		*np;
508 	char		fname[MAXPATHNAME+1];
509 	char		*base;
510 	int		len;
511 
512 	if (dp == NULL) {
513 		errmsg("Cannot open directory '%s'\n", name);
514 		return;
515 	}
516 	strlcpy(fname, name, sizeof (fname));
517 	base = &fname[strlen(fname)-1];
518 	if (*base != '/')
519 		*++base = '/';
520 	base++;
521 	len = sizeof (fname) - strlen(fname);
522 	while ((d = readdir(dp)) != NULL) {
523 		char * oparm = N.n_parm;
524 
525 		np = d->d_name;
526 
527 		if (np[0] != 's' || np[1] != '.' || np[2] == '\0')
528 			continue;
529 
530 		strlcpy(base, np, len);
531 		N.n_parm = NULL;
532 		dofile(fname);
533 		N.n_parm = oparm;
534 	}
535 	closedir(dp);
536 }
537 
538 LOCAL void
dofile(name)539 dofile(name)
540 	char	*name;
541 {
542 	FILE	*f;
543 	char	*buf = NULL;
544 	size_t	bufsize = 0;
545 	int	len;
546 	BOOL	firstline = TRUE;
547 	BOOL	globalsection = FALSE;
548 	struct tm tm;
549 	char	*bname;
550 	char	*pname;
551 	struct filedata *fdata;
552 	char	type = 0;
553 
554 	if (setjmp(Fjmp))
555 		return;
556 	if (N.n_parm) {
557 #ifdef	__needed__
558 		char	*ofile = name;
559 #endif
560 
561 		name = bulkprepare(&N, name);
562 		if (name == NULL) {
563 #ifdef	__needed__
564 			if (N.n_ifile)
565 				ofile = N.n_ifile;
566 #endif
567 			/*
568 			 * The error is typically
569 			 * "directory specified as s-file (cm14)"
570 			 */
571 			fatal(gettext(bulkerror(&N)));
572 		}
573 	}
574 
575 	f = fopen(name, "rb");
576 	if (f == NULL) {
577 		errmsg("Cannot open '%s'.\n", name);
578 		return;
579 	}
580 #ifdef	USE_SETVBUF
581 	setvbuf(f, NULL, _IOFBF, VBUF_SIZE);
582 #endif
583 	if (list == NULL) {
584 		listmax += 128;
585 		list = malloc(listmax*sizeof (*list));
586 		if (list == NULL)
587 			comerr("No memory.\n");
588 	}
589 
590 	bname = pname = name;
591 	if ((pname = strrchr(pname, '/')) == 0)
592 		pname = name;
593 	else
594 		bname = ++pname;
595 	if (pname[0] == 's' && pname[1] == '.')
596 		pname += 2;
597 	if (*SccsPath && (pname != &name[2])) {
598 		char	*p = malloc(strlen(name) + 2);
599 
600 		if (p) {
601 			char	*sp;
602 
603 			sp = strstr(name, SccsPath);
604 			if (sp == NULL)
605 				len = bname - name;
606 			else
607 				len = sp - name;
608 			sprintf(p, "%.*s%s", len, name, pname);
609 		}
610 		pname = p;
611 	} else if (Cwd) {
612 		char	*p = malloc(strlen(Cwd) + strlen(pname) + 1);
613 
614 		if (p)
615 			sprintf(p, "%s%s", Cwd, pname);
616 		pname = p;
617 	} else {
618 		pname = strdup(pname);
619 	}
620 	if (pname == NULL)
621 		comerr("No memory.\n");
622 
623 	fdata = malloc(sizeof (struct filedata));
624 	if (fdata == NULL)
625 		comerr("No memory.\n");
626 	fdata->init_path = pname;	/* Cheat at the beginning */
627 
628 	while ((len = getdelim(&buf, &bufsize, '\n', f)) > 0) {
629 		if (buf[len-1] == '\n')
630 			buf[--len] = '\0';
631 		if (firstline) {
632 			firstline = FALSE;
633 			if (buf[0] != 1 || buf[1] != 'h') {
634 				fclose(f);
635 				return;
636 			}
637 		}
638 		if (len == 0)
639 			continue;
640 		if (buf[0] != 1)	/* Not a SCCS control line */
641 			continue;
642 		if (buf[1] == 't')	/* End of meta data reached */
643 			break;
644 		if (changeset && type == 'R') {
645 			if (buf[1] == 'd')
646 				type = 0;
647 			else
648 				continue;
649 		}
650 		if (buf[1] == 'd') {	/* Delta entry star line */
651 			char	vers[256];
652 			char	user[256];
653 			time_t	t;
654 			Llong	lt;
655 			int	nsecs;
656 			int	gmtoffs;
657 			char	*p = &buf[4];
658 
659 			type = buf[3];
660 			len = sscanf(p, "%s %d/%d/%d %d:%d:%d.%d%d %s",
661 				vers,
662 				&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
663 				&tm.tm_hour, &tm.tm_min, &tm.tm_sec,
664 				&nsecs,
665 				&gmtoffs,
666 				user);
667 			if (len == 10) {
668 				int hours = gmtoffs / 100;
669 				int mins  = gmtoffs % 100;
670 
671 				gmtoffs = hours * 3600 + mins * 60;
672 			} else {
673 				gmtoffs = 1;
674 				nsecs = 0;
675 			}
676 			if (len < 10)
677 				len = sscanf(p, "%s %d/%d/%d %d:%d:%d%d %s",
678 				vers,
679 				&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
680 				&tm.tm_hour, &tm.tm_min, &tm.tm_sec,
681 				&gmtoffs,
682 				user);
683 			if (len == 9) {
684 				int hours = gmtoffs / 100;
685 				int mins  = gmtoffs % 100;
686 
687 				gmtoffs = hours * 3600 + mins * 60;
688 			} else if (len < 9) {
689 				/*
690 				 * XXX GMT offset aus localtime bestimmen?
691 				 * XXX Nein, wir nehmen mktime() bei len >= 9.
692 				 */
693 				gmtoffs = 1;
694 			}
695 			if (len < 9)
696 				len = sscanf(p, "%s %d/%d/%d %d:%d:%d %s",
697 				vers,
698 				&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
699 				&tm.tm_hour, &tm.tm_min, &tm.tm_sec,
700 				user);
701 			if (len < 8) {
702 				errmsgno(EX_BAD,
703 					"Cannot scan date '%s' from '%s'.\n",
704 				p, name);
705 			}
706 
707 			if (tm.tm_year >= 100)
708 				tm.tm_year -= 1900;
709 			else if (tm.tm_year >= 0 && tm.tm_year < 69)
710 				tm.tm_year += 100;
711 			tm.tm_isdst = -1;		/* let mktime() do it */
712 			tm.tm_mon -= 1;
713 			seterrno(0);
714 			if (tm.tm_year >= 138 &&	/* >= year 2038 && */
715 			    sizeof (t) < sizeof (lt)) {	/* 32 bit time_t */
716 
717 				/*
718 				 * Make mk{gm}time() work until 2094 w. 32 Bit
719 				 * 56 years is 2x the # of years when the
720 				 * calendar repeats the same weekday...
721 				 */
722 				tm.tm_year -= 56;	/* 2 * 4 * 7 */
723 				if (len >= 9) {		/* w. GMT offset */
724 							/* never fails */
725 					t = lt = mklgmtime(&tm);
726 					lt -= gmtoffs;
727 					t -= gmtoffs;
728 				} else {
729 #undef	mktime						/* Don't use xmktime */
730 					lt = t = mktime(&tm);
731 				}
732 				tm.tm_year += 56;
733 				lt += 1767225600;	/* 56 years */
734 			} else {
735 				if (len >= 9) {		/* w. GMT offset */
736 							/* never fails */
737 					t = lt = mklgmtime(&tm);
738 					lt -= gmtoffs;
739 					t -= gmtoffs;
740 				} else {
741 					lt = t = mktime(&tm);
742 				}
743 			}
744 			/*
745 			 * Be careful, on IRIX mktime() sets errno but
746 			 * returns a time_t != -1.
747 			 */
748 			if (t == (time_t)-1 && geterrno() != 0) {
749 				comerr("Cannot convert date '%s' from '%s'.\n",
750 				p, name);
751 			}
752 
753 /*#define	XXX*/
754 #ifdef	XXX
755 			error("len: %d '%s' %d/%d/%d%n",
756 				len, vers,
757 				tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
758 				&len);
759 			error("%*s %2.2d:%2.2d:%2.2d %s %.20s%d %lld %d\n",
760 				23 - len, "",
761 				tm.tm_hour, tm.tm_min, tm.tm_sec,
762 				user, ctime(&t), tm.tm_year+1900,
763 				lt, geterrno());
764 #endif
765 
766 			if (listsize >= listmax) {
767 				listmax += 128;
768 				list = realloc(list, listmax*sizeof (*list));
769 			}
770 			if (list == NULL)
771 				comerr("No memory.\n");
772 			list[listsize].time = t;
773 			list[listsize].nsec = 0;
774 			list[listsize].ltime = lt;
775 			list[listsize].gmtoff = gmtoffs;
776 			list[listsize].tm   = tm;
777 			list[listsize].user = strdup(user);
778 			list[listsize].userlen = strlen(user);
779 			list[listsize].vers = strdup(vers);
780 			list[listsize].comment = NULL;
781 			list[listsize].commentlen = 0;
782 			list[listsize].flags = 0;
783 			list[listsize].ghash = -1;
784 			list[listsize].type = type;
785 			list[listsize].file = pname;
786 			list[listsize].fdata = fdata;
787 
788 			if (nsecs && changeset) {
789 				dtime_t	dt;
790 
791 				p = &buf[4];
792 				NONBLANK(p);
793 				while (*p != '\0' && *p != ' ' && *p != '\t')
794 					p++;
795 				NONBLANK(p);
796 				date_abz(p, &dt, 0);
797 				list[listsize].nsec = dt.dt_nsec;
798 			}
799 
800 		} else if (buf[1] == 'S') {	/* SID specific metadata */
801 			if (buf[2] == ' ' && buf[3] == 's') {
802 				long	l = -1;
803 
804 				astolb(&buf[4], &l, 10);
805 				if (l >= 0 && l <= 0xFFFF)
806 					list[listsize].ghash = l;
807 			}
808 
809 		} else if (buf[1] == 'c') {		/* Comment */
810 			if (buf[2] == '_' && !extended)
811 				continue;
812 			if (list[listsize].comment == NULL) {
813 				list[listsize].comment = strdup(&buf[3]);
814 				handle_created_msg(list[listsize].comment);
815 				list[listsize].commentlen =
816 					strlen(list[listsize].comment);
817 			} else {
818 				/*
819 				 * multi line comments
820 				 */
821 				int lastlen = list[listsize].commentlen;
822 
823 				list[listsize].comment =
824 				    realloc(list[listsize].comment,
825 						lastlen + (4-3) + len + 1);
826 				if (list[listsize].comment == NULL)
827 					comerr("No memory.\n");
828 							    /* 4 bytes */
829 				if (changeset) {
830 					strcat(list[listsize].comment, "\n");
831 					list[listsize].commentlen += 1;
832 				} else {
833 					strcat(list[listsize].comment, "\n\t  ");
834 					list[listsize].commentlen += 4;
835 				}
836 				strcat(list[listsize].comment, &buf[3]);
837 				list[listsize].commentlen += strlen(&buf[3]);
838 			}
839 
840 		} else if (buf[1] == 'e') {
841 			if (list[listsize].user == NULL) {
842 				errmsgno(EX_BAD, "Corrupt file '%s'.\n", name);
843 				continue;
844 			}
845 			/*
846 			 * Check for very old SCCS history files that may have
847 			 * no comment at all in special for Release 1.1.
848 			 */
849 			if (list[listsize].comment == NULL)
850 				list[listsize].comment = strdup("");
851 			listsize++;
852 		} else if (buf[1] == 'u') {		/* End of delta table */
853 			globalsection = TRUE;
854 
855 		} else if (globalsection && buf[1] == 'G') {
856 			char	*p;
857 
858 			if (buf[2] != ' ')
859 				continue;
860 			if (buf[3] == 'r') {		/* urand number */
861 				p = &buf[4];
862 				NONBLANK(p);
863 				urand_ab(p, &fdata->urand);
864 				if (urand_valid(&fdata->urand)) {
865 					char	ubuf[100];
866 
867 					urand_ba(&fdata->urand,
868 						ubuf, sizeof (ubuf));
869 				}
870 
871 			} else if (buf[3] == 'p') {	/* inital path */
872 				p = &buf[4];
873 				NONBLANK(p);
874 				fdata->init_path = strdup(p);
875 				if (fdata->init_path == NULL)
876 					comerr("No memory.\n");
877 			}
878 		}
879 	}
880 	fclose(f);
881 }
882 
883 LOCAL int
fgetline(f,buf,len)884 fgetline(f, buf, len)
885 	FILE	*f;
886 	char	*buf;
887 	int	len;
888 {
889 	if (fgets(buf, len, f) == NULL) {
890 		if (feof(f) || ferror(f))
891 			return (EOF);
892 	}
893 	len = strlen(buf);
894 	if (len > 0 && buf[len-1] == '\n')
895 		buf[--len] = '\0';
896 	return (len);
897 }
898 
899 /*
900  * Handle the initial "date and time created ..." message.
901  */
902 LOCAL void
handle_created_msg(s)903 handle_created_msg(s)
904 	char	*s;
905 {
906 	if (strncmp(s, "date and time created ",
907 		    strlen("date and time created ")) == 0) {
908 		char	*p1;
909 		char	*p2;
910 
911 		/*
912 		 * If it includes nanoseconds, remove the nanoseconds.
913 		 */
914 		if ((p1 = strchr(s, '.'))) {
915 			if ((p2 = strstr(p1, " by "))) {
916 				/*
917 				 * But keep the timezone offset.
918 				 */
919 				if ((p2 > (p1+5)) &&
920 				    (p2[-5] == '+' || p2[-5] == '-'))
921 					p2 -= 5;
922 				strcpy(p1, p2);
923 			}
924 		}
925 	}
926 }
927 
928 LOCAL int
getN(argp,valp)929 getN(argp, valp)
930 	const char	*argp;
931 	void		*valp;
932 {
933 	initN(&N);
934 	N.n_parm = (char *)argp;
935 	return (TRUE);
936 }
937 
938 LOCAL int
getX(argp,valp)939 getX(argp, valp)
940 	const char	*argp;
941 	void		*valp;
942 {
943 	X.x_parm = (char *)argp;
944 	X.x_flags = XO_NULLPATH;
945 	if (!parseX(&X))
946 		return (BADFLAG);
947 	return (TRUE);
948 }
949 
950 LOCAL void
print_changeset(fp,lp)951 print_changeset(fp, lp)
952 	FILE		*fp;
953 	struct	delt	*lp;
954 {
955 	char	ubuf[20];
956 	char	*p  = lp->fdata->init_path;
957 
958 	urand_ba(&lp->fdata->urand, ubuf, sizeof (ubuf));
959 	fprintf(fp, "%s|%s|%5.5d|%zd|%s\n",
960 		ubuf, lp->vers, lp->ghash, strlen(p), p);
961 }
962 
963 LOCAL Llong
find_changeset(i,xtp,printit)964 find_changeset(i, xtp, printit)
965 	int	i;
966 	struct xtime *xtp;
967 	BOOL	printit;
968 {
969 	int	j;
970 	int	k;
971 	struct xtime xtime;
972 #define	NFDATA	4096
973 	struct filedata *fdata[NFDATA];
974 	struct filedata **fdp = fdata;
975 	int	nfdata = 0;
976 	int	fdatamax = NFDATA;
977 
978 	if (xtp == NULL)
979 		xtp = &xtime;
980 
981 	xtp->xt = list[i].time;
982 	xtp->xlt = list[i].ltime;
983 	xtp->xns = list[i].nsec;
984 	xtp->xgmtoff = list[i].gmtoff;
985 	xtp->xtm = list[i].tm;
986 
987 	if (printit) {
988 		struct xtime xptime;
989 
990 		if (list[i].flags & PRINTED)
991 			return (LLONG_MAX);
992 
993 		if (changeset && Cs == NULL) {
994 			/*
995 			 * Open file to collect changeset entries for the next
996 			 * commit.
997 			 */
998 			if ((Cs = fopen(csname, "wb")) == NULL)
999 				comerr("Cannot open '%s'.\n", csname);
1000 		}
1001 		/*
1002 		 * XXX Should we implement a variant with local time +GMT off?
1003 		 */
1004 		find_changeset(i, &xptime, FALSE);
1005 		printf("%.20s%d %s\n",
1006 			ctime(&xptime.xt), xptime.xtm.tm_year + 1900,
1007 			mapuser(list[i].user));
1008 		if (changeset)
1009 			print_changeset(Cs, &list[i]);
1010 		else
1011 			printf("	* %s%s %s\n",
1012 				list[i].type == 'R'? "R ":"",
1013 				list[i].file,
1014 				list[i].vers);
1015 		list[i].flags |= PRINTED;
1016 	}
1017 	if (!multfile)
1018 		fdp[nfdata++] = list[i].fdata;
1019 	for (j = i+1; j < listsize; j++) {
1020 		if (list[j].flags & PRINTED)
1021 			continue;
1022 		if (nopooling) {
1023 			if (list[i].time - list[j].time > 60)
1024 				break;
1025 			if (list[j].time - list[i].time > 60)
1026 				break;
1027 		}
1028 		if (nopooling > 1 &&
1029 		    list[i].time != list[j].time)
1030 			break;
1031 		if (reverse && list[j].time - list[i].time > maxdelta)
1032 			break;
1033 		if (!reverse && list[i].time - list[j].time > maxdelta)
1034 			break;
1035 		if (list[i].comment == NULL || list[j].comment == NULL)
1036 			continue;
1037 		if (list[i].comment[0] == '\0') {
1038 			if (list[i].time - list[j].time > 60)
1039 				break;
1040 			if (list[j].time - list[i].time > 60)
1041 				break;
1042 		}
1043 		if (list[i].commentlen == list[j].commentlen &&
1044 		    streql(list[i].comment, list[j].comment)) {
1045 			if (list[i].userlen != list[j].userlen ||
1046 			    !streql(list[i].user, list[j].user)) {
1047 				/*
1048 				 * When creating a changeset, we cannot allow
1049 				 * a commit with a different user name
1050 				 * inside our timeline.
1051 				 */
1052 				continue;
1053 			}
1054 			for (k = 0; k < nfdata; k++) {
1055 				/*
1056 				 * Check whether the same filename
1057 				 * already appears in our list.
1058 				 */
1059 				if (fdp[k] == list[j].fdata) {
1060 					goto out;
1061 				}
1062 			}
1063 			if (nfdata >= fdatamax) {
1064 				fdatamax += 1024;
1065 
1066 				if (fdp != fdata) {
1067 					fdp = realloc(fdp,
1068 						fdatamax * sizeof (*fdp));
1069 				} else {
1070 					fdp = malloc(fdatamax * sizeof (*fdp));
1071 					if (fdp)
1072 						movebytes(fdata, fdp,
1073 							sizeof (fdata));
1074 				}
1075 				if (fdp == NULL)
1076 					comerr("No memory.\n");
1077 			}
1078 			if (!multfile)
1079 				fdp[nfdata++] = list[j].fdata;
1080 
1081 			if (xtp->xlt < list[j].ltime) {
1082 				xtp->xt = list[j].time;
1083 				xtp->xlt = list[j].ltime;
1084 				xtp->xns = list[j].nsec;
1085 				xtp->xgmtoff = list[j].gmtoff;
1086 				xtp->xtm = list[j].tm;
1087 			} else if ((xtp->xlt == list[j].ltime) &&
1088 				    (xtp->xns < list[j].nsec)) {
1089 				xtp->xns = list[j].nsec;
1090 			}
1091 			if (printit) {
1092 				if (changeset)
1093 					print_changeset(Cs, &list[j]);
1094 				else
1095 					printf("	* %s%s %s\n",
1096 						list[j].type == 'R'? "R ":"",
1097 						list[j].file,
1098 						list[j].vers);
1099 				list[j].flags |= PRINTED;
1100 			}
1101 		}
1102 	}
1103 out:
1104 	if (printit) {
1105 		printf("	  %s\n\n",
1106 			list[i].comment);
1107 	}
1108 
1109 	/*
1110 	 * Make a delta for the next collection of changeset entries.
1111 	 * This simulates an "sccs commit" for that bundle.
1112 	 */
1113 	if (printit && changeset) {
1114 		commit_changeset(i, xtp);
1115 	}
1116 	if (fdp != fdata)
1117 		free(fdp);
1118 	return (xtp->xlt);
1119 }
1120 
1121 LOCAL void
commit_changeset(i,xtp)1122 commit_changeset(i, xtp)
1123 	int	i;		/* Index in global array */
1124 	struct xtime *xtp;
1125 {
1126 	char	cm[10240];	/* Comment */
1127 	char	dy[100];	/* Datetime */
1128 	char	cs[100];	/* gpath, mapped user, user */
1129 	char	us[100];	/* mapped user */
1130 	char	nm[100];	/* user name */
1131 	char	*mu;
1132 	pid_t	pid;
1133 	int	gmtoffs = xtp->xgmtoff;
1134 
1135 	if (gmtoffs < 0)
1136 		gmtoffs = -gmtoffs;
1137 
1138 	fflush(Cs);
1139 	fclose(Cs);
1140 	Cs = NULL;
1141 
1142 	snprintf(cm, sizeof (cm), "-y%s", list[i].comment);
1143 
1144 	us[0] = '\0';
1145 	if ((mu = mapuser(list[i].user)) != list[i].user) {
1146 		snprintf(us, sizeof (us), ",mail=%s", mu);
1147 	}
1148 
1149 	snprintf(nm, sizeof (nm), ",user=%s", list[i].user);
1150 
1151 	snprintf(cs, sizeof (cs), "-Xgpath=%s%s%s", csname, us, nm);
1152 
1153 	snprintf(dy, sizeof (dy),
1154 		"%s=%d/%2.2d%2.2d%2.2d%2.2d%2.2d.%9.9ld%c%2.2d%2.2d",
1155 		"-Xdate",
1156 		xtp->xtm.tm_year + 1900,
1157 		xtp->xtm.tm_mon + 1,
1158 		xtp->xtm.tm_mday,
1159 		xtp->xtm.tm_hour,
1160 		xtp->xtm.tm_min,
1161 		xtp->xtm.tm_sec,
1162 		xtp->xns,
1163 		xtp->xgmtoff < 0 ? '-':'+',
1164 		gmtoffs / 3600,
1165 		(gmtoffs % 3600) / 60);
1166 
1167 	/*
1168 	 * /opt/schily/ccs/bin/delta -q -f \
1169 	 * 	-Xprepend,nobulk,gpath=/tmp/cs.$$ \
1170 	 *	-Xmail=mmm -Xdate=xxx -ycomment
1171 	 */
1172 	if ((pid = vfork()) == 0) {
1173 		execl(PROGPATH(delta), "delta",
1174 			"-q", "-f",
1175 			"-Xprepend,nobulk",
1176 			cs,	/* -Xgpath=%s,mail=%s,user=%s */
1177 			dy,	/* -Xdate=%s		    */
1178 			cm,	/* -ycomment		    */
1179 			".sccs/SCCS/s.changeset",
1180 			(char *)NULL);
1181 		_exit(1);
1182 	} else if (pid < 0) {
1183 		comerr("Cannot fork().\n");
1184 	} else {
1185 		WAIT_T	w;
1186 
1187 		wait(&w);
1188 		if (*((int *)(&w)) != 0) {
1189 			comerrno(EX_BAD, "Cannot run %s.\n",
1190 				PROGPATH(delta));
1191 		}
1192 	}
1193 }
1194