[tar] Fix support for pipes in Fuchsia

- I broke skipblk() when I added support for lseek(). Seeking does not
work on pipes and it's necessary to fall back to sequential reads.
- Pipes will sometimes return short reads. This is legal but unusual.
I updated unarchive() to cleanly handle this condition.
- Also fixed directory creation, which wasn't setting the mode because
of a premature call to mkdirp().

Change-Id: I72e77dd35a1a95afce3c1ef8241cf8f66d320a7d
diff --git a/tar.c b/tar.c
index 7f2eccc..613d562 100644
--- a/tar.c
+++ b/tar.c
@@ -255,7 +255,7 @@
 static int
 unarchive(char *fname, ssize_t l, char b[BLKSIZ])
 {
-	char lname[101], *tmp, *p;
+	char lname[101], *p;
 	long mode, major, minor, type, mtime, uid, gid;
 	struct header *h = (struct header *)b;
 	int fd = -1;
@@ -266,9 +266,13 @@
 	if (remove(fname) < 0 && errno != ENOENT && errno != ENOTEMPTY)
 		weprintf("remove %s:", fname);
 
-	tmp = estrdup(fname);
-	mkdirp(dirname(tmp));
-	free(tmp);
+	// tar files normally create the directory chain. This is a fallback
+	// for noncompliant tar files.
+	if (h->type != DIRECTORY) {
+		char* tmp = estrdup(fname);
+		mkdirp(dirname(tmp));
+		free(tmp);
+	}
 
 	switch (h->type) {
 	case REG:
@@ -324,16 +328,30 @@
 		eprintf("strtol %s: invalid number\n", h->gid);
 
 	if (fd != -1) {
+		// Ceiling to BLKSIZ boundary
+		int readsize = (l + (BLKSIZ-1)) & ~(BLKSIZ-1);
 		char chunk[COPY_CHUNK_SIZE];
-		for (; l > 0; l -= COPY_CHUNK_SIZE) {
-			// Ceiling to BLKSIZ boundary
-			int ceilsize = (MIN(l, COPY_CHUNK_SIZE) + (BLKSIZ-1)) & ~(BLKSIZ-1);
-			if (eread(tarfd, chunk, ceilsize) != ceilsize) {
+		int lastread = 0;
+
+		for (; readsize > 0; l -= lastread, readsize -= lastread) {
+			int chunk_size = MIN(readsize, COPY_CHUNK_SIZE);
+			// Short reads are legal, so don't expect to read
+			// everything that was requested.
+			lastread = eread(tarfd, chunk, chunk_size);
+			if (lastread == 0) {
 				close(fd);
 				remove(fname);
-				eprintf("unexpected end of file reading %s.\n", fname);
+				eprintf("unexpected end of file reading %s.\n",
+					fname);
 			}
-			ewrite(fd, chunk, ceilsize);
+
+			// TODO(jimbe) Remove this "if" block when we better
+			// understand what causes a short read.
+			//if (chunk_size != lastread) {
+			//	printf("info: short read %d %d\n", chunk_size,
+			//	       lastread);
+			//}
+			ewrite(fd, chunk, MIN(l, lastread));
 		}
 		close(fd);
 	}
@@ -361,11 +379,33 @@
 static void
 skipblk(ssize_t l)
 {
-	// Ceiling to the next BLKSIZ boundary
+	char b[BLKSIZ];
+	int lastread = 0;
+	// Ceiling to BLKSIZ boundary
 	int ceilsize = (l + (BLKSIZ-1)) & ~(BLKSIZ-1);
-	if (lseek(tarfd, ceilsize, SEEK_CUR) == -1) {
+
+	off_t offset = lseek(tarfd, ceilsize, SEEK_CUR);
+	if (offset >= ceilsize)
+		return;
+        // TODO(jimbe) remove EOPNOTSUPP when US-297 is fixed
+	if (errno != ESPIPE && errno != EOPNOTSUPP) {
 		eprintf("unexpected end of file.\n");
 	}
+
+	// This is a pipe, socket or FIFO. Fall back to a sequential read.
+	for (; ceilsize > 0; ceilsize -= lastread) {
+		int chunk_size = MIN(ceilsize, BLKSIZ);
+		lastread = eread(tarfd, b, chunk_size);
+		// TODO(jimbe) Remove this "if" block when we better understand
+		// what causes a short read.
+		//if (chunk_size != lastread) {
+		//	printf("info: short read %d %d\n", chunk_size,
+		//	       lastread);
+		//}
+		if (lastread == 0) {
+			eprintf("unexpected end of file %d.\n", errno);
+		}
+        }
 }
 
 static int