/* 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 "); 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 , 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 "); 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 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 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 :"); path = ""; } 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; }