176273Sbrian/*- 276273Sbrian * Copyright (c) 1999 The NetBSD Foundation, Inc. 376273Sbrian * All rights reserved. 476273Sbrian * 576273Sbrian * This code is derived from software contributed to The NetBSD Foundation 676273Sbrian * by Klaus Klein. 776273Sbrian * 876273Sbrian * Redistribution and use in source and binary forms, with or without 976273Sbrian * modification, are permitted provided that the following conditions 1076273Sbrian * are met: 1176273Sbrian * 1. Redistributions of source code must retain the above copyright 1276273Sbrian * notice, this list of conditions and the following disclaimer. 1376273Sbrian * 2. Redistributions in binary form must reproduce the above copyright 1476273Sbrian * notice, this list of conditions and the following disclaimer in the 1576273Sbrian * documentation and/or other materials provided with the distribution. 1676273Sbrian * 1776273Sbrian * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 1876273Sbrian * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 1976273Sbrian * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 2076273Sbrian * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 2176273Sbrian * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 2276273Sbrian * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 2376273Sbrian * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 2476273Sbrian * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 2576273Sbrian * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 2676273Sbrian * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 2776273Sbrian * POSSIBILITY OF SUCH DAMAGE. 2876273Sbrian */ 2976273Sbrian 3076273Sbrian#include <sys/cdefs.h> 3176273Sbrian#ifndef lint 3276273Sbrian__COPYRIGHT( 3376273Sbrian"@(#) Copyright (c) 1999\ 3476273Sbrian The NetBSD Foundation, Inc. All rights reserved."); 3576273Sbrian__RCSID("$FreeBSD: releng/11.0/usr.bin/nl/nl.c 265319 2014-05-04 12:20:40Z pluknet $"); 3676273Sbrian#endif 3776273Sbrian 38189168Sdas#define _WITH_GETLINE 3976273Sbrian#include <sys/types.h> 4076273Sbrian 4197337Stjr#include <err.h> 4276273Sbrian#include <errno.h> 4376273Sbrian#include <limits.h> 4476273Sbrian#include <locale.h> 4576273Sbrian#include <regex.h> 4676273Sbrian#include <stdio.h> 4776273Sbrian#include <stdlib.h> 4876273Sbrian#include <string.h> 4976273Sbrian#include <unistd.h> 50132078Stjr#include <wchar.h> 5176273Sbrian 5276273Sbriantypedef enum { 5376273Sbrian number_all, /* number all lines */ 5476273Sbrian number_nonempty, /* number non-empty lines */ 5576273Sbrian number_none, /* no line numbering */ 5676273Sbrian number_regex /* number lines matching regular expression */ 5776273Sbrian} numbering_type; 5876273Sbrian 5976273Sbrianstruct numbering_property { 6076273Sbrian const char * const name; /* for diagnostics */ 6176273Sbrian numbering_type type; /* numbering type */ 6276273Sbrian regex_t expr; /* for type == number_regex */ 6376273Sbrian}; 6476273Sbrian 6576273Sbrian/* line numbering formats */ 6676273Sbrian#define FORMAT_LN "%-*d" /* left justified, leading zeros suppressed */ 6776273Sbrian#define FORMAT_RN "%*d" /* right justified, leading zeros suppressed */ 6876273Sbrian#define FORMAT_RZ "%0*d" /* right justified, leading zeros kept */ 6976273Sbrian 7076273Sbrian#define FOOTER 0 7176273Sbrian#define BODY 1 7276273Sbrian#define HEADER 2 7376273Sbrian#define NP_LAST HEADER 7476273Sbrian 7576273Sbrianstatic struct numbering_property numbering_properties[NP_LAST + 1] = { 76226362Sed { .name = "footer", .type = number_none }, 77226362Sed { .name = "body", .type = number_nonempty }, 78226362Sed { .name = "header", .type = number_none } 7976273Sbrian}; 8076273Sbrian 8176273Sbrian#define max(a, b) ((a) > (b) ? (a) : (b)) 8276273Sbrian 8376273Sbrian/* 8476273Sbrian * Maximum number of characters required for a decimal representation of a 8576273Sbrian * (signed) int; courtesy of tzcode. 8676273Sbrian */ 8776273Sbrian#define INT_STRLEN_MAXIMUM \ 8876273Sbrian ((sizeof (int) * CHAR_BIT - 1) * 302 / 1000 + 2) 8976273Sbrian 9092921Simpstatic void filter(void); 9192921Simpstatic void parse_numbering(const char *, int); 9292921Simpstatic void usage(void); 9376273Sbrian 9476273Sbrian/* 9576273Sbrian * Dynamically allocated buffer suitable for string representation of ints. 9676273Sbrian */ 9776273Sbrianstatic char *intbuffer; 9876273Sbrian 99132078Stjr/* delimiter characters that indicate the start of a logical page section */ 100132078Stjrstatic char delim[2 * MB_LEN_MAX]; 101132078Stjrstatic int delimlen; 102132078Stjr 10376273Sbrian/* 10476273Sbrian * Configurable parameters. 10576273Sbrian */ 10676273Sbrian 10776273Sbrian/* line numbering format */ 10876273Sbrianstatic const char *format = FORMAT_RN; 10976273Sbrian 11076273Sbrian/* increment value used to number logical page lines */ 11176273Sbrianstatic int incr = 1; 11276273Sbrian 11376273Sbrian/* number of adjacent blank lines to be considered (and numbered) as one */ 11476273Sbrianstatic unsigned int nblank = 1; 11576273Sbrian 11676273Sbrian/* whether to restart numbering at logical page delimiters */ 11776273Sbrianstatic int restart = 1; 11876273Sbrian 11976273Sbrian/* characters used in separating the line number and the corrsp. text line */ 12076273Sbrianstatic const char *sep = "\t"; 12176273Sbrian 12276273Sbrian/* initial value used to number logical page lines */ 12376273Sbrianstatic int startnum = 1; 12476273Sbrian 12576273Sbrian/* number of characters to be used for the line number */ 12676273Sbrian/* should be unsigned but required signed by `*' precision conversion */ 12776273Sbrianstatic int width = 6; 12876273Sbrian 12976273Sbrian 13076273Sbrianint 131226362Sedmain(int argc, char *argv[]) 13276273Sbrian{ 133144840Sstefanf int c; 13476273Sbrian long val; 13576273Sbrian unsigned long uval; 13676273Sbrian char *ep; 137132078Stjr size_t intbuffersize, clen; 138132078Stjr char delim1[MB_LEN_MAX] = { '\\' }, delim2[MB_LEN_MAX] = { ':' }; 139132078Stjr size_t delim1len = 1, delim2len = 1; 14076273Sbrian 14176273Sbrian (void)setlocale(LC_ALL, ""); 14276273Sbrian 14376273Sbrian while ((c = getopt(argc, argv, "pb:d:f:h:i:l:n:s:v:w:")) != -1) { 14476273Sbrian switch (c) { 14576273Sbrian case 'p': 14676273Sbrian restart = 0; 14776273Sbrian break; 14876273Sbrian case 'b': 14976273Sbrian parse_numbering(optarg, BODY); 15076273Sbrian break; 15176273Sbrian case 'd': 152132078Stjr clen = mbrlen(optarg, MB_CUR_MAX, NULL); 153132078Stjr if (clen == (size_t)-1 || clen == (size_t)-2) 154132078Stjr errc(EXIT_FAILURE, EILSEQ, NULL); 155132078Stjr if (clen != 0) { 156132078Stjr memcpy(delim1, optarg, delim1len = clen); 157132078Stjr clen = mbrlen(optarg + delim1len, 158132078Stjr MB_CUR_MAX, NULL); 159132078Stjr if (clen == (size_t)-1 || 160132078Stjr clen == (size_t)-2) 161132078Stjr errc(EXIT_FAILURE, EILSEQ, NULL); 162132078Stjr if (clen != 0) { 163132078Stjr memcpy(delim2, optarg + delim1len, 164132078Stjr delim2len = clen); 165132078Stjr if (optarg[delim1len + clen] != '\0') 166132078Stjr errx(EXIT_FAILURE, 167132078Stjr "invalid delim argument -- %s", 168132078Stjr optarg); 169132078Stjr } 17076273Sbrian } 17176273Sbrian break; 17276273Sbrian case 'f': 17376273Sbrian parse_numbering(optarg, FOOTER); 17476273Sbrian break; 17576273Sbrian case 'h': 17676273Sbrian parse_numbering(optarg, HEADER); 17776273Sbrian break; 17876273Sbrian case 'i': 17976273Sbrian errno = 0; 18076273Sbrian val = strtol(optarg, &ep, 10); 18176273Sbrian if ((ep != NULL && *ep != '\0') || 18297338Stjr ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) 18397338Stjr errx(EXIT_FAILURE, 18497338Stjr "invalid incr argument -- %s", optarg); 18576273Sbrian incr = (int)val; 18676273Sbrian break; 18776273Sbrian case 'l': 18876273Sbrian errno = 0; 18976273Sbrian uval = strtoul(optarg, &ep, 10); 19076273Sbrian if ((ep != NULL && *ep != '\0') || 19197338Stjr (uval == ULONG_MAX && errno != 0)) 19297338Stjr errx(EXIT_FAILURE, 19397338Stjr "invalid num argument -- %s", optarg); 19476273Sbrian nblank = (unsigned int)uval; 19576273Sbrian break; 19676273Sbrian case 'n': 19776273Sbrian if (strcmp(optarg, "ln") == 0) { 19876273Sbrian format = FORMAT_LN; 19976273Sbrian } else if (strcmp(optarg, "rn") == 0) { 20076273Sbrian format = FORMAT_RN; 20176273Sbrian } else if (strcmp(optarg, "rz") == 0) { 20276273Sbrian format = FORMAT_RZ; 20397338Stjr } else 20497338Stjr errx(EXIT_FAILURE, 20597338Stjr "illegal format -- %s", optarg); 20676273Sbrian break; 20776273Sbrian case 's': 20876273Sbrian sep = optarg; 20976273Sbrian break; 21076273Sbrian case 'v': 21176273Sbrian errno = 0; 21276273Sbrian val = strtol(optarg, &ep, 10); 21376273Sbrian if ((ep != NULL && *ep != '\0') || 21497338Stjr ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) 21597338Stjr errx(EXIT_FAILURE, 21697338Stjr "invalid startnum value -- %s", optarg); 21776273Sbrian startnum = (int)val; 21876273Sbrian break; 21976273Sbrian case 'w': 22076273Sbrian errno = 0; 22176273Sbrian val = strtol(optarg, &ep, 10); 22276273Sbrian if ((ep != NULL && *ep != '\0') || 22397338Stjr ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) 22497338Stjr errx(EXIT_FAILURE, 22597338Stjr "invalid width value -- %s", optarg); 22676273Sbrian width = (int)val; 22797338Stjr if (!(width > 0)) 22897338Stjr errx(EXIT_FAILURE, 22997338Stjr "width argument must be > 0 -- %d", 23076273Sbrian width); 23176273Sbrian break; 23276273Sbrian case '?': 23376273Sbrian default: 23476273Sbrian usage(); 23576273Sbrian /* NOTREACHED */ 23676273Sbrian } 23776273Sbrian } 23876273Sbrian argc -= optind; 23976273Sbrian argv += optind; 24076273Sbrian 24176273Sbrian switch (argc) { 24276273Sbrian case 0: 24376273Sbrian break; 24476273Sbrian case 1: 245265319Spluknet if (strcmp(argv[0], "-") != 0 && 246265319Spluknet freopen(argv[0], "r", stdin) == NULL) 24797337Stjr err(EXIT_FAILURE, "%s", argv[0]); 24876273Sbrian break; 24976273Sbrian default: 25076273Sbrian usage(); 25176273Sbrian /* NOTREACHED */ 25276273Sbrian } 25376273Sbrian 254132078Stjr /* Generate the delimiter sequence */ 255132078Stjr memcpy(delim, delim1, delim1len); 256132078Stjr memcpy(delim + delim1len, delim2, delim2len); 257132078Stjr delimlen = delim1len + delim2len; 258132078Stjr 25976273Sbrian /* Allocate a buffer suitable for preformatting line number. */ 260226362Sed intbuffersize = max((int)INT_STRLEN_MAXIMUM, width) + 1; /* NUL */ 26197337Stjr if ((intbuffer = malloc(intbuffersize)) == NULL) 26297337Stjr err(EXIT_FAILURE, "cannot allocate preformatting buffer"); 26376273Sbrian 26476273Sbrian /* Do the work. */ 26576273Sbrian filter(); 26676273Sbrian 26776273Sbrian exit(EXIT_SUCCESS); 26876273Sbrian /* NOTREACHED */ 26976273Sbrian} 27076273Sbrian 27176273Sbrianstatic void 272226362Sedfilter(void) 27376273Sbrian{ 274189168Sdas char *buffer; 275189168Sdas size_t buffersize; 276189168Sdas ssize_t linelen; 27776273Sbrian int line; /* logical line number */ 27876273Sbrian int section; /* logical page section */ 27976273Sbrian unsigned int adjblank; /* adjacent blank lines */ 28076273Sbrian int consumed; /* intbuffer measurement */ 281165462Simp int donumber = 0, idx; 28276273Sbrian 28376273Sbrian adjblank = 0; 28476273Sbrian line = startnum; 28576273Sbrian section = BODY; 28676273Sbrian 287189168Sdas buffer = NULL; 288189168Sdas buffersize = 0; 289189168Sdas while ((linelen = getline(&buffer, &buffersize, stdin)) > 0) { 29076273Sbrian for (idx = FOOTER; idx <= NP_LAST; idx++) { 29176273Sbrian /* Does it look like a delimiter? */ 292189168Sdas if (delimlen * (idx + 1) > linelen) 293189168Sdas break; 294132078Stjr if (memcmp(buffer + delimlen * idx, delim, 295189168Sdas delimlen) != 0) 29676273Sbrian break; 297189168Sdas /* Was this the whole line? */ 298189168Sdas if (buffer[delimlen * (idx + 1)] == '\n') { 299189168Sdas section = idx; 300189168Sdas adjblank = 0; 301189168Sdas if (restart) 302189168Sdas line = startnum; 303189168Sdas goto nextline; 30476273Sbrian } 30576273Sbrian } 30676273Sbrian 30776273Sbrian switch (numbering_properties[section].type) { 30876273Sbrian case number_all: 30976273Sbrian /* 31076273Sbrian * Doing this for number_all only is disputable, but 31176273Sbrian * the standard expresses an explicit dependency on 31276273Sbrian * `-b a' etc. 31376273Sbrian */ 31476273Sbrian if (buffer[0] == '\n' && ++adjblank < nblank) 31576273Sbrian donumber = 0; 31676273Sbrian else 31776273Sbrian donumber = 1, adjblank = 0; 31876273Sbrian break; 31976273Sbrian case number_nonempty: 32076273Sbrian donumber = (buffer[0] != '\n'); 32176273Sbrian break; 32276273Sbrian case number_none: 32376273Sbrian donumber = 0; 32476273Sbrian break; 32576273Sbrian case number_regex: 32676273Sbrian donumber = 32776273Sbrian (regexec(&numbering_properties[section].expr, 32876273Sbrian buffer, 0, NULL, 0) == 0); 32976273Sbrian break; 33076273Sbrian } 33176273Sbrian 33276273Sbrian if (donumber) { 33376273Sbrian /* Note: sprintf() is safe here. */ 33476273Sbrian consumed = sprintf(intbuffer, format, width, line); 33576273Sbrian (void)printf("%s", 33676273Sbrian intbuffer + max(0, consumed - width)); 33776273Sbrian line += incr; 33876273Sbrian } else { 33976273Sbrian (void)printf("%*s", width, ""); 34076273Sbrian } 341189168Sdas (void)fputs(sep, stdout); 342189168Sdas (void)fwrite(buffer, linelen, 1, stdout); 34376273Sbrian 34497337Stjr if (ferror(stdout)) 34597337Stjr err(EXIT_FAILURE, "output error"); 34676273Sbriannextline: 34776273Sbrian ; 34876273Sbrian } 34976273Sbrian 35097337Stjr if (ferror(stdin)) 35197337Stjr err(EXIT_FAILURE, "input error"); 352189168Sdas 353189168Sdas free(buffer); 35476273Sbrian} 35576273Sbrian 35676273Sbrian/* 35776273Sbrian * Various support functions. 35876273Sbrian */ 35976273Sbrian 36076273Sbrianstatic void 361226362Sedparse_numbering(const char *argstr, int section) 36276273Sbrian{ 36376273Sbrian int error; 36476273Sbrian char errorbuf[NL_TEXTMAX]; 36576273Sbrian 36676273Sbrian switch (argstr[0]) { 36776273Sbrian case 'a': 36876273Sbrian numbering_properties[section].type = number_all; 36976273Sbrian break; 37076273Sbrian case 'n': 37176273Sbrian numbering_properties[section].type = number_none; 37276273Sbrian break; 37376273Sbrian case 't': 37476273Sbrian numbering_properties[section].type = number_nonempty; 37576273Sbrian break; 37676273Sbrian case 'p': 37776273Sbrian /* If there was a previous expression, throw it away. */ 37876273Sbrian if (numbering_properties[section].type == number_regex) 37976273Sbrian regfree(&numbering_properties[section].expr); 38076273Sbrian else 38176273Sbrian numbering_properties[section].type = number_regex; 38276273Sbrian 38376273Sbrian /* Compile/validate the supplied regular expression. */ 38476273Sbrian if ((error = regcomp(&numbering_properties[section].expr, 38576273Sbrian &argstr[1], REG_NEWLINE|REG_NOSUB)) != 0) { 38676273Sbrian (void)regerror(error, 38776273Sbrian &numbering_properties[section].expr, 38876273Sbrian errorbuf, sizeof (errorbuf)); 38997338Stjr errx(EXIT_FAILURE, 39097338Stjr "%s expr: %s -- %s", 39176273Sbrian numbering_properties[section].name, errorbuf, 39276273Sbrian &argstr[1]); 39376273Sbrian } 39476273Sbrian break; 39576273Sbrian default: 39697338Stjr errx(EXIT_FAILURE, 39797338Stjr "illegal %s line numbering type -- %s", 39876273Sbrian numbering_properties[section].name, argstr); 39976273Sbrian } 40076273Sbrian} 40176273Sbrian 40276273Sbrianstatic void 403226362Sedusage(void) 40476273Sbrian{ 40576273Sbrian 406119025Stjr (void)fprintf(stderr, 407119025Stjr"usage: nl [-p] [-b type] [-d delim] [-f type] [-h type] [-i incr] [-l num]\n" 408119025Stjr" [-n format] [-s sep] [-v startnum] [-w width] [file]\n"); 40976273Sbrian exit(EXIT_FAILURE); 41076273Sbrian} 411