26 using std::string_view;
30 constexpr
unsigned BAD_FAT = 0xFF7;
31 constexpr
unsigned EOF_FAT = 0xFFF;
49 unsigned MSXtar::clusterToSector(
unsigned cluster)
const
51 return 1 + rootDirLast + sectorsPerCluster * (cluster - 2);
57 unsigned MSXtar::sectorToCluster(
unsigned sector)
const
59 return 2 + ((sector - (1 + rootDirLast)) / sectorsPerCluster);
65 void MSXtar::parseBootSector(
const MSXBootSector& boot)
67 unsigned nbRootDirSectors = boot.dirEntries / 16;
68 sectorsPerFat = boot.sectorsFat;
69 sectorsPerCluster = boot.spCluster;
71 if (boot.nrSectors == 0) {
73 "Illegal number of sectors: ", boot.nrSectors);
75 if (boot.nrSides == 0) {
77 "Illegal number of sides: ", boot.nrSides);
79 if (boot.nrFats == 0) {
81 "Illegal number of FATs: ", boot.nrFats);
83 if (sectorsPerFat == 0) {
85 "Illegal number sectors per FAT: ", sectorsPerFat);
87 if (nbRootDirSectors == 0) {
89 "Illegal number of root dir sectors: ", nbRootDirSectors);
91 if (sectorsPerCluster == 0) {
93 "Illegal number of sectors per cluster: ", sectorsPerCluster);
96 rootDirStart = 1 + boot.nrFats * sectorsPerFat;
97 chrootSector = rootDirStart;
98 rootDirLast = rootDirStart + nbRootDirSectors - 1;
99 maxCluster = sectorToCluster(boot.nrSectors);
104 unsigned maxFatCluster = (2 *
SECTOR_SIZE * sectorsPerFat) / 3;
105 maxCluster =
std::min(maxCluster, maxFatCluster);
108 void MSXtar::writeLogicalSector(
unsigned sector,
const SectorBuffer& buf)
110 assert(!fatBuffer.empty());
111 unsigned fatSector = sector - 1;
112 if (fatSector < sectorsPerFat) {
115 memcpy(&fatBuffer[fatSector], &buf,
sizeof(buf));
116 fatCacheDirty =
true;
122 void MSXtar::readLogicalSector(
unsigned sector, SectorBuffer& buf)
124 assert(!fatBuffer.empty());
125 unsigned fatSector = sector - 1;
126 if (fatSector < sectorsPerFat) {
129 memcpy(&buf, &fatBuffer[fatSector],
sizeof(buf));
150 fatCacheDirty =
false;
151 fatBuffer.resize(sectorsPerFat);
157 if (!fatCacheDirty)
return;
159 for (
auto i :
xrange(sectorsPerFat)) {
170 static constexpr
unsigned normalizeFAT(
unsigned cluster)
176 unsigned MSXtar::readFAT(
unsigned clNr)
const
178 assert(!fatBuffer.empty());
179 const auto* data = fatBuffer[0].raw;
180 const auto* p = &data[(clNr * 3) / 2];
181 unsigned result = (clNr & 1)
182 ? (p[0] >> 4) + (p[1] << 4)
183 : p[0] + ((p[1] & 0x0F) << 8);
184 return normalizeFAT(result);
188 void MSXtar::writeFAT(
unsigned clNr,
unsigned val)
190 assert(!fatBuffer.empty());
192 auto* data = fatBuffer[0].raw;
193 auto* p = &data[(clNr * 3) / 2];
195 p[0] = (p[0] & 0x0F) + (val << 4);
199 p[1] = (p[1] & 0xF0) + ((val >> 8) & 0x0F);
201 fatCacheDirty =
true;
206 unsigned MSXtar::findFirstFreeCluster()
208 for (
auto cluster :
xrange(2u, maxCluster)) {
209 if (readFAT(cluster) == 0) {
213 throw MSXException(
"Disk full.");
218 unsigned MSXtar::getNextSector(
unsigned sector)
220 if (sector <= rootDirLast) {
222 return (sector == rootDirLast) ? 0 : sector + 1;
224 unsigned currCluster = sectorToCluster(sector);
225 if (currCluster == sectorToCluster(sector + 1)) {
230 unsigned nextCl = readFAT(currCluster);
231 return (nextCl ==
EOF_FAT) ? 0 : clusterToSector(nextCl);
237 static unsigned getStartCluster(
const MSXDirEntry& entry)
239 return normalizeFAT(entry.startCluster);
247 unsigned MSXtar::appendClusterToSubdir(
unsigned sector)
249 unsigned nextCl = findFirstFreeCluster();
250 unsigned nextSector = clusterToSector(nextCl);
254 memset(&buf, 0,
sizeof(buf));
255 for (
auto i :
xrange(sectorsPerCluster)) {
256 writeLogicalSector(i + nextSector, buf);
259 unsigned curCl = sectorToCluster(sector);
260 assert(readFAT(curCl) ==
EOF_FAT);
261 writeFAT(curCl, nextCl);
270 unsigned MSXtar::findUsableIndexInSector(
unsigned sector)
273 readLogicalSector(sector, buf);
276 for (
auto i :
xrange(16)) {
277 if (buf.dirEntry[i].filename[0] ==
one_of(0x00,
char(0xE5))) {
288 MSXtar::DirEntry MSXtar::addEntryToDir(
unsigned sector)
293 result.sector = sector;
295 if (sector <= rootDirLast) {
297 for ( ; result.sector <= rootDirLast; result.sector++) {
298 result.index = findUsableIndexInSector(result.sector);
299 if (result.index !=
unsigned(-1)) {
303 throw MSXException(
"Root directory full.");
308 result.index = findUsableIndexInSector(result.sector);
309 if (result.index !=
unsigned(-1)) {
312 unsigned nextSector = getNextSector(result.sector);
313 if (nextSector == 0) {
314 nextSector = appendClusterToSubdir(result.sector);
316 result.sector = nextSector;
322 static char toMSXChr(
char a)
325 if (a ==
one_of(
' ',
'.')) {
333 static string makeSimpleMSXFileName(string_view fullFilename)
338 string result(8 + 3,
' ');
339 if (fullFile ==
one_of(
".",
"..")) {
340 memcpy(result.data(), fullFile.data(), fullFile.size());
351 string fileS(file.data(), std::min<size_t>(8, file.size()));
352 string extS (ext .data(), std::min<size_t>(3, ext .
size()));
357 memcpy(result.data() + 0, fileS.data(), fileS.size());
358 memcpy(result.data() + 8, extS .data(), extS .size());
367 unsigned MSXtar::addSubdir(
368 std::string_view msxName,
unsigned t,
unsigned d,
unsigned sector)
371 DirEntry result = addEntryToDir(sector);
375 readLogicalSector(result.sector, buf);
377 auto& dirEntry = buf.dirEntry[result.index];
381 memcpy(&dirEntry, makeSimpleMSXFileName(msxName).data(), 11);
384 unsigned curCl = findFirstFreeCluster();
385 dirEntry.startCluster = curCl;
389 writeLogicalSector(result.sector, buf);
392 unsigned logicalSector = clusterToSector(curCl);
393 memset(&buf, 0,
sizeof(buf));
394 for (
auto i :
xrange(sectorsPerCluster)) {
395 writeLogicalSector(i + logicalSector, buf);
399 memset(&buf.dirEntry[0], 0,
sizeof(MSXDirEntry));
400 memset(&buf.dirEntry[0],
' ', 11);
401 memset(&buf.dirEntry[0],
'.', 1);
403 buf.dirEntry[0].time =
t;
404 buf.dirEntry[0].date = d;
405 buf.dirEntry[0].startCluster = curCl;
407 memset(&buf.dirEntry[1], 0,
sizeof(MSXDirEntry));
408 memset(&buf.dirEntry[1],
' ', 11);
409 memset(&buf.dirEntry[1],
'.', 2);
411 buf.dirEntry[1].time =
t;
412 buf.dirEntry[1].date = d;
413 buf.dirEntry[1].startCluster = sectorToCluster(sector);
416 writeLogicalSector(logicalSector, buf);
418 return logicalSector;
424 static TimeDate getTimeDate(time_t totalSeconds)
426 if (tm* mtim = localtime(&totalSeconds)) {
427 unsigned time = (mtim->tm_sec >> 1) + (mtim->tm_min << 5) +
428 (mtim->tm_hour << 11);
429 unsigned date = mtim->tm_mday + ((mtim->tm_mon + 1) << 5) +
430 ((mtim->tm_year + 1900 - 1980) << 9);
448 return getTimeDate(
reinterpret_cast<time_t&
>(st.st_mtime));
454 unsigned MSXtar::addSubdirToDSK(
zstring_view hostName, std::string_view msxName,
457 auto [time, date] = getTimeDate(hostName);
458 return addSubdir(msxName, time, date, sector);
465 void MSXtar::alterFileInDSK(MSXDirEntry& msxDirEntry,
const string& hostName)
469 if (stat(hostName.c_str(), &st)) {
470 throw MSXException(
"Error reading host file: ", hostName);
472 unsigned hostSize = st.st_size;
473 unsigned remaining = hostSize;
476 File file(hostName,
"rb");
480 unsigned curCl = getStartCluster(msxDirEntry);
485 unsigned newCl = findFirstFreeCluster();
487 msxDirEntry.startCluster = newCl;
489 writeFAT(prevCl, newCl);
494 }
catch (MSXException&) {
500 unsigned logicalSector = clusterToSector(curCl);
501 for (
unsigned j = 0; (j < sectorsPerCluster) && remaining; ++j) {
503 memset(&buf, 0,
sizeof(buf));
505 file.read(&buf, chunkSize);
506 writeLogicalSector(logicalSector + j, buf);
507 remaining -= chunkSize;
512 curCl = readFAT(curCl);
517 msxDirEntry.startCluster = 0;
524 unsigned nextCl = readFAT(curCl);
530 msxDirEntry.size = hostSize - remaining;
533 throw MSXException(
"Disk full, ", hostName,
" truncated.");
541 MSXtar::DirEntry MSXtar::findEntryInDir(
542 const string& name,
unsigned sector, SectorBuffer& buf)
545 result.sector = sector;
547 while (result.sector) {
549 readLogicalSector(result.sector, buf);
550 for (result.index = 0; result.index < 16; ++result.index) {
551 if (string_view(buf.dirEntry[result.index].filename, 11) == name) {
556 result.sector = getNextSector(result.sector);
563 string MSXtar::addFileToDSK(
const string& fullHostName,
unsigned rootSector)
566 string msxName = makeSimpleMSXFileName(hostName);
570 DirEntry fullMsxDirEntry = findEntryInDir(msxName, rootSector, dummy);
571 if (fullMsxDirEntry.sector != 0) {
573 return strCat(
"Warning: preserving entry ", hostName,
'\n');
577 DirEntry entry = addEntryToDir(rootSector);
578 readLogicalSector(entry.sector, buf);
579 auto& dirEntry = buf.dirEntry[entry.index];
580 memset(&dirEntry, 0,
sizeof(dirEntry));
581 memcpy(&dirEntry, msxName.data(), 11);
585 auto [time, date] = getTimeDate(fullHostName);
586 dirEntry.time = time;
587 dirEntry.date = date;
590 alterFileInDSK(dirEntry, fullHostName);
591 }
catch (MSXException&) {
593 writeLogicalSector(entry.sector, buf);
596 writeLogicalSector(entry.sector, buf);
602 string MSXtar::recurseDirFill(string_view dirName,
unsigned sector)
606 auto fileAction = [&](
const string& path) {
608 messages += addFileToDSK(path, sector);
610 auto dirAction = [&](
const string& path, std::string_view name) {
611 string msxFileName = makeSimpleMSXFileName(name);
613 DirEntry entry = findEntryInDir(msxFileName, sector, buf);
614 if (entry.sector != 0) {
616 auto& msxDirEntry = buf.dirEntry[entry.index];
619 unsigned nextSector = clusterToSector(
620 getStartCluster(msxDirEntry));
621 messages += recurseDirFill(path, nextSector);
625 "MSX file ", msxFileName,
626 " is not a directory.\n");
630 unsigned nextSector = addSubdirToDSK(path, name, sector);
631 messages += recurseDirFill(path, nextSector);
640 static string condensName(
const MSXDirEntry& dirEntry)
643 for (
unsigned i = 0; (i < 8) && (dirEntry.name.base[i] !=
' '); ++i) {
644 result += char(tolower(dirEntry.name.base[i]));
646 if (dirEntry.name.ext[0] !=
' ') {
648 for (
unsigned i = 0; (i < 3) && (dirEntry.name.ext[i] !=
' '); ++i) {
649 result += char(tolower(dirEntry.name.ext[i]));
657 static void changeTime(
zstring_view resultFile,
const MSXDirEntry& dirEntry)
659 unsigned t = dirEntry.time;
660 unsigned d = dirEntry.date;
663 mTim.tm_sec = (
t & 0x001f) << 1;
664 mTim.tm_min = (
t & 0x07e0) >> 5;
665 mTim.tm_hour = (
t & 0xf800) >> 11;
666 mTim.tm_mday = (d & 0x001f);
667 mTim.tm_mon = ((d & 0x01e0) >> 5) - 1;
668 mTim.tm_year = ((d & 0xfe00) >> 9) + 80;
670 uTim.actime = mktime(&mTim);
671 uTim.modtime = mktime(&mTim);
672 utime(resultFile.
c_str(), &uTim);
678 for (
unsigned sector = chrootSector; sector != 0; sector = getNextSector(sector)) {
680 readLogicalSector(sector, buf);
681 for (
auto& dirEntry : buf.
dirEntry) {
682 if ((dirEntry.filename[0] ==
one_of(
char(0xe5),
char(0x00))) ||
683 (dirEntry.attrib ==
T_MSX_LFN))
continue;
686 string tmp = condensName(dirEntry);
690 (dirEntry.attrib &
T_MSX_DIR ?
'd' :
'-'),
692 (dirEntry.attrib &
T_MSX_HID ?
'h' :
'-'),
693 (dirEntry.attrib &
T_MSX_VOL ?
'v' :
'-'),
694 (dirEntry.attrib &
T_MSX_ARC ?
'a' :
'-'),
697 dirEntry.size <<
'\n');
706 chroot(newRootDir,
false);
711 unsigned tmpMSXchrootSector = chrootSector;
712 chroot(newRootDir,
true);
713 chrootSector = tmpMSXchrootSector;
716 void MSXtar::chroot(string_view newRootDir,
bool createDir)
720 chrootSector = rootDirStart;
724 while (!newRootDir.empty()) {
726 newRootDir = lastPart;
731 string simple = makeSimpleMSXFileName(firstPart);
732 DirEntry entry = findEntryInDir(simple, chrootSector, buf);
733 if (entry.sector == 0) {
735 throw MSXException(
"Subdirectory ", firstPart,
741 auto [
t, d] = getTimeDate(now);
742 chrootSector = addSubdir(simple,
t, d, chrootSector);
744 auto& dirEntry = buf.dirEntry[entry.index];
746 throw MSXException(firstPart,
" is not a directory.");
748 chrootSector = clusterToSector(getStartCluster(dirEntry));
753 void MSXtar::fileExtract(
const string& resultFile,
const MSXDirEntry& dirEntry)
755 unsigned size = dirEntry.size;
756 unsigned sector = clusterToSector(getStartCluster(dirEntry));
758 File file(resultFile,
"wb");
759 while (
size && sector) {
761 readLogicalSector(sector, buf);
763 file.write(&buf, savesize);
765 sector = getNextSector(sector);
768 changeTime(resultFile, dirEntry);
772 string MSXtar::singleItemExtract(string_view dirName, string_view itemName,
777 string msxName = makeSimpleMSXFileName(itemName);
778 DirEntry entry = findEntryInDir(msxName, sector, buf);
779 if (entry.sector == 0) {
780 return strCat(itemName,
" not found!\n");
783 auto& msxDirEntry = buf.dirEntry[entry.index];
785 string fullName =
strCat(dirName,
'/', condensName(msxDirEntry));
793 clusterToSector(getStartCluster(msxDirEntry)));
796 fileExtract(fullName, msxDirEntry);
803 void MSXtar::recurseDirExtract(string_view dirName,
unsigned sector)
805 for ( ; sector != 0; sector = getNextSector(sector)) {
807 readLogicalSector(sector, buf);
808 for (
auto& dirEntry : buf.dirEntry) {
809 if (dirEntry.filename[0] ==
one_of(
char(0xe5),
char(0x00),
'.')) {
812 string filename = condensName(dirEntry);
814 if (!dirName.empty()) {
818 fileExtract(fullName, dirEntry);
823 changeTime(fullName, dirEntry);
826 clusterToSector(getStartCluster(dirEntry)));
834 return recurseDirFill(rootDirName, chrootSector);
839 return addFileToDSK(
filename, chrootSector);
844 return singleItemExtract(rootDirName, itemName, chrootSector);
849 recurseDirExtract(rootDirName, chrootSector);
void swap(openmsx::MemBuffer< T > &l, openmsx::MemBuffer< T > &r) noexcept
const std::string & getMessage() const &
std::string getItemFromDir(std::string_view rootDirName, std::string_view itemName)
MSXtar(SectorAccessibleDisk &disk)
void getDir(std::string_view rootDirName)
std::string addFile(const std::string &filename)
void chdir(std::string_view newRootDir)
void mkdir(std::string_view newRootDir)
std::string addDir(std::string_view rootDirName)
void readSector(size_t sector, SectorBuffer &buf)
void readSectors(SectorBuffer *buffers, size_t startSector, size_t nbSectors)
static constexpr size_t SECTOR_SIZE
size_t getNbSectors() const
void writeSector(size_t sector, const SectorBuffer &buf)
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
constexpr const char * c_str() const
std::pair< string_view, string_view > splitOnLast(string_view str, string_view chars)
void trimRight(string &str, const char *chars)
std::pair< string_view, string_view > splitOnFirst(string_view str, string_view chars)
void trimLeft(string &str, const char *chars)
bool startsWith(string_view total, string_view part)
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
void mkdirp(string path)
Acts like the unix command "mkdir -p".
This file implemented 3 utility functions:
constexpr unsigned SECTOR_SIZE
constexpr unsigned EOF_FAT
constexpr byte T_MSX_READ
constexpr const char *const filename
constexpr unsigned BAD_FAT
bool foreach_file_and_directory(std::string path, FileAction fileAction, DirAction dirAction)
size_t size(std::string_view utf8)
auto transform_in_place(ForwardRange &&range, UnaryOperation op)
std::string strCat(Ts &&...ts)
void strAppend(std::string &result, Ts &&...ts)
constexpr auto xrange(T e)