From d0b7f7926725da51328be5b125fbeab51fa77c2f Mon Sep 17 00:00:00 2001
From: swym <0xfd000000@gmail.com>
Date: Wed, 14 Aug 2019 22:32:34 +0200
Subject: [PATCH] feat(fatfs): Implement opendir, readdir & unlink

This commit adds 3 new API calls

- epic_file_opendir()
- epic_file_readdir()
- epic_file_unlink()

and 2 new functions in Pycardium

- os.listdir()
- os.unlink()
---
 epicardium/epicardium.h        |  70 +++++++++++---
 epicardium/fs/filesystem_fat.c | 171 ++++++++++++++++++++++++++-------
 epicardium/fs/internal.h       |   3 +
 epicardium/modules/fileops.c   |  43 +++++++++
 pycardium/modules/os.c         |  49 +++++++++-
 pycardium/modules/qstrdefs.h   |   2 +
 6 files changed, 285 insertions(+), 53 deletions(-)

diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index c680d559..2dd6b63f 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -60,6 +60,9 @@ typedef _Bool bool;
 #define API_FILE_SEEK              0x46
 #define API_FILE_TELL              0x47
 #define API_FILE_STAT              0x48
+#define API_FILE_OPENDIR           0x49
+#define API_FILE_READDIR           0x4a
+#define API_FILE_UNLINK            0x4b
 
 #define API_RTC_GET_SECONDS        0x50
 #define API_RTC_SCHEDULE_ALARM     0x51
@@ -805,13 +808,28 @@ API(
 	int epic_file_open(const char* filename, const char* modeString)
 );
 
-/** */
+/**
+ * epic_file_close
+ *
+ * :param int fd: descriptor returned by epic_file_opendir
+ *
+ * :return: ``0`` on success, negative on error
+ */
 API(API_FILE_CLOSE, int epic_file_close(int fd));
 
 /** */
 API(API_FILE_READ, int epic_file_read(int fd, void* buf, size_t nbytes));
 
-/** */
+/**
+ * epic_file_write
+ *
+ * :param int fd: descriptor returned by epic_file_open
+ * :param const void* buf: data to write
+ * :param size_t nbytes: no of bytes to write
+ *
+ * :return: ``< 0`` on error, ``nbytes`` on success. (Partial writes don't occur on success!)
+ *
+*/
 API(
 	API_FILE_WRITE,
 	int epic_file_write(int fd, const void* buf, size_t nbytes)
@@ -842,6 +860,9 @@ enum epic_stat_type {
 	EPICSTAT_DIR,
 };
 
+#define EPICSTAT_MAX_PATH 	255 //conveniently the same as FF_MAX_LFN
+
+
 /** */
 struct epic_stat {
 	/** Entity Type: file, directory or none */
@@ -860,21 +881,10 @@ struct epic_stat {
 	/** Size in bytes. */
 	uint32_t size;
 
-	/**
-	 * Which FAT volume this entity resides on.
-	 *
-	 * (will be needed later once we distinguish between system and user volume)
-	 */
-	uint8_t volume;
-	uint8_t _reserved[9];
+	char name[EPICSTAT_MAX_PATH + 1];
+	uint8_t _reserved[12];
 };
 
-#ifndef __cplusplus
-#if defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
-_Static_assert(sizeof(struct epic_stat) == 20, "");
-#endif
-#endif
-
 /**
  * stat path
  *
@@ -887,6 +897,36 @@ API(API_FILE_STAT, int epic_file_stat(
 	const char* path, struct epic_stat* stat
 ));
 
+/**
+ * open directory
+ *
+ * :param char* path: directory to open
+ *
+ * :return: ``> 0`` on success, negative on error
+ */
+API(API_FILE_OPENDIR, int epic_file_opendir(const char* path));
+
+/**
+ * readdir
+ *
+ * :param int fd: descriptor returned by epic_file_opendir
+ * :param epic_stat* stat: pointer to result - pass NULL to reset iteration offset of fd
+ *
+ * :return: ``0`` on success, negative on error
+ */
+API(API_FILE_READDIR, int epic_file_readdir(int fd, struct epic_stat* stat));
+
+/**
+ * epic_file_unlink
+ *
+ * :param char* path: file to delete
+ *
+ * :return: ``0`` on success, negative on error
+ */
+API(API_FILE_UNLINK, int epic_file_unlink(const char* path));
+
+
+
 /**
  * RTC
  * ===
diff --git a/epicardium/fs/filesystem_fat.c b/epicardium/fs/filesystem_fat.c
index 2ff00a67..05b7f4a0 100644
--- a/epicardium/fs/filesystem_fat.c
+++ b/epicardium/fs/filesystem_fat.c
@@ -65,6 +65,9 @@ static bool globalLockAccquire();
 static void globalLockRelease();
 static void efs_close_all(EpicFileSystem *fs);
 
+/**
+ * if EPICSTAT_NONE is passed to `expected`, the type is not checked.
+ */
 static bool efs_get_opened(
 	EpicFileSystem *fs,
 	int i,
@@ -73,6 +76,19 @@ static bool efs_get_opened(
 	int *rc
 );
 
+static bool
+efs_get_new(EpicFileSystem *fs, uint32_t *idx, struct FatObject **obj, int *rc);
+
+static int efs_obj_init(
+	EpicFileSystem *fs,
+	struct FatObject *obj,
+	uint32_t index,
+	enum epic_stat_type type
+);
+
+static void efs_obj_deinit(EpicFileSystem *fs, struct FatObject *obj);
+static void efs_init_stat(struct epic_stat *stat, FILINFO *finfo);
+
 static EpicFileSystem s_globalFileSystem;
 
 #if (EPIC_FAT_STATIC_SEMAPHORE == 1)
@@ -225,8 +241,10 @@ static bool efs_get_opened(
 	    generation >= EPIC_FAT_FD_MAX_GENERATION) {
 		return false;
 	}
-	if (fs->pool[index].type != expected ||
-	    fs->pool[index].generation != generation) {
+	if (fs->pool[index].generation != generation) {
+		return false;
+	}
+	if (expected != EPICSTAT_NONE && fs->pool[index].type != expected) {
 		return false;
 	}
 
@@ -235,6 +253,54 @@ static bool efs_get_opened(
 	return true;
 }
 
+static bool
+efs_get_new(EpicFileSystem *fs, uint32_t *idx, struct FatObject **obj, int *rc)
+{
+	uint32_t index;
+
+	*obj = NULL;
+	*rc  = 0;
+	*idx = 0;
+
+	//find free object to use
+	for (index = 0; index < EPIC_FAT_MAX_OPENED; ++index) {
+		if (fs->pool[index].type == EPICSTAT_NONE) {
+			break;
+		}
+	}
+	if (index == EPIC_FAT_MAX_OPENED) {
+		*rc = -s_libffToErrno[FR_TOO_MANY_OPEN_FILES];
+		return false;
+	}
+
+	*obj = &fs->pool[index];
+	return true;
+}
+
+static int efs_obj_init(
+	EpicFileSystem *fs,
+	struct FatObject *obj,
+	uint32_t index,
+	enum epic_stat_type type
+) {
+	uint32_t generation;
+
+	generation = fs->generationCount++;
+	if (generation == EPIC_FAT_FD_MAX_GENERATION) {
+		fs->generationCount = 1;
+	}
+	obj->type       = type;
+	obj->generation = generation;
+
+	return EPIC_FAT_FD(index, generation);
+}
+
+static void efs_obj_deinit(EpicFileSystem *fs, struct FatObject *obj)
+{
+	obj->type       = EPICSTAT_NONE;
+	obj->generation = 0;
+}
+
 /* here we're trying to mirror glibc's behaviour:
  * any combination of rwax parses but only the first of those flags wins:
  *    - rw, ra, rr all open read-only
@@ -282,58 +348,45 @@ static inline bool parse_mode(const char *mstring, int *mode)
 int efs_open(EpicFileSystem *fs, const char *filename, const char *modeString)
 {
 	struct FatObject *o = NULL;
-	uint32_t index, generation;
+	uint32_t index;
 	int mode = 0;
 	int res;
 
-	//find free object to use
-	for (index = 0; index < EPIC_FAT_MAX_OPENED; ++index) {
-		if (fs->pool[index].type == EPICSTAT_NONE) {
-			break;
-		}
-	}
-	if (index == EPIC_FAT_MAX_OPENED) {
-		return -s_libffToErrno[FR_TOO_MANY_OPEN_FILES];
-	}
-
-	generation = fs->generationCount++;
-	if (generation == EPIC_FAT_FD_MAX_GENERATION) {
-		fs->generationCount = 1;
-	}
-	o = &fs->pool[index];
-
 	if (!parse_mode(modeString, &mode)) {
 		return -EINVAL;
 	}
 
+	if (!efs_get_new(fs, &index, &o, &res)) {
+		return res;
+	}
+
 	res = f_open(&o->file, filename, mode);
 	if (res != FR_OK) {
 		return -s_libffToErrno[res];
 	}
 
-	o->type       = EPICSTAT_FILE;
-	o->generation = generation;
-
 	// for 'a' mode, we must begin at the end of the file
 	if ((mode & FA_OPEN_APPEND) != 0) {
 		f_lseek(&o->file, f_size(&o->file));
 	}
 
-	return EPIC_FAT_FD(index, generation);
+	return efs_obj_init(fs, o, index, EPICSTAT_FILE);
 }
 
 int efs_close(EpicFileSystem *fs, int fd)
 {
 	int res;
 	struct FatObject *o;
-	if (efs_get_opened(fs, fd, EPICSTAT_FILE, &o, &res)) {
-		res = f_close(&o->file);
+	if (efs_get_opened(fs, fd, EPICSTAT_NONE, &o, &res)) {
+		if (o->type == EPICSTAT_FILE) {
+			res = f_close(&o->file);
+		} else {
+			res = f_closedir(&o->dir);
+		}
 		if (res != FR_OK) {
 			return -s_libffToErrno[res];
 		}
-
-		o->type       = EPICSTAT_NONE;
-		o->generation = 0;
+		efs_obj_deinit(fs, o);
 	}
 	return res;
 }
@@ -346,13 +399,12 @@ void efs_close_all(EpicFileSystem *fs)
 			f_close(&fs->pool[i].file);
 			break;
 		case EPICSTAT_DIR:
-			//NYI
+			f_closedir(&fs->pool[i].dir);
 			break;
 		case EPICSTAT_NONE:
 			break;
 		}
-		fs->pool[i].type       = EPICSTAT_NONE;
-		fs->pool[i].generation = 0;
+		efs_obj_deinit(fs, &fs->pool[i]);
 	}
 }
 
@@ -440,18 +492,65 @@ int efs_tell(EpicFileSystem *fs, int fd)
 	return res;
 }
 
+static void efs_init_stat(struct epic_stat *stat, FILINFO *finfo)
+{
+	if (finfo->fname[0] != 0) {
+		if (finfo->fattrib & AM_DIR) {
+			stat->type = EPICSTAT_DIR;
+		} else {
+			stat->type = EPICSTAT_FILE;
+		}
+		strncpy(stat->name, finfo->fname, EPICSTAT_MAX_PATH);
+	} else {
+		stat->name[0] = 0;
+		stat->type    = EPICSTAT_NONE;
+	}
+}
+
 int efs_stat(EpicFileSystem *fs, const char *filename, struct epic_stat *stat)
 {
 	int res = 0;
-	FILINFO finfo;
+	static FILINFO finfo;
 	res = f_stat(filename, &finfo);
 	if (res == 0) {
-		if (finfo.fattrib & AM_DIR) {
-			stat->type = EPICSTAT_DIR;
-		} else {
-			stat->type = EPICSTAT_FILE;
+		efs_init_stat(stat, &finfo);
+	}
+	return -s_libffToErrno[res];
+}
+
+int efs_opendir(EpicFileSystem *fs, const char *path)
+{
+	int res;
+	struct FatObject *o;
+	uint32_t index;
+
+	if (efs_get_new(fs, &index, &o, &res)) {
+		res = f_opendir(&o->dir, path);
+		if (res != FR_OK) {
+			return -s_libffToErrno[res];
 		}
+		return efs_obj_init(fs, o, index, EPICSTAT_DIR);
 	}
+	return res;
+}
+
+int efs_readdir(EpicFileSystem *fs, int fd, struct epic_stat *stat)
+{
+	int res;
+	struct FatObject *o;
+	if (efs_get_opened(fs, fd, EPICSTAT_DIR, &o, &res)) {
+		FILINFO finfo;
+		res = f_readdir(&o->dir, stat ? &finfo : NULL);
+		if (res == FR_OK && stat) {
+			efs_init_stat(stat, &finfo);
+		}
+	}
+	return res;
+}
+
+int efs_unlink(EpicFileSystem *fs, const char *path)
+{
+	int res = f_unlink(path);
 	return -s_libffToErrno[res];
 }
 
diff --git a/epicardium/fs/internal.h b/epicardium/fs/internal.h
index b3f3acb3..97b878f0 100644
--- a/epicardium/fs/internal.h
+++ b/epicardium/fs/internal.h
@@ -26,6 +26,9 @@ int efs_flush(EpicFileSystem *fs, int fd);
 int efs_seek(EpicFileSystem *fs, int fd, long offset, int whence);
 int efs_tell(EpicFileSystem *fs, int fd);
 int efs_stat(EpicFileSystem *fs, const char *filename, struct epic_stat *stat);
+int efs_opendir(EpicFileSystem *fs, const char *path);
+int efs_readdir(EpicFileSystem *fs, int fd, struct epic_stat *stat);
+int efs_unlink(EpicFileSystem *fs, const char *path);
 /**
  * lock global filesystem
  * 
diff --git a/epicardium/modules/fileops.c b/epicardium/modules/fileops.c
index fc13417b..53dc7fd4 100644
--- a/epicardium/modules/fileops.c
+++ b/epicardium/modules/fileops.c
@@ -8,6 +8,16 @@
 
 #include "fs/internal.h"
 
+#if defined(__GNUC__) &&                                                       \
+	((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
+#define HAVE_STATIC_ASSERT 1
+#elif defined(__clang__)
+#define HAVE_STATIC_ASSERT 1
+#endif
+#if HAVE_STATIC_ASSERT
+_Static_assert(sizeof(struct epic_stat) == 276, "");
+#endif
+
 int epic_file_open(const char *filename, const char *mode)
 {
 	EpicFileSystem *fs;
@@ -95,3 +105,36 @@ int epic_file_stat(const char *filename, struct epic_stat *stat)
 	}
 	return res;
 }
+
+int epic_file_opendir(const char *path)
+{
+	EpicFileSystem *fs;
+	int res = efs_lock_global(&fs);
+	if (res == 0) {
+		res = efs_opendir(fs, path);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
+
+int epic_file_readdir(int fd, struct epic_stat *stat)
+{
+	EpicFileSystem *fs;
+	int res = efs_lock_global(&fs);
+	if (res == 0) {
+		res = efs_readdir(fs, fd, stat);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
+
+int epic_file_unlink(const char *path)
+{
+	EpicFileSystem *fs;
+	int res = efs_lock_global(&fs);
+	if (res == 0) {
+		res = efs_unlink(fs, path);
+		efs_unlock_global(fs);
+	}
+	return res;
+}
diff --git a/pycardium/modules/os.c b/pycardium/modules/os.c
index 3f84c26e..e5c625ef 100644
--- a/pycardium/modules/os.c
+++ b/pycardium/modules/os.c
@@ -48,13 +48,58 @@ static mp_obj_t mp_os_exec(mp_obj_t name_in)
 }
 static MP_DEFINE_CONST_FUN_OBJ_1(exec_obj, mp_os_exec);
 
-static const mp_rom_map_elem_t os_module_gobals_table[] = {
+static mp_obj_t mp_os_listdir(mp_obj_t py_path)
+{
+	const char *path = mp_obj_str_get_str(py_path);
+	int fd           = epic_file_opendir(path);
+
+	if (fd < 0) {
+		mp_raise_OSError(-fd);
+	}
+	struct epic_stat entry;
+	mp_obj_list_t *list = mp_obj_new_list(0, NULL);
+	for (;;) {
+		int res = epic_file_readdir(fd, &entry);
+		if (res < 0) {
+			m_del_obj(mp_obj_list_t, list);
+			epic_file_close(fd);
+			mp_raise_OSError(-res);
+		}
+		if (entry.type == EPICSTAT_NONE) {
+			break;
+		}
+		mp_obj_list_append(
+			list, mp_obj_new_str(entry.name, strlen(entry.name))
+		);
+	}
+	epic_file_close(fd);
+	return MP_OBJ_FROM_PTR(list);
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(listdir_obj, mp_os_listdir);
+
+static mp_obj_t mp_os_unlink(mp_obj_t py_path)
+{
+	const char *path = mp_obj_str_get_str(py_path);
+	int rc           = epic_file_unlink(path);
+
+	if (rc < 0) {
+		mp_raise_OSError(-rc);
+	}
+	return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(unlink_obj, mp_os_unlink);
+
+static const mp_rom_map_elem_t os_module_globals_table[] = {
 	{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_os) },
 	{ MP_ROM_QSTR(MP_QSTR_exit), MP_ROM_PTR(&exit_obj) },
 	{ MP_ROM_QSTR(MP_QSTR_exec), MP_ROM_PTR(&exec_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_listdir), MP_ROM_PTR(&listdir_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_unlink), MP_ROM_PTR(&unlink_obj) },
 };
-static MP_DEFINE_CONST_DICT(os_module_globals, os_module_gobals_table);
 
+static MP_DEFINE_CONST_DICT(os_module_globals, os_module_globals_table);
+
+// Define module object.
 const mp_obj_module_t os_module = {
 	.base    = { &mp_type_module },
 	.globals = (mp_obj_dict_t *)&os_module_globals,
diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h
index f25307d5..2da8ea8e 100644
--- a/pycardium/modules/qstrdefs.h
+++ b/pycardium/modules/qstrdefs.h
@@ -89,3 +89,5 @@ Q(write)
 Q(os)
 Q(exit)
 Q(exec)
+Q(listdir)
+Q(unlink)
-- 
GitLab