/* $NetBSD: xlint.c,v 1.27 2002/01/31 19:09:33 tv Exp $ */

/*
 * Copyright (c) 1996 Christopher G. Demetriou.  All Rights Reserved.
 * Copyright (c) 1994, 1995 Jochen Pohl
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Jochen Pohl for
 *	The NetBSD Project.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#if defined(__RCSID) && !defined(lint)
__RCSID("$NetBSD: xlint.c,v 1.27 2002/01/31 19:09:33 tv Exp $");
#endif
__FBSDID("$FreeBSD: releng/9.2/usr.bin/xlint/xlint/xlint.c 230381 2012-01-20 15:43:08Z jilles $");

#include <sys/param.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "lint.h"
#include "pathnames.h"

#define DEFAULT_PATH		_PATH_DEFPATH

int main(int, char *[]);

/* directory for temporary files */
static	const	char *tmpdir;

/* path name for cpp output */
static	char	*cppout;

/* file descriptor for cpp output */
static	int	cppoutfd = -1;

/* files created by 1st pass */
static	char	**p1out;

/* input files for 2nd pass (without libraries) */
static	char	**p2in;

/* library which will be created by 2nd pass */
static	char	*p2out;

/* flags always passed to cc(1) */
static	char	**cflags;

/* flags for cc(1), controled by sflag/tflag */
static	char	**lcflags;

/* flags for lint1 */
static	char	**l1flags;

/* flags for lint2 */
static	char	**l2flags;

/* libraries for lint2 */
static	char	**l2libs;

/* default libraries */
static	char	**deflibs;

/* additional libraries */
static	char	**libs;

/* search path for libraries */
static	char	**libsrchpath;

static  char	*libexec_path;

/* flags */
static	int	iflag, oflag, Cflag, sflag, tflag, Fflag, dflag, Bflag;

/* print the commands executed to run the stages of compilation */
static	int	Vflag;

/* filename for oflag */
static	char	*outputfn;

/* reset after first .c source has been processed */
static	int	first = 1;

/*
 * name of a file which is currently written by a child and should
 * be removed after abnormal termination of the child
 */
static	const	char *currfn;

#if !defined(TARGET_PREFIX)
#define	TARGET_PREFIX	""
#endif
static const char target_prefix[] = TARGET_PREFIX;

static	void	appstrg(char ***, char *);
static	void	appcstrg(char ***, const char *);
static	void	applst(char ***, char *const *);
static	void	freelst(char ***);
static	char	*concat2(const char *, const char *);
static	char	*concat3(const char *, const char *, const char *);
static	void	terminate(int) __attribute__((__noreturn__));
static	const	char *lbasename(const char *, int);
static	void	appdef(char ***, const char *);
static	void	usage(void) __dead2;
static	void	fname(const char *);
static	void	runchild(const char *, char *const *, const char *, int);
static	void	findlibs(char *const *);
static	int	rdok(const char *);
static	void	lint2(void);
static	void	cat(char *const *, const char *);

/*
 * Some functions to deal with lists of strings.
 * Take care that we get no surprises in case of asyncron signals.
 */
static void
appstrg(char ***lstp, char *s)
{
	char	**lst, **olst;
	int	i;

	olst = *lstp;
	for (i = 0; olst[i] != NULL; i++)
		continue;
	lst = xrealloc(olst, (i + 2) * sizeof (char *));
	lst[i] = s;
	lst[i + 1] = NULL;
	*lstp = lst;
}

static void
appcstrg(char ***lstp, const char *s)
{

	appstrg(lstp, xstrdup(s));
}

static void
applst(char ***destp, char *const *src)
{
	int	i, k;
	char	**dest, **odest;

	odest = *destp;
	for (i = 0; odest[i] != NULL; i++)
		continue;
	for (k = 0; src[k] != NULL; k++)
		continue;
	dest = xrealloc(odest, (i + k + 1) * sizeof (char *));
	for (k = 0; src[k] != NULL; k++)
		dest[i + k] = xstrdup(src[k]);
	dest[i + k] = NULL;
	*destp = dest;
}

static void
freelst(char ***lstp)
{
	char	*s;
	int	i;

	for (i = 0; (*lstp)[i] != NULL; i++)
		continue;
	while (i-- > 0) {
		s = (*lstp)[i];
		(*lstp)[i] = NULL;
		free(s);
	}
}

static char *
concat2(const char *s1, const char *s2)
{
	char	*s;

	s = xmalloc(strlen(s1) + strlen(s2) + 1);
	(void)strcpy(s, s1);
	(void)strcat(s, s2);

	return (s);
}

static char *
concat3(const char *s1, const char *s2, const char *s3)
{
	char	*s;

	s = xmalloc(strlen(s1) + strlen(s2) + strlen(s3) + 1);
	(void)strcpy(s, s1);
	(void)strcat(s, s2);
	(void)strcat(s, s3);

	return (s);
}

/*
 * Clean up after a signal.
 */
static void
terminate(int signo)
{
	int	i;

	if (cppoutfd != -1)
		(void)close(cppoutfd);
	if (cppout != NULL)
		(void)remove(cppout);

	if (p1out != NULL) {
		for (i = 0; p1out[i] != NULL; i++)
			(void)remove(p1out[i]);
	}

	if (p2out != NULL)
		(void)remove(p2out);

	if (currfn != NULL)
		(void)remove(currfn);

	exit(signo != 0 ? 1 : 0);
}

/*
 * Returns a pointer to the last component of strg after delim.
 * Returns strg if the string does not contain delim.
 */
static const char *
lbasename(const char *strg, int delim)
{
	const	char *cp, *cp1, *cp2;

	cp = cp1 = cp2 = strg;
	while (*cp != '\0') {
		if (*cp++ == delim) {
			cp2 = cp1;
			cp1 = cp;
		}
	}
	return (*cp1 == '\0' ? cp2 : cp1);
}

static void
appdef(char ***lstp, const char *def)
{

	appstrg(lstp, concat2("-D__", def));
	appstrg(lstp, concat3("-D__", def, "__"));
}

static void
usage(void)
{

	(void)fprintf(stderr,
	    "usage: lint [-abceghprvwxzHF] [-s|-t] [-i|-nu] [-Dname[=def]]"
	    " [-Uname] [-X <id>[,<id>]...\n");
	(void)fprintf(stderr,
	    "\t[-Idirectory] [-Ldirectory] [-llibrary] [-ooutputfile]"
	    " file...\n");
	(void)fprintf(stderr,
	    "       lint [-abceghprvwzHF] [-s|-t] -Clibrary [-Dname[=def]]\n"
	    " [-X <id>[,<id>]...\n");
	(void)fprintf(stderr, "\t[-Idirectory] [-Uname] [-Bpath] file"
	    " ...\n");
	terminate(-1);
}


int
main(int argc, char *argv[])
{
	int	c;
	char	flgbuf[3], *s;
	const char *tmp;
	size_t	len;

	if ((tmp = getenv("TMPDIR")) == NULL || (len = strlen(tmp)) == 0) {
		tmpdir = _PATH_TMP;
	} else {
		s = xmalloc(len + 2);
		(void)sprintf(s, "%s%s", tmp, tmp[len - 1] == '/' ? "" : "/");
		tmpdir = s;
	}

	cppout = xmalloc(strlen(tmpdir) + sizeof ("lint0.XXXXXX"));
	(void)sprintf(cppout, "%slint0.XXXXXX", tmpdir);
	cppoutfd = mkstemp(cppout);
	if (cppoutfd == -1) {
		warn("can't make temp");
		terminate(-1);
	}

	p1out = xcalloc(1, sizeof (char *));
	p2in = xcalloc(1, sizeof (char *));
	cflags = xcalloc(1, sizeof (char *));
	lcflags = xcalloc(1, sizeof (char *));
	l1flags = xcalloc(1, sizeof (char *));
	l2flags = xcalloc(1, sizeof (char *));
	l2libs = xcalloc(1, sizeof (char *));
	deflibs = xcalloc(1, sizeof (char *));
	libs = xcalloc(1, sizeof (char *));
	libsrchpath = xcalloc(1, sizeof (char *));

	appcstrg(&cflags, "-E");
	appcstrg(&cflags, "-x");
	appcstrg(&cflags, "c");
#if 0
	appcstrg(&cflags, "-D__attribute__(x)=");
	appcstrg(&cflags, "-D__extension__(x)=/*NOSTRICT*/0");
#else
	appcstrg(&cflags, "-U__GNUC__");
	appcstrg(&cflags, "-undef");
#endif
	appcstrg(&cflags, "-Wp,-C");
	appcstrg(&cflags, "-Wcomment");
	appcstrg(&cflags, "-D__LINT__");
	appcstrg(&cflags, "-Dlint");		/* XXX don't def. with -s */

	appdef(&cflags, "lint");

	appcstrg(&deflibs, "c");

	if (signal(SIGHUP, terminate) == SIG_IGN)
		(void)signal(SIGHUP, SIG_IGN);
	(void)signal(SIGINT, terminate);
	(void)signal(SIGQUIT, terminate);
	(void)signal(SIGTERM, terminate);

	while ((c = getopt(argc, argv, "abcd:eghil:no:prstuvwxzB:C:D:FHI:L:U:VX:")) != -1) {
		switch (c) {

		case 'a':
		case 'b':
		case 'c':
		case 'e':
		case 'g':
		case 'r':
		case 'v':
		case 'w':
		case 'z':
			(void)sprintf(flgbuf, "-%c", c);
			appcstrg(&l1flags, flgbuf);
			break;

		case 'F':
			Fflag = 1;
			/* FALLTHROUGH */
		case 'u':
		case 'h':
			(void)sprintf(flgbuf, "-%c", c);
			appcstrg(&l1flags, flgbuf);
			appcstrg(&l2flags, flgbuf);
			break;

		case 'X':
			(void)sprintf(flgbuf, "-%c", c);
			appcstrg(&l1flags, flgbuf);
			appcstrg(&l1flags, optarg);
			break;

		case 'i':
			if (Cflag)
				usage();
			iflag = 1;
			break;

		case 'n':
			freelst(&deflibs);
			break;

		case 'p':
			appcstrg(&lcflags, "-Wtraditional");
			appcstrg(&lcflags, "-Wno-system-headers");
			appcstrg(&l1flags, "-p");
			appcstrg(&l2flags, "-p");
			if (*deflibs != NULL) {
				freelst(&deflibs);
				appcstrg(&deflibs, "c");
			}
			break;

		case 's':
			if (tflag)
				usage();
			freelst(&lcflags);
			appcstrg(&lcflags, "-trigraphs");
			appcstrg(&lcflags, "-Wtrigraphs");
			appcstrg(&lcflags, "-pedantic");
			appcstrg(&lcflags, "-D__STRICT_ANSI__");
			appcstrg(&l1flags, "-s");
			appcstrg(&l2flags, "-s");
			sflag = 1;
			break;

#if !HAVE_CONFIG_H
		case 't':
			if (sflag)
				usage();
			freelst(&lcflags);
			appcstrg(&lcflags, "-traditional");
			appstrg(&lcflags, concat2("-D", MACHINE));
			appstrg(&lcflags, concat2("-D", MACHINE_ARCH));
			appcstrg(&l1flags, "-t");
			appcstrg(&l2flags, "-t");
			tflag = 1;
			break;
#endif

		case 'x':
			appcstrg(&l2flags, "-x");
			break;

		case 'C':
			if (Cflag || oflag || iflag)
				usage();
			Cflag = 1;
			appstrg(&l2flags, concat2("-C", optarg));
			p2out = xmalloc(sizeof ("llib-l.ln") + strlen(optarg));
			(void)sprintf(p2out, "llib-l%s.ln", optarg);
			freelst(&deflibs);
			break;

		case 'd':
			if (dflag)
				usage();
			dflag = 1;
			appcstrg(&cflags, "-nostdinc");
			appcstrg(&cflags, "-idirafter");
			appcstrg(&cflags, optarg);
			break;

		case 'D':
		case 'I':
		case 'U':
			(void)sprintf(flgbuf, "-%c", c);
			appstrg(&cflags, concat2(flgbuf, optarg));
			break;

		case 'l':
			appcstrg(&libs, optarg);
			break;

		case 'o':
			if (Cflag || oflag)
				usage();
			oflag = 1;
			outputfn = xstrdup(optarg);
			break;

		case 'L':
			appcstrg(&libsrchpath, optarg);
			break;

		case 'H':
			appcstrg(&l2flags, "-H");
			break;

		case 'B':
			Bflag = 1;
			libexec_path = xstrdup(optarg);
			break;

		case 'V':
			Vflag = 1;
			break;

		default:
			usage();
			/* NOTREACHED */
		}
	}
	argc -= optind;
	argv += optind;

	/*
	 * To avoid modifying getopt(3)'s state engine midstream, we
	 * explicitly accept just a few options after the first source file.
	 *
	 * In particular, only -l<lib> and -L<libdir> (and these with a space
	 * after -l or -L) are allowed.
	 */
	while (argc > 0) {
		const char *arg = argv[0];

		if (arg[0] == '-') {
			char ***list;

			/* option */
			switch (arg[1]) {
			case 'l':
				list = &libs;
				break;

			case 'L':
				list = &libsrchpath;
				break;

			default:
				usage();
				/* NOTREACHED */
			}
			if (arg[2])
				appcstrg(list, arg + 2);
			else if (argc > 1) {
				argc--;
				appcstrg(list, *++argv);
			} else
				usage();
		} else {
			/* filename */
			fname(arg);
			first = 0;
		}
		argc--;
		argv++;
	}

	if (first)
		usage();

	if (iflag)
		terminate(0);

	if (!oflag) {
		if ((tmp = getenv("LIBDIR")) == NULL || strlen(tmp) == 0)
			tmp = PATH_LINTLIB;
		appcstrg(&libsrchpath, tmp);
		findlibs(libs);
		findlibs(deflibs);
	}

	(void)printf("Lint pass2:\n");
	lint2();

	if (oflag)
		cat(p2in, outputfn);

	if (Cflag)
		p2out = NULL;

	terminate(0);
	/* NOTREACHED */
}

/*
 * Read a file name from the command line
 * and pass it through lint1 if it is a C source.
 */
static void
fname(const char *name)
{
	const	char *bn, *suff;
	char	**args, *ofn, *p, *pathname;
	size_t	len;
	int is_stdin;
	int	fd;

	is_stdin = (strcmp(name, "-") == 0);
	bn = lbasename(name, '/');
	suff = lbasename(bn, '.');

	if (strcmp(suff, "ln") == 0) {
		/* only for lint2 */
		if (!iflag)
			appcstrg(&p2in, name);
		return;
	}

	if (!is_stdin && strcmp(suff, "c") != 0 &&
	    (strncmp(bn, "llib-l", 6) != 0 || bn != suff)) {
		warnx("unknown file type: %s\n", name);
		return;
	}

	if (!iflag || !first)
		(void)printf("%s:\n",
		    is_stdin ? "{standard input}" : Fflag ? name : bn);

	/* build the name of the output file of lint1 */
	if (oflag) {
		ofn = outputfn;
		outputfn = NULL;
		oflag = 0;
	} else if (iflag) {
		if (is_stdin) {
			warnx("-i not supported without -o for standard input");
			return;
		}
		ofn = xmalloc(strlen(bn) + (bn == suff ? 4 : 2));
		len = bn == suff ? strlen(bn) : (size_t)((suff - 1) - bn);
		(void)sprintf(ofn, "%.*s", (int)len, bn);
		(void)strcat(ofn, ".ln");
	} else {
		ofn = xmalloc(strlen(tmpdir) + sizeof ("lint1.XXXXXX"));
		(void)sprintf(ofn, "%slint1.XXXXXX", tmpdir);
		fd = mkstemp(ofn);
		if (fd == -1) {
			warn("can't make temp");
			terminate(-1);
		}
		close(fd);
	}
	if (!iflag)
		appcstrg(&p1out, ofn);

	args = xcalloc(1, sizeof (char *));

	/* run cc */

	if (getenv("CC") == NULL) {
		pathname = xmalloc(strlen(PATH_USRBIN) + sizeof ("/cc"));
		(void)sprintf(pathname, "%s/cc", PATH_USRBIN);
		appcstrg(&args, pathname);
	} else {
		pathname = strdup(getenv("CC"));
		for (p = strtok(pathname, " \t"); p; p = strtok(NULL, " \t"))
			appcstrg(&args, p);
	}

	applst(&args, cflags);
	applst(&args, lcflags);
	appcstrg(&args, name);

	/* we reuse the same tmp file for cpp output, so rewind and truncate */
	if (lseek(cppoutfd, (off_t)0, SEEK_SET) != 0) {
		warn("lseek");
		terminate(-1);
	}
	if (ftruncate(cppoutfd, (off_t)0) != 0) {
		warn("ftruncate");
		terminate(-1);
	}

	runchild(pathname, args, cppout, cppoutfd);
	free(pathname);
	freelst(&args);

	/* run lint1 */

	if (!Bflag) {
		pathname = xmalloc(strlen(PATH_LIBEXEC) + sizeof ("/lint1") +
		    strlen(target_prefix));
		(void)sprintf(pathname, "%s/%slint1", PATH_LIBEXEC,
		    target_prefix);
	} else {
		/*
		 * XXX Unclear whether we should be using target_prefix
		 * XXX here.  --thorpej@wasabisystems.com
		 */
		pathname = xmalloc(strlen(libexec_path) + sizeof ("/lint1"));
		(void)sprintf(pathname, "%s/lint1", libexec_path);
	}

	appcstrg(&args, pathname);
	applst(&args, l1flags);
	appcstrg(&args, cppout);
	appcstrg(&args, ofn);

	runchild(pathname, args, ofn, -1);
	free(pathname);
	freelst(&args);

	appcstrg(&p2in, ofn);
	free(ofn);

	free(args);
}

static void
runchild(const char *path, char *const *args, const char *crfn, int fdout)
{
	int	status, rv, signo, i;

	if (Vflag) {
		for (i = 0; args[i] != NULL; i++)
			(void)printf("%s ", args[i]);
		(void)printf("\n");
	}

	currfn = crfn;

	(void)fflush(stdout);

	switch (vfork()) {
	case -1:
		warn("cannot fork");
		terminate(-1);
		/* NOTREACHED */
	default:
		/* parent */
		break;
	case 0:
		/* child */

		/* setup the standard output if necessary */
		if (fdout != -1) {
			dup2(fdout, STDOUT_FILENO);
			close(fdout);
		}
		(void)execvp(path, args);
		warn("cannot exec %s", path);
		_exit(1);
		/* NOTREACHED */
	}

	while ((rv = wait(&status)) == -1 && errno == EINTR) ;
	if (rv == -1) {
		warn("wait");
		terminate(-1);
	}
	if (WIFSIGNALED(status)) {
		signo = WTERMSIG(status);
#if HAVE_DECL_SYS_SIGNAME
		warnx("%s got SIG%s", path, sys_signame[signo]);
#else
		warnx("%s got signal %d", path, signo);
#endif
		terminate(-1);
	}
	if (WEXITSTATUS(status) != 0)
		terminate(-1);
	currfn = NULL;
}

static void
findlibs(char *const *liblst)
{
	int	i, k;
	const	char *lib, *path;
	char	*lfn;
	size_t	len;

	lfn = NULL;

	for (i = 0; (lib = liblst[i]) != NULL; i++) {
		for (k = 0; (path = libsrchpath[k]) != NULL; k++) {
			len = strlen(path) + strlen(lib);
			lfn = xrealloc(lfn, len + sizeof ("/llib-l.ln"));
			(void)sprintf(lfn, "%s/llib-l%s.ln", path, lib);
			if (rdok(lfn))
				break;
			lfn = xrealloc(lfn, len + sizeof ("/lint/llib-l.ln"));
			(void)sprintf(lfn, "%s/lint/llib-l%s.ln", path, lib);
			if (rdok(lfn))
				break;
		}
		if (path != NULL) {
			appstrg(&l2libs, concat2("-l", lfn));
		} else {
			warnx("cannot find llib-l%s.ln", lib);
		}
	}

	free(lfn);
}

static int
rdok(const char *path)
{
	struct	stat sbuf;

	if (stat(path, &sbuf) == -1)
		return (0);
	if (!S_ISREG(sbuf.st_mode))
		return (0);
	if (access(path, R_OK) == -1)
		return (0);
	return (1);
}

static void
lint2(void)
{
	char	*path, **args;

	args = xcalloc(1, sizeof (char *));

	if (!Bflag) {
		path = xmalloc(strlen(PATH_LIBEXEC) + sizeof ("/lint2") +
		    strlen(target_prefix));
		(void)sprintf(path, "%s/%slint2", PATH_LIBEXEC,
		    target_prefix);
	} else {
		/*
		 * XXX Unclear whether we should be using target_prefix
		 * XXX here.  --thorpej@wasabisystems.com
		 */
		path = xmalloc(strlen(libexec_path) + sizeof ("/lint2"));
		(void)sprintf(path, "%s/lint2", libexec_path);
	}

	appcstrg(&args, path);
	applst(&args, l2flags);
	applst(&args, l2libs);
	applst(&args, p2in);

	runchild(path, args, p2out, -1);
	free(path);
	freelst(&args);
	free(args);
}

static void
cat(char *const *srcs, const char *dest)
{
	int	ifd, ofd, i;
	char	*src, *buf;
	ssize_t	rlen;

	if ((ofd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0666)) == -1) {
		warn("cannot open %s", dest);
		terminate(-1);
	}

	buf = xmalloc(MBLKSIZ);

	for (i = 0; (src = srcs[i]) != NULL; i++) {
		if ((ifd = open(src, O_RDONLY)) == -1) {
			free(buf);
			warn("cannot open %s", src);
			terminate(-1);
		}
		do {
			if ((rlen = read(ifd, buf, MBLKSIZ)) == -1) {
				free(buf);
				warn("read error on %s", src);
				terminate(-1);
			}
			if (write(ofd, buf, (size_t)rlen) == -1) {
				free(buf);
				warn("write error on %s", dest);
				terminate(-1);
			}
		} while (rlen == MBLKSIZ);
		(void)close(ifd);
	}
	(void)close(ofd);
	free(buf);
}
