tail: read only a portion of a file if it's huge. closes bug 551

function                                             old     new   delta
tail_main                                           1494    1526     +32
tail_read                                            125     129      +4

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/coreutils/tail.c b/coreutils/tail.c
index 5f98bff..05dadcd 100644
--- a/coreutils/tail.c
+++ b/coreutils/tail.c
@@ -51,10 +51,10 @@
 	struct stat sbuf;
 
 	/* /proc files report zero st_size, don't lseek them. */
-	if (fstat(fd, &sbuf) == 0 && sbuf.st_size) {
+	if (fstat(fd, &sbuf) == 0 && sbuf.st_size > 0) {
 		current = lseek(fd, 0, SEEK_CUR);
 		if (sbuf.st_size < current)
-			lseek(fd, 0, SEEK_SET);
+			xlseek(fd, 0, SEEK_SET);
 	}
 
 	r = full_read(fd, buf, count);
@@ -174,8 +174,9 @@
 		int newlines_seen;
 		unsigned seen;
 		int nread;
+		int fd = fds[i];
 
-		if (ENABLE_FEATURE_FANCY_TAIL && fds[i] < 0)
+		if (ENABLE_FEATURE_FANCY_TAIL && fd < 0)
 			continue; /* may happen with -E */
 
 		if (nfiles > header_threshhold) {
@@ -183,30 +184,46 @@
 			fmt = header_fmt;
 		}
 
-		/* Optimizing count-bytes case if the file is seekable.
-		 * Beware of backing up too far.
-		 * Also we exclude files with size 0 (because of /proc/xxx) */
-		if (COUNT_BYTES && !from_top) {
-			off_t current = lseek(fds[i], 0, SEEK_END);
+		if (!from_top) {
+			off_t current = lseek(fd, 0, SEEK_END);
 			if (current > 0) {
-				if (count == 0)
-					continue; /* showing zero bytes is easy :) */
-				current -= count;
+				unsigned off;
+				if (COUNT_BYTES) {
+				/* Optimizing count-bytes case if the file is seekable.
+				 * Beware of backing up too far.
+				 * Also we exclude files with size 0 (because of /proc/xxx) */
+					if (count == 0)
+						continue; /* showing zero bytes is easy :) */
+					current -= count;
+					if (current < 0)
+						current = 0;
+					xlseek(fd, current, SEEK_SET);
+					bb_copyfd_size(fd, STDOUT_FILENO, count);
+					continue;
+				}
+#if 1 /* This is technically incorrect for *LONG* strings, but very useful */
+				/* Optimizing count-lines case if the file is seekable.
+				 * We assume the lines are <64k.
+				 * (Users complain that tail takes too long
+				 * on multi-gigabyte files) */
+				off = (count | 0xf); /* for small counts, be more paranoid */
+				if (off > (INT_MAX / (64*1024)))
+					off = (INT_MAX / (64*1024));
+				current -= off * (64*1024);
 				if (current < 0)
 					current = 0;
-				xlseek(fds[i], current, SEEK_SET);
-				bb_copyfd_size(fds[i], STDOUT_FILENO, count);
-				continue;
+				xlseek(fd, current, SEEK_SET);
+#endif
 			}
 		}
 
 		buf = tailbuf;
 		taillen = 0;
 		/* "We saw 1st line/byte".
-		 * Used only by +N code ("start from Nth", 1-based) */
+		 * Used only by +N code ("start from Nth", 1-based): */
 		seen = 1;
 		newlines_seen = 0;
-		while ((nread = tail_read(fds[i], buf, tailbufsize-taillen)) > 0) {
+		while ((nread = tail_read(fd, buf, tailbufsize-taillen)) > 0) {
 			if (from_top) {
 				int nwrite = nread;
 				if (seen < count) {