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