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