[U-Boot] [RFC/WIP 2/2] fs/fat: introduce new dirent iterator

Rob Clark robdclark at gmail.com
Fri Aug 11 09:59:45 UTC 2017


Untangle the directory traversal.  Eventually if this replaced
do_fat_read_at() and get_dentfromdir(), it would remove a lot of code.

Only tested with a grand total of one fat fs img.  Although it actually
seems to fix some bugs with existing traversal (I noticed some files in
the root directory that where not showing up with "ls").

I don't particularly want to convert read/ls/etc over to using this
myself, since I don't really have enough example fat filesystems to
test with, and I can't even get all of the fs-test.sh tests running
properly.  But something along the lines of this is what I think a
sane fat.c (and probably fat_write.c?) should be using.

This opens up the possibility for a faster stateful readdir (ie.
fs_opendir()/fs_readdir()/fs_closedir(), which didn't have to re-
traverse the directory structure at each fs_readdir() step.  I'm
not sure if it would be weird for readdir() to be stateful, while
the rest of u-boot's fs APIs are not.
---
 fs/fat/fat.c | 274 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 274 insertions(+)

diff --git a/fs/fat/fat.c b/fs/fat/fat.c
index 68db36ebd7..2709147eaa 100644
--- a/fs/fat/fat.c
+++ b/fs/fat/fat.c
@@ -1298,6 +1298,273 @@ exit:
 	return ret;
 }
 
+///////////////////////////////////////////////////////////////////////////////
+
+/*
+ * Directory iterator, to simplify filesystem traversal
+ */
+
+typedef struct {
+	// TODO maybe it would simplify things if fat_itr was malloc'd and
+	// had ptr to it's parent?  if various fat_* API's re-used this it
+	// would perhaps give a way to have a common helper to find the
+	// parent directory iterator and then fat_itr_free() when done..
+	fsdata    *fsdata;
+	unsigned   cursect;
+	dir_entry *dent;
+	int        remaining;     /* remaining dent's in current cluster */
+	int        last_cluster;
+	int        is_root;
+
+	/* current iterator position values: */
+	char       l_name[VFAT_MAXLEN_BYTES];
+	char       s_name[14];
+	char      *name;          /* l_name if there is one, else s_name */
+
+	u8         block[MAX_CLUSTSIZE] __aligned(ARCH_DMA_MINALIGN);
+} fat_itr;
+
+static int fat_itr_isdir(fat_itr *itr);
+
+/* root directory iterator */
+static int fat_itr_root(fat_itr *itr, fsdata *fsdata)
+{
+	if (get_fs_info(fsdata))
+		return -1;
+
+	itr->fsdata = fsdata;
+	itr->cursect = fsdata->rootdir_sect;
+	itr->dent = NULL;
+	itr->remaining = 0;
+	itr->last_cluster = 0;
+	itr->is_root = 1;
+
+	return 0;
+}
+
+/* sub-directory iterator */
+static void fat_itr_child(fat_itr *itr, fat_itr *parent)
+{
+	fsdata *mydata = parent->fsdata;  // TODO ugg, get rid of stupid assumption of mydata in macros..
+	unsigned clustnum = START(parent->dent);
+
+	assert(fat_itr_isdir(parent));
+
+	itr->fsdata = parent->fsdata;
+	if (clustnum > 0) {
+		itr->cursect = itr->fsdata->data_begin +
+			(clustnum * itr->fsdata->clust_size);
+	} else {
+		itr->cursect = 0;
+	}
+	itr->dent = NULL;
+	itr->remaining = 0;
+	itr->last_cluster = 0;
+	itr->is_root = 0;
+}
+
+static void *next_cluster(fat_itr *itr)
+{
+	fsdata *mydata = itr->fsdata;  // TODO ugg, get rid of stupid assumption of mydata in macros..
+	int ret;
+
+	/* have we reached the end? */
+	if (itr->last_cluster)
+		return NULL;
+
+	debug("FAT read(sect=%d), clust_size=%d, DIRENTSPERBLOCK=%zd\n",
+	      itr->cursect, itr->fsdata->clust_size, DIRENTSPERBLOCK);
+
+	/* NOTE: do_fat_read_at() had complicated logic to deal w/
+	 * vfat names that span multiple clusters in the fat16 case,
+	 * which get_dentfromdir() probably also needed (and was
+	 * missing).  And not entirely sure what fat32 didn't have
+	 * the same issue..  We solve that by only caring about one
+	 * dent at a time and iteratively constructing the vfat long
+	 * name.
+	 */
+	ret = disk_read(itr->cursect, itr->fsdata->clust_size,
+			itr->block);
+	if (ret < 0) {
+		debug("Error: reading block\n");
+		return NULL;
+	}
+
+	// TODO maybe ((fatsize == 32) || !is_root) are same case?
+	if (itr->is_root) {
+		if (itr->fsdata->fatsize == 32) {
+			itr->cursect = get_fatent(itr->fsdata, itr->cursect);
+			if (CHECK_CLUST(itr->cursect, itr->fsdata->fatsize)) {
+				debug("cursect: 0x%x\n", itr->cursect);
+				itr->last_cluster = 1;
+			}
+		} else {
+			itr->cursect++;
+			if (itr->cursect - itr->fsdata->rootdir_sect >=
+			    itr->fsdata->rootdir_size) {
+				debug("cursect: 0x%x\n", itr->cursect);
+				itr->last_cluster = 1;
+			}
+		}
+
+	} else {
+		itr->cursect = get_fatent(itr->fsdata, itr->cursect);
+		if (CHECK_CLUST(itr->cursect, itr->fsdata->fatsize)) {
+			debug("cursect: 0x%x\n", itr->cursect);
+			itr->last_cluster = 1;
+		}
+	}
+
+	return itr->block;
+}
+
+static dir_entry *next_dent(fat_itr *itr)
+{
+	if (itr->remaining == 0) {
+		struct dir_entry *dent = next_cluster(itr);
+
+		/* have we reached the last cluster? */
+		if (!dent)
+			return NULL;
+
+		itr->remaining = itr->fsdata->sect_size / sizeof(dir_entry);
+		itr->dent = dent;
+	} else {
+		itr->remaining--;
+		itr->dent++;
+	}
+
+	/* have we reached the last valid entry? */
+	if (itr->dent->name[0] == 0)
+		return NULL;
+
+	return itr->dent;
+}
+
+// TODO use utf16_to_utf8???
+static dir_entry *extract_vfat_name(fat_itr *itr)
+{
+	struct dir_entry *dent = itr->dent;
+	int seqn = itr->dent->name[0] & ~LAST_LONG_ENTRY_MASK;
+	int n = 0;
+
+	while (seqn--) {
+		char buf[13+1];
+		int idx = 0;
+
+		slot2str((dir_slot *)dent, buf, &idx);
+
+		/* shift accumulated long-name up and copy new part in: */
+		memmove(itr->l_name + idx, itr->l_name, n);
+		memcpy(itr->l_name, buf, idx);
+		n += idx;
+
+		dent = next_dent(itr);
+		if (!dent)
+			return NULL;
+	}
+
+	itr->l_name[n] = '\0';
+
+	return dent;
+}
+
+// XXX bool?
+static int fat_itr_next(fat_itr *itr)
+{
+	dir_entry *dent;
+
+	itr->name = NULL;
+
+	while (1) {
+		dent = next_dent(itr);
+		if (!dent)
+			return 0;
+
+		if (dent->name[0] == DELETED_FLAG ||
+		    dent->name[0] == aRING)
+			continue;
+
+		if (dent->attr & ATTR_VOLUME) {
+			if (vfat_enabled &&
+			    (dent->attr & ATTR_VFAT) == ATTR_VFAT &&
+			    (dent->name[0] & LAST_LONG_ENTRY_MASK)) {
+				dent = extract_vfat_name(itr);
+				if (!dent)
+					continue;
+				itr->name = itr->l_name;
+				break;
+			} else {
+				/* Volume label or VFAT entry, skip */
+				continue;
+			}
+		}
+
+		break;
+	}
+
+	get_name(dent, itr->s_name);
+	if (!itr->name)
+		itr->name = itr->s_name;
+
+	return 1;
+}
+
+// XXX bool?
+static int fat_itr_isdir(fat_itr *itr)
+{
+	return !!(itr->dent->attr & ATTR_DIR);
+}
+
+
+/*
+ * Test Code:
+ */
+
+static void indent(int level)
+{
+	while (level--)
+		printf("\t");
+}
+
+static void traverse_directory(fat_itr *itr, int level)
+{
+	while (fat_itr_next(itr)) {
+		if (fat_itr_isdir(itr)) {
+			fat_itr dir;
+
+			indent(level + 1);
+			printf("%s/\n", itr->name);
+
+			/* don't traverse . and ..: */
+			if (!strcmp(itr->name, ".") ||
+			    !strcmp(itr->name, ".."))
+				continue;
+
+			fat_itr_child(&dir, itr);
+			traverse_directory(&dir, level + 1);
+		} else {
+			printf(" %8u", FAT2CPU32(itr->dent->size));
+			indent(level);
+			printf("%s\n", itr->name);
+		}
+	}
+}
+
+static void itr_test(void)
+{
+	fsdata fsdata;
+	fat_itr itr;
+
+	if (fat_itr_root(&itr, &fsdata))
+		return;
+
+	traverse_directory(&itr, 1);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+
 int do_fat_read(const char *filename, void *buffer, loff_t maxsize, int dols,
 		loff_t *actread)
 {
@@ -1422,6 +1689,13 @@ int fat_read_file(const char *filename, void *buf, loff_t offset, loff_t len,
 int fat_readdir(const char *filename, loff_t offset, struct fs_dirent *dent)
 {
 	loff_t actread;
+	if (1) {
+		printf("------------------------------\n");
+		printf("------------------------------\n");
+		itr_test();
+		printf("------------------------------\n");
+		printf("------------------------------\n");
+	}
 	return do_fat_read_at(filename, offset, dent, sizeof(*dent),
 			      LS_READDIR, 0, &actread);
 }
-- 
2.13.0



More information about the U-Boot mailing list