43 [[nodiscard]]
static unsigned normalizeFAT(
unsigned cluster)
48 unsigned DirAsDSK::readFATHelper(
const SectorBuffer* fatBuf,
unsigned cluster)
const
51 assert(cluster < maxCluster);
52 const auto* buf = fatBuf[0].raw;
53 const auto* p = &buf[(cluster * 3) / 2];
54 unsigned result = (cluster & 1)
55 ? (p[0] >> 4) + (p[1] << 4)
56 : p[0] + ((p[1] & 0x0F) << 8);
57 return normalizeFAT(result);
60 void DirAsDSK::writeFATHelper(SectorBuffer* fatBuf,
unsigned cluster,
unsigned val)
const
63 assert(cluster < maxCluster);
64 auto* buf = fatBuf[0].raw;
65 auto* p = &buf[(cluster * 3) / 2];
67 p[0] = (p[0] & 0x0F) + (val << 4);
71 p[1] = (p[1] & 0xF0) + ((val >> 8) & 0x0F);
75 SectorBuffer* DirAsDSK::fat()
79 SectorBuffer* DirAsDSK::fat2()
81 return §ors[firstSector2ndFAT];
85 unsigned DirAsDSK::readFAT(
unsigned cluster)
87 return readFATHelper(fat(), cluster);
91 void DirAsDSK::writeFAT12(
unsigned cluster,
unsigned val)
93 writeFATHelper(fat (), cluster, val);
94 writeFATHelper(fat2(), cluster, val);
100 unsigned DirAsDSK::findNextFreeCluster(
unsigned cluster)
102 assert(cluster < maxCluster);
106 }
while ((cluster < maxCluster) && (readFAT(cluster) !=
FREE_FAT));
109 unsigned DirAsDSK::findFirstFreeCluster()
115 unsigned DirAsDSK::getFreeCluster()
117 unsigned cluster = findFirstFreeCluster();
118 if (cluster == maxCluster) {
119 throw MSXException(
"disk full");
124 unsigned DirAsDSK::clusterToSector(
unsigned cluster)
const
127 assert(cluster < maxCluster);
132 std::pair<unsigned, unsigned> DirAsDSK::sectorToClusterOffset(
unsigned sector)
const
134 assert(sector >= firstDataSector);
135 assert(sector < nofSectors);
136 sector -= firstDataSector;
139 return {cluster, offset};
141 unsigned DirAsDSK::sectorToCluster(
unsigned sector)
const
143 auto [cluster, offset] = sectorToClusterOffset(sector);
147 MSXDirEntry& DirAsDSK::msxDir(DirIndex dirIndex)
149 assert(dirIndex.sector < nofSectors);
151 return sectors[dirIndex.sector].dirEntry[dirIndex.idx];
155 unsigned DirAsDSK::nextMsxDirSector(
unsigned sector)
157 if (sector < firstDataSector) {
159 assert(firstDirSector <= sector);
161 if (sector == firstDataSector) {
168 auto [cluster, offset] = sectorToClusterOffset(sector);
173 unsigned nextCl = readFAT(cluster);
178 return clusterToSector(nextCl);
183 bool DirAsDSK::checkMSXFileExists(
184 const string& msxFilename,
unsigned msxDirSector)
186 vector<bool> visited(nofSectors,
false);
188 if (visited[msxDirSector]) {
192 visited[msxDirSector] =
true;
195 DirIndex dirIndex(msxDirSector, idx);
196 if (memcmp(msxDir(dirIndex).
filename,
197 msxFilename.data(), 8 + 3) == 0) {
201 msxDirSector = nextMsxDirSector(msxDirSector);
202 }
while (msxDirSector !=
unsigned(-1));
210 DirAsDSK::DirIndex DirAsDSK::findHostFileInDSK(std::string_view hostName)
212 for (
const auto& [dirIdx, mapDir] : mapDirs) {
213 if (mapDir.hostName == hostName) {
217 return {unsigned(-1), unsigned(-1)};
221 bool DirAsDSK::checkFileUsedInDSK(std::string_view hostName)
223 DirIndex dirIndex = findHostFileInDSK(hostName);
224 return dirIndex.sector != unsigned(-1);
227 static string hostToMsxName(
string hostName)
231 return (a ==
' ') ?
'_' : ::toupper(a);
236 string result(8 + 3,
' ');
237 memcpy(result.data() + 0, file.data(), std::min<size_t>(8, file.size()));
238 memcpy(result.data() + 8, ext .data(), std::min<size_t>(3, ext .size()));
243 static string msxToHostName(
const char* msxName)
246 for (
unsigned i = 0; (i < 8) && (msxName[i] !=
' '); ++i) {
247 result += char(tolower(msxName[i]));
249 if (msxName[8] !=
' ') {
251 for (
unsigned i = 8; (i < (8 + 3)) && (msxName[i] !=
' '); ++i) {
252 result += char(tolower(msxName[i]));
263 , diskChanger(diskChanger_)
265 , hostDir(FileOperations::
expandTilde(hostDir_.getResolved() +
'/'))
266 , syncMode(syncMode_)
267 , lastAccess(EmuTime::zero())
274 , sectors(nofSectors)
288 memset(sectors.data(), 0xE5,
sizeof(
SectorBuffer) * nofSectors);
291 byte mediaDescriptor = (numSides == 2) ? 0xF9 : 0xF8;
295 memcpy(§ors[0], &protoBootSector,
sizeof(protoBootSector));
296 auto& bootSector = sectors[0].bootSector;
301 bootSector.nrSectors = nofSectors;
302 bootSector.descriptor = mediaDescriptor;
303 bootSector.sectorsFat = nofSectorsPerFat;
305 bootSector.nrSides = numSides;
313 fat ()->
raw[0] = mediaDescriptor; fat ()->
raw[1] = 0xFF; fat ()->
raw[2] = 0xFF;
314 fat2()->
raw[0] = mediaDescriptor; fat2()->
raw[1] = 0xFF; fat2()->
raw[2] = 0xFF;
320 assert(mapDirs.empty());
333 bool needSync = [&] {
335 auto now = scheduler->getCurrentTime();
336 auto delta = now - lastAccess;
352 assert(sector < nofSectors);
359 bool needSync = [&] {
361 auto now = scheduler->getCurrentTime();
362 auto delta = now - lastAccess;
381 memcpy(&buf, §ors[sector],
sizeof(buf));
384 void DirAsDSK::syncWithHost()
389 checkDeletedHostFiles();
395 checkModifiedHostFiles();
398 addNewHostFiles({}, firstDirSector);
401 void DirAsDSK::checkDeletedHostFiles()
405 for (
const auto& [dirIdx, mapDir] :
copy) {
406 if (!mapDirs.contains(dirIdx)) {
417 auto fullHostName =
tmpStrCat(hostDir, mapDir.hostName);
418 bool isMSXDirectory = (msxDir(dirIdx).
attrib &
429 deleteMSXFile(dirIdx);
434 void DirAsDSK::deleteMSXFile(DirIndex dirIndex)
437 mapDirs.erase(dirIndex);
439 char c = msxDir(dirIndex).
filename[0];
440 if (c ==
one_of(0,
char(0xE5))) {
448 const char* msxName = msxDir(dirIndex).
filename;
449 if ((memcmp(msxName,
". ", 11) == 0) ||
450 (memcmp(msxName,
".. ", 11) == 0)) {
458 deleteMSXFilesInDir(clusterToSector(cluster));
464 msxDir(dirIndex).
filename[0] = char(0xE5);
467 freeFATChain(msxDir(dirIndex).startCluster);
470 void DirAsDSK::deleteMSXFilesInDir(
unsigned msxDirSector)
472 vector<bool> visited(nofSectors,
false);
474 if (visited[msxDirSector]) {
478 visited[msxDirSector] =
true;
481 deleteMSXFile(DirIndex(msxDirSector, idx));
483 msxDirSector = nextMsxDirSector(msxDirSector);
484 }
while (msxDirSector !=
unsigned(-1));
487 void DirAsDSK::freeFATChain(
unsigned cluster)
490 while ((
FIRST_CLUSTER <= cluster) && (cluster < maxCluster)) {
491 unsigned nextCl = readFAT(cluster);
497 void DirAsDSK::checkModifiedHostFiles()
500 for (
const auto& [dirIdx, mapDir] :
copy) {
501 if (!mapDirs.contains(dirIdx)) {
505 auto fullHostName =
tmpStrCat(hostDir, mapDir.hostName);
506 bool isMSXDirectory = (msxDir(dirIdx).
attrib &
520 if (!isMSXDirectory &&
521 ((mapDir.mtime != fst.st_mtime) ||
522 (mapDir.filesize != size_t(fst.st_size)))) {
523 importHostFile(dirIdx, fst);
528 deleteMSXFile(dirIdx);
536 assert(mapDirs.contains(dirIndex));
539 setMSXTimeStamp(dirIndex, fst);
554 size_t hostSize = fst.st_size;
555 auto& mapDir = mapDirs[dirIndex];
556 mapDir.filesize = hostSize;
557 mapDir.mtime = fst.st_mtime;
559 bool moreClustersInChain =
true;
565 moreClustersInChain =
false;
566 curCl = findFirstFreeCluster();
569 auto remainingSize = hostSize;
572 File file(hostDir + mapDir.hostName,
575 while (remainingSize && (curCl < maxCluster)) {
576 unsigned logicalSector = clusterToSector(curCl);
578 unsigned sector = logicalSector + i;
579 assert(sector < nofSectors);
580 auto* buf = §ors[sector];
585 if (remainingSize == 0) {
593 writeFAT12(prevCl, curCl);
601 if (moreClustersInChain) {
602 curCl = readFAT(curCl);
605 (curCl >= maxCluster)) {
608 moreClustersInChain =
false;
609 curCl = findFirstFreeCluster();
612 curCl = findNextFreeCluster(curCl);
615 if (remainingSize != 0) {
617 mapDir.hostName,
" truncated.");
619 }
catch (FileException& e) {
622 mapDir.hostName,
": ", e.getMessage(),
623 " Truncated file on MSX disk.");
637 if (moreClustersInChain) {
642 msxDir(dirIndex).
size = uint32_t(hostSize - remainingSize);
654 time_t mtime = fst.st_mtime;
655 auto* mtim = localtime(&mtime);
656 int t1 = mtim ? (mtim->tm_sec >> 1) + (mtim->tm_min << 5) +
657 (mtim->tm_hour << 11)
659 msxDir(dirIndex).
time = t1;
660 int t2 = mtim ? mtim->tm_mday + ((mtim->tm_mon + 1) << 5) +
661 ((mtim->tm_year + 1900 - 1980) << 9)
663 msxDir(dirIndex).
date = t2;
674 static size_t weight(
const string& hostName)
682 result += ext.size() * 10;
684 result += file.size();
688 void DirAsDSK::addNewHostFiles(
const string& hostSubDir,
unsigned msxDirSector)
693 vector<string> hostNames;
695 ReadDir dir(
tmpStrCat(hostDir, hostSubDir));
696 while (
auto* d = dir.getEntry()) {
697 hostNames.emplace_back(d->d_name);
701 [](
const string& l,
const string& r) {
return weight(l) < weight(r); });
703 for (
auto& hostName : hostNames) {
710 auto fullHostName =
tmpStrCat(hostDir, hostSubDir, hostName);
713 throw MSXException(
"Error accessing ", fullHostName);
716 addNewDirectory(hostSubDir, hostName, msxDirSector, fst);
718 addNewHostFile(hostSubDir, hostName, msxDirSector, fst);
720 throw MSXException(
"Not a regular file: ", fullHostName);
722 }
catch (MSXException& e) {
728 void DirAsDSK::addNewDirectory(
const string& hostSubDir,
const string& hostName,
731 DirIndex dirIndex = findHostFileInDSK(
tmpStrCat(hostSubDir, hostName));
732 unsigned newMsxDirSector;
733 if (dirIndex.sector ==
unsigned(-1)) {
736 unsigned cluster = getFreeCluster();
741 dirIndex = fillMSXDirEntry(hostSubDir, hostName, msxDirSector);
747 setMSXTimeStamp(dirIndex, fst);
752 newMsxDirSector = clusterToSector(cluster);
754 memset(§ors[newMsxDirSector + i], 0,
SECTOR_SIZE);
756 DirIndex idx0(newMsxDirSector, 0);
757 DirIndex idx1(newMsxDirSector, 1);
758 memset(msxDir(idx0).
filename,
' ', 11);
759 memset(msxDir(idx1).
filename,
' ', 11);
760 memset(msxDir(idx0).
filename,
'.', 1);
761 memset(msxDir(idx1).
filename,
'.', 2);
764 setMSXTimeStamp(idx0, fst);
765 setMSXTimeStamp(idx1, fst);
767 msxDir(idx1).
startCluster = msxDirSector == firstDirSector
768 ? 0 : sectorToCluster(msxDirSector);
783 newMsxDirSector = clusterToSector(cluster);
787 addNewHostFiles(
strCat(hostSubDir, hostName,
'/'), newMsxDirSector);
790 void DirAsDSK::addNewHostFile(
const string& hostSubDir,
const string& hostName,
793 if (checkFileUsedInDSK(
tmpStrCat(hostSubDir, hostName))) {
798 int diskSpace = (nofSectors - firstDataSector) *
SECTOR_SIZE;
799 if (fst.st_size > diskSpace) {
801 hostDir, hostSubDir, hostName);
805 DirIndex dirIndex = fillMSXDirEntry(hostSubDir, hostName, msxDirSector);
806 importHostFile(dirIndex, fst);
809 DirAsDSK::DirIndex DirAsDSK::fillMSXDirEntry(
810 const string& hostSubDir,
const string& hostName,
unsigned msxDirSector)
812 string hostPath = hostSubDir + hostName;
815 DirIndex dirIndex = getFreeDirEntry(msxDirSector);
818 string msxFilename = hostToMsxName(hostName);
819 if (checkMSXFileExists(msxFilename, msxDirSector)) {
822 "MSX name ", msxToHostName(msxFilename.c_str()),
828 mapDirs[dirIndex].hostName = hostPath;
829 memset(&msxDir(dirIndex), 0,
sizeof(MSXDirEntry));
830 memcpy(msxDir(dirIndex).
filename, msxFilename.data(), 8 + 3);
832 }
catch (MSXException& e) {
833 throw MSXException(
"Couldn't add ", hostPath,
": ",
838 DirAsDSK::DirIndex DirAsDSK::getFreeDirEntry(
unsigned msxDirSector)
840 vector<bool> visited(nofSectors,
false);
842 if (visited[msxDirSector]) {
844 throw MSXException(
"cycle in FAT");
846 visited[msxDirSector] =
true;
849 DirIndex dirIndex(msxDirSector, idx);
850 const char* msxName = msxDir(dirIndex).
filename;
851 if (msxName[0] ==
one_of(
char(0x00),
char(0xE5))) {
854 assert(!mapDirs.contains(dirIndex));
858 unsigned sector = nextMsxDirSector(msxDirSector);
859 if (sector ==
unsigned(-1))
break;
860 msxDirSector = sector;
864 if (msxDirSector == (firstDataSector - 1)) {
866 throw MSXException(
"root directory full");
871 unsigned cluster = sectorToCluster(msxDirSector);
872 unsigned newCluster = getFreeCluster();
873 unsigned sector = clusterToSector(newCluster);
875 writeFAT12(cluster, newCluster);
876 writeFAT12(newCluster,
EOF_FAT);
884 assert(sector_ < nofSectors);
886 auto sector = unsigned(sector_);
890 lastAccess = scheduler->getCurrentTime();
899 }
else if (sector < firstSector2ndFAT) {
900 writeFATSector(sector, buf);
901 }
else if (sector < firstDirSector) {
904 memcpy(§ors[sector], &buf,
sizeof(buf));
905 }
else if (DirIndex dirDirIndex; isDirSector(sector, dirDirIndex)) {
907 writeDIRSector(sector, dirDirIndex, buf);
909 writeDataSector(sector, buf);
913 void DirAsDSK::writeFATSector(
unsigned sector,
const SectorBuffer& buf)
916 vector<SectorBuffer> oldFAT(nofSectorsPerFat);
917 memcpy(&oldFAT[0], fat(),
SECTOR_SIZE * nofSectorsPerFat);
920 memcpy(§ors[sector], &buf,
sizeof(buf));
924 if (readFAT(i) != readFATHelper(oldFAT.data(), i)) {
925 exportFileFromFATChange(i, oldFAT.data());
936 assert(readFAT(i) == readFATHelper(oldFAT.data(), i)); (void)i;
940 void DirAsDSK::exportFileFromFATChange(
unsigned cluster, SectorBuffer* oldFAT)
943 auto [startCluster, chainLength] = getChainStart(cluster);
947 vector<bool> visited(maxCluster,
false);
948 unsigned tmp = startCluster;
956 unsigned next = readFAT(tmp);
957 writeFATHelper(oldFAT, tmp, next);
963 DirIndex dirIndex, dirDirIndex;
964 if (getDirEntryForCluster(startCluster, dirIndex, dirDirIndex)) {
965 exportToHost(dirIndex, dirDirIndex);
969 std::pair<unsigned, unsigned> DirAsDSK::getChainStart(
unsigned cluster)
975 unsigned chainLength = 0;
977 if (readFAT(i) == cluster) {
984 return {cluster, chainLength};
990 template<
typename FUNC>
bool DirAsDSK::scanMsxDirs(FUNC func,
unsigned sector)
993 vector<unsigned> dirs;
994 vector<DirIndex> dirs2;
998 if (func.onDirSector(sector))
return true;
1002 DirIndex dirIndex(sector, idx);
1003 const MSXDirEntry& entry = msxDir(dirIndex);
1004 if (func.onDirEntry(dirIndex, entry))
return true;
1006 if ((entry.filename[0] ==
one_of(
char(0x00),
char(0xE5))) ||
1013 (cluster >= maxCluster)) {
1019 unsigned dir = clusterToSector(cluster);
1030 dirs.push_back(dir);
1031 dirs2.push_back(dirIndex);
1033 sector = nextMsxDirSector(sector);
1034 }
while (sector !=
unsigned(-1));
1037 if (rdIdx == dirs.size()) {
1042 func.onVisitSubDir(dirs2[rdIdx]);
1043 sector = dirs[rdIdx++];
1088 return sector == dirSector;
1092 bool DirAsDSK::isDirSector(
unsigned sector, DirIndex& dirDirIndex)
1094 return scanMsxDirs(
IsDirSector(sector, dirDirIndex), firstDirSector);
1100 DirAsDSK::DirIndex& dirIndex_,
1101 DirAsDSK::DirIndex& dirDirIndex_)
1115 bool DirAsDSK::getDirEntryForCluster(
unsigned cluster,
1116 DirIndex& dirIndex, DirIndex& dirDirIndex)
1121 DirAsDSK::DirIndex DirAsDSK::getDirEntryForCluster(
unsigned cluster)
1123 DirIndex dirIndex, dirDirIndex;
1124 if (getDirEntryForCluster(cluster, dirIndex, dirDirIndex)) {
1127 return {unsigned(-1), unsigned(-1)};
1143 void DirAsDSK::unmapHostFiles(
unsigned msxDirSector)
1148 void DirAsDSK::exportToHost(DirIndex dirIndex, DirIndex dirDirIndex)
1155 const char* msxName = msxDir(dirIndex).
filename;
1157 if (
auto* v =
lookup(mapDirs, dirIndex)) {
1159 hostName = v->hostName;
1163 if (msxName[0] ==
one_of(
char(0x00),
char(0xE5))) {
1168 if (dirDirIndex.sector != 0) {
1170 auto* v2 =
lookup(mapDirs, dirDirIndex);
1172 hostSubDir = v2->hostName;
1176 hostName = hostSubDir + msxToHostName(msxName);
1177 mapDirs[dirIndex].hostName = hostName;
1180 if ((memcmp(msxName,
". ", 11) == 0) ||
1181 (memcmp(msxName,
".. ", 11) == 0)) {
1185 exportToHostDir(dirIndex, hostName);
1187 exportToHostFile(dirIndex, hostName);
1191 void DirAsDSK::exportToHostDir(DirIndex dirIndex,
const string& hostName)
1199 unsigned msxDirSector = clusterToSector(cluster);
1205 vector<bool> visited(nofSectors,
false);
1207 if (visited[msxDirSector]) {
1211 visited[msxDirSector] =
true;
1213 if (readFAT(sectorToCluster(msxDirSector)) ==
FREE_FAT) {
1220 exportToHost(DirIndex(msxDirSector, idx), dirIndex);
1222 msxDirSector = nextMsxDirSector(msxDirSector);
1223 }
while (msxDirSector !=
unsigned(-1));
1224 }
catch (FileException& e) {
1225 cliComm.
printWarning(
"Error while syncing host directory: ",
1226 hostName,
": ", e.getMessage());
1230 void DirAsDSK::exportToHostFile(DirIndex dirIndex,
const string& hostName)
1238 unsigned msxSize = msxDir(dirIndex).
size;
1241 unsigned offset = 0;
1242 vector<bool> visited(maxCluster,
false);
1245 if (visited[curCl]) {
1249 visited[curCl] =
true;
1251 unsigned logicalSector = clusterToSector(curCl);
1253 if (offset >= msxSize)
break;
1254 unsigned sector = logicalSector + i;
1255 assert(sector < nofSectors);
1256 auto writeSize = std::min<size_t>(msxSize - offset,
SECTOR_SIZE);
1257 file.write(§ors[sector], writeSize);
1260 if (offset >= msxSize)
break;
1261 curCl = readFAT(curCl);
1263 }
catch (FileException& e) {
1264 cliComm.
printWarning(
"Error while syncing host file: ",
1265 hostName,
": ", e.getMessage());
1269 void DirAsDSK::writeDIRSector(
unsigned sector, DirIndex dirDirIndex,
1270 const SectorBuffer& buf)
1274 const auto& newEntry = buf.dirEntry[idx];
1275 DirIndex dirIndex(sector, idx);
1276 if (memcmp(&msxDir(dirIndex), &newEntry,
sizeof(newEntry)) != 0) {
1277 writeDIREntry(dirIndex, dirDirIndex, newEntry);
1281 assert(memcmp(§ors[sector], &buf,
sizeof(buf)) == 0);
1284 void DirAsDSK::writeDIREntry(DirIndex dirIndex, DirIndex dirDirIndex,
1285 const MSXDirEntry& newEntry)
1287 if (memcmp(msxDir(dirIndex).
filename, newEntry.filename, 8 + 3) != 0 ||
1291 if (
auto it = mapDirs.find(dirIndex); it !=
end(mapDirs)) {
1295 auto fullHostName =
tmpStrCat(hostDir, it->second.hostName);
1304 (cluster < maxCluster)) {
1305 unmapHostFiles(clusterToSector(cluster));
1312 memcpy(&msxDir(dirIndex), &newEntry,
sizeof(newEntry));
1315 exportToHost(dirIndex, dirDirIndex);
1318 void DirAsDSK::writeDataSector(
unsigned sector,
const SectorBuffer& buf)
1320 assert(sector >= firstDataSector);
1321 assert(sector < nofSectors);
1324 memcpy(§ors[sector], &buf,
sizeof(buf));
1327 auto [cluster, offset] = sectorToClusterOffset(sector);
1328 auto [startCluster, chainLength] = getChainStart(cluster);
1332 DirIndex dirIndex = getDirEntryForCluster(startCluster);
1334 auto* v =
lookup(mapDirs, dirIndex);
1341 string fullHostName = hostDir + v->hostName;
1343 File file(fullHostName,
"rb+");
1345 unsigned msxSize = msxDir(dirIndex).
size;
1346 if (msxSize > offset) {
1347 auto writeSize = std::min<size_t>(msxSize - offset,
sizeof(buf));
1348 file.write(&buf, writeSize);
1350 }
catch (FileException& e) {
1351 cliComm.
printWarning(
"Couldn't write to file ", fullHostName,
1352 ": ", e.getMessage());