Index: uspace/srv/fs/fat/fat_fat.c
===================================================================
--- uspace/srv/fs/fat/fat_fat.c	(revision ed90317444652a29e89501a128d66020aaea52b5)
+++ uspace/srv/fs/fat/fat_fat.c	(revision dba4a23c182853c642247ff5f9ce35437d1d1691)
@@ -49,4 +49,15 @@
 #include <mem.h>
 
+/*
+ * Convenience macros for computing some frequently used values from the
+ * primitive boot sector members.
+ */
+#define RDS(bs)		((sizeof(fat_dentry_t) * RDE((bs))) / BPS((bs))) + \
+			(((sizeof(fat_dentry_t) * RDE((bs))) % BPS((bs))) != 0)
+#define SSA(bs)		(RSCNT((bs)) + FATCNT((bs)) * SF((bs)) + RDS(bs))
+
+#define CLBN2PBN(bs, cl, bn) \
+	(SSA((bs)) + ((cl) - FAT_CLST_FIRST) * SPC((bs)) + (bn) % SPC((bs)))
+
 /**
  * The fat_alloc_lock mutex protects all copies of the File Allocation Table
@@ -74,12 +85,7 @@
 {
 	block_t *b;
-	unsigned bps;
-	unsigned rscnt;		/* block address of the first FAT */
 	uint16_t clusters = 0;
 	fat_cluster_t clst = firstc;
 	int rc;
-
-	bps = uint16_t_le2host(bs->bps);
-	rscnt = uint16_t_le2host(bs->rscnt);
 
 	if (firstc == FAT_CLST_RES0) {
@@ -99,8 +105,9 @@
 		if (lastc)
 			*lastc = clst;	/* remember the last cluster number */
-		fsec = (clst * sizeof(fat_cluster_t)) / bps;
-		fidx = clst % (bps / sizeof(fat_cluster_t));
+		fsec = (clst * sizeof(fat_cluster_t)) / BPS(bs);
+		fidx = clst % (BPS(bs) / sizeof(fat_cluster_t));
 		/* read FAT1 */
-		rc = block_get(&b, dev_handle, rscnt + fsec, BLOCK_FLAGS_NONE);
+		rc = block_get(&b, dev_handle, RSCNT(bs) + fsec,
+		    BLOCK_FLAGS_NONE);
 		if (rc != EOK)
 			return rc;
@@ -125,7 +132,5 @@
  * @param block		Pointer to a block pointer for storing result.
  * @param bs		Buffer holding the boot sector of the file system.
- * @param dev_handle	Device handle of the file system.
- * @param firstc	First cluster used by the file. Can be zero if the file
- *			is empty.
+ * @param nodep		FAT node.
  * @param bn		Block number.
  * @param flags		Flags passed to libblock.
@@ -134,16 +139,75 @@
  */
 int
+fat_block_get(block_t **block, struct fat_bs *bs, fat_node_t *nodep,
+    aoff64_t bn, int flags)
+{
+	fat_cluster_t firstc = nodep->firstc;
+	fat_cluster_t currc;
+	aoff64_t relbn = bn;
+	int rc;
+
+	if (!nodep->size)
+		return ELIMIT;
+
+	if (nodep->firstc == FAT_CLST_ROOT) 
+		goto fall_through;
+
+	if (((((nodep->size - 1) / BPS(bs)) / SPC(bs)) == bn / SPC(bs)) &&
+	    nodep->lastc_cached_valid) {
+	    	/*
+		 * This is a request to read a block within the last cluster
+		 * when fortunately we have the last cluster number cached.
+		 */
+		return block_get(block, nodep->idx->dev_handle,
+		    CLBN2PBN(bs, nodep->lastc_cached_value, bn), flags);
+	}
+
+	if (nodep->currc_cached_valid && bn >= nodep->currc_cached_bn) {
+		/*
+		 * We can start with the cluster cached by the previous call to
+		 * fat_block_get().
+		 */
+		firstc = nodep->currc_cached_value;
+		relbn -= (nodep->currc_cached_bn / SPC(bs)) * SPC(bs);
+	}
+
+fall_through:
+	rc = _fat_block_get(block, bs, nodep->idx->dev_handle, firstc,
+	    &currc, relbn, flags);
+	if (rc != EOK)
+		return rc;
+	
+	/*
+	 * Update the "current" cluster cache.
+	 */
+	nodep->currc_cached_valid = true;
+	nodep->currc_cached_bn = bn;
+	nodep->currc_cached_value = currc;
+
+	return rc;
+}
+
+/** Read block from file located on a FAT file system.
+ *
+ * @param block		Pointer to a block pointer for storing result.
+ * @param bs		Buffer holding the boot sector of the file system.
+ * @param dev_handle	Device handle of the file system.
+ * @param fcl		First cluster used by the file. Can be zero if the file
+ *			is empty.
+ * @param clp		If not NULL, address where the cluster containing bn
+ *			will be stored.
+ *			stored 
+ * @param bn		Block number.
+ * @param flags		Flags passed to libblock.
+ *
+ * @return		EOK on success or a negative error code.
+ */
+int
 _fat_block_get(block_t **block, fat_bs_t *bs, dev_handle_t dev_handle,
-    fat_cluster_t firstc, aoff64_t bn, int flags)
-{
-	unsigned bps;
-	unsigned rscnt;		/* block address of the first FAT */
-	unsigned rde;
-	unsigned rds;		/* root directory size */
-	unsigned sf;
-	unsigned ssa;		/* size of the system area */
+    fat_cluster_t fcl, fat_cluster_t *clp, aoff64_t bn, int flags)
+{
 	uint16_t clusters;
 	unsigned max_clusters;
-	fat_cluster_t lastc;
+	fat_cluster_t c;
 	int rc;
 
@@ -151,33 +215,25 @@
 	 * This function can only operate on non-zero length files.
 	 */
-	if (firstc == FAT_CLST_RES0)
+	if (fcl == FAT_CLST_RES0)
 		return ELIMIT;
 
-	bps = uint16_t_le2host(bs->bps);
-	rscnt = uint16_t_le2host(bs->rscnt);
-	rde = uint16_t_le2host(bs->root_ent_max);
-	sf = uint16_t_le2host(bs->sec_per_fat);
-
-	rds = (sizeof(fat_dentry_t) * rde) / bps;
-	rds += ((sizeof(fat_dentry_t) * rde) % bps != 0);
-	ssa = rscnt + bs->fatcnt * sf + rds;
-
-	if (firstc == FAT_CLST_ROOT) {
+	if (fcl == FAT_CLST_ROOT) {
 		/* root directory special case */
-		assert(bn < rds);
-		rc = block_get(block, dev_handle, rscnt + bs->fatcnt * sf + bn,
-		    flags);
+		assert(bn < RDS(bs));
+		rc = block_get(block, dev_handle,
+		    RSCNT(bs) + FATCNT(bs) * SF(bs) + bn, flags);
 		return rc;
 	}
 
-	max_clusters = bn / bs->spc;
-	rc = fat_cluster_walk(bs, dev_handle, firstc, &lastc, &clusters,
-	    max_clusters);
+	max_clusters = bn / SPC(bs);
+	rc = fat_cluster_walk(bs, dev_handle, fcl, &c, &clusters, max_clusters);
 	if (rc != EOK)
 		return rc;
 	assert(clusters == max_clusters);
 
-	rc = block_get(block, dev_handle,
-	    ssa + (lastc - FAT_CLST_FIRST) * bs->spc + bn % bs->spc, flags);
+	rc = block_get(block, dev_handle, CLBN2PBN(bs, c, bn), flags);
+
+	if (clp)
+		*clp = c;
 
 	return rc;
@@ -198,24 +254,19 @@
 int fat_fill_gap(fat_bs_t *bs, fat_node_t *nodep, fat_cluster_t mcl, aoff64_t pos)
 {
-	uint16_t bps;
-	unsigned spc;
 	block_t *b;
 	aoff64_t o, boundary;
 	int rc;
 
-	bps = uint16_t_le2host(bs->bps);
-	spc = bs->spc;
-	
-	boundary = ROUND_UP(nodep->size, bps * spc);
+	boundary = ROUND_UP(nodep->size, BPS(bs) * SPC(bs));
 
 	/* zero out already allocated space */
 	for (o = nodep->size; o < pos && o < boundary;
-	    o = ALIGN_DOWN(o + bps, bps)) {
-	    	int flags = (o % bps == 0) ?
+	    o = ALIGN_DOWN(o + BPS(bs), BPS(bs))) {
+	    	int flags = (o % BPS(bs) == 0) ?
 		    BLOCK_FLAGS_NOREAD : BLOCK_FLAGS_NONE;
-		rc = fat_block_get(&b, bs, nodep, o / bps, flags);
-		if (rc != EOK)
-			return rc;
-		memset(b->data + o % bps, 0, bps - o % bps);
+		rc = fat_block_get(&b, bs, nodep, o / BPS(bs), flags);
+		if (rc != EOK)
+			return rc;
+		memset(b->data + o % BPS(bs), 0, BPS(bs) - o % BPS(bs));
 		b->dirty = true;		/* need to sync node */
 		rc = block_put(b);
@@ -228,10 +279,10 @@
 	
 	/* zero out the initial part of the new cluster chain */
-	for (o = boundary; o < pos; o += bps) {
+	for (o = boundary; o < pos; o += BPS(bs)) {
 		rc = _fat_block_get(&b, bs, nodep->idx->dev_handle, mcl,
-		    (o - boundary) / bps, BLOCK_FLAGS_NOREAD);
-		if (rc != EOK)
-			return rc;
-		memset(b->data, 0, min(bps, pos - o));
+		    NULL, (o - boundary) / BPS(bs), BLOCK_FLAGS_NOREAD);
+		if (rc != EOK)
+			return rc;
+		memset(b->data, 0, min(BPS(bs), pos - o));
 		b->dirty = true;		/* need to sync node */
 		rc = block_put(b);
@@ -257,19 +308,13 @@
 {
 	block_t *b;
-	uint16_t bps;
-	uint16_t rscnt;
-	uint16_t sf;
 	fat_cluster_t *cp;
 	int rc;
 
-	bps = uint16_t_le2host(bs->bps);
-	rscnt = uint16_t_le2host(bs->rscnt);
-	sf = uint16_t_le2host(bs->sec_per_fat);
-
-	rc = block_get(&b, dev_handle, rscnt + sf * fatno +
-	    (clst * sizeof(fat_cluster_t)) / bps, BLOCK_FLAGS_NONE);
+	rc = block_get(&b, dev_handle, RSCNT(bs) + SF(bs) * fatno +
+	    (clst * sizeof(fat_cluster_t)) / BPS(bs), BLOCK_FLAGS_NONE);
 	if (rc != EOK)
 		return rc;
-	cp = (fat_cluster_t *)b->data + clst % (bps / sizeof(fat_cluster_t));
+	cp = (fat_cluster_t *)b->data +
+	    clst % (BPS(bs) / sizeof(fat_cluster_t));
 	*value = uint16_t_le2host(*cp);
 	rc = block_put(b);
@@ -293,20 +338,14 @@
 {
 	block_t *b;
-	uint16_t bps;
-	uint16_t rscnt;
-	uint16_t sf;
 	fat_cluster_t *cp;
 	int rc;
 
-	bps = uint16_t_le2host(bs->bps);
-	rscnt = uint16_t_le2host(bs->rscnt);
-	sf = uint16_t_le2host(bs->sec_per_fat);
-
-	assert(fatno < bs->fatcnt);
-	rc = block_get(&b, dev_handle, rscnt + sf * fatno +
-	    (clst * sizeof(fat_cluster_t)) / bps, BLOCK_FLAGS_NONE);
+	assert(fatno < FATCNT(bs));
+	rc = block_get(&b, dev_handle, RSCNT(bs) + SF(bs) * fatno +
+	    (clst * sizeof(fat_cluster_t)) / BPS(bs), BLOCK_FLAGS_NONE);
 	if (rc != EOK)
 		return rc;
-	cp = (fat_cluster_t *)b->data + clst % (bps / sizeof(fat_cluster_t));
+	cp = (fat_cluster_t *)b->data +
+	    clst % (BPS(bs) / sizeof(fat_cluster_t));
 	*cp = host2uint16_t_le(value);
 	b->dirty = true;		/* need to sync block */
@@ -364,11 +403,4 @@
     fat_cluster_t *mcl, fat_cluster_t *lcl)
 {
-	uint16_t bps;
-	uint16_t rscnt;
-	uint16_t sf;
-	uint32_t ts;
-	unsigned rde;
-	unsigned rds;
-	unsigned ssa;
 	block_t *blk;
 	fat_cluster_t *lifo;	/* stack for storing free cluster numbers */ 
@@ -380,16 +412,4 @@
 	if (!lifo)
 		return ENOMEM;
-	
-	bps = uint16_t_le2host(bs->bps);
-	rscnt = uint16_t_le2host(bs->rscnt);
-	sf = uint16_t_le2host(bs->sec_per_fat);
-	rde = uint16_t_le2host(bs->root_ent_max);
-	ts = (uint32_t) uint16_t_le2host(bs->totsec16);
-	if (ts == 0)
-		ts = uint32_t_le2host(bs->totsec32);
-
-	rds = (sizeof(fat_dentry_t) * rde) / bps;
-	rds += ((sizeof(fat_dentry_t) * rde) % bps != 0);
-	ssa = rscnt + bs->fatcnt * sf + rds;
 	
 	/*
@@ -397,9 +417,10 @@
 	 */
 	fibril_mutex_lock(&fat_alloc_lock);
-	for (b = 0, cl = 0; b < sf; b++) {
-		rc = block_get(&blk, dev_handle, rscnt + b, BLOCK_FLAGS_NONE);
+	for (b = 0, cl = 0; b < SF(bs); b++) {
+		rc = block_get(&blk, dev_handle, RSCNT(bs) + b,
+		    BLOCK_FLAGS_NONE);
 		if (rc != EOK)
 			goto error;
-		for (c = 0; c < bps / sizeof(fat_cluster_t); c++, cl++) {
+		for (c = 0; c < BPS(bs) / sizeof(fat_cluster_t); c++, cl++) {
 			/*
 			 * Check if the cluster is physically there. This check
@@ -408,5 +429,6 @@
 			 * from the size of the file allocation table.
 			 */
-			if ((cl >= 2) && ((cl - 2) * bs->spc + ssa >= ts)) {
+			if ((cl >= 2) &&
+			    ((cl - 2) * SPC(bs) + SSA(bs) >= TS(bs))) {
 				rc = block_put(blk);
 				if (rc != EOK)
@@ -511,33 +533,44 @@
  * @param nodep		Node representing the file.
  * @param mcl		First cluster of the cluster chain to append.
+ * @param lcl		Last cluster of the cluster chain to append.
  *
  * @return		EOK on success or a negative error code.
  */
-int fat_append_clusters(fat_bs_t *bs, fat_node_t *nodep, fat_cluster_t mcl)
+int
+fat_append_clusters(fat_bs_t *bs, fat_node_t *nodep, fat_cluster_t mcl,
+    fat_cluster_t lcl)
 {
 	dev_handle_t dev_handle = nodep->idx->dev_handle;
-	fat_cluster_t lcl;
+	fat_cluster_t lastc;
 	uint16_t numc;
 	uint8_t fatno;
 	int rc;
 
-	rc = fat_cluster_walk(bs, dev_handle, nodep->firstc, &lcl, &numc,
-	    (uint16_t) -1);
-	if (rc != EOK)
-		return rc;
-
-	if (numc == 0) {
-		/* No clusters allocated to the node yet. */
-		nodep->firstc = mcl;
-		nodep->dirty = true;		/* need to sync node */
-		return EOK;
+	if (nodep->lastc_cached_valid) {
+		lastc = nodep->lastc_cached_value;
+		nodep->lastc_cached_valid = false;
+	} else {
+		rc = fat_cluster_walk(bs, dev_handle, nodep->firstc, &lastc,
+		    &numc, (uint16_t) -1);
+		if (rc != EOK)
+			return rc;
+
+		if (numc == 0) {
+			/* No clusters allocated to the node yet. */
+			nodep->firstc = mcl;
+			nodep->dirty = true;	/* need to sync node */
+			return EOK;
+		}
 	}
 
 	for (fatno = FAT1; fatno < bs->fatcnt; fatno++) {
-		rc = fat_set_cluster(bs, nodep->idx->dev_handle, fatno, lcl,
+		rc = fat_set_cluster(bs, nodep->idx->dev_handle, fatno, lastc,
 		    mcl);
 		if (rc != EOK)
 			return rc;
 	}
+
+	nodep->lastc_cached_valid = true;
+	nodep->lastc_cached_value = lcl;
 
 	return EOK;
@@ -548,5 +581,5 @@
  * @param bs		Buffer holding the boot sector of the file system.
  * @param nodep		FAT node where the chopping will take place.
- * @param lastc		Last cluster which will remain in the node. If this
+ * @param lcl		Last cluster which will remain in the node. If this
  *			argument is FAT_CLST_RES0, then all clusters will
  *			be chopped off.
@@ -554,10 +587,17 @@
  * @return		EOK on success or a negative return code.
  */
-int fat_chop_clusters(fat_bs_t *bs, fat_node_t *nodep, fat_cluster_t lastc)
-{
-	int rc;
-
+int fat_chop_clusters(fat_bs_t *bs, fat_node_t *nodep, fat_cluster_t lcl)
+{
+	int rc;
 	dev_handle_t dev_handle = nodep->idx->dev_handle;
-	if (lastc == FAT_CLST_RES0) {
+
+	/*
+	 * Invalidate cached cluster numbers.
+	 */
+	nodep->lastc_cached_valid = false;
+	if (nodep->currc_cached_value != lcl)
+		nodep->currc_cached_valid = false;
+
+	if (lcl == FAT_CLST_RES0) {
 		/* The node will have zero size and no clusters allocated. */
 		rc = fat_free_clusters(bs, dev_handle, nodep->firstc);
@@ -570,5 +610,5 @@
 		unsigned fatno;
 
-		rc = fat_get_cluster(bs, dev_handle, FAT1, lastc, &nextc);
+		rc = fat_get_cluster(bs, dev_handle, FAT1, lcl, &nextc);
 		if (rc != EOK)
 			return rc;
@@ -576,5 +616,5 @@
 		/* Terminate the cluster chain in all copies of FAT. */
 		for (fatno = FAT1; fatno < bs->fatcnt; fatno++) {
-			rc = fat_set_cluster(bs, dev_handle, fatno, lastc,
+			rc = fat_set_cluster(bs, dev_handle, fatno, lcl,
 			    FAT_CLST_LAST1);
 			if (rc != EOK)
@@ -588,4 +628,10 @@
 	}
 
+	/*
+	 * Update and re-enable the last cluster cache.
+	 */
+	nodep->lastc_cached_valid = true;
+	nodep->lastc_cached_value = lcl;
+
 	return EOK;
 }
@@ -596,15 +642,12 @@
 	int i;
 	block_t *b;
-	unsigned bps;
-	int rc;
-
-	bps = uint16_t_le2host(bs->bps);
-	
-	for (i = 0; i < bs->spc; i++) {
-		rc = _fat_block_get(&b, bs, dev_handle, c, i,
+	int rc;
+
+	for (i = 0; i < SPC(bs); i++) {
+		rc = _fat_block_get(&b, bs, dev_handle, c, NULL, i,
 		    BLOCK_FLAGS_NOREAD);
 		if (rc != EOK)
 			return rc;
-		memset(b->data, 0, bps);
+		memset(b->data, 0, BPS(bs));
 		b->dirty = true;
 		rc = block_put(b);
