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}