123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529 |
- /*++
- Copyright (c) 2013 Minoca Corp. All Rights Reserved
- Module Name:
- tail.c
- Abstract:
- This module implements the tail utility.
- Author:
- Evan Green 3-Jul-2013
- Environment:
- POSIX
- --*/
- //
- // ------------------------------------------------------------------- Includes
- //
- #include <minoca/lib/types.h>
- #include <assert.h>
- #include <ctype.h>
- #include <errno.h>
- #include <getopt.h>
- #include <libgen.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include "swlib.h"
- //
- // ---------------------------------------------------------------- Definitions
- //
- #define TAIL_VERSION_MAJOR 1
- #define TAIL_VERSION_MINOR 0
- #define TAIL_USAGE \
- "usage: tail [-f] [-c number | -n number] [file]\n" \
- "The tail command copies its input to standard output starting at the \n" \
- "given position. Positions start with + to specify an offset from the \n" \
- "beginning of the file, or - for offsets from the end of the file. \n" \
- "Both lines and bytes start counting at 1, not 0. Specifying a number \n" \
- "without a sign is the same as specifying a -. Valid options are:\n" \
- " -f, --follow -- If the input is a regular file or the operand \n" \
- " specifies a FIFO, do not terminate after the last line of the \n"\
- " input has been copied. Read and copy further bytes as they \n" \
- " become available. If no file operand is specified and standard \n"\
- " in is a pipe, this option is ignored.\n" \
- " -c, --bytes=number -- Output the first or last number of bytes, \n" \
- " depending on whether a + or - is prepended to the number.\n" \
- " -n, --lines=number -- Output the first or last number of lines.\n" \
- " --help -- Show this help text and exit.\n" \
- " --version - Show the application version information and exit.\n"
- #define TAIL_OPTIONS_STRING "fc:n:"
- //
- // Define tail options.
- //
- //
- // This option is set to continue printing even after the end of the file.
- //
- #define TAIL_OPTION_FOLLOW 0x00000001
- //
- // This option is set to count lines instead of bytes.
- //
- #define TAIL_OPTION_LINES 0x00000002
- //
- // This option is set to indicate that the offset is from the end of the file.
- //
- #define TAIL_OPTION_FROM_END 0x00000004
- //
- // Define the default offset.
- //
- #define TAIL_DEFAULT_OFFSET 10
- //
- // Define the maximum size of a "line" as far as keeping a buffer for it goes.
- //
- #define TAIL_MAX_LINE 2048
- //
- // ------------------------------------------------------ Data Type Definitions
- //
- //
- // ----------------------------------------------- Internal Function Prototypes
- //
- //
- // -------------------------------------------------------------------- Globals
- //
- struct option TailLongOptions[] = {
- {"follow", no_argument, 0, 'f'},
- {"bytes", required_argument, 0, 'c'},
- {"lines", required_argument, 0, 'n'},
- {"help", no_argument, 0, 'h'},
- {"version", no_argument, 0, 'V'},
- {NULL, 0, 0, 0},
- };
- //
- // ------------------------------------------------------------------ Functions
- //
- INT
- TailMain (
- INT ArgumentCount,
- CHAR **Arguments
- )
- /*++
- Routine Description:
- This routine is the main entry point for the tail utility.
- Arguments:
- ArgumentCount - Supplies the number of command line arguments the program
- was invoked with.
- Arguments - Supplies a tokenized array of command line arguments.
- Return Value:
- Returns an integer exit code. 0 for success, nonzero otherwise.
- --*/
- {
- PSTR Argument;
- ULONG ArgumentIndex;
- UINTN BackupCount;
- PUCHAR Buffer;
- UINTN BufferNextIndex;
- UINTN BufferSize;
- UINTN BufferValidSize;
- INT Character;
- PSTR FileName;
- FILE *Input;
- ULONGLONG Multiplier;
- ULONGLONG Offset;
- INT Option;
- ULONG Options;
- UINTN StartIndex;
- struct stat Stat;
- int Status;
- Buffer = NULL;
- Input = NULL;
- Multiplier = 1;
- Offset = TAIL_DEFAULT_OFFSET;
- Options = TAIL_OPTION_FROM_END | TAIL_OPTION_LINES;
- //
- // Handle something like tail -40 myfile or tail -4.
- //
- if (((ArgumentCount == 2) || (ArgumentCount == 3)) &&
- (Arguments[1][0] == '-') && (isdigit(Arguments[1][1]))) {
- Offset = strtoull(Arguments[1] + 1, NULL, 10);
- Options |= TAIL_OPTION_FROM_END;
- ArgumentIndex = 2;
- } else {
- //
- // Process the control arguments.
- //
- while (TRUE) {
- Option = getopt_long(ArgumentCount,
- Arguments,
- TAIL_OPTIONS_STRING,
- TailLongOptions,
- NULL);
- if (Option == -1) {
- break;
- }
- if ((Option == '?') || (Option == ':')) {
- Status = 1;
- goto MainEnd;
- }
- switch (Option) {
- case 'f':
- Options |= TAIL_OPTION_FOLLOW;
- break;
- case 'c':
- case 'n':
- if (Option == 'c') {
- Options &= ~TAIL_OPTION_LINES;
- } else {
- Options |= TAIL_OPTION_LINES;
- }
- Argument = optarg;
- assert(Argument != NULL);
- if (*Argument == '+') {
- Options &= ~TAIL_OPTION_FROM_END;
- Argument += 1;
- } else {
- Options |= TAIL_OPTION_FROM_END;
- if (*Argument == '-') {
- Argument += 1;
- }
- }
- Offset = SwParseFileSize(Argument);
- if (Offset == -1ULL) {
- SwPrintError(0, Argument, "Invalid size");
- Status = EINVAL;
- goto MainEnd;
- }
- break;
- case 'V':
- SwPrintVersion(TAIL_VERSION_MAJOR, TAIL_VERSION_MINOR);
- return 1;
- case 'h':
- printf(TAIL_USAGE);
- return 1;
- default:
- assert(FALSE);
- Status = 1;
- goto MainEnd;
- }
- }
- ArgumentIndex = optind;
- }
- if (ArgumentIndex > ArgumentCount) {
- ArgumentIndex = ArgumentCount;
- }
- FileName = NULL;
- if (ArgumentIndex < ArgumentCount) {
- FileName = Arguments[ArgumentIndex];
- ArgumentIndex += 1;
- }
- if (ArgumentIndex < ArgumentCount) {
- SwPrintError(0, Arguments[ArgumentIndex], "Unexpected operand");
- Status = EINVAL;
- goto MainEnd;
- }
- //
- // Open up the file if one was specified, or use standard in (in which case
- // the follow option is ignored).
- //
- if (FileName != NULL) {
- Input = fopen(FileName, "r");
- if (Input == NULL) {
- Status = errno;
- SwPrintError(Status, FileName, "Unable to open");
- goto MainEnd;
- }
- } else {
- FileName = "(stdin)";
- Input = stdin;
- Options &= ~TAIL_OPTION_FOLLOW;
- }
- assert(Offset != 0);
- if ((Options & TAIL_OPTION_FROM_END) == 0) {
- Offset -= 1;
- }
- Offset *= Multiplier;
- //
- // If it's a regular file in byte mode, use seek.
- //
- if ((Input != stdin) & ((Options & TAIL_OPTION_LINES) == 0)) {
- Status = SwOsStat(FileName, TRUE, &Stat);
- if ((Status == 0) && (S_ISREG(Stat.st_mode))) {
- if ((Options & TAIL_OPTION_FROM_END) != 0) {
- if (Offset > Stat.st_size) {
- Offset = Stat.st_size;
- }
- Status = fseek(Input, Stat.st_size - Offset, SEEK_SET);
- } else {
- Status = fseek(Input, Offset, SEEK_SET);
- }
- if (Status == 0) {
- Offset = 0;
- }
- }
- }
- //
- // If there's still an offset, go to it.
- //
- if (Offset != 0) {
- if ((Options & TAIL_OPTION_FROM_END) != 0) {
- BufferSize = Offset;
- if ((Options & TAIL_OPTION_LINES) != 0) {
- BufferSize *= TAIL_MAX_LINE;
- }
- Buffer = malloc(BufferSize);
- if (Buffer == NULL) {
- Status = ENOMEM;
- goto MainEnd;
- }
- BufferNextIndex = 0;
- BufferValidSize = 0;
- //
- // Get to the end of the file.
- //
- while (TRUE) {
- Character = fgetc(Input);
- if (Character == EOF) {
- break;
- }
- Buffer[BufferNextIndex] = Character;
- BufferNextIndex += 1;
- if (BufferNextIndex == BufferSize) {
- BufferNextIndex = 0;
- }
- if (BufferValidSize < BufferSize) {
- BufferValidSize += 1;
- }
- }
- //
- // Back up by the requested number of bytes or lines.
- //
- if ((Options & TAIL_OPTION_LINES) != 0) {
- BackupCount = 0;
- //
- // Start from the character before the very last character,
- // in order to skip a newline at the very end.
- //
- StartIndex = BufferNextIndex;
- if (StartIndex == 0) {
- StartIndex = BufferSize - 1;
- } else {
- StartIndex -= 1;
- }
- while (BackupCount < BufferValidSize) {
- if (StartIndex == 0) {
- StartIndex = BufferSize - 1;
- } else {
- StartIndex -= 1;
- }
- if (Buffer[StartIndex] == '\n') {
- Offset -= 1;
- //
- // Go forward by one character at the end so everything
- // doesn't begin with that newline.
- //
- if (Offset == 0) {
- StartIndex += 1;
- if (StartIndex == BufferSize) {
- StartIndex = 0;
- }
- break;
- }
- }
- BackupCount += 1;
- }
- //
- // Account for that last character that was skipped.
- //
- BackupCount += 1;
- } else {
- assert(BufferSize == Offset);
- if (BufferNextIndex < BufferValidSize) {
- StartIndex = BufferNextIndex - BufferValidSize + BufferSize;
- } else {
- StartIndex = BufferNextIndex - BufferValidSize;
- }
- BackupCount = BufferValidSize;
- }
- //
- // Print out the buffered contents.
- //
- while (BackupCount != 0) {
- fputc(Buffer[StartIndex], stdout);
- if (StartIndex == BufferSize - 1) {
- StartIndex = 0;
- } else {
- StartIndex += 1;
- }
- BackupCount -= 1;
- }
- //
- // Seek from the beginning.
- //
- } else {
- while (Offset != 0) {
- Character = fgetc(Input);
- if (Character == EOF) {
- break;
- }
- if (((Options & TAIL_OPTION_LINES) == 0) ||
- (Character == '\n')) {
- Offset -= 1;
- }
- }
- }
- }
- //
- // Now the easy part, just print the contents.
- //
- Status = 0;
- while (TRUE) {
- Character = fgetc(Input);
- if (Character == EOF) {
- if (ferror(Input) != 0) {
- Status = errno;
- break;
- }
- //
- // If following and this is just the end of the file, sleep for a
- // second and try again.
- //
- if ((Options & TAIL_OPTION_FOLLOW)) {
- SwSleep(1000000);
- continue;
- }
- //
- // Otherwise, stop.
- //
- break;
- }
- fputc(Character, stdout);
- }
- MainEnd:
- if (Buffer != NULL) {
- free(Buffer);
- }
- if ((Input != NULL) && (Input != stdin)) {
- fclose(Input);
- }
- return Status;
- }
- //
- // --------------------------------------------------------- Internal Functions
- //
|