xref: /freebsd/usr.bin/ident/ident.c (revision a0b9e2e854027e6ff61fb075a1309dbc71c42b54)
1 /*-
2  * Copyright (c) 2015 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 __FBSDID("$FreeBSD$");
29 
30 #include <sys/capsicum.h>
31 #include <sys/types.h>
32 #include <sys/sbuf.h>
33 
34 #include <capsicum_helpers.h>
35 #include <ctype.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <stdbool.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <unistd.h>
42 #include <xlocale.h>
43 
44 typedef enum {
45 	/* state	condition to transit to next state */
46 	INIT,		/* '$' */
47 	DELIM_SEEN,	/* letter */
48 	KEYWORD,	/* punctuation mark */
49 	PUNC_SEEN,	/* ':' -> _SVN; space -> TEXT */
50 	PUNC_SEEN_SVN,	/* space */
51 	TEXT
52 } analyzer_states;
53 
54 static int
55 scan(FILE *fp, const char *name, bool quiet)
56 {
57 	int c;
58 	bool hasid = false;
59 	bool subversion = false;
60 	analyzer_states state = INIT;
61 	struct sbuf *id = sbuf_new_auto();
62 	locale_t l;
63 
64 	l = newlocale(LC_ALL_MASK, "C", NULL);
65 
66 	if (name != NULL)
67 		printf("%s:\n", name);
68 
69 	while ((c = fgetc(fp)) != EOF) {
70 		switch (state) {
71 		case INIT:
72 			if (c == '$') {
73 				/* Transit to DELIM_SEEN if we see $ */
74 				state = DELIM_SEEN;
75 			} else {
76 				/* Otherwise, stay in INIT state */
77 				continue;
78 			}
79 			break;
80 		case DELIM_SEEN:
81 			if (isalpha_l(c, l)) {
82 				/* Transit to KEYWORD if we see letter */
83 				sbuf_clear(id);
84 				sbuf_putc(id, '$');
85 				sbuf_putc(id, c);
86 				state = KEYWORD;
87 
88 				continue;
89 			} else if (c == '$') {
90 				/* Or, stay in DELIM_SEEN if more $ */
91 				continue;
92 			} else {
93 				/* Otherwise, transit back to INIT */
94 				state = INIT;
95 			}
96 			break;
97 		case KEYWORD:
98 			sbuf_putc(id, c);
99 
100 			if (isalpha_l(c, l)) {
101 				/*
102 				 * Stay in KEYWORD if additional letter is seen
103 				 */
104 				continue;
105 			} else if (c == ':') {
106 				/*
107 				 * See ':' for the first time, transit to
108 				 * PUNC_SEEN.
109 				 */
110 				state = PUNC_SEEN;
111 				subversion = false;
112 			} else if (c == '$') {
113 				/*
114 				 * Incomplete ident.  Go back to DELIM_SEEN
115 				 * state because we see a '$' which could be
116 				 * the beginning of a keyword.
117 				 */
118 				state = DELIM_SEEN;
119 			} else {
120 				/*
121 				 * Go back to INIT state otherwise.
122 				 */
123 				state = INIT;
124 			}
125 			break;
126 		case PUNC_SEEN:
127 		case PUNC_SEEN_SVN:
128 			sbuf_putc(id, c);
129 
130 			switch (c) {
131 			case ':':
132 				/*
133 				 * If we see '::' (seen : in PUNC_SEEN),
134 				 * activate subversion treatment and transit
135 				 * to PUNC_SEEN_SVN state.
136 				 *
137 				 * If more than two :'s were seen, the ident
138 				 * is invalid and we would therefore go back
139 				 * to INIT state.
140 				 */
141 				if (state == PUNC_SEEN) {
142 					state = PUNC_SEEN_SVN;
143 					subversion = true;
144 				} else {
145 					state = INIT;
146 				}
147 				break;
148 			case ' ':
149 				/*
150 				 * A space after ':' or '::' indicates we are at the
151 				 * last component of potential ident.
152 				 */
153 				state = TEXT;
154 				break;
155 			default:
156 				/* All other characters are invalid */
157 				state = INIT;
158 				break;
159 			}
160 			break;
161 		case TEXT:
162 			sbuf_putc(id, c);
163 
164 			if (iscntrl_l(c, l)) {
165 				/* Control characters are not allowed in this state */
166 				state = INIT;
167 			} else if (c == '$') {
168 				sbuf_finish(id);
169 				/*
170 				 * valid ident should end with a space.
171 				 *
172 				 * subversion extension uses '#' to indicate that
173 				 * the keyword expansion have exceeded the fixed
174 				 * width, so it is also permitted if we are in
175 				 * subversion mode.  No length check is enforced
176 				 * because GNU RCS ident(1) does not do it either.
177 				 */
178 				c = sbuf_data(id)[sbuf_len(id) - 2];
179 				if (c == ' ' || (subversion && c == '#')) {
180 					printf("     %s\n", sbuf_data(id));
181 					hasid = true;
182 				}
183 				state = INIT;
184 			}
185 			/* Other characters: stay in the state */
186 			break;
187 		}
188 	}
189 	sbuf_delete(id);
190 	freelocale(l);
191 
192 	if (!hasid) {
193 		if (!quiet)
194 			fprintf(stderr, "%s warning: no id keywords in %s\n",
195 			    getprogname(), name ? name : "standard input");
196 
197 		return (EXIT_FAILURE);
198 	}
199 
200 	return (EXIT_SUCCESS);
201 }
202 
203 int
204 main(int argc, char **argv)
205 {
206 	bool quiet = false;
207 	int ch, i, *fds, fd;
208 	int ret = EXIT_SUCCESS;
209 	size_t nfds;
210 	FILE *fp;
211 
212 	while ((ch = getopt(argc, argv, "qV")) != -1) {
213 		switch (ch) {
214 		case 'q':
215 			quiet = true;
216 			break;
217 		case 'V':
218 			/* Do nothing, compat with GNU rcs's ident */
219 			return (EXIT_SUCCESS);
220 		default:
221 			errx(EXIT_FAILURE, "usage: %s [-q] [-V] [file...]",
222 			    getprogname());
223 		}
224 	}
225 
226 	argc -= optind;
227 	argv += optind;
228 
229 	if (caph_limit_stdio() < 0)
230 		err(EXIT_FAILURE, "unable to limit stdio");
231 
232 	if (argc == 0) {
233 		nfds = 1;
234 		fds = malloc(sizeof(*fds));
235 		if (fds == NULL)
236 			err(EXIT_FAILURE, "unable to allocate fds array");
237 		fds[0] = STDIN_FILENO;
238 	} else {
239 		nfds = argc;
240 		fds = malloc(sizeof(*fds) * nfds);
241 		if (fds == NULL)
242 			err(EXIT_FAILURE, "unable to allocate fds array");
243 
244 		for (i = 0; i < argc; i++) {
245 			fds[i] = fd = open(argv[i], O_RDONLY);
246 			if (fd < 0) {
247 				warn("%s", argv[i]);
248 				ret = EXIT_FAILURE;
249 				continue;
250 			}
251 			if (caph_limit_stream(fd, CAPH_READ) < 0)
252 				err(EXIT_FAILURE,
253 				    "unable to limit fcntls/rights for %s",
254 				    argv[i]);
255 		}
256 	}
257 
258 	/* Enter Capsicum sandbox. */
259 	if (caph_enter() < 0)
260 		err(EXIT_FAILURE, "unable to enter capability mode");
261 
262 	for (i = 0; i < (int)nfds; i++) {
263 		if (fds[i] < 0)
264 			continue;
265 
266 		fp = fdopen(fds[i], "r");
267 		if (fp == NULL) {
268 			warn("%s", argv[i]);
269 			ret = EXIT_FAILURE;
270 			continue;
271 		}
272 		if (scan(fp, argc == 0 ? NULL : argv[i], quiet) != EXIT_SUCCESS)
273 			ret = EXIT_FAILURE;
274 		fclose(fp);
275 	}
276 
277 	return (ret);
278 }
279