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