xref: /freebsd/usr.bin/wc/wc.c (revision f126890ac5386406dadf7c4cfa9566cbb56537c5)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1980, 1987, 1991, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
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  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/capsicum.h>
33 #include <sys/param.h>
34 #include <sys/stat.h>
35 
36 #include <capsicum_helpers.h>
37 #include <ctype.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <locale.h>
41 #include <stdbool.h>
42 #include <stdint.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 #include <wchar.h>
48 #include <wctype.h>
49 #include <libxo/xo.h>
50 
51 #include <libcasper.h>
52 #include <casper/cap_fileargs.h>
53 
54 static const char *stdin_filename = "stdin";
55 
56 static fileargs_t *fa;
57 static uintmax_t tlinect, twordct, tcharct, tlongline;
58 static bool doline, doword, dochar, domulti, dolongline;
59 static volatile sig_atomic_t siginfo;
60 static xo_handle_t *stderr_handle;
61 
62 static void	show_cnt(const char *file, uintmax_t linect, uintmax_t wordct,
63 		    uintmax_t charct, uintmax_t llct);
64 static int	cnt(const char *);
65 static void	usage(void);
66 
67 static void
68 siginfo_handler(int sig __unused)
69 {
70 
71 	siginfo = 1;
72 }
73 
74 static void
75 reset_siginfo(void)
76 {
77 
78 	signal(SIGINFO, SIG_DFL);
79 	siginfo = 0;
80 }
81 
82 int
83 main(int argc, char *argv[])
84 {
85 	int ch, errors, total;
86 	cap_rights_t rights;
87 
88 	(void) setlocale(LC_CTYPE, "");
89 
90 	argc = xo_parse_args(argc, argv);
91 	if (argc < 0)
92 		exit(EXIT_FAILURE);
93 
94 	while ((ch = getopt(argc, argv, "clmwL")) != -1)
95 		switch((char)ch) {
96 		case 'l':
97 			doline = true;
98 			break;
99 		case 'w':
100 			doword = true;
101 			break;
102 		case 'c':
103 			dochar = true;
104 			domulti = false;
105 			break;
106 		case 'L':
107 			dolongline = true;
108 			break;
109 		case 'm':
110 			domulti = true;
111 			dochar = false;
112 			break;
113 		case '?':
114 		default:
115 			usage();
116 		}
117 	argv += optind;
118 	argc -= optind;
119 
120 	fa = fileargs_init(argc, argv, O_RDONLY, 0,
121 	    cap_rights_init(&rights, CAP_READ, CAP_FSTAT), FA_OPEN);
122 	if (fa == NULL)
123 		xo_err(EXIT_FAILURE, "Unable to initialize casper");
124 	caph_cache_catpages();
125 	if (caph_limit_stdio() < 0)
126 		xo_err(EXIT_FAILURE, "Unable to limit stdio");
127 	if (caph_enter_casper() < 0)
128 		xo_err(EXIT_FAILURE, "Unable to enter capability mode");
129 
130 	/* Wc's flags are on by default. */
131 	if (!(doline || doword || dochar || domulti || dolongline))
132 		doline = doword = dochar = true;
133 
134 	stderr_handle = xo_create_to_file(stderr, XO_STYLE_TEXT, 0);
135 	xo_open_container("wc");
136 	xo_open_list("file");
137 
138 	(void)signal(SIGINFO, siginfo_handler);
139 	errors = 0;
140 	total = 0;
141 	if (argc == 0) {
142 		xo_open_instance("file");
143 		if (cnt(NULL) != 0)
144 			++errors;
145 		xo_close_instance("file");
146 	} else {
147 		while (argc--) {
148 			xo_open_instance("file");
149 			if (cnt(*argv++) != 0)
150 				++errors;
151 			xo_close_instance("file");
152 			++total;
153 		}
154 	}
155 
156 	xo_close_list("file");
157 
158 	if (total > 1) {
159 		xo_open_container("total");
160 		show_cnt("total", tlinect, twordct, tcharct, tlongline);
161 		xo_close_container("total");
162 	}
163 
164 	fileargs_free(fa);
165 	xo_close_container("wc");
166 	if (xo_finish() < 0)
167 		xo_err(EXIT_FAILURE, "stdout");
168 	exit(errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
169 }
170 
171 static void
172 show_cnt(const char *file, uintmax_t linect, uintmax_t wordct,
173     uintmax_t charct, uintmax_t llct)
174 {
175 	xo_handle_t *xop;
176 
177 	if (!siginfo)
178 		xop = NULL;
179 	else {
180 		xop = stderr_handle;
181 		siginfo = 0;
182 	}
183 
184 	if (doline)
185 		xo_emit_h(xop, " {:lines/%7ju/%ju}", linect);
186 	if (doword)
187 		xo_emit_h(xop, " {:words/%7ju/%ju}", wordct);
188 	if (dochar || domulti)
189 		xo_emit_h(xop, " {:characters/%7ju/%ju}", charct);
190 	if (dolongline)
191 		xo_emit_h(xop, " {:long-lines/%7ju/%ju}", llct);
192 	if (file != stdin_filename)
193 		xo_emit_h(xop, " {:filename/%s}\n", file);
194 	else
195 		xo_emit_h(xop, "\n");
196 }
197 
198 static int
199 cnt(const char *file)
200 {
201 	static char buf[MAXBSIZE];
202 	struct stat sb;
203 	mbstate_t mbs;
204 	const char *p;
205 	uintmax_t linect, wordct, charct, llct, tmpll;
206 	ssize_t len;
207 	size_t clen;
208 	int fd;
209 	wchar_t wch;
210 	bool gotsp, warned;
211 
212 	linect = wordct = charct = llct = tmpll = 0;
213 	if (file == NULL) {
214 		fd = STDIN_FILENO;
215 		file = stdin_filename;
216 	} else if ((fd = fileargs_open(fa, file)) < 0) {
217 		xo_warn("%s: open", file);
218 		return (1);
219 	}
220 	if (doword || (domulti && MB_CUR_MAX != 1))
221 		goto word;
222 	/*
223 	 * If all we need is the number of characters and it's a regular file,
224 	 * just stat it.
225 	 */
226 	if (doline == 0 && dolongline == 0) {
227 		if (fstat(fd, &sb)) {
228 			xo_warn("%s: fstat", file);
229 			(void)close(fd);
230 			return (1);
231 		}
232 		/* pseudo-filesystems advertize a zero size */
233 		if (S_ISREG(sb.st_mode) && sb.st_size > 0) {
234 			reset_siginfo();
235 			charct = sb.st_size;
236 			show_cnt(file, linect, wordct, charct, llct);
237 			tcharct += charct;
238 			(void)close(fd);
239 			return (0);
240 		}
241 	}
242 	/*
243 	 * For files we can't stat, or if we need line counting, slurp the
244 	 * file.  Line counting is split out because it's a lot faster to get
245 	 * lines than to get words, since the word count requires locale
246 	 * handling.
247 	 */
248 	while ((len = read(fd, buf, sizeof(buf))) != 0) {
249 		if (len < 0) {
250 			xo_warn("%s: read", file);
251 			(void)close(fd);
252 			return (1);
253 		}
254 		if (siginfo)
255 			show_cnt(file, linect, wordct, charct, llct);
256 		charct += len;
257 		if (doline || dolongline) {
258 			for (p = buf; len > 0; --len, ++p) {
259 				if (*p == '\n') {
260 					if (tmpll > llct)
261 						llct = tmpll;
262 					tmpll = 0;
263 					++linect;
264 				} else {
265 					tmpll++;
266 				}
267 			}
268 		}
269 	}
270 	reset_siginfo();
271 	if (doline)
272 		tlinect += linect;
273 	if (dochar)
274 		tcharct += charct;
275 	if (dolongline && llct > tlongline)
276 		tlongline = llct;
277 	show_cnt(file, linect, wordct, charct, llct);
278 	(void)close(fd);
279 	return (0);
280 
281 	/* Do it the hard way... */
282 word:	gotsp = true;
283 	warned = false;
284 	memset(&mbs, 0, sizeof(mbs));
285 	while ((len = read(fd, buf, sizeof(buf))) != 0) {
286 		if (len < 0) {
287 			xo_warn("%s: read", file);
288 			(void)close(fd);
289 			return (1);
290 		}
291 		p = buf;
292 		while (len > 0) {
293 			if (siginfo)
294 				show_cnt(file, linect, wordct, charct, llct);
295 			if (!domulti || MB_CUR_MAX == 1) {
296 				clen = 1;
297 				wch = (unsigned char)*p;
298 			} else if ((clen = mbrtowc(&wch, p, len, &mbs)) == 0) {
299 				clen = 1;
300 			} else if (clen == (size_t)-1) {
301 				if (!warned) {
302 					errno = EILSEQ;
303 					xo_warn("%s", file);
304 					warned = true;
305 				}
306 				memset(&mbs, 0, sizeof(mbs));
307 				clen = 1;
308 				wch = (unsigned char)*p;
309 			} else if (clen == (size_t)-2) {
310 				break;
311 			}
312 			charct++;
313 			if (wch != L'\n')
314 				tmpll++;
315 			len -= clen;
316 			p += clen;
317 			if (wch == L'\n') {
318 				if (tmpll > llct)
319 					llct = tmpll;
320 				tmpll = 0;
321 				++linect;
322 			}
323 			if (iswspace(wch)) {
324 				gotsp = true;
325 			} else if (gotsp) {
326 				gotsp = false;
327 				++wordct;
328 			}
329 		}
330 	}
331 	reset_siginfo();
332 	if (domulti && MB_CUR_MAX > 1) {
333 		if (mbrtowc(NULL, NULL, 0, &mbs) == (size_t)-1 && !warned)
334 			xo_warn("%s", file);
335 	}
336 	if (doline)
337 		tlinect += linect;
338 	if (doword)
339 		twordct += wordct;
340 	if (dochar || domulti)
341 		tcharct += charct;
342 	if (dolongline && llct > tlongline)
343 		tlongline = llct;
344 	show_cnt(file, linect, wordct, charct, llct);
345 	(void)close(fd);
346 	return (0);
347 }
348 
349 static void
350 usage(void)
351 {
352 	xo_error("usage: wc [-Lclmw] [file ...]\n");
353 	exit(EXIT_FAILURE);
354 }
355