diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/tail.c | 911 |
1 files changed, 430 insertions, 481 deletions
diff --git a/src/tail.c b/src/tail.c index be41195ac..1613412a7 100644 --- a/src/tail.c +++ b/src/tail.c @@ -107,20 +107,6 @@ enum header_mode char *xmalloc (); int safe_read (); -static int file_lines (); -static int pipe_bytes (); -static int pipe_lines (); -static int start_bytes (); -static int start_lines (); -static int tail (); -static int tail_bytes (); -static int tail_file (); -static int tail_lines (); -static long dump_remainder (); -static void tail_forever (); -static void usage (); -static void write_header (); - /* The name this program was run with. */ char *program_name; @@ -146,287 +132,45 @@ static struct option const long_options[] = {NULL, 0, NULL, 0} }; -void -main (argc, argv) - int argc; - char **argv; -{ - enum header_mode header_mode = multiple_files; - int exit_status = 0; - /* If from_start, the number of items to skip before printing; otherwise, - the number of items at the end of the file to print. Initially, -1 - means the value has not been set. */ - off_t n_units = -1; - long int tmp_long; - int c; /* Option character. */ - int fileind; /* Index in ARGV of first file name. */ - - program_name = argv[0]; - have_read_stdin = 0; - count_lines = 1; - forever = forever_multiple = from_start = print_headers = 0; - - if (argc > 1 - && ((argv[1][0] == '-' && ISDIGIT (argv[1][1])) - || (argv[1][0] == '+' && (ISDIGIT (argv[1][1]) || argv[1][1] == 0)))) - { - /* Old option syntax: a dash or plus, one or more digits (zero digits - are acceptable with a plus), and one or more option letters. */ - if (argv[1][0] == '+') - from_start = 1; - if (argv[1][1] != '\0') - { - strtol_error s_err; - char *p; - - s_err = xstrtol (++argv[1], &p, 0, &tmp_long, "bkm"); - n_units = tmp_long; - if (s_err == LONGINT_OVERFLOW) - { - STRTOL_FATAL_ERROR (argv[1], _("argument"), s_err); - } - /* Parse any appended option letters. */ - while (*p) - { - switch (*p) - { - case 'c': - /* Interpret N_UNITS as # of bytes. */ - count_lines = 0; - break; - - case 'f': - forever = 1; - break; - - case 'l': - count_lines = 1; - break; - - case 'q': - header_mode = never; - break; - - case 'v': - header_mode = always; - break; - - default: - error (0, 0, _("unrecognized option `-%c'"), *p); - usage (1); - } - ++p; - } - } - /* Make the options we just parsed invisible to getopt. */ - argv[1] = argv[0]; - argv++; - argc--; - } - - while ((c = getopt_long (argc, argv, "c:n:fqv", long_options, (int *) 0)) - != EOF) - { - strtol_error s_err; - - switch (c) - { - case 0: - break; - - case 'c': - count_lines = 0; - goto getnum; - - case 'n': - count_lines = 1; - getnum: - if (*optarg == '+') - { - from_start = 1; - } - - s_err = xstrtol (optarg, NULL, 0, &tmp_long, "bkm"); - if (tmp_long < 0) - tmp_long = -tmp_long; - n_units = tmp_long; - if (s_err != LONGINT_OK) - { - STRTOL_FATAL_ERROR (optarg, (c == 'n' - ? _("number of lines") - : _("number of bytes")), s_err); - } - break; - - case 'f': - forever = 1; - break; - - case 'q': - header_mode = never; - break; - - case 'v': - header_mode = always; - break; - - default: - usage (1); - } - } - - if (show_version) - { - printf ("tail - %s\n", version_string); - exit (0); - } - - if (show_help) - usage (0); - - if (n_units == -1) - n_units = DEFAULT_N_LINES; - - /* To start printing with item N_UNITS from the start of the file, skip - N_UNITS - 1 items. `tail +0' is actually meaningless, but for Unix - compatibility it's treated the same as `tail +1'. */ - if (from_start) - { - if (n_units) - --n_units; - } - - fileind = optind; - - if (optind < argc - 1 && forever) - { - forever_multiple = 1; - forever = 0; - file_descs = (int *) xmalloc ((argc - optind) * sizeof (int)); - file_sizes = (off_t *) xmalloc ((argc - optind) * sizeof (off_t)); - } - - if (header_mode == always - || (header_mode == multiple_files && optind < argc - 1)) - print_headers = 1; - - if (optind == argc) - exit_status |= tail_file ("-", n_units, 0); - - for (; optind < argc; ++optind) - exit_status |= tail_file (argv[optind], n_units, optind - fileind); - - if (forever_multiple) - tail_forever (argv + fileind, argc - fileind); - - if (have_read_stdin && close (0) < 0) - error (1, errno, "-"); - if (fclose (stdout) == EOF) - error (1, errno, _("write error")); - exit (exit_status); -} - -/* Display the last N_UNITS units of file FILENAME. - "-" for FILENAME means the standard input. - FILENUM is this file's index in the list of files the user gave. - Return 0 if successful, 1 if an error occurred. */ - -static int -tail_file (filename, n_units, filenum) - const char *filename; - off_t n_units; - int filenum; +static void +usage (int status) { - int fd, errors; - struct stat stats; - - if (!strcmp (filename, "-")) - { - have_read_stdin = 1; - filename = _("standard input"); - if (print_headers) - write_header (filename, NULL); - errors = tail (filename, 0, n_units); - if (forever_multiple) - { - if (fstat (0, &stats) < 0) - { - error (0, errno, _("standard input")); - errors = 1; - } - else if (!S_ISREG (stats.st_mode)) - { - error (0, 0, - _("standard input: cannot follow end of non-regular file")); - errors = 1; - } - if (errors) - file_descs[filenum] = -1; - else - { - file_descs[filenum] = 0; - file_sizes[filenum] = stats.st_size; - } - } - } + if (status != 0) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); else { - /* Not standard input. */ - fd = open (filename, O_RDONLY); - if (fd == -1) - { - if (forever_multiple) - file_descs[filenum] = -1; - error (0, errno, "%s", filename); - errors = 1; - } - else - { - if (print_headers) - write_header (filename, NULL); - errors = tail (filename, fd, n_units); - if (forever_multiple) - { - if (fstat (fd, &stats) < 0) - { - error (0, errno, "%s", filename); - errors = 1; - } - else if (!S_ISREG (stats.st_mode)) - { - error (0, 0, _("%s: cannot follow end of non-regular file"), - filename); - errors = 1; - } - if (errors) - { - close (fd); - file_descs[filenum] = -1; - } - else - { - file_descs[filenum] = fd; - file_sizes[filenum] = stats.st_size; - } - } - else - { - if (close (fd)) - { - error (0, errno, "%s", filename); - errors = 1; - } - } - } + printf (_("\ +Usage: %s [OPTION]... [FILE]...\n\ +"), + program_name); + printf (_("\ +Print last 10 lines of each FILE to standard output.\n\ +With more than one FILE, precede each with a header giving the file name.\n\ +With no FILE, or when FILE is -, read standard input.\n\ +\n\ + -c, --bytes=N output the last N bytes\n\ + -f, --follow output appended data as the file grows\n\ + -n, --lines=N output the last N lines, instead of last 10\n\ + -q, --quiet, --silent never output headers giving file names\n\ + -v, --verbose always output headers giving file names\n\ + --help display this help and exit\n\ + --version output version information and exit\n\ +\n\ +If the first character of N (the number of bytes or lines) is a `+',\n\ +print beginning with the Nth item from the start of each file, otherwise,\n\ +print the last N items in the file. N may have a multiplier suffix:\n\ +b for 512, k for 1024, m for 1048576 (1 Meg). A first OPTION of -VALUE\n\ +or +VALUE is treated like -n VALUE or -n +VALUE unless VALUE has one of\n\ +the [bkm] suffix multipliers, in which case it is treated like -c VALUE\n\ +or -c +VALUE.\n\ +")); } - - return errors; + exit (status); } static void -write_header (filename, comment) - const char *filename; - const char *comment; +write_header (const char *filename, const char *comment) { static int first_file = 1; @@ -436,140 +180,6 @@ write_header (filename, comment) first_file = 0; } -/* Display the last N_UNITS units of file FILENAME, open for reading - in FD. - Return 0 if successful, 1 if an error occurred. */ - -static int -tail (filename, fd, n_units) - const char *filename; - int fd; - off_t n_units; -{ - if (count_lines) - return tail_lines (filename, fd, (long) n_units); - else - return tail_bytes (filename, fd, n_units); -} - -/* Output the last N_BYTES bytes of file FILENAME open for reading in FD. - Return 0 if successful, 1 if an error occurred. */ - -static int -tail_bytes (filename, fd, n_bytes) - const char *filename; - int fd; - off_t n_bytes; -{ - struct stat stats; - - /* FIXME: resolve this like in dd.c. */ - /* Use fstat instead of checking for errno == ESPIPE because - lseek doesn't work on some special files but doesn't return an - error, either. */ - if (fstat (fd, &stats)) - { - error (0, errno, "%s", filename); - return 1; - } - - if (from_start) - { - if (S_ISREG (stats.st_mode)) - lseek (fd, n_bytes, SEEK_CUR); - else if (start_bytes (filename, fd, n_bytes)) - return 1; - dump_remainder (filename, fd); - } - else - { - if (S_ISREG (stats.st_mode)) - { - off_t current_pos, end_pos; - size_t bytes_remaining; - - if ((current_pos = lseek (fd, (off_t) 0, SEEK_CUR)) != -1 - && (end_pos = lseek (fd, (off_t) 0, SEEK_END)) != -1) - { - off_t diff; - /* Be careful here. The current position may actually be - beyond the end of the file. */ - bytes_remaining = (diff = end_pos - current_pos) < 0 ? 0 : diff; - } - else - { - error (0, errno, "%s", filename); - return 1; - } - - if (bytes_remaining <= n_bytes) - { - /* From the current position to end of file, there are no - more bytes than have been requested. So reposition the - file pointer to the incoming current position and print - everything after that. */ - lseek (fd, current_pos, SEEK_SET); - } - else - { - /* There are more bytes remaining than were requested. - Back up. */ - lseek (fd, -n_bytes, SEEK_END); - } - dump_remainder (filename, fd); - } - else - return pipe_bytes (filename, fd, n_bytes); - } - return 0; -} - -/* Output the last N_LINES lines of file FILENAME open for reading in FD. - Return 0 if successful, 1 if an error occurred. */ - -static int -tail_lines (filename, fd, n_lines) - const char *filename; - int fd; - long n_lines; -{ - struct stat stats; - off_t length; - - if (fstat (fd, &stats)) - { - error (0, errno, "%s", filename); - return 1; - } - - if (from_start) - { - if (start_lines (filename, fd, n_lines)) - return 1; - dump_remainder (filename, fd); - } - else - { - /* Use file_lines only if FD refers to a regular file with - its file pointer positioned at beginning of file. */ - /* FIXME: adding the lseek conjunct is a kludge. - Once there's a reasonable test suite, fix the true culprit: - file_lines. file_lines shouldn't presume that the input - file pointer is initially positioned to beginning of file. */ - if (S_ISREG (stats.st_mode) - && lseek (fd, (off_t) 0, SEEK_CUR) == (off_t) 0) - { - length = lseek (fd, (off_t) 0, SEEK_END); - if (length != 0 && file_lines (filename, fd, n_lines, length)) - return 1; - dump_remainder (filename, fd); - } - else - return pipe_lines (filename, fd, n_lines); - } - return 0; -} - /* Print the last N_LINES lines from the end of file FD. Go backward through the file, reading `BUFSIZ' bytes at a time (except probably the first), until we hit the start of the file or have @@ -579,11 +189,7 @@ tail_lines (filename, fd, n_lines) Return 0 if successful, 1 if an error occurred. */ static int -file_lines (filename, fd, n_lines, pos) - const char *filename; - int fd; - long n_lines; - off_t pos; +file_lines (const char *filename, int fd, long int n_lines, off_t pos) { char buffer[BUFSIZ]; int bytes_read; @@ -652,10 +258,7 @@ file_lines (filename, fd, n_lines, pos) Return 0 if successful, 1 if an error occured. */ static int -pipe_lines (filename, fd, n_lines) - const char *filename; - int fd; - long n_lines; +pipe_lines (const char *filename, int fd, long int n_lines) { struct linebuffer { @@ -774,10 +377,7 @@ free_lbuffers: Return 0 if successful, 1 if an error occurred. */ static int -pipe_bytes (filename, fd, n_bytes) - const char *filename; - int fd; - off_t n_bytes; +pipe_bytes (const char *filename, int fd, off_t n_bytes) { struct charbuffer { @@ -871,10 +471,7 @@ free_cbuffers: Return 1 on error, 0 if ok. */ static int -start_bytes (filename, fd, n_bytes) - const char *filename; - int fd; - off_t n_bytes; +start_bytes (const char *filename, int fd, off_t n_bytes) { char buffer[BUFSIZ]; int bytes_read = 0; @@ -896,10 +493,7 @@ start_bytes (filename, fd, n_bytes) Return 1 on error, 0 if ok. */ static int -start_lines (filename, fd, n_lines) - const char *filename; - int fd; - long n_lines; +start_lines (const char *filename, int fd, long int n_lines) { char buffer[BUFSIZ]; int bytes_read = 0; @@ -930,9 +524,7 @@ start_lines (filename, fd, n_lines) until killed. Return the number of bytes read from the file. */ static long -dump_remainder (filename, fd) - const char *filename; - int fd; +dump_remainder (const char *filename, int fd) { char buffer[BUFSIZ]; int bytes_read; @@ -964,9 +556,7 @@ output: second and try again. We do this until the user interrupts us. */ static void -tail_forever (names, nfiles) - char **names; - int nfiles; +tail_forever (char **names, int nfiles) { int last; @@ -1022,40 +612,399 @@ tail_forever (names, nfiles) } } -static void -usage (status) - int status; +/* Output the last N_BYTES bytes of file FILENAME open for reading in FD. + Return 0 if successful, 1 if an error occurred. */ + +static int +tail_bytes (const char *filename, int fd, off_t n_bytes) { - if (status != 0) - fprintf (stderr, _("Try `%s --help' for more information.\n"), - program_name); + struct stat stats; + + /* FIXME: resolve this like in dd.c. */ + /* Use fstat instead of checking for errno == ESPIPE because + lseek doesn't work on some special files but doesn't return an + error, either. */ + if (fstat (fd, &stats)) + { + error (0, errno, "%s", filename); + return 1; + } + + if (from_start) + { + if (S_ISREG (stats.st_mode)) + lseek (fd, n_bytes, SEEK_CUR); + else if (start_bytes (filename, fd, n_bytes)) + return 1; + dump_remainder (filename, fd); + } else { - printf (_("\ -Usage: %s [OPTION]... [FILE]...\n\ -"), - program_name); - printf (_("\ -Print last 10 lines of each FILE to standard output.\n\ -With more than one FILE, precede each with a header giving the file name.\n\ -With no FILE, or when FILE is -, read standard input.\n\ -\n\ - -c, --bytes=N output the last N bytes\n\ - -f, --follow output appended data as the file grows\n\ - -n, --lines=N output the last N lines, instead of last 10\n\ - -q, --quiet, --silent never output headers giving file names\n\ - -v, --verbose always output headers giving file names\n\ - --help display this help and exit\n\ - --version output version information and exit\n\ -\n\ -If the first character of N (the number of bytes or lines) is a `+',\n\ -print beginning with the Nth item from the start of each file, otherwise,\n\ -print the last N items in the file. N may have a multiplier suffix:\n\ -b for 512, k for 1024, m for 1048576 (1 Meg). A first OPTION of -VALUE\n\ -or +VALUE is treated like -n VALUE or -n +VALUE unless VALUE has one of\n\ -the [bkm] suffix multipliers, in which case it is treated like -c VALUE\n\ -or -c +VALUE.\n\ -")); + if (S_ISREG (stats.st_mode)) + { + off_t current_pos, end_pos; + size_t bytes_remaining; + + if ((current_pos = lseek (fd, (off_t) 0, SEEK_CUR)) != -1 + && (end_pos = lseek (fd, (off_t) 0, SEEK_END)) != -1) + { + off_t diff; + /* Be careful here. The current position may actually be + beyond the end of the file. */ + bytes_remaining = (diff = end_pos - current_pos) < 0 ? 0 : diff; + } + else + { + error (0, errno, "%s", filename); + return 1; + } + + if (bytes_remaining <= n_bytes) + { + /* From the current position to end of file, there are no + more bytes than have been requested. So reposition the + file pointer to the incoming current position and print + everything after that. */ + lseek (fd, current_pos, SEEK_SET); + } + else + { + /* There are more bytes remaining than were requested. + Back up. */ + lseek (fd, -n_bytes, SEEK_END); + } + dump_remainder (filename, fd); + } + else + return pipe_bytes (filename, fd, n_bytes); } - exit (status); + return 0; +} + +/* Output the last N_LINES lines of file FILENAME open for reading in FD. + Return 0 if successful, 1 if an error occurred. */ + +static int +tail_lines (const char *filename, int fd, long int n_lines) +{ + struct stat stats; + off_t length; + + if (fstat (fd, &stats)) + { + error (0, errno, "%s", filename); + return 1; + } + + if (from_start) + { + if (start_lines (filename, fd, n_lines)) + return 1; + dump_remainder (filename, fd); + } + else + { + /* Use file_lines only if FD refers to a regular file with + its file pointer positioned at beginning of file. */ + /* FIXME: adding the lseek conjunct is a kludge. + Once there's a reasonable test suite, fix the true culprit: + file_lines. file_lines shouldn't presume that the input + file pointer is initially positioned to beginning of file. */ + if (S_ISREG (stats.st_mode) + && lseek (fd, (off_t) 0, SEEK_CUR) == (off_t) 0) + { + length = lseek (fd, (off_t) 0, SEEK_END); + if (length != 0 && file_lines (filename, fd, n_lines, length)) + return 1; + dump_remainder (filename, fd); + } + else + return pipe_lines (filename, fd, n_lines); + } + return 0; +} + +/* Display the last N_UNITS units of file FILENAME, open for reading + in FD. + Return 0 if successful, 1 if an error occurred. */ + +static int +tail (const char *filename, int fd, off_t n_units) +{ + if (count_lines) + return tail_lines (filename, fd, (long) n_units); + else + return tail_bytes (filename, fd, n_units); +} + +/* Display the last N_UNITS units of file FILENAME. + "-" for FILENAME means the standard input. + FILENUM is this file's index in the list of files the user gave. + Return 0 if successful, 1 if an error occurred. */ + +static int +tail_file (const char *filename, off_t n_units, int filenum) +{ + int fd, errors; + struct stat stats; + + if (!strcmp (filename, "-")) + { + have_read_stdin = 1; + filename = _("standard input"); + if (print_headers) + write_header (filename, NULL); + errors = tail (filename, 0, n_units); + if (forever_multiple) + { + if (fstat (0, &stats) < 0) + { + error (0, errno, _("standard input")); + errors = 1; + } + else if (!S_ISREG (stats.st_mode)) + { + error (0, 0, + _("standard input: cannot follow end of non-regular file")); + errors = 1; + } + if (errors) + file_descs[filenum] = -1; + else + { + file_descs[filenum] = 0; + file_sizes[filenum] = stats.st_size; + } + } + } + else + { + /* Not standard input. */ + fd = open (filename, O_RDONLY); + if (fd == -1) + { + if (forever_multiple) + file_descs[filenum] = -1; + error (0, errno, "%s", filename); + errors = 1; + } + else + { + if (print_headers) + write_header (filename, NULL); + errors = tail (filename, fd, n_units); + if (forever_multiple) + { + if (fstat (fd, &stats) < 0) + { + error (0, errno, "%s", filename); + errors = 1; + } + else if (!S_ISREG (stats.st_mode)) + { + error (0, 0, _("%s: cannot follow end of non-regular file"), + filename); + errors = 1; + } + if (errors) + { + close (fd); + file_descs[filenum] = -1; + } + else + { + file_descs[filenum] = fd; + file_sizes[filenum] = stats.st_size; + } + } + else + { + if (close (fd)) + { + error (0, errno, "%s", filename); + errors = 1; + } + } + } + } + + return errors; +} + +void +main (int argc, char **argv) +{ + enum header_mode header_mode = multiple_files; + int exit_status = 0; + /* If from_start, the number of items to skip before printing; otherwise, + the number of items at the end of the file to print. Initially, -1 + means the value has not been set. */ + off_t n_units = -1; + long int tmp_long; + int c; /* Option character. */ + int fileind; /* Index in ARGV of first file name. */ + + program_name = argv[0]; + have_read_stdin = 0; + count_lines = 1; + forever = forever_multiple = from_start = print_headers = 0; + + if (argc > 1 + && ((argv[1][0] == '-' && ISDIGIT (argv[1][1])) + || (argv[1][0] == '+' && (ISDIGIT (argv[1][1]) || argv[1][1] == 0)))) + { + /* Old option syntax: a dash or plus, one or more digits (zero digits + are acceptable with a plus), and one or more option letters. */ + if (argv[1][0] == '+') + from_start = 1; + if (argv[1][1] != '\0') + { + strtol_error s_err; + char *p; + + s_err = xstrtol (++argv[1], &p, 0, &tmp_long, "bkm"); + n_units = tmp_long; + if (s_err == LONGINT_OVERFLOW) + { + STRTOL_FATAL_ERROR (argv[1], _("argument"), s_err); + } + /* Parse any appended option letters. */ + while (*p) + { + switch (*p) + { + case 'c': + /* Interpret N_UNITS as # of bytes. */ + count_lines = 0; + break; + + case 'f': + forever = 1; + break; + + case 'l': + count_lines = 1; + break; + + case 'q': + header_mode = never; + break; + + case 'v': + header_mode = always; + break; + + default: + error (0, 0, _("unrecognized option `-%c'"), *p); + usage (1); + } + ++p; + } + } + /* Make the options we just parsed invisible to getopt. */ + argv[1] = argv[0]; + argv++; + argc--; + } + + while ((c = getopt_long (argc, argv, "c:n:fqv", long_options, (int *) 0)) + != EOF) + { + strtol_error s_err; + + switch (c) + { + case 0: + break; + + case 'c': + count_lines = 0; + goto getnum; + + case 'n': + count_lines = 1; + getnum: + if (*optarg == '+') + { + from_start = 1; + } + + s_err = xstrtol (optarg, NULL, 0, &tmp_long, "bkm"); + if (tmp_long < 0) + tmp_long = -tmp_long; + n_units = tmp_long; + if (s_err != LONGINT_OK) + { + STRTOL_FATAL_ERROR (optarg, (c == 'n' + ? _("number of lines") + : _("number of bytes")), s_err); + } + break; + + case 'f': + forever = 1; + break; + + case 'q': + header_mode = never; + break; + + case 'v': + header_mode = always; + break; + + default: + usage (1); + } + } + + if (show_version) + { + printf ("tail - %s\n", version_string); + exit (0); + } + + if (show_help) + usage (0); + + if (n_units == -1) + n_units = DEFAULT_N_LINES; + + /* To start printing with item N_UNITS from the start of the file, skip + N_UNITS - 1 items. `tail +0' is actually meaningless, but for Unix + compatibility it's treated the same as `tail +1'. */ + if (from_start) + { + if (n_units) + --n_units; + } + + fileind = optind; + + if (optind < argc - 1 && forever) + { + forever_multiple = 1; + forever = 0; + file_descs = (int *) xmalloc ((argc - optind) * sizeof (int)); + file_sizes = (off_t *) xmalloc ((argc - optind) * sizeof (off_t)); + } + + if (header_mode == always + || (header_mode == multiple_files && optind < argc - 1)) + print_headers = 1; + + if (optind == argc) + exit_status |= tail_file ("-", n_units, 0); + + for (; optind < argc; ++optind) + exit_status |= tail_file (argv[optind], n_units, optind - fileind); + + if (forever_multiple) + tail_forever (argv + fileind, argc - fileind); + + if (have_read_stdin && close (0) < 0) + error (1, errno, "-"); + if (fclose (stdout) == EOF) + error (1, errno, _("write error")); + exit (exit_status); } |