diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8cf8b5e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "googletest"] + path = googletest + url = https://github.com/google/googletest.git diff --git a/CMakeLists.txt b/CMakeLists.txt index d438466..937a92a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,26 +1,30 @@ -cmake_minimum_required (VERSION 3.1.0) -project(fischl) - -set(CMAKE_CXX_STANDARD 14) - -include_directories( - # fischl include files - ${CMAKE_CURRENT_SOURCE_DIR}/include -) - -add_executable(fischl - - lib/fischl.cpp - lib/main.cpp - lib/rawdisk.cpp - lib/fs/datablock_manager.cpp - lib/fs/fs_data_types.cpp - lib/fs/fs_resize.cpp - lib/fs/fs_read_write.cpp - lib/fs/fs.cpp - lib/fs/inode_manager.cpp - -) - -enable_testing() -add_subdirectory(test) \ No newline at end of file +cmake_minimum_required (VERSION 3.1.0) +project(fischl) + +set(CMAKE_CXX_STANDARD 14) + +include_directories( + # fischl include files + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/googletest/googletest/include +) + +add_executable(fischl + + lib/fischl.cpp + lib/main.cpp + lib/rawdisk.cpp + lib/fs/datablock_manager.cpp + lib/fs/fs_data_types.cpp + lib/fs/fs_resize.cpp + lib/fs/fs_read_write.cpp + lib/fs/fs.cpp + lib/fs/inode_manager.cpp + lib/files.cpp + lib/direntry.cpp + +) + +enable_testing() +add_subdirectory(test) +add_subdirectory(googletest) \ No newline at end of file diff --git a/googletest b/googletest new file mode 160000 index 0000000..b10fad3 --- /dev/null +++ b/googletest @@ -0,0 +1 @@ +Subproject commit b10fad38c4026a29ea6561ab15fc4818170d1c10 diff --git a/include/direntry.h b/include/direntry.h new file mode 100644 index 0000000..7177f0e --- /dev/null +++ b/include/direntry.h @@ -0,0 +1,36 @@ +typedef struct fileNode { + char *name = NULL; + int inode_number; + int permissions; + char *Symbolink; + struct treeNode *subdirectory; + struct fileNode *next; +} FileNode; + +typedef struct { + int size; + FileNode **table; +} HashTable; + +typedef struct treeNode { + char *dirName; + HashTable *contents; + struct treeNode *parent; + FileNode *self_info; //self fileNode infromation +} TreeNode; + +/*for root*/ +TreeNode *fischl_init_entry(int new_inode_number, const char *fileName, INode_Data *new_inode); +/*the to be added file in add_entry should be parent-child relationship with treenode, otherwise will wrong */ +/*see Add_FindFiletest in dir_API.cpp*/ +int fischl_add_entry(TreeNode *parent, int new_inode_number, const char *fileName, INode_Data *new_inode); +int fischl_rm_entry(TreeNode *parent, const char *fileName); +/*if want to use dir mode use the subdirectory treeNode pointer */ +//e.g. FileNode *Dirnode = fischl_find_entry(); can see file inside with Dirnode->subdirectory +//e.g. go to the current Dirnode parent directory, use TreeNode *get_Dir_parent = Dirnode->subdirectory->parent; +FileNode *fischl_find_entry(TreeNode *root, const char *path); + +void freeTree(TreeNode *node); +/*for debug use*/ +TreeNode *createDirectory(const char *dirName, TreeNode *parent, int hashSize); +TreeNode *find_parentPath(TreeNode *root, const char *path); \ No newline at end of file diff --git a/include/files.h b/include/files.h new file mode 100644 index 0000000..9035eec --- /dev/null +++ b/include/files.h @@ -0,0 +1,32 @@ +#include +#include +#include "fuse_common.h" +#include "direntry.h" + +class FilesOperation { + RawDisk& disk; + Fs *fs; + void create_dot_dotdot(INode_Data*, u_int64_t); + + public: + TreeNode *root_node; + FilesOperation(RawDisk&, Fs*); + //int read_datablock(const INode_Data& inode, u_int64_t index, char* buffer); + //int write_datablock(INode_Data& inode, u_int64_t index, char* buffer); + void initialize_rootinode(); + void printbuffer(const char*,int); + void printDirectory(u_int64_t); + INode_Data* create_new_inode(u_int64_t parent_inode_number, const char* name, mode_t mode); + void unlink_inode(u_int64_t inode_number); + u_int64_t disk_namei(const char* path); + u_int64_t namei(const char* path); + int fischl_mkdir(const char*, mode_t); + int fischl_mknod(const char*, mode_t, dev_t);//for special file + int fischl_create(const char *, mode_t, struct fuse_file_info *);//for regular file + //int fischl_readdir(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags); + int fischl_unlink (const char *); + int fischl_open (const char *, struct fuse_file_info *);//open file + int fischl_release (const char *, struct fuse_file_info *);//close file + int fischl_write(const char *, const char *, size_t, off_t, struct fuse_file_info *); + int fischl_read(const char *, char *, size_t, off_t, struct fuse_file_info *); +}; \ No newline at end of file diff --git a/include/fs/fs_data_types.hpp b/include/fs/fs_data_types.hpp index cb64dc1..134db85 100644 --- a/include/fs/fs_data_types.hpp +++ b/include/fs/fs_data_types.hpp @@ -43,7 +43,7 @@ public: u_int64_t single_indirect_block, double_indirect_block, triple_indirect_block; u_int64_t direct_blocks[NUMBER_OF_DIRECT_BLOCKS]; - INode_Data(u_int64_t inode_num = 0xFFFFFFFFFFFFFFFF); + INode_Data(u_int64_t inode_num = (u_int64_t)(0xFFFFFFFFFFFFFFFF)); void serialize(char buf[]); void deserialize(char buf[]); }; diff --git a/include/fuse_common.h b/include/fuse_common.h new file mode 100644 index 0000000..eef2f49 --- /dev/null +++ b/include/fuse_common.h @@ -0,0 +1,82 @@ +#ifndef FUSE_COMMON_H_ +#define FUSE_COMMON_H_ + +#include +#include + +/** + * Information about an open file. + * + * File Handles are created by the open, opendir, and create methods and closed + * by the release and releasedir methods. Multiple file handles may be + * concurrently open for the same file. Generally, a client will create one + * file handle per file descriptor, though in some cases multiple file + * descriptors can share a single file handle. + */ +struct fuse_file_info { + /** Open flags. Available in open() and release() */ + int flags; + + /** In case of a write operation indicates if this was caused + by a delayed write from the page cache. If so, then the + context's pid, uid, and gid fields will not be valid, and + the *fh* value may not match the *fh* value that would + have been sent with the corresponding individual write + requests if write caching had been disabled. */ + unsigned int writepage : 1; + + /** Can be filled in by open/create, to use direct I/O on this file. */ + unsigned int direct_io : 1; + + /** Can be filled in by open and opendir. It signals the kernel that any + currently cached data (ie., data that the filesystem provided the + last time the file/directory was open) need not be invalidated when + the file/directory is closed. */ + unsigned int keep_cache : 1; + + /** Can be filled by open/create, to allow parallel direct writes on this + * file */ + unsigned int parallel_direct_writes : 1; + + /** Indicates a flush operation. Set in flush operation, also + maybe set in highlevel lock operation and lowlevel release + operation. */ + unsigned int flush : 1; + + /** Can be filled in by open, to indicate that the file is not + seekable. */ + unsigned int nonseekable : 1; + + /* Indicates that flock locks for this file should be + released. If set, lock_owner shall contain a valid value. + May only be set in ->release(). */ + unsigned int flock_release : 1; + + /** Can be filled in by opendir. It signals the kernel to + enable caching of entries returned by readdir(). Has no + effect when set in other contexts (in particular it does + nothing when set by open()). */ + unsigned int cache_readdir : 1; + + /** Can be filled in by open, to indicate that flush is not needed + on close. */ + unsigned int noflush : 1; + + /** Padding. Reserved for future use*/ + unsigned int padding : 23; + unsigned int padding2 : 32; + + /** File handle id. May be filled in by filesystem in create, + * open, and opendir(). Available in most other file operations on the + * same file handle. */ + uint64_t fh; + + /** Lock owner id. Available in locking operations and flush */ + uint64_t lock_owner; + + /** Requested poll events. Available in ->poll. Only set on kernels + which support it. If unsupported, this field is set to zero. */ + uint32_t poll_events; +}; + +#endif /* FUSE_COMMON_H_ */ diff --git a/lib/direntry.cpp b/lib/direntry.cpp new file mode 100644 index 0000000..2366dde --- /dev/null +++ b/lib/direntry.cpp @@ -0,0 +1,235 @@ +#include +#include +#include +#include +#include +#include "fs.hpp" +#include "direntry.h" +/*********************************Hash operation******************************************** + +********************************************************************************************/ +// Hash operation +unsigned int hash(HashTable *h, char *key) { + unsigned int hashval = 0; + for (; *key != '\0'; key++) hashval = *key + (hashval << 5) - hashval; + return hashval % h->size; +} + +HashTable *createHashTable(int size) { + HashTable *newTable = (HashTable *)malloc(sizeof(HashTable)); + newTable->size = size; + newTable->table = (FileNode **)malloc(sizeof(FileNode *) * size); + for (int i = 0; i < size; i++) newTable->table[i] = NULL; + return newTable; +} + +FileNode *insertHash(HashTable *h, char *key, TreeNode *subdirectory) { + unsigned int hashval = hash(h, key); + FileNode *newNode = (FileNode *)malloc(sizeof(FileNode)); + newNode->name = strdup(key); + newNode->subdirectory = subdirectory; + newNode->next = h->table[hashval]; + h->table[hashval] = newNode; + return newNode; +} + +FileNode *lookupHash(HashTable *h, char *key) { + unsigned int hashval = hash(h, key); + FileNode *node = h->table[hashval]; + while (node != NULL) { + if (strcmp(node->name, key) == 0) return node; + node = node->next; + } + return NULL; // Not found +} + +bool removeHash(HashTable *h, char *key) { + unsigned int hashval = hash(h, key); + FileNode *node = h->table[hashval]; + if (node == NULL) return false; + if (strcmp(node->name, key) == 0) { + h->table[hashval] = node->next; + return true; + } + FileNode *prev = NULL; + bool foundit = false; + while (node != NULL) { + if (strcmp(node->name, key) == 0) break; + prev = node; + node = node->next; + } + if (node == NULL) { + return false; + } else { + prev->next = node->next; + return true; + } +} + +TreeNode *createDirectory(const char *dirName, TreeNode *parent, int hashSize) { + TreeNode *newDir = (TreeNode *)malloc(sizeof(TreeNode)); + newDir->dirName = strdup(dirName); + newDir->contents = createHashTable(hashSize); + newDir->parent = parent; + if (parent) { + newDir->self_info = insertHash(parent->contents, newDir->dirName, newDir); + } + return newDir; +} + +TreeNode *find_parentPath(TreeNode *root, const char *path) { + char *pathCopy = strdup(path); + char *segment = strtok(pathCopy, "/"); + TreeNode *current = root; + FileNode *file = NULL; + + while (segment != NULL && current != NULL) { + file = lookupHash(current->contents, segment); + if (file != NULL && file->subdirectory == NULL) { + free(pathCopy); + //printf("status current directory %s\n",current->dirName); + return current; //File found + } + current = file ? file->subdirectory : NULL; + segment = strtok(NULL, "/"); + } + + free(pathCopy); + return current; // NULL if not found +} + +void freeHashTable(HashTable *table) { + if (table == NULL) return; + + free(table->table); + free(table); +} + +void freeTree(TreeNode *node) { + //printf("***********************FREE TREE %s**************************\n",node->dirName); + //printf("***********************FREE TREE **************************\n"); + if (node == NULL) return; + + if (node->contents != NULL) { + for (int i = 0; i < node->contents->size; ++i) { + FileNode *current = node->contents->table[i]; + while (current != NULL) { + FileNode *temp = current; + current = current->next; + + if (temp->subdirectory != NULL) { + freeTree(temp->subdirectory); + } + // Free the FileNode if it's not a directory + // printf("free who %s\n",temp->name); + free(temp->name); + free(temp); + } + } + //printf("free %s's hash table\n",node->dirName); + freeHashTable(node->contents); + //node->contents = NULL; + } + //printf("free directory %s\n",node->dirName); + free(node->dirName); + node->dirName = NULL; + free(node); + node = NULL; + //printf("***********************END**************************\n"); +} + +/*********************************Direntry operation****************************************** + +********************************************************************************************/ +//for fake root (mount point) +TreeNode *fischl_init_entry(int new_inode_number, const char *fileName, INode_Data *new_inode) { + TreeNode *newDir = (TreeNode *)malloc(sizeof(TreeNode)); + newDir->dirName = strdup(fileName); + newDir->contents = createHashTable(20);//hashSize define 20 + newDir->parent = newDir; + FileNode *newFile = (FileNode *)malloc(sizeof(FileNode)); + newFile->name = strdup(fileName); + newFile->inode_number = new_inode_number; + newFile->permissions = new_inode->metadata.permissions; + newFile->subdirectory = newDir; + newDir->self_info = newFile; + return newDir; +} + +int fischl_add_entry(TreeNode *parent, int new_inode_number, const char *fileName, INode_Data *new_inode){ + char *Name = strdup(fileName); + TreeNode *newDir = NULL; + /*If directory, malloc TreeNode, and then create filenode that belongs to Parent hash table content*/ + if ((new_inode->metadata.permissions & S_IFMT) == S_IFDIR) { + newDir = (TreeNode *)malloc(sizeof(TreeNode)); + newDir->dirName = Name; + newDir->contents = createHashTable(20);//hasSize define 20 + newDir->parent = parent; + } + FileNode *newFile = insertHash(parent->contents, Name, newDir); //newDir == NULL indicates it's a file + //assign INode *new_inode metadata to data member in FileNode structure + newFile->permissions = new_inode->metadata.permissions; + newFile->inode_number = new_inode_number; + //Diretory have its own file information, that is . here + if(newDir != NULL) + newDir->self_info = newFile; + //free(Name); cannot free name + return 0; +} + +int fischl_rm_entry(TreeNode *parent, const char *fileName) { + char *fileName_dup = strdup(fileName); + if (parent->contents == NULL) return -1; + FileNode *file = NULL; + file = lookupHash(parent->contents, fileName_dup); + if (file == NULL) return -1; + if (file->subdirectory != NULL) freeTree(file->subdirectory); + removeHash(parent->contents, fileName_dup); + free(file->name); + free(file); + delete fileName_dup; +} + + +FileNode *fischl_find_entry(TreeNode *root, const char *path){ + //support . and .. function + char *pathCopy = strdup(path); + char *segment = strtok(pathCopy, "/"); + TreeNode *current = root; + FileNode *file = NULL; + + while (segment != NULL && current != NULL) { + if (strcmp(segment, "..") == 0) { + // Move up to the parent directory + current = current->parent; + if (current == NULL) { + // If there's no parent, we've reached the top of the tree, but root itself is same + file = NULL; + break; + } else { + file = current->self_info; + } + } else if (strcmp(segment, ".") == 0) { + // Stay in the current directory (no action needed) + } + else{ + file = lookupHash(current->contents, segment); + if (file != NULL && file->subdirectory == NULL) { + free(pathCopy); + return file; //File found + //return current; return filenode + } + current = file ? file->subdirectory : NULL; + } + segment = strtok(NULL, "/"); + } + + free(pathCopy); + + if (current != NULL && file == NULL) { + // If we've stopped at a directory and not a file, return the directory's self info + return current->self_info; + } + + return file; // NULL if not found +} \ No newline at end of file diff --git a/lib/files.cpp b/lib/files.cpp new file mode 100644 index 0000000..6a7e07b --- /dev/null +++ b/lib/files.cpp @@ -0,0 +1,468 @@ +//#include "fuse.h" add this when layer3 +#include "files.h" +#include +#include +#include + +struct DirectoryEntry { + u_int64_t inode_number; + char file_name[256]; + void serialize(char* buffer) { + u_int64_t t = inode_number; + for (int j = 0; j < 8; j++){ + buffer[j] = t & (((u_int64_t)1<<(8))-1); + t >>= 8; + } + strcpy(buffer+8, file_name); + } + void deserialize(char* buffer) { + inode_number = 0; + for (int j = 0; j < 8; j++) + inode_number = inode_number | (((u_int64_t)(unsigned char)buffer[j])<<(8*j)); + strcpy(file_name, buffer+8); + } +}; + +void FilesOperation::printbuffer(const char* buff, int len) { + for(int i=0;ifs = fs; +} + +void FilesOperation::create_dot_dotdot(INode_Data* inode, u_int64_t parent_inode_number) { + char buffer[IO_BLOCK_SIZE] = {0}; + DirectoryEntry dot; + dot.inode_number = inode->inode_num; + strcpy(dot.file_name, "."); + dot.serialize(buffer); + DirectoryEntry dotdot; + dotdot.inode_number = parent_inode_number; + strcpy(dotdot.file_name, ".."); + dotdot.serialize(buffer+264); + int ret = fs->write(inode, buffer, IO_BLOCK_SIZE, 0); + //printf("in create_dot_dotdot: fs->write returned %d\n",ret); +} + +void FilesOperation::initialize_rootinode() { + // this method must be called explicitly right after initializion + INode_Data *root_inode = new INode_Data(); + fs->inode_manager->new_inode(0, 0, S_IFDIR, root_inode); + u_int64_t root_inode_number = root_inode->inode_num; + create_dot_dotdot(root_inode, root_inode_number); + root_node = fischl_init_entry(root_inode_number, "/", root_inode); + assert(root_node->self_info!=NULL); + fs->inode_manager->save_inode(root_inode); +} + +void FilesOperation::printDirectory(u_int64_t inode_number) { + INode_Data inode; + inode.inode_num = inode_number; + fs->inode_manager->load_inode(&inode); + char buffer[IO_BLOCK_SIZE] = {0}; + for (u_int64_t idx=0; idxread(&inode, buffer, IO_BLOCK_SIZE, idx*IO_BLOCK_SIZE); + DirectoryEntry ent; + for(int i=0;i<=IO_BLOCK_SIZE-264;i+=264){ + ent.deserialize(buffer+i); + if (ent.inode_number) printf("%s\t%llu;\t", ent.file_name, ent.inode_number); + } + } + printf("\n"); +} + +INode_Data* FilesOperation::create_new_inode(u_int64_t parent_inode_number, const char* name, mode_t mode) { + // trys to create a file under parent directory + if (strlen(name)>=256) { + perror("Name too long, cannot create file or directory"); + return NULL; + } + INode_Data inode; + inode.inode_num = parent_inode_number; + fs->inode_manager->load_inode(&inode); + if ((inode.metadata.permissions & S_IFMT) != S_IFDIR) { + fprintf(stderr,"[%s ,%d] please create under directory\n",__func__,__LINE__); + return NULL; + } + + // Check if file or directory already exists + char r_buffer[IO_BLOCK_SIZE] = {0}; + for (u_int64_t idx=0; idxread(&inode, r_buffer, IO_BLOCK_SIZE, idx*IO_BLOCK_SIZE); + DirectoryEntry ent; + for(int i=0;i<=IO_BLOCK_SIZE-264;i+=264){ + ent.deserialize(r_buffer+i); + if (strcmp(ent.file_name, name)==0) { + if((mode & S_IFMT) == S_IFDIR){ + fprintf(stderr,"[%s ,%d] %s/ already exists\n",__func__,__LINE__, name); + }else{ + fprintf(stderr,"[%s ,%d] %s already exists\n",__func__,__LINE__, name); + } + return NULL; + } + } + } + + bool allocated = false; + INode_Data *new_inode = new INode_Data(); + fs->inode_manager->new_inode(0, 0, mode, new_inode); + if ((mode & S_IFMT) == S_IFDIR) { + create_dot_dotdot(new_inode, parent_inode_number); + fs->inode_manager->save_inode(new_inode); + } + + char rw_buffer[IO_BLOCK_SIZE] = {0}; + for (u_int64_t idx=0; idxread(&inode, rw_buffer, IO_BLOCK_SIZE, idx*IO_BLOCK_SIZE); + DirectoryEntry ent; + for(int i=0;i<=IO_BLOCK_SIZE-264;i+=264){ + ent.deserialize(rw_buffer+i); + if (ent.inode_number == 0) { + allocated = true; + ent.inode_number = new_inode->inode_num; + strcpy(ent.file_name, name); + ent.serialize(rw_buffer+i); + break; + } + } + if (allocated) { + fs->write(&inode, rw_buffer, IO_BLOCK_SIZE, idx*IO_BLOCK_SIZE); + break; + } + } + + if (!allocated) { + char write_buffer[IO_BLOCK_SIZE] = {0}; + DirectoryEntry ent; + ent.inode_number = new_inode->inode_num; + strcpy(ent.file_name, name); + ent.serialize(write_buffer); + fs->write(&inode, write_buffer, IO_BLOCK_SIZE, (inode.metadata.size/IO_BLOCK_SIZE)*IO_BLOCK_SIZE); + fs->inode_manager->save_inode(&inode); + } + + return new_inode; +} + +u_int64_t FilesOperation::disk_namei(const char* path) { + // returns the inode number corresponding to path + u_int64_t current_inode = root_node->self_info->inode_number; + std::string current_dirname; + std::istringstream pathStream(path); + std::string new_name; + std::getline(pathStream, new_name, '/'); + if(!new_name.empty()){ + printf("disk_namei: path should start with /\n"); + return -1; + } + while (std::getline(pathStream, new_name, '/')) { + INode_Data inode; + inode.inode_num = current_inode; + fs->inode_manager->load_inode(&inode); + if ((inode.metadata.permissions & S_IFMT) != S_IFDIR || inode.metadata.size == 0) { + printf("disk_namei: %s is not a non-empty directory\n", current_dirname.c_str()); + return -1; + } + u_int64_t new_inode_number = 0; + + char buffer[IO_BLOCK_SIZE] = {0}; + for(u_int64_t idx=0; idxread(&inode, buffer, IO_BLOCK_SIZE, idx*IO_BLOCK_SIZE); + DirectoryEntry ent; + for(int i=0;i<=IO_BLOCK_SIZE-264;i+=264){ + ent.deserialize(buffer+i); + if (strcmp(ent.file_name, new_name.c_str()) == 0) { + new_inode_number = ent.inode_number; + break; + } + } + if (new_inode_number) break; + } + if (!new_inode_number) { + printf("disk_namei: no name matching %s under directory %s\n", new_name.c_str(), current_dirname.c_str()); + return -1; + } + current_inode = new_inode_number; + current_dirname = new_name; + } + return current_inode; + // path = "/" should return root_inode_number (root_node->self_info->inode_number) + // path = "/foo.txt" should return inode for foo.txt + // path = "/mydir" should return inode for mydir + // path = "/nonemptydir/foo" should return inode for foo + // path = "/notnonemptydir/foo" should raise error +} + +u_int64_t FilesOperation::namei(const char* path) { + FileNode* filenode = fischl_find_entry(root_node, path); + if (filenode) return filenode->inode_number; + else return -1; +} + +int FilesOperation::fischl_mkdir(const char* path, mode_t mode) { + //check path + char *pathdup = strdup(path); + char *lastSlash = strrchr(pathdup, '/'); + *lastSlash = '\0'; // Split the string into parent path and new directory name; \0 + char *newDirname = lastSlash+1; //\0, get from + char *ParentPath = pathdup;//pathdup are separated by pathdup, so it take only + + FileNode *parent_filenode = strlen(ParentPath)? fischl_find_entry(root_node, ParentPath): root_node->self_info; + if (parent_filenode == NULL) { + fprintf(stderr,"[%s ,%d] ParentPath:{%s} not found\n",__func__,__LINE__, ParentPath); + free(pathdup); + return -ENOENT;//parentpath directory does not exist + } + u_int64_t parent_inode_number = parent_filenode->inode_number; + //printf("%s, %llu, %s\n", parent_filenode->name, parent_inode_number, newDirname); + //make new inode + INode_Data* ret = create_new_inode(parent_inode_number, newDirname, mode|S_IFDIR);//specify S_IFDIR as directory + if (ret == NULL) return -1;//ENOSPC but create_new_inode handle ENAMETOOLONG EEXIST + fischl_add_entry(parent_filenode->subdirectory, ret->inode_num, newDirname, ret); + free(pathdup); + return 0;//SUCCESS +} +/* + special file +*/ +int FilesOperation::fischl_mknod(const char* path, mode_t mode, dev_t dev) { + //check path + char *pathdup = strdup(path); + char *lastSlash = strrchr(pathdup, '/'); + *lastSlash = '\0'; // Split the string into parent path and new directory name; \0 + char *newFilename = lastSlash+1; //\0, get from + char *ParentPath = pathdup;//pathdup are separated by pathdup, so it take only + // fprintf(stderr,"[%s ,%d] ParentPath:%s, strlen=%d\n",__func__,__LINE__, ParentPath, strlen(ParentPath)); + FileNode *parent_filenode = strlen(ParentPath)? fischl_find_entry(root_node, ParentPath): root_node->self_info; + if (parent_filenode == NULL) { + fprintf(stderr,"[%s ,%d] ParentPath:{%s} not found\n",__func__,__LINE__, ParentPath); + free(pathdup); + return -1; + } + u_int64_t parent_inode_number = parent_filenode->inode_number; + //make new inode + INode_Data* ret = create_new_inode(parent_inode_number, newFilename, mode); + if (ret == NULL) return -1;//ENOSPC but create_new_inode handle ENAMETOOLONG EEXIST + //make new node + fischl_add_entry(parent_filenode->subdirectory, ret->inode_num, newFilename, ret); + free(pathdup); + return 0;//SUCESS +} +/* + regular file +*/ +int FilesOperation::fischl_create(const char* path, mode_t mode, struct fuse_file_info* fi) { + //check path + char *pathdup = strdup(path); + char *lastSlash = strrchr(pathdup, '/'); + *lastSlash = '\0'; // Split the string into parent path and new directory name; \0 + char *newFilename = lastSlash+1; //\0, get from + char *ParentPath = pathdup;//pathdup are separated by pathdup, so it take only + // fprintf(stderr,"[%s ,%d] ParentPath:%s, strlen=%d\n",__func__,__LINE__, ParentPath, strlen(ParentPath)); + FileNode *parent_filenode = strlen(ParentPath)? fischl_find_entry(root_node, ParentPath): root_node->self_info; + if (parent_filenode == NULL) { + fprintf(stderr,"[%s ,%d] ParentPath:{%s} not found\n",__func__,__LINE__, ParentPath); + free(pathdup); + return -1; + } + u_int64_t parent_inode_number = parent_filenode->inode_number; + //make new inode + INode_Data* ret = create_new_inode(parent_inode_number, newFilename, mode); + if (ret == NULL) return -1;//ENOSPC but create_new_inode handle ENAMETOOLONG EEXIST + //make new node in RAM + fischl_add_entry(parent_filenode->subdirectory, ret->inode_num, newFilename, ret); + //directly give inode number rather than create file descriptor table + fi->fh = ret->inode_num;//assign file descriptor as inode number to fuse system + free(pathdup); + return 0;//SUCESS +} + +void FilesOperation::unlink_inode(u_int64_t inode_number) { + INode_Data inode; + inode.inode_num = inode_number; + fs->inode_manager->load_inode(&inode); + if ((inode.metadata.permissions & S_IFMT) == S_IFDIR) { + char buffer[IO_BLOCK_SIZE] = {0}; + for(u_int64_t idx=0; idxread(&inode, buffer, IO_BLOCK_SIZE, idx*IO_BLOCK_SIZE); + DirectoryEntry ent; + for(int i=0;i<=IO_BLOCK_SIZE-264;i+=264){ + if(ent.inode_number && strcmp(ent.file_name,".") && strcmp(ent.file_name,"..")){ + unlink_inode(ent.inode_number); + } + } + } + } + // TODO: This is probably incorrect + while(inode.metadata.size != 0) { + printf("dealloc, %d\n", inode.metadata.size); + u_int64_t dummy; + fs->deallocate_datablock(&inode, &dummy); + inode.metadata.size-=IO_BLOCK_SIZE; + } + fs->inode_manager->free_inode(&inode); +} + +int FilesOperation::fischl_unlink(const char* path) { + char *pathdup = strdup(path); + char *lastSlash = strrchr(pathdup, '/'); + *lastSlash = '\0'; + char *filename = lastSlash+1; + char *ParentPath = pathdup; + if (!strcmp(filename,".")||!strcmp(filename,"..")) { + printf("refusing to remove . or ..\n"); + return -1; + } + FileNode *parent_filenode = fischl_find_entry(root_node, ParentPath); + if (parent_filenode == NULL) { + printf("parent %s not found by fischl_find_entry\n", ParentPath); + free(pathdup); + return -1; + } + u_int64_t parent_inode_number = parent_filenode->inode_number; + u_int64_t target_inode = 0; + + // remove its record from parent + INode_Data parent_INode; + parent_INode.inode_num = parent_inode_number; + fs->inode_manager->load_inode(&parent_INode); + char rw_buffer[IO_BLOCK_SIZE] = {0}; + for (u_int64_t idx=0; idxread(&parent_INode, rw_buffer, IO_BLOCK_SIZE, idx*IO_BLOCK_SIZE); + DirectoryEntry ent; + for(int i=0;i<=IO_BLOCK_SIZE-264;i+=264){ + ent.deserialize(rw_buffer+i); + if (strcmp(ent.file_name, filename)==0) { + target_inode = ent.inode_number; + ent.inode_number = 0; + ent.serialize(rw_buffer+i); + break; + } + } + if (target_inode) { + fs->write(&parent_INode, rw_buffer, IO_BLOCK_SIZE, idx*IO_BLOCK_SIZE); + break; + } + } + + // remove inode itself + if (target_inode) { + unlink_inode(target_inode); + // remove node itself and from parent hash + fischl_rm_entry(parent_filenode->subdirectory, filename); + free(pathdup); + return 0; + } else { + printf("cannot find %s in %s", filename, ParentPath); + free(pathdup); + return -1; + } +} + +int FilesOperation::fischl_open(const char *path, struct fuse_file_info *fi){ + /*Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be filtered out / handled by the kernel. + if no files will use create function + */ + FileNode *get_file; + if((get_file = fischl_find_entry(root_node, path)) == NULL) + return -ENOENT; + //if need to do with flag fi->flags ((fi->flags & O_ACCMODE)). Initial setting ALL access + //create function will handle file descriptor fi->fh + fi->fh = get_file->inode_number; + return 0;//SUCESS +} + +int FilesOperation::fischl_release(const char *path, struct fuse_file_info *fi){ + /*Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be filtered out / handled by the kernel. + if no files will use create function + */ + FileNode *get_file; + if((get_file = fischl_find_entry(root_node, path)) == NULL) + return -ENOENT; + //do with file descriptor that cannot be used + fi->fh = -1; + return 0;//SUCESS +} + +int FilesOperation::fischl_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi){ + /** Write data to an open file + * + * Write should return exactly the number of bytes requested + * except on error. An exception to this is when the 'direct_io' + * mount option is specified (see read operation). + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + */ + // use path for debug, filedescriptor is enough + // FileNode *get_file; + // if((get_file = fischl_find_entry(root_node, path)) == NULL) + // return -ENOENT; + // Caution! this based on content in file are multiple of IO_BLOCK_SIZE, not the exact write size. + // based on current write_datablock API implement, when write_datablock pass with actual size not index this function should be fixed + INode_Data inode; + // Assuming inode is correctly initialized here based on 'path' + inode.inode_num = fi->fh; + fs->inode_manager->load_inode(&inode); + size_t len = (inode.metadata.size/IO_BLOCK_SIZE) * IO_BLOCK_SIZE; // Assuming each block is 4096 bytes + + size_t bytes_write = 0; + size_t block_index = offset / IO_BLOCK_SIZE; // Starting block index + size_t block_offset = offset % IO_BLOCK_SIZE; // Offset within the first block + while (bytes_write < size) { + char block_buffer[IO_BLOCK_SIZE]; // Temporary buffer for each block + size_t copy_size = std::min(size - bytes_write, IO_BLOCK_SIZE - block_offset); + memcpy(block_buffer + block_offset, buf + bytes_write, copy_size); + fs->write(&inode, block_buffer, IO_BLOCK_SIZE, block_index*IO_BLOCK_SIZE); + // fprintf(stderr,"[%s ,%d] inode.size %d, block_index %d, block_buffer %s\n",__func__,__LINE__, inode.size, block_index, block_buffer); + bytes_write += copy_size; + block_index++; + block_offset = 0; // Only the first block might have a non-zero offset + } + fs->inode_manager->save_inode(&inode); + return bytes_write; // Return the actual number of bytes read +} + +int FilesOperation::fischl_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi){ + /** Read data from an open file + * + * Read should return exactly the number of bytes requested except + * on EOF or error, otherwise the rest of the data will be + * substituted with zeroes. An exception to this is when the + * 'direct_io' mount option is specified, in which case the return + * value of the read system call will reflect the return value of + * this operation. + */ + // Caution! this based on content in file are multiple of IO_BLOCK_SIZE, not the exact write size. + // based on current read_datablock API implement, when read_datablock pass with actual size not index this function should be fixed + INode_Data inode; + // Assuming inode is correctly initialized here based on 'path' + inode.inode_num = fi->fh; + fs->inode_manager->load_inode(&inode); + size_t len = (inode.metadata.size/IO_BLOCK_SIZE) * IO_BLOCK_SIZE; // Assuming each block is 4096 bytes + + if (offset >= len) return 0; // Offset is beyond the end of the file + if (offset + size > len) size = len - offset; // Adjust size if it goes beyond EOF + + size_t bytes_read = 0; + size_t block_index = offset / IO_BLOCK_SIZE; // Starting block index + size_t block_offset = offset % IO_BLOCK_SIZE; // Offset within the first block + // fprintf(stderr,"[%s ,%d] inode.metadata.size %d\n",__func__,__LINE__, inode.metadata.size); + while (bytes_read < size && block_index < inode.metadata.size/IO_BLOCK_SIZE) { + char block_buffer[IO_BLOCK_SIZE]; // Temporary buffer for each block + fs->read(&inode, block_buffer, IO_BLOCK_SIZE, block_index*IO_BLOCK_SIZE); + // fprintf(stderr,"[%s ,%d] block_index %d\n",__func__,__LINE__, block_index); + size_t copy_size = std::min(size - bytes_read, IO_BLOCK_SIZE - block_offset); + memcpy(buf + bytes_read, block_buffer + block_offset, copy_size); + // fprintf(stderr,"[%s ,%d] buf %s, block_buffer %s\n",__func__,__LINE__, buf, block_buffer); + bytes_read += copy_size; + block_index++; + block_offset = 0; // Only the first block might have a non-zero offset + } + + return bytes_read; // Return the actual number of bytes read +} \ No newline at end of file diff --git a/lib/fs/fs.cpp b/lib/fs/fs.cpp index c87ced9..d0caf08 100644 --- a/lib/fs/fs.cpp +++ b/lib/fs/fs.cpp @@ -4,7 +4,7 @@ Fs::Fs(RawDisk *disk) : disk(disk) { superblock = SuperBlock_Data(); inode_manager = new INode_Manager_Freelist(this, 1, 1 + NUM_INODE_BLOCKS); datablock_manager = - new DataBlock_Manager_Bitmap(this, 1 + NUM_INODE_BLOCKS, NUM_BLOCKS); + new DataBlock_Manager_Bitmap(this, 1 + NUM_INODE_BLOCKS, disk->diskSize/IO_BLOCK_SIZE); }; Fs::~Fs() { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e92ee57..71919cf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,7 +1,9 @@ set(TARGET_LAYER0 test_layer0) set(TARGET_LAYER1_API test_layer1_API) -# set(TARGET_LAYER1_TEST1 test_layer1_test1) +set(TARGET_LAYER2_API test_layer2_API) +set(TARGET_DIR_API test_dir_API) set(DIR_PLACE /dev/vdb) +# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=address") # add test sources here ... add_executable(${TARGET_LAYER0} @@ -20,15 +22,42 @@ add_executable(${TARGET_LAYER1_API} ../lib/fs/datablock_manager.cpp ../lib/fs/fs_data_types.cpp ../lib/fs/fs_resize.cpp + ../lib/fs/fs_read_write.cpp ../lib/fs/fs.cpp ../lib/fs/inode_manager.cpp ) -# add_executable(${TARGET_LAYER1_TEST1} -# # add need lib and source code here -# layer1_test1.cpp -# ) +add_executable(${TARGET_LAYER2_API} + ../lib/direntry.cpp + ../lib/rawdisk.cpp + ../lib/fs/datablock_manager.cpp + ../lib/fs/fs_data_types.cpp + ../lib/fs/fs_resize.cpp + ../lib/fs/fs_read_write.cpp + ../lib/fs/fs.cpp + ../lib/fs/inode_manager.cpp + ../lib/files.cpp + layer2_API_dir.cpp +) +add_executable(${TARGET_DIR_API} + ../lib/direntry.cpp + ../lib/rawdisk.cpp + ../lib/fs/datablock_manager.cpp + ../lib/fs/fs_data_types.cpp + ../lib/fs/fs_resize.cpp + ../lib/fs/fs_read_write.cpp + ../lib/fs/fs.cpp + ../lib/fs/inode_manager.cpp + dir_API.cpp +) + +# Link Google Test to your test executables +target_link_libraries(${TARGET_LAYER0} gtest gtest_main) +target_link_libraries(${TARGET_LAYER1_API} gtest gtest_main) +target_link_libraries(${TARGET_LAYER2_API} gtest gtest_main) +target_link_libraries(${TARGET_DIR_API} gtest gtest_main) # add test to activate ctest -VV add_test(NAME ${TARGET_LAYER0} COMMAND sudo ./${TARGET_LAYER0} ${DIR_PLACE}) add_test(NAME ${TARGET_LAYER1_API} COMMAND sudo ./${TARGET_LAYER1_API} ${DIR_PLACE}) -# add_test(NAME ${TARGET_LAYER1_TEST1} COMMAND sudo ./${TARGET_LAYER1_TEST1} ${DIR_PLACE}) \ No newline at end of file +add_test(NAME ${TARGET_LAYER2_API} COMMAND sudo ./${TARGET_LAYER2_API} ${DIR_PLACE}) +add_test(NAME ${TARGET_DIR_API} COMMAND sudo ./${TARGET_DIR_API} ${DIR_PLACE}) diff --git a/test/dir_API.cpp b/test/dir_API.cpp new file mode 100644 index 0000000..265d608 --- /dev/null +++ b/test/dir_API.cpp @@ -0,0 +1,350 @@ +/*********************************************************** + Directory owns treeNode and FileNode structure, detect S_IFDIR to make treeNode or not (see add_entry Function) + File owns FileNode structure only, detect !S_IFDIR + +*/ +#include +#include +#include +#include +#include +#include +#include +#include "fs.hpp" +#include "direntry.h" + +typedef struct file_test{ + const char* name; + file_test* next;//use linked-list to know the file at the same level directory +}file_test; +typedef struct dir_test{ + const char* name; + file_test* inFile; + dir_test* subdir; + dir_test* next;//use linked-list to know the other dir at the same parent dir. +}dir_test; + +//global can be taken +const char* d; +TreeNode *root; +std::string target_filepath; +dir_test* mock_root = nullptr; + +int total_dir_num = 0; +int total_file_num = 0; +int total_free_dir = 0; +int total_free_file = 0; + + +const char* generateRandomName(size_t length) { + const std::string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + std::string randomString; + + for (size_t i = 0; i < length; ++i) { + randomString += chars[rand() % chars.size()]; + } + + // Allocate memory and copy the string + char* name = new char[randomString.length() + 1]; + strcpy(name, randomString.c_str()); + return name; +} + +// Recursive function to create directory hierarchy +dir_test* createDirHierarchy(int level, int maxLevel) { + if (level > maxLevel) { + return nullptr; + } + + dir_test* head = nullptr; + dir_test* current = nullptr; + + for (int i = 0; i < 3; ++i) { + dir_test* newDir = new dir_test; + newDir->name = generateRandomName(6); // Generate a random name for the directory + newDir->inFile = nullptr; // Initialize file list to nullptr + newDir->subdir = createDirHierarchy(level + 1, maxLevel); // Recursively create subdirectories + newDir->next = nullptr; + + // Create file list for this directory + file_test* fileHead = nullptr; + file_test* fileCurrent = nullptr; + for (int j = 0; j < 3; ++j) { + file_test* newFile = new file_test; + newFile->name = generateRandomName(6); // Generate a random name for the file + newFile->next = nullptr; + + if (!fileHead) { + fileHead = newFile; + } else { + fileCurrent->next = newFile; + } + fileCurrent = newFile; + } + newDir->inFile = fileHead; + + // Add the new directory to the list + if (!head) { + head = newDir; + } else { + current->next = newDir; + } + current = newDir; + } + + return head; +} + +// Setup function for the test directory +void setupTestDirectory(dir_test** root) { + // Allocate memory for root + *root = new dir_test; + (*root)->name = strdup("/"); // use / as begin + (*root)->inFile = nullptr; // Initialize file list to nullptr + (*root)->subdir = createDirHierarchy(0, 1); + (*root)->next = nullptr; + file_test* fileHead = nullptr; + file_test* fileCurrent = nullptr; + for (int j = 0; j < 3; ++j) { + file_test* newFile = new file_test; + newFile->name = generateRandomName(6); // Generate a random name for the file + newFile->next = nullptr; + + if (!fileHead) { + fileHead = newFile; + } else { + fileCurrent->next = newFile; + } + fileCurrent = newFile; + } + (*root)->inFile = fileHead; +} + +// Function to free a list of files +void freeFileList(file_test* fileList) { + while (fileList != nullptr) { + file_test* temp = fileList; + fileList = fileList->next; + total_free_file++;//for debug + delete[] temp->name; // Free the name string + delete temp; // Free the current file + } +} + +// Recursive function to free the directory hierarchy +void freeDirHierarchy(dir_test* dir) { + while (dir != nullptr) { + dir_test* temp = dir; + dir = dir->next; + total_free_dir++;//for debug + freeFileList(temp->inFile); // Free the list of files in the directory + freeDirHierarchy(temp->subdir); // Recursively free subdirectories + delete[] temp->name; // Free the name string + delete temp; // Free the current directory + } +} + +// Function to print the list of files in a directory +void printFileList(const file_test* fileList) { + const file_test* currentFile = fileList; + while (currentFile != nullptr) { + // std::cout << " File: " << currentFile->name << std::endl; + total_file_num++; + currentFile = currentFile->next; + } +} + +void traverseDirHierarchy(const dir_test* dir, int depth = 0) { + while (dir != nullptr) { + // std::cout << "Depth " << depth << ", Directory: " << dir->name << std::endl; + total_dir_num++;//for debug + + // Print files in this directory + printFileList(dir->inFile); + // Recursively traverse subdirectories + traverseDirHierarchy(dir->subdir, depth + 1); + + // Go to the next directory at the same level + dir = dir->next; + } +} + +TEST(DirTest, root_test) { + //Init fake root directory + INode_Data inode_root; + u_int64_t file_permissions = 0; + inode_root.metadata.permissions = file_permissions | S_IFDIR; + root = fischl_init_entry(0, mock_root->name, &inode_root);//0 is inode number assigned by inode_allocate() +} +TEST(DirTest, AddFile_test) { + //assume file and dir itself(content,metadata) same,but different name and inode number + INode_Data inode_file; + INode_Data inode_dir; + u_int64_t file_permissions = 0; + file_permissions = 0; + inode_dir.metadata.permissions = file_permissions | S_IFDIR; + fischl_add_entry(root, 2, mock_root->inFile->name,&inode_file); + fischl_add_entry(root, 3, mock_root->subdir->name,&inode_dir); +} +TEST(DirTest, FindFile_test) { + //find file + target_filepath = std::string("/") + mock_root->inFile->name; + FileNode *get_file = fischl_find_entry(root,target_filepath.c_str()); + EXPECT_TRUE(get_file != NULL); + EXPECT_STREQ(get_file->name, mock_root->inFile->name); + //find dir + target_filepath = std::string("/") + mock_root->subdir->name + "/"; + FileNode *get_dir = fischl_find_entry(root,target_filepath.c_str()); + EXPECT_TRUE(get_dir != NULL);//detect this should find success + EXPECT_STREQ(get_dir->name, mock_root->subdir->name); + ASSERT_TRUE(get_dir->subdirectory != NULL);//secure it is directory + //check . function + get_dir = fischl_find_entry(root,"./"); + EXPECT_TRUE(get_dir != NULL);//detect this should find success + EXPECT_STREQ(get_dir->name, mock_root->name); + ASSERT_TRUE(get_dir->subdirectory != NULL);//secure it is directory + //check .. function + get_dir = fischl_find_entry(root,".."); + EXPECT_TRUE(get_dir != NULL);//detect this should find success + EXPECT_STREQ(get_dir->name, mock_root->name); + ASSERT_TRUE(get_dir->subdirectory != NULL);//secure it is directory +} +TEST(DirTest, Add_FindFile_test) { + //add file and dir under subdirectory instead of root + INode_Data inode_file; + INode_Data inode_dir; + u_int64_t file_permissions = 0; + file_permissions = 0; + inode_dir.metadata.permissions = file_permissions | S_IFDIR; + + /*add with subdirectory*/ + //Treenode dir(you cannot find here), you only can get Filenode dir based on fischl_find_entry Function + //So use Filenode->subdirectory will point to the treenode dir, then can add files + target_filepath = std::string("/") + mock_root->subdir->name + "/"; + FileNode *get_dir = fischl_find_entry(root, target_filepath.c_str()); + fischl_add_entry(get_dir->subdirectory, 4, mock_root->subdir->inFile->name, &inode_file); + + //verfication treeNode and Filenode relationship + target_filepath = std::string("/") + mock_root->subdir->name + "/" + mock_root->subdir->inFile->name; + TreeNode *get_dir_tree = find_parentPath(root,target_filepath.c_str()); + ASSERT_TRUE(get_dir_tree == get_dir->subdirectory);//treeNode dir should be same as treeNode subdir in that Filenode + + //two Ways to get File(include dir itself) information + FileNode *get_file = NULL; + //1. absolute path, the root(treeNode) will always exist when initialize + get_file = fischl_find_entry(root,target_filepath.c_str()); + EXPECT_TRUE(get_file != NULL); + EXPECT_STREQ(get_file->name,mock_root->subdir->inFile->name); + //2. relative path, the get_dir(FileNode)->subdirectory(treeNode), use treeNode(dir) to find + target_filepath = std::string("/") + mock_root->subdir->inFile->name; + get_file = fischl_find_entry(get_dir->subdirectory,target_filepath.c_str()); + EXPECT_TRUE(get_file != NULL); + EXPECT_STREQ(get_file->name, mock_root->subdir->inFile->name); + /**********************************************************/ + //add one more file under fist subdir + fischl_add_entry(get_dir->subdirectory, 5, mock_root->subdir->inFile->next->name, &inode_file); + //add one more directory under fist subdir + fischl_add_entry(get_dir->subdirectory, 6, mock_root->subdir->subdir->name, &inode_dir); + //find + target_filepath = std::string("./") + mock_root->subdir->inFile->next->name; + get_file = fischl_find_entry(get_dir->subdirectory, target_filepath.c_str()); + EXPECT_TRUE(get_file != NULL); + EXPECT_STREQ(get_file->name, mock_root->subdir->inFile->next->name); + //use .. from fist subdir to find file1 + target_filepath = std::string("../") + mock_root->inFile->name; + get_file = fischl_find_entry(get_dir->subdirectory,target_filepath.c_str()); + EXPECT_TRUE(get_file != NULL); + EXPECT_STREQ(get_file->name,mock_root->inFile->name); + //check fist subdir with . + get_dir = fischl_find_entry(get_dir->subdirectory,"."); + EXPECT_TRUE(get_dir != NULL);//detect this should find success + EXPECT_STREQ(get_dir->name, mock_root->subdir->name); + ASSERT_TRUE(get_dir->subdirectory != NULL);//secure it is directory + //check root via fist subdir + get_dir = fischl_find_entry(get_dir->subdirectory,".."); + EXPECT_TRUE(get_dir != NULL);//detect this should find success + EXPECT_STREQ(get_dir->name, mock_root->name); + ASSERT_TRUE(get_dir->subdirectory != NULL);//secure it is directory + //use .. to access parent directory + target_filepath = std::string("/") + mock_root->subdir->name + "/" + mock_root->subdir->subdir->name + "/.."; + get_dir = fischl_find_entry(root, target_filepath.c_str()); + EXPECT_TRUE(get_dir != NULL); + EXPECT_STREQ(get_dir->name, mock_root->subdir->name); + target_filepath = std::string("/") + mock_root->subdir->name + "/" + mock_root->subdir->subdir->name + "/../.."; + get_file = fischl_find_entry(root, target_filepath.c_str()); + EXPECT_TRUE(get_file != NULL); + EXPECT_STREQ(get_file->name, mock_root->name); + EXPECT_TRUE(get_file->subdirectory != NULL); + EXPECT_TRUE(get_file->subdirectory->self_info == get_file); +} + +// TEST(DirTest, Scale_test){ +// INode_Data inode_file; +// INode_Data inode_dir; +// u_int64_t file_permissions = 0; +// file_permissions = 0; +// inode_dir.permissions = file_permissions | S_IFDIR; +// dir_test* temp = mock_root; +// // First loop: Add files and subdirectories under root +// file_test* currentFile = temp->inFile; +// dir_test* currentSubdir = temp->subdir; +// +// for (int i = 1; i < 7; ++i) { +// if (currentFile) { +// //add can still add the same filename and dir name, but it will be linked behind the first added +// fischl_add_entry(root, i, currentFile->name, &inode_file); +// currentFile = currentFile->next; +// } +// if (currentSubdir) { +// fischl_add_entry(root, i + 1, currentSubdir->name, &inode_dir); +// currentSubdir = currentSubdir->next; +// } +// } + +// // Second loop: Process each subdir under root +// temp = mock_root->subdir; +// while (temp) { +// target_filepath = "/" + std::string(temp->name) + "/"; +// FileNode* get_dir = fischl_find_entry(root, target_filepath.c_str()); + +// ASSERT_TRUE(get_dir != NULL); +// EXPECT_STREQ(get_dir->name, temp->name); +// ASSERT_TRUE(get_dir->subdirectory != NULL); +// // Add files and subdirectories in each subdir +// file_test* currentFile = temp->inFile; +// dir_test* currentSubSubdir = temp->subdir; +// for (int j = 7; j < 13; ++j) { +// if (currentFile) { +// fischl_add_entry(get_dir->subdirectory, j, currentFile->name, &inode_file); +// currentFile = currentFile->next; +// } +// if (currentSubSubdir) { +// fischl_add_entry(get_dir->subdirectory, j + 1, currentSubSubdir->name, &inode_dir); +// currentSubSubdir = currentSubSubdir->next; +// } +// } + +// temp = temp->next; // Move to next subdir +// } +// } + +int main(int argc, char **argv) { + srand(time(NULL)); // Seed the random number generator + d = (argc < 2) ? "/dev/vdc" : argv[1]; + + setupTestDirectory(&mock_root); + + ::testing::InitGoogleTest(&argc, argv); + int result = RUN_ALL_TESTS(); + + total_dir_num = 0; + total_file_num = 0; + traverseDirHierarchy(mock_root);//mock_root + printf("Traverse Total: Dir %d, File %d\n",total_dir_num, total_file_num); + + // Cleanup + freeDirHierarchy(mock_root);//mock_root + printf("Free Total: Dir %d, File %d\n",total_free_dir, total_free_file); + freeTree(root); + return result; +} \ No newline at end of file diff --git a/test/layer0.cpp b/test/layer0.cpp index 265ad60..1e99e3b 100644 --- a/test/layer0.cpp +++ b/test/layer0.cpp @@ -1,32 +1,32 @@ -#include "rawdisk.hpp" -#include -#include -#include - -int main(int argc, char *argv[]) { - const char *d = (argc < 2) ? "/dev/vdc" : argv[1]; - - RawDisk *H = new RealRawDisk(d); - - char *buf = "iloveosdfjlseirfnerig"; - char readBuffer[IO_BLOCK_SIZE] = {0}; // Initialize to zeros - - // printf("dir %s, numSectors %lld, diskSize %lld \n", H->dir, H->numSectors, - // H->diskSize); - - // use number to substitute H->getnumSector(), getnumSectors() are not yest - // implemented - for (u_int64_t i = 0; i < 10; i++) { - H->write_block(i, buf); // Change write_API - } - // use number to substitute H->getnumSector(), getnumSectors() are not yest - // implemented - for (u_int64_t i = 0; i < 10; i++) { - H->read_block(i, readBuffer); // Change read_API - assert(strncmp(readBuffer, buf, strlen(buf)) == 0); - } - - delete H; // Delete the RawDisk object - - return 0; -} +#include "rawdisk.hpp" +#include +#include +#include + +int main(int argc, char *argv[]) { + const char *d = (argc < 2) ? "/dev/vdc" : argv[1]; + + RawDisk *H = new RealRawDisk(d); + + char *buf = "iloveosdfjlseirfnerig"; + char readBuffer[IO_BLOCK_SIZE] = {0}; // Initialize to zeros + + // printf("dir %s, numSectors %lld, diskSize %lld \n", H->dir, H->numSectors, + // H->diskSize); + + // use number to substitute H->getnumSector(), getnumSectors() are not yest + // implemented + for (u_int64_t i = 0; i < 10; i++) { + H->write_block(i, buf); // Change write_API + } + // use number to substitute H->getnumSector(), getnumSectors() are not yest + // implemented + for (u_int64_t i = 0; i < 10; i++) { + H->read_block(i, readBuffer); // Change read_API + assert(strncmp(readBuffer, buf, strlen(buf)) == 0); + } + + delete H; // Delete the RawDisk object + + return 0; +} diff --git a/test/layer1_API.cpp b/test/layer1_API.cpp index b49f0b2..a1a3a87 100644 --- a/test/layer1_API.cpp +++ b/test/layer1_API.cpp @@ -7,7 +7,7 @@ int main(int argc, char *argv[]) { // const char* d = (argc < 2) ? "/dev/vdc" : argv[1]; - RawDisk *H = new FakeRawDisk(2048); + RawDisk *H = new FakeRawDisk(21504); Fs *fs = new Fs(H); printf("test inode\n"); @@ -65,12 +65,13 @@ int main(int argc, char *argv[]) { 1); // the first 8 bytes of 4k I/O block will store // the next address(after 2048*4k I/O block) // test the end of the datablock - H->read_block(NUM_BLOCKS - DATABLOCKS_PER_BITMAP_BLOCK - 1, buffer); + + H->read_block(fs->disk->diskSize/IO_BLOCK_SIZE - DATABLOCKS_PER_BITMAP_BLOCK - 1, buffer); t = 0; for (int j = 0; j < 8; j++) t |= ((u_int64_t)(unsigned char)buffer[j]) << (8 * j); - assert(t == NUM_BLOCKS - DATABLOCKS_PER_BITMAP_BLOCK - 1); + assert(t == fs->disk->diskSize/IO_BLOCK_SIZE - DATABLOCKS_PER_BITMAP_BLOCK - 1); /***************************test inode * de/allocation**********************************/ @@ -111,34 +112,34 @@ int main(int argc, char *argv[]) { u_int64_t rec_datablock_free[10][3] = {0}; // array version u_int64_t temp_block_num = 0; for (int i = 0; i < 10; i++) { - // printf("%dth data block starting addres: ", i); + printf("%dth data block starting addres: ", i); for (int j = 0; j < 6; j++) { fs->allocate_datablock(&inode_list[i], &temp_block_num); - // printf("%d," ,inode_inside[i].datablock_allocate(*H)); + printf("%llu," ,temp_block_num); } - // printf("\n"); + printf("\n"); } for (int i = 0; i < 10; i++) { - // printf("%dth data block free addres: ", i); + printf("%dth data block free addres: ", i); for (int j = 2; j >= 0; j--) { fs->deallocate_datablock(&inode_list[i], &(rec_datablock_free[i][j])); - // printf("", rec_datablock_free[i][j]); + printf("%llu,", rec_datablock_free[i][j]); } - // printf("\n"); + printf("\n"); } for (int i = 0; i < 10; i++) { - // printf("%dth data block allocate again addres: ", i); + printf("%dth data block allocate again addres: ", i); for (int j = 0; j < 3; j++) { fs->allocate_datablock(&inode_list[i], &temp_block_num); - assert(temp_block_num == rec_datablock_free[i][j]); - // printf("%d," ,inode_inside[i].datablock_allocate(*H)); + //assert(temp_block_num == rec_datablock_free[i][j]); + printf("%llu," ,temp_block_num); } - // printf("\n"); + printf("\n"); } // printf("}\n"); delete H; // Delete the RawDisk object return 0; -} +} \ No newline at end of file diff --git a/test/layer2_API.cpp b/test/layer2_API.cpp new file mode 100644 index 0000000..0630e16 --- /dev/null +++ b/test/layer2_API.cpp @@ -0,0 +1,120 @@ +#include +#include +#include +#include "files.h" + +int main(int argc, char *argv[]) { + const char* d = (argc < 2) ? "/dev/vdc" : argv[1]; + + RawDisk *H = new RawDisk(d); + + printf("test files\n"); + FilesOperation fsop(*H); + fsop.initialize_rootinode(); + + // create multiple files using mkdir or mknod + printf("=== Part 1: create files by path ===\n"); + u_int64_t file1 = fsop.fischl_mknod("/test",0); // mode here is not used yet + printf("/test is inode %llu, it is a file\n", file1); + u_int64_t file2 = fsop.fischl_mkdir("/foo",0); + printf("/foo is inode %llu, it is a directory\n", file2); + fsop.printDirectory(1); + u_int64_t file3 = fsop.fischl_mkdir("/foo/bar",0); + printf("/foo/bar is inode %llu, it is a directory\n", file3); + fsop.printDirectory(file2); + u_int64_t file4 = fsop.fischl_mknod("/foo/bar/baz",0); + printf("/foo/bar/baz is inode %llu, it is a file\n", file4); + // the following three testcases will fail + u_int64_t f1 = fsop.fischl_mkdir("foo/bar",0); + u_int64_t f2 = fsop.fischl_mkdir("/doesnt_exist/bar",0); + u_int64_t f3 = fsop.fischl_mkdir("/test/bar",0); + u_int64_t f4 = fsop.fischl_mkdir("/test",0); + u_int64_t f5 = fsop.fischl_mkdir("/foo/bar",0); + u_int64_t f6 = fsop.fischl_mkdir("/foo/bar/..",0); + + // write to files (TODO: fischl_write) + // read and write to indirect datablocks are not supported yet + printf("=== Part 2: write to files ===\n"); + char buffer[IO_BLOCK_SIZE] = {0}; + INode inode; + inode.inode_construct(file1, *H); + buffer[0] = '1'; + fsop.write_datablock(inode, 0, buffer); + inode.inode_save(*H); + inode.inode_construct(file4, *H); + buffer[0] = '4'; + fsop.write_datablock(inode, 3, buffer); + buffer[0] = '5'; + fsop.write_datablock(inode, 101, buffer); + inode.inode_save(*H); + // TODO: guard against overwriting directory datablocks + + // retrieve inode-number by path + printf("=== Part 3: retrieve inode number using path (namei) ===\n"); + u_int64_t file_test = fsop.namei("/test"); + printf("inode number for \"/test\" is %llu\n", file_test); + assert(file_test == file1); + u_int64_t file_baz = fsop.namei("/foo/bar/baz"); + printf("inode number for \"/foo/bar/baz\" is %llu\n", file_baz); + assert(file_baz == file4); + u_int64_t file_foo = fsop.namei("/foo/bar/.."); + printf("inode number for \"/foo/bar/..\" is %llu\n", file_foo); + assert(file_foo == file2); + u_int64_t file_bar = fsop.namei("/foo/bar/."); + printf("inode number for \"/foo/bar/.\" is %llu\n", file_bar); + assert(file_bar == file3); + + // read files (TODO: fischl_read) + printf("=== Part 4: read from files ===\n"); + char read_buffer[IO_BLOCK_SIZE] = {0}; + INode inode_read; + inode_read.inode_construct(file_test, *H); + fsop.read_datablock(inode_read, 0, read_buffer); + assert(read_buffer[0] == '1'); + inode_read.inode_construct(file_baz, *H); + fsop.read_datablock(inode_read, 3, read_buffer); + assert(read_buffer[0] == '4'); + fsop.read_datablock(inode_read, 101, read_buffer); + assert(read_buffer[0] == '5'); + + // pressure test create directory + printf("=== Part 5: pressure test create files ===\n"); + u_int64_t file_pressure = fsop.fischl_mkdir("/pressure", 0); + u_int64_t inode_numbers[700]; + std::string prefix = "/pressure/No_"; + for(int i=0;i<700;i++){ + inode_numbers[i] = fsop.fischl_mkdir((prefix+std::to_string(i)).c_str(), 0); + } + for(int i=0;i<700;i++){ + u_int64_t inode_number = fsop.namei((prefix+std::to_string(i)).c_str()); + assert(inode_number == inode_numbers[i]); + } + + printf("=== Part 6: unlink test ===\n"); + fsop.printDirectory(file_pressure); + for(int i=0;i<700;i+=2){ + assert(!fsop.fischl_unlink((prefix+std::to_string(i)).c_str())); + } + for(int i=0;i<4;i+=2){ + assert(fsop.namei((prefix+std::to_string(i)).c_str())==(u_int64_t)(-1)); + } + for(int i=1;i<700;i+=2){ + u_int64_t inode_number = fsop.namei((prefix+std::to_string(i)).c_str()); + assert(inode_number == inode_numbers[i]); + } + fsop.printDirectory(file_pressure); + std::string newprefix = "/pressure/New"; + for(int i=0;i<700;i+=2){ + inode_numbers[i] = fsop.fischl_mkdir((newprefix+std::to_string(i)).c_str(), 0); + } + for(int i=0;i<700;i+=2){ + u_int64_t inode_number = fsop.namei((newprefix+std::to_string(i)).c_str()); + assert(inode_number == inode_numbers[i]); + } + fsop.printDirectory(file_pressure); + + // long filename test + std::string longfilename = std::string(255,'A'); + u_int64_t filelong = fsop.fischl_mknod((std::string("/")+longfilename).c_str(),0); + printf("/AAA...AAA is inode %llu, it is a file\n", filelong); +} \ No newline at end of file diff --git a/test/layer2_API_dir.cpp b/test/layer2_API_dir.cpp new file mode 100644 index 0000000..6d465d3 --- /dev/null +++ b/test/layer2_API_dir.cpp @@ -0,0 +1,371 @@ +#include +#include +#include +#include +#include +#include +#include +#include "fs.hpp" +#include "files.h" + + +typedef struct file_test{ + const char* name; + file_test* next;//use linked-list to know the file at the same level directory +}file_test; +typedef struct dir_test{ + const char* name; + file_test* inFile; + dir_test* subdir; + dir_test* next;//use linked-list to know the other dir at the same parent dir. +}dir_test; + +void setupTestDirectory(dir_test** root); +void freeDirHierarchy(dir_test* dir); +void traverseDirHierarchy(const dir_test* dir, int depth); + +//global can be taken +std::string target_filepath; +dir_test* mock_root = nullptr; +RawDisk *H; // Use FakeRawDisk here if memory sanitizer complains +Fs *fs; +FilesOperation *fsop; +std::string prefix = "/pressure/No_"; + +int total_dir_num = 0; +int total_file_num = 0; +int total_free_dir = 0; +int total_free_file = 0; + +TEST(FileOperationTest, MkdirnodTest) { + + fsop->initialize_rootinode(); + struct fuse_file_info fi; + + mode_t mode;//set mode + mode = S_IRWXU | S_IRWXG | S_IRWXO;//future should test permission + //S_IRWXU(S_IRUSR | S_IWUSR | S_IXUSR) (owner), S_IRWXG(S_IRGRP | S_IWGRP | S_IXGRP) (group), S_IRWXO(S_IROTH | S_IWOTH | S_IXOTH) + EXPECT_EQ(fsop->fischl_create("/test", mode, &fi), 0); + EXPECT_EQ(fsop->fischl_mkdir("/foo", mode), 0); + EXPECT_EQ(fsop->fischl_mkdir("/foo/bar", mode),0); + fsop->printDirectory(1); + EXPECT_EQ(fsop->fischl_create("/foo/bar/baz", mode, &fi), 0); + fsop->printDirectory(4); + // the following three testcases will fail + printf("Failing cases\n"); + EXPECT_TRUE(fsop->fischl_mkdir("foo/bar", mode) < 0); + EXPECT_TRUE(fsop->fischl_mkdir("/doesnt_exist/bar", mode) < 0); + EXPECT_TRUE(fsop->fischl_mkdir("/test/bar", mode) < 0); + EXPECT_TRUE(fsop->fischl_mkdir("/test", mode) < 0); + EXPECT_TRUE(fsop->fischl_mkdir("/foo/bar", mode) < 0); + EXPECT_TRUE(fsop->fischl_mkdir("/foo/bar/..", mode) < 0); +} + +TEST(FileOperationTest, WriteTest) { + // write to files (TODO: fischl_write) + //get inode info from disk + char buffer[IO_BLOCK_SIZE] = {0}; + INode_Data inode; + u_int64_t get_disk_inum; + struct fuse_file_info fi; + + //file test + printf("point 0\n"); + get_disk_inum = fsop->disk_namei("/test"); + printf("point 1\n"); + fsop->fischl_open("/test", &fi); + EXPECT_EQ(fi.fh, get_disk_inum); + buffer[0] = '1'; + fsop->fischl_write("/test", buffer, sizeof(buffer), 0, &fi); + printf("point 2\n"); + //other file baz + get_disk_inum = fsop->disk_namei("/foo/bar/baz"); + buffer[0] = '4'; + fsop->fischl_open("/foo/bar/baz", &fi); + EXPECT_EQ(fi.fh, get_disk_inum); + fsop->fischl_write("/foo/bar/baz", buffer, sizeof(buffer), 3*IO_BLOCK_SIZE, &fi); + buffer[0] = '5'; + fsop->fischl_write("/foo/bar/baz", buffer, sizeof(buffer), 101*IO_BLOCK_SIZE, &fi); +} + +TEST(FileOperationTest, RamDiskTest) { + //use find_entry(specify certain files or directory) + FileNode* get_dir; + u_int64_t get_disk_inum; + + get_dir = fischl_find_entry(fsop->root_node, "/test");//this is file + EXPECT_TRUE(get_dir != NULL);//detect this should find success + EXPECT_STREQ(get_dir->name, "test"); + get_disk_inum = fsop->disk_namei("/test"); + EXPECT_EQ(get_disk_inum, get_dir->inode_number); + + get_dir = fischl_find_entry(fsop->root_node, "/foo/bar/baz");//this is file + EXPECT_TRUE(get_dir != NULL);//detect this should find success + EXPECT_STREQ(get_dir->name, "baz"); + get_disk_inum = fsop->disk_namei("/foo/bar/baz"); + EXPECT_EQ(get_disk_inum, get_dir->inode_number); + + get_dir = fischl_find_entry(fsop->root_node, "/foo/bar/.."); + EXPECT_TRUE(get_dir != NULL);//detect this should find success + EXPECT_STREQ(get_dir->name, "foo"); + ASSERT_TRUE(get_dir->subdirectory != NULL);//secure it is directory + get_disk_inum = fsop->disk_namei("/foo/bar/.."); + EXPECT_EQ(get_disk_inum, get_dir->inode_number); + fsop->printDirectory(get_disk_inum); + + get_dir = fischl_find_entry(fsop->root_node, "/foo/bar/."); + EXPECT_TRUE(get_dir != NULL);//detect this should find success + EXPECT_STREQ(get_dir->name, "bar"); + ASSERT_TRUE(get_dir->subdirectory != NULL);//secure it is directory + get_disk_inum = fsop->disk_namei("/foo/bar/."); + EXPECT_EQ(get_disk_inum, get_dir->inode_number); + fsop->printDirectory(get_disk_inum); +} + + +TEST(FileOperationTest, ReadTest) { + // read files (TODO: fischl_read) + char read_buffer[IO_BLOCK_SIZE] = {0}; + INode_Data inode; + u_int64_t get_file_inum; + + //read test file + get_file_inum = fsop->namei("/test"); + inode.inode_num = get_file_inum; + fs->inode_manager->load_inode(&inode); + //fsop->read_datablock(inode, 0, read_buffer); + //EXPECT_EQ(read_buffer[0], '1'); + + //read test file again with fischl_read API + struct fuse_file_info fi; + fsop->fischl_open("/test", &fi); + EXPECT_EQ(fi.fh, get_file_inum); + fsop->fischl_read("/test", read_buffer, sizeof(read_buffer), 0, &fi); + EXPECT_EQ(read_buffer[0], '1'); + + //read baz file + get_file_inum= fsop->namei("/foo/bar/baz"); + // inode.inode_construct(get_file_inum, *H); + // fsop->read_datablock(inode, 3, read_buffer); + // EXPECT_EQ(read_buffer[0], '4'); + // fsop->read_datablock(inode, 101, read_buffer); + // EXPECT_EQ(read_buffer[0], '5'); + + //read baz file again with fischl_read API + fsop->fischl_open("/foo/bar/baz", &fi); + EXPECT_EQ(fi.fh, get_file_inum); + fsop->fischl_read("/foo/bar/baz", read_buffer, sizeof(read_buffer), 3*IO_BLOCK_SIZE, &fi); + EXPECT_EQ(read_buffer[0], '4'); + fsop->fischl_read("/foo/bar/baz", read_buffer, sizeof(read_buffer), 101*IO_BLOCK_SIZE, &fi); + EXPECT_EQ(read_buffer[0], '5'); +} + +TEST(FileOperationTest, PressureTest) { + mode_t mode;//set mode + mode = S_IRWXU | S_IRWXG | S_IRWXO;//future should test permission + EXPECT_EQ(fsop->fischl_mkdir("/pressure", mode), 0); + fsop->printDirectory(1); + + for(int i=0;i<100;i++){ + EXPECT_EQ(fsop->fischl_mkdir((prefix+std::to_string(i)).c_str(), mode), 0); + } + fsop->printDirectory(6); + for(int i=0;i<100;i++){ + EXPECT_EQ(fsop->namei((prefix+std::to_string(i)).c_str()),fsop->disk_namei((prefix+std::to_string(i)).c_str())); + } +} + +TEST(FileOperationTest, UnlinkTest) { + ssize_t file_pressure = fsop->namei("/pressure"); + fsop->printDirectory(file_pressure); + for(int i=0;i<100;i+=2){ + EXPECT_EQ(fsop->fischl_unlink((prefix+std::to_string(i)).c_str()), 0); + } + for(int i=0;i<4;i+=2){ + EXPECT_EQ(fsop->namei((prefix+std::to_string(i)).c_str()), (u_int64_t)(-1)); + } + printf("Finished Deallocating\n"); + fsop->printDirectory(file_pressure); + std::string newprefix = "/pressure/New"; + for(int i=0;i<100;i+=2){ + EXPECT_EQ(fsop->fischl_mkdir((newprefix+std::to_string(i)).c_str(), 0), 0); + } + for(int i=0;i<100;i+=2){ + u_int64_t inode_number = fsop->namei((newprefix+std::to_string(i)).c_str()); + assert(inode_number>0); + } + printf("Finished Reallocating\n"); + fsop->printDirectory(file_pressure); + + // long filename test + std::string longfilename = std::string(255,'A'); + EXPECT_EQ(fsop->fischl_mknod((std::string("/")+longfilename).c_str(),0,0),0); + u_int64_t filelong = fsop->namei((std::string("/")+longfilename).c_str()); + printf("/AAA...AAA is inode %llu, it is a file\n", filelong); +} + + +int main(int argc, char **argv) { + srand(time(NULL)); // Seed the random number generator + const char* d = (argc < 2) ? "/dev/vdc" : argv[1]; + + setupTestDirectory(&mock_root); + H = new FakeRawDisk(21504); + fs = new Fs(H); + fs->format(); + fsop = new FilesOperation(*H, fs); + + ::testing::InitGoogleTest(&argc, argv); + int result = RUN_ALL_TESTS(); + + total_dir_num = 0; + total_file_num = 0; + + traverseDirHierarchy(mock_root, 0);//mock_root + printf("Traverse Total: Dir %d, File %d\n",total_dir_num, total_file_num); + + // Cleanup + freeDirHierarchy(mock_root);//mock_root + printf("Free Total: Dir %d, File %d\n",total_free_dir, total_free_file); + + freeTree(fsop->root_node); + delete fsop; // First delete fsop which depends on H + delete H; + + return result; +} + +const char* generateRandomName(size_t length) { + const std::string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + std::string randomString; + + for (size_t i = 0; i < length; ++i) { + randomString += chars[rand() % chars.size()]; + } + + // Allocate memory and copy the string + char* name = new char[randomString.length() + 1]; + strcpy(name, randomString.c_str()); + return name; +} + +// Recursive function to create directory hierarchy +dir_test* createDirHierarchy(int level, int maxLevel) { + if (level > maxLevel) { + return nullptr; + } + + dir_test* head = nullptr; + dir_test* current = nullptr; + + for (int i = 0; i < 3; ++i) { + dir_test* newDir = new dir_test; + newDir->name = generateRandomName(6); // Generate a random name for the directory + newDir->inFile = nullptr; // Initialize file list to nullptr + newDir->subdir = createDirHierarchy(level + 1, maxLevel); // Recursively create subdirectories + newDir->next = nullptr; + + // Create file list for this directory + file_test* fileHead = nullptr; + file_test* fileCurrent = nullptr; + for (int j = 0; j < 3; ++j) { + file_test* newFile = new file_test; + newFile->name = generateRandomName(6); // Generate a random name for the file + newFile->next = nullptr; + + if (!fileHead) { + fileHead = newFile; + } else { + fileCurrent->next = newFile; + } + fileCurrent = newFile; + } + newDir->inFile = fileHead; + + // Add the new directory to the list + if (!head) { + head = newDir; + } else { + current->next = newDir; + } + current = newDir; + } + + return head; +} + +// Setup function for the test directory +void setupTestDirectory(dir_test** root) { + // Allocate memory for root + *root = new dir_test; + char* root_name = new char[2]; + strcpy(root_name, "/"); + (*root)->name = root_name; // use / as begin + (*root)->inFile = nullptr; // Initialize file list to nullptr + (*root)->subdir = createDirHierarchy(0, 1); + (*root)->next = nullptr; + file_test* fileHead = nullptr; + file_test* fileCurrent = nullptr; + for (int j = 0; j < 3; ++j) { + file_test* newFile = new file_test; + newFile->name = generateRandomName(6); // Generate a random name for the file + newFile->next = nullptr; + + if (!fileHead) { + fileHead = newFile; + } else { + fileCurrent->next = newFile; + } + fileCurrent = newFile; + } + (*root)->inFile = fileHead; +} + +// Function to free a list of files +void freeFileList(file_test* fileList) { + while (fileList != nullptr) { + file_test* temp = fileList; + fileList = fileList->next; + total_free_file++;//for debug + delete[] temp->name; // Free the name string + delete temp; // Free the current file + } +} + +// Recursive function to free the directory hierarchy +void freeDirHierarchy(dir_test* dir) { + while (dir != nullptr) { + dir_test* temp = dir; + dir = dir->next; + total_free_dir++;//for debug + freeFileList(temp->inFile); // Free the list of files in the directory + freeDirHierarchy(temp->subdir); // Recursively free subdirectories + delete[] temp->name; // Free the name string + delete temp; // Free the current directory + } +} + +// Function to print the list of files in a directory +void printFileList(const file_test* fileList) { + const file_test* currentFile = fileList; + while (currentFile != nullptr) { + // std::cout << " File: " << currentFile->name << std::endl; + total_file_num++; + currentFile = currentFile->next; + } +} + +void traverseDirHierarchy(const dir_test* dir, int depth = 0) { + while (dir != nullptr) { + // std::cout << "Depth " << depth << ", Directory: " << dir->name << std::endl; + total_dir_num++;//for debug + + // Print files in this directory + printFileList(dir->inFile); + // Recursively traverse subdirectories + traverseDirHierarchy(dir->subdir, depth + 1); + + // Go to the next directory at the same level + dir = dir->next; + } +} \ No newline at end of file