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