Merge "Sync with upstream 643521d:" am: 3cddfb862c
am: 6e892c28ab

Change-Id: If8a7c8df2e75d347eebb1a1b74782ffa314981a0
diff --git a/boot.c b/boot.c
index 9e0958a..33577ca 100644
--- a/boot.c
+++ b/boot.c
@@ -33,6 +33,9 @@
   "$FreeBSD$";
 #endif /* not lint */
 
+#include <sys/param.h>
+
+#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
@@ -46,10 +49,8 @@
 {
 	u_char block[DOSBOOTBLOCKSIZE];
 	u_char fsinfo[2 * DOSBOOTBLOCKSIZE];
-	u_char backup[DOSBOOTBLOCKSIZE];
 	int ret = FSOK;
-	int i;
-	
+
 	if ((size_t)read(dosfs, block, sizeof block) != sizeof block) {
 		perr("could not read boot block");
 		return FSFATAL;
@@ -64,61 +65,133 @@
 	memset(boot, 0, sizeof *boot);
 	boot->ValidFat = -1;
 
-	/* decode bios parameter block */
+	/* Decode BIOS Parameter Block */
+
+	/* Bytes per sector: can only be  512, 1024, 2048 and 4096. */
 	boot->bpbBytesPerSec = block[11] + (block[12] << 8);
-	boot->bpbSecPerClust = block[13];
-	boot->bpbResSectors = block[14] + (block[15] << 8);
-	boot->bpbFATs = block[16];
-	boot->bpbRootDirEnts = block[17] + (block[18] << 8);
-	boot->bpbSectors = block[19] + (block[20] << 8);
-	boot->bpbMedia = block[21];
-	boot->bpbFATsmall = block[22] + (block[23] << 8);
-	boot->SecPerTrack = block[24] + (block[25] << 8);
-	boot->bpbHeads = block[26] + (block[27] << 8);
-	boot->bpbHiddenSecs = block[28] + (block[29] << 8) +
-	    (block[30] << 16) + (block[31] << 24);
-	boot->bpbHugeSectors = block[32] + (block[33] << 8) +
-	    (block[34] << 16) + (block[35] << 24);
-
-	boot->FATsecs = boot->bpbFATsmall;
-
-	if (boot->bpbBytesPerSec % DOSBOOTBLOCKSIZE_REAL != 0 ||
-	    boot->bpbBytesPerSec / DOSBOOTBLOCKSIZE_REAL == 0) {
+	if (boot->bpbBytesPerSec < DOSBOOTBLOCKSIZE_REAL ||
+	    boot->bpbBytesPerSec > DOSBOOTBLOCKSIZE ||
+	    !powerof2(boot->bpbBytesPerSec)) {
 		pfatal("Invalid sector size: %u", boot->bpbBytesPerSec);
 		return FSFATAL;
 	}
+
+	/* Sectors per cluster: can only be: 1, 2, 4, 8, 16, 32, 64, 128. */
+	boot->bpbSecPerClust = block[13];
+	if (boot->bpbSecPerClust == 0 || !powerof2(boot->bpbSecPerClust)) {
+		pfatal("Invalid cluster size: %u", boot->bpbSecPerClust);
+		return FSFATAL;
+	}
+
+	/* Reserved sectors: must be non-zero */
+	boot->bpbResSectors = block[14] + (block[15] << 8);
+	if (boot->bpbResSectors < 1) {
+		pfatal("Invalid reserved sectors: %u",
+		    boot->bpbResSectors);
+		return FSFATAL;
+	}
+
+	/* Number of FATs */
+	boot->bpbFATs = block[16];
 	if (boot->bpbFATs == 0) {
 		pfatal("Invalid number of FATs: %u", boot->bpbFATs);
 		return FSFATAL;
 	}
-	if (!boot->bpbRootDirEnts)
-		boot->flags |= FAT32;
-	if (boot->flags & FAT32) {
-		boot->FATsecs = block[36] + (block[37] << 8)
-				+ (block[38] << 16) + (block[39] << 24);
-		if (block[40] & 0x80)
-			boot->ValidFat = block[40] & 0x0f;
 
-		/* check version number: */
-		if (block[42] || block[43]) {
-			/* Correct?				XXX */
-			pfatal("Unknown file system version: %x.%x",
-			       block[43], block[42]);
+	/* Root directory entries for FAT12 and FAT16 */
+	boot->bpbRootDirEnts = block[17] + (block[18] << 8);
+	if (!boot->bpbRootDirEnts) {
+		/* bpbRootDirEnts = 0 suggests that we are FAT32 */
+		boot->flags |= FAT32;
+	}
+
+	/* Total sectors (16 bits) */
+	boot->bpbSectors = block[19] + (block[20] << 8);
+	if (boot->bpbSectors != 0 && (boot->flags & FAT32)) {
+		pfatal("Invalid 16-bit total sector count on FAT32: %u",
+		    boot->bpbSectors);
+		return FSFATAL;
+	}
+
+	/* Media type: ignored */
+	boot->bpbMedia = block[21];
+
+	/* FAT12/FAT16: 16-bit count of sectors per FAT */
+	boot->bpbFATsmall = block[22] + (block[23] << 8);
+	if (boot->bpbFATsmall != 0 && (boot->flags & FAT32)) {
+		pfatal("Invalid 16-bit FAT sector count on FAT32: %u",
+		    boot->bpbFATsmall);
+		return FSFATAL;
+	}
+
+	/* Legacy CHS geometry numbers: ignored */
+	boot->SecPerTrack = block[24] + (block[25] << 8);
+	boot->bpbHeads = block[26] + (block[27] << 8);
+
+	/* Hidden sectors: ignored */
+	boot->bpbHiddenSecs = block[28] + (block[29] << 8) +
+	    (block[30] << 16) + (block[31] << 24);
+
+	/* Total sectors (32 bits) */
+	boot->bpbHugeSectors = block[32] + (block[33] << 8) +
+	    (block[34] << 16) + (block[35] << 24);
+	if (boot->bpbHugeSectors == 0) {
+		if (boot->flags & FAT32) {
+			pfatal("FAT32 with sector count of zero");
+			return FSFATAL;
+		} else if (boot->bpbSectors == 0) {
+			pfatal("FAT with sector count of zero");
 			return FSFATAL;
 		}
-		boot->bpbRootClust = block[44] + (block[45] << 8)
-			       + (block[46] << 16) + (block[47] << 24);
-		boot->bpbFSInfo = block[48] + (block[49] << 8);
-		boot->bpbBackup = block[50] + (block[51] << 8);
+		boot->NumSectors = boot->bpbSectors;
+	} else {
+		if (boot->bpbSectors != 0) {
+			pfatal("Invalid FAT sector count");
+			return FSFATAL;
+		}
+		boot->NumSectors = boot->bpbHugeSectors;
+	}
 
+
+
+
+	if (boot->flags & FAT32) {
 		/* If the OEM Name field is EXFAT, it's not FAT32, so bail */
 		if (!memcmp(&block[3], "EXFAT   ", 8)) {
 			pfatal("exFAT filesystem is not supported.");
 			return FSFATAL;
 		}
 
-		/* check basic parameters */
-		if ((boot->bpbFSInfo == 0) || (boot->bpbSecPerClust == 0)) {
+		/* 32-bit count of sectors per FAT */
+		boot->FATsecs = block[36] + (block[37] << 8)
+				+ (block[38] << 16) + (block[39] << 24);
+
+		if (block[40] & 0x80)
+			boot->ValidFat = block[40] & 0x0f;
+
+		/* FAT32 version, bail out if not 0.0 */
+		if (block[42] || block[43]) {
+			pfatal("Unknown file system version: %x.%x",
+			       block[43], block[42]);
+			return FSFATAL;
+		}
+
+		/*
+		 * Cluster number of the first cluster of root directory.
+		 *
+		 * Should be 2 but do not require it.
+		 */
+		boot->bpbRootClust = block[44] + (block[45] << 8)
+			       + (block[46] << 16) + (block[47] << 24);
+
+		/* Sector number of the FSInfo structure, usually 1 */
+		boot->bpbFSInfo = block[48] + (block[49] << 8);
+
+		/* Sector number of the backup boot block, ignored */
+		boot->bpbBackup = block[50] + (block[51] << 8);
+
+		/* Check basic parameters */
+		if (boot->bpbFSInfo == 0) {
 			/*
 			 * Either the BIOS Parameter Block has been corrupted,
 			 * or this is not a FAT32 filesystem, most likely an
@@ -127,6 +200,8 @@
 			pfatal("Invalid FAT32 Extended BIOS Parameter Block");
 			return FSFATAL;
 		}
+
+		/* Read in and verify the FSInfo block */
 		if (lseek(dosfs, boot->bpbFSInfo * boot->bpbBytesPerSec,
 		    SEEK_SET) != boot->bpbFSInfo * boot->bpbBytesPerSec
 		    || read(dosfs, fsinfo, sizeof fsinfo) != sizeof fsinfo) {
@@ -164,8 +239,8 @@
 				ret = FSBOOTMOD;
 			} else
 				boot->bpbFSInfo = 0;
-		}
-		if (boot->bpbFSInfo) {
+		} else {
+			/* We appear to have a valid FSInfo block, decode */
 			boot->FSFree = fsinfo[0x1e8] + (fsinfo[0x1e9] << 8)
 				       + (fsinfo[0x1ea] << 16)
 				       + (fsinfo[0x1eb] << 24);
@@ -173,51 +248,29 @@
 				       + (fsinfo[0x1ee] << 16)
 				       + (fsinfo[0x1ef] << 24);
 		}
-
-		if (lseek(dosfs, boot->bpbBackup * boot->bpbBytesPerSec,
-		    SEEK_SET)
-		    != boot->bpbBackup * boot->bpbBytesPerSec
-		    || read(dosfs, backup, sizeof backup) != sizeof  backup) {
-			perr("could not read backup bootblock");
-			return FSFATAL;
-		}
-		backup[65] = block[65];				/* XXX */
-		if (memcmp(block + 11, backup + 11, 79)) {
-			/*
-			 * XXX We require a reference that explains
-			 * that these bytes need to match, or should
-			 * drop the check.  gdt@NetBSD has observed
-			 * filesystems that work fine under Windows XP
-			 * and NetBSD that do not match, so the
-			 * requirement is suspect.  For now, just
-			 * print out useful information and continue.
-			 */
-			pwarn("backup (block %d) mismatch with primary bootblock:\n",
-			        boot->bpbBackup);
-			for (i = 11; i < 11 + 90; i++) {
-				if (block[i] != backup[i])
-					pwarn("\ti=%d\tprimary 0x%02x\tbackup 0x%02x\n",
-					       i, block[i], backup[i]);
-			}
-		}
-		/* Check backup bpbFSInfo?					XXX */
+	} else {
+		/* !FAT32: FAT12/FAT16 */
+		boot->FATsecs = boot->bpbFATsmall;
 	}
 
-	if (boot->bpbSecPerClust == 0) {
-		pfatal("Invalid cluster size: %u", boot->bpbSecPerClust);
+	if (boot->FATsecs > UINT32_MAX / boot->bpbFATs) {
+		pfatal("Invalid FATs(%u) with FATsecs(%zu)",
+			boot->bpbFATs, (size_t)boot->FATsecs);
 		return FSFATAL;
 	}
-	if (boot->bpbSectors) {
-		boot->bpbHugeSectors = 0;
-		boot->NumSectors = boot->bpbSectors;
-	} else
-		boot->NumSectors = boot->bpbHugeSectors;
-	boot->ClusterOffset = (boot->bpbRootDirEnts * 32 +
+
+	boot->FirstCluster = (boot->bpbRootDirEnts * 32 +
 	    boot->bpbBytesPerSec - 1) / boot->bpbBytesPerSec +
-	    boot->bpbResSectors + boot->bpbFATs * boot->FATsecs -
-	    CLUST_FIRST * boot->bpbSecPerClust;
-	boot->NumClusters = (boot->NumSectors - boot->ClusterOffset) /
-	    boot->bpbSecPerClust;
+	    boot->bpbResSectors + boot->bpbFATs * boot->FATsecs;
+
+	if (boot->FirstCluster + boot->bpbSecPerClust > boot->NumSectors) {
+		pfatal("Cluster offset too large (%u clusters)\n",
+		    boot->FirstCluster);
+		return FSFATAL;
+	}
+
+	boot->NumClusters = (boot->NumSectors - boot->FirstCluster) / boot->bpbSecPerClust +
+	    CLUST_FIRST;
 
 	if (boot->flags&FAT32)
 		boot->ClustMask = CLUST32_MASK;
diff --git a/dir.c b/dir.c
index e56bbf8..7e459c7 100644
--- a/dir.c
+++ b/dir.c
@@ -169,20 +169,24 @@
 	char *cp, *np;
 	int nl;
 
-	cp = namebuf + sizeof namebuf - 1;
-	*cp = '\0';
-	do {
+	cp = namebuf + sizeof namebuf;
+	*--cp = '\0';
+
+	for(;;) {
 		np = dir->lname[0] ? dir->lname : dir->name;
 		nl = strlen(np);
-		if ((cp -= nl) <= namebuf + 1)
+		if (cp <= namebuf + 1 + nl) {
+			*--cp = '?';
 			break;
+		}
+		cp -= nl;
 		memcpy(cp, np, nl);
+		dir = dir->parent;
+		if (!dir)
+			break;
 		*--cp = '/';
-	} while ((dir = dir->parent) != NULL);
-	if (dir)
-		*--cp = '?';
-	else
-		cp++;
+	}
+
 	return cp;
 }
 
@@ -220,7 +224,6 @@
 resetDosDirSection(struct bootblock *boot, struct fatEntry *fat)
 {
 	int b1, b2;
-	cl_t cl;
 	int ret = FSOK;
 	size_t len;
 
@@ -253,24 +256,9 @@
 			       boot->bpbRootClust);
 			return FSFATAL;
 		}
-		cl = fat[boot->bpbRootClust].next;
-		if (cl < CLUST_FIRST
-		    || (cl >= CLUST_RSRVD && cl< CLUST_EOFS)
-		    || fat[boot->bpbRootClust].head != boot->bpbRootClust) {
-			if (cl == CLUST_FREE)
-				pwarn("Root directory starts with free cluster\n");
-			else if (cl >= CLUST_RSRVD)
-				pwarn("Root directory starts with cluster marked %s\n",
-				      rsrvdcltype(cl));
-			else {
-				pfatal("Root directory doesn't start a cluster chain");
-				return FSFATAL;
-			}
-			if (ask(1, "Fix")) {
-				fat[boot->bpbRootClust].next = CLUST_FREE;
-				ret = FSFATMOD;
-			} else
-				ret = FSFATAL;
+		if (fat[boot->bpbRootClust].head != boot->bpbRootClust) {
+			pfatal("Root directory doesn't start a cluster chain");
+			return FSFATAL;
 		}
 
 		fat[boot->bpbRootClust].flags |= FAT_USED;
@@ -329,7 +317,8 @@
 				break;
 			e = delbuf + endoff;
 		}
-		off = startcl * boot->bpbSecPerClust + boot->ClusterOffset;
+		off = (startcl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
+
 		off *= boot->bpbBytesPerSec;
 		if (lseek(f, off, SEEK_SET) != off) {
 			perr("Unable to lseek to %" PRId64, off);
@@ -469,7 +458,7 @@
 		off = boot->bpbResSectors + boot->bpbFATs *
 			boot->FATsecs;
 	} else {
-		off = cl * boot->bpbSecPerClust + boot->ClusterOffset;
+		off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
 	}
 
 	/*
@@ -550,7 +539,7 @@
 			    boot->FATsecs;
 		} else {
 			last = boot->bpbSecPerClust * boot->bpbBytesPerSec;
-			off = cl * boot->bpbSecPerClust + boot->ClusterOffset;
+			off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster;
 		}
 
 		off *= boot->bpbBytesPerSec;
@@ -1081,8 +1070,9 @@
 			lfcl = (lostDir->head < boot->NumClusters) ? lostDir->head : 0;
 			return FSERROR;
 		}
-		lfoff = lfcl * boot->ClusterSize
-		    + boot->ClusterOffset * boot->bpbBytesPerSec;
+		lfoff = (lfcl - CLUST_FIRST) * boot->ClusterSize
+		    + boot->FirstCluster * boot->bpbBytesPerSec;
+
 		if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
 		    || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
 			perr("could not read LOST.DIR");
diff --git a/dosfs.h b/dosfs.h
index 3d84ea0..9f1480f 100644
--- a/dosfs.h
+++ b/dosfs.h
@@ -74,7 +74,7 @@
 	u_int32_t NumSectors;		/* how many sectors are there */
 	u_int32_t FATsecs;		/* how many sectors are in FAT */
 	u_int32_t NumFatEntries;	/* how many entries really are there */
-	u_int	ClusterOffset;		/* at what sector would sector 0 start */
+	u_int	FirstCluster;		/* at what sector is Cluster CLUST_FIRST */
 	u_int	ClusterSize;		/* Cluster size in bytes */
 
 	/* Now some statistics: */
diff --git a/fat.c b/fat.c
index 97bfdab..7ca81ab 100644
--- a/fat.c
+++ b/fat.c
@@ -54,10 +54,10 @@
  * 31...... ........ ........ .......0
  * rrrr1111 11111111 11111111 mmmmmmmm         FAT32 entry 0
  * rrrrsh11 11111111 11111111 11111xxx         FAT32 entry 1
- * 
+ *
  *                   11111111 mmmmmmmm         FAT16 entry 0
  *                   sh111111 11111xxx         FAT16 entry 1
- * 
+ *
  * r = reserved
  * m = BPB media ID byte
  * s = clean flag (1 = dismounted; 0 = still mounted)
@@ -166,11 +166,11 @@
 _readfat(int fs, struct bootblock *boot, u_int no, u_char **buffer)
 {
 	off_t off;
-	size_t len;
 
-	*buffer = malloc(len = boot->FATsecs * boot->bpbBytesPerSec);
+	*buffer = calloc(boot->FATsecs, boot->bpbBytesPerSec);
 	if (*buffer == NULL) {
-		perr("No space for FAT sectors (%zu)", len);
+		perr("No space for FAT sectors (%zu)",
+		    (size_t)boot->FATsecs);
 		return 0;
 	}
 
@@ -205,20 +205,19 @@
 	u_char *buffer, *p;
 	cl_t cl;
 	int ret = FSOK;
-	size_t len;
 
 	boot->NumFree = boot->NumBad = 0;
 
 	if (!_readfat(fs, boot, no, &buffer))
 		return FSFATAL;
 
-	fat = malloc(len = boot->NumClusters * sizeof(struct fatEntry));
+	fat = calloc(boot->NumClusters, sizeof(struct fatEntry));
 	if (fat == NULL) {
-		perr("No space for FAT clusters (%zu)", len);
+		perr("No space for FAT clusters (%zu)",
+		    (size_t)boot->NumClusters);
 		free(buffer);
 		return FSFATAL;
 	}
-	(void)memset(fat, 0, len);
 
 	if (buffer[0] != boot->bpbMedia
 	    || buffer[1] != 0xff || buffer[2] != 0xff
@@ -519,7 +518,6 @@
 		}
 		if (head == fat[n].head) {
 			pwarn("Cluster chain starting at %u loops at cluster %u\n",
-
 			    head, p);
 			goto clear;
 		}
@@ -566,12 +564,13 @@
 	off_t off;
 	int ret = FSOK;
 
-	buffer = malloc(fatsz = boot->FATsecs * boot->bpbBytesPerSec);
+	fatsz = boot->FATsecs * boot->bpbBytesPerSec;
+	buffer = calloc(boot->FATsecs, boot->bpbBytesPerSec);
 	if (buffer == NULL) {
-		perr("No space for FAT sectors (%zu)", fatsz);
+		perr("No space for FAT sectors (%zu)",
+		    (size_t)boot->FATsecs);
 		return FSFATAL;
 	}
-	memset(buffer, 0, fatsz);
 	boot->NumFree = 0;
 	p = buffer;
 	if (correct_fat) {