From 1dd7d6191e762addcbf09b297cf860cf18a05591 Mon Sep 17 00:00:00 2001 From: Mathieu Maret Date: Thu, 23 May 2024 00:21:41 +0200 Subject: [PATCH] VFS: design for the name cache system --- core/fs.c | 13 +++ core/fs.h | 7 ++ core/fsEntry.c | 215 +++++++++++++++++++++++++++++++++++++++++++++++++ core/fsEntry.h | 183 +++++++++++++++++++++++++++++++++++++++++ core/klibc.c | 2 +- core/klibc.h | 2 +- core/main.c | 4 + tests/test.c | 190 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 614 insertions(+), 2 deletions(-) create mode 100644 core/fs.c create mode 100644 core/fs.h create mode 100644 core/fsEntry.c create mode 100644 core/fsEntry.h diff --git a/core/fs.c b/core/fs.c new file mode 100644 index 0000000..290b8c5 --- /dev/null +++ b/core/fs.c @@ -0,0 +1,13 @@ +#include "fs.h" + +int fsInodeUnref(struct inode *inode) +{ + (void)inode; + return 0; +}; + +int fsInodeRef(struct inode *inode) +{ + (void)inode; + return 0; +}; diff --git a/core/fs.h b/core/fs.h new file mode 100644 index 0000000..aa70ed1 --- /dev/null +++ b/core/fs.h @@ -0,0 +1,7 @@ +#pragma once + + +struct inode {//TODO + }; +int fsInodeUnref(struct inode *inode); +int fsInodeRef(struct inode *inode); diff --git a/core/fsEntry.c b/core/fsEntry.c new file mode 100644 index 0000000..957dbf9 --- /dev/null +++ b/core/fsEntry.c @@ -0,0 +1,215 @@ +#include "alloc.h" +#include "assert.h" +#include "fsEntry.h" +#include "errno.h" +#include "list.h" +#include "mem.h" + +bool_t pstringSplipPath(struct pstr *path, struct pstr *upper, struct pstr *lower) +{ + typeof(path->name) curPos = path->name; + typeof(path->len) curLen = path->len; + typeof(path->len) upperLen = 0; + + // Skip leading / + while (curLen > 0 && curPos[0] == '/') { + curPos++; + curLen--; + } + + upper->name = curPos; + + // Find first non / + while (curLen > 0 && curPos[0] != '/') { + upperLen++; + curLen--; + curPos++; + } + upper->len = upperLen; + + while (curLen > 0 && curPos[0] == '/') { + curPos++; + curLen--; + } + lower->name = curPos; + lower->len = path->len - (lower->name - path->name); + + return lower->len > 0; +} + +bool_t pstringIsEq(const struct pstr *p1, const struct pstr *p2) +{ + return p1->len == p2->len && (memcmp(p1->name, p2->name, p1->len) == 0); +} + +bool_t pstringIsStr(const struct pstr *p, char *s) +{ + size_t slen = strlen(s); + return p->len == slen && (memcmp(p->name, s, slen) == 0); +} + +int fsEntryRef(struct fs_entry *entry) +{ + entry->ref++; + return 0; +} + +int fsEntryUnref(struct fs_entry **entry) +{ + struct fs_entry *toDelete = NULL, *entryTmp; + + entryTmp = *entry; + + while (entryTmp) { + assert(entryTmp->ref > 0); + entryTmp->ref--; + + if (entryTmp->ref > 0) + break; + + if (entryTmp == *entry) + *entry = NULL; + + if (entryTmp->parent) { + struct fs_entry *parent = entryTmp->parent; + + assert(entryTmp->parent->ref >= 1); + + list_delete_named(parent->childs, entryTmp, prevSiblings, nextSiblings); + // At next iteration we will decrement parent's ref cnt + } + + // Avoid recursivity in kernel by using a list of entry to suppress + list_add_tail_named(toDelete, entryTmp, prevSiblings, nextSiblings); + + // now, unref parent + entryTmp = entryTmp->parent; + } + + // free 0 ref entries + while (!list_is_empty_named(toDelete, prevSiblings, nextSiblings)) { + entryTmp = list_pop_head_named(toDelete, prevSiblings, nextSiblings); + fsInodeUnref(entryTmp->node); + free(entryTmp); + } + + return 0; +} + +struct fs_entry *fsEntryAlloc(struct fs_entry *parent, const struct pstr *name, struct inode *inode) +{ + struct fs_entry *entry = zalloc(sizeof(struct fs_entry)); + + if (!entry) + return NULL; + + if (name && name->len > 0) { + char *allocName = malloc(name->len); + + if (!allocName) { + free(entry); + return NULL; + } + + memcpy(allocName, name->name, name->len); + entry->name.len = name->len; + entry->name.name = allocName; + } + + entry->ref = 1; + entry->node = inode; + entry->parent = parent; + + fsInodeRef(inode); + + if (parent) { + fsEntryRef(parent); + list_add_head_named(parent->childs, entry, prevSiblings, nextSiblings); + } + + return entry; +} + +int fsEntryLookup(struct fs_entry *entry, const struct pstr *name, const struct fs_entry *root, struct fs_entry **result) +{ + if (pstringIsStr(name, ".")) { + *result = entry; + } else if (pstringIsStr(name, "..")) { + if (entry == root) { + *result = entry; + } else { + // If this entry is mounted, get the mountpoint + for (; entry->mountpoint; entry = entry->mountpoint) { + // No-op + } + + assert(NULL != entry->parent); + *result = entry->parent; + } + fsEntryRef(*result); + return 0; + } else { + struct fs_entry *child; + int childIdx; + list_foreach_forward_named(entry->childs, child, childIdx, prevSiblings, nextSiblings) + { + if (pstringIsEq(&child->name, name)) { + break; + } + } + + if (!list_foreach_early_break(entry->childs, child, childIdx)) + return -ENOENT; + + *result = child; + } + + // Follow the mount chain + for (; (*result)->mountedFs; *result = (*result)->mountedFs) + ; + + fsEntryRef(*result); + + return 0; +} + +int fsEntrySetup(void) +{ + return allocBookSlab(sizeof(struct fs_entry), PAGE_SIZE, 0, 0); +} + +int fsEntryMount(struct fs_entry *mountpoint, struct fs_entry *mounted) +{ + if (mountpoint->mountedFs != NULL || mounted->mountpoint != NULL) { + return -EBUSY; + } + + mountpoint->mountedFs = mounted; + mounted->mountpoint = mountpoint; + + fsEntryRef(mounted); + fsEntryRef(mountpoint); + + return 0; +} + +int fsEntryUmount(struct fs_entry *mounted) +{ + if (!mounted->mountpoint || !mounted->mountpoint->mountedFs || mounted->mountpoint->mountedFs != mounted) + return -ENOENT; + + if (mounted->mountedFs) + return -EBUSY; + + // self + mounted + if (mounted->ref != 2) + return -EBUSY; + + + mounted->mountpoint->mountedFs = NULL; + fsEntryUnref(&mounted->mountpoint); + mounted->mountpoint = NULL; + fsEntryUnref(&mounted); + + return 0; +} diff --git a/core/fsEntry.h b/core/fsEntry.h new file mode 100644 index 0000000..01c856c --- /dev/null +++ b/core/fsEntry.h @@ -0,0 +1,183 @@ +#pragma once + +#include "fs.h" +#include "stdint.h" +#include "stddef.h" + +/** + * Those structures aimes to represent VFS directory entries in memory. + * It's the equivalent of the Linux dentry or SimpleOs sos_fs_nscache_node. + */ + +/** + * Path or Pascal String + * */ +struct pstr { + size_t len; + const char *name; +}; + +#define PSTR_INIT(n, l) \ + { \ + .len = l, .name = n \ + } + +#define PSTR_STATIC_INIT(n) \ + { \ + .len = sizeof(n) - 1, .name = n \ + } + +/** + * @brief Structure representing a file system entry. + * + * This structure holds information about a file system entry, + * including its name, associated inode, relationships within + * the file system hierarchy, and reference count. + */ +struct fs_entry { + struct pstr name; + struct inode *node; + + struct fs_entry *parent; // parent directory + struct fs_entry *mountpoint; // if this is a mounted fs, point to mountpoint + struct fs_entry *mountedFs; // Fs possibly mounted on this fsEntry + + struct fs_entry *childs; // point to the list of children + struct fs_entry *nextSiblings, *prevSiblings; // Sibling at the same level + + uint ref; // reference counter +}; + +/** + * @brief Splits a pstr path into upper and lower components. + * + * This function takes a path and splits it into an upper component + * (the first segment of the path) and a lower component (the rest of the path). + * + * @param path Pointer to the input pstr structure representing the path. + * @param upper Pointer to the pstr structure to store the upper component. + * @param lower Pointer to the pstr structure to store the lower component. + * @return bool_t Returns true if there is a lower component, false otherwise. + */ +bool_t pstringSplipPath(struct pstr *path, struct pstr *upper, struct pstr *lower); + +/** + * @brief Compares two pstr structures to check if they are equal. + * + * This function compares the length and the content of the strings + * in the two provided pstr structures. + * + * @param a Pointer to the first pstr structure. + * @param b Pointer to the second pstr structure. + * @return int Returns 1 if the structures are equal, 0 otherwise. + */ +bool_t pstringIsEq(const struct pstr *p1, const struct pstr *p2); + +/** + * @brief Checks if a pstr structure matches a given C string. + * + * @param s Pointer to the null-terminated C string to compare. + * @return bool_t Returns true if the pstr matches the C string, false otherwise. + */ +bool_t pstringIsStr(const struct pstr *p, char *s); + +/** + * @brief Increments the reference count of a file system entry. + * + * This function increments the reference count of the given file system + * entry. It is typically used to manage the lifecycle of file system + * entries, ensuring that they are not prematurely deallocated. + * + * @param entry Pointer to the fsEntry structure whose reference count will be incremented. + * @return int Returns 0 on success. + */ +int fsEntryRef(struct fs_entry *entry); + +/** + * @brief Decrements the reference count of a file system entry. + * + * This function decrements the reference count of the given file system + * entry. It is typically used to manage the lifecycle of file system + * entries, ensuring that they are not prematurely deallocated. + * + * @param entry Pointer to the fsEntry structure whose reference count will be decremented. + * @return int Returns 0 on success. + */ +int fsEntryUnref(struct fs_entry **entry); + +/** + * @brief Allocates and initializes a new fsEntry. + * + * This function allocates memory for a new `fsEntry` structure, initializes + * it with the provided name and inode, and adds it to the parent's list of children. + * + * @param parent Pointer to the parent `fsEntry` structure. Can be NULL if the new entry has no parent. + * @param name Pointer to the `pstr` structure representing the name of the new entry. Can be NULL. + * @param inode Pointer to the `inode` structure associated with the new entry. + * + * @return struct fsEntry* Pointer to the newly allocated `fsEntry` structure, or NULL if allocation fails. + * + * @note The reference count of the new entry is set to 1. + * @note If a parent is provided, the new entry is added to the parent's list of children and the parent's reference count is incremented. + * @note The inode's reference count is incremented. + */ +struct fs_entry *fsEntryAlloc(struct fs_entry *parent, const struct pstr *name, struct inode *inode); + +/** + * @brief Looks up an entry in the file system. + * + * This function searches for a specified entry within the children + * of a given directory entry. The search is limited to the entries + * that are already in memory. If the entry is not found, it indicates + * that the entry must be resolved using disk accesses. + * + * @param entry The node in which we are looking for the entry. + * @param name The name of the entry we are looking for. + * @param root The base node beyond which lookup must not go (to + * support chroot): a kind of "barrier". + * @param result The fsEntry for the given entry (set only when + * the return value is 0). + * + * @return int Returns 0 if the entry is found, otherwise returns a non-zero error code. + * -ENOENT if the entry could not be found in the entry directory. + * + * @note The mountpoints are followed. + * @note result is a NEW reference to the node. It should be unreferenced when unused. + */ +int fsEntryLookup(struct fs_entry *entry, const struct pstr *name, + const struct fs_entry *root, struct fs_entry **result); +/** + * @brief Setup the fsEntry subsystem + * + */ +int fsEntrySetup(void); + +/** + * @brief Mounts a file system entry at a specified mount point. + * + * This function mounts a file system entry (`mounted`) at a specified mount point (`mountpoint`). + * It updates the `mountedFs` field of the mount point and the `mountpoint` field of the mounted entry. + * + * @param mountpoint Pointer to the `fsEntry` structure representing the mount point. + * @param mounted Pointer to the `fsEntry` structure representing the file system entry to be mounted. + * + * @return int Returns 0 on success, or -EBUSY if either the mount point is already occupied or the mounted entry is already mounted. + * + * @note The reference counts of both the mount point and the mounted entry are incremented. + */ +int fsEntryMount(struct fs_entry *mountpoint, struct fs_entry *mounted); + +/** + * @brief Unmounts a file system entry. + * + * This function unmounts a file system entry (`mounted`) from its mount point. + * It updates the `mountedFs` field of the mount point and the `mountpoint` field of the mounted entry. + * + * @param mounted Pointer to the `fsEntry` structure representing the file system entry to be unmounted. + * + * @return int Returns 0 on success, -ENOENT if the entry is not mounted, or -EBUSY if the entry is busy. + * + * @note The unmount operation fails if the mounted entry has children or other references. + * @note The reference counts of both the mount point and the mounted entry are decremented. + */ +int fsEntryUmount(struct fs_entry *mounted); diff --git a/core/klibc.c b/core/klibc.c index e3566a5..0952b8e 100644 --- a/core/klibc.c +++ b/core/klibc.c @@ -171,7 +171,7 @@ void reverse(char s[]) } /* K&R */ -int strlen(const char s[]) +size_t strlen(const char s[]) { int i = 0; while (s[i] != '\0') diff --git a/core/klibc.h b/core/klibc.h index 3d6b13a..0339720 100644 --- a/core/klibc.h +++ b/core/klibc.h @@ -26,7 +26,7 @@ __attribute__ ((access (write_only, 1, 3))) void *memset(void *s, int c, size_t char *itoa(long long int value, char *str, int base); int atoi(const char *str); void reverse(char s[]); -int strlen(const char s[]); +size_t strlen(const char s[]); __attribute__ ((access (read_only, 1, 2))) unsigned int strnlen(const char *s, size_t count); int strcmp(const char s1[], const char s2[]); __attribute__ ((access (read_only, 2), access (write_only, 1, 3))) char *strzcpy(char *dst, const char *src, int len); diff --git a/core/main.c b/core/main.c index c3a188f..24312a5 100644 --- a/core/main.c +++ b/core/main.c @@ -3,6 +3,7 @@ #include "ata.h" #include "elf.h" #include "exception.h" +#include "fsEntry.h" #include "gdt.h" #include "idt.h" #include "interrupt.h" @@ -259,6 +260,9 @@ void kmain(unsigned long magic, unsigned long addr) irqSetRoutine(IRQ_TIMER, pit_handler); ATAInit(); + + // VFS name caching + fsEntrySetup(); #ifdef RUN_TEST run_test(); #endif diff --git a/tests/test.c b/tests/test.c index 4f7d964..3936f1b 100644 --- a/tests/test.c +++ b/tests/test.c @@ -3,8 +3,12 @@ #include "ata.h" #include "assert.h" #include "cpu_context.h" +#include "errno.h" +#include "fsEntry.h" #include "kernel.h" #include "klibc.h" +#include "stddef.h" +#include "stdint.h" #include "thread.h" #include "list.h" #include "mem.h" @@ -493,8 +497,194 @@ void testRingBuffer() ringbufferDestroy(inst); } +void printPstr(struct pstr *str){ + for(uint i = 0; i < str->len; i++){ + putc(str->name[i]); + } +} +void printPstrln(struct pstr *str){ + printPstr(str); + putc('\n'); +} + +void testFsPstring() +{ + bool_t result; + struct pstr path, upper, lower, lower2, lower3; + + // Test 1 + path.name = "/usr/local/bin"; + path.len = 14; + result = pstringSplipPath(&path, &upper, &lower); + printf("Test 1:\n"); + printPstrln(&path); + printPstrln(&upper); + printPstrln(&lower); + printf("Result: %s\n\n", result ? "true" : "false"); + + // Test 2 + path.name = "/home/user"; + path.len = 10; + result = pstringSplipPath(&path, &upper, &lower); + printf("Test 2:\n"); + printPstrln(&path); + printPstrln(&upper); + printPstrln(&lower); + printf("Result: %s\n\n", result ? "true" : "false"); + + // Test 3 + path.name = "/tmp"; + path.len = 4; + result = pstringSplipPath(&path, &upper, &lower); + printf("Test 3:\n"); + printPstrln(&path); + printPstrln(&upper); + printPstrln(&lower); + printf("Result: %s\n\n", result ? "true" : "false"); + + // Test 4 + path.name = "/var/log/"; + path.len = 9; + result = pstringSplipPath(&path, &upper, &lower); + printf("Test 4:\n"); + printPstrln(&path); + printPstrln(&upper); + printPstrln(&lower); + printf("Result: %s\n\n", result ? "true" : "false"); + + // Test 5 + path.name = "/"; + path.len = 1; + result = pstringSplipPath(&path, &upper, &lower); + printf("Test 5:\n"); + printPstrln(&path); + printPstrln(&upper); + printPstrln(&lower); + printf("Result: %s\n\n", result ? "true" : "false"); + + // Test 6 + const char *tmp = "///home///file/test///"; + path.name = tmp; + path.len = strlen(tmp); + struct pstr test = PSTR_STATIC_INIT("test"); + struct pstr empty = PSTR_INIT("", 0); + struct pstr slash = PSTR_INIT("/", 1); + struct pstr dot = PSTR_INIT("..", 2); + + printf("Test 6:\n"); + printPstr(&path); + assert(pstringSplipPath(&path, &upper, &lower)); + printf("\nUpper: "); + printPstr(&upper); + printf("\nLower: "); + printPstr(&lower); + printf("\n"); + assert(pstringSplipPath(&lower, &upper, &lower2)); + printf("\nUpper: "); + printPstr(&upper); + printf("\nLower: "); + printPstr(&lower2); + printf("\n"); + assert(pstringSplipPath(&lower2, &upper, &lower3) == 0); + printf("\nUpper: "); + printPstr(&upper); + printf("\nLower: "); + printPstr(&lower3); + printf("\n"); + assert(pstringIsEq(&upper, &test)); + assert(pstringSplipPath(&empty, &upper, &lower) == FALSE); + assert(pstringSplipPath(&slash, &upper, &lower) == FALSE); + assert(pstringIsEq(&empty, &test) == FALSE); + assert(pstringIsEq(&dot, &test) == FALSE); + + +} + +void testFsEntry() +{ + printf("Test fsEntry: create tree\n"); + + struct inode inode; + struct pstr rootname = PSTR_STATIC_INIT("/"); + struct pstr homename = PSTR_STATIC_INIT("home"); + struct pstr usrname = PSTR_STATIC_INIT("usr"); + struct pstr file1name = PSTR_STATIC_INIT("file1"); + struct pstr mntname = PSTR_STATIC_INIT("mnt"); + struct pstr mntfilename = PSTR_STATIC_INIT("file2"); + struct pstr dot = PSTR_STATIC_INIT("."); + struct pstr dot2 = PSTR_STATIC_INIT(".."); + + // create fs with /home, /mnt, /usr/file1 + struct fs_entry *root = fsEntryAlloc(NULL, &rootname, &inode); + struct fs_entry *home = fsEntryAlloc(root, &homename, &inode); + struct fs_entry *usr = fsEntryAlloc(root, &usrname, &inode); + struct fs_entry *mnt = fsEntryAlloc(root, &mntname, &inode); + struct fs_entry *file1 = fsEntryAlloc(home, &file1name, &inode); + + // /file2 + struct fs_entry *rootExt = fsEntryAlloc(NULL, &rootname, &inode); + struct fs_entry *mntfile = fsEntryAlloc(rootExt, &mntfilename, &inode); + struct fs_entry *res, *res2; + + assert(root && home && usr); + + printf("Test fsEntry: Lookup\n"); + // find a child + assert(fsEntryLookup(root, &homename, NULL, &res) == 0); + assert(home == res); + fsEntryUnref(&res); + + // cd . + assert(fsEntryLookup(home, &dot, NULL, &res) == 0); + assert(home == res); + fsEntryUnref(&res); + + // cd .. + assert(fsEntryLookup(home, &dot2, NULL, &res) == 0); + assert(root == res); + fsEntryUnref(&res); + + // find a non-child fail + assert(fsEntryLookup(home, &usrname, NULL, &res) == -ENOENT); + + // find mnt and mount rootExt + assert(fsEntryLookup(root, &mntname, NULL, &res) == 0); + assert(res == mnt); + assert(fsEntryMount(res, rootExt)==0); + fsEntryUnref(&res); //lookup increase ref + + // find on mounted + assert(fsEntryLookup(root, &mntname, NULL, &res) == 0); + fsEntryUnref(&res); //lookup increase ref + assert(fsEntryLookup(res, &mntfilename, NULL, &res2) == 0); + fsEntryUnref(&res2); //lookup increase ref + + // Clen rootExt + fsEntryUnref(&mntfile); + + // unmount rootExt + assert(fsEntryUmount(rootExt) == 0); + + + printf("Test fsEntry: free tree\n"); + fsEntryUnref(&usr); + assert(root->ref > 0); + assert(usr == NULL); + fsEntryUnref(&home); + assert(home != NULL); + fsEntryUnref(&file1); + assert(file1 == NULL); + fsEntryUnref(&mnt); + assert(mnt == NULL); + + fsEntryUnref(&mntfile); + fsEntryUnref(&rootExt); +} + void run_test(void) { + testFsPstring(); + testFsEntry(); // Example of checking thx to access attributs //int a[4] = {0}; //int b[3] = {0};