#if HAVE_NBTOOL_CONFIG_H
#include "nbtool_config.h"
#endif
#include <sys/cdefs.h>
#if defined(__RCSID)
__RCSID("$NetBSD: xlint.c,v 1.126 2024/12/08 17:12:01 rillig Exp $");
#endif
#include <sys/param.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <errno.h>
#include <stdarg.h>
#include <fcntl.h>
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <util.h>
#include "lint.h"
#include "pathnames.h"
#include "findcc.h"
typedef struct {
char **items;
size_t len;
size_t cap;
} list;
static struct {
list flags;
list lcflags;
char *outfile;
int output_fd;
} cpp;
static struct {
list flags;
list outfiles;
} lint1;
static struct {
list flags;
list input_files;
list input_libraries;
char *output_library;
} lint2;
static const char *tmpdir;
static list default_libraries;
static list additional_libraries;
static list library_search_path;
static const char *libexec_dir;
static bool Cflag, dflag, Fflag, iflag, sflag, tflag, Vflag;
static char *output_filename;
static bool seen_filename;
static const char *currfn;
#if !defined(TARGET_PREFIX)
#define TARGET_PREFIX ""
#endif
static const char target_prefix[] = TARGET_PREFIX;
static void
list_add_ref(list *l, char *s)
{
if (l->len >= l->cap) {
l->cap = 2 * l->len + 16;
l->items = xrealloc(l->items, sizeof(*l->items) * l->cap);
}
l->items[l->len++] = s;
}
static void
list_add(list *l, const char *s)
{
list_add_ref(l, xstrdup(s));
}
static void
list_add_flag(list *l, int c)
{
list_add(l, (const char[3]){ '-', (char)c, '\0' });
}
static void
list_add_unique(list *l, const char *s)
{
for (size_t i = 0; i < l->len; i++)
if (strcmp(l->items[i], s) == 0)
return;
list_add(l, s);
}
static void
list_add_all(list *dst, const list *src)
{
for (size_t i = 0; i < src->len; i++)
list_add(dst, src->items[i]);
}
static void
list_clear(list *l)
{
while (l->len > 0)
free(l->items[--l->len]);
}
static char *
concat2(const char *s1, const char *s2)
{
size_t len1 = strlen(s1);
size_t len2 = strlen(s2);
char *s = xrealloc(NULL, len1 + len2 + 1);
memcpy(s, s1, len1);
memcpy(s + len1, s2, len2 + 1);
return s;
}
static void
set_tmpdir(void)
{
const char *tmp;
size_t len;
tmpdir = (tmp = getenv("TMPDIR")) != NULL && (len = strlen(tmp)) != 0
? concat2(tmp, tmp[len - 1] == '/' ? "" : "/")
: xstrdup(_PATH_TMP);
}
__dead static void
terminate(int signo)
{
if (cpp.output_fd != -1)
(void)close(cpp.output_fd);
if (cpp.outfile != NULL) {
const char *keep_env = getenv("LINT_KEEP_CPPOUT");
bool keep = keep_env != NULL && (strcmp(keep_env, "yes") == 0
|| (strcmp(keep_env, "on-error") == 0 && signo != 0));
if (keep)
(void)printf("lint: preprocessor output kept in %s\n",
cpp.outfile);
else
(void)remove(cpp.outfile);
}
for (size_t i = 0; i < lint1.outfiles.len; i++)
(void)remove(lint1.outfiles.items[i]);
if (lint2.output_library != NULL)
(void)remove(lint2.output_library);
if (currfn != NULL && currfn != cpp.outfile)
(void)remove(currfn);
if (signo != 0)
(void)raise_default_signal(signo);
exit(signo != 0 ? 1 : 0);
}
__dead __printflike(1, 2) static void
usage(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
(void)vfprintf(stderr, fmt, ap);
va_end(ap);
if (fmt[0] != '\0')
(void)fprintf(stderr, "\n");
const char *name = getprogname();
int indent = (int)(strlen("usage: ") + strlen(name));
(void)fprintf(stderr,
"usage: %s [-abceghprstvwxzFHPSTV] [-Alevel] [-i|-nu]\n"
"%*s [-Dname[=def]] [-Uname] [-Idirectory] "
"[-M...] [-W...] [-Z ...]\n"
"%*s [-ddirectory] [-Ldirectory] [-llibrary] [-ooutputfile]\n"
"%*s [-Bpath] [-X id,...] [-q id,...] [-R old=new] file ...\n",
name, indent, "", indent, "", indent, "");
(void)fprintf(stderr,
" %s [-abceghprstvwzFHPSTV] [-Alevel] -Clibrary\n"
"%*s [-Bpath] [-R old=new] file ...\n",
name, indent, "");
terminate(-1);
}
static const char *
skip_last(const char *path, int delim)
{
const char *base = path;
for (const char *p = path; *p != '\0'; p++)
if (*p == delim)
base = p + 1;
return base;
}
static bool
is_safe_shell(char ch)
{
return ch_isalnum(ch)
|| ch == '%' || ch == '+' || ch == ',' || ch == '-' || ch == '.'
|| ch == '/' || ch == ':' || ch == '=' || ch == '@' || ch == '_';
}
static void
print_sh_quoted(const char *s)
{
if (s[0] == '\0')
goto needs_quoting;
for (const char *p = s; *p != '\0'; p++)
if (!is_safe_shell(*p))
goto needs_quoting;
(void)printf("%s", s);
return;
needs_quoting:
(void)putchar('\'');
for (const char *p = s; *p != '\0'; p++) {
if (*p == '\'')
(void)printf("'\\''");
else
(void)putchar(*p);
}
(void)putchar('\'');
}
static void
run_child(const char *path, const list *args, const char *crfn, int fdout)
{
if (Vflag) {
print_sh_quoted(args->items[0]);
for (size_t i = 1; i < args->len - 1; i++) {
(void)printf(" ");
print_sh_quoted(args->items[i]);
}
(void)printf("\n");
}
currfn = crfn;
(void)fflush(stdout);
switch (vfork()) {
case -1:
warn("cannot fork");
terminate(-1);
default:
break;
case 0:
if (fdout != -1) {
(void)dup2(fdout, STDOUT_FILENO);
(void)close(fdout);
}
(void)execvp(path, args->items);
warn("cannot exec %s", path);
_exit(1);
}
int status, rv, signo;
while ((rv = wait(&status)) == -1 && errno == EINTR)
continue;
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
run_cpp(const char *name)
{
const char *cc = getenv("CC");
if (cc == NULL)
cc = DEFAULT_CC;
char *abs_cc = findcc(cc);
if (abs_cc == NULL && setenv("PATH", _PATH_DEFPATH, 1) == 0)
abs_cc = findcc(cc);
if (abs_cc == NULL) {
(void)fprintf(stderr, "%s: %s: not found\n", getprogname(), cc);
exit(EXIT_FAILURE);
}
list args = { NULL, 0, 0 };
list_add_ref(&args, abs_cc);
list_add_all(&args, &cpp.flags);
list_add_all(&args, &cpp.lcflags);
list_add(&args, name);
list_add_ref(&args, NULL);
if (lseek(cpp.output_fd, 0, SEEK_SET) != 0) {
warn("lseek");
terminate(-1);
}
if (ftruncate(cpp.output_fd, 0) != 0) {
warn("ftruncate");
terminate(-1);
}
run_child(abs_cc, &args, cpp.outfile, cpp.output_fd);
list_clear(&args);
}
static void
run_lint1(const char *out_fname)
{
char *abs_lint1 = libexec_dir != NULL
? concat2(libexec_dir, "/lint1")
: xasprintf("%s/%slint1", PATH_LIBEXEC, target_prefix);
list args = { NULL, 0, 0 };
list_add_ref(&args, abs_lint1);
list_add_all(&args, &lint1.flags);
list_add(&args, cpp.outfile);
list_add(&args, out_fname);
list_add_ref(&args, NULL);
run_child(abs_lint1, &args, out_fname, -1);
list_clear(&args);
}
static void
handle_filename(const char *name)
{
const char *base = skip_last(name, '/');
const char *suff = skip_last(base, '.');
if (strcmp(suff, "ln") == 0) {
if (!iflag)
list_add(&lint2.input_files, name);
return;
}
if (strcmp(suff, "c") == 0)
goto c_file;
if (strncmp(base, "llib-l", 6) == 0 && base == suff)
goto c_file;
warnx("unknown file type: %s", name);
return;
c_file:
if (!iflag || seen_filename)
(void)printf("%s:\n", Fflag ? name : base);
char *ofn;
if (output_filename != NULL) {
ofn = output_filename;
output_filename = NULL;
} else if (iflag) {
size_t len = base == suff
? strlen(base)
: (size_t)((suff - 1) - base);
ofn = xasprintf("%.*s.ln", (int)len, base);
} else {
ofn = xasprintf("%slint1.XXXXXX", tmpdir);
int fd = mkstemp(ofn);
if (fd == -1) {
warn("can't make temp");
terminate(-1);
}
(void)close(fd);
}
if (!iflag)
list_add(&lint1.outfiles, ofn);
run_cpp(name);
run_lint1(ofn);
list_add(&lint2.input_files, ofn);
free(ofn);
}
static bool
file_is_readable(const char *path)
{
struct stat sbuf;
return stat(path, &sbuf) == 0
&& S_ISREG(sbuf.st_mode)
&& access(path, R_OK) == 0;
}
static void
find_lib(const char *lib)
{
char *lfn;
for (size_t i = 0; i < library_search_path.len; i++) {
const char *dir = library_search_path.items[i];
lfn = xasprintf("%s/llib-l%s.ln", dir, lib);
if (file_is_readable(lfn))
goto found;
free(lfn);
lfn = xasprintf("%s/lint/llib-l%s.ln", dir, lib);
if (file_is_readable(lfn))
goto found;
free(lfn);
}
warnx("cannot find llib-l%s.ln", lib);
return;
found:
list_add_ref(&lint2.input_libraries, concat2("-l", lfn));
free(lfn);
}
static void
find_libs(const list *l)
{
for (size_t i = 0; i < l->len; i++)
find_lib(l->items[i]);
}
static void
run_lint2(void)
{
char *abs_lint2 = libexec_dir != NULL
? concat2(libexec_dir, "/lint2")
: xasprintf("%s/%slint2", PATH_LIBEXEC, target_prefix);
list args = { NULL, 0, 0 };
list_add_ref(&args, abs_lint2);
list_add_all(&args, &lint2.flags);
list_add_all(&args, &lint2.input_libraries);
list_add_all(&args, &lint2.input_files);
list_add_ref(&args, NULL);
run_child(abs_lint2, &args, lint2.output_library, -1);
list_clear(&args);
}
static void
cat(const list *srcs, const char *dest)
{
int ifd, ofd;
ssize_t rlen;
char buf[0x4000];
if ((ofd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0666)) == -1) {
warn("cannot open %s", dest);
terminate(-1);
}
for (size_t i = 0; i < srcs->len; i++) {
const char *src = srcs->items[i];
if ((ifd = open(src, O_RDONLY)) == -1) {
warn("cannot open %s", src);
terminate(-1);
}
do {
if ((rlen = read(ifd, buf, sizeof(buf))) == -1) {
warn("read error on %s", src);
terminate(-1);
}
if (write(ofd, buf, (size_t)rlen) != rlen) {
warn("write error on %s", dest);
terminate(-1);
}
} while (rlen == sizeof(buf));
(void)close(ifd);
}
(void)close(ofd);
}
int
main(int argc, char *argv[])
{
setprogname(argv[0]);
set_tmpdir();
cpp.outfile = concat2(tmpdir, "lint0.XXXXXX");
cpp.output_fd = mkstemp(cpp.outfile);
if (cpp.output_fd == -1) {
warn("can't make temp");
terminate(-1);
}
list_add(&cpp.flags, "-E");
list_add(&cpp.flags, "-x");
list_add(&cpp.flags, "c");
list_add(&cpp.flags, "-U__GNUC__");
list_add(&cpp.flags, "-U__PCC__");
list_add(&cpp.flags, "-U__SSE__");
list_add(&cpp.flags, "-U__SSE4_1__");
list_add(&cpp.flags, "-Wp,-CC");
list_add(&cpp.flags, "-Wcomment");
list_add(&cpp.flags, "-D__LINT__");
list_add(&cpp.flags, "-Dlint");
list_add(&cpp.flags, "-D__lint");
list_add(&cpp.flags, "-D__lint__");
list_add(&cpp.flags, "-dD");
list_add(&default_libraries, "c");
if (signal(SIGHUP, terminate) == SIG_IGN)
(void)signal(SIGHUP, SIG_IGN);
(void)signal(SIGINT, terminate);
(void)signal(SIGQUIT, terminate);
(void)signal(SIGTERM, terminate);
int c;
while ((c = getopt(argc, argv,
"abcd:eghil:no:pq:rstuvwxzA:B:C:D:FHI:L:M:PR:STU:VW:X:Z:")) != -1) {
switch (c) {
case 'a':
case 'b':
case 'c':
case 'e':
case 'g':
case 'r':
case 'v':
case 'w':
case 'z':
case 'P':
list_add_flag(&lint1.flags, c);
break;
case 'A':
case 'q':
case 'R':
case 'X':
list_add_flag(&lint1.flags, c);
list_add(&lint1.flags, optarg);
break;
case 'F':
Fflag = true;
case 'h':
list_add_flag(&lint1.flags, c);
list_add_flag(&lint2.flags, c);
break;
case 'i':
if (Cflag)
usage("%c and %s flags cannot be specified "
"together", 'C', "i");
iflag = true;
break;
case 'n':
list_clear(&default_libraries);
break;
case 'p':
if (default_libraries.len > 0) {
list_clear(&default_libraries);
list_add(&default_libraries, "c");
}
list_add_flag(&lint1.flags, c);
break;
case 's':
if (tflag)
usage("%c and %s flags cannot be specified "
"together", 's', "t");
list_clear(&cpp.lcflags);
list_add(&cpp.lcflags, "-trigraphs");
list_add(&cpp.lcflags, "-Wtrigraphs");
list_add(&cpp.lcflags, "-pedantic");
list_add(&cpp.lcflags, "-D__STRICT_ANSI__");
sflag = true;
list_add_flag(&lint1.flags, c);
list_add_flag(&lint2.flags, c);
break;
case 'S':
if (tflag)
usage("%c and %s flags cannot be specified "
"together", 'S', "t");
list_add_flag(&lint1.flags, c);
break;
case 'T':
list_add(&cpp.flags, "-I" PATH_STRICT_BOOL_INCLUDE);
list_add_flag(&lint1.flags, c);
break;
#if !HAVE_NBTOOL_CONFIG_H
case 't':
if (sflag)
usage("%c and %s flags cannot be specified "
"together", 's', "t");
tflag = true;
list_clear(&cpp.lcflags);
list_add(&cpp.lcflags, "-traditional");
list_add(&cpp.lcflags, "-Wtraditional");
list_add(&cpp.lcflags, "-D" MACHINE);
list_add(&cpp.lcflags, "-D" MACHINE_ARCH);
list_add_flag(&lint1.flags, c);
list_add_flag(&lint2.flags, c);
break;
#endif
case 'u':
case 'x':
case 'H':
list_add_flag(&lint2.flags, c);
break;
case 'C':
if (Cflag)
usage("%c flag already specified", 'C');
if (output_filename != NULL || iflag)
usage("%c and %s flags cannot be specified "
"together", 'C', "o or i");
Cflag = true;
list_add_flag(&lint2.flags, c);
list_add(&lint2.flags, optarg);
lint2.output_library = xasprintf("llib-l%s.ln", optarg);
list_clear(&default_libraries);
break;
case 'd':
if (dflag)
usage("%c flag already specified", 'd');
dflag = true;
list_add(&cpp.flags, "--sysroot");
list_add(&cpp.flags, optarg);
break;
case 'D':
case 'I':
case 'M':
case 'U':
case 'W':
list_add_ref(&cpp.flags,
xasprintf("-%c%s", c, optarg));
break;
case 'l':
list_add_unique(&additional_libraries, optarg);
break;
case 'o':
if (output_filename != NULL)
usage("%c flag already specified", 'o');
if (Cflag)
usage("%c and %s flags cannot be specified "
"together", 'C', "o");
output_filename = xstrdup(optarg);
break;
case 'L':
list_add_unique(&library_search_path, optarg);
break;
case 'B':
libexec_dir = xstrdup(optarg);
break;
case 'V':
Vflag = true;
break;
case 'Z':
list_add(&cpp.flags, optarg);
break;
default:
usage("");
}
}
argc -= optind;
argv += optind;
for (; argc > 0; argc--, argv++) {
const char *arg = argv[0];
if (arg[0] == '-') {
list *lp;
if (arg[1] == 'l')
lp = &additional_libraries;
else if (arg[1] == 'L')
lp = &library_search_path;
else
usage("Unknown late option '%s'", arg);
if (arg[2] != '\0')
list_add_unique(lp, arg + 2);
else if (argc > 1) {
argc--, argv++;
list_add_unique(lp, argv[0]);
} else
usage("Missing argument for l or L");
} else {
handle_filename(arg);
seen_filename = true;
}
}
if (!seen_filename)
usage("Missing filename");
if (iflag)
terminate(0);
if (output_filename == NULL) {
const char *ks = getenv("LIBDIR");
if (ks == NULL || ks[0] == '\0')
ks = PATH_LINTLIB;
list_add(&library_search_path, ks);
find_libs(&additional_libraries);
find_libs(&default_libraries);
}
run_lint2();
if (output_filename != NULL)
cat(&lint2.input_files, output_filename);
if (Cflag)
lint2.output_library = NULL;
terminate(0);
}