xref: /freebsd/usr.bin/ident/ident.c (revision e2340276fc734a1f0bd0d2cf16fcfba7936c9462)
1 /*-
2  * Copyright (c) 2015-2021 Baptiste Daroussin <bapt@FreeBSD.org>
3  * Copyright (c) 2015 Xin LI <delphij@FreeBSD.org>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer
10  *    in this position and unchanged.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include <sys/cdefs.h>
28 #include <sys/capsicum.h>
29 #include <sys/types.h>
30 #include <sys/sbuf.h>
31 
32 #include <capsicum_helpers.h>
33 #include <ctype.h>
34 #include <err.h>
35 #include <errno.h>
36 #include <stdbool.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <xlocale.h>
42 
43 typedef enum {
44 	/* state	condition to transit to next state */
45 	INIT,		/* '$' */
46 	DELIM_SEEN,	/* letter */
47 	KEYWORD,	/* punctuation mark */
48 	PUNC_SEEN,	/* ':' -> _SVN; space -> TEXT */
49 	PUNC_SEEN_SVN,	/* space */
50 	TEXT
51 } analyzer_states;
52 
53 static int
54 scan(FILE *fp, const char *name, bool quiet)
55 {
56 	int c;
57 	bool hasid = false;
58 	bool subversion = false;
59 	analyzer_states state = INIT;
60 	FILE* buffp;
61 	char *buf;
62 	size_t sz;
63 	locale_t l;
64 
65 	l = newlocale(LC_ALL_MASK, "C", NULL);
66 	sz = 0;
67 	buf = NULL;
68 	buffp = open_memstream(&buf, &sz);
69 	if (buffp == NULL)
70 		err(EXIT_FAILURE, "open_memstream()");
71 
72 	if (name != NULL)
73 		printf("%s:\n", name);
74 
75 	while ((c = fgetc(fp)) != EOF) {
76 		switch (state) {
77 		case INIT:
78 			if (c == '$') {
79 				/* Transit to DELIM_SEEN if we see $ */
80 				state = DELIM_SEEN;
81 			} else {
82 				/* Otherwise, stay in INIT state */
83 				continue;
84 			}
85 			break;
86 		case DELIM_SEEN:
87 			if (isalpha_l(c, l)) {
88 				/* Transit to KEYWORD if we see letter */
89 				if (buf != NULL)
90 					memset(buf, 0, sz);
91 				rewind(buffp);
92 				fputc('$', buffp);
93 				fputc(c, buffp);
94 				state = KEYWORD;
95 
96 				continue;
97 			} else if (c == '$') {
98 				/* Or, stay in DELIM_SEEN if more $ */
99 				continue;
100 			} else {
101 				/* Otherwise, transit back to INIT */
102 				state = INIT;
103 			}
104 			break;
105 		case KEYWORD:
106 			fputc(c, buffp);
107 
108 			if (isalpha_l(c, l)) {
109 				/*
110 				 * Stay in KEYWORD if additional letter is seen
111 				 */
112 				continue;
113 			} else if (c == ':') {
114 				/*
115 				 * See ':' for the first time, transit to
116 				 * PUNC_SEEN.
117 				 */
118 				state = PUNC_SEEN;
119 				subversion = false;
120 			} else if (c == '$') {
121 				/*
122 				 * Incomplete ident.  Go back to DELIM_SEEN
123 				 * state because we see a '$' which could be
124 				 * the beginning of a keyword.
125 				 */
126 				state = DELIM_SEEN;
127 			} else {
128 				/*
129 				 * Go back to INIT state otherwise.
130 				 */
131 				state = INIT;
132 			}
133 			break;
134 		case PUNC_SEEN:
135 		case PUNC_SEEN_SVN:
136 			fputc(c, buffp);
137 
138 			switch (c) {
139 			case ':':
140 				/*
141 				 * If we see '::' (seen : in PUNC_SEEN),
142 				 * activate subversion treatment and transit
143 				 * to PUNC_SEEN_SVN state.
144 				 *
145 				 * If more than two :'s were seen, the ident
146 				 * is invalid and we would therefore go back
147 				 * to INIT state.
148 				 */
149 				if (state == PUNC_SEEN) {
150 					state = PUNC_SEEN_SVN;
151 					subversion = true;
152 				} else {
153 					state = INIT;
154 				}
155 				break;
156 			case ' ':
157 				/*
158 				 * A space after ':' or '::' indicates we are at the
159 				 * last component of potential ident.
160 				 */
161 				state = TEXT;
162 				break;
163 			default:
164 				/* All other characters are invalid */
165 				state = INIT;
166 				break;
167 			}
168 			break;
169 		case TEXT:
170 			fputc(c, buffp);
171 
172 			if (iscntrl_l(c, l)) {
173 				/* Control characters are not allowed in this state */
174 				state = INIT;
175 			} else if (c == '$') {
176 				fflush(buffp);
177 				/*
178 				 * valid ident should end with a space.
179 				 *
180 				 * subversion extension uses '#' to indicate that
181 				 * the keyword expansion have exceeded the fixed
182 				 * width, so it is also permitted if we are in
183 				 * subversion mode.  No length check is enforced
184 				 * because GNU RCS ident(1) does not do it either.
185 				 */
186 				c = buf[strlen(buf) -2 ];
187 				if (c == ' ' || (subversion && c == '#')) {
188 					printf("     %s\n", buf);
189 					hasid = true;
190 				}
191 				state = INIT;
192 			}
193 			/* Other characters: stay in the state */
194 			break;
195 		}
196 	}
197 	fclose(buffp);
198 	free(buf);
199 	freelocale(l);
200 
201 	if (!hasid) {
202 		if (!quiet)
203 			fprintf(stderr, "%s warning: no id keywords in %s\n",
204 			    getprogname(), name ? name : "standard input");
205 
206 		return (EXIT_FAILURE);
207 	}
208 
209 	return (EXIT_SUCCESS);
210 }
211 
212 int
213 main(int argc, char **argv)
214 {
215 	bool quiet = false;
216 	int ch, i, *fds, fd;
217 	int ret = EXIT_SUCCESS;
218 	size_t nfds;
219 	FILE *fp;
220 
221 	while ((ch = getopt(argc, argv, "qV")) != -1) {
222 		switch (ch) {
223 		case 'q':
224 			quiet = true;
225 			break;
226 		case 'V':
227 			/* Do nothing, compat with GNU rcs's ident */
228 			return (EXIT_SUCCESS);
229 		default:
230 			errx(EXIT_FAILURE, "usage: %s [-q] [-V] [file...]",
231 			    getprogname());
232 		}
233 	}
234 
235 	argc -= optind;
236 	argv += optind;
237 
238 	if (caph_limit_stdio() < 0)
239 		err(EXIT_FAILURE, "unable to limit stdio");
240 
241 	if (argc == 0) {
242 		nfds = 1;
243 		fds = malloc(sizeof(*fds));
244 		if (fds == NULL)
245 			err(EXIT_FAILURE, "unable to allocate fds array");
246 		fds[0] = STDIN_FILENO;
247 	} else {
248 		nfds = argc;
249 		fds = malloc(sizeof(*fds) * nfds);
250 		if (fds == NULL)
251 			err(EXIT_FAILURE, "unable to allocate fds array");
252 
253 		for (i = 0; i < argc; i++) {
254 			fds[i] = fd = open(argv[i], O_RDONLY);
255 			if (fd < 0) {
256 				warn("%s", argv[i]);
257 				ret = EXIT_FAILURE;
258 				continue;
259 			}
260 			if (caph_limit_stream(fd, CAPH_READ) < 0)
261 				err(EXIT_FAILURE,
262 				    "unable to limit fcntls/rights for %s",
263 				    argv[i]);
264 		}
265 	}
266 
267 	/* Enter Capsicum sandbox. */
268 	if (caph_enter() < 0)
269 		err(EXIT_FAILURE, "unable to enter capability mode");
270 
271 	for (i = 0; i < (int)nfds; i++) {
272 		if (fds[i] < 0)
273 			continue;
274 
275 		fp = fdopen(fds[i], "r");
276 		if (fp == NULL) {
277 			warn("%s", argv[i]);
278 			ret = EXIT_FAILURE;
279 			continue;
280 		}
281 		if (scan(fp, argc == 0 ? NULL : argv[i], quiet) != EXIT_SUCCESS)
282 			ret = EXIT_FAILURE;
283 		fclose(fp);
284 	}
285 
286 	return (ret);
287 }
288