xref: /freebsd/usr.bin/ident/ident.c (revision 394f6b1b0a658755a9420906fb7a459c3d9501a5)
1 /*-
2  * Copyright (c) 2026 Dag-Erling Smørgrav <des@FreeBSD.org>
3  * Copyright (c) 2015-2021 Baptiste Daroussin <bapt@FreeBSD.org>
4  * Copyright (c) 2015 Xin LI <delphij@FreeBSD.org>
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/types.h>
29 #include <sys/capsicum.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 <getopt.h>
37 #include <locale.h>
38 #include <stdbool.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 
44 #include <libcasper.h>
45 #include <casper/cap_fileargs.h>
46 
47 typedef enum {
48 	/* state	condition to transit to next state */
49 	INIT,		/* '$' */
50 	DELIM_SEEN,	/* letter */
51 	KEYWORD,	/* punctuation mark */
52 	PUNC_SEEN,	/* ':' -> _SVN; space -> TEXT */
53 	PUNC_SEEN_SVN,	/* space */
54 	TEXT
55 } analyzer_states;
56 
57 static int
scan(FILE * fp,const char * name,bool quiet)58 scan(FILE *fp, const char *name, bool quiet)
59 {
60 	int c;
61 	bool hasid = false;
62 	bool subversion = false;
63 	analyzer_states state = INIT;
64 	FILE *buffp;
65 	char *buf;
66 	size_t sz;
67 
68 	sz = 0;
69 	buf = NULL;
70 	if ((buffp = open_memstream(&buf, &sz)) == NULL)
71 		goto bufferr;
72 
73 	if (name != NULL) {
74 		printf("%s:\n", name);
75 		if (fflush(stdout) == EOF)
76 			err(EXIT_FAILURE, "stdout");
77 	}
78 
79 	while ((c = fgetc(fp)) != EOF) {
80 		switch (state) {
81 		case INIT:
82 			if (c == '$') {
83 				/* Transit to DELIM_SEEN if we see $ */
84 				state = DELIM_SEEN;
85 			} else {
86 				/* Otherwise, stay in INIT state */
87 				continue;
88 			}
89 			break;
90 		case DELIM_SEEN:
91 			if (isalpha(c)) {
92 				/* Transit to KEYWORD if we see letter */
93 				if (buf != NULL)
94 					memset(buf, 0, sz);
95 				if (fseek(buffp, 0, SEEK_SET) != 0 ||
96 				    fputc('$', buffp) == EOF ||
97 				    fputc(c, buffp) == EOF)
98 					goto bufferr;
99 				state = KEYWORD;
100 
101 				continue;
102 			} else if (c == '$') {
103 				/* Or, stay in DELIM_SEEN if more $ */
104 				continue;
105 			} else {
106 				/* Otherwise, transit back to INIT */
107 				state = INIT;
108 			}
109 			break;
110 		case KEYWORD:
111 			if (fputc(c, buffp) == EOF)
112 				goto bufferr;
113 
114 			if (isalpha(c)) {
115 				/*
116 				 * Stay in KEYWORD if additional letter is seen
117 				 */
118 				continue;
119 			} else if (c == ':') {
120 				/*
121 				 * See ':' for the first time, transit to
122 				 * PUNC_SEEN.
123 				 */
124 				state = PUNC_SEEN;
125 				subversion = false;
126 			} else if (c == '$') {
127 				/*
128 				 * Incomplete ident.  Go back to DELIM_SEEN
129 				 * state because we see a '$' which could be
130 				 * the beginning of a keyword.
131 				 */
132 				state = DELIM_SEEN;
133 			} else {
134 				/*
135 				 * Go back to INIT state otherwise.
136 				 */
137 				state = INIT;
138 			}
139 			break;
140 		case PUNC_SEEN:
141 		case PUNC_SEEN_SVN:
142 			if (fputc(c, buffp) == EOF)
143 				goto bufferr;
144 
145 			switch (c) {
146 			case ':':
147 				/*
148 				 * If we see '::' (seen : in PUNC_SEEN),
149 				 * activate subversion treatment and transit
150 				 * to PUNC_SEEN_SVN state.
151 				 *
152 				 * If more than two :'s were seen, the ident
153 				 * is invalid and we would therefore go back
154 				 * to INIT state.
155 				 */
156 				if (state == PUNC_SEEN) {
157 					state = PUNC_SEEN_SVN;
158 					subversion = true;
159 				} else {
160 					state = INIT;
161 				}
162 				break;
163 			case ' ':
164 				/*
165 				 * A space after ':' or '::' indicates we are at the
166 				 * last component of potential ident.
167 				 */
168 				state = TEXT;
169 				break;
170 			default:
171 				/* All other characters are invalid */
172 				state = INIT;
173 				break;
174 			}
175 			break;
176 		case TEXT:
177 			if (fputc(c, buffp) == EOF)
178 				goto bufferr;
179 
180 			if (iscntrl(c)) {
181 				/* Control characters are not allowed in this state */
182 				state = INIT;
183 			} else if (c == '$') {
184 				if (fflush(buffp) == EOF)
185 					goto bufferr;
186 				/*
187 				 * valid ident should end with a space.
188 				 *
189 				 * subversion extension uses '#' to indicate that
190 				 * the keyword expansion have exceeded the fixed
191 				 * width, so it is also permitted if we are in
192 				 * subversion mode.  No length check is enforced
193 				 * because GNU RCS ident(1) does not do it either.
194 				 */
195 				c = buf[strlen(buf) - 2];
196 				if (c == ' ' || (subversion && c == '#')) {
197 					printf("     %s\n", buf);
198 					if (fflush(stdout) == EOF)
199 						err(EXIT_FAILURE, "stdout");
200 					hasid = true;
201 				}
202 				state = INIT;
203 			}
204 			/* Other characters: stay in the state */
205 			break;
206 		}
207 	}
208 	if (fclose(buffp) == EOF)
209 		goto bufferr;
210 	free(buf);
211 
212 	if (!hasid) {
213 		if (!quiet) {
214 			fprintf(stderr, "%s warning: no id keywords in %s\n",
215 			    getprogname(), name ? name : "standard input");
216 		}
217 		return (EXIT_FAILURE);
218 	}
219 
220 	return (EXIT_SUCCESS);
221 bufferr:
222 	err(EXIT_FAILURE, "buffer");
223 }
224 
225 static void
usage(void)226 usage(void)
227 {
228 	fprintf(stderr, "usage: %s [-q] [-V] [file...]", getprogname());
229 	exit(EXIT_FAILURE);
230 }
231 
232 static struct option longopts[] = {
233 	{ "quiet",	no_argument,	NULL,	'q' },
234 	{ "version",	no_argument,	NULL,	'V' },
235 	{ NULL,		0,		NULL,	0 }
236 };
237 
238 int
main(int argc,char ** argv)239 main(int argc, char **argv)
240 {
241 	fileargs_t *fa;
242 	cap_rights_t rights;
243 	bool quiet = false;
244 	int i, opt, ret;
245 	FILE *fp;
246 
247 	setlocale(LC_CTYPE, "C");
248 
249 	while ((opt = getopt_long(argc, argv, "+qV", longopts, NULL)) != -1) {
250 		switch (opt) {
251 		case 'q':
252 			quiet = true;
253 			break;
254 		case 'V':
255 			/* Do nothing, compat with GNU rcs's ident */
256 			return (EXIT_SUCCESS);
257 		default:
258 			usage();
259 		}
260 	}
261 
262 	argc -= optind;
263 	argv += optind;
264 
265 	cap_rights_init(&rights, CAP_READ, CAP_FSTAT, CAP_FCNTL);
266 	fa = fileargs_init(argc, argv, O_RDONLY, 0, &rights, FA_OPEN);
267 	if (fa == NULL)
268 		err(EXIT_FAILURE, "Unable to initialize casper");
269 	caph_cache_catpages();
270 	if (caph_limit_stdio() != 0)
271 		err(EXIT_FAILURE, "Unable to limit stdio");
272 	if (caph_enter_casper() != 0)
273 		err(EXIT_FAILURE, "Unable to enter capability mode");
274 
275 	if (argc == 0) {
276 		ret = scan(stdin, NULL, quiet);
277 	} else {
278 		ret = EXIT_SUCCESS;
279 		for (i = 0; i < argc; i++) {
280 			if ((fp = fileargs_fopen(fa, argv[i], "r")) == NULL)
281 				err(EXIT_FAILURE, "%s", argv[i]);
282 			if (scan(fp, argv[i], quiet) != EXIT_SUCCESS)
283 				ret = EXIT_FAILURE;
284 			(void)fclose(fp);
285 		}
286 	}
287 
288 	fileargs_free(fa);
289 	return (ret);
290 }
291