summaryrefslogblamecommitdiffstats
path: root/makelint.c
blob: aec636e5becbbe9a38ddf734ac29a4878bedfe7c (plain) (tree)














































































































































                                                                                                                         
                                                                                                              




















































                                                                             
/* See LICENSE file for copyright and license details. */
#include "common.h"

NUSAGE(EXIT_ERROR, "[-f makefile]");



int exit_status = 0;

static const char *default_makefiles[] = {
	"makefile",
	"Makefile"
};


static int
open_default_makefile(const char **pathp)
{
	int fd;
	size_t i;

	/* The specification says that the alternatives “shall be
	 * tried”, but it uses the phrase “if neither … are found”,
	 * implying that make(1) shall fail if a file cannot be
	 * opened and only try the next alternative if it failed
	 * becomes the file does not exist.
	 */

	for (i = 0; i < ELEMSOF(default_makefiles); i++) {
		*pathp = default_makefiles[i];
		fd = open(*pathp, O_RDONLY);
		if (fd >= 0) {
			printinfof(WC_MAKEFILE, "found standard makefile to use: %s", *pathp);
			goto find_existing_fallbacks;
		} else if (errno != ENOENT) {
			eprintf("found standard makefile to use, but failed to open: %s:", *pathp);
		}
	}

	printerrorf("couldn't find any makefile to use, portable "
	            "alternatives are ./makefile and ./Makefile");

find_existing_fallbacks:
	for (; i < ELEMSOF(default_makefiles); i++)
		if (!access(default_makefiles[i], F_OK))
			warnf_warning(WC_EXTRA_MAKEFILE, "found additional standard makefile: %s", *pathp);

	return fd;
}


static void
cmdline_opt_f(const char *arg, const char **makefile_pathp)
{
	static int warning_emitted = 0;

	if (*makefile_pathp && !warning_emitted) {
		warning_emitted = 1;
		warnf_unspecified(WC_CMDLINE, "the -f option has been specified multiple times, "
		                              "they are processed in order, but the behaviour is "
	                                      "otherwise unspecified");
		printinfof(WC_CMDLINE, "this implementation will use the last "
		                       "option and discard earlier options");
	}

	*makefile_pathp = arg;
}


static struct line *
load_file(int fd, const char *fname, size_t *nlinesp)
{
	struct line *lines;
	char *buf = NULL, *p;
	size_t size = 0;
	size_t len = 0;
	size_t i;
	ssize_t r;

	/* getline(3) may seem like the best way to read line by line,
	 * however, it may terminate before the end of the line is
	 * reached, which we would have to deal with, additionally,
	 * we want to check for null bytes. Therefore we will keep
	 * this simple and use read(3) and scan manually; and as a
	 * bonus we can leave the file descriptor open, and let the
	 * caller than created it close it.
	 */

	i = 0;
	*nlinesp = 0;
	for (;;) {
		if (len == size)
			buf = erealloc(buf, size += 2048);
		r = read(fd, &buf[len], size - len);
		if (r > 0)
			len += (size_t)r;
		else if (!r)
			break;
		else if (errno == EINTR)
			continue;
		else
			eprintf("read %s:", fname);

		for (; i < len; i++) {
			if (buf[i] == '\n') {
				*nlinesp += 1;
				buf[i] = '\0';
			} else if (buf[i] == '\0') {
				/* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_403 */
				warnf_undefined(WC_TEXT, "%s:%zu: file contains a NUL byte, this is disallowed, because "
				                         "input files are text files, and causes undefined behaviour",
				                fname, *nlinesp + 1);
				/* make(1) should probably just abort */
				printinfof(WC_TEXT, "this implementation will replace it with a <space>");
				buf[i] = ' ';
			}
		}
	}

	if (len && buf[len - 1] != '\0') { /* LF has been converted to NUL above */
		/* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_403 */
		warnf_undefined(WC_TEXT, "%s:%zu: is non-empty but does not end with a <newline>, which is "
		                         "required because input files are text files, and omission of it "
		                         "causes undefined behaviour",
		                fname, *nlinesp + 1);
		/* make(1) should probably just abort */
		printinfof(WC_TEXT, "this implementation will add the missing <newline>");
		buf = erealloc(buf, len + 1);
		buf[len++] = '\0';
		*nlinesp += 1;
	}

	lines = *nlinesp ? ecalloc(*nlinesp, sizeof(*lines)) : NULL;
	for (p = buf, i = 0; i < *nlinesp; i++) {
		lines[i].lineno = i + 1;
		lines[i].path = fname;
		lines[i].len = strlen(p);
		lines[i].data = ememdup(p, lines[i].len + 1);
		if (lines[i].len + 1 > 2048) {
			/* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_403 */
			warnf_undefined(WC_TEXT, "%s:%zu: line is, including the <newline> character, longer than "
			                         "2048 bytes which causes undefined behaviour as input files are "
			                         "text files and POSIX only guarantees support for lines up to 2048 "
			                         "bytes long including the <newline> character in text files",
			                fname, *nlinesp + 1);
		}
		p += lines[i].len + 1;
	}

	free(buf);
	return lines;
}


int
main(int argc, char *argv[])
{
	const char *path = NULL;
	int fd;
	struct line *lines;
	size_t nlines;

	libsimple_default_failure_exit = EXIT_ERROR;

	/* make(1) shall support mixing of options and operands (uptil --) */
	ARGBEGIN {
	case 'f':
		cmdline_opt_f(ARG(), &path);
		break;

	default:
		usage();
	} ARGEND;

	if (argc)
		usage();

	if (!path) {
		fd = open_default_makefile(&path);
	} else if (!strcmp(path, "-")) {
		/* “A pathname of '-' shall denote the standard input” */
		fd = dup(STDIN_FILENO);
		if (fd < 0)
			eprintf("dup <stdin>:");
		path = "<stdin>";
	} else {
		fd = open(path, O_RDONLY);
		if (fd < 0)
			eprintf("open %s O_RDONLY:", path);
	}

	lines = load_file(fd, path, &nlines);
	close(fd);

	free(lines);
	return exit_status;
}