summaryrefslogtreecommitdiffstats
path: root/mklint.c
diff options
context:
space:
mode:
Diffstat (limited to 'mklint.c')
-rw-r--r--mklint.c197
1 files changed, 197 insertions, 0 deletions
diff --git a/mklint.c b/mklint.c
new file mode 100644
index 0000000..aec636e
--- /dev/null
+++ b/mklint.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 "
+ "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;
+}