main shinobi / src / util.c
  1#include "internal.h"
  2
  3#include <stdio.h>
  4#include <stdlib.h>
  5#include <string.h>
  6#include <ctype.h>
  7#include <unistd.h>
  8
  9/* shared utility functions */
 10
 11const char *
 12shinmode_name(enum ShinMode mode)
 13{
 14	switch (mode) {
 15	case MODE_GNU:
 16		return "gnu";
 17	case MODE_POSIX_2024:
 18		return "posix2024";
 19	case MODE_POSIX_2008:
 20		return "posix2008";
 21	}
 22	return "gnu";
 23}
 24
 25int
 26shinmode_parse(const char *s, enum ShinMode *out)
 27{
 28	if (strcmp(s, "gnu") == 0) {
 29#if SHIN_WITH_GNU
 30		*out = MODE_GNU;
 31		return 0;
 32#else
 33		/* gnu support was not compiled in */
 34		return -1;
 35#endif
 36	}
 37	if (strcmp(s, "posix2024") == 0 || strcmp(s, "posix") == 0) {
 38		*out = MODE_POSIX_2024;
 39		return 0;
 40	}
 41	if (strcmp(s, "posix2008") == 0) {
 42		*out = MODE_POSIX_2008;
 43		return 0;
 44	}
 45	return -1;
 46}
 47
 48static const char *progname = "shin";
 49
 50void
 51set_progname(const char *name)
 52{
 53	progname = name;
 54}
 55
 56void *
 57xmalloc(size_t n)
 58{
 59	void *p;
 60
 61	p = malloc(n ? n : 1);
 62	if (!p) {
 63		fprintf(stderr, "out of memory\n");
 64		exit(1);
 65	}
 66	return p;
 67}
 68
 69void *
 70xrealloc(void *p, size_t n)
 71{
 72	void *q;
 73
 74	q = realloc(p, n ? n : 1);
 75	if (!q) {
 76		fprintf(stderr, "out of memory\n");
 77		exit(1);
 78	}
 79	return q;
 80}
 81
 82char *
 83xstrndup(const char *s, size_t n)
 84{
 85	char *p;
 86
 87	p = xmalloc(n + 1);
 88	memcpy(p, s, n);
 89	p[n] = 0;
 90	return p;
 91}
 92
 93char *
 94xstrdup(const char *s)
 95{
 96	return xstrndup(s, strlen(s));
 97}
 98
 99/* bump-pointer arena: alloc grows a slab chain, arena_free drops all slabs */
100
101struct ArenaBlock {
102	struct ArenaBlock *next;
103	size_t cap;
104	size_t pos;
105	/* data follows in memory */
106};
107
108#define ARENA_DEFAULT_BLOCK (64 * 1024)
109#define ARENA_ALIGN sizeof(void *)
110
111void
112arena_init(struct Arena *a, size_t block_size)
113{
114	a->head = 0;
115	a->block_size = block_size ? block_size : ARENA_DEFAULT_BLOCK;
116}
117
118void *
119arena_alloc(struct Arena *a, size_t n)
120{
121	struct ArenaBlock *b;
122	size_t aligned;
123	char *p;
124
125	aligned = (n + ARENA_ALIGN - 1) & ~(ARENA_ALIGN - 1);
126	b = a->head;
127	if (!b || b->pos + aligned > b->cap) {
128		size_t bsz;
129
130		bsz = aligned > a->block_size ? aligned : a->block_size;
131		b = xmalloc(sizeof(*b) + bsz);
132		b->cap = bsz;
133		b->pos = 0;
134		b->next = a->head;
135		a->head = b;
136	}
137	p = (char *)(b + 1) + b->pos;
138	b->pos += aligned;
139	return p;
140}
141
142char *
143arena_strndup(struct Arena *a, const char *s, size_t n)
144{
145	char *p;
146
147	p = arena_alloc(a, n + 1);
148	memcpy(p, s, n);
149	p[n] = 0;
150	return p;
151}
152
153char *
154arena_strdup(struct Arena *a, const char *s)
155{
156	return arena_strndup(a, s, strlen(s));
157}
158
159void
160arena_free(struct Arena *a)
161{
162	struct ArenaBlock *b, *next;
163
164	for (b = a->head; b; b = next) {
165		next = b->next;
166		free(b);
167	}
168	a->head = 0;
169}
170
171/* intern table: == is enough to compare, strings live til exit */
172
173#define INTERN_INIT_CAP 512
174
175struct InternEntry {
176	const char *s;
177	size_t h;
178};
179
180static struct InternEntry *intern_table;
181static size_t intern_n;
182static size_t intern_cap;
183
184static size_t
185strhash(const char *s)
186{
187	/* fnv-1a */
188	size_t h = 2166136261u;
189	while (*s) {
190		h ^= (unsigned char)*s++;
191		h *= 16777619u;
192	}
193	return h;
194}
195
196static void
197interngrow(void)
198{
199	size_t i, newcap;
200	struct InternEntry *newtbl;
201
202	newcap = intern_cap ? intern_cap * 2 : INTERN_INIT_CAP;
203	newtbl = xmalloc(newcap * sizeof(newtbl[0]));
204	memset(newtbl, 0, newcap * sizeof(newtbl[0]));
205	for (i = 0; i < intern_cap; i++) {
206		size_t j;
207
208		if (!intern_table[i].s)
209			continue;
210		j = intern_table[i].h & (newcap - 1);
211		while (newtbl[j].s)
212			j = (j + 1) & (newcap - 1);
213		newtbl[j] = intern_table[i];
214	}
215	free(intern_table);
216	intern_table = newtbl;
217	intern_cap = newcap;
218}
219
220const char *
221intern(const char *s)
222{
223	size_t h, i;
224
225	if (!intern_cap || intern_n * 3 >= intern_cap * 2)
226		interngrow();
227	h = strhash(s);
228	i = h & (intern_cap - 1);
229	for (;;) {
230		if (!intern_table[i].s) {
231			intern_table[i].s = xstrdup(s);
232			intern_table[i].h = h;
233			intern_n++;
234			return intern_table[i].s;
235		}
236		if (intern_table[i].h == h && strcmp(intern_table[i].s, s) == 0)
237			return intern_table[i].s;
238		i = (i + 1) & (intern_cap - 1);
239	}
240}
241
242char *
243readfile(const char *path)
244{
245	FILE *fp;
246	long n;
247	char *buf;
248
249	fp = fopen(path, "rb");
250	if (!fp)
251		return 0;
252	if (fseek(fp, 0, SEEK_END) < 0) {
253		fclose(fp);
254		return 0;
255	}
256	n = ftell(fp);
257	if (n < 0) {
258		fclose(fp);
259		return 0;
260	}
261	if (fseek(fp, 0, SEEK_SET) < 0) {
262		fclose(fp);
263		return 0;
264	}
265	buf = xmalloc((size_t)n + 1);
266	if (fread(buf, 1, (size_t)n, fp) != (size_t)n) {
267		fclose(fp);
268		free(buf);
269		return 0;
270	}
271	buf[n] = 0;
272	fclose(fp);
273	return buf;
274}
275
276int
277loadmakefile(const char *path, char **path_out, char **src_out)
278{
279	static const char *const defaults[] = {
280	    "GNUmakefile",
281	    "makefile",
282	    "Makefile",
283	    0,
284	};
285	size_t i;
286	char *src;
287	char *fullpath;
288
289	src = 0;
290	fullpath = 0;
291	if (path) {
292		fullpath = xstrdup(path);
293		src = readfile(fullpath);
294	} else {
295		for (i = 0; defaults[i]; i++) {
296			src = readfile(defaults[i]);
297			if (!src)
298				continue;
299			fullpath = xstrdup(defaults[i]);
300			break;
301		}
302	}
303	if (!src) {
304		free(fullpath);
305		return -1;
306	}
307	*path_out = fullpath;
308	*src_out = src;
309	return 0;
310}
311
312char *
313appendassigns(char *src, const struct StrList *assigns)
314{
315	size_t i, len, extra;
316	char *out;
317
318	if (!assigns->n)
319		return src;
320	len = strlen(src);
321	extra = len > 0 && src[len - 1] != '\n' ? 1 : 0;
322	for (i = 0; i < assigns->n; i++)
323		extra += strlen(assigns->v[i]) + 1;
324	out = xmalloc(len + extra + 1);
325	memcpy(out, src, len);
326	extra = len;
327	if (extra > 0 && out[extra - 1] != '\n')
328		out[extra++] = '\n';
329	for (i = 0; i < assigns->n; i++) {
330		size_t n;
331
332		n = strlen(assigns->v[i]);
333		memcpy(out + extra, assigns->v[i], n);
334		extra += n;
335		out[extra++] = '\n';
336	}
337	out[extra] = 0;
338	free(src);
339	return out;
340}
341
342char *
343getcwddup(void)
344{
345	size_t size;
346	char *buf;
347
348	size = 128;
349	for (;;) {
350		buf = xmalloc(size);
351		if (getcwd(buf, size))
352			return buf;
353		free(buf);
354		size *= 2;
355	}
356}
357
358char *
359joinpath(const char *dir, const char *name)
360{
361	size_t ndir, nname;
362	char *out;
363
364	if (!name || !name[0])
365		return xstrdup(dir);
366	if (name[0] == '/')
367		return xstrdup(name);
368	ndir = strlen(dir);
369	nname = strlen(name);
370	out = xmalloc(ndir + 1 + nname + 1);
371	memcpy(out, dir, ndir);
372	out[ndir] = '/';
373	memcpy(out + ndir + 1, name, nname);
374	out[ndir + 1 + nname] = 0;
375	return out;
376}
377
378char *
379normpath(const char *path)
380{
381	size_t i, n, parts_n, outlen;
382	int absolute;
383	char **parts;
384	char *out;
385
386	absolute = path[0] == '/';
387	n = strlen(path);
388	parts = xmalloc((n + 1) * sizeof(parts[0]));
389	parts_n = 0;
390	for (i = 0; i < n;) {
391		size_t start, len;
392
393		while (path[i] == '/')
394			i++;
395		start = i;
396		while (path[i] && path[i] != '/')
397			i++;
398		len = i - start;
399		if (!len)
400			continue;
401		if (len == 1 && path[start] == '.')
402			continue;
403		if (len == 2 && path[start] == '.' && path[start + 1] == '.') {
404			if (parts_n > 0 && strcmp(parts[parts_n - 1], "..") != 0) {
405				free(parts[--parts_n]);
406				continue;
407			}
408			if (!absolute)
409				parts[parts_n++] = xstrndup(path + start, len);
410			continue;
411		}
412		parts[parts_n++] = xstrndup(path + start, len);
413	}
414	if (absolute && parts_n == 0) {
415		free(parts);
416		return xstrdup("/");
417	}
418	if (!absolute && parts_n == 0) {
419		free(parts);
420		return xstrdup(".");
421	}
422	outlen = absolute ? 1 : 0;
423	for (i = 0; i < parts_n; i++)
424		outlen += strlen(parts[i]) + 1;
425	out = xmalloc(outlen + 1);
426	n = 0;
427	if (absolute)
428		out[n++] = '/';
429	for (i = 0; i < parts_n; i++) {
430		size_t len;
431
432		if (n > 0 && out[n - 1] != '/')
433			out[n++] = '/';
434		len = strlen(parts[i]);
435		memcpy(out + n, parts[i], len);
436		n += len;
437		free(parts[i]);
438	}
439	out[n] = 0;
440	free(parts);
441	return out;
442}
443
444char *
445joinstrs(const struct StrList *list, const char *sep)
446{
447	size_t i, seplen, total, pos;
448	char *s;
449
450	if (!list->n)
451		return xstrdup("");
452	seplen = strlen(sep);
453	total = 0;
454	for (i = 0; i < list->n; i++)
455		total += strlen(list->v[i]);
456	total += seplen * (list->n - 1);
457	s = xmalloc(total + 1);
458	pos = 0;
459	for (i = 0; i < list->n; i++) {
460		size_t n;
461
462		if (i > 0) {
463			memcpy(s + pos, sep, seplen);
464			pos += seplen;
465		}
466		n = strlen(list->v[i]);
467		memcpy(s + pos, list->v[i], n);
468		pos += n;
469	}
470	s[pos] = 0;
471	return s;
472}
473
474void
475addstr(struct StrList *list, const char *s)
476{
477	if (list->n >= list->cap) {
478		list->cap = list->cap ? list->cap * 2 : 4;
479		list->v = xrealloc(list->v, list->cap * sizeof(list->v[0]));
480	}
481	list->v[list->n++] = xstrdup(s);
482}
483
484int
485hasword(const struct StrList *list, const char *word)
486{
487	size_t i;
488
489	for (i = 0; i < list->n; i++) {
490		if (strcmp(list->v[i], word) == 0)
491			return 1;
492	}
493	return 0;
494}
495
496int
497targetownedby(const struct Target *t, const char *owner)
498{
499	if (!t)
500		return 0;
501	if (!owner || !owner[0])
502		return !t->owner || !t->owner[0];
503	return t->owner && strcmp(t->owner, owner) == 0;
504}
505
506const struct Target *
507defaulttarget(const struct Graph *graph, const char *owner)
508{
509	const struct Target *all;
510	const char *base;
511	char *ownedall;
512	size_t i;
513
514	ownedall = 0;
515	if (owner && owner[0]) {
516		ownedall = cat3(owner, "/", "all");
517		all = findctarget(graph, ownedall);
518		free(ownedall);
519	} else {
520		all = findctarget(graph, "all");
521	}
522	if (targetownedby(all, owner))
523		return all;
524	for (i = 0; i < graph->n; i++) {
525		if (!targetownedby(&graph->v[i], owner))
526			continue;
527		base = strrchr(graph->v[i].name, '/');
528		base = base ? base + 1 : graph->v[i].name;
529		if (base[0] == '.')
530			continue;
531		if (strcmp(base, "_PHONY") == 0)
532			continue;
533		if (graph->v[i].name == intern("clean") || graph->v[i].name == intern("fmt"))
534			continue;
535		if (graph->v[i].recipes.n > 0 || graph->v[i].prereqs.n > 0 ||
536		    graph->v[i].impprereqs.n > 0 || graph->v[i].order_only.n > 0)
537			return &graph->v[i];
538	}
539	return 0;
540}
541
542char *
543cat3(const char *a, const char *b, const char *c)
544{
545	size_t na, nb, nc;
546	char *s;
547
548	na = strlen(a);
549	nb = strlen(b);
550	nc = strlen(c);
551	s = xmalloc(na + nb + nc + 1);
552	memcpy(s, a, na);
553	memcpy(s + na, b, nb);
554	memcpy(s + na + nb, c, nc);
555	s[na + nb + nc] = 0;
556	return s;
557}
558
559void
560addnode(struct NodeList *list, struct Node node)
561{
562	if (list->n >= list->cap) {
563		list->cap = list->cap ? list->cap * 2 : 4;
564		list->v = xrealloc(list->v, list->cap * sizeof(list->v[0]));
565	}
566	list->v[list->n++] = node;
567}
568
569void
570addwords(struct StrList *dest, const struct StrList *src)
571{
572	size_t i;
573
574	if (dest->n + src->n > dest->cap) {
575		dest->cap = dest->n + src->n;
576		dest->v = xrealloc(dest->v, dest->cap * sizeof(dest->v[0]));
577	}
578	for (i = 0; i < src->n; i++)
579		dest->v[dest->n++] = xstrdup(src->v[i]);
580}
581
582void
583adduniqwords(struct StrList *dest, const struct StrList *src)
584{
585	size_t i;
586
587	for (i = 0; i < src->n; i++) {
588		if (hasword(dest, src->v[i]))
589			continue;
590		addstr(dest, src->v[i]);
591	}
592}
593
594void
595addrecipe(struct RecipeList *dest, const char *raw)
596{
597	struct Recipe *r;
598	const char *s;
599	size_t n;
600
601	s = raw;
602	while (*s == ' ' || *s == '\t')
603		s++;
604	if (dest->n >= dest->cap) {
605		dest->cap = dest->cap ? dest->cap * 2 : 4;
606		dest->v = xrealloc(dest->v, dest->cap * sizeof(dest->v[0]));
607	}
608	r = &dest->v[dest->n++];
609	memset(r, 0, sizeof(*r));
610	while (*s == '@' || *s == '+' || *s == '-') {
611		if (*s == '@')
612			r->silent = 1;
613		else if (*s == '+')
614			r->recursive = 1;
615		else if (*s == '-')
616			r->ignore = 1;
617		s++;
618		while (*s == ' ' || *s == '\t')
619			s++;
620	}
621	n = strlen(s);
622	while (n > 0 && isspace((unsigned char)s[n - 1]))
623		n--;
624	r->body = xstrndup(s, n);
625	r->submake = parsesubmake(&r->sm, r->body);
626}
627
628void
629addrecipes(struct RecipeList *dest, const struct RecipeList *src)
630{
631	size_t i;
632
633	if (dest->n + src->n > dest->cap) {
634		dest->cap = dest->n + src->n;
635		dest->v = xrealloc(dest->v, dest->cap * sizeof(dest->v[0]));
636	}
637	for (i = 0; i < src->n; i++) {
638		dest->v[dest->n].body = xstrdup(src->v[i].body);
639		dest->v[dest->n].silent = src->v[i].silent;
640		dest->v[dest->n].ignore = src->v[i].ignore;
641		dest->v[dest->n].recursive = src->v[i].recursive;
642		dest->v[dest->n].submake = src->v[i].submake;
643		copysubmake(&dest->v[dest->n].sm, &src->v[i].sm);
644		dest->n++;
645	}
646}
647
648static const struct Target *
649findtarget0(const struct Graph *graph, const char *name)
650{
651	const char *iname;
652	size_t i;
653
654	iname = intern(name);
655	for (i = 0; i < graph->n; i++) {
656		if (graph->v[i].name == iname)
657			return &graph->v[i];
658	}
659	return 0;
660}
661
662struct Target *
663findtarget(struct Graph *graph, const char *name)
664{
665	return (struct Target *)findtarget0(graph, name);
666}
667
668const struct Target *
669findctarget(const struct Graph *graph, const char *name)
670{
671	return findtarget0(graph, name);
672}
673
674const char *
675firstprereq(const struct Target *t)
676{
677	if (t->impprereqs.n > 0)
678		return t->impprereqs.v[0];
679	if (t->prereqs.n > 0)
680		return t->prereqs.v[0];
681	return 0;
682}
683
684char *
685joinallprereqs(const struct Target *t, const char *sep)
686{
687	struct StrList list;
688	char *s;
689
690	memset(&list, 0, sizeof(list));
691	addwords(&list, &t->impprereqs);
692	addwords(&list, &t->prereqs);
693	s = joinstrs(&list, sep);
694	freestrs(&list);
695	return s;
696}
697
698size_t
699totalprereqs(const struct Target *t)
700{
701	return t->prereqs.n + t->impprereqs.n;
702}
703
704void
705freestrs(struct StrList *list)
706{
707	size_t i;
708
709	if (!list)
710		return;
711	for (i = 0; i < list->n; i++)
712		free(list->v[i]);
713	free(list->v);
714	list->v = 0;
715	list->n = 0;
716	list->cap = 0;
717}
718
719void
720freerecipes(struct RecipeList *list)
721{
722	size_t i;
723
724	if (!list)
725		return;
726	for (i = 0; i < list->n; i++)
727		free(list->v[i].body);
728	for (i = 0; i < list->n; i++)
729		freesubmake(&list->v[i].sm);
730	free(list->v);
731	list->v = 0;
732	list->n = 0;
733	list->cap = 0;
734}
735
736void
737envsetvar(struct Env *env, const char *name, char *val, int simple, enum Origin origin, int exported)
738{
739	struct Var *v;
740
741	v = findvar(env, name);
742	if (v) {
743		if ((int)origin < (int)v->origin) {
744			free(val);
745			return;
746		}
747		free(v->val);
748		v->val = val;
749		v->simple = simple;
750		v->origin = origin;
751		if (exported)
752			v->exported = 1;
753		return;
754	}
755	if (env->n >= env->cap) {
756		env->cap = env->cap ? env->cap * 2 : 4;
757		env->v = xrealloc(env->v, env->cap * sizeof(env->v[0]));
758	}
759	env->v[env->n].name = intern(name);
760	env->v[env->n].val = val;
761	env->v[env->n].simple = simple;
762	env->v[env->n].origin = origin;
763	env->v[env->n].exported = exported;
764	env->n++;
765}
766
767void
768warnlikemake(const char *path, int line, const char *msg)
769{
770	fprintf(stderr, "%s:%d: warning: %s\n", path, line, msg);
771}
772
773void
774dielikemake(const char *path, int line, const char *msg, const char *detail)
775{
776	if (path)
777		fprintf(stderr, "%s:%d: *** %s%s%s.  Stop.\n",
778		        path, line,
779		        msg,
780		        detail ? ": " : "",
781		        detail ? detail : "");
782	else
783		fprintf(stderr, "%s: *** %s%s%s.  Stop.\n",
784		        progname,
785		        msg,
786		        detail ? " " : "",
787		        detail ? detail : "");
788}