main shrubtools / nviz / log.c
  1#include <errno.h>
  2#include <inttypes.h>
  3#include <stdio.h>
  4#include <stdlib.h>
  5#include <string.h>
  6#include "graph.h"
  7#include "log.h"
  8#include "util.h"
  9
 10static FILE *logfile;
 11static const char *logname = ".ninja_log";
 12static const char *logtmpname = ".ninja_log.tmp";
 13static const char *logfmt = "# ninja log v%d\n";
 14static const int logver = 7;
 15
 16static char *
 17nextfield(char **end)
 18{
 19	char *s = *end;
 20
 21	if (!*s) {
 22		warn("corrupt build log: missing field");
 23		return NULL;
 24	}
 25	*end += strcspn(*end, "\t\n");
 26	if (**end)
 27		*(*end)++ = '\0';
 28
 29	return s;
 30}
 31
 32void
 33loginit(const char *builddir)
 34{
 35	int ver;
 36	char *logpath = (char *)logname, *logtmppath = (char *)logtmpname, *p, *s;
 37	size_t nline, nentry, i;
 38	struct edge *e;
 39	struct node *n;
 40	int64_t mtime;
 41	struct buffer buf = {0};
 42
 43	nline = 0;
 44	nentry = 0;
 45
 46	if (logfile)
 47		fclose(logfile);
 48	if (builddir)
 49		xasprintf(&logpath, "%s/%s", builddir, logname);
 50	logfile = fopen(logpath, "r+");
 51	if (!logfile) {
 52		if (errno != ENOENT)
 53			fatal("open %s:", logpath);
 54		goto rewrite;
 55	}
 56	setvbuf(logfile, NULL, _IOLBF, 0);
 57	if (fscanf(logfile, logfmt, &ver) < 1)
 58		goto rewrite;
 59	if (ver != logver)
 60		goto rewrite;
 61
 62	for (;;) {
 63		if (buf.cap - buf.len < BUFSIZ) {
 64			buf.cap = buf.cap ? buf.cap * 2 : BUFSIZ;
 65			buf.data = xreallocarray(buf.data, buf.cap, 1);
 66		}
 67		buf.data[buf.cap - 2] = '\0';
 68		if (!fgets(buf.data + buf.len, buf.cap - buf.len, logfile))
 69			break;
 70		if (buf.data[buf.cap - 2] && buf.data[buf.cap - 2] != '\n') {
 71			buf.len = buf.cap - 1;
 72			continue;
 73		}
 74		++nline;
 75		p = buf.data;
 76		buf.len = 0;
 77		if (!nextfield(&p))  /* start time */
 78			continue;
 79		if (!nextfield(&p))  /* end time */
 80			continue;
 81		s = nextfield(&p);  /* mtime (used for restat) */
 82		if (!s)
 83			continue;
 84		mtime = strtoll(s, &s, 10);
 85		if (*s) {
 86			warn("corrupt build log: invalid mtime");
 87			continue;
 88		}
 89		s = nextfield(&p);  /* output path */
 90		if (!s)
 91			continue;
 92		n = nodeget(s, 0);
 93		if (!n || !n->gen)
 94			continue;
 95		if (n->logmtime == MTIME_MISSING)
 96			++nentry;
 97		n->logmtime = mtime;
 98		s = nextfield(&p);  /* command hash */
 99		if (!s)
100			continue;
101		n->hash = strtoull(s, &s, 16);
102		if (*s) {
103			warn("corrupt build log: invalid hash for '%s'", n->path->s);
104			continue;
105		}
106	}
107	free(buf.data);
108	if (ferror(logfile)) {
109		warn("build log read:");
110		goto rewrite;
111	}
112	if (nline <= 100 || nline <= 3 * nentry) {
113		if (builddir)
114			free(logpath);
115		return;
116	}
117
118rewrite:
119	if (logfile)
120		fclose(logfile);
121	if (builddir)
122		xasprintf(&logtmppath, "%s/%s", builddir, logtmpname);
123	logfile = fopen(logtmppath, "w");
124	if (!logfile)
125		fatal("open %s:", logtmppath);
126	setvbuf(logfile, NULL, _IOLBF, 0);
127	fprintf(logfile, logfmt, logver);
128	if (nentry > 0) {
129		for (e = alledges; e; e = e->allnext) {
130			for (i = 0; i < e->nout; ++i) {
131				n = e->out[i];
132				if (!n->hash)
133					continue;
134				logrecord(n);
135			}
136		}
137	}
138	fflush(logfile);
139	if (ferror(logfile))
140		fatal("build log write failed");
141	if (rename(logtmppath, logpath) < 0)
142		fatal("build log rename:");
143	if (builddir) {
144		free(logpath);
145		free(logtmppath);
146	}
147}
148
149void
150logclose(void)
151{
152	fflush(logfile);
153	if (ferror(logfile))
154		fatal("build log write failed");
155	fclose(logfile);
156}
157
158void
159logrecord(struct node *n)
160{
161	fprintf(logfile, "0\t0\t%" PRId64 "\t%s\t%" PRIx64 "\n", n->logmtime, n->path->s, n->hash);
162}