www.usr.com/support/gpl/USR9108_release1.5.tar.gz
[bcm963xx.git] / userapps / opensource / busybox / coreutils / wc.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * wc implementation for busybox
4  *
5  * Copyright (C) 2003  Manuel Novoa III  <mjn3@codepoet.org>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20  *
21  */
22
23 /* BB_AUDIT SUSv3 _NOT_ compliant -- option -m is not currently supported. */
24 /* http://www.opengroup.org/onlinepubs/007904975/utilities/wc.html */
25
26 /* Mar 16, 2003      Manuel Novoa III   (mjn3@codepoet.org)
27  *
28  * Rewritten to fix a number of problems and do some size optimizations.
29  * Problems in the previous busybox implementation (besides bloat) included:
30  *  1) broken 'wc -c' optimization (read note below)
31  *  2) broken handling of '-' args
32  *  3) no checking of ferror on EOF returns
33  *  4) isprint() wasn't considered when word counting.
34  *
35  * TODO:
36  *
37  * When locale support is enabled, count multibyte chars in the '-m' case.
38  *
39  * NOTES:
40  *
41  * The previous busybox wc attempted an optimization using stat for the
42  * case of counting chars only.  I omitted that because it was broken.
43  * It didn't take into account the possibility of input coming from a
44  * pipe, or input from a file with file pointer not at the beginning.
45  *
46  * To implement such a speed optimization correctly, not only do you
47  * need the size, but also the file position.  Note also that the
48  * file position may be past the end of file.  Consider the example
49  * (adapted from example in gnu wc.c)
50  *
51  *      echo hello > /tmp/testfile &&
52  *      (dd ibs=1k skip=1 count=0 &> /dev/null ; wc -c) < /tmp/testfile
53  *
54  * for which 'wc -c' should output '0'.
55  */
56
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <unistd.h>
61 #include "busybox.h"
62
63 #ifdef CONFIG_LOCALE_SUPPORT
64 #include <locale.h>
65 #include <ctype.h>
66 #define isspace_given_isprint(c) isspace(c)
67 #else
68 #undef isspace
69 #undef isprint
70 #define isspace(c) ((((c) == ' ') || (((unsigned int)((c) - 9)) <= (13 - 9))))
71 #define isprint(c) (((unsigned int)((c) - 0x20)) <= (0x7e - 0x20))
72 #define isspace_given_isprint(c) ((c) == ' ')
73 #endif
74
75 enum {
76         WC_LINES        = 0,
77         WC_WORDS        = 1,
78         WC_CHARS        = 2,
79         WC_LENGTH       = 3
80 };
81
82 /* Note: If this changes, remember to change the initialization of
83  *       'name' in wc_main.  It needs to point to the terminating nul. */
84 static const char wc_opts[] = "lwcL";   /* READ THE WARNING ABOVE! */
85
86 enum {
87         OP_INC_LINE     = 1, /* OP_INC_LINE must be 1. */
88         OP_SPACE        = 2,
89         OP_NEWLINE      = 4,
90         OP_TAB          = 8,
91         OP_NUL          = 16,
92 };
93
94 /* Note: If fmt_str changes, the offsets to 's' in the OUTPUT section
95  *       will need to be updated. */
96 static const char fmt_str[] = " %7u\0 %s\n";
97 static const char total_str[] = "total";
98
99 int wc_main(int argc, char **argv)
100 {
101         FILE *fp;
102         const char *s;
103         unsigned int *pcounts;
104         unsigned int counts[4];
105         unsigned int totals[4];
106         unsigned int linepos;
107         unsigned int u;
108         int num_files = 0;
109         int c;
110         char status = EXIT_SUCCESS;
111         char in_word;
112         char print_type;
113
114         print_type = bb_getopt_ulflags(argc, argv, wc_opts);
115
116         if (print_type == 0) {
117                 print_type = (1 << WC_LINES) | (1 << WC_WORDS) | (1 << WC_CHARS);
118         }
119
120         argv += optind;
121         if (!*argv) {
122                 *--argv = (char *) bb_msg_standard_input;
123         }
124
125         memset(totals, 0, sizeof(totals));
126
127         pcounts = counts;
128
129         do {
130                 ++num_files;
131                 if (!(fp = bb_wfopen_input(*argv))) {
132                         status = EXIT_FAILURE;
133                         continue;
134                 }
135
136                 memset(counts, 0, sizeof(counts));
137                 linepos = 0;
138                 in_word = 0;
139
140                 do {
141                         ++counts[WC_CHARS];
142                         c = getc(fp);
143                         if (isprint(c)) {
144                                 ++linepos;
145                                 if (!isspace_given_isprint(c)) {
146                                         in_word = 1;
147                                         continue;
148                                 }
149                         } else if (((unsigned int)(c - 9)) <= 4) {
150                                 /* \t  9
151                                  * \n 10
152                                  * \v 11
153                                  * \f 12
154                                  * \r 13
155                                  */
156                                 if (c == '\t') {
157                                         linepos = (linepos | 7) + 1;
158                                 } else {                        /* '\n', '\r', '\f', or '\v' */
159                                 DO_EOF:
160                                         if (linepos > counts[WC_LENGTH]) {
161                                                 counts[WC_LENGTH] = linepos;
162                                         }
163                                         if (c == '\n') {
164                                                 ++counts[WC_LINES];
165                                         }
166                                         if (c != '\v') {
167                                                 linepos = 0;
168                                         }
169                                 }
170                         } else if (c == EOF) {
171                                 if (ferror(fp)) {
172                                         bb_perror_msg("%s", *argv);
173                                         status = EXIT_FAILURE;
174                                 }
175                                 --counts[WC_CHARS];
176                                 goto DO_EOF;            /* Treat an EOF as '\r'. */
177                         } else {
178                                 continue;
179                         }
180
181                         counts[WC_WORDS] += in_word;
182                         in_word = 0;
183                         if (c == EOF) {
184                                 break;
185                         }
186                 } while (1);
187
188                 if (totals[WC_LENGTH] < counts[WC_LENGTH]) {
189                         totals[WC_LENGTH] = counts[WC_LENGTH];
190                 }
191                 totals[WC_LENGTH] -= counts[WC_LENGTH];
192
193                 bb_fclose_nonstdin(fp);
194
195         OUTPUT:
196                 s = fmt_str + 1;                        /* Skip the leading space on 1st pass. */
197                 u = 0;
198                 do {
199                         if (print_type & (1 << u)) {
200                                 bb_printf(s, pcounts[u]);
201                                 s = fmt_str;            /* Ok... restore the leading space. */
202                         }
203                         totals[u] += pcounts[u];
204                 } while (++u < 4);
205
206                 s += 8;                                         /* Set the format to the empty string. */
207
208                 if (*argv != bb_msg_standard_input) {
209                         s -= 3;                                 /* We have a name, so do %s conversion. */
210                 }
211                 bb_printf(s, *argv);
212
213         } while (*++argv);
214
215         /* If more than one file was processed, we want the totals.  To save some
216          * space, we set the pcounts ptr to the totals array.  This has the side
217          * effect of trashing the totals array after outputting it, but that's
218          * irrelavent since we no longer need it. */
219         if (num_files > 1) {
220                 num_files = 0;                          /* Make sure we don't get here again. */
221                 *--argv = (char *) total_str;
222                 pcounts = totals;
223                 goto OUTPUT;
224         }
225
226         bb_fflush_stdout_and_exit(status);
227 }