diff options
| author | Mattias Andrée <maandree@kth.se> | 2021-12-31 23:20:41 +0100 | 
|---|---|---|
| committer | Mattias Andrée <maandree@kth.se> | 2021-12-31 23:20:41 +0100 | 
| commit | 8f2a555f900228b531edba5e44e2e0cd7e8fe61e (patch) | |
| tree | 51dd5bada07ce28f9022a6410fadcb74a3362310 | |
| download | makel-8f2a555f900228b531edba5e44e2e0cd7e8fe61e.tar.gz makel-8f2a555f900228b531edba5e44e2e0cd7e8fe61e.tar.bz2 makel-8f2a555f900228b531edba5e44e2e0cd7e8fe61e.tar.xz | |
First commit
Signed-off-by: Mattias Andrée <maandree@kth.se>
Diffstat (limited to '')
| -rw-r--r-- | .gitignore | 15 | ||||
| -rw-r--r-- | LICENSE | 15 | ||||
| -rw-r--r-- | Makefile | 39 | ||||
| -rw-r--r-- | common.h | 57 | ||||
| -rw-r--r-- | config.mk | 8 | ||||
| -rw-r--r-- | makelint.c | 197 | ||||
| -rw-r--r-- | ui.c | 47 | 
7 files changed, 378 insertions, 0 deletions
| diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40c3059 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*\#* +*~ +*.o +*.a +*.lo +*.su +*.so +*.so.* +*.dll +*.dylib +*.gch +*.gcov +*.gcno +*.gcda +/makelint @@ -0,0 +1,15 @@ +ISC License + +© 2021 Mattias Andrée <maandree@kth.se> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e67e73d --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OBJ =\ +	makelint.o\ +	ui.o + +HDR =\ +	common.h + +all: makelint +$(OBJ): $(HDR) + +.c.o: +	$(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + +makelint: $(OBJ) +	$(CC) -o $@ $(OBJ) $(LDFLAGS) + +install: makelint +	mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" +	mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1/" +	cp -- makelint "$(DESTDIR)$(PREFIX)/bin/" +	cp -- makelint.1 "$(DESTDIR)$(MANPREFIX)/man1/" + +uninstall: +	-rm -f -- "$(DESTDIR)$(PREFIX)/bin/makelint" +	-rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/makelint.1" + +clean: +	-rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch *.gcov *.gcno *.gcda +	-rm -f -- makelint + +.SUFFIXES: +.SUFFIXES: .o .c + +.PHONY: all install uninstall clean diff --git a/common.h b/common.h new file mode 100644 index 0000000..b2d0a4e --- /dev/null +++ b/common.h @@ -0,0 +1,57 @@ +/* See LICENSE file for copyright and license details. */ +#include <libsimple.h> +#include <libsimple-arg.h> + +#define EXIT_STYLE         1 +#define EXIT_WARNING       2 +#define EXIT_UNSPECIFIED   3 +#define EXIT_NONCONFIRMING 4 +#define EXIT_UNDEFINED     5 +#define EXIT_CRITICAL      6 +#define EXIT_ERROR         7 + + +#define LIST_WARNING_CLASSES(X)\ +	X(WC_MAKEFILE, "makefile", INFORM)\ +	X(WC_EXTRA_MAKEFILE, "extra-makefile", WARN)\ +	X(WC_CMDLINE, "cmdline", WARN)\ +	X(WC_TEXT, "text", WARN) + + +enum action { +	IGNORE, +	INFORM, +	WARN +}; + +enum warning_class { +#define X(ENUM, NAME, ACTION) ENUM, +	LIST_WARNING_CLASSES(X) +#undef X +	NUM_WARNING_CLASS +}; + +struct warning_class_data { +	const char *name; +	enum action action; +}; + +struct line { +	char *data; +	size_t len; +	const char *path; +	size_t lineno; +}; + + +extern int exit_status; + + +/* ui.c */ +extern struct warning_class_data warning_classes[]; +void xprintwarningf(enum warning_class class, int severity, const char *fmt, ...); +#define printinfof(CLASS, ...) xprintwarningf(CLASS, EXIT_STYLE, __VA_ARGS__) +#define warnf_warning(CLASS, ...) xprintwarningf(CLASS, EXIT_WARNING, __VA_ARGS__) +#define warnf_unspecified(CLASS, ...) xprintwarningf(CLASS, EXIT_UNSPECIFIED, __VA_ARGS__) +#define warnf_undefined(CLASS, ...) xprintwarningf(CLASS, EXIT_UNDEFINED, __VA_ARGS__) +void printerrorf(const char *fmt, ...); diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..d354a28 --- /dev/null +++ b/config.mk @@ -0,0 +1,8 @@ +PREFIX    = /usr +MANPREFIX = $(PREFIX)/share/man + +CC = cc + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE +CFLAGS   = -std=c99 -Wall -g +LDFLAGS  = -lsimple diff --git a/makelint.c b/makelint.c new file mode 100644 index 0000000..45f599a --- /dev/null +++ b/makelint.c @@ -0,0 +1,197 @@ +/* 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 " +			                         "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; +} @@ -0,0 +1,47 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +struct warning_class_data warning_classes[] = { +#define X(ENUM, NAME, ACTION) {NAME, ACTION}, +	LIST_WARNING_CLASSES(X) +#undef X +	{NULL, 0} +}; + + +static void +vxprintwarningf(enum warning_class class, int severity, const char *fmt, va_list ap) +{ +	if (warning_classes[class].action != IGNORE) { +		fprintf(stderr, "%s: [%s] ", argv0, +		        warning_classes[class].action == INFORM ? "info" : "warn"); +		vfprintf(stderr, fmt, ap); +		fprintf(stderr, " (-w%s)\n", warning_classes[class].name); +		if (warning_classes[class].action != INFORM) +			exit_status = MAX(exit_status, severity); +	} +} + + +void +xprintwarningf(enum warning_class class, int severity, const char *fmt, ...) +{ +	va_list ap; +	va_start(ap, fmt); +	vxprintwarningf(class, severity, fmt, ap); +	va_end(ap); +} + + +void +printerrorf(const char *fmt, ...) +{ +	va_list ap; +	va_start(ap, fmt); +	fprintf(stderr, "%s: [error] ", argv0); +	vfprintf(stderr, fmt, ap); +	fprintf(stderr, "\n"); +	va_end(ap); +	exit(EXIT_CRITICAL); +} | 
