feat: fix file rename implementation

This commit is contained in:
2026-03-25 07:18:13 +01:00
parent f3ea22935f
commit 5918dc4ee5
10 changed files with 360 additions and 33 deletions
+4
View File
@@ -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);
}
+4
View File
@@ -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);
+3
View File
@@ -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;
+105
View File
@@ -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<int N>
@@ -1465,6 +1569,7 @@ namespace Fs::Ext2 {
Thunks<N>::Create,
Thunks<N>::Delete,
Thunks<N>::Mkdir,
Thunks<N>::Rename,
};
}
+216
View File
@@ -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<int N>
@@ -1497,6 +1712,7 @@ namespace Fs::Fat32 {
Thunks<N>::Create,
Thunks<N>::Delete,
Thunks<N>::Mkdir,
Thunks<N>::Rename,
};
}
+19
View File
@@ -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;
+2
View File
@@ -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);