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