summaryrefslogtreecommitdiffstats
path: root/mklint.c
blob: 1c17eea2aae84ecf040ccbc88010f2d067aa94a5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/* See LICENSE file for copyright and license details. */
#include "common.h"

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



int exit_status = 0;

static const char *const 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++; i < ELEMSOF(default_makefiles); i++)
		if (!access(default_makefiles[i], F_OK))
			warnf_warning(WC_EXTRA_MAKEFILE, "found additional standard makefile: %s",
			              default_makefiles[i]);

	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;
}