1/* $NetBSD: mkdep.c,v 1.47 2021/08/20 05:45:19 rillig Exp $ */
2
3/*-
4 * Copyright (c) 1999 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Matthias Scheler.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#if HAVE_NBTOOL_CONFIG_H
33#include "nbtool_config.h"
34#endif
35
36#include <sys/cdefs.h>
37#if !defined(lint)
38__COPYRIGHT("@(#) Copyright (c) 1999 The NetBSD Foundation, Inc.\
39 All rights reserved.");
40__RCSID("$NetBSD: mkdep.c,v 1.47 2021/08/20 05:45:19 rillig Exp $");
41#endif /* not lint */
42
43#include <sys/mman.h>
44#include <sys/param.h>
45#include <sys/wait.h>
46#include <ctype.h>
47#include <err.h>
48#include <fcntl.h>
49#include <getopt.h>
50#include <locale.h>
51#include <paths.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <unistd.h>
56
57#include "findcc.h"
58
59typedef struct opt opt_t;
60struct opt {
61	opt_t	*left;
62	opt_t	*right;
63	int	len;
64	int	count;
65	char	name[4];
66};
67
68typedef struct suff_list {
69	size_t	len;
70	char	*suff;
71	struct suff_list *next;
72} suff_list_t;
73
74/* tree of includes for -o processing */
75static opt_t *opt;
76static int width;
77static int verbose;
78
79#define DEFAULT_PATH		_PATH_DEFPATH
80#define DEFAULT_FILENAME	".depend"
81
82static void save_for_optional(const char *, const char *);
83static size_t write_optional(int, opt_t *, size_t);
84
85__dead static void
86usage(void)
87{
88	(void)fprintf(stderr,
89	    "usage: %s [-aDdiopqv] [-f file] [-P prefix] [-s suffixes] "
90	    "-- [flags] file ...\n",
91	    getprogname());
92	exit(EXIT_FAILURE);
93}
94
95static int
96run_cc(int argc, char **argv, const char **fname)
97{
98	const char *CC;
99	const char *tmpdir;
100	char * volatile pathname;
101	static char tmpfilename[MAXPATHLEN];
102	char **args;
103	int tmpfd;
104	pid_t pid, cpid;
105	int status;
106
107	if ((CC = getenv("CC")) == NULL)
108		CC = DEFAULT_CC;
109	if ((pathname = findcc(CC)) == NULL)
110		if (!setenv("PATH", DEFAULT_PATH, 1))
111			pathname = findcc(CC);
112	if (pathname == NULL)
113		err(EXIT_FAILURE, "%s: not found", CC);
114	if ((args = malloc((argc + 3) * sizeof(char *))) == NULL)
115		err(EXIT_FAILURE, "malloc");
116
117	args[0] = __UNCONST(CC);
118	args[1] = __UNCONST("-M");
119	(void)memcpy(&args[2], argv, (argc + 1) * sizeof(char *));
120
121	if ((tmpdir = getenv("TMPDIR")) == NULL)
122		tmpdir = _PATH_TMP;
123	(void)snprintf(tmpfilename, sizeof (tmpfilename), "%s/%s", tmpdir,
124	    "mkdepXXXXXX");
125	if ((tmpfd = mkstemp(tmpfilename)) < 0)
126		err(EXIT_FAILURE,  "Unable to create temporary file %s",
127		    tmpfilename);
128	(void)unlink(tmpfilename);
129	*fname = tmpfilename;
130
131	if (verbose) {
132		char **a;
133		for (a = args; *a; a++)
134			printf("%s ", *a);
135		printf("\n");
136	}
137
138	switch (cpid = vfork()) {
139	case 0:
140		(void)dup2(tmpfd, STDOUT_FILENO);
141		(void)close(tmpfd);
142
143		(void)execv(pathname, args);
144		_exit(EXIT_FAILURE);
145		/* NOTREACHED */
146
147	case -1:
148		err(EXIT_FAILURE, "unable to fork");
149	}
150
151	free(pathname);
152	free(args);
153
154	while (((pid = wait(&status)) != cpid) && (pid >= 0))
155		continue;
156
157	if (status)
158		errx(EXIT_FAILURE, "compile failed.");
159
160	return tmpfd;
161}
162
163static const char *
164read_fname(void)
165{
166	static char *fbuf;
167	static int fbuflen;
168	int len, ch;
169
170	for (len = 0; (ch = getchar()) != EOF; len++) {
171		if (isspace(ch)) {
172			if (len != 0)
173				break;
174			len--;
175			continue;
176		}
177		if (len >= fbuflen - 1) {
178			fbuf = realloc(fbuf, fbuflen += 32);
179			if (fbuf == NULL)
180				err(EXIT_FAILURE, "no memory");
181		}
182		fbuf[len] = ch;
183	}
184	if (len == 0)
185		return NULL;
186	fbuf[len] = 0;
187	return fbuf;
188}
189
190static struct option longopt[] = {
191	{ "sysroot", 1, NULL, 'R' },
192	{ NULL, 0, NULL, '\0' },
193};
194
195static void
196addsuff(suff_list_t **l, const char *s, size_t len)
197{
198	suff_list_t *p = calloc(1, sizeof(*p));
199	if (p == NULL)
200		err(1, "calloc");
201	p->suff = malloc(len + 1);
202	if (p->suff == NULL)
203		err(1, "malloc");
204	memcpy(p->suff, s, len);
205	p->suff[len] = '\0';
206	p->len = len;
207	p->next = *l;
208	*l = p;
209}
210
211int
212main(int argc, char **argv)
213{
214	int 	aflag, dflag, iflag, oflag, qflag;
215	const char *filename;
216	int	dependfile;
217	char	*buf, *lim, *ptr, *line, *suf, *colon, *eol;
218	int	ok_ind, ch;
219	size_t	sz;
220	int	fd;
221	size_t  slen;
222	const char *fname;
223	const char *prefix = NULL;
224	const char *suffixes = NULL, *s;
225	suff_list_t *suff_list = NULL, *sl;
226
227	suf = NULL;		/* XXXGCC -Wuninitialized [sun2] */
228	sl = NULL;		/* XXXGCC -Wuninitialized [sun2] */
229
230	setlocale(LC_ALL, "");
231	setprogname(argv[0]);
232
233	aflag = O_WRONLY | O_APPEND | O_CREAT | O_TRUNC;
234	dflag = 0;
235	iflag = 0;
236	oflag = 0;
237	qflag = 0;
238	filename = DEFAULT_FILENAME;
239	dependfile = -1;
240
241	opterr = 0;	/* stop getopt() bleating about errors. */
242	for (;;) {
243		ok_ind = optind;
244		ch = getopt_long(argc, argv, "aDdf:ioP:pqRs:v", longopt, NULL);
245		switch (ch) {
246		case -1:
247			ok_ind = optind;
248			break;
249		case 'a':	/* Append to output file */
250			aflag &= ~O_TRUNC;
251			continue;
252		case 'D':	/* Process *.d files (don't run cc -M) */
253			dflag = 2;	/* Read names from stdin */
254			opterr = 1;
255			continue;
256		case 'd':	/* Process *.d files (don't run cc -M) */
257			dflag = 1;
258			opterr = 1;
259			continue;
260		case 'f':	/* Name of output file */
261			filename = optarg;
262			continue;
263		case 'i':
264			iflag = 1;
265			continue;
266		case 'o':	/* Mark dependent files .OPTIONAL */
267			oflag = 1;
268			continue;
269		case 'P':	/* Prefix for each target filename */
270			prefix = optarg;
271			continue;
272		case 'p':	/* Program mode (x.o: -> x:) */
273			suffixes = "";
274			continue;
275		case 'q':	/* Quiet */
276			qflag = 1;
277			continue;
278		case 'R':
279			/* sysroot = optarg */
280			continue;
281		case 's':	/* Suffix list */
282			suffixes = optarg;
283			continue;
284		case 'v':
285			verbose = 1;
286			continue;
287		default:
288			if (dflag)
289				usage();
290			/* Unknown arguments are passed to "${CC} -M" */
291			break;
292		}
293		break;
294	}
295
296	argc -= ok_ind;
297	argv += ok_ind;
298	if ((argc == 0 && !dflag) || (argc != 0 && dflag == 2))
299		usage();
300
301	if (suffixes != NULL) {
302		if (*suffixes) {
303			for (s = suffixes; (sz = strcspn(s, ", ")) != 0;) {
304				addsuff(&suff_list, s, sz);
305				s += sz;
306				while (*s && strchr(", ", *s))
307					s++;
308			}
309		} else
310			addsuff(&suff_list, "", 0);
311	}
312
313	dependfile = open(filename, aflag, 0666);
314	if (dependfile == -1)
315		goto wrerror;
316
317	while (dflag == 2 || *argv != NULL) {
318		if (dflag) {
319			if (dflag == 2) {
320				fname = read_fname();
321				if (fname == NULL)
322					break;
323			} else
324				fname = *argv++;
325			if (iflag) {
326				if (dprintf(dependfile, ".-include \"%s\"\n",
327				    fname) < 0)
328					goto wrerror;
329				continue;
330			}
331			fd = open(fname, O_RDONLY, 0);
332			if (fd == -1) {
333				if (!qflag)
334					warn("ignoring %s", fname);
335				continue;
336			}
337		} else {
338			fd = run_cc(argc, argv, &fname);
339			/* consume all args... */
340			argv += argc;
341		}
342
343		sz = lseek(fd, 0, SEEK_END);
344		if (sz == 0) {
345			close(fd);
346			continue;
347		}
348		buf = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
349		close(fd);
350
351		if (buf == MAP_FAILED)
352			err(EXIT_FAILURE, "unable to mmap file %s", fname);
353		lim = buf + sz - 1;
354
355		/* Remove leading "./" from filenames */
356		for (ptr = buf; ptr < lim; ptr++) {
357			if (ptr[1] != '.' || ptr[2] != '/'
358			    || !isspace((unsigned char)ptr[0]))
359				continue;
360			ptr[1] = ' ';
361			ptr[2] = ' ';
362		}
363
364		for (line = eol = buf; eol <= lim;) {
365			while (eol <= lim && *eol++ != '\n')
366				/* Find end of this line */
367				continue;
368			if (line == eol - 1) {
369				/* empty line - ignore */
370				line = eol;
371				continue;
372			}
373			if (eol[-2] == '\\')
374				/* Assemble continuation lines */
375				continue;
376			for (colon = line; *colon != ':'; colon++) {
377				if (colon >= eol) {
378					colon = NULL;
379					break;
380				}
381			}
382			if (isspace((unsigned char)*line) || colon == NULL) {
383				/* No dependency - just transcribe line */
384				if (write(dependfile, line, eol - line) < 0)
385					goto wrerror;
386				line = eol;
387				continue;
388			}
389			if (suff_list != NULL) {
390				/* Find the .o: */
391				/* First allow for any whitespace */
392				for (suf = colon; suf > buf; suf--) {
393					if (!isspace((unsigned char)suf[-1]))
394						break;
395				}
396				if (suf == buf)
397					errx(EXIT_FAILURE,
398					    "Corrupted file `%s'", fname);
399				/* Then look for any valid suffix */
400				for (sl = suff_list; sl != NULL;
401				    sl = sl->next) {
402					if (sl->len && buf <= suf - sl->len &&
403					    !memcmp(suf - sl->len, sl->suff,
404						    sl->len))
405						break;
406				}
407				/*
408				 * Not found, check for .o, since the
409				 * original file will have it.
410				 */
411				if (sl == NULL) {
412					if (memcmp(suf - 2, ".o", 2) == 0)
413						slen = 2;
414					else
415						slen = 0;
416				} else
417					slen = sl->len;
418			}
419			if (suff_list != NULL && slen != 0) {
420				suf -= slen;
421				for (sl = suff_list; sl != NULL; sl = sl->next)
422				{
423					if (sl != suff_list)
424						if (write(dependfile, " ", 1)
425						    < 0)
426							goto wrerror;
427					if (prefix != NULL)
428						if (write(dependfile, prefix,
429						    strlen(prefix)) < 0)
430							goto wrerror;
431					if (write(dependfile, line,
432					    suf - line) < 0)
433						goto wrerror;
434					if (write(dependfile, sl->suff,
435					    sl->len) < 0)
436						goto wrerror;
437				}
438				if (write(dependfile, colon, eol - colon) < 0)
439					goto wrerror;
440			} else {
441				if (prefix != NULL)
442					if (write(dependfile, prefix,
443					    strlen(prefix)) < 0)
444						goto wrerror;
445				if (write(dependfile, line, eol - line) < 0)
446					goto wrerror;
447			}
448
449			if (oflag)
450				save_for_optional(colon + 1, eol);
451			line = eol;
452		}
453		munmap(buf, sz);
454	}
455
456	if (oflag && opt != NULL) {
457		if (write(dependfile, ".OPTIONAL:", 10) < 0)
458			goto wrerror;
459		width = 9;
460		sz = write_optional(dependfile, opt, 0);
461		if (sz == (size_t)-1)
462			goto wrerror;
463		/* 'depth' is about 39 for an i386 kernel */
464		/* fprintf(stderr, "Recursion depth %d\n", sz); */
465	}
466	close(dependfile);
467
468	exit(EXIT_SUCCESS);
469wrerror:
470	err(EXIT_FAILURE, "unable to %s to file %s",
471	    aflag & O_TRUNC ? "write" : "append", filename);
472}
473
474
475/*
476 * Only save each file once - the kernel .depend is 3MB and there is
477 * no point doubling its size.
478 * The data seems to be 'random enough' so the simple binary tree
479 * only has a reasonable depth.
480 */
481static void
482save_for_optional(const char *start, const char *limit)
483{
484	opt_t **l, *n;
485	const char *name, *end;
486	int c;
487
488	while (start < limit && strchr(" \t\n\\", *start))
489		start++;
490	for (name = start; ; name = end) {
491		while (name < limit && strchr(" \t\n\\", *name))
492			name++;
493		for (end = name; end < limit && !strchr(" \t\n\\", *end);)
494			end++;
495		if (name >= limit)
496			break;
497		if (end[-1] == 'c' && end[-2] == '.' && name == start)
498			/* ignore dependency on the files own .c */
499			continue;
500		for (l = &opt;;) {
501			n = *l;
502			if (n == NULL) {
503				n = malloc(sizeof *n + (end - name));
504				n->left = n->right = 0;
505				n->len = end - name;
506				n->count = 1;
507				n->name[0] = ' ';
508				memcpy(n->name + 1, name, end - name);
509				*l = n;
510				break;
511			}
512			c = (end - name) - n->len;
513			if (c == 0)
514				c = memcmp(n->name + 1, name, (end - name));
515			if (c == 0) {
516				/* Duplicate */
517				n->count++;
518				break;
519			}
520			if (c < 0)
521				l = &n->left;
522			else
523				l = &n->right;
524		}
525	}
526}
527
528static size_t
529write_optional(int fd, opt_t *node, size_t depth)
530{
531	size_t d1 = ++depth;
532
533	if (node->left)
534		d1 = write_optional(fd, node->left, d1);
535	if (width > 76 - node->len) {
536		if (write(fd, " \\\n ", 4) < 0)
537			return (size_t)-1;
538		width = 1;
539	}
540	width += 1 + node->len;
541	if (write(fd, node->name, 1 + node->len) < 0)
542		return (size_t)-1;
543	if (node->right)
544		depth = write_optional(fd, node->right, depth);
545	return d1 > depth ? d1 : depth;
546}
547