xref: /netbsd/usr.bin/xlint/xlint/xlint.c (revision 3a0e83fa)
1 /* $NetBSD: xlint.c,v 1.114 2023/07/13 08:40:38 rillig Exp $ */
2 
3 /*
4  * Copyright (c) 1996 Christopher G. Demetriou.  All Rights Reserved.
5  * Copyright (c) 1994, 1995 Jochen Pohl
6  * All Rights Reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *	This product includes software developed by Jochen Pohl for
19  *	The NetBSD Project.
20  * 4. The name of the author may not be used to endorse or promote products
21  *    derived from this software without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
24  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33  */
34 
35 #if HAVE_NBTOOL_CONFIG_H
36 #include "nbtool_config.h"
37 #endif
38 
39 #include <sys/cdefs.h>
40 #if defined(__RCSID)
41 __RCSID("$NetBSD: xlint.c,v 1.114 2023/07/13 08:40:38 rillig Exp $");
42 #endif
43 
44 #include <sys/param.h>
45 #include <sys/wait.h>
46 #include <sys/stat.h>
47 #include <sys/utsname.h>
48 #include <errno.h>
49 #include <stdarg.h>
50 #include <fcntl.h>
51 #include <paths.h>
52 #include <signal.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57 #include <util.h>
58 
59 #include "lint.h"
60 #include "pathnames.h"
61 #include "findcc.h"
62 
63 #define DEFAULT_PATH		_PATH_DEFPATH
64 
65 typedef struct {
66 	char	**items;
67 	size_t	len;
68 	size_t	cap;
69 } list;
70 
71 /* Parameters for the C preprocessor. */
72 static struct {
73 	list	flags;		/* flags always passed */
74 	list	lcflags;	/* flags, controlled by sflag/tflag */
75 	char	*outfile;	/* path name for preprocessed C source */
76 	int	outfd;		/* file descriptor for outfile */
77 } cpp = { .outfd = -1 };
78 
79 /* Parameters for lint1, which checks an isolated translation unit. */
80 static struct {
81 	list	flags;
82 	list	outfiles;
83 } lint1;
84 
85 /* Parameters for lint2, which performs cross-translation-unit checks. */
86 static struct {
87 	list	flags;
88 	list	infiles;	/* input files (without libraries) */
89 	list	inlibs;		/* input libraries */
90 	char	*outlib;	/* output library that will be created */
91 } lint2;
92 
93 /* directory for temporary files */
94 static	const char *tmpdir;
95 
96 /* default libraries */
97 static	list	deflibs;
98 
99 /* additional libraries */
100 static	list	libs;
101 
102 /* search path for libraries */
103 static	list	libsrchpath;
104 
105 static const char *libexec_dir;
106 
107 /* flags */
108 static	bool	iflag, oflag, Cflag, sflag, tflag, Fflag, dflag;
109 
110 /* print the commands executed to run the stages of compilation */
111 static	bool	Vflag;
112 
113 /* filename for oflag */
114 static	char	*outputfn;
115 
116 /* reset after first .c source has been processed */
117 static	bool	first = true;
118 
119 /*
120  * name of a file which is currently written by a child and should
121  * be removed after abnormal termination of the child
122  */
123 static	const	char *currfn;
124 
125 #if !defined(TARGET_PREFIX)
126 #define	TARGET_PREFIX	""
127 #endif
128 static const char target_prefix[] = TARGET_PREFIX;
129 
130 
131 static void
list_add_ref(list * l,char * s)132 list_add_ref(list *l, char *s)
133 {
134 
135 	if (l->len >= l->cap) {
136 		l->cap = 2 * l->len + 16;
137 		l->items = xrealloc(l->items, sizeof(*l->items) * l->cap);
138 	}
139 	l->items[l->len++] = s;
140 }
141 
142 static void
list_add(list * l,const char * s)143 list_add(list *l, const char *s)
144 {
145 
146 	list_add_ref(l, xstrdup(s));
147 }
148 
149 static void
list_add_flag(list * l,int c)150 list_add_flag(list *l, int c)
151 {
152 
153 	list_add(l, (const char[3]){ '-', (char)c, '\0' });
154 }
155 
156 static void
list_add_unique(list * l,const char * s)157 list_add_unique(list *l, const char *s)
158 {
159 
160 	for (size_t i = 0; i < l->len; i++)
161 		if (strcmp(l->items[i], s) == 0)
162 			return;
163 	list_add(l, s);
164 }
165 
166 static void
list_add_all(list * dst,const list * src)167 list_add_all(list *dst, const list *src)
168 {
169 
170 	for (size_t i = 0; i < src->len; i++)
171 		list_add(dst, src->items[i]);
172 }
173 
174 static void
list_clear(list * l)175 list_clear(list *l)
176 {
177 
178 	while (l->len > 0)
179 		free(l->items[--l->len]);
180 }
181 
182 static char *
concat2(const char * s1,const char * s2)183 concat2(const char *s1, const char *s2)
184 {
185 
186 	size_t len1 = strlen(s1);
187 	size_t len2 = strlen(s2);
188 	char *s = xmalloc(len1 + len2 + 1);
189 	memcpy(s, s1, len1);
190 	memcpy(s + len1, s2, len2 + 1);
191 
192 	return s;
193 }
194 
195 static void
set_tmpdir(void)196 set_tmpdir(void)
197 {
198 	const char *tmp;
199 	size_t len;
200 
201 	tmpdir = (tmp = getenv("TMPDIR")) != NULL && (len = strlen(tmp)) != 0
202 	    ? concat2(tmp, tmp[len - 1] == '/' ? "" : "/")
203 	    : xstrdup(_PATH_TMP);
204 }
205 
206 /* Clean up after a signal or at the regular end. */
207 static void __attribute__((__noreturn__))
terminate(int signo)208 terminate(int signo)
209 {
210 
211 	if (cpp.outfd != -1)
212 		(void)close(cpp.outfd);
213 	if (cpp.outfile != NULL) {
214 		const char *keep_env = getenv("LINT_KEEP_CPPOUT");
215 		bool keep = keep_env != NULL && (strcmp(keep_env, "yes") == 0
216 		    || (strcmp(keep_env, "on-error") == 0 && signo != 0));
217 		if (keep)
218 			(void)printf("lint: preprocessor output kept in %s\n",
219 			    cpp.outfile);
220 		else
221 			(void)remove(cpp.outfile);
222 	}
223 
224 	for (size_t i = 0; i < lint1.outfiles.len; i++)
225 		(void)remove(lint1.outfiles.items[i]);
226 
227 	if (lint2.outlib != NULL)
228 		(void)remove(lint2.outlib);
229 
230 	if (currfn != NULL && currfn != cpp.outfile)
231 		(void)remove(currfn);
232 
233 	if (signo != 0)
234 		(void)raise_default_signal(signo);
235 	exit(signo != 0 ? 1 : 0);
236 }
237 
238 static void __attribute__((__noreturn__, __format__(__printf__, 1, 2)))
usage(const char * fmt,...)239 usage(const char *fmt, ...)
240 {
241 	va_list ap;
242 
243 	va_start(ap, fmt);
244 	(void)vfprintf(stderr, fmt, ap);
245 	va_end(ap);
246 	if (fmt[0] != '\0')
247 		(void)fprintf(stderr, "\n");
248 
249 	const char *name = getprogname();
250 	int indent = (int)(strlen("usage: ") + strlen(name));
251 	(void)fprintf(stderr,
252 	    "usage: %s [-abceghprstvwxzFHPSTV] [-Alevel] [-i|-nu]\n"
253 	    "%*s [-Dname[=def]] [-Uname] [-Idirectory] "
254 	    "[-M...] [-W...] [-Z ...]\n"
255 	    "%*s [-ddirectory] [-Ldirectory] [-llibrary] [-ooutputfile]\n"
256 	    "%*s [-Bpath] [-X id,...] [-q id,...] [-R old=new] file ...\n",
257 	    name, indent, "", indent, "", indent, "");
258 	(void)fprintf(stderr,
259 	    "       %s [-abceghprstvwzFHPSTV] [-Alevel] -Clibrary\n"
260 	    "%*s [-Bpath] [-R old=new] file ...\n",
261 	    name, indent, "");
262 	terminate(-1);
263 }
264 
265 /*
266  * Returns a pointer to the last component of path after delim.
267  * Returns path if the string does not contain delim.
268  */
269 static const char *
lbasename(const char * path,int delim)270 lbasename(const char *path, int delim)
271 {
272 
273 	const char *base = path;
274 	for (const char *p = path; *p != '\0'; p++)
275 		if (*p == delim)
276 			base = p + 1;
277 	return base;
278 }
279 
280 static bool
is_safe_shell(char ch)281 is_safe_shell(char ch)
282 {
283 
284 	return ch_isalnum(ch) || ch == '%' || ch == '+' || ch == ',' ||
285 	       ch == '-' || ch == '.' || ch == '/' || ch == ':' ||
286 	       ch == '=' || ch == '@' || ch == '_';
287 }
288 
289 static void
print_sh_quoted(const char * s)290 print_sh_quoted(const char *s)
291 {
292 
293 	if (s[0] == '\0')
294 		goto needs_quoting;
295 	for (const char *p = s; *p != '\0'; p++)
296 		if (!is_safe_shell(*p))
297 			goto needs_quoting;
298 
299 	(void)printf("%s", s);
300 	return;
301 
302 needs_quoting:
303 	(void)putchar('\'');
304 	for (const char *p = s; *p != '\0'; p++) {
305 		if (*p == '\'')
306 			(void)printf("'\\''");
307 		else
308 			(void)putchar(*p);
309 	}
310 	(void)putchar('\'');
311 }
312 
313 static void
run_child(const char * path,list * args,const char * crfn,int fdout)314 run_child(const char *path, list *args, const char *crfn, int fdout)
315 {
316 	int status, rv, signo;
317 
318 	if (Vflag) {
319 		print_sh_quoted(args->items[0]);
320 		for (size_t i = 1; i < args->len - 1; i++) {
321 			(void)printf(" ");
322 			print_sh_quoted(args->items[i]);
323 		}
324 		(void)printf("\n");
325 	}
326 
327 	currfn = crfn;
328 
329 	(void)fflush(stdout);
330 
331 	switch (vfork()) {
332 	case -1:
333 		warn("cannot fork");
334 		terminate(-1);
335 		/* NOTREACHED */
336 	default:
337 		/* parent */
338 		break;
339 	case 0:
340 		/* child */
341 
342 		/* set up the standard output if necessary */
343 		if (fdout != -1) {
344 			(void)dup2(fdout, STDOUT_FILENO);
345 			(void)close(fdout);
346 		}
347 		(void)execvp(path, args->items);
348 		warn("cannot exec %s", path);
349 		_exit(1);
350 		/* NOTREACHED */
351 	}
352 
353 	while ((rv = wait(&status)) == -1 && errno == EINTR) ;
354 	if (rv == -1) {
355 		warn("wait");
356 		terminate(-1);
357 	}
358 	if (WIFSIGNALED(status)) {
359 		signo = WTERMSIG(status);
360 #if HAVE_DECL_SYS_SIGNAME
361 		warnx("%s got SIG%s", path, sys_signame[signo]);
362 #else
363 		warnx("%s got signal %d", path, signo);
364 #endif
365 		terminate(-1);
366 	}
367 	if (WEXITSTATUS(status) != 0)
368 		terminate(-1);
369 	currfn = NULL;
370 }
371 
372 static void
run_cpp(const char * name)373 run_cpp(const char *name)
374 {
375 
376 	const char *cc = getenv("CC");
377 	if (cc == NULL)
378 		cc = DEFAULT_CC;
379 
380 	char *abs_cc = findcc(cc);
381 	if (abs_cc == NULL && setenv("PATH", DEFAULT_PATH, 1) == 0)
382 		abs_cc = findcc(cc);
383 	if (abs_cc == NULL) {
384 		(void)fprintf(stderr, "%s: %s: not found\n", getprogname(), cc);
385 		exit(EXIT_FAILURE);
386 	}
387 
388 	list args = { NULL, 0, 0 };
389 	list_add_ref(&args, abs_cc);
390 	list_add_all(&args, &cpp.flags);
391 	list_add_all(&args, &cpp.lcflags);
392 	list_add(&args, name);
393 	list_add_ref(&args, NULL);
394 
395 	/* we reuse the same tmp file for cpp output, so rewind and truncate */
396 	if (lseek(cpp.outfd, 0, SEEK_SET) != 0) {
397 		warn("lseek");
398 		terminate(-1);
399 	}
400 	if (ftruncate(cpp.outfd, 0) != 0) {
401 		warn("ftruncate");
402 		terminate(-1);
403 	}
404 
405 	run_child(abs_cc, &args, cpp.outfile, cpp.outfd);
406 	list_clear(&args);
407 }
408 
409 static void
run_lint1(const char * out_fname)410 run_lint1(const char *out_fname)
411 {
412 
413 	char *abs_lint1 = libexec_dir != NULL
414 	    ? concat2(libexec_dir, "/lint1")
415 	    : xasprintf("%s/%slint1", PATH_LIBEXEC, target_prefix);
416 
417 	list args = { NULL, 0, 0 };
418 	list_add_ref(&args, abs_lint1);
419 	list_add_all(&args, &lint1.flags);
420 	list_add(&args, cpp.outfile);
421 	list_add(&args, out_fname);
422 	list_add_ref(&args, NULL);
423 
424 	run_child(abs_lint1, &args, out_fname, -1);
425 	list_clear(&args);
426 }
427 
428 /*
429  * Read a file name from the command line
430  * and pass it through lint1 if it is a C source.
431  */
432 static void
handle_filename(const char * name)433 handle_filename(const char *name)
434 {
435 	const char *bn, *suff;
436 	char *ofn;
437 	size_t len;
438 	int fd;
439 
440 	bn = lbasename(name, '/');
441 	suff = lbasename(bn, '.');
442 
443 	if (strcmp(suff, "ln") == 0) {
444 		/* only for lint2 */
445 		if (!iflag)
446 			list_add(&lint2.infiles, name);
447 		return;
448 	}
449 
450 	if (strcmp(suff, "c") != 0 &&
451 	    (strncmp(bn, "llib-l", 6) != 0 || bn != suff)) {
452 		warnx("unknown file type: %s", name);
453 		return;
454 	}
455 
456 	if (!iflag || !first)
457 		(void)printf("%s:\n", Fflag ? name : bn);
458 
459 	/* build the name of the output file of lint1 */
460 	if (oflag) {
461 		ofn = outputfn;
462 		outputfn = NULL;
463 		oflag = false;
464 	} else if (iflag) {
465 		len = bn == suff ? strlen(bn) : (size_t)((suff - 1) - bn);
466 		ofn = xasprintf("%.*s.ln", (int)len, bn);
467 	} else {
468 		ofn = xasprintf("%slint1.XXXXXX", tmpdir);
469 		fd = mkstemp(ofn);
470 		if (fd == -1) {
471 			warn("can't make temp");
472 			terminate(-1);
473 		}
474 		(void)close(fd);
475 	}
476 	if (!iflag)
477 		list_add(&lint1.outfiles, ofn);
478 
479 	run_cpp(name);
480 	run_lint1(ofn);
481 
482 	list_add(&lint2.infiles, ofn);
483 	free(ofn);
484 }
485 
486 static bool
file_is_readable(const char * path)487 file_is_readable(const char *path)
488 {
489 	struct stat sbuf;
490 
491 	if (stat(path, &sbuf) == -1)
492 		return false;
493 	if (!S_ISREG(sbuf.st_mode))
494 		return false;
495 	if (access(path, R_OK) == -1)
496 		return false;
497 	return true;
498 }
499 
500 static void
find_lib(const char * lib)501 find_lib(const char *lib)
502 {
503 	char *lfn;
504 
505 	for (size_t i = 0; i < libsrchpath.len; i++) {
506 		const char *dir = libsrchpath.items[i];
507 		lfn = xasprintf("%s/llib-l%s.ln", dir, lib);
508 		if (file_is_readable(lfn))
509 			goto found;
510 		free(lfn);
511 
512 		lfn = xasprintf("%s/lint/llib-l%s.ln", dir, lib);
513 		if (file_is_readable(lfn))
514 			goto found;
515 		free(lfn);
516 	}
517 
518 	warnx("cannot find llib-l%s.ln", lib);
519 	return;
520 
521 found:
522 	list_add_ref(&lint2.inlibs, concat2("-l", lfn));
523 	free(lfn);
524 }
525 
526 static void
find_libs(const list * l)527 find_libs(const list *l)
528 {
529 
530 	for (size_t i = 0; i < l->len; i++)
531 		find_lib(l->items[i]);
532 }
533 
534 static void
run_lint2(void)535 run_lint2(void)
536 {
537 
538 	char *abs_lint2 = libexec_dir != NULL
539 	    ? concat2(libexec_dir, "/lint2")
540 	    : xasprintf("%s/%slint2", PATH_LIBEXEC, target_prefix);
541 
542 	list args = { NULL, 0, 0 };
543 	list_add_ref(&args, abs_lint2);
544 	list_add_all(&args, &lint2.flags);
545 	list_add_all(&args, &lint2.inlibs);
546 	list_add_all(&args, &lint2.infiles);
547 	list_add_ref(&args, NULL);
548 
549 	run_child(abs_lint2, &args, lint2.outlib, -1);
550 	list_clear(&args);
551 }
552 
553 static void
cat(const list * srcs,const char * dest)554 cat(const list *srcs, const char *dest)
555 {
556 	int ifd, ofd;
557 	ssize_t rlen;
558 	char buf[0x4000];
559 
560 	if ((ofd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0666)) == -1) {
561 		warn("cannot open %s", dest);
562 		terminate(-1);
563 	}
564 
565 	for (size_t i = 0; i < srcs->len; i++) {
566 		const char *src = srcs->items[i];
567 		if ((ifd = open(src, O_RDONLY)) == -1) {
568 			warn("cannot open %s", src);
569 			terminate(-1);
570 		}
571 		do {
572 			if ((rlen = read(ifd, buf, sizeof(buf))) == -1) {
573 				warn("read error on %s", src);
574 				terminate(-1);
575 			}
576 			if (write(ofd, buf, (size_t)rlen) != rlen) {
577 				warn("write error on %s", dest);
578 				terminate(-1);
579 			}
580 		} while (rlen == sizeof(buf));
581 		(void)close(ifd);
582 	}
583 	(void)close(ofd);
584 }
585 
586 int
main(int argc,char * argv[])587 main(int argc, char *argv[])
588 {
589 
590 	setprogname(argv[0]);
591 	set_tmpdir();
592 
593 	cpp.outfile = concat2(tmpdir, "lint0.XXXXXX");
594 	cpp.outfd = mkstemp(cpp.outfile);
595 	if (cpp.outfd == -1) {
596 		warn("can't make temp");
597 		terminate(-1);
598 	}
599 
600 	list_add(&cpp.flags, "-E");
601 	list_add(&cpp.flags, "-x");
602 	list_add(&cpp.flags, "c");
603 	list_add(&cpp.flags, "-U__GNUC__");
604 	list_add(&cpp.flags, "-U__PCC__");
605 	list_add(&cpp.flags, "-U__SSE__");
606 	list_add(&cpp.flags, "-U__SSE4_1__");
607 	list_add(&cpp.flags, "-Wp,-CC");
608 	list_add(&cpp.flags, "-Wcomment");
609 	list_add(&cpp.flags, "-D__LINT__");
610 	list_add(&cpp.flags, "-Dlint");	/* XXX don't define with -s */
611 	list_add(&cpp.flags, "-D__lint");
612 	list_add(&cpp.flags, "-D__lint__");
613 
614 	list_add(&deflibs, "c");
615 
616 	if (signal(SIGHUP, terminate) == SIG_IGN)
617 		(void)signal(SIGHUP, SIG_IGN);
618 	(void)signal(SIGINT, terminate);
619 	(void)signal(SIGQUIT, terminate);
620 	(void)signal(SIGTERM, terminate);
621 
622 	int c;
623 	while ((c = getopt(argc, argv,
624 	    "abcd:eghil:no:pq:rstuvwxzA:B:C:D:FHI:L:M:PR:STU:VW:X:Z:")) != -1) {
625 		switch (c) {
626 
627 		case 'a':
628 		case 'b':
629 		case 'c':
630 		case 'e':
631 		case 'g':
632 		case 'r':
633 		case 'v':
634 		case 'w':
635 		case 'z':
636 		case 'P':
637 			list_add_flag(&lint1.flags, c);
638 			break;
639 
640 		case 'A':
641 		case 'q':
642 		case 'R':
643 		case 'X':
644 			list_add_flag(&lint1.flags, c);
645 			list_add(&lint1.flags, optarg);
646 			break;
647 
648 		case 'F':
649 			Fflag = true;
650 			/* FALLTHROUGH */
651 		case 'u':
652 		case 'h':
653 			list_add_flag(&lint1.flags, c);
654 			list_add_flag(&lint2.flags, c);
655 			break;
656 
657 		case 'i':
658 			if (Cflag)
659 				usage("%c and %s flags cannot be specified "
660 				    "together", 'C', "i");
661 			iflag = true;
662 			break;
663 
664 		case 'n':
665 			list_clear(&deflibs);
666 			break;
667 
668 		case 'p':
669 			if (deflibs.len > 0) {
670 				list_clear(&deflibs);
671 				list_add(&deflibs, "c");
672 			}
673 			list_add_flag(&lint1.flags, c);
674 			break;
675 
676 		case 's':
677 			if (tflag)
678 				usage("%c and %s flags cannot be specified "
679 				    "together", 's', "t");
680 			list_clear(&cpp.lcflags);
681 			list_add(&cpp.lcflags, "-trigraphs");
682 			list_add(&cpp.lcflags, "-Wtrigraphs");
683 			list_add(&cpp.lcflags, "-pedantic");
684 			list_add(&cpp.lcflags, "-D__STRICT_ANSI__");
685 			sflag = true;
686 			list_add_flag(&lint1.flags, c);
687 			list_add_flag(&lint2.flags, c);
688 			break;
689 
690 		case 'S':
691 			if (tflag)
692 				usage("%c and %s flags cannot be specified "
693 				    "together", 'S', "t");
694 			list_add_flag(&lint1.flags, c);
695 			break;
696 
697 		case 'T':
698 			list_add(&cpp.flags, "-I" PATH_STRICT_BOOL_INCLUDE);
699 			list_add_flag(&lint1.flags, c);
700 			break;
701 
702 #if !HAVE_NBTOOL_CONFIG_H
703 		case 't':
704 			if (sflag)
705 				usage("%c and %s flags cannot be specified "
706 				    "together", 's', "t");
707 			tflag = true;
708 			list_clear(&cpp.lcflags);
709 			list_add(&cpp.lcflags, "-traditional");
710 			list_add(&cpp.lcflags, "-Wtraditional");
711 			list_add(&cpp.lcflags, "-D" MACHINE);
712 			list_add(&cpp.lcflags, "-D" MACHINE_ARCH);
713 			list_add_flag(&lint1.flags, c);
714 			list_add_flag(&lint2.flags, c);
715 			break;
716 #endif
717 
718 		case 'x':
719 		case 'H':
720 			list_add_flag(&lint2.flags, c);
721 			break;
722 
723 		case 'C':
724 			if (Cflag)
725 				usage("%c flag already specified", 'C');
726 			if (oflag || iflag)
727 				usage("%c and %s flags cannot be specified "
728 				    "together", 'C', "o or i");
729 			Cflag = true;
730 			list_add_flag(&lint2.flags, c);
731 			list_add(&lint2.flags, optarg);
732 			lint2.outlib = xasprintf("llib-l%s.ln", optarg);
733 			list_clear(&deflibs);
734 			break;
735 
736 		case 'd':
737 			if (dflag)
738 				usage("%c flag already specified", 'd');
739 			dflag = true;
740 			list_add(&cpp.flags, "--sysroot");
741 			list_add(&cpp.flags, optarg);
742 			break;
743 
744 		case 'D':
745 		case 'I':
746 		case 'M':
747 		case 'U':
748 		case 'W':
749 			list_add_ref(&cpp.flags,
750 			    xasprintf("-%c%s", c, optarg));
751 			break;
752 
753 		case 'l':
754 			list_add_unique(&libs, optarg);
755 			break;
756 
757 		case 'o':
758 			if (oflag)
759 				usage("%c flag already specified", 'o');
760 			if (Cflag)
761 				usage("%c and %s flags cannot be specified "
762 				    "together", 'C', "o");
763 			oflag = true;
764 			outputfn = xstrdup(optarg);
765 			break;
766 
767 		case 'L':
768 			list_add_unique(&libsrchpath, optarg);
769 			break;
770 
771 		case 'B':
772 			libexec_dir = xstrdup(optarg);
773 			break;
774 
775 		case 'V':
776 			Vflag = true;
777 			break;
778 
779 		case 'Z':
780 			list_add(&cpp.flags, optarg);
781 			break;
782 
783 		default:
784 			usage("");
785 			/* NOTREACHED */
786 		}
787 	}
788 	argc -= optind;
789 	argv += optind;
790 
791 	/*
792 	 * To avoid modifying getopt(3)'s state engine midstream, we
793 	 * explicitly accept just a few options after the first source file.
794 	 *
795 	 * In particular, only -l<lib> and -L<libdir> (and these with a space
796 	 * after -l or -L) are allowed.
797 	 */
798 	for (; argc > 0; argc--, argv++) {
799 		const char *arg = argv[0];
800 
801 		if (arg[0] == '-') {
802 			list *lp;
803 
804 			if (arg[1] == 'l')
805 				lp = &libs;
806 			else if (arg[1] == 'L')
807 				lp = &libsrchpath;
808 			else
809 				usage("Unknown late option '%s'", arg);
810 
811 			if (arg[2] != '\0')
812 				list_add_unique(lp, arg + 2);
813 			else if (argc > 1) {
814 				argc--, argv++;
815 				list_add_unique(lp, argv[0]);
816 			} else
817 				usage("Missing argument for l or L");
818 		} else {
819 			handle_filename(arg);
820 			first = false;
821 		}
822 	}
823 
824 	if (first)
825 		usage("Missing filename");
826 
827 	if (iflag)
828 		terminate(0);
829 
830 	if (!oflag) {
831 		const char *ks = getenv("LIBDIR");
832 		if (ks == NULL || ks[0] == '\0')
833 			ks = PATH_LINTLIB;
834 		list_add(&libsrchpath, ks);
835 		find_libs(&libs);
836 		find_libs(&deflibs);
837 	}
838 
839 	run_lint2();
840 
841 	if (oflag)
842 		cat(&lint2.infiles, outputfn);
843 
844 	if (Cflag)
845 		lint2.outlib = NULL;
846 
847 	terminate(0);
848 	/* NOTREACHED */
849 }
850