diff --git a/.gitignore b/.gitignore index d4fb281..855f03e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ # debug information files *.dwo +test_dir/* +data/* +build/* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 33e34a5..7c58a46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,33 @@ project(FastDevFS) find_package(PkgConfig REQUIRED) pkg_check_modules(FUSE3 REQUIRED fuse3) -add_executable(FastDevFS main.cpp) -target_include_directories(FastDevFS PRIVATE ${FUSE3_INCLUDE_DIRS}) + +add_executable(FastDevFS + main.cpp + + src/src/fuse_functions/getattr.cpp + src/src/fuse_functions/readdir.cpp + src/src/fuse_functions/opendir.cpp + src/src/fuse_functions/mkdir.cpp + src/src/fuse_functions/rmdir.cpp + src/src/fuse_functions/access.cpp + src/src/fuse_functions/statfs.cpp + src/src/fuse_functions/file_funcs.cpp + src/src/fuse_functions/utimens.cpp + + src/src/daemon/dir_manager.cpp + src/src/daemon/hash.cpp + src/src/daemon/file_io.cpp + src/src/config.cpp +) + +target_include_directories(FastDevFS PRIVATE + ${FUSE3_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR}/src/include +) + target_link_libraries(FastDevFS PRIVATE ${FUSE3_LIBRARIES}) -target_compile_options(FastDevFS PRIVATE ${FUSE3_CFLAGS_OTHER}) \ No newline at end of file +target_compile_options(FastDevFS PRIVATE ${FUSE3_CFLAGS_OTHER}) + +enable_testing() +add_subdirectory(test) \ No newline at end of file diff --git a/dir_tree.dat b/dir_tree.dat new file mode 100644 index 0000000..a0a9dff Binary files /dev/null and b/dir_tree.dat differ diff --git a/fdfs.conf b/fdfs.conf new file mode 100644 index 0000000..95202ec --- /dev/null +++ b/fdfs.conf @@ -0,0 +1,2 @@ +dedup=true +data_dir=./data \ No newline at end of file diff --git a/hash_table.dat b/hash_table.dat new file mode 100644 index 0000000..1746b8e Binary files /dev/null and b/hash_table.dat differ diff --git a/main.cpp b/main.cpp index 6c09fa4..39eaf61 100644 --- a/main.cpp +++ b/main.cpp @@ -1,24 +1,200 @@ -#include -#include +// #define FUSE_USE_VERSION 31 + +// #include +// #include +// #include "src/include/fuse_functions/getattr.h" +// #include "src/include/fuse_functions/readdir.h" +// #include "src/include/fuse_functions/opendir.h" + +// using namespace std; + +// // int get_attr(const char* path, struct stat* stbuf, struct fuse_file_info* fi){ +// // cout<<"getattr called for path: "< +#include + +#include "daemon/dir_manager.h" +#include "daemon/hash.h" +#include "daemon/file_io.h" +#include "fuse_functions/getattr.h" +#include "fuse_functions/readdir.h" +#include "fuse_functions/opendir.h" +#include "fuse_functions/mkdir.h" +#include "fuse_functions/rmdir.h" +#include "fuse_functions/access.h" +#include "fuse_functions/statfs.h" +#include "fuse_functions/file_funcs.h" +#include "fuse_functions/utimens.h" +#include "sys/mman.h" +#include "config.h" +#include using namespace std; +#define DIR_TREE_FILE "dir_tree.dat" +#define HASH_TABLE_FILE "hash_table.dat" + +/* + * Global DirManager instance. + * Used by all FUSE callbacks. + */ +DirManager* g_dir_manager = nullptr; + +static struct fuse_operations fdfs_ops; +extern FSConfig g_config; +// static struct fuse_operations fdfs_ops = { +// .getattr = fdfs_getattr, +// .readdir = fdfs_readdir, +// .opendir = fdfs_opendir, +// // .mkdir = fdfs_mkdir, +// // .rmdir = fdfs_rmdir, +// // .access = fdfs_access, +// // .statfs = fdfs_statfs, + +// // // safety stubs +// // .open = fdfs_open, +// // .read = fdfs_read, +// // .write = fdfs_write, +// // .create = fdfs_create, +// // .unlink = fdfs_unlink, +// // .truncate = fdfs_truncate, + +// // // metadata no-ops +// // .chmod = fdfs_chmod, +// // .chown = fdfs_chown, +// // .utimens = fdfs_utimens, +// }; + + +int main(int argc, char* argv[]) { + cout << "Starting FastDevFS Daemon..." << endl; + + // Initialize DirManager with mmap + int fd = open(DIR_TREE_FILE, O_RDWR | O_CREAT, 0644); + if (fd < 0) { + perror("open"); + exit(1); + } + + size_t dir_manager_size = sizeof(DirManager); + + /* ensure file size */ + if (ftruncate(fd, dir_manager_size) < 0) { + perror("ftruncate"); + exit(1); + } + + /* mmap - maps DirManager to shared memory */ + g_dir_manager = (DirManager*) mmap( + NULL, + dir_manager_size, + PROT_READ | PROT_WRITE, + MAP_SHARED, + fd, + 0 + ); + + if (g_dir_manager == MAP_FAILED) { + perror("mmap"); + exit(1); + } + + // Initialize directory ADT only if not already initialized + if (!is_dir_manager_initialized(g_dir_manager)) { + dir_manager_init(g_dir_manager); + } + + // Initialize data directory for file storage + if (init_data_dir() < 0) { + cerr << "Failed to initialize data directory" << endl; + exit(1); + } + + // Initialize HashTable with separate mmap + int hash_fd = open(HASH_TABLE_FILE, O_RDWR | O_CREAT, 0644); + if (hash_fd < 0) { + perror("open hash_table"); + exit(1); + } + + size_t hash_table_size = sizeof(HashTable); + + /* ensure file size */ + if (ftruncate(hash_fd, hash_table_size) < 0) { + perror("ftruncate hash_table"); + exit(1); + } + + /* mmap - maps HashTable to shared memory */ + g_hash_table = (HashTable*) mmap( + NULL, + hash_table_size, + PROT_READ | PROT_WRITE, + MAP_SHARED, + hash_fd, + 0 + ); + + if (g_hash_table == MAP_FAILED) { + perror("mmap hash_table"); + exit(1); + } + + // Initialize hash table only if not already initialized + if (g_hash_table->magic != HASH_TABLE_MAGIC) { + hash_init(g_hash_table); + } + + // basic fuse_functions + fdfs_ops.getattr = fdfs_getattr; + fdfs_ops.opendir = fdfs_opendir; + fdfs_ops.readdir = fdfs_readdir; + fdfs_ops.mkdir = fdfs_mkdir; + fdfs_ops.rmdir = fdfs_rmdir; + fdfs_ops.access = fdfs_access; + fdfs_ops.statfs = fdfs_statfs; + + // file ops + fdfs_ops.open = fdfs_open; + fdfs_ops.read = fdfs_read; + fdfs_ops.write = fdfs_write; + fdfs_ops.create = fdfs_create; + fdfs_ops.unlink = fdfs_unlink; + fdfs_ops.truncate = fdfs_truncate; + fdfs_ops.utimens = fdfs_utimens; -int get_attr(const char* path, struct stat* stbuf, struct fuse_file_info* fi){ - cout<<"getattr called for path: "< + +struct FSConfig { + bool dedup_enabled = false; + std::string data_dir = "data"; +}; + +extern FSConfig g_config; + +bool load_config(const std::string path); \ No newline at end of file diff --git a/src/include/daemon/dir_manager.h b/src/include/daemon/dir_manager.h new file mode 100644 index 0000000..432106b --- /dev/null +++ b/src/include/daemon/dir_manager.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "daemon/hash.h" + +#define MAX_NODES 10000 +#define NAME_SIZE 256 +#define DIR_MANAGER_MAGIC 0xDEADBEEF + +typedef struct DirNode { + char name[NAME_SIZE]; + int parent; + int first_child; + int next_sibling; + + mode_t mode; // file type + permissions + uid_t uid; + gid_t gid; + off_t size; + time_t atime; + time_t mtime; + time_t ctime; + nlink_t nlink; + + bool in_use; + int next_free; +} DirNode; + +typedef struct DirManager { + unsigned int magic; // Magic number to detect initialization + DirNode nodes[MAX_NODES]; + int root; + int free_list; + pthread_rwlock_t rwlock; +} DirManager; + +// API +void dir_manager_init(DirManager* dm); +int insert_node(DirManager* dm, const char* path); +int lookup_node(DirManager* dm, const char* path); +int lookup_node_nolock(DirManager* dm, const char* path); +bool remove_node(DirManager* dm, const char* path); +bool is_dir_manager_initialized(DirManager* dm); diff --git a/src/include/daemon/file_io.h b/src/include/daemon/file_io.h new file mode 100644 index 0000000..1927cfe --- /dev/null +++ b/src/include/daemon/file_io.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +// Data directory for storing file contents (will be set to absolute path) +extern char g_data_dir_path[512]; + +// Helper functions for file I/O operations +// These functions handle reading/writing file data to the host filesystem +// using inode numbers as filenames + +/** + * Get the path to the data file for a given inode + * @param inode The inode number + * @param path_buf Buffer to store the constructed path + * @param buf_size Size of the buffer + */ +void get_data_file_path(int inode, char* path_buf, size_t buf_size); + +/** + * Read data from the host filesystem for a given inode + * @param inode The inode number + * @param buf Buffer to read data into + * @param size Number of bytes to read + * @param offset Offset to start reading from + * @return Number of bytes read, or -1 on error + */ +ssize_t read_inode_data(int inode, char* buf, size_t size, off_t offset); + +/** + * Write data to the host filesystem for a given inode + * @param inode The inode number + * @param buf Buffer containing data to write + * @param size Number of bytes to write + * @param offset Offset to start writing at + * @return Number of bytes written, or -1 on error + */ +ssize_t write_inode_data(int inode, const char* buf, size_t size, off_t offset); + +/** + * Truncate the data file for a given inode + * @param inode The inode number + * @param new_size New size for the file + * @return 0 on success, -1 on error + */ +int truncate_inode_data(int inode, off_t new_size); + +/** + * Delete the data file for a given inode + * @param inode The inode number + * @return 0 on success, -1 on error + */ +int delete_inode_data(int inode); + +/** + * Initialize the data directory + * @return 0 on success, -1 on error + */ +int init_data_dir(); diff --git a/src/include/daemon/hash.h b/src/include/daemon/hash.h new file mode 100644 index 0000000..9c8368b --- /dev/null +++ b/src/include/daemon/hash.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#define HASH_SIZE 4096 +#define MAX_ENTRIES 10000 +#define KEY_SIZE 1024 // full path key +#define HASH_TABLE_MAGIC 0xCAFEBABE + +// single hash table entry (chained) +typedef struct HashEntry { + uint64_t hash; // hash of key + char key[KEY_SIZE]; // full key (path) + int value; // node index + int next; // next entry in chain +} HashEntry; + +// static hash table +typedef struct HashTable { + unsigned int magic; // Magic number for persistence detection + int buckets[HASH_SIZE]; + HashEntry entries[MAX_ENTRIES]; + int free_head; +} HashTable; + +// Global mmap'd hash table +extern HashTable* g_hash_table; + +// initialization +void hash_init(HashTable* ht); + +// full-path polynomial hash +uint64_t hash_path_poly(const char* path); + +// operations (key-aware) +bool hash_insert(HashTable* ht, uint64_t hash, const char* key, int value); +int hash_lookup(HashTable* ht, uint64_t hash, const char* key); +bool hash_remove(HashTable* ht, uint64_t hash, const char* key); diff --git a/src/include/fuse_functions/access.h b/src/include/fuse_functions/access.h new file mode 100644 index 0000000..d9f4459 --- /dev/null +++ b/src/include/fuse_functions/access.h @@ -0,0 +1,13 @@ +#ifndef FASTDEVFS_ACCESS_H +#define FASTDEVFS_ACCESS_H + +#define FUSE_USE_VERSION 31 +#include + +/* + * access FUSE callback + * Checks existence and basic permissions + */ +int fdfs_access(const char* path, int mask); + +#endif // FASTDEVFS_ACCESS_H diff --git a/src/include/fuse_functions/file_funcs.h b/src/include/fuse_functions/file_funcs.h new file mode 100644 index 0000000..a220f0c --- /dev/null +++ b/src/include/fuse_functions/file_funcs.h @@ -0,0 +1,17 @@ +#define FUSE_USE_VERSION 31 + +#pragma once + +#include +#include + +// file ops +int fdfs_open(const char *path, struct fuse_file_info *fi); +int fdfs_read(const char *path, char *buf, size_t size, + off_t offset, struct fuse_file_info *fi); +int fdfs_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi); +int fdfs_create(const char *path, mode_t mode, + struct fuse_file_info *fi); +int fdfs_unlink(const char *path); +int fdfs_truncate(const char *path, off_t size, fuse_file_info* fi); diff --git a/src/include/fuse_functions/getattr.h b/src/include/fuse_functions/getattr.h new file mode 100644 index 0000000..ddfb70b --- /dev/null +++ b/src/include/fuse_functions/getattr.h @@ -0,0 +1,15 @@ +#ifndef FASTDEVFS_GETATTR_H +#define FASTDEVFS_GETATTR_H + +#define FUSE_USE_VERSION 31 +#include + +/* + * getattr FUSE callback + * Uses DirManager ADT to resolve paths + */ +int fdfs_getattr(const char* path, + struct stat* stbuf, + struct fuse_file_info* fi); + +#endif // FASTDEVFS_GETATTR_H diff --git a/src/include/fuse_functions/mkdir.h b/src/include/fuse_functions/mkdir.h new file mode 100644 index 0000000..61c9dfd --- /dev/null +++ b/src/include/fuse_functions/mkdir.h @@ -0,0 +1,13 @@ +#ifndef FASTDEVFS_MKDIR_H +#define FASTDEVFS_MKDIR_H + +#define FUSE_USE_VERSION 31 +#include + +/* + * mkdir FUSE callback + * Creates a directory using DirManager ADT + */ +int fdfs_mkdir(const char* path, mode_t mode); + +#endif // FASTDEVFS_MKDIR_H diff --git a/src/include/fuse_functions/opendir.h b/src/include/fuse_functions/opendir.h new file mode 100644 index 0000000..c2b1310 --- /dev/null +++ b/src/include/fuse_functions/opendir.h @@ -0,0 +1,14 @@ +#ifndef FASTDEVFS_OPENDIR_H +#define FASTDEVFS_OPENDIR_H + +#define FUSE_USE_VERSION 31 +#include + +/* + * opendir FUSE callback + * Verifies directory existence using DirManager ADT + */ +int fdfs_opendir(const char* path, + struct fuse_file_info* fi); + +#endif // FASTDEVFS_OPENDIR_H diff --git a/src/include/fuse_functions/readdir.h b/src/include/fuse_functions/readdir.h new file mode 100644 index 0000000..3178631 --- /dev/null +++ b/src/include/fuse_functions/readdir.h @@ -0,0 +1,18 @@ +#ifndef FASTDEVFS_READDIR_H +#define FASTDEVFS_READDIR_H + +#define FUSE_USE_VERSION 31 +#include + +/* + * readdir FUSE callback + * Lists directory contents using DirManager ADT + */ +int fdfs_readdir(const char* path, + void* buf, + fuse_fill_dir_t filler, + off_t offset, + struct fuse_file_info* fi, + enum fuse_readdir_flags flags); + +#endif // FASTDEVFS_READDIR_H diff --git a/src/include/fuse_functions/rmdir.h b/src/include/fuse_functions/rmdir.h new file mode 100644 index 0000000..204e07b --- /dev/null +++ b/src/include/fuse_functions/rmdir.h @@ -0,0 +1,13 @@ +#ifndef FASTDEVFS_RMDIR_H +#define FASTDEVFS_RMDIR_H + +#define FUSE_USE_VERSION 31 +#include + +/* + * rmdir FUSE callback + * Removes an empty directory using DirManager ADT + */ +int fdfs_rmdir(const char* path); + +#endif // FASTDEVFS_RMDIR_H diff --git a/src/include/fuse_functions/statfs.h b/src/include/fuse_functions/statfs.h new file mode 100644 index 0000000..cd40739 --- /dev/null +++ b/src/include/fuse_functions/statfs.h @@ -0,0 +1,13 @@ +#ifndef FASTDEVFS_STATFS_H +#define FASTDEVFS_STATFS_H + +#define FUSE_USE_VERSION 31 +#include + +/* + * statfs FUSE callback + * Reports filesystem statistics + */ +int fdfs_statfs(const char* path, struct statvfs* stbuf); + +#endif // FASTDEVFS_STATFS_H diff --git a/src/include/fuse_functions/utimens.h b/src/include/fuse_functions/utimens.h new file mode 100644 index 0000000..3801e29 --- /dev/null +++ b/src/include/fuse_functions/utimens.h @@ -0,0 +1,15 @@ +#ifndef FDFS_UTIMENS_H +#define FDFS_UTIMENS_H + +#define FUSE_USE_VERSION 31 +#include + +/* + * Update file times (atime, mtime) + * Called by touch and similar utilities + */ +int fdfs_utimens(const char* path, + const struct timespec tv[2], + struct fuse_file_info* fi); + +#endif // FDFS_UTIMENS_H diff --git a/src/include/services/dedup/dedup_table.h b/src/include/services/dedup/dedup_table.h new file mode 100644 index 0000000..8ca35ce --- /dev/null +++ b/src/include/services/dedup/dedup_table.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#define DEDUP_HASH_SIZE 4096 // number of buckets +#define DEDUP_MAX_ENTRIES 100000 // max stored hashes + +#define DEDUP_MAGIC 0xD3D3D3D3 + +// 256-bit SHA hash +typedef struct { + uint8_t bytes[32]; // SHA-256 = 32 bytes +} SHA256Hash; + +// Chain array element +typedef struct { + uint64_t poly_hash; // poly hash of SHA256 + uint32_t file_node_index; // index of file node in Dir_Manager + int next; // linked list chaining for collision resolution +} DedupChainEntry; + +// Dedup hash table +typedef struct { + uint32_t magic; + int buckets[DEDUP_HASH_SIZE]; // bucket array storing first_indices into chain array + DedupChainEntry chain[DEDUP_MAX_ENTRIES]; // chain array with hash and file node index + int free_head; // free list head for chain array +} DedupTable; + +// Global pointer (mmap like your hash table) +extern DedupTable* g_dedup_table; + + +// API + +void dedup_init(DedupTable* dt); + +int dedup_lookup(DedupTable* dt, const SHA256Hash* hash); + +bool dedup_insert(DedupTable* dt, + const SHA256Hash* hash, + uint64_t block_id); + +bool dedup_remove(DedupTable* dt, + const SHA256Hash* hash); + +void dedup_inc_ref(DedupTable* dt, int entry_index); + +void dedup_dec_ref(DedupTable* dt, int entry_index); diff --git a/src/src/config.cpp b/src/src/config.cpp new file mode 100644 index 0000000..cd591c3 --- /dev/null +++ b/src/src/config.cpp @@ -0,0 +1,34 @@ +#include "config.h" +#include +#include +#include +FSConfig g_config; + +bool load_config(const std::string path) +{ + std::ifstream file(path); + if (!file.is_open()) + { + std::cerr<<"In load_config: Could not open file."; + return false; + } + std::string line; + + while (std::getline(file, line)) + { + std::istringstream iss(line); + std::string key, value; + + if (std::getline(iss, key, '=') && + std::getline(iss, value)) + { + if (key == "dedup") + g_config.dedup_enabled = (value == "true"); + + if (key == "data_dir") + g_config.data_dir = value; + } + } + + return true; +} \ No newline at end of file diff --git a/src/src/daemon/dir_manager.cpp b/src/src/daemon/dir_manager.cpp new file mode 100644 index 0000000..e293c6a --- /dev/null +++ b/src/src/daemon/dir_manager.cpp @@ -0,0 +1,336 @@ +// #include "daemon/dir_manager.h" +// #include +// #include + +// void dir_manager_init(DirManager* dm) { +// pthread_rwlock_init(&dm->rwlock, NULL); + +// dm->magic = DIR_MANAGER_MAGIC; +// dm->root = 0; +// dm->nodes[0].name[0] = '/'; +// dm->nodes[0].parent = -1; +// dm->nodes[0].first_child = -1; +// dm->nodes[0].next_sibling = -1; +// dm->nodes[0].in_use = true; + +// dm->free_list = 1; +// for (int i = 1; i < MAX_NODES - 1; i++) { +// dm->nodes[i].next_free = i + 1; +// dm->nodes[i].in_use = false; +// } +// dm->nodes[MAX_NODES - 1].next_free = -1; +// } + +// bool is_dir_manager_initialized(DirManager* dm) { +// return dm->magic == DIR_MANAGER_MAGIC; +// } + +// int lookup_node(DirManager* dm, const char* path){ +// pthread_rwlock_rdlock(&dm->rwlock); + +// if (strcmp(path, "/") == 0) { +// pthread_rwlock_unlock(&dm->rwlock); +// return dm->root; +// } + +// uint64_t h = hash_path_poly(path); +// int res = hash_lookup(g_hash_table, h, path); + +// pthread_rwlock_unlock(&dm->rwlock); +// return res; +// } + +// int insert_node(DirManager* dm, const char* path) { +// pthread_rwlock_wrlock(&dm->rwlock); + +// if (strcmp(path, "/") == 0) { +// pthread_rwlock_unlock(&dm->rwlock); +// return -1; +// } + +// int parent = dm->root; +// char token[NAME_SIZE]; +// int ti = 0; + +// for (const char* p = path; ; ++p) { +// if (*p == '/' || *p == '\0') { +// if (ti > 0) { +// token[ti] = '\0'; + +// char full_key[KEY_SIZE]; +// snprintf(full_key, KEY_SIZE, "%.*s", +// (int)(p - path), path); + +// uint64_t h = hash_path_poly(full_key); +// int node = hash_lookup(g_hash_table, h, full_key); + +// if (*p == '\0') { +// if (node != -1 || dm->free_list == -1) { +// pthread_rwlock_unlock(&dm->rwlock); +// return -1; +// } + +// int new_node = dm->free_list; +// dm->free_list = dm->nodes[new_node].next_free; + +// DirNode* n = &dm->nodes[new_node]; +// strncpy(n->name, token, NAME_SIZE); +// n->parent = parent; +// n->first_child = -1; +// n->next_sibling = dm->nodes[parent].first_child; +// n->in_use = true; + +// dm->nodes[parent].first_child = new_node; +// hash_insert(g_hash_table, h, full_key, new_node); + +// pthread_rwlock_unlock(&dm->rwlock); +// return new_node; +// } + +// if (node == -1) { +// pthread_rwlock_unlock(&dm->rwlock); +// return -1; +// } + +// parent = node; +// ti = 0; +// } +// if (*p == '\0') +// break; +// } else if (ti < NAME_SIZE - 1) { +// token[ti++] = *p; +// } +// } + +// pthread_rwlock_unlock(&dm->rwlock); +// return -1; +// } + +// bool remove_node(DirManager* dm, const char* path) { +// pthread_rwlock_wrlock(&dm->rwlock); + +// int node = lookup_node(dm, path); +// if (node <= 0) { +// pthread_rwlock_unlock(&dm->rwlock); +// return false; +// } + +// DirNode* n = &dm->nodes[node]; +// if (n->first_child != -1) { +// pthread_rwlock_unlock(&dm->rwlock); +// return false; +// } + +// uint64_t h = hash_path_poly(path); + +// int parent = n->parent; +// int* link = &dm->nodes[parent].first_child; +// while (*link != -1 && *link != node) +// link = &dm->nodes[*link].next_sibling; + +// if (*link == node) +// *link = n->next_sibling; + +// hash_remove(g_hash_table, h, path); + +// n->in_use = false; +// n->next_free = dm->free_list; +// dm->free_list = node; + +// pthread_rwlock_unlock(&dm->rwlock); +// return true; +// } + + + + + + +#include "daemon/dir_manager.h" +#include +#include +#include +#include +#include +/* -------------------- INIT -------------------- */ + +void dir_manager_init(DirManager* dm) { + pthread_rwlock_init(&dm->rwlock, NULL); + + dm->magic = DIR_MANAGER_MAGIC; + dm->root = 0; + + DirNode* r = &dm->nodes[0]; + + strcpy(r->name, "/"); + r->parent = -1; + r->first_child = -1; + r->next_sibling = -1; + + r->mode = S_IFDIR | 0755; + r->uid = getuid(); + r->gid = getgid(); + r->size = 0; + r->nlink = 2; // . and .. + + time_t now = time(NULL); + r->atime = r->mtime = r->ctime = now; + + r->in_use = true; + + /* freelist */ + dm->free_list = 1; + for (int i = 1; i < MAX_NODES - 1; i++) { + dm->nodes[i].next_free = i + 1; + dm->nodes[i].in_use = false; + } + dm->nodes[MAX_NODES - 1].next_free = -1; +} + +bool is_dir_manager_initialized(DirManager* dm) { + return dm->magic == DIR_MANAGER_MAGIC; +} + +/* -------------------- LOOKUP -------------------- */ +/* nolock helper (CRITICAL to avoid deadlock) */ + +int lookup_node_nolock(DirManager* dm, const char* path) { + if (strcmp(path, "/") == 0) + return dm->root; + + uint64_t h = hash_path_poly(path); + std::cout<<"In look_up_nolock"<< hash_lookup(g_hash_table, h, path); + return hash_lookup(g_hash_table, h, path); +} + +int lookup_node(DirManager* dm, const char* path) { + pthread_rwlock_rdlock(&dm->rwlock); + int res = lookup_node_nolock(dm, path); + pthread_rwlock_unlock(&dm->rwlock); + return res; +} + +/* -------------------- INSERT -------------------- */ + +int insert_node(DirManager* dm, const char* path) { + pthread_rwlock_wrlock(&dm->rwlock); + + if (strcmp(path, "/") == 0) { + pthread_rwlock_unlock(&dm->rwlock); + return -1; + } + + int parent = dm->root; + char token[NAME_SIZE]; + int ti = 0; + + for (const char* p = path; ; ++p) { + if (*p == '/' || *p == '\0') { + if (ti > 0) { + token[ti] = '\0'; + + char full_key[KEY_SIZE]; + snprintf(full_key, KEY_SIZE, "%.*s", + (int)(p - path), path); + + uint64_t h = hash_path_poly(full_key); + int node = hash_lookup(g_hash_table, h, full_key); + + /* final component -> create */ + if (*p == '\0') { + if (node != -1 || dm->free_list == -1) { + pthread_rwlock_unlock(&dm->rwlock); + return -1; + } + + int new_node = dm->free_list; + dm->free_list = dm->nodes[new_node].next_free; + + DirNode* n = &dm->nodes[new_node]; + + strncpy(n->name, token, NAME_SIZE); + n->parent = parent; + n->first_child = -1; + n->next_sibling = dm->nodes[parent].first_child; + + /* metadata defaults (directory) */ + n->mode = S_IFDIR | 0755; + n->uid = getuid(); + n->gid = getgid(); + n->size = 0; + n->nlink = 2; + + time_t now = time(NULL); + n->atime = n->mtime = n->ctime = now; + + n->in_use = true; + + dm->nodes[parent].first_child = new_node; + dm->nodes[parent].nlink++; // new subdir + + hash_insert(g_hash_table, h, full_key, new_node); + + pthread_rwlock_unlock(&dm->rwlock); + return new_node; + } + + /* intermediate component must exist */ + if (node == -1) { + pthread_rwlock_unlock(&dm->rwlock); + return -1; + } + + parent = node; + ti = 0; + } + if (*p == '\0') + break; + } else if (ti < NAME_SIZE - 1) { + token[ti++] = *p; + } + } + + pthread_rwlock_unlock(&dm->rwlock); + return -1; +} + +/* -------------------- REMOVE -------------------- */ + +bool remove_node(DirManager* dm, const char* path) { + pthread_rwlock_wrlock(&dm->rwlock); + + int node = lookup_node_nolock(dm, path); + if (node <= 0) { + pthread_rwlock_unlock(&dm->rwlock); + return false; + } + + DirNode* n = &dm->nodes[node]; + + /* must be empty */ + if (n->first_child != -1) { + pthread_rwlock_unlock(&dm->rwlock); + return false; + } + + uint64_t h = hash_path_poly(path); + + int parent = n->parent; + int* link = &dm->nodes[parent].first_child; + while (*link != -1 && *link != node) + link = &dm->nodes[*link].next_sibling; + + if (*link == node) + *link = n->next_sibling; + + dm->nodes[parent].nlink--; // remove subdir + + hash_remove(g_hash_table, h, path); + + n->in_use = false; + n->next_free = dm->free_list; + dm->free_list = node; + + pthread_rwlock_unlock(&dm->rwlock); + return true; +} \ No newline at end of file diff --git a/src/src/daemon/file_io.cpp b/src/src/daemon/file_io.cpp new file mode 100644 index 0000000..64c2404 --- /dev/null +++ b/src/src/daemon/file_io.cpp @@ -0,0 +1,120 @@ +#include "daemon/file_io.h" +#include +#include +#include +#include +#include +#include +#include + +// Global variable to store absolute path to data directory +char g_data_dir_path[512] = {0}; + +void get_data_file_path(int inode, char* path_buf, size_t buf_size) { + snprintf(path_buf, buf_size, "%s/%d", g_data_dir_path, inode); +} + +int init_data_dir() { + struct stat st = {0}; + + // Get current working directory and construct absolute path + char cwd[512]; + if (getcwd(cwd, sizeof(cwd)) == NULL) { + perror("getcwd"); + return -1; + } + + // Store absolute path to data directory + snprintf(g_data_dir_path, sizeof(g_data_dir_path), "%s/data", cwd); + + // Create data directory if it doesn't exist + if (stat(g_data_dir_path, &st) == -1) { + if (mkdir(g_data_dir_path, 0755) == -1) { + perror("mkdir data directory"); + return -1; + } + } + + return 0; +} + +ssize_t read_inode_data(int inode, char* buf, size_t size, off_t offset) { + char path[256]; + get_data_file_path(inode, path, sizeof(path)); + + int fd = open(path, O_RDONLY); + if (fd < 0) { + // File doesn't exist yet - return all zeros + if (errno == ENOENT) { + memset(buf, 0, size); + return size; + } + return -1; + } + + // Seek to the offset + if (lseek(fd, offset, SEEK_SET) < 0) { + close(fd); + return -1; + } + + // Read the data + ssize_t bytes_read = read(fd, buf, size); + close(fd); + + return bytes_read; +} + +ssize_t write_inode_data(int inode, const char* buf, size_t size, off_t offset) { + char path[256]; + get_data_file_path(inode, path, sizeof(path)); + + // Open with O_CREAT to create file if it doesn't exist + int fd = open(path, O_WRONLY | O_CREAT, 0644); + if (fd < 0) { + perror("open for write"); + return -1; + } + + // Seek to the offset + if (lseek(fd, offset, SEEK_SET) < 0) { + close(fd); + return -1; + } + + // Write the data + ssize_t bytes_written = write(fd, buf, size); + close(fd); + + return bytes_written; +} + +int truncate_inode_data(int inode, off_t new_size) { + char path[256]; + get_data_file_path(inode, path, sizeof(path)); + + // If truncating to 0, we can just delete the file + if (new_size == 0) { + unlink(path); // Ignore errors if file doesn't exist + return 0; + } + + // Otherwise, truncate it + if (truncate(path, new_size) < 0 && errno != ENOENT) { + return -1; + } + + return 0; +} + +int delete_inode_data(int inode) { + char path[256]; + get_data_file_path(inode, path, sizeof(path)); + + // Delete the data file + if (unlink(path) < 0 && errno != ENOENT) { + return -1; + } + + return 0; +} diff --git a/src/src/daemon/hash.cpp b/src/src/daemon/hash.cpp new file mode 100644 index 0000000..0707917 --- /dev/null +++ b/src/src/daemon/hash.cpp @@ -0,0 +1,88 @@ +#include "daemon/hash.h" +#include + +// Global HashTable instance - mmap'd in main.cpp +HashTable* g_hash_table = nullptr; + +void hash_init(HashTable* ht) { + ht->magic = HASH_TABLE_MAGIC; + for (int i = 0; i < HASH_SIZE; i++) + ht->buckets[i] = -1; + + for (int i = 0; i < MAX_ENTRIES - 1; i++) + ht->entries[i].next = i + 1; + + ht->entries[MAX_ENTRIES - 1].next = -1; + ht->free_head = 0; +} + +// Polynomial rolling hash on FULL PATH +uint64_t hash_path_poly(const char* path) { + const uint64_t P = 131; + uint64_t h = 0; + + for (const unsigned char* p = (const unsigned char*)path; *p; ++p) { + h = (h * P + *p) % MAX_ENTRIES; + } + return h; +} + +bool hash_insert(HashTable* ht, uint64_t hash, + const char* key, int value) { + int bucket = hash % HASH_SIZE; + + for (int i = ht->buckets[bucket]; i != -1; i = ht->entries[i].next) { + if (ht->entries[i].hash == hash && + strcmp(ht->entries[i].key, key) == 0) + return false; + } + + if (ht->free_head == -1) + return false; + + int idx = ht->free_head; + ht->free_head = ht->entries[idx].next; + + ht->entries[idx].hash = hash; + strncpy(ht->entries[idx].key, key, KEY_SIZE - 1); + ht->entries[idx].key[KEY_SIZE - 1] = '\0'; + ht->entries[idx].value = value; + + ht->entries[idx].next = ht->buckets[bucket]; + ht->buckets[bucket] = idx; + return true; +} + +int hash_lookup(HashTable* ht, uint64_t hash, + const char* key) { + int bucket = hash % HASH_SIZE; + + for (int i = ht->buckets[bucket]; i != -1; i = ht->entries[i].next) { + if (ht->entries[i].hash == hash && + strcmp(ht->entries[i].key, key) == 0) + return ht->entries[i].value; + } + return -1; +} + +bool hash_remove(HashTable* ht, uint64_t hash, + const char* key) { + int bucket = hash % HASH_SIZE; + int prev = -1; + + for (int i = ht->buckets[bucket]; i != -1; prev = i, i = ht->entries[i].next) { + if (ht->entries[i].hash == hash && + strcmp(ht->entries[i].key, key) == 0) { + + if (prev == -1) + ht->buckets[bucket] = ht->entries[i].next; + else + ht->entries[prev].next = ht->entries[i].next; + + ht->entries[i].next = ht->free_head; + ht->free_head = i; + return true; + } + } + return false; +} diff --git a/src/src/fuse_functions/access.cpp b/src/src/fuse_functions/access.cpp new file mode 100644 index 0000000..96837e9 --- /dev/null +++ b/src/src/fuse_functions/access.cpp @@ -0,0 +1,26 @@ +#include "fuse_functions/access.h" +#include "daemon/dir_manager.h" + +#include +#include + +/* + * Global DirManager instance + */ +extern DirManager* g_dir_manager; + +int fdfs_access(const char* path, int mask) +{ + (void) mask; // permissions not enforced yet + + // Check if path exists + int node = lookup_node(g_dir_manager, path); + if (node == -1) { + std::cout << "fdfs_access: returning -ENOENT" << std::endl; + return -ENOENT; + } + + // Allow all access for now + std::cout << "fdfs_access: returning 0" << std::endl; + return 0; +} diff --git a/src/src/fuse_functions/file_funcs.cpp b/src/src/fuse_functions/file_funcs.cpp new file mode 100644 index 0000000..07cc18c --- /dev/null +++ b/src/src/fuse_functions/file_funcs.cpp @@ -0,0 +1,205 @@ +#include "fuse_functions/file_funcs.h" +#include "daemon/dir_manager.h" +#include "daemon/file_io.h" + +#include +#include +#include +#include +#include + +extern DirManager* g_dir_manager; + +/* CREATE */ +int fdfs_create(const char *path, mode_t mode, struct fuse_file_info *fi) { + int node = insert_node(g_dir_manager, path); + if (node < 0) { + std::cout << "fdfs_create: returning -EEXIST" << std::endl; + return -EEXIST; + } + + pthread_rwlock_wrlock(&g_dir_manager->rwlock); + + DirNode *n = &g_dir_manager->nodes[node]; + n->mode = S_IFREG | (mode & 0777); + n->uid = getuid(); + n->gid = getgid(); + n->size = 0; + n->nlink = 1; + + time_t now = time(NULL); + n->atime = n->mtime = n->ctime = now; + + pthread_rwlock_unlock(&g_dir_manager->rwlock); + std::cout << "fdfs_create: returning 0" << std::endl; + return 0; +} + +/* OPEN */ +int fdfs_open(const char *path, struct fuse_file_info *fi) { + pthread_rwlock_rdlock(&g_dir_manager->rwlock); + + int node = lookup_node_nolock(g_dir_manager, path); + if (node < 0) { + pthread_rwlock_unlock(&g_dir_manager->rwlock); + std::cout << "fdfs_open: returning -ENOENT" << std::endl; + return -ENOENT; + } + + DirNode *n = &g_dir_manager->nodes[node]; + if (S_ISDIR(n->mode)) { + pthread_rwlock_unlock(&g_dir_manager->rwlock); + std::cout << "fdfs_open: returning -EISDIR" << std::endl; + return -EISDIR; + } + + pthread_rwlock_unlock(&g_dir_manager->rwlock); + std::cout << "fdfs_open: returning 0" << std::endl; + return 0; +} + +/* READ */ +int fdfs_read(const char *path, char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) { + pthread_rwlock_rdlock(&g_dir_manager->rwlock); + + int node = lookup_node_nolock(g_dir_manager, path); + if (node < 0) { + pthread_rwlock_unlock(&g_dir_manager->rwlock); + std::cout << "fdfs_read: returning -ENOENT" << std::endl; + return -ENOENT; + } + + DirNode *n = &g_dir_manager->nodes[node]; + if (offset >= n->size) { + pthread_rwlock_unlock(&g_dir_manager->rwlock); + std::cout << "fdfs_read: returning 0 (offset >= size)" << std::endl; + return 0; + } + + // Calculate how much data to read + size_t to_read = size; + if (offset + to_read > n->size) { + to_read = n->size - offset; + } + + pthread_rwlock_unlock(&g_dir_manager->rwlock); + + // Read data from host filesystem using inode number + ssize_t bytes_read = read_inode_data(node, buf, to_read, offset); + if (bytes_read < 0) { + std::cout << "fdfs_read: returning -EIO" << std::endl; + return -EIO; + } + + // Update access time + pthread_rwlock_wrlock(&g_dir_manager->rwlock); + n = &g_dir_manager->nodes[node]; + n->atime = time(NULL); + pthread_rwlock_unlock(&g_dir_manager->rwlock); + + std::cout << "fdfs_read: returning " << bytes_read << std::endl; + return bytes_read; +} + +/* WRITE */ +int fdfs_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) { + + // if (g_config.dedup_enabled) + // { + // // call dedup logic + // } + // else + // { + // // normal write_inode_data() + // } + + pthread_rwlock_wrlock(&g_dir_manager->rwlock); + std::cout << "fdfs_write: writing " << size << " bytes to " << path << " at offset " << offset << "data: "<rwlock); + std::cout << "fdfs_write: returning -ENOENT" << std::endl; + return -ENOENT; + } + + pthread_rwlock_unlock(&g_dir_manager->rwlock); + + // Write data to host filesystem using inode number + ssize_t bytes_written = write_inode_data(node, buf, size, offset); + if (bytes_written < 0) { + std::cout << "fdfs_write: returning -EIO" << std::endl; + return -EIO; + } + + // Update file size and modification time + pthread_rwlock_wrlock(&g_dir_manager->rwlock); + DirNode *n = &g_dir_manager->nodes[node]; + + off_t end = offset + size; + if (end > n->size) + n->size = end; + + n->mtime = n->ctime = time(NULL); + pthread_rwlock_unlock(&g_dir_manager->rwlock); + + std::cout << "fdfs_write: returning " << bytes_written << std::endl; + return bytes_written; +} + +/* TRUNCATE */ +int fdfs_truncate(const char *path, off_t size, + struct fuse_file_info *fi) { + pthread_rwlock_wrlock(&g_dir_manager->rwlock); + + int node = lookup_node_nolock(g_dir_manager, path); + if (node < 0) { + pthread_rwlock_unlock(&g_dir_manager->rwlock); + std::cout << "fdfs_truncate: returning -ENOENT" << std::endl; + return -ENOENT; + } + + pthread_rwlock_unlock(&g_dir_manager->rwlock); + + // Truncate the data file on host filesystem + if (truncate_inode_data(node, size) < 0) { + std::cout << "fdfs_truncate: returning -EIO" << std::endl; + return -EIO; + } + + // Update file size and modification time + pthread_rwlock_wrlock(&g_dir_manager->rwlock); + DirNode *n = &g_dir_manager->nodes[node]; + n->size = size; + n->mtime = n->ctime = time(NULL); + pthread_rwlock_unlock(&g_dir_manager->rwlock); + + std::cout << "fdfs_truncate: returning 0" << std::endl; + return 0; +} + +/* UNLINK */ +int fdfs_unlink(const char *path) { + pthread_rwlock_wrlock(&g_dir_manager->rwlock); + + int node = lookup_node_nolock(g_dir_manager, path); + if (node < 0) { + pthread_rwlock_unlock(&g_dir_manager->rwlock); + std::cout << "fdfs_unlink: returning -ENOENT" << std::endl; + return -ENOENT; + } + + pthread_rwlock_unlock(&g_dir_manager->rwlock); + + // Delete the data file from host filesystem + delete_inode_data(node); + + if (!remove_node(g_dir_manager, path)) { + std::cout << "fdfs_unlink: returning -ENOENT (remove failed)" << std::endl; + return -ENOENT; + } + + std::cout << "fdfs_unlink: returning 0" << std::endl; + return 0; +} \ No newline at end of file diff --git a/src/src/fuse_functions/getattr.cpp b/src/src/fuse_functions/getattr.cpp new file mode 100644 index 0000000..255d00f --- /dev/null +++ b/src/src/fuse_functions/getattr.cpp @@ -0,0 +1,49 @@ +#include "fuse_functions/getattr.h" +#include "daemon/dir_manager.h" + +#include +#include +#include +#include + +/* + * DirManager instance + * (defined in main.cpp and shared across FUSE callbacks) + */ +extern DirManager* g_dir_manager; + +int fdfs_getattr(const char* path, + struct stat* stbuf, + struct fuse_file_info* fi) +{ + (void) fi; + memset(stbuf, 0, sizeof(struct stat)); + + // Ask DirManager if path exists + pthread_rwlock_rdlock(&g_dir_manager->rwlock); + + int node = lookup_node_nolock(g_dir_manager, path); + if (node == -1) { + pthread_rwlock_unlock(&g_dir_manager->rwlock); + std::cout << "fdfs_getattr: returning -ENOENT" << std::endl; + return -ENOENT; + } + + // Get the actual node metadata + DirNode* n = &g_dir_manager->nodes[node]; + + // Fill stat structure with actual metadata from DirNode + stbuf->st_mode = n->mode; + stbuf->st_nlink = n->nlink; + stbuf->st_size = n->size; + stbuf->st_uid = n->uid; + stbuf->st_gid = n->gid; + stbuf->st_atime = n->atime; + stbuf->st_mtime = n->mtime; + stbuf->st_ctime = n->ctime; + + pthread_rwlock_unlock(&g_dir_manager->rwlock); + + std::cout << "fdfs_getattr: returning 0" << std::endl; + return 0; +} diff --git a/src/src/fuse_functions/mkdir.cpp b/src/src/fuse_functions/mkdir.cpp new file mode 100644 index 0000000..a57a7f5 --- /dev/null +++ b/src/src/fuse_functions/mkdir.cpp @@ -0,0 +1,49 @@ +#include "fuse_functions/mkdir.h" +#include "daemon/dir_manager.h" + +#include +#include +#include + +/* + * Global DirManager instance + * (defined in main.cpp) + */ +extern DirManager* g_dir_manager; + +int fdfs_mkdir(const char* path, mode_t mode) +{ + (void) mode; // permissions not handled yet + + // Disallow creating root + if (strcmp(path, "/") == 0) { + std::cout << "fdfs_mkdir: returning -EEXIST (root)" << std::endl; + return -EEXIST; + } + + /* + * insert_node returns: + * - node index on success + * - -1 on failure + */ + int node = insert_node(g_dir_manager, path); + if (node == -1) { + /* + * Possible reasons: + * - parent does not exist + * - directory already exists + * - no free nodes left + * + * We conservatively return EEXIST or ENOENT. + */ + if (lookup_node(g_dir_manager, path) != -1) { + std::cout << "fdfs_mkdir: returning -EEXIST" << std::endl; + return -EEXIST; + } + std::cout << "fdfs_mkdir: returning -ENOENT" << std::endl; + return -ENOENT; + } + + std::cout << "fdfs_mkdir: returning 0" << std::endl; + return 0; +} diff --git a/src/src/fuse_functions/opendir.cpp b/src/src/fuse_functions/opendir.cpp new file mode 100644 index 0000000..15bd2bf --- /dev/null +++ b/src/src/fuse_functions/opendir.cpp @@ -0,0 +1,28 @@ +#include "fuse_functions/opendir.h" +#include "daemon/dir_manager.h" + +#include +#include +#include + +/* + * Global DirManager instance + * (defined in main.cpp) + */ +extern DirManager* g_dir_manager; + +int fdfs_opendir(const char* path, + struct fuse_file_info* fi) +{ + (void) fi; + + // Ask ADT whether directory exists + int node = lookup_node(g_dir_manager, path); + if (node == -1) { + std::cout << "fdfs_opendir: returning -ENOENT" << std::endl; + return -ENOENT; + } + + std::cout << "fdfs_opendir: returning 0" << std::endl; + return 0; +} diff --git a/src/src/fuse_functions/readdir.cpp b/src/src/fuse_functions/readdir.cpp new file mode 100644 index 0000000..2eb786a --- /dev/null +++ b/src/src/fuse_functions/readdir.cpp @@ -0,0 +1,51 @@ +#include "fuse_functions/readdir.h" +#include "daemon/dir_manager.h" + +#include +#include +#include +/* + * Global DirManager instance + * (defined in main.cpp) + */ +extern DirManager* g_dir_manager; + +int fdfs_readdir(const char* path, + void* buf, + fuse_fill_dir_t filler, + off_t offset, + struct fuse_file_info* fi, + enum fuse_readdir_flags flags) +{ + (void) offset; + (void) fi; + (void) flags; + // Find directory node + int dir_node = lookup_node(g_dir_manager, path); + if (dir_node == -1) { + std::cout << "fdfs_readdir: returning -ENOENT" << std::endl; + return -ENOENT; + } + + // Every directory must contain "." and ".." + filler(buf, ".", nullptr, 0, static_cast(0)); + filler(buf, "..", nullptr, 0, static_cast(0)); + + // Iterate over children using sibling list + int child = (*g_dir_manager).nodes[dir_node].first_child; + while (child != -1) { + DirNode* n = &g_dir_manager->nodes[child]; + + if (n->in_use) { + filler(buf, n->name, nullptr, 0, + static_cast(0)); + } + + child = n->next_sibling; + // std::cout<<"I) am Here"; + // std::cout.flush(); + } + + std::cout << "fdfs_readdir: returning 0" << std::endl; + return 0; +} diff --git a/src/src/fuse_functions/rmdir.cpp b/src/src/fuse_functions/rmdir.cpp new file mode 100644 index 0000000..61e343e --- /dev/null +++ b/src/src/fuse_functions/rmdir.cpp @@ -0,0 +1,38 @@ +#include "fuse_functions/rmdir.h" +#include "daemon/dir_manager.h" + +#include +#include +#include + +/* + * Global DirManager instance + * (defined in main.cpp) + */ +extern DirManager* g_dir_manager; + +int fdfs_rmdir(const char* path) +{ + // Disallow removing root + if (strcmp(path, "/") == 0) { + std::cout << "fdfs_rmdir: returning -EBUSY (root)" << std::endl; + return -EBUSY; + } + + /* + * remove_node returns: + * - true → directory removed + * - false → failure (non-empty, not found, etc.) + */ + bool ok = remove_node(g_dir_manager, path); + if (!ok) { + // if (node == -1) { + // return -ENOENT; + // } commented bcz of dead locking in lookup_node + std::cout << "fdfs_rmdir: returning -ENOTEMPTY" << std::endl; + return -ENOTEMPTY; + } + + std::cout << "fdfs_rmdir: returning 0" << std::endl; + return 0; +} diff --git a/src/src/fuse_functions/statfs.cpp b/src/src/fuse_functions/statfs.cpp new file mode 100644 index 0000000..5448501 --- /dev/null +++ b/src/src/fuse_functions/statfs.cpp @@ -0,0 +1,30 @@ +#include "fuse_functions/statfs.h" + +#include +#include + +/* + * We return fake but consistent filesystem stats. + * This is normal for in-memory / virtual filesystems. + */ +int fdfs_statfs(const char* path, struct statvfs* stbuf) +{ + (void) path; + + memset(stbuf, 0, sizeof(struct statvfs)); + + stbuf->f_bsize = 4096; // block size + stbuf->f_frsize = 4096; // fragment size + + stbuf->f_blocks = 1024 * 1024; // total blocks (fake) + stbuf->f_bfree = 1024 * 1024; // free blocks + stbuf->f_bavail = 1024 * 1024; // available blocks + + stbuf->f_files = 100000; // total "inodes" + stbuf->f_ffree = 100000; // free "inodes" + + stbuf->f_namemax = 255; // max filename length + + std::cout << "fdfs_statfs: returning 0" << std::endl; + return 0; +} diff --git a/src/src/fuse_functions/utimens.cpp b/src/src/fuse_functions/utimens.cpp new file mode 100644 index 0000000..8d68478 --- /dev/null +++ b/src/src/fuse_functions/utimens.cpp @@ -0,0 +1,44 @@ +#include "fuse_functions/utimens.h" +#include "daemon/dir_manager.h" + +#include +#include +#include + +/* + * Global DirManager instance + */ +extern DirManager* g_dir_manager; + +int fdfs_utimens(const char* path, + const struct timespec tv[2], + struct fuse_file_info* fi) +{ + (void) fi; + + pthread_rwlock_wrlock(&g_dir_manager->rwlock); + + int node = lookup_node_nolock(g_dir_manager, path); + if (node < 0) { + pthread_rwlock_unlock(&g_dir_manager->rwlock); + std::cout << "fdfs_utimens: returning -ENOENT" << std::endl; + return -ENOENT; + } + + DirNode* n = &g_dir_manager->nodes[node]; + + // If tv is NULL, use current time + if (tv == nullptr) { + time_t now = time(NULL); + n->atime = now; + n->mtime = now; + } else { + // tv[0] is access time, tv[1] is modification time + n->atime = tv[0].tv_sec; + n->mtime = tv[1].tv_sec; + } + + pthread_rwlock_unlock(&g_dir_manager->rwlock); + std::cout << "fdfs_utimens: returning 0" << std::endl; + return 0; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..b122e84 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.10) + +find_package(GTest REQUIRED) + +add_executable(run_tests + test_dir_manager.cpp + test_hash_store.cpp + persistence_test.cpp + ../src/src/daemon/dir_manager.cpp + ../src/src/daemon/hash.cpp +) + +target_include_directories(run_tests PRIVATE + ../src/include +) + +target_link_libraries(run_tests + GTest::GTest + GTest::Main + pthread +) diff --git a/test/persistence_test.cpp b/test/persistence_test.cpp new file mode 100644 index 0000000..06c3bd8 --- /dev/null +++ b/test/persistence_test.cpp @@ -0,0 +1,127 @@ +#include + +#include +#include +#include +#include +#include + +#include "daemon/dir_manager.h" +#include "daemon/hash.h" + +/* + * System-level persistence tests. + * + * Verifies that DirManager and HashTable survive restart + * when backed by a file using mmap. + * + * NOTE: + * - pthread locks are NOT persistent and must be re-initialized + * - structural data (nodes, hash entries) must survive + */ + +static const char* PERSIST_FILE = "/tmp/fastdevfs_persist_test.dat"; + +/* --------------------------------------------------------- + * Helper: map a file of given size + * --------------------------------------------------------- */ +static void* map_file(const char* path, size_t size, int& fd) { + fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0600); + if (fd == -1) + return MAP_FAILED; + + if (ftruncate(fd, size) != 0) + return MAP_FAILED; + + return mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); +} + +/* --------------------------------------------------------- + * DirManager persistence + * --------------------------------------------------------- */ +TEST(PersistenceTest, DirManagerPersistsAcrossRestart) { + int fd; + void* addr = map_file(PERSIST_FILE, sizeof(DirManager), fd); + ASSERT_NE(addr, MAP_FAILED); + + DirManager* dm = static_cast(addr); + + // first-time initialization + dir_manager_init(dm); + + insert_node(dm, "/home"); + insert_node(dm, "/home/user"); + insert_node(dm, "/var"); + + // flush + unmap (simulate shutdown) + msync(addr, sizeof(DirManager), MS_SYNC); + munmap(addr, sizeof(DirManager)); + close(fd); + + // remap (simulate restart) + fd = open(PERSIST_FILE, O_RDWR, 0600); + ASSERT_NE(fd, -1); + + addr = mmap(nullptr, sizeof(DirManager), + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + ASSERT_NE(addr, MAP_FAILED); + + DirManager* dm_after = static_cast(addr); + + // IMPORTANT: reinitialize lock ONLY + pthread_rwlock_init(&dm_after->rwlock, nullptr); + + // verify persistence + EXPECT_NE(lookup_node(dm_after, "/home"), -1); + EXPECT_NE(lookup_node(dm_after, "/home/user"), -1); + EXPECT_NE(lookup_node(dm_after, "/var"), -1); + + munmap(addr, sizeof(DirManager)); + close(fd); + unlink(PERSIST_FILE); +} + +/* --------------------------------------------------------- + * HashTable persistence + * --------------------------------------------------------- */ +TEST(PersistenceTest, HashTablePersistsAcrossRestart) { + int fd; + void* addr = map_file(PERSIST_FILE, sizeof(HashTable), fd); + ASSERT_NE(addr, MAP_FAILED); + + HashTable* ht = static_cast(addr); + + hash_init(ht); + + const char* key1 = "alpha"; + const char* key2 = "beta"; + + uint64_t h1 = hash_path_poly(key1); + uint64_t h2 = hash_path_poly(key2); + + ASSERT_TRUE(hash_insert(ht, h1, key1, 11)); + ASSERT_TRUE(hash_insert(ht, h2, key2, 22)); + + // flush + unmap + msync(addr, sizeof(HashTable), MS_SYNC); + munmap(addr, sizeof(HashTable)); + close(fd); + + // remap + fd = open(PERSIST_FILE, O_RDWR, 0600); + ASSERT_NE(fd, -1); + + addr = mmap(nullptr, sizeof(HashTable), + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + ASSERT_NE(addr, MAP_FAILED); + + HashTable* ht_after = static_cast(addr); + + // verify persistence + EXPECT_EQ(hash_lookup(ht_after, h1, key1), 11); + EXPECT_EQ(hash_lookup(ht_after, h2, key2), 22); + + munmap(addr, sizeof(HashTable)); + close(fd); + unlink(PERSIST_FILE); +} diff --git a/test/test_dir_manager.cpp b/test/test_dir_manager.cpp new file mode 100644 index 0000000..76acd9e --- /dev/null +++ b/test/test_dir_manager.cpp @@ -0,0 +1,235 @@ +#include +#include +#include +#include + +#include "daemon/dir_manager.h" + +/* + * Tests for DirManager ADT. + * + * Design assumptions: + * - Paths are treated as literal strings + * - No canonicalization (/, //, trailing slash) + * - Hashing is full-path based + * - Concurrency is handled via RW locks + */ + +class DirManagerTest : public ::testing::Test { +protected: + DirManager dm; + + void SetUp() override { + dir_manager_init(&dm); + } +}; + +/* ---------- Basic behavior ---------- */ + +TEST_F(DirManagerTest, RootExists) { + EXPECT_EQ(lookup_node(&dm, "/"), 0); +} + +TEST_F(DirManagerTest, InsertAndLookupSingleDir) { + int node = insert_node(&dm, "/home"); + EXPECT_NE(node, -1); + EXPECT_EQ(lookup_node(&dm, "/home"), node); +} + +/* ---------- Nested paths ---------- */ + +TEST_F(DirManagerTest, InsertNestedDirectories) { + EXPECT_NE(insert_node(&dm, "/home"), -1); + EXPECT_NE(insert_node(&dm, "/home/user"), -1); + EXPECT_NE(insert_node(&dm, "/home/user/docs"), -1); + + EXPECT_NE(lookup_node(&dm, "/home/user/docs"), -1); +} + +TEST_F(DirManagerTest, InsertWithoutParentFails) { + // parent "/a" does not exist + EXPECT_EQ(insert_node(&dm, "/a/b"), -1); +} + +/* ---------- Duplicate & idempotency ---------- */ + +TEST_F(DirManagerTest, DuplicateInsertFails) { + EXPECT_NE(insert_node(&dm, "/tmp"), -1); + EXPECT_EQ(insert_node(&dm, "/tmp"), -1); +} + +TEST_F(DirManagerTest, LookupIsIdempotent) { + insert_node(&dm, "/x"); + int first = lookup_node(&dm, "/x"); + int second = lookup_node(&dm, "/x"); + EXPECT_EQ(first, second); +} + +/* ---------- Removal semantics ---------- */ + +TEST_F(DirManagerTest, RemoveEmptyDirectory) { + insert_node(&dm, "/var"); + EXPECT_TRUE(remove_node(&dm, "/var")); + EXPECT_EQ(lookup_node(&dm, "/var"), -1); +} + +TEST_F(DirManagerTest, RemoveNonEmptyDirectoryFails) { + insert_node(&dm, "/etc"); + insert_node(&dm, "/etc/conf"); + + EXPECT_FALSE(remove_node(&dm, "/etc")); + EXPECT_NE(lookup_node(&dm, "/etc"), -1); +} + +TEST_F(DirManagerTest, RemoveRootFails) { + EXPECT_FALSE(remove_node(&dm, "/")); +} + +/* ---------- Freelist reuse ---------- */ + +TEST_F(DirManagerTest, RemoveThenReinsert) { + insert_node(&dm, "/tmp"); + EXPECT_TRUE(remove_node(&dm, "/tmp")); + EXPECT_NE(insert_node(&dm, "/tmp"), -1); +} + +/* ---------- Sibling integrity ---------- */ + +TEST_F(DirManagerTest, MultipleChildrenSameParent) { + insert_node(&dm, "/p"); + insert_node(&dm, "/p/a"); + insert_node(&dm, "/p/b"); + insert_node(&dm, "/p/c"); + + EXPECT_NE(lookup_node(&dm, "/p/a"), -1); + EXPECT_NE(lookup_node(&dm, "/p/b"), -1); + EXPECT_NE(lookup_node(&dm, "/p/c"), -1); +} + +TEST_F(DirManagerTest, RemoveOneSiblingDoesNotAffectOthers) { + insert_node(&dm, "/p"); + insert_node(&dm, "/p/a"); + insert_node(&dm, "/p/b"); + + EXPECT_TRUE(remove_node(&dm, "/p/a")); + EXPECT_EQ(lookup_node(&dm, "/p/a"), -1); + EXPECT_NE(lookup_node(&dm, "/p/b"), -1); +} + +/* ---------- Path edge cases (literal semantics) ---------- */ + +TEST_F(DirManagerTest, TrailingSlashIsDifferentPath) { + insert_node(&dm, "/data"); + + EXPECT_NE(lookup_node(&dm, "/data"), -1); + EXPECT_EQ(lookup_node(&dm, "/data/"), -1); +} + +TEST_F(DirManagerTest, MultipleSlashesAreDifferentPath) { + insert_node(&dm, "/a"); + insert_node(&dm, "/a//b"); + + EXPECT_NE(lookup_node(&dm, "/a//b"), -1); + EXPECT_EQ(lookup_node(&dm, "/a/b"), -1); +} + +TEST_F(DirManagerTest, EmptyPathFails) { + EXPECT_EQ(insert_node(&dm, ""), -1); + EXPECT_EQ(lookup_node(&dm, ""), -1); +} + +/* ---------- Bounds & safety ---------- */ + +TEST_F(DirManagerTest, VeryLongNameHandledSafely) { + std::string longName(300, 'a'); + std::string path = "/" + longName; + + int node = insert_node(&dm, path.c_str()); + EXPECT_NE(node, -1); + EXPECT_NE(lookup_node(&dm, path.c_str()), -1); +} + +/* ---------- Transaction behavior ---------- */ + +TEST_F(DirManagerTest, FailedInsertIsAtomic) { + insert_node(&dm, "/a"); + + EXPECT_EQ(insert_node(&dm, "/a/b/c"), -1); + + EXPECT_NE(lookup_node(&dm, "/a"), -1); + EXPECT_EQ(lookup_node(&dm, "/a/b"), -1); +} + +/* ---------- Stress test ---------- */ + +TEST_F(DirManagerTest, ManyInsertsAndLookups) { + const int N = 1000; + std::vector paths; + + for (int i = 0; i < N; i++) { + paths.push_back("/dir" + std::to_string(i)); + } + + for (int i = 0; i < N; i++) { + EXPECT_NE(insert_node(&dm, paths[i].c_str()), -1); + } + + for (int i = 0; i < N; i++) { + EXPECT_NE(lookup_node(&dm, paths[i].c_str()), -1); + } +} + +/* ---------- Concurrency ---------- */ + +TEST_F(DirManagerTest, ConcurrentReadersDoNotBlock) { + insert_node(&dm, "/shared"); + + auto reader = [&]() { + for (int i = 0; i < 1000; i++) { + EXPECT_NE(lookup_node(&dm, "/shared"), -1); + } + }; + + std::vector threads; + for (int i = 0; i < 4; i++) + threads.emplace_back(reader); + + for (auto& t : threads) + t.join(); +} + +TEST_F(DirManagerTest, ReaderWriterInterleavingSafe) { + insert_node(&dm, "/x"); + + auto reader = [&]() { + for (int i = 0; i < 1000; i++) + lookup_node(&dm, "/x"); + }; + + auto writer = [&]() { + insert_node(&dm, "/y"); + }; + + std::thread t1(reader); + std::thread t2(writer); + + t1.join(); + t2.join(); + + EXPECT_NE(lookup_node(&dm, "/y"), -1); +} + +TEST_F(DirManagerTest, ConcurrentWritersSafe) { + auto writer = [&](const char* p) { + insert_node(&dm, p); + }; + + std::thread t1(writer, "/a"); + std::thread t2(writer, "/b"); + + t1.join(); + t2.join(); + + EXPECT_NE(lookup_node(&dm, "/a"), -1); + EXPECT_NE(lookup_node(&dm, "/b"), -1); +} diff --git a/test/test_hash_store.cpp b/test/test_hash_store.cpp new file mode 100644 index 0000000..237a36c --- /dev/null +++ b/test/test_hash_store.cpp @@ -0,0 +1,192 @@ +#include +#include +#include + +#include +#include +#include + +#include "daemon/hash.h" + +/* + * Tests for static hash table. + * Hash is treated as an index; key is the identity. + * Collisions are resolved by chaining + key comparison. + */ + +class HashStoreTest : public ::testing::Test { +protected: + HashTable ht; + + void SetUp() override { + hash_init(&ht); + } +}; + +/* ---------- Basic behavior ---------- */ + +TEST_F(HashStoreTest, InsertLookupSingle) { + const char* key = "file"; + uint64_t h = hash_path_poly(key); + + EXPECT_TRUE(hash_insert(&ht, h, key, 10)); + EXPECT_EQ(hash_lookup(&ht, h, key), 10); +} + +TEST_F(HashStoreTest, LookupNonExistingReturnsMinusOne) { + const char* key = "ghost"; + uint64_t h = hash_path_poly(key); + + EXPECT_EQ(hash_lookup(&ht, h, key), -1); +} + +/* ---------- Duplicate handling ---------- */ + +TEST_F(HashStoreTest, DuplicateInsertAllowedOrRejectedButSafe) { + const char* key = "dup"; + uint64_t h = hash_path_poly(key); + + EXPECT_TRUE(hash_insert(&ht, h, key, 1)); + + bool second = hash_insert(&ht, h, key, 2); + + if (second) { + int v = hash_lookup(&ht, h, key); + EXPECT_TRUE(v == 1 || v == 2); + } else { + EXPECT_EQ(hash_lookup(&ht, h, key), 1); + } +} + +/* ---------- Remove semantics ---------- */ + +TEST_F(HashStoreTest, RemoveExistingEntry) { + const char* key = "tmp"; + uint64_t h = hash_path_poly(key); + + EXPECT_TRUE(hash_insert(&ht, h, key, 5)); + EXPECT_TRUE(hash_remove(&ht, h, key)); + EXPECT_EQ(hash_lookup(&ht, h, key), -1); +} + +TEST_F(HashStoreTest, RemoveNonExistingFailsGracefully) { + const char* key = "missing"; + uint64_t h = hash_path_poly(key); + + EXPECT_FALSE(hash_remove(&ht, h, key)); +} + +/* ---------- Freelist reuse ---------- */ + +TEST_F(HashStoreTest, RemoveThenReinsertUsesSlotSafely) { + const char* key1 = "a"; + const char* key2 = "b"; + + uint64_t h1 = hash_path_poly(key1); + uint64_t h2 = hash_path_poly(key2); + + EXPECT_TRUE(hash_insert(&ht, h1, key1, 1)); + EXPECT_TRUE(hash_remove(&ht, h1, key1)); + EXPECT_TRUE(hash_insert(&ht, h2, key2, 2)); + + EXPECT_EQ(hash_lookup(&ht, h1, key1), -1); + EXPECT_EQ(hash_lookup(&ht, h2, key2), 2); +} + +/* ---------- Collision behavior ---------- */ + +TEST_F(HashStoreTest, HandlesBucketCollisionsCorrectly) { + std::vector keys; + std::vector hashes; + + // force same bucket, different keys + for (int i = 0; i < 50; i++) { + keys.push_back("key_" + std::to_string(i)); + hashes.push_back(hash_path_poly(keys[i].c_str())); + } + + for (int i = 0; i < 50; i++) { + EXPECT_TRUE(hash_insert(&ht, hashes[i], keys[i].c_str(), i)); + } + + for (int i = 0; i < 50; i++) { + EXPECT_EQ(hash_lookup(&ht, hashes[i], keys[i].c_str()), i); + } +} + +/* ---------- Hash properties ---------- */ + +TEST_F(HashStoreTest, HashIsOrderSensitive) { + EXPECT_NE(hash_path_poly("a/b"), hash_path_poly("b/a")); +} + +TEST_F(HashStoreTest, HashSeparatesComponents) { + EXPECT_NE(hash_path_poly("ab"), hash_path_poly("a/b")); +} + +/* ---------- Stress ---------- */ + +TEST_F(HashStoreTest, ManyInsertsAndLookups) { + const int N = 2000; + std::vector keys; + std::vector hashes; + + for (int i = 0; i < N; i++) { + keys.push_back("key_" + std::to_string(i)); + hashes.push_back(hash_path_poly(keys[i].c_str())); + } + + int inserted = 0; + for (int i = 0; i < N; i++) { + if (hash_insert(&ht, hashes[i], keys[i].c_str(), i)) + inserted++; + } + + EXPECT_GT(inserted, N * 0.9); + + for (int i = 0; i < N; i++) { + int v = hash_lookup(&ht, hashes[i], keys[i].c_str()); + if (v != -1) + EXPECT_EQ(v, i); + } +} + +/* ---------- Capacity limits ---------- */ + +TEST_F(HashStoreTest, TableEventuallyFillsUp) { + int success = 0; + + for (int i = 0; i < MAX_ENTRIES + 10; i++) { + std::string key = "k" + std::to_string(i); + uint64_t h = hash_path_poly(key.c_str()); + + if (hash_insert(&ht, h, key.c_str(), i)) + success++; + } + + EXPECT_EQ(success, MAX_ENTRIES); +} + +/* ---------- Performance (non-failing benchmark) ---------- */ + +TEST_F(HashStoreTest, HashPathPerformance) { + const int ITERATIONS = 100; + const char* key = "very_long_directory_name_for_benchmarking"; + + uint64_t h = 0; + auto start = std::chrono::steady_clock::now(); + + for (int i = 0; i < ITERATIONS; i++) { + h ^= hash_path_poly(key); + } + + auto end = std::chrono::steady_clock::now(); + std::chrono::duration elapsed = end - start; + + std::cout << "\n[ HASH BENCHMARK ]\n"; + std::cout << "Avg time per hash (µs): " + << elapsed.count() / ITERATIONS << "\n"; + std::cout << "Final hash (prevent opt): " << h << "\n"; + + SUCCEED(); +}