]> andersk Git - moira.git/blame - gen/mailhub.dc
move zip code correctly
[moira.git] / gen / mailhub.dc
CommitLineData
bac4ceaa 1
2/* $Header$
3 *
4 * This generates the /usr/lib/aliases file for the mailhub.
5 *
6 * (c) Copyright 1988, 1990 by the Massachusetts Institute of Technology.
7 * For copying and distribution information, please see the file
8 * <mit-copyright.h>.
9 */
10
11#include <mit-copyright.h>
12#include <stdio.h>
13#include <string.h>
14#include <ctype.h>
15#include <moira.h>
16#include <moira_site.h>
17#include <sys/types.h>
18#include <sys/stat.h>
19#include <sys/time.h>
20EXEC SQL INCLUDE sqlca;
21
22
23extern int errno;
24char *whoami = "mailhub.gen";
25char *perm_malloc();
26char *pstrsave();
27char *divide = "##############################################################";
28
29#define ML_WID 72
30#define AL_MAX_WID 896
31
32#define FALSE 0
33#define TRUE (!FALSE)
34
35FILE *out= stdout;
36
37
38main(argc, argv)
39int argc;
40char **argv;
41{
42 long tm = time(NULL);
43 char filename[64], *targetfile;
44 struct stat sb;
45 EXEC SQL BEGIN DECLARE SECTION;
46 int flag;
47 EXEC SQL END DECLARE SECTION;
48
49#ifsql INGRES
50 EXEC SQL CONNECT sms;
51#endsql
52#ifsql INFORMIX
53 EXEC SQL DATABASE sms;
54#endsql
55
56 if (argc == 2) {
57 if (stat(argv[1], &sb) == 0) {
58 if (ModDiff (&flag, "users", sb.st_mtime))
59 exit(MR_DATE);
60 if (flag < 0) {
61 fprintf(stderr, "File %s does not need to be rebuilt.\n",
62 argv[1]);
63 exit(MR_NO_CHANGE);
64 }
65 }
66 targetfile = argv[1];
67 sprintf(filename, "%s~", targetfile);
68 if ((out = fopen(filename, "w")) == NULL) {
69 fprintf(stderr, "unable to open %s for output\n", filename);
70 exit(MR_OCONFIG);
71 }
72 } else if (argc != 1) {
73 fprintf(stderr, "usage: %s [outfile]\n", argv[0]);
74 exit(MR_ARGS);
75 }
76
77 fprintf(out, "%s\n# Aliases File Extract of %s", divide, ctime(&tm));
78 fprintf(out, "# This file is automatically generated, do not edit it directly.\n%s\n\n", divide);
79
80 get_info();
81
82#ifsql INGRES
83 EXEC SQL DISCONNECT;
84#endsql
85#ifsql INFORMIX
86 EXEC SQL CLOSE DATABASE;
87#endsql
88
89 fprintf(stderr, "Sorting Info\n");
90 sort_info();
91
92 fprintf(stderr, "Dumping information\n");
93 do_people();
94
95 fprintf(out, "\n%s\n# End of aliases file\n", divide);
96
97 if (fclose(out)) {
98 perror("close failed");
99 exit(MR_CCONFIG);
100 }
101
102 if (argc == 2)
103 fix_file(targetfile);
104 exit(MR_SUCCESS);
105}
106
107
108
109struct hash *users, *machines, *strings, *lists, *names;
110struct user {
111 char *login;
112 char *first;
113 char *last;
114 char mi;
115 char *pobox;
116};
117struct member {
118 struct member *next;
119 char *name;
120 int list_id;
121};
122struct list {
123 char *name;
124 char maillist;
125 char acl_t;
126 int acl_id;
127 struct member *m;
128};
129struct names {
130 char *name;
131 struct names *next;
132 int keep:1;
133 int id:31;
134};
135
136
137get_info()
138{
139 EXEC SQL BEGIN DECLARE SECTION;
140 int id, pid, bid, cnt, maillistp, acl, mid;
141 char name[129], type[9], fname[17], mname[17], lname[17], buf[257];
142 EXEC SQL END DECLARE SECTION;
143 char *s;
144 register struct user *u;
145 struct list *l, *memberlist;
146 register struct member *m;
147
148 EXEC SQL WHENEVER SQLERROR GOTO sqlerr;
149
150 EXEC SQL SELECT modtime INTO :buf FROM users WHERE users_id = 0;
151 EXEC SQL SELECT modtime INTO :buf FROM list WHERE list_id = 0;
152
153 cnt = 0;
154 machines = create_hash(10);
155
156 EXEC SQL DECLARE m_cursor CURSOR FOR
157 SELECT m.mach_id, m.name
158 FROM machine m, users u
159 WHERE m.mach_id = u.pop_id;
160 EXEC SQL OPEN m_cursor;
161 while (1) {
162 EXEC SQL FETCH m_cursor INTO :id, :name;
163 if (sqlca.sqlcode != 0) break;
164 if (s = index(name, '.'))
165 *s = 0;
166 else
167 strtrim(name);
168#ifdef ATHENA
169 strcat(name, ".LOCAL");
170#endif
171 if (hash_store(machines, id, pstrsave(name)) < 0) {
172 fprintf(stderr, "Out of memory!\n");
173 exit(MR_NO_MEM);
174 }
175 cnt++;
176 }
177 EXEC SQL CLOSE m_cursor;
178
179 fprintf(stderr, "Loaded %d machines\n", cnt);
180
181 cnt = 0;
182 strings = create_hash(4000);
183
184 EXEC SQL DECLARE s_cursor CURSOR FOR
185 SELECT string_id, string
186 FROM strings;
187 EXEC SQL OPEN s_cursor;
188 while (1) {
189 EXEC SQL FETCH s_cursor INTO :id, :name;
190 if (sqlca.sqlcode != 0) break;
191 if (hash_store(strings, id, pstrsave(strtrim(name))) < 0) {
192 fprintf(stderr, "Out of memory!\n");
193 exit(MR_NO_MEM);
194 }
195 cnt++;
196 }
197 EXEC SQL CLOSE s_cursor;
198
199 fprintf(stderr, "Loaded %d strings\n", cnt);
200
201 cnt = 0;
202 users = create_hash(12001);
203
204 EXEC SQL DECLARE u_cursor CURSOR FOR
205 SELECT users_id, login, first, middle, last, potype, pop_id, box_id
206 FROM users
207 WHERE status != 3;
208 EXEC SQL OPEN u_cursor;
209 while (1) {
210 EXEC SQL FETCH u_cursor INTO :id, :name, :fname, :mname, :lname,
211 :type, :pid, :bid;
212 if (sqlca.sqlcode != 0) break;
213 u = (struct user *) perm_malloc(sizeof(struct user));
214 u->login = pstrsave(strtrim(name));
215 u->first = pstrsave(strtrim(fname));
216 u->last = pstrsave(strtrim(lname));
217 if (mname[0] != ' ')
218 u->mi = mname[0];
219 else
220 u->mi = 0;
221
222 if (type[0] == 'P' && (s = hash_lookup(machines, pid))) {
223 sprintf(buf, "%s@%s", u->login, s);
224 u->pobox = pstrsave(buf);
225 } else if (type[0] == 'S') {
226 u->pobox = hash_lookup(strings, bid);
227 } else
228 u->pobox = (char *) NULL;
229 if (hash_store(users, id, u) < 0) {
230 fprintf(stderr, "Out of memory!\n");
231 exit(MR_NO_MEM);
232 }
233 cnt++;
234 }
235 EXEC SQL CLOSE u_cursor;
236 fprintf(stderr, "Loaded %d users\n", cnt);
237
238 cnt = 0;
239 lists = create_hash(15000);
240
241 EXEC SQL DECLARE l_cursor CURSOR FOR
242 SELECT list_id, name, maillist, acl_type, acl_id
243 FROM list
244 WHERE active != 0;
245 EXEC SQL OPEN l_cursor;
246 while (1) {
247 EXEC SQL FETCH l_cursor INTO :id, :name, :maillistp, :type, :acl;
248 if (sqlca.sqlcode != 0) break;
249 l = (struct list *) perm_malloc(sizeof(struct list));
250 l->name = pstrsave(strtrim(name));
251 l->maillist = maillistp;
252 l->acl_t = type[0];
253 l->acl_id = acl;
254 l->m = (struct member *) NULL;
255 if (hash_store(lists, id, l) < 0) {
256 fprintf(stderr, "Out of memory!\n");
257 exit(MR_NO_MEM);
258 }
259 cnt++;
260 }
261 EXEC SQL CLOSE l_cursor;
262 fprintf(stderr, "Loaded %d lists\n", cnt);
263
264 cnt = 0;
265
266 EXEC SQL DECLARE m_cursor2 CURSOR FOR
267 SELECT list_id, member_type, member_id
268 FROM imembers
269 WHERE direct = 1;
270 EXEC SQL OPEN m_cursor2;
271 while (1) {
272 EXEC SQL FETCH m_cursor2 INTO :id, :type, :mid;
273 if (sqlca.sqlcode != 0) break;
274 cnt++;
275 if (l = (struct list *) hash_lookup(lists, id)) {
276 m = (struct member *) perm_malloc(sizeof(struct member));
277 if (type[0] == 'U' &&
278 (u = (struct user *) hash_lookup(users, mid))) {
279 m->list_id = 0;
280 m->name = u->login;
281 m->next = l->m;
282 l->m = m;
283 } else if (type[0] == 'L' &&
284 (memberlist = (struct list *) hash_lookup(lists, mid))) {
285 m->list_id = mid;
286 m->name = memberlist->name;
287 m->next = l->m;
288 l->m = m;
289 } else if (type[0] == 'S' &&
290 (s = hash_lookup(strings, mid))) {
291 m->list_id = 0;
292 m->next = l->m;
293 l->m = m;
294 m->name = s;
295 }
296 }
297 }
298 EXEC SQL CLOSE m_cursor2;
299 fprintf(stderr, "Loaded %d members\n", cnt);
300 return;
301 sqlerr:
302 com_err(whoami, MR_INGRES_ERR, " code %d\n", sqlca.sqlcode);
303 critical_alert("DCM", "Hesiod build encountered DATABASE ERROR %d",
304 sqlca.sqlcode);
305 exit(MR_INGRES_ERR);
306}
307
308
309save_mlist(id, l, force)
310int id;
311struct list *l;
312int force;
313{
314 register struct member *m;
315 register struct list *l1;
316
317 if (l->maillist > 1 ||
318 (l->maillist == 0 && !force))
319 return;
320
321 if (l->m && l->m->next == NULL &&
322 !strcasecmp(l->name, l->m->name)) {
323 l->maillist = 3;
324 return;
325 }
326 l->maillist = 2;
327 insert_name(l->name, -1, TRUE, FALSE);
328 output_mlist(id, l);
329
330 if (l->acl_t == 'L' && (l1 = (struct list *)hash_lookup(lists, l->acl_id)))
331 save_mlist(0, l1, TRUE);
332
333 for (m = l->m; m; m = m->next) {
334 if (m->list_id && (l1 = (struct list *)hash_lookup(lists, m->list_id)))
335 save_mlist(0, l1, TRUE);
336 }
337}
338
339
340insert_login(id, u, dummy)
341int id;
342struct user *u;
343int dummy;
344{
345 if (u->pobox && u->login[0] != '#')
346 insert_name(u->login, id, TRUE, FALSE);
347}
348
349void insert_names(id, u, dummy)
350int id;
351struct user *u;
352int dummy;
353{
354 char buffer[256];
355
356 insert_name(u->last, id, FALSE, FALSE);
357 sprintf(buffer, "%s_%s", u->first, u->last);
358 insert_name(buffer, id, FALSE, TRUE);
359/* sprintf(buffer, "%c_%s", u->first[0], u->last);
360 insert_name(buffer, id, FALSE, TRUE); */
361 if (u->mi) {
362 sprintf(buffer, "%s_%c_%s", u->first, u->mi, u->last);
363 insert_name(buffer, id, FALSE, TRUE);
364 }
365}
366
367int incount = 0;
368
369insert_name(s, id, nodups, copy)
370char *s;
371int id;
372int nodups;
373int copy;
374{
375 int code;
376 register struct names *ns;
377
378 incount++;
379 code = hashstr(s);
380 ns = (struct names *) hash_lookup(names, code);
381 if (ns == NULL) {
382 if ((ns = (struct names *) perm_malloc(sizeof(struct names))) == NULL) {
383 fprintf(stderr, "ran out of memory inserting name (sorting)\n");
384 exit(MR_NO_MEM);
385 }
386 if (copy)
387 ns->name = pstrsave(s);
388 else
389 ns->name = s;
390 ns->keep = nodups;
391 ns->id = id;
392 ns->next = NULL;
393 if (hash_store(names, code, ns) < 0) {
394 fprintf(stderr, "Out of memory!\n");
395 exit(MR_NO_MEM);
396 }
397 return;
398 }
399 if (strcasecmp(ns->name, s)) {
400 while (ns->next) {
401 ns = ns->next;
402 if (!strcasecmp(ns->name, s))
403 goto foundns;
404 }
405 if ((ns->next = (struct names *) perm_malloc(sizeof(struct names)))
406 == NULL) {
407 fprintf(stderr, "ran out of memory insterting name (sorting)\n");
408 exit(MR_NO_MEM);
409 }
410 ns = ns->next;
411 if (copy)
412 ns->name = pstrsave(s);
413 else
414 ns->name = s;
415 ns->keep = nodups;
416 ns->id = id;
417 ns->next = NULL;
418 return;
419 }
420 foundns:
421 if (nodups || ns->keep) {
422 if (nodups && ns->keep)
423 fprintf(stderr, "duplicated name: %s\n", s);
424 return;
425 }
426 ns->id = 0;
427}
428
429
430/* Illegal chars: ! " % ( ) , . / : ; < = > @ [ \ ] ^ { | } */
431
432static int illegalchars[] = {
433 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* ^@ - ^O */
434 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* ^P - ^_ */
435 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, /* SPACE - / */
436 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, /* 0 - ? */
437 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* @ - O */
438 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, /* P - _ */
439 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ` - o */
440 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, /* p - ^? */
441 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
442 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
443 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
444 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
445 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
446 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
447 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
448 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
449};
450
451
452/* While hashing the string, punt any illegal characters */
453
454int hashstr(s)
455register char *s;
456{
457 register int result;
458 register int c;
459
460 for (result = 0; c = *s; s++) {
461 if (illegalchars[c]) {
462 register char *p;
463 for (p = s; *p; p++)
464 *p = p[1];
465 continue;
466 }
467 if (isupper(c))
468 c = *s = tolower(c);
469/* result = result * 31 + *s; */
470 result = (result << 5) - result + c - '`';
471 }
472 return(result < 0 ? -result : result);
473}
474
475
476sort_info()
477{
478 names = create_hash(20001);
479 hash_step(users, insert_login, NULL);
480 incount = 0;
481 fprintf(out, "\n%s\n# Mailing lists\n%s\n", divide, divide);
482 hash_step(lists, save_mlist, FALSE);
483 fprintf(stderr, "Output %d lists\n", incount);
484 hash_step(users, insert_names, NULL);
485 fprintf(stderr, "Inserted %d names\n", incount);
486}
487
488
489output_data(dummy, nms, out)
490int dummy;
491struct names *nms;
492FILE *out;
493{
494 register struct names *ns;
495 register struct user *u;
496
497 incount++;
498 for (ns = nms; ns; ns = ns->next) {
499 if (ns->name[0] == 0 || ns->name[1] == 0) {
500 fprintf(stderr, "punting %s due to short name\n", ns->name);
501 continue;
502 }
503 if (ns->id > 0) {
504 u = (struct user *) hash_lookup(users, ns->id);
505 if (u->pobox) {
506 fprintf(out, "%s: %s\n", ns->name, u->pobox);
507 } else {
508 fprintf(out, "%s: =%s=@nobox\n", ns->name, ns->name);
509 }
510 } else if (ns->id == 0) {
511 fprintf(out, "%s: =%s=@ambig\n", ns->name, ns->name);
512 }
513 }
514}
515
516
517int lwid, bol, awid;
518
519output_mlist(id, l)
520int id;
521register struct list *l;
522{
523 struct list *l1;
524 register struct member *m;
525 register struct user *u;
526
527 if (l->acl_t == 'L' &&
528 (l1 = (struct list *) hash_lookup(lists, l->acl_id)))
529 fprintf(out, "owner-%s: %s\n%s: ", l->name, l1->name, l->name);
530 else if (l->acl_t == 'U' &&
531 (u = (struct user *) hash_lookup(users, l->acl_id)))
532 fprintf(out, "owner-%s: %s\n%s: ", l->name, u->login, l->name);
533 else
534 fprintf(out, "%s: ", l->name);
535
536
537 lwid = strlen(l->name) + 2;
538 bol = 1;
539 for (m = l->m; m; m = m->next) {
540 do_member(out, m->name);
541 }
542 if (l->m == (struct member *)NULL)
543 fprintf(out, "/dev/null");
544 fprintf(out, "\n\n");
545 incount++;
546}
547
548
549/* print out strings separated by commas, doing line breaks as appropriate */
550
551do_member(out, s)
552FILE *out;
553register char *s;
554{
555 register wwid;
556 static int cont = 1;
557
558 wwid = strlen(s);
559
560 if (!bol && awid + wwid + 2 > AL_MAX_WID) {
561 fprintf(out, ",\n\tcontinuation-%d\ncontinuation-%d: ", cont, cont);
562 cont++;
563 awid = lwid = 17 + wwid;
564 fputs(s, out);
565 return;
566 }
567
568 if (bol) {
569 lwid += wwid;
570 awid = lwid;
571 fputs(s, out);
572 bol = 0;
573 return;
574 }
575 if (lwid + wwid + 2 > ML_WID) {
576 fprintf(out, ",\n\t%s", s);
577 awid += lwid + wwid + 2;
578 lwid = wwid + 8;
579 return;
580 }
581 lwid += wwid + 2;
582 fprintf(out, ", %s", s);
583}
584
585
586do_people()
587{
588 incount = 0;
589 fprintf(out, "\n%s\n# People\n%s\n", divide, divide);
590 hash_step(names, output_data, out);
591 fprintf(stderr, "Output %d entries\n", incount);
592}
593
594
595#define chunk_size 102400
596
597char *perm_malloc(size)
598unsigned size;
599{
600 static char *pool = NULL;
601 static unsigned pool_size = 0;
602 register char *ret;
603
604 if (size > pool_size) {
605 pool = (char *) malloc(chunk_size);
606 pool_size = chunk_size;
607 }
608 ret = pool;
609 pool += size;
610 pool = (char *)(((unsigned) (pool + 1)) & ~1);
611 pool_size -= (pool - ret);
612 return(ret);
613}
614
615
616/*
617 * Make a (permenant) copy of a string.
618 */
619char *
620pstrsave(s)
621 char *s;
622{
623 register int len;
624 register char *p;
625 /* Kludge for sloppy string semantics */
626 if (!s) {
627 printf("NULL != \"\" !!!!\r\n");
628 p = perm_malloc(1);
629 *p = '\0';
630 return p;
631 }
632 len = strlen(s) + 1;
633 p = perm_malloc((u_int)len);
634 if (p) bcopy(s, p, len);
635 return p;
636}
637
638#define hash_func(h, key) (key >= 0 ? (key % h->size) : (-key % h->size))
639
640/* Create a hash table. The size is just a hint, not a maximum. */
641
642struct hash *create_hash(size)
643int size;
644{
645 struct hash *h;
646
647 h = (struct hash *) perm_malloc(sizeof(struct hash));
648 if (h == (struct hash *) NULL)
649 return((struct hash *) NULL);
650 h->size = size;
651 h->data = (struct bucket **) perm_malloc(size * sizeof(char *));
652 if (h->data == (struct bucket **) NULL) {
653 free(h);
654 return((struct hash *) NULL);
655 }
656 bzero(h->data, size * sizeof(char *));
657 return(h);
658}
659
660/* Lookup an object in the hash table. Returns the value associated with
661 * the key, or NULL (thus NULL is not a very good value to store...)
662 */
663
664char *hash_lookup(h, key)
665struct hash *h;
666register int key;
667{
668 register struct bucket *b;
669
670 b = h->data[hash_func(h, key)];
671 while (b && b->key != key)
672 b = b->next;
673 if (b && b->key == key)
674 return(b->data);
675 else
676 return(NULL);
677}
678
679
680/* Update an existing object in the hash table. Returns 1 if the object
681 * existed, or 0 if not.
682 */
683
684int hash_update(h, key, value)
685struct hash *h;
686register int key;
687char *value;
688{
689 register struct bucket *b;
690
691 b = h->data[hash_func(h, key)];
692 while (b && b->key != key)
693 b = b->next;
694 if (b && b->key == key) {
695 b->data = value;
696 return(1);
697 } else
698 return(0);
699}
700
701
702/* Store an item in the hash table. Returns 0 if the key was not previously
703 * there, 1 if it was, or -1 if we ran out of memory.
704 */
705
706int hash_store(h, key, value)
707struct hash *h;
708register int key;
709char *value;
710{
711 register struct bucket *b, **p;
712
713 p = &(h->data[hash_func(h, key)]);
714 if (*p == NULL) {
715 b = *p = (struct bucket *) perm_malloc(sizeof(struct bucket));
716 if (b == (struct bucket *) NULL)
717 return(-1);
718 b->next = NULL;
719 b->key = key;
720 b->data = value;
721 return(0);
722 }
723
724 for (b = *p; b && b->key != key; b = *p)
725 p = (struct bucket **) *p;
726 if (b && b->key == key) {
727 b->data = value;
728 return(1);
729 }
730 b = *p = (struct bucket *) perm_malloc(sizeof(struct bucket));
731 if (b == (struct bucket *) NULL)
732 return(-1);
733 b->next = NULL;
734 b->key = key;
735 b->data = value;
736 return(0);
737}
738
739
740/* Search through the hash table for a given value. For each piece of
741 * data with that value, call the callback proc with the corresponding key.
742 */
743
744hash_search(h, value, callback)
745struct hash *h;
746register char *value;
747void (*callback)();
748{
749 register struct bucket *b, **p;
750
751 for (p = &(h->data[h->size - 1]); p >= h->data; p--) {
752 for (b = *p; b; b = b->next) {
753 if (b->data == value)
754 (*callback)(b->key);
755 }
756 }
757}
758
759
760/* Step through the hash table, calling the callback proc with each key.
761 */
762
763hash_step(h, callback, hint)
764struct hash *h;
765void (*callback)();
766char *hint;
767{
768 register struct bucket *b, **p;
769
770 for (p = &(h->data[h->size - 1]); p >= h->data; p--) {
771 for (b = *p; b; b = b->next) {
772 (*callback)(b->key, b->data, hint);
773 }
774 }
775}
776
777
778/* Deallocate all of the memory associated with a table */
779
780hash_destroy(h)
781struct hash *h;
782{
783 register struct bucket *b, **p, *b1;
784
785 for (p = &(h->data[h->size - 1]); p >= h->data; p--) {
786 for (b = *p; b; b = b1) {
787 b1 = b->next;
788 free(b);
789 }
790 }
791}
This page took 0.151477 seconds and 5 git commands to generate.