1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 1999 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Klaus Klein.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include <sys/types.h>
33
34 #include <err.h>
35 #include <errno.h>
36 #include <limits.h>
37 #include <locale.h>
38 #include <regex.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include <wchar.h>
44
45 #include <capsicum_helpers.h>
46
47 typedef enum {
48 number_all, /* number all lines */
49 number_nonempty, /* number non-empty lines */
50 number_none, /* no line numbering */
51 number_regex /* number lines matching regular expression */
52 } numbering_type;
53
54 struct numbering_property {
55 const char * const name; /* for diagnostics */
56 numbering_type type; /* numbering type */
57 regex_t expr; /* for type == number_regex */
58 };
59
60 /* line numbering formats */
61 #define FORMAT_LN "%-*d" /* left justified, leading zeros suppressed */
62 #define FORMAT_RN "%*d" /* right justified, leading zeros suppressed */
63 #define FORMAT_RZ "%0*d" /* right justified, leading zeros kept */
64
65 #define FOOTER 0
66 #define BODY 1
67 #define HEADER 2
68 #define NP_LAST HEADER
69
70 static struct numbering_property numbering_properties[NP_LAST + 1] = {
71 { .name = "footer", .type = number_none },
72 { .name = "body", .type = number_nonempty },
73 { .name = "header", .type = number_none }
74 };
75
76 #define max(a, b) ((a) > (b) ? (a) : (b))
77
78 /*
79 * Maximum number of characters required for a decimal representation of a
80 * (signed) int; courtesy of tzcode.
81 */
82 #define INT_STRLEN_MAXIMUM \
83 ((sizeof (int) * CHAR_BIT - 1) * 302 / 1000 + 2)
84
85 static void filter(void);
86 static void parse_numbering(const char *, int);
87 static void usage(void);
88
89 /*
90 * Dynamically allocated buffer suitable for string representation of ints.
91 */
92 static char *intbuffer;
93
94 /* delimiter characters that indicate the start of a logical page section */
95 static char delim[2 * MB_LEN_MAX];
96 static int delimlen;
97
98 /*
99 * Configurable parameters.
100 */
101
102 /* line numbering format */
103 static const char *format = FORMAT_RN;
104
105 /* increment value used to number logical page lines */
106 static int incr = 1;
107
108 /* number of adjacent blank lines to be considered (and numbered) as one */
109 static unsigned int nblank = 1;
110
111 /* whether to restart numbering at logical page delimiters */
112 static int restart = 1;
113
114 /* characters used in separating the line number and the corrsp. text line */
115 static const char *sep = "\t";
116
117 /* initial value used to number logical page lines */
118 static int startnum = 1;
119
120 /* number of characters to be used for the line number */
121 /* should be unsigned but required signed by `*' precision conversion */
122 static int width = 6;
123
124
125 int
main(int argc,char * argv[])126 main(int argc, char *argv[])
127 {
128 int c;
129 long val;
130 unsigned long uval;
131 char *ep;
132 size_t intbuffersize, clen;
133 char delim1[MB_LEN_MAX] = { '\\' }, delim2[MB_LEN_MAX] = { ':' };
134 size_t delim1len = 1, delim2len = 1;
135
136 (void)setlocale(LC_ALL, "");
137
138 while ((c = getopt(argc, argv, "pb:d:f:h:i:l:n:s:v:w:")) != -1) {
139 switch (c) {
140 case 'p':
141 restart = 0;
142 break;
143 case 'b':
144 parse_numbering(optarg, BODY);
145 break;
146 case 'd':
147 clen = mbrlen(optarg, MB_CUR_MAX, NULL);
148 if (clen == (size_t)-1 || clen == (size_t)-2)
149 errc(EXIT_FAILURE, EILSEQ, NULL);
150 if (clen != 0) {
151 memcpy(delim1, optarg, delim1len = clen);
152 clen = mbrlen(optarg + delim1len,
153 MB_CUR_MAX, NULL);
154 if (clen == (size_t)-1 ||
155 clen == (size_t)-2)
156 errc(EXIT_FAILURE, EILSEQ, NULL);
157 if (clen != 0) {
158 memcpy(delim2, optarg + delim1len,
159 delim2len = clen);
160 if (optarg[delim1len + clen] != '\0')
161 errx(EXIT_FAILURE,
162 "invalid delim argument -- %s",
163 optarg);
164 }
165 }
166 break;
167 case 'f':
168 parse_numbering(optarg, FOOTER);
169 break;
170 case 'h':
171 parse_numbering(optarg, HEADER);
172 break;
173 case 'i':
174 errno = 0;
175 val = strtol(optarg, &ep, 10);
176 if ((ep != NULL && *ep != '\0') ||
177 ((val == LONG_MIN || val == LONG_MAX) && errno != 0))
178 errx(EXIT_FAILURE,
179 "invalid incr argument -- %s", optarg);
180 incr = (int)val;
181 break;
182 case 'l':
183 errno = 0;
184 uval = strtoul(optarg, &ep, 10);
185 if ((ep != NULL && *ep != '\0') ||
186 (uval == ULONG_MAX && errno != 0))
187 errx(EXIT_FAILURE,
188 "invalid num argument -- %s", optarg);
189 nblank = (unsigned int)uval;
190 break;
191 case 'n':
192 if (strcmp(optarg, "ln") == 0) {
193 format = FORMAT_LN;
194 } else if (strcmp(optarg, "rn") == 0) {
195 format = FORMAT_RN;
196 } else if (strcmp(optarg, "rz") == 0) {
197 format = FORMAT_RZ;
198 } else
199 errx(EXIT_FAILURE,
200 "illegal format -- %s", optarg);
201 break;
202 case 's':
203 sep = optarg;
204 break;
205 case 'v':
206 errno = 0;
207 val = strtol(optarg, &ep, 10);
208 if ((ep != NULL && *ep != '\0') ||
209 ((val == LONG_MIN || val == LONG_MAX) && errno != 0))
210 errx(EXIT_FAILURE,
211 "invalid startnum value -- %s", optarg);
212 startnum = (int)val;
213 break;
214 case 'w':
215 errno = 0;
216 val = strtol(optarg, &ep, 10);
217 if ((ep != NULL && *ep != '\0') ||
218 ((val == LONG_MIN || val == LONG_MAX) && errno != 0))
219 errx(EXIT_FAILURE,
220 "invalid width value -- %s", optarg);
221 width = (int)val;
222 if (!(width > 0))
223 errx(EXIT_FAILURE,
224 "width argument must be > 0 -- %d",
225 width);
226 break;
227 case '?':
228 default:
229 usage();
230 /* NOTREACHED */
231 }
232 }
233 argc -= optind;
234 argv += optind;
235
236 switch (argc) {
237 case 0:
238 break;
239 case 1:
240 if (strcmp(argv[0], "-") != 0 &&
241 freopen(argv[0], "r", stdin) == NULL)
242 err(EXIT_FAILURE, "%s", argv[0]);
243 break;
244 default:
245 usage();
246 /* NOTREACHED */
247 }
248
249 /* Limit standard descriptors and enter capability mode */
250 caph_cache_catpages();
251 if (caph_limit_stdio() < 0 || caph_enter() < 0)
252 err(EXIT_FAILURE, "capsicum");
253
254 /* Generate the delimiter sequence */
255 memcpy(delim, delim1, delim1len);
256 memcpy(delim + delim1len, delim2, delim2len);
257 delimlen = delim1len + delim2len;
258
259 /* Allocate a buffer suitable for preformatting line number. */
260 intbuffersize = max((int)INT_STRLEN_MAXIMUM, width) + 1; /* NUL */
261 if ((intbuffer = malloc(intbuffersize)) == NULL)
262 err(EXIT_FAILURE, "cannot allocate preformatting buffer");
263
264 /* Do the work. */
265 filter();
266
267 exit(EXIT_SUCCESS);
268 /* NOTREACHED */
269 }
270
271 static void
filter(void)272 filter(void)
273 {
274 char *buffer;
275 size_t buffersize;
276 ssize_t linelen;
277 int line; /* logical line number */
278 int section; /* logical page section */
279 unsigned int adjblank; /* adjacent blank lines */
280 int consumed; /* intbuffer measurement */
281 int donumber = 0, idx;
282
283 adjblank = 0;
284 line = startnum;
285 section = BODY;
286
287 buffer = NULL;
288 buffersize = 0;
289 while ((linelen = getline(&buffer, &buffersize, stdin)) > 0) {
290 for (idx = FOOTER; idx <= NP_LAST; idx++) {
291 /* Does it look like a delimiter? */
292 if (delimlen * (idx + 1) > linelen)
293 break;
294 if (memcmp(buffer + delimlen * idx, delim,
295 delimlen) != 0)
296 break;
297 /* Was this the whole line? */
298 if (buffer[delimlen * (idx + 1)] == '\n') {
299 section = idx;
300 adjblank = 0;
301 if (restart)
302 line = startnum;
303 goto nextline;
304 }
305 }
306
307 switch (numbering_properties[section].type) {
308 case number_all:
309 /*
310 * Doing this for number_all only is disputable, but
311 * the standard expresses an explicit dependency on
312 * `-b a' etc.
313 */
314 if (buffer[0] == '\n' && ++adjblank < nblank)
315 donumber = 0;
316 else
317 donumber = 1, adjblank = 0;
318 break;
319 case number_nonempty:
320 donumber = (buffer[0] != '\n');
321 break;
322 case number_none:
323 donumber = 0;
324 break;
325 case number_regex:
326 donumber =
327 (regexec(&numbering_properties[section].expr,
328 buffer, 0, NULL, 0) == 0);
329 break;
330 }
331
332 if (donumber) {
333 /* Note: sprintf() is safe here. */
334 consumed = sprintf(intbuffer, format, width, line);
335 (void)printf("%s",
336 intbuffer + max(0, consumed - width));
337 line += incr;
338 } else {
339 (void)printf("%*s", width, "");
340 }
341 (void)fputs(sep, stdout);
342 (void)fwrite(buffer, linelen, 1, stdout);
343
344 if (ferror(stdout))
345 err(EXIT_FAILURE, "output error");
346 nextline:
347 ;
348 }
349
350 if (ferror(stdin))
351 err(EXIT_FAILURE, "input error");
352
353 free(buffer);
354 }
355
356 /*
357 * Various support functions.
358 */
359
360 static void
parse_numbering(const char * argstr,int section)361 parse_numbering(const char *argstr, int section)
362 {
363 int error;
364 char errorbuf[NL_TEXTMAX];
365
366 switch (argstr[0]) {
367 case 'a':
368 numbering_properties[section].type = number_all;
369 break;
370 case 'n':
371 numbering_properties[section].type = number_none;
372 break;
373 case 't':
374 numbering_properties[section].type = number_nonempty;
375 break;
376 case 'p':
377 /* If there was a previous expression, throw it away. */
378 if (numbering_properties[section].type == number_regex)
379 regfree(&numbering_properties[section].expr);
380 else
381 numbering_properties[section].type = number_regex;
382
383 /* Compile/validate the supplied regular expression. */
384 if ((error = regcomp(&numbering_properties[section].expr,
385 &argstr[1], REG_NEWLINE|REG_NOSUB)) != 0) {
386 (void)regerror(error,
387 &numbering_properties[section].expr,
388 errorbuf, sizeof (errorbuf));
389 errx(EXIT_FAILURE,
390 "%s expr: %s -- %s",
391 numbering_properties[section].name, errorbuf,
392 &argstr[1]);
393 }
394 break;
395 default:
396 errx(EXIT_FAILURE,
397 "illegal %s line numbering type -- %s",
398 numbering_properties[section].name, argstr);
399 }
400 }
401
402 static void
usage(void)403 usage(void)
404 {
405
406 (void)fprintf(stderr,
407 "usage: nl [-p] [-b type] [-d delim] [-f type] [-h type] [-i incr] [-l num]\n"
408 " [-n format] [-s sep] [-v startnum] [-w width] [file]\n");
409 exit(EXIT_FAILURE);
410 }
411