1/*
2 * This file has been donated to Jam.
3 */
4
5# include "jam.h"
6# include "lists.h"
7# include "parse.h"
8# include "rules.h"
9# include "regexp.h"
10# include "headers.h"
11# include "newstr.h"
12# include "hash.h"
13# include "hcache.h"
14# include "variable.h"
15# include "search.h"
16# include "pathsys.h"
17
18#ifdef OPT_HEADER_CACHE_EXT
19
20/*
21 * Craig W. McPheeters, Alias|Wavefront.
22 *
23 * hcache.c hcache.h - handle cacheing of #includes in source files
24 *
25 * Create a cache of files scanned for headers.  When starting jam,
26 * look for the cache file and load it if present.  When finished the
27 * binding phase, create a new header cache.  The cache contains
28 * files, their timestamps and the header files found in their scan.
29 * During the binding phase of jam, look in the header cache first for
30 * the headers contained in a file.  If the cache is present and
31 * valid, use its contents.  This results in dramatic speedups with
32 * large projects (eg. 3min -> 1min startup for one project.)
33 *
34 * External routines:
35 *    hcache_init() - read and parse the local .jamdeps file.
36 *    hcache_done() - write a new .jamdeps file
37 *    hcache() - return list of headers on target.  Use cache or do a scan.
38 *
39 * The dependency file format is an ascii file with 1 line per target.
40 * Each line has the following fields:
41 * @boundname@ timestamp @file@ @file@ @file@ ... \n
42 * */
43
44struct hcachedata {
45    const char	*boundname;
46    time_t		time;
47    LIST		*includes;
48    LIST		*hdrscan; /* the HDRSCAN value for this target */
49    int			age;	/* if too old, we'll remove it from cache */
50    struct hcachedata	*next;
51} ;
52
53typedef struct hcachedata HCACHEDATA ;
54
55
56static struct hash *hcachehash = 0;
57static HCACHEDATA  *hcachelist = 0;
58
59static int queries = 0;
60static int hits = 0;
61
62#define CACHE_FILE_VERSION "version 4"
63#define CACHE_RECORD_HEADER "header"
64#define CACHE_RECORD_END "end"
65
66/*
67 * Return the name of the header cache file.  May return NULL.
68 *
69 * The user sets this by setting the HCACHEFILE variable in a Jamfile.
70 * We cache the result so the user can't change the cache file during
71 * header scanning.
72 */
73static const char*
74cache_name(void)
75{
76    static const char* name = 0;
77    if (!name) {
78	LIST *hcachevar = var_get("HCACHEFILE");
79
80	if (hcachevar) {
81	    TARGET *t = bindtarget( hcachevar->string );
82
83	    pushsettings( t->settings );
84	    t->boundname = search( t->name, &t->time );
85	    popsettings( t->settings );
86
87	    if (hcachevar) {
88		name = copystr(t->boundname);
89	    }
90	}
91    }
92    return name;
93}
94
95/*
96 * Return the maximum age a cache entry can have before it is purged
97 * from the cache.
98 */
99static int
100cache_maxage(void)
101{
102    int age = 100;
103    LIST *var = var_get("HCACHEMAXAGE");
104
105    if (var) {
106	age = atoi(var->string);
107	if (age < 0)
108	    age = 0;
109    }
110
111    return age;
112}
113
114/*
115 * Read a netstring.  The caveat is that the string can't contain
116 * ASCII 0.  The returned value is as returned by newstr(), so it need
117 * not be freed.
118 */
119const char*
120read_netstring(FILE* f)
121{
122    unsigned long len;
123    static char* buf = NULL;
124    static unsigned long buf_len = 0;
125
126    if (fscanf(f, " %9lu", &len) != 1)
127	return NULL;
128    if (fgetc(f) != (int)'\t')
129	return NULL;
130
131    if (len > 1024 * 64)
132	return NULL;		/* sanity check */
133
134    if (len > buf_len)
135    {
136	unsigned long new_len = buf_len * 2;
137	if (new_len < len)
138	    new_len = len;
139	buf = realloc(buf, new_len + 1);
140	if (buf)
141	    buf_len = new_len;
142    }
143
144    if (!buf)
145	return NULL;
146
147    if (fread(buf, 1, len, f) != len)
148	return NULL;
149    if (fgetc(f) != (int)'\n')
150	return NULL;
151
152    buf[len] = 0;
153    return newstr(buf);
154}
155
156/*
157 * Write a netstring.
158 */
159void
160write_netstring(FILE* f, const char* s)
161{
162    if (!s)
163	s = "";
164    fprintf(f, "%lu\t%s\n", strlen(s), s);
165}
166
167void
168hcache_init()
169{
170    HCACHEDATA  cachedata, *c;
171    FILE		*f;
172    const char	*version;
173    int			header_count = 0;
174    const char*	hcachename;
175
176    hcachehash = hashinit (sizeof (HCACHEDATA), "hcache");
177
178    if (! (hcachename = cache_name()))
179	return;
180
181    if (! (f = fopen (hcachename, "rb" )))
182	return;
183
184    version = read_netstring(f);
185    if (!version || strcmp(version, CACHE_FILE_VERSION)) {
186	fclose(f);
187	return;
188    }
189
190    while (1)
191    {
192	const char* record_type;
193	const char *time_str;
194	const char *age_str;
195	const char *includes_count_str;
196	const char *hdrscan_count_str;
197	int i, count;
198	LIST *l;
199
200	record_type = read_netstring(f);
201	if (!record_type) {
202	    fprintf(stderr, "invalid %s\n", hcachename);
203	    goto bail;
204	}
205	if (!strcmp(record_type, CACHE_RECORD_END)) {
206	    break;
207	}
208	if (strcmp(record_type, CACHE_RECORD_HEADER)) {
209	    fprintf(stderr, "invalid %s with record separator <%s>\n",
210		    hcachename, record_type ? record_type : "<null>");
211	    goto bail;
212	}
213
214	c = &cachedata;
215
216	c->boundname = read_netstring(f);
217	time_str = read_netstring(f);
218	age_str = read_netstring(f);
219	includes_count_str = read_netstring(f);
220
221	if (!c->boundname || !time_str || !age_str
222	    || !includes_count_str)
223	{
224	    fprintf(stderr, "invalid %s\n", hcachename);
225	    goto bail;
226	}
227
228	c->time = atoi(time_str);
229	c->age = atoi(age_str) + 1;
230
231	count = atoi(includes_count_str);
232	for (l = 0, i = 0; i < count; i++) {
233	    const char* s = read_netstring(f);
234	    if (!s) {
235		fprintf(stderr, "invalid %s\n", hcachename);
236		goto bail;
237	    }
238	    l = list_new(l, s, 1);
239	}
240	c->includes = l;
241
242	hdrscan_count_str = read_netstring(f);
243	if (!includes_count_str) {
244	    list_free(c->includes);
245	    fprintf(stderr, "invalid %s\n", hcachename);
246	    goto bail;
247	}
248
249	count = atoi(hdrscan_count_str);
250	for (l = 0, i = 0; i < count; i++) {
251	    const char* s = read_netstring(f);
252	    if (!s) {
253		fprintf(stderr, "invalid %s\n", hcachename);
254		goto bail;
255	    }
256	    l = list_new(l, s, 1);
257	}
258	c->hdrscan = l;
259
260	if (!hashenter(hcachehash, (HASHDATA **)&c)) {
261	    fprintf(stderr, "can't insert header cache item, bailing on %s\n",
262		    hcachename);
263	    goto bail;
264	}
265
266	c->next = hcachelist;
267	hcachelist = c;
268
269	header_count++;
270    }
271
272    if (DEBUG_HEADER) {
273	printf("hcache read from file %s\n", hcachename);
274    }
275
276 bail:
277    fclose(f);
278}
279
280void
281hcache_done()
282{
283    FILE		*f;
284    HCACHEDATA  *c;
285    int 		header_count = 0;
286    const char*	hcachename;
287    int			maxage;
288
289    if (!hcachehash)
290	return;
291
292    if (! (hcachename = cache_name()))
293	return;
294
295    if (! (f = fopen (hcachename, "wb" )))
296	return;
297
298    maxage = cache_maxage();
299
300    /* print out the version */
301    write_netstring(f, CACHE_FILE_VERSION);
302
303    c = hcachelist;
304    for (c = hcachelist; c; c = c->next) {
305	LIST	*l;
306	char time_str[30];
307	char age_str[30];
308	char includes_count_str[30];
309	char hdrscan_count_str[30];
310
311	if (maxage == 0)
312	    c->age = 0;
313	else if (c->age > maxage)
314	    continue;
315
316	sprintf(includes_count_str, "%u", list_length(c->includes));
317	sprintf(hdrscan_count_str, "%u", list_length(c->hdrscan));
318	sprintf(time_str, "%lu", c->time);
319	sprintf(age_str, "%u", c->age);
320
321	write_netstring(f, CACHE_RECORD_HEADER);
322	write_netstring(f, c->boundname);
323	write_netstring(f, time_str);
324	write_netstring(f, age_str);
325	write_netstring(f, includes_count_str);
326	for (l = c->includes; l; l = list_next(l)) {
327	    write_netstring(f, l->string);
328	}
329	write_netstring(f, hdrscan_count_str);
330	for (l = c->hdrscan; l; l = list_next(l)) {
331	    write_netstring(f, l->string);
332	}
333	fputs("\n", f);
334	header_count++;
335    }
336    write_netstring(f, CACHE_RECORD_END);
337
338    if (DEBUG_HEADER) {
339	printf("hcache written to %s.   %d dependencies, %.0f%% hit rate\n",
340	       hcachename, header_count,
341	       queries ? 100.0 * hits / queries : 0);
342    }
343
344    fclose (f);
345}
346
347LIST *
348hcache (TARGET *t, LIST *hdrscan)
349{
350    HCACHEDATA  cachedata, *c = &cachedata;
351    LIST 	*l = 0;
352    char _normalizedPath[PATH_MAX];
353    char *normalizedPath = normalize_path(t->boundname, _normalizedPath,
354    	sizeof(_normalizedPath));
355
356    ++queries;
357
358    if (normalizedPath)
359	c->boundname = normalizedPath;
360    else
361	c->boundname = t->boundname;
362
363    if (hashcheck (hcachehash, (HASHDATA **) &c))
364    {
365	if (c->time == t->time)
366	{
367	    LIST *l1 = hdrscan, *l2 = c->hdrscan;
368	    while (l1 && l2) {
369		if (l1->string != l2->string) {
370		    l1 = NULL;
371		} else {
372		    l1 = list_next(l1);
373		    l2 = list_next(l2);
374		}
375	    }
376	    if (l1 || l2) {
377		if (DEBUG_HEADER)
378		    printf("HDRSCAN out of date in cache for %s\n",
379			   t->boundname);
380
381		printf("HDRSCAN out of date for %s\n", t->boundname);
382		printf(" real  : ");
383		list_print(hdrscan);
384		printf("\n cached: ");
385		list_print(c->hdrscan);
386		printf("\n");
387
388		list_free(c->includes);
389		list_free(c->hdrscan);
390		c->includes = 0;
391		c->hdrscan = 0;
392	    } else {
393		if (DEBUG_HEADER)
394		    printf ("using header cache for %s\n", t->boundname);
395		c->age = 0;
396		++hits;
397		l = list_copy (0, c->includes);
398		return l;
399	    }
400	} else {
401	    if (DEBUG_HEADER)
402	        printf ("header cache out of date for %s\n", t->boundname);
403	    list_free (c->includes);
404	    list_free(c->hdrscan);
405	    c->includes = 0;
406	    c->hdrscan = 0;
407	}
408    } else {
409	if (hashenter (hcachehash, (HASHDATA **)&c)) {
410	    c->boundname = newstr (c->boundname);
411	    c->next = hcachelist;
412	    hcachelist = c;
413	}
414    }
415
416    /* 'c' points at the cache entry.  Its out of date. */
417
418    l = headers1 (t->boundname, hdrscan);
419
420    c->time = t->time;
421    c->age = 0;
422    c->includes = list_copy (0, l);
423    c->hdrscan = list_copy(0, hdrscan);
424
425    return l;
426}
427
428#endif
429