head: support -n -NUM and -c -NUM

function                                             old     new   delta
head_main                                            406     832    +426
packed_usage                                       29234   29252     +18
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/0 up/down: 444/0)             Total: 444 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/coreutils/head.c b/coreutils/head.c
index 598fccb..291e1ce 100644
--- a/coreutils/head.c
+++ b/coreutils/head.c
@@ -21,7 +21,8 @@
 //usage:       "With more than one FILE, precede each with a filename header.\n"
 //usage:     "\n	-n N[kbm]	Print first N lines"
 //usage:	IF_FEATURE_FANCY_HEAD(
-//usage:     "\n	-c N[kbm]	Print first N bytes"
+//usage:     "\n	-n -N[kbm]	Print all except N last lines"
+//usage:     "\n	-c [-]N[kbm]	Print first N bytes"
 //usage:     "\n	-q		Never print headers"
 //usage:     "\n	-v		Always print headers"
 //usage:	)
@@ -38,6 +39,110 @@
 
 /* This is a NOEXEC applet. Be very careful! */
 
+#if !ENABLE_FEATURE_FANCY_HEAD
+# define print_first_N(fp,count,bytes) print_first_N(fp,count)
+#endif
+static void
+print_first_N(FILE *fp, unsigned long count, bool count_bytes)
+{
+#if !ENABLE_FEATURE_FANCY_HEAD
+	const int count_bytes = 0;
+#endif
+	while (count) {
+		int c = getc(fp);
+		if (c == EOF)
+			break;
+		if (count_bytes || (c == '\n'))
+			--count;
+		putchar(c);
+	}
+}
+
+#if ENABLE_FEATURE_FANCY_HEAD
+static void
+print_except_N_last_bytes(FILE *fp, unsigned count)
+{
+	unsigned char *circle = xmalloc(++count);
+	unsigned head = 0;
+	for(;;) {
+		int c;
+		c = getc(fp);
+		if (c == EOF)
+			goto ret;
+		circle[head++] = c;
+		if (head == count)
+			break;
+	}
+	for (;;) {
+		int c;
+		if (head == count)
+			head = 0;
+		putchar(circle[head]);
+		c = getc(fp);
+		if (c == EOF)
+			goto ret;
+		circle[head] = c;
+		head++;
+	}
+ ret:
+	free(circle);
+}
+
+static void
+print_except_N_last_lines(FILE *fp, unsigned count)
+{
+	char **circle = xzalloc((++count) * sizeof(circle[0]));
+	unsigned head = 0;
+	for(;;) {
+		char *c;
+		c = xmalloc_fgets(fp);
+		if (!c)
+			goto ret;
+		circle[head++] = c;
+		if (head == count)
+			break;
+	}
+	for (;;) {
+		char *c;
+		if (head == count)
+			head = 0;
+		fputs(circle[head], stdout);
+		c = xmalloc_fgets(fp);
+		if (!c)
+			goto ret;
+		free(circle[head]);
+		circle[head++] = c;
+	}
+ ret:
+	head = 0;
+	for(;;) {
+		free(circle[head++]);
+		if (head == count)
+			break;
+	}
+	free(circle);
+}
+#else
+/* Must never be called */
+void print_except_N_last_bytes(FILE *fp, unsigned count);
+void print_except_N_last_lines(FILE *fp, unsigned count);
+#endif
+
+#if !ENABLE_FEATURE_FANCY_HEAD
+# define eat_num(negative_N,p) eat_num(p)
+#endif
+static unsigned long
+eat_num(bool *negative_N, const char *p)
+{
+#if ENABLE_FEATURE_FANCY_HEAD
+	if (*p == '-') {
+		*negative_N = 1;
+		p++;
+	}
+#endif
+	return xatoul_sfx(p, head_tail_suffixes);
+}
+
 static const char head_opts[] ALIGN1 =
 	"n:"
 #if ENABLE_FEATURE_FANCY_HEAD
@@ -52,8 +157,13 @@
 {
 	unsigned long count = 10;
 #if ENABLE_FEATURE_FANCY_HEAD
-	int count_bytes = 0;
 	int header_threshhold = 1;
+	bool count_bytes = 0;
+	bool negative_N = 0;
+#else
+# define header_threshhold 1
+# define count_bytes       0
+# define negative_N        0
 #endif
 	FILE *fp;
 	const char *fmt;
@@ -68,7 +178,7 @@
 	) {
 		--argc;
 		++argv;
-		p = (*argv) + 1;
+		p = argv[0] + 1;
 		goto GET_COUNT;
 	}
 #endif
@@ -92,7 +202,7 @@
 #if ENABLE_INCLUDE_SUSv2 || ENABLE_FEATURE_FANCY_HEAD
  GET_COUNT:
 #endif
-			count = xatoul_sfx(p, head_tail_suffixes);
+			count = eat_num(&negative_N, p);
 			break;
 		default:
 			bb_show_usage();
@@ -105,39 +215,35 @@
 		*--argv = (char*)"-";
 
 	fmt = header_fmt_str + 1;
-#if ENABLE_FEATURE_FANCY_HEAD
 	if (argc <= header_threshhold) {
+#if ENABLE_FEATURE_FANCY_HEAD
 		header_threshhold = 0;
-	}
 #else
-	if (argc <= 1) {
 		fmt += 11; /* "" */
-	}
-	/* Now define some things here to avoid #ifdefs in the code below.
-	 * These should optimize out of the if conditions below. */
-#define header_threshhold   1
-#define count_bytes         0
 #endif
+	}
+	if (negative_N) {
+		if (count >= INT_MAX / sizeof(char*))
+			bb_error_msg("count is too big: %lu", count);
+	}
 
 	do {
 		fp = fopen_or_warn_stdin(*argv);
 		if (fp) {
-			unsigned long i;
-
 			if (fp == stdin) {
 				*argv = (char *) bb_msg_standard_input;
 			}
 			if (header_threshhold) {
 				printf(fmt, *argv);
 			}
-			i = count;
-			while (i) {
-				int c = getc(fp);
-				if (c == EOF)
-					break;
-				if (count_bytes || (c == '\n'))
-					--i;
-				putchar(c);
+			if (negative_N) {
+				if (count_bytes) {
+					print_except_N_last_bytes(fp, count);
+				} else {
+					print_except_N_last_lines(fp, count);
+				}
+			} else {
+				print_first_N(fp, count, count_bytes);
 			}
 			die_if_ferror_stdout();
 			if (fclose_if_not_stdin(fp)) {