diff --git a/kernel/src/Api/Filesystem.hpp b/kernel/src/Api/Filesystem.hpp index 65b205c..2236334 100644 --- a/kernel/src/Api/Filesystem.hpp +++ b/kernel/src/Api/Filesystem.hpp @@ -81,6 +81,10 @@ namespace Montauk { return Fs::Vfs::VfsMkdir(path); } + static int Sys_FRename(const char* oldPath, const char* newPath) { + return Fs::Vfs::VfsRename(oldPath, newPath); + } + static int Sys_DriveList(int* outDrives, int maxEntries) { return Fs::Vfs::VfsDriveList(outDrives, maxEntries); } diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp index b0af8fa..333b0ca 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -200,6 +200,10 @@ namespace Montauk { case SYS_FMKDIR: if (!ValidUserPtr(frame->arg1)) return -1; return (int64_t)Sys_FMkdir((const char*)frame->arg1); + case SYS_FRENAME: + if (!ValidUserPtr(frame->arg1)) return -1; + if (!ValidUserPtr(frame->arg2)) return -1; + return (int64_t)Sys_FRename((const char*)frame->arg1, (const char*)frame->arg2); case SYS_DRIVELIST: if (!ValidUserPtr(frame->arg1)) return -1; return (int64_t)Sys_DriveList((int*)frame->arg1, (int)frame->arg2); diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index b0ca727..ad19b04 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -182,6 +182,9 @@ namespace Montauk { static constexpr uint64_t SYS_SETUSER = 92; static constexpr uint64_t SYS_GETUSER = 93; + /* Filesystem.hpp */ + static constexpr uint64_t SYS_FRENAME = 94; + static constexpr int SOCK_TCP = 1; static constexpr int SOCK_UDP = 2; diff --git a/kernel/src/Fs/Ext2.cpp b/kernel/src/Fs/Ext2.cpp index 87101e9..2e6d1a6 100644 --- a/kernel/src/Fs/Ext2.cpp +++ b/kernel/src/Fs/Ext2.cpp @@ -1437,6 +1437,109 @@ namespace Fs::Ext2 { return 0; } + // ========================================================================= + // Rename — move a directory entry (inode stays the same) + // ========================================================================= + + static int RenameImpl(int inst, const char* oldPath, const char* newPath) { + if (inst < 0 || inst >= g_instanceCount || !g_instances[inst].active) return -1; + auto& self = g_instances[inst]; + + // Split old path + char oldParentPath[MaxNameLen]; + char oldFileName[MaxNameLen]; + SplitPath(oldPath, oldParentPath, MaxNameLen, oldFileName, MaxNameLen); + if (oldFileName[0] == '\0') return -1; + + // Split new path + char newParentPath[MaxNameLen]; + char newFileName[MaxNameLen]; + SplitPath(newPath, newParentPath, MaxNameLen, newFileName, MaxNameLen); + if (newFileName[0] == '\0') return -1; + + // Find old entry + uint32_t oldParentInodeNum; + Inode oldParentInode; + if (!TraversePath(self, oldParentPath, &oldParentInodeNum, &oldParentInode)) return -1; + if ((oldParentInode.i_mode & IMODE_TYPE_MASK) != IMODE_DIR) return -1; + + ParsedEntry oldEntry; + if (!FindInDirectory(self, oldParentInode, oldFileName, &oldEntry)) return -1; + + // Find new parent + uint32_t newParentInodeNum; + Inode newParentInode; + if (!TraversePath(self, newParentPath, &newParentInodeNum, &newParentInode)) return -1; + if ((newParentInode.i_mode & IMODE_TYPE_MASK) != IMODE_DIR) return -1; + + // If destination already exists, delete it + ParsedEntry destEntry; + if (FindInDirectory(self, newParentInode, newFileName, &destEntry)) { + Inode destInode; + if (!ReadInode(self, destEntry.inodeNum, &destInode)) return -1; + + bool destIsDir = (destInode.i_mode & IMODE_TYPE_MASK) == IMODE_DIR; + if (destIsDir) { + ParsedEntry children[1]; + int childCount = ReadDirectoryEntries(self, destInode, children, 1); + if (childCount > 0) return -1; + } + + if (!RemoveDirEntry(self, newParentInode, newFileName)) return -1; + + if (destIsDir) { + newParentInode.i_links_count--; + WriteInode(self, newParentInodeNum, &newParentInode); + } + + destInode.i_links_count--; + if (destIsDir) destInode.i_links_count--; + if (destInode.i_links_count <= 0) { + FreeInodeBlocks(self, destInode); + destInode.i_mode = 0; + WriteInode(self, destEntry.inodeNum, &destInode); + FreeInode(self, destEntry.inodeNum); + } else { + WriteInode(self, destEntry.inodeNum, &destInode); + } + + // Re-read new parent inode since RemoveDirEntry may have modified it + ReadInode(self, newParentInodeNum, &newParentInode); + } + + // Remove old directory entry (does not touch the inode) + // Re-read old parent in case it's the same dir and was modified above + ReadInode(self, oldParentInodeNum, &oldParentInode); + if (!RemoveDirEntry(self, oldParentInode, oldFileName)) return -1; + + // Add new directory entry pointing to the same inode + if (!AddDirEntry(self, newParentInodeNum, newParentInode, + oldEntry.inodeNum, newFileName, oldEntry.fileType)) { + // Failed to add — try to restore old entry + ReadInode(self, oldParentInodeNum, &oldParentInode); + AddDirEntry(self, oldParentInodeNum, oldParentInode, + oldEntry.inodeNum, oldFileName, oldEntry.fileType); + return -1; + } + + // If moving a directory across parents, update ".." entry and link counts + bool isDir = oldEntry.fileType == EXT2_FT_DIR; + if (isDir && oldParentInodeNum != newParentInodeNum) { + // Decrement old parent link count, increment new parent + ReadInode(self, oldParentInodeNum, &oldParentInode); + oldParentInode.i_links_count--; + WriteInode(self, oldParentInodeNum, &oldParentInode); + + ReadInode(self, newParentInodeNum, &newParentInode); + newParentInode.i_links_count++; + WriteInode(self, newParentInodeNum, &newParentInode); + + // TODO: update ".." entry inside the moved directory to point to new parent + } + + return 0; + } + // ========================================================================= // Template thunks — generate unique function pointers per instance // ========================================================================= @@ -1451,6 +1554,7 @@ namespace Fs::Ext2 { static int Create(const char* p) { return CreateImpl(N, p); } static int Delete(const char* p) { return DeleteImpl(N, p); } static int Mkdir(const char* p) { return MkdirImpl(N, p); } + static int Rename(const char* o, const char* n) { return RenameImpl(N, o, n); } }; template @@ -1465,6 +1569,7 @@ namespace Fs::Ext2 { Thunks::Create, Thunks::Delete, Thunks::Mkdir, + Thunks::Rename, }; } diff --git a/kernel/src/Fs/Fat32.cpp b/kernel/src/Fs/Fat32.cpp index cf2ec1c..793d86a 100644 --- a/kernel/src/Fs/Fat32.cpp +++ b/kernel/src/Fs/Fat32.cpp @@ -1469,6 +1469,220 @@ namespace Fs::Fat32 { return 0; } + // ========================================================================= + // Rename — atomic directory entry move + // ========================================================================= + + static int RenameImpl(int inst, const char* oldPath, const char* newPath) { + if (inst < 0 || inst >= g_instanceCount || !g_instances[inst].active) return -1; + auto& self = g_instances[inst]; + + // Split old path + char oldParentPath[MaxNameLen]; + char oldFileName[MaxNameLen]; + SplitPath(oldPath, oldParentPath, MaxNameLen, oldFileName, MaxNameLen); + if (oldFileName[0] == '\0') return -1; + + // Split new path + char newParentPath[MaxNameLen]; + char newFileName[MaxNameLen]; + SplitPath(newPath, newParentPath, MaxNameLen, newFileName, MaxNameLen); + if (newFileName[0] == '\0') return -1; + + // Traverse to old parent directory + ParsedEntry oldParent; + if (!TraversePath(inst, oldParentPath, &oldParent)) return -1; + if (!(oldParent.attributes & ATTR_DIRECTORY)) return -1; + + // Find old entry + ParsedEntry oldEntry; + if (!FindInDirectory(inst, oldParent.firstCluster, oldFileName, &oldEntry)) return -1; + + // Save the data we need from the old entry before deleting it + uint32_t savedFirstCluster = oldEntry.firstCluster; + uint32_t savedFileSize = oldEntry.fileSize; + uint8_t savedAttributes = oldEntry.attributes; + + // Traverse to new parent directory + ParsedEntry newParent; + if (!TraversePath(inst, newParentPath, &newParent)) return -1; + if (!(newParent.attributes & ATTR_DIRECTORY)) return -1; + + // If destination already exists, delete it (including its cluster chain) + ParsedEntry destEntry; + if (FindInDirectory(inst, newParent.firstCluster, newFileName, &destEntry)) { + // Don't allow overwriting a non-empty directory + if (destEntry.attributes & ATTR_DIRECTORY) { + ParsedEntry children[1]; + int childCount = ReadDirectory(inst, destEntry.firstCluster, children, 1); + if (childCount > 0) return -1; + } + // Free destination cluster chain + uint32_t cl = destEntry.firstCluster; + while (!IsEndOfChain(cl) && cl >= 2) { + uint32_t next = GetNextCluster(self, cl); + WriteFatEntry(self, cl, CLUSTER_FREE); + cl = next; + } + // Mark destination directory entries as deleted + // (reuse the delete-entry-marking logic inline) + uint32_t cluster = newParent.firstCluster; + while (!IsEndOfChain(cluster)) { + if (!ReadCluster(self, cluster)) return -1; + int perCluster = (int)(self.clusterSize / 32); + bool modified = false; + for (int i = 0; i < perCluster; i++) { + uint8_t* e = self.clusterBuf + i * 32; + if (e[0] == 0x00) break; + if (e[0] == 0xE5) continue; + uint64_t clusterPartSec = ClusterToPartSector(self, cluster); + uint32_t byteOff = i * 32; + uint64_t entrySec = clusterPartSec + (byteOff / self.bytesPerSector); + uint32_t entryOff = byteOff % self.bytesPerSector; + if (entrySec == destEntry.sfnPartSector && + entryOff == destEntry.sfnOffInSector) { + e[0] = 0xE5; + modified = true; + } + } + if (modified) WriteClusterData(self, cluster, self.clusterBuf); + cluster = GetNextCluster(self, cluster); + } + } + + // Mark old directory entries as deleted (but DO NOT free the cluster chain) + { + uint32_t cluster = oldParent.firstCluster; + bool inLfnRun = false; + + while (!IsEndOfChain(cluster)) { + if (!ReadCluster(self, cluster)) return -1; + int perCluster = (int)(self.clusterSize / 32); + bool modified = false; + + for (int i = 0; i < perCluster; i++) { + uint8_t* e = self.clusterBuf + i * 32; + if (e[0] == 0x00) goto old_write_back; + if (e[0] == 0xE5) { inLfnRun = false; continue; } + + uint8_t attr = e[11]; + if (attr == ATTR_LFN) { + uint8_t seq = e[0]; + if (seq & 0x40) { + inLfnRun = false; + int lfnCount = seq & 0x1F; + int sfnIdx = i + lfnCount; + if (sfnIdx < perCluster) { + uint64_t clusterPartSec = ClusterToPartSector(self, cluster); + uint32_t byteOff = sfnIdx * 32; + uint64_t sfnSec = clusterPartSec + (byteOff / self.bytesPerSector); + uint32_t sfnOff = byteOff % self.bytesPerSector; + if (sfnSec == oldEntry.sfnPartSector && + sfnOff == oldEntry.sfnOffInSector) { + inLfnRun = true; + e[0] = 0xE5; + modified = true; + } + } + } else if (inLfnRun) { + e[0] = 0xE5; + modified = true; + } + continue; + } + + uint64_t clusterPartSec = ClusterToPartSector(self, cluster); + uint32_t byteOff = i * 32; + uint64_t entrySec = clusterPartSec + (byteOff / self.bytesPerSector); + uint32_t entryOff = byteOff % self.bytesPerSector; + if (entrySec == oldEntry.sfnPartSector && + entryOff == oldEntry.sfnOffInSector) { + e[0] = 0xE5; + modified = true; + inLfnRun = false; + goto old_write_back; + } + inLfnRun = false; + } + + old_write_back: + if (modified) WriteClusterData(self, cluster, self.clusterBuf); + { + uint64_t clusterPartSec = ClusterToPartSector(self, cluster); + uint64_t clusterEndSec = clusterPartSec + self.sectorsPerCluster; + if (oldEntry.sfnPartSector >= clusterPartSec && + oldEntry.sfnPartSector < clusterEndSec) { + break; // done removing old entry + } + } + cluster = GetNextCluster(self, cluster); + } + } + + // Create new directory entry with the saved cluster chain and size + char shortName[11]; + GenerateShortName(newFileName, shortName); + MakeShortNameUnique(inst, newParent.firstCluster, shortName); + + uint8_t lfnEntries[20 * 32]; + int lfnCount = 0; + bool needsLfn = NeedsLfn(newFileName, shortName); + if (needsLfn) { + uint8_t checksum = LfnChecksum(shortName); + lfnCount = BuildLfnEntries(newFileName, checksum, lfnEntries); + } + + int totalSlots = lfnCount + 1; + DirSlotPos pos = FindFreeDirSlots(inst, newParent.firstCluster, totalSlots); + if (!pos.found) return -1; + + // Build all entries (LFN + SFN) contiguously + uint8_t allEntries[21 * 32]; + if (lfnCount > 0) { + memcpy(allEntries, lfnEntries, lfnCount * 32); + } + + // Build the SFN entry with preserved cluster/size + uint8_t* sfn = allEntries + lfnCount * 32; + memset(sfn, 0, 32); + memcpy(sfn, shortName, 11); + sfn[11] = savedAttributes; + + uint16_t clHi = (uint16_t)(savedFirstCluster >> 16); + uint16_t clLo = (uint16_t)(savedFirstCluster & 0xFFFF); + memcpy(sfn + 20, &clHi, 2); + memcpy(sfn + 26, &clLo, 2); + memcpy(sfn + 28, &savedFileSize, 4); + + // Write entries to disk, handling sector boundaries + uint64_t curSector = pos.partSector; + uint32_t curOff = pos.offsetInSec; + uint8_t sectorBuf[512]; + + if (!ReadPartSectors(self, curSector, 1, sectorBuf)) return -1; + + int bytesTotal = totalSlots * 32; + int written = 0; + + while (written < bytesTotal) { + int space = (int)self.bytesPerSector - (int)curOff; + int chunk = bytesTotal - written; + if (chunk > space) chunk = space; + + memcpy(sectorBuf + curOff, allEntries + written, chunk); + written += chunk; + + if (!WritePartSectors(self, curSector, 1, sectorBuf)) return -1; + if (written < bytesTotal) { + curSector++; + curOff = 0; + if (!ReadPartSectors(self, curSector, 1, sectorBuf)) return -1; + } + } + + return 0; + } + // ========================================================================= // Template thunks — generate unique function pointers per instance // ========================================================================= @@ -1483,6 +1697,7 @@ namespace Fs::Fat32 { static int Create(const char* p) { return CreateImpl(N, p); } static int Delete(const char* p) { return DeleteImpl(N, p); } static int Mkdir(const char* p) { return MkdirImpl(N, p); } + static int Rename(const char* o, const char* n) { return RenameImpl(N, o, n); } }; template @@ -1497,6 +1712,7 @@ namespace Fs::Fat32 { Thunks::Create, Thunks::Delete, Thunks::Mkdir, + Thunks::Rename, }; } diff --git a/kernel/src/Fs/Vfs.cpp b/kernel/src/Fs/Vfs.cpp index aba7192..604f5e1 100644 --- a/kernel/src/Fs/Vfs.cpp +++ b/kernel/src/Fs/Vfs.cpp @@ -211,6 +211,25 @@ namespace Fs::Vfs { return count; } + int VfsRename(const char* oldPath, const char* newPath) { + int oldDrive, newDrive; + const char* oldLocal; + const char* newLocal; + + if (!ParsePath(oldPath, oldDrive, oldLocal)) return -1; + if (!ParsePath(newPath, newDrive, newLocal)) return -1; + + // Cross-drive rename not supported + if (oldDrive != newDrive) return -1; + if (oldDrive < 0 || oldDrive >= MaxDrives || driveTable[oldDrive] == nullptr) return -1; + if (driveTable[oldDrive]->Rename == nullptr) return -1; + + vfsLock.Acquire(); + int result = driveTable[oldDrive]->Rename(oldLocal, newLocal); + vfsLock.Release(); + return result; + } + int VfsReadDir(const char* path, const char** outNames, int maxEntries) { int drive; const char* localPath; diff --git a/kernel/src/Fs/Vfs.hpp b/kernel/src/Fs/Vfs.hpp index b2d0cc7..162ffcf 100644 --- a/kernel/src/Fs/Vfs.hpp +++ b/kernel/src/Fs/Vfs.hpp @@ -23,6 +23,7 @@ namespace Fs::Vfs { int (*Create)(const char* path); int (*Delete)(const char* path); int (*Mkdir)(const char* path); + int (*Rename)(const char* oldPath, const char* newPath); }; void Initialize(); @@ -37,6 +38,7 @@ namespace Fs::Vfs { void VfsClose(int handle); int VfsReadDir(const char* path, const char** outNames, int maxEntries); int VfsMkdir(const char* path); + int VfsRename(const char* oldPath, const char* newPath); // Returns number of registered drives, fills outDrives[] with their indices int VfsDriveList(int* outDrives, int maxEntries); diff --git a/programs/include/libc/montauk.h b/programs/include/libc/montauk.h index 3100347..b194d70 100644 --- a/programs/include/libc/montauk.h +++ b/programs/include/libc/montauk.h @@ -89,6 +89,7 @@ extern "C" { #define MTK_SYS_WINSETCURSOR 68 #define MTK_SYS_FDELETE 77 #define MTK_SYS_FMKDIR 78 +#define MTK_SYS_FRENAME 94 #define MTK_SYS_DRIVELIST 79 #define MTK_SYS_AUDIOOPEN 80 #define MTK_SYS_AUDIOCLOSE 81 @@ -377,6 +378,10 @@ static inline int mtk_mkdir(const char *path) { return (int)_mtk_syscall1(MTK_SYS_FMKDIR, (long)path); } +static inline int mtk_rename(const char *oldpath, const char *newpath) { + return (int)_mtk_syscall2(MTK_SYS_FRENAME, (long)oldpath, (long)newpath); +} + static inline int mtk_readdir(const char *path, const char **names, int max) { return (int)_mtk_syscall3(MTK_SYS_READDIR, (long)path, (long)names, (long)max); } diff --git a/programs/lib/libc/libc.c b/programs/lib/libc/libc.c index d3edb5a..842df44 100644 --- a/programs/lib/libc/libc.c +++ b/programs/lib/libc/libc.c @@ -95,6 +95,7 @@ static inline long _zos_syscall4(long nr, long a1, long a2, long a3, long a4) { #define SYS_FCREATE 42 #define SYS_FDELETE 77 #define SYS_FMKDIR 78 +#define SYS_FRENAME 94 /* ======================================================================== errno @@ -1217,40 +1218,8 @@ int remove(const char *path) { } int rename(const char *oldpath, const char *newpath) { - /* No atomic rename syscall -- copy + delete */ if (oldpath == NULL || newpath == NULL) return -1; - - int src = (int)_zos_syscall1(SYS_OPEN, (long)oldpath); - if (src < 0) return -1; - - unsigned long sz = (unsigned long)_zos_syscall1(SYS_GETSIZE, (long)src); - - /* Create destination */ - _zos_syscall1(SYS_FDELETE, (long)newpath); - int dst = (int)_zos_syscall1(SYS_FCREATE, (long)newpath); - if (dst < 0) { - _zos_syscall1(SYS_CLOSE, (long)src); - return -1; - } - - /* Copy in chunks */ - char copybuf[4096]; - unsigned long off = 0; - while (off < sz) { - unsigned long chunk = sz - off; - if (chunk > sizeof(copybuf)) chunk = sizeof(copybuf); - int r = (int)_zos_syscall4(SYS_READ, (long)src, - (long)copybuf, (long)off, (long)chunk); - if (r <= 0) break; - _zos_syscall4(SYS_FWRITE, (long)dst, - (long)copybuf, (long)off, (long)r); - off += (unsigned long)r; - } - - _zos_syscall1(SYS_CLOSE, (long)src); - _zos_syscall1(SYS_CLOSE, (long)dst); - _zos_syscall1(SYS_FDELETE, (long)oldpath); - return 0; + return (int)_zos_syscall2(SYS_FRENAME, (long)oldpath, (long)newpath); } void perror(const char *s) { diff --git a/programs/lib/libc/obj/libc.o b/programs/lib/libc/obj/libc.o index 8d5d43a..7c0b06b 100644 Binary files a/programs/lib/libc/obj/libc.o and b/programs/lib/libc/obj/libc.o differ