diff --git a/kernel/include/kernel/fs.h b/kernel/include/kernel/fs.h index 058571d..8145687 100644 --- a/kernel/include/kernel/fs.h +++ b/kernel/include/kernel/fs.h @@ -38,6 +38,7 @@ typedef struct fs_t { folder_inode_t* root; uint32_t uid; uint32_t (*create)(struct fs_t*, const char*, uint32_t, uint32_t); + int32_t (*rename)(struct fs_t*, uint32_t, uint32_t, uint32_t); int32_t (*unlink)(struct fs_t*, uint32_t, uint32_t); uint32_t (*read)(struct fs_t*, uint32_t, uint32_t, uint8_t*, uint32_t); uint32_t (*append)(struct fs_t*, uint32_t, uint8_t*, uint32_t); @@ -52,6 +53,7 @@ typedef sos_directory_entry_t* (*fs_readdir_t)(struct fs_t*, uint32_t, uint32_t) typedef uint32_t (*fs_append_t)(struct fs_t*, uint32_t, uint8_t*, uint32_t); typedef uint32_t (*fs_read_t)(struct fs_t*, uint32_t, uint32_t, uint8_t*, uint32_t); typedef int32_t (*fs_unlink_t)(struct fs_t*, uint32_t, uint32_t); +typedef int32_t (*fs_rename_t)(struct fs_t*, uint32_t, uint32_t, uint32_t); typedef uint32_t (*fs_create_t)(struct fs_t*, const char*, uint32_t, uint32_t); typedef int32_t (*fs_close_t)(struct fs_t*, uint32_t); @@ -61,6 +63,7 @@ char* fs_normalize_path(const char* p); inode_t* fs_open(const char* path, uint32_t mode); uint32_t fs_mkdir(const char* path, uint32_t mode); int32_t fs_unlink(const char* path); +int32_t fs_rename(const char* oldp, const char* newp); int32_t fs_close(inode_t* in); uint32_t fs_read(inode_t* in, uint32_t offset, uint8_t* buf, uint32_t size); uint32_t fs_write(inode_t* in, uint8_t* buf, uint32_t size); diff --git a/kernel/include/kernel/uapi/uapi_syscall.h b/kernel/include/kernel/uapi/uapi_syscall.h index e56c00a..b77176a 100644 --- a/kernel/include/kernel/uapi/uapi_syscall.h +++ b/kernel/include/kernel/uapi/uapi_syscall.h @@ -23,7 +23,8 @@ #define SYS_CHDIR 17 #define SYS_GETCWD 18 #define SYS_UNLINK 19 -#define SYS_MAX 20 // First invalid syscall number +#define SYS_RENAME 20 +#define SYS_MAX 21 // First invalid syscall number #define SYS_INFO_UPTIME 1 #define SYS_INFO_MEMORY 2 diff --git a/kernel/src/misc/ext2.c b/kernel/src/misc/ext2.c index 277a95e..dbb8e73 100644 --- a/kernel/src/misc/ext2.c +++ b/kernel/src/misc/ext2.c @@ -155,6 +155,7 @@ typedef struct ext2_fs_t { uint32_t ext2_create(ext2_fs_t* fs, const char* name, uint32_t type, uint32_t parent_inode); int32_t ext2_unlink(ext2_fs_t* fs, uint32_t d_ino, uint32_t ino); +int32_t ext2_rename(ext2_fs_t* fs, uint32_t dir_ino, uint32_t ino, uint32_t destdir_ino); uint32_t ext2_mkdir(ext2_fs_t* fs, const char* name, uint32_t parent_inode); uint32_t ext2_read(ext2_fs_t* fs, uint32_t inode, uint32_t offset, uint8_t* buf, uint32_t size); uint32_t ext2_append(ext2_fs_t* fs, uint32_t inode, uint8_t* data, uint32_t size); @@ -205,6 +206,7 @@ fs_t* init_ext2(uint8_t* data, uint32_t len) { e2fs->fs.append = (fs_append_t) ext2_append; e2fs->fs.create = (fs_create_t) ext2_create; + e2fs->fs.rename = (fs_rename_t) ext2_rename; e2fs->fs.get_fs_inode = (fs_get_fs_inode_t) ext2_get_fs_inode; e2fs->fs.read = (fs_read_t) ext2_read; e2fs->fs.readdir = (fs_readdir_t) ext2_readdir; @@ -254,6 +256,57 @@ int32_t ext2_unlink(ext2_fs_t* fs, uint32_t d_ino, uint32_t ino) { return 0; } +/* Move `ino` whose parent directory is `dir_ino`, to the directory `destdir_ino`. + */ +int32_t ext2_rename(ext2_fs_t* fs, uint32_t dir_ino, uint32_t ino, uint32_t destdir_ino) { + list_t* entries = directory_to_entries(fs, dir_ino); + list_t* iter; + dentry_t* ent; + + if (!entries) { + return -1; + } + + /* Look for the file to move, remove it from the list */ + list_for_each(iter, ent, entries) { + if (ent->inode == ino) { + list_del(iter); + break; + } + } + + /* Not found */ + if (iter == entries) { + free_directory_entries(entries); + kfree(entries); + + return -1; + } + + /* Update the destination dir's list */ + list_t* new_entries = directory_to_entries(fs, destdir_ino); + + if (!new_entries) { + free_directory_entries(entries); + kfree(entries); + + return -1; + } + + list_add(new_entries, make_directory_entry(ent->name, ent->inode, ent->type)); + + /* Write changes to disk */ + write_directory_entries(fs, destdir_ino, new_entries); + write_directory_entries(fs, dir_ino, entries); + + free_directory_entries(entries); + kfree(entries); + free_directory_entries(new_entries); + kfree(new_entries); + + return 0; +} + /* Appends `size` bytes from `data` to the file pointed to by `inode`. * Returns the number of bytes written. */ @@ -906,6 +959,11 @@ static list_t* directory_to_entries(ext2_fs_t* fs, uint32_t ino) { ext2_inode_t* in = get_inode(fs, ino); uint32_t offset = 0; + if (!in) { + kfree(list); + return NULL; + } + while (offset < in->size_lower) { ent = ext2_readdir(fs, ino, offset); diff --git a/kernel/src/misc/fs.c b/kernel/src/misc/fs.c index 4bdb8f9..e5ce2b2 100644 --- a/kernel/src/misc/fs.c +++ b/kernel/src/misc/fs.c @@ -261,6 +261,78 @@ int32_t fs_unlink(const char* path) { return 0; } +/* Renames the file pointed to by `oldp` to `newp`, moving it across directories + * as needed. See "man 2 rename" for the expected behavior. + * Note: doesn't support renaming a directory to an existing empty directory. + */ +int32_t fs_rename(const char* oldp, const char* newp) { + inode_t* old = fs_open(oldp, O_RDONLY); + inode_t* new = fs_open(newp, O_RDONLY); + + if (!old) { + return -1; + } + + if (old->type == DENT_DIRECTORY && new) { + return -1; + } + + /* Both point to the same file */ + if (new == old) { + return 0; + } + + /* Moving across filesystems is unsupported right now */ + if (new && new->fs != old->fs) { + return -1; + } + + /* Destination will be replaced, remove it */ + if (new) { + if (fs_unlink(newp) == -1) { + return -1; + } + } + + /* Do the renaming on the fs */ + char* noldp = fs_normalize_path(oldp); + char* nnewp = fs_normalize_path(newp); + folder_inode_t* src = (folder_inode_t*) fs_open(dirname(noldp), O_RDONLY); + folder_inode_t* dst = (folder_inode_t*) fs_open(dirname(nnewp), O_RDONLY); + + int32_t ret = FS(old)->rename(FS(old), src->ino.inode_no, old->inode_no, dst->ino.inode_no); + + if (ret == -1) { + kfree(noldp); + kfree(nnewp); + return -1; + } + + /* Rename in VFS too: remove from the original parent directory */ + list_t* to_iterate = old->type == DENT_DIRECTORY ? + &src->subfolders : &src->subfiles; + list_t* iter; + tnode_t* tn; + list_for_each(iter, tn, to_iterate) { + if (tn->inode->inode_no == old->inode_no) { + list_del(iter); + break; + } + } + + /* Add to the destination parent directory */ + kfree(tn->name); + tn->name = strdup(basename(nnewp)); + list_t* to_add_to = old->type == DENT_DIRECTORY ? + &dst->subfolders : &dst->subfiles; + list_add(to_add_to, tn); + + kfree(noldp); + kfree(nnewp); + + return 0; +} + /* A process has released its grip on a file: notify the fs. */ int32_t fs_close(inode_t* in) { diff --git a/kernel/src/sys/syscall.c b/kernel/src/sys/syscall.c index e13a0bb..957a747 100644 --- a/kernel/src/sys/syscall.c +++ b/kernel/src/sys/syscall.c @@ -35,6 +35,7 @@ static void syscall_ftell(registers_t* regs); static void syscall_chdir(registers_t* regs); static void syscall_getcwd(registers_t* regs); static void syscall_unlink(registers_t* regs); +static void syscall_rename(registers_t* regs); handler_t syscall_handlers[SYSCALL_NUM] = { 0 }; @@ -60,6 +61,7 @@ void init_syscall() { syscall_handlers[SYS_CHDIR] = syscall_chdir; syscall_handlers[SYS_GETCWD] = syscall_getcwd; syscall_handlers[SYS_UNLINK] = syscall_unlink; + syscall_handlers[SYS_RENAME] = syscall_rename; } static void syscall_handler(registers_t* regs) { @@ -252,4 +254,11 @@ static void syscall_unlink(registers_t* regs) { char* path = (char*) regs->ebx; regs->eax = fs_unlink(path); +} + +static void syscall_rename(registers_t* regs) { + char* old_path = (char*) regs->ebx; + char* new_path = (char*) regs->ecx; + + regs->eax = fs_rename(old_path, new_path); } \ No newline at end of file diff --git a/libc/include/stdio.h b/libc/include/stdio.h index 46f0c93..4ca7450 100644 --- a/libc/include/stdio.h +++ b/libc/include/stdio.h @@ -14,6 +14,8 @@ int printf(const char* __restrict, ...); int putchar(int); int puts(const char*); +int rename(const char* old, const char* new); + #ifndef _KERNEL_ FILE* fopen(const char* path, const char* mode); int fclose(FILE* stream); diff --git a/modules/src/mv.c b/modules/src/mv.c new file mode 100644 index 0000000..238d9d2 --- /dev/null +++ b/modules/src/mv.c @@ -0,0 +1,19 @@ +#include + +#include + +int main(int argc, char* argv[]) { + if (argc < 3) { + printf("usage: %s SOURCE DEST\n", argv[0]); + return 1; + } + + int ret = rename(argv[1], argv[2]); + + if (ret) { + printf("%s: failed\n"); + return 2; + } + + return 0; +} \ No newline at end of file