360 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			360 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
| /* tail - copy the end of a file	Author: Norbert Schlenker */
 | |
| 
 | |
| /*   Syntax:	tail [-f] [-c number | -n number] [file]
 | |
|  *		tail -[number][c|l][f] [file]		(obsolescent)
 | |
|  *		tail +[number][c|l][f] [file]		(obsolescent)
 | |
|  *   Flags:
 | |
|  *	-c number	Measure starting point in bytes.  If number begins
 | |
|  *			with '+', the starting point is relative to the
 | |
|  *			the file's beginning.  If number begins with '-'
 | |
|  *			or has no sign, the starting point is relative to
 | |
|  *			the end of the file.
 | |
|  *	-f		Keep trying to read after EOF on files and FIFOs.
 | |
|  *	-n number	Measure starting point in lines.  The number
 | |
|  *			following the flag has significance similar to
 | |
|  *			that described for the -c flag.
 | |
|  *
 | |
|  *   If neither -c nor -n are specified, the default is tail -n 10.
 | |
|  *
 | |
|  *   In the obsolescent syntax, an argument with a 'c' following the
 | |
|  *   (optional) number is equivalent to "-c number" in the standard
 | |
|  *   syntax, with number including the leading sign ('+' or '-') of the
 | |
|  *   argument.  An argument with 'l' following the number is equivalent
 | |
|  *   to "-n number" in the standard syntax.  If the number is not
 | |
|  *   specified, 10 is used as the default.  If neither 'c' nor 'l' are
 | |
|  *   specified, 'l' is assumed.  The character 'f' may be suffixed to
 | |
|  *   the argument and is equivalent to specifying "-f" in the standard
 | |
|  *   syntax.  Look for lines marked "OBSOLESCENT".
 | |
|  *
 | |
|  *   If no file is specified, standard input is assumed. 
 | |
|  *
 | |
|  *   P1003.2 does not specify tail's behavior when a count of 0 is given.
 | |
|  *   It also does not specify clearly whether the first byte (line) of a
 | |
|  *   file should be numbered 0 or 1.  Historical behavior is that the
 | |
|  *   first byte is actually number 1 (contrary to all Unix standards).
 | |
|  *   Historically, a count of 0 (or -0) results in no output whatsoever,
 | |
|  *   while a count of +0 results in the entire file being copied (just like
 | |
|  *   +1).  The implementor does not agree with these behaviors, but has
 | |
|  *   copied them slavishly.  Look for lines marked "HISTORICAL".
 | |
|  *   
 | |
|  *   Author:    Norbert Schlenker
 | |
|  *   Copyright: None.  Released to the public domain.
 | |
|  *   Reference: P1003.2 section 4.59 (draft 10)
 | |
|  *   Notes:	Under Minix, this program requires chmem =30000.
 | |
|  *   Bugs:	No internationalization support; all messages are in English.
 | |
|  */
 | |
| 
 | |
| /* Force visible Posix names */
 | |
| #ifndef _POSIX_SOURCE
 | |
| #define _POSIX_SOURCE 1
 | |
| #endif
 | |
| 
 | |
| /* External interfaces */
 | |
| #include <sys/types.h>
 | |
| #include <sys/stat.h>
 | |
| #include <unistd.h>
 | |
| #include <ctype.h>
 | |
| #include <stdlib.h>
 | |
| #include <stdio.h>
 | |
| 
 | |
| /* External interfaces that should have been standardized into <getopt.h> */
 | |
| extern char *optarg;
 | |
| extern int optind;
 | |
| 
 | |
| /* We expect this constant to be defined in <limits.h> in a Posix program,
 | |
|  * but we'll specify it here just in case it's been left out.
 | |
|  */
 | |
| #ifndef LINE_MAX
 | |
| #define LINE_MAX 2048		/* minimum acceptable lower bound */
 | |
| #endif
 | |
| 
 | |
| /* Magic numbers suggested or required by Posix specification */
 | |
| #define SUCCESS	0		/* exit code in case of success */
 | |
| #define FAILURE 1		/*                   or failure */
 | |
| #define DEFAULT_COUNT 10	/* default number of lines or bytes */
 | |
| #define MIN_BUFSIZE (LINE_MAX * DEFAULT_COUNT)
 | |
| #define SLEEP_INTERVAL	1	/* sleep for one second intervals with -f */
 | |
| 
 | |
| #define FALSE 0
 | |
| #define TRUE 1
 | |
| 
 | |
| /* Internal functions - prototyped under Minix */
 | |
| _PROTOTYPE(int main, (int argc, char **argv));
 | |
| _PROTOTYPE(int tail, (int count, int bytes, int read_until_killed));
 | |
| _PROTOTYPE(int keep_reading, (void));
 | |
| _PROTOTYPE(void usage, (void));
 | |
| 
 | |
| int main(argc, argv)
 | |
| int argc;
 | |
| char *argv[];
 | |
| {
 | |
|   int cflag = FALSE;
 | |
|   int nflag = FALSE;
 | |
|   int fflag = FALSE;
 | |
|   int number = -DEFAULT_COUNT;
 | |
|   char *suffix;
 | |
|   int opt;
 | |
|   struct stat stat_buf;
 | |
| 
 | |
| /* Determining whether this invocation is via the standard syntax or
 | |
|  * via an obsolescent one is a nasty kludge.  Here it is, but there is
 | |
|  * no pretense at elegance.
 | |
|  */
 | |
|   if (argc == 1) {		/* simple:  default read of a pipe */
 | |
| 	exit(tail(-DEFAULT_COUNT, 0, fflag));
 | |
|   }
 | |
|   if ((argv[1][0] == '+') ||	/* OBSOLESCENT */
 | |
|       (argv[1][0] == '-' && ((isdigit(argv[1][1])) ||
 | |
| 			     (argv[1][1] == 'l') ||
 | |
| 			     (argv[1][1] == 'c' && argv[1][2] == 'f')))) {
 | |
| 	--argc; ++argv;
 | |
| 	if (isdigit(argv[0][1])) {
 | |
| 		number = (int)strtol(argv[0], &suffix, 10);
 | |
| 		if (number == 0) {		/* HISTORICAL */
 | |
| 			if (argv[0][0] == '+')
 | |
| 				number = 1;
 | |
| 			else
 | |
| 				exit(SUCCESS);
 | |
| 		}
 | |
| 	} else {
 | |
| 		number = (argv[0][0] == '+') ? DEFAULT_COUNT : -DEFAULT_COUNT;
 | |
| 		suffix = &(argv[0][1]);
 | |
| 	}
 | |
| 	if (*suffix != '\0') {
 | |
| 		if (*suffix == 'c') {
 | |
| 			cflag = TRUE;
 | |
| 			++suffix;
 | |
| 		}
 | |
| 		else
 | |
| 		if (*suffix == 'l') {
 | |
| 			nflag = TRUE;
 | |
| 			++suffix;
 | |
| 		}
 | |
| 	}
 | |
| 	if (*suffix != '\0') {
 | |
| 		if (*suffix == 'f') {
 | |
| 			fflag = TRUE;
 | |
| 			++suffix;
 | |
| 		}
 | |
| 	}
 | |
| 	if (*suffix != '\0') {	/* bad form: assume to be a file name */
 | |
| 		number = -DEFAULT_COUNT;
 | |
| 		cflag = nflag = FALSE;
 | |
| 		fflag = FALSE;
 | |
| 	} else {
 | |
| 		--argc; ++argv;
 | |
| 	}
 | |
|   } else {			/* new standard syntax */
 | |
| 	while ((opt = getopt(argc, argv, "c:fn:")) != EOF) {
 | |
| 		switch (opt) {
 | |
| 		      case 'c':
 | |
| 			cflag = TRUE;
 | |
| 			if (*optarg == '+' || *optarg == '-')
 | |
| 				number = atoi(optarg);
 | |
| 			else
 | |
| 			if (isdigit(*optarg))
 | |
| 				number = -atoi(optarg);
 | |
| 			else
 | |
| 				usage();
 | |
| 			if (number == 0) {		/* HISTORICAL */
 | |
| 				if (*optarg == '+')
 | |
| 					number = 1;
 | |
| 				else
 | |
| 					exit(SUCCESS);
 | |
| 			}
 | |
| 			break;
 | |
| 		      case 'f':
 | |
| 			fflag = TRUE;
 | |
| 			break;
 | |
| 		      case 'n':
 | |
| 			nflag = TRUE;
 | |
| 			if (*optarg == '+' || *optarg == '-')
 | |
| 				number = atoi(optarg);
 | |
| 			else
 | |
| 			if (isdigit(*optarg))
 | |
| 				number = -atoi(optarg);
 | |
| 			else
 | |
| 				usage();
 | |
| 			if (number == 0) {		/* HISTORICAL */
 | |
| 				if (*optarg == '+')
 | |
| 					number = 1;
 | |
| 				else
 | |
| 					exit(SUCCESS);
 | |
| 			}
 | |
| 			break;
 | |
| 		      default:
 | |
| 			usage();
 | |
| 			/* NOTREACHED */
 | |
| 		}
 | |
| 	}
 | |
| 	argc -= optind;
 | |
| 	argv += optind;
 | |
|   }
 | |
| 
 | |
|   if (argc > 1 ||		/* too many arguments */
 | |
|       (cflag && nflag)) {	/* both bytes and lines specified */
 | |
| 	usage();
 | |
|   }
 | |
| 
 | |
|   if (argc > 0) {		/* an actual file */
 | |
| 	if (freopen(argv[0], "r", stdin) != stdin) {
 | |
| 		fputs("tail: could not open ", stderr);
 | |
| 		fputs(argv[0], stderr);
 | |
| 		fputs("\n", stderr);
 | |
| 		exit(FAILURE);
 | |
| 	}
 | |
| 	/* There is an optimization possibility here.  If a file is being
 | |
| 	 * read, we need not look at the front of it.  If we seek backwards
 | |
|          * from the end, we can (potentially) avoid looking at most of the
 | |
| 	 * file.  Some systems fail when asked to seek backwards to a point
 | |
| 	 * before the start of the file, so we avoid that possibility.
 | |
| 	 */
 | |
| 	if (number < 0 && fstat(fileno(stdin), &stat_buf) == 0) {
 | |
| 		long offset = cflag ? (long)number : (long)number * LINE_MAX;
 | |
| 
 | |
| 		if (-offset < stat_buf.st_size)
 | |
| 			fseek(stdin, offset, SEEK_END);
 | |
| 	}
 | |
|   } else {
 | |
| 	fflag = FALSE;		/* force -f off when reading a pipe */
 | |
|   }
 | |
|   exit(tail(number, cflag, fflag));
 | |
|   /* NOTREACHED */
 | |
| }
 | |
| 
 | |
| int tail(count, bytes, read_until_killed)
 | |
| int count;			/* lines or bytes desired */
 | |
| int bytes;			/* TRUE if we want bytes */
 | |
| int read_until_killed;		/* keep reading at EOF */
 | |
| {
 | |
|   int c;
 | |
|   char *buf;			/* pointer to input buffer */
 | |
|   char *buf_end;		/* and one past its end */
 | |
|   char *start;			/* pointer to first desired character in buf */
 | |
|   char *finish;			/* pointer past last desired character */
 | |
|   int wrapped_once = FALSE;	/* TRUE after buf has been filled once */
 | |
| 
 | |
| /* This is magic.  If count is positive, it means start at the count'th
 | |
|  * line or byte, with the first line or byte considered number 1.  Thus,
 | |
|  * we want to SKIP one less line or byte than the number specified.  In
 | |
|  * the negative case, we look backward from the end of the file for the
 | |
|  * (count + 1)'th newline or byte, so we really want the count to be one
 | |
|  * LARGER than was specified (in absolute value).  In either case, the
 | |
|  * right thing to do is:
 | |
|  */
 | |
|   --count;
 | |
| 
 | |
| /* Count is positive:  skip the desired lines or bytes and then copy. */
 | |
|   if (count >= 0) {
 | |
| 	while (count > 0 && (c = getchar()) != EOF) {
 | |
| 		if (bytes || c == '\n')
 | |
| 			--count;
 | |
| 	}
 | |
| 	while ((c = getchar()) != EOF) {
 | |
| 		if (putchar(c) == EOF)
 | |
| 			return FAILURE;
 | |
| 	}
 | |
| 	if (read_until_killed)
 | |
| 		return keep_reading();
 | |
| 	return ferror(stdin) ? FAILURE : SUCCESS;
 | |
|   }
 | |
| 
 | |
| /* Count is negative:  allocate a reasonably large buffer. */
 | |
|   if ((buf = (char *)malloc(MIN_BUFSIZE + 1)) == (char *)NULL) {
 | |
| 	fputs("tail: out of memory\n", stderr);
 | |
| 	return FAILURE;
 | |
|   }
 | |
|   buf_end = buf + (MIN_BUFSIZE + 1);
 | |
| 
 | |
| /* Read the entire file into the buffer. */
 | |
|   finish = buf;
 | |
|   while ((c = getchar()) != EOF) {
 | |
| 	*finish++ = c;
 | |
| 	if (finish == buf_end) {
 | |
| 		finish = buf;
 | |
| 		wrapped_once = TRUE;
 | |
| 	}
 | |
|   }
 | |
|   if (ferror(stdin))
 | |
| 	return FAILURE;
 | |
| 
 | |
| /* Back up inside the buffer.  The count has already been adjusted to
 | |
|  * back up exactly one character too far, so we will bump the buffer
 | |
|  * pointer once after we're done.
 | |
|  * 
 | |
|  * BUG: For large line counts, the buffer may not be large enough to
 | |
|  *	hold all the lines.  The specification allows the program to
 | |
|  *	fail in such a case - this program will simply dump the entire
 | |
|  *	buffer's contents as its best attempt at the desired behavior.
 | |
|  */
 | |
|   if (finish != buf || wrapped_once) {		/* file was not empty */
 | |
| 	start = (finish == buf) ? buf_end - 1 : finish - 1;
 | |
| 	while (start != finish) {
 | |
| 		if ((bytes || *start == '\n') && ++count == 0)
 | |
| 			break;
 | |
| 		if (start == buf) {
 | |
| 			start = buf_end - 1;
 | |
| 			if (!wrapped_once)	/* never wrapped: stop now */
 | |
| 				break;
 | |
| 		} else {
 | |
| 			--start;
 | |
| 		}
 | |
| 	}
 | |
| 	if (++start == buf_end) {		/* bump after going too far */
 | |
| 		start = buf;
 | |
| 	}
 | |
| 	if (finish > start) {
 | |
| 		fwrite(start, 1, finish - start, stdout);
 | |
| 	} else {
 | |
| 		fwrite(start, 1, buf_end - start, stdout);
 | |
| 		fwrite(buf, 1, finish - buf, stdout);
 | |
| 	}
 | |
|   }
 | |
|   if (read_until_killed)
 | |
| 	return keep_reading();
 | |
|   return ferror(stdout) ? FAILURE : SUCCESS;
 | |
| }
 | |
| 
 | |
| /* Wake at intervals to reread standard input.  Copy anything read to
 | |
|  * standard output and then go to sleep again.
 | |
|  */
 | |
| int keep_reading()
 | |
| {
 | |
|   char buf[1024];
 | |
|   int n;
 | |
|   int i;
 | |
|   off_t pos;
 | |
|   struct stat st;
 | |
| 
 | |
|   pos = lseek(0, (off_t) 0, SEEK_CUR);
 | |
|   for (;;) {
 | |
|   	for (i = 0; i < 60; i++) {
 | |
|   		while ((n = read(0, buf, sizeof(buf))) > 0) {
 | |
|   			if (write(1, buf, n) < 0) return FAILURE;
 | |
|   		}
 | |
|   		if (n < 0) return FAILURE;
 | |
| 
 | |
| 		sleep(SLEEP_INTERVAL);
 | |
| 	}
 | |
| 
 | |
| 	/* Rewind if suddenly truncated. */
 | |
| 	if (pos != -1) {
 | |
| 		if (fstat(0, &st) == -1) {
 | |
| 			pos = -1;
 | |
| 		} else
 | |
| 		if (st.st_size < pos) {
 | |
| 			pos = lseek(0, (off_t) 0, SEEK_SET);
 | |
| 		} else {
 | |
| 			pos = st.st_size;
 | |
| 		}
 | |
| 	}
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* Tell the user the standard syntax. */
 | |
| void usage()
 | |
| {
 | |
|   fputs("Usage: tail [-f] [-c number | -n number] [file]\n", stderr);
 | |
|   exit(FAILURE);
 | |
| }
 | 
