feat: fix file rename implementation

This commit is contained in:
2026-03-25 07:18:13 +01:00
parent 6e471c2eb4
commit 8295899b8f
10 changed files with 360 additions and 33 deletions
+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,
};
}