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}