openMSX
MSXtar.cc
Go to the documentation of this file.
1// Note: For Mac OS X 10.3 <ctime> must be included before <utime.h>.
2#include <ctime>
3#ifndef _MSC_VER
4#include <utime.h>
5#else
6#include <sys/utime.h>
7#endif
8
9#include "MSXtar.hh"
11#include "FileOperations.hh"
12#include "foreach_file.hh"
13#include "MSXException.hh"
14#include "StringOp.hh"
15#include "strCat.hh"
16#include "File.hh"
17#include "one_of.hh"
18#include "stl.hh"
19#include "xrange.hh"
20#include <cstring>
21#include <cassert>
22#include <cctype>
23#include <sys/stat.h>
24
25using std::string;
26using std::string_view;
27
28namespace openmsx {
29
30constexpr unsigned BAD_FAT = 0xFF7;
31constexpr unsigned EOF_FAT = 0xFFF; // actually 0xFF8-0xFFF, signals EOF in FAT12
33
34constexpr byte T_MSX_REG = 0x00; // Normal file
35constexpr byte T_MSX_READ = 0x01; // Read-Only file
36constexpr byte T_MSX_HID = 0x02; // Hidden file
37constexpr byte T_MSX_SYS = 0x04; // System file
38constexpr byte T_MSX_VOL = 0x08; // filename is Volume Label
39constexpr byte T_MSX_DIR = 0x10; // entry is a subdir
40constexpr byte T_MSX_ARC = 0x20; // Archive bit
41// This particular combination of flags indicates that this dir entry is used
42// to store a long Unicode file name.
43// For details, read http://home.teleport.com/~brainy/lfn.htm
44constexpr byte T_MSX_LFN = 0x0F; // LFN entry (long files names)
45
49unsigned MSXtar::clusterToSector(unsigned cluster) const
50{
51 return 1 + rootDirLast + sectorsPerCluster * (cluster - 2);
52}
53
57unsigned MSXtar::sectorToCluster(unsigned sector) const
58{
59 return 2 + ((sector - (1 + rootDirLast)) / sectorsPerCluster);
60}
61
62
65void MSXtar::parseBootSector(const MSXBootSector& boot)
66{
67 unsigned nbRootDirSectors = boot.dirEntries / 16;
68 sectorsPerFat = boot.sectorsFat;
69 sectorsPerCluster = boot.spCluster;
70
71 if (boot.nrSectors == 0) { // TODO: check limits more accurately
72 throw MSXException(
73 "Illegal number of sectors: ", boot.nrSectors);
74 }
75 if (boot.nrSides == 0) { // TODO: check limits more accurately
76 throw MSXException(
77 "Illegal number of sides: ", boot.nrSides);
78 }
79 if (boot.nrFats == 0) { // TODO: check limits more accurately
80 throw MSXException(
81 "Illegal number of FATs: ", boot.nrFats);
82 }
83 if (sectorsPerFat == 0) { // TODO: check limits more accurately
84 throw MSXException(
85 "Illegal number sectors per FAT: ", sectorsPerFat);
86 }
87 if (nbRootDirSectors == 0) { // TODO: check limits more accurately
88 throw MSXException(
89 "Illegal number of root dir sectors: ", nbRootDirSectors);
90 }
91 if (sectorsPerCluster == 0) { // TODO: check limits more accurately
92 throw MSXException(
93 "Illegal number of sectors per cluster: ", sectorsPerCluster);
94 }
95
96 rootDirStart = 1 + boot.nrFats * sectorsPerFat;
97 chrootSector = rootDirStart;
98 rootDirLast = rootDirStart + nbRootDirSectors - 1;
99 maxCluster = sectorToCluster(boot.nrSectors);
100
101 // Some (invalid) diskimages have a too small FAT to be able to address
102 // all clusters of the image. OpenMSX SVN revisions pre-11326 even
103 // created such invalid images for some disk sizes!!
104 unsigned maxFatCluster = (2 * SECTOR_SIZE * sectorsPerFat) / 3;
105 maxCluster = std::min(maxCluster, maxFatCluster);
106}
107
108void MSXtar::writeLogicalSector(unsigned sector, const SectorBuffer& buf)
109{
110 assert(!fatBuffer.empty());
111 unsigned fatSector = sector - 1;
112 if (fatSector < sectorsPerFat) {
113 // we have a cache and this is a sector of the 1st FAT
114 // --> update cache
115 memcpy(&fatBuffer[fatSector], &buf, sizeof(buf));
116 fatCacheDirty = true;
117 } else {
118 disk.writeSector(sector, buf);
119 }
120}
121
122void MSXtar::readLogicalSector(unsigned sector, SectorBuffer& buf)
123{
124 assert(!fatBuffer.empty());
125 unsigned fatSector = sector - 1;
126 if (fatSector < sectorsPerFat) {
127 // we have a cache and this is a sector of the 1st FAT
128 // --> read from cache
129 memcpy(&buf, &fatBuffer[fatSector], sizeof(buf));
130 } else {
131 disk.readSector(sector, buf);
132 }
133}
134
136 : disk(sectordisk)
137{
138 if (disk.getNbSectors() == 0) {
139 throw MSXException("No disk inserted.");
140 }
141 try {
142 SectorBuffer buf;
143 disk.readSector(0, buf);
144 parseBootSector(buf.bootSector);
145 } catch (MSXException& e) {
146 throw MSXException("Bad disk image: ", e.getMessage());
147 }
148
149 // cache complete FAT
150 fatCacheDirty = false;
151 fatBuffer.resize(sectorsPerFat);
152 disk.readSectors(&fatBuffer[0], 1, sectorsPerFat);
153}
154
155// Not used when NRVO is used (but NRVO optimization is not (yet) mandated)
156MSXtar::MSXtar(MSXtar&& other) noexcept
157 : disk(other.disk)
158 , fatBuffer(std::move(other.fatBuffer))
159 , maxCluster(other.maxCluster)
160 , sectorsPerCluster(other.sectorsPerCluster)
161 , sectorsPerFat(other.sectorsPerFat)
162 , rootDirStart(other.rootDirStart)
163 , rootDirLast(other.rootDirLast)
164 , chrootSector(other.chrootSector)
165 , fatCacheDirty(other.fatCacheDirty)
166{
167 other.fatCacheDirty = false;
168}
169
171{
172 if (!fatCacheDirty) return;
173
174 for (auto i : xrange(sectorsPerFat)) {
175 try {
176 disk.writeSector(i + 1, fatBuffer[i]);
177 } catch (MSXException&) {
178 // nothing
179 }
180 }
181}
182
183// transform BAD_FAT (0xFF7) and EOF_FAT-range (0xFF8-0xFFF)
184// to a single value: EOF_FAT (0xFFF)
185static constexpr unsigned normalizeFAT(unsigned cluster)
186{
187 return (cluster < BAD_FAT) ? cluster : EOF_FAT;
188}
189
190// Get the next clusternumber from the FAT chain
191unsigned MSXtar::readFAT(unsigned clNr) const
192{
193 assert(!fatBuffer.empty()); // FAT must already be cached
194 const auto* data = fatBuffer[0].raw;
195 const auto* p = &data[(clNr * 3) / 2];
196 unsigned result = (clNr & 1)
197 ? (p[0] >> 4) + (p[1] << 4)
198 : p[0] + ((p[1] & 0x0F) << 8);
199 return normalizeFAT(result);
200}
201
202// Write an entry to the FAT
203void MSXtar::writeFAT(unsigned clNr, unsigned val)
204{
205 assert(!fatBuffer.empty()); // FAT must already be cached
206 assert(val < 4096); // FAT12
207 auto* data = fatBuffer[0].raw;
208 auto* p = &data[(clNr * 3) / 2];
209 if (clNr & 1) {
210 p[0] = (p[0] & 0x0F) + (val << 4);
211 p[1] = val >> 4;
212 } else {
213 p[0] = val;
214 p[1] = (p[1] & 0xF0) + ((val >> 8) & 0x0F);
215 }
216 fatCacheDirty = true;
217}
218
219// Find the next clusternumber marked as free in the FAT
220// @throws When no more free clusters
221unsigned MSXtar::findFirstFreeCluster()
222{
223 for (auto cluster : xrange(2u, maxCluster)) {
224 if (readFAT(cluster) == 0) {
225 return cluster;
226 }
227 }
228 throw MSXException("Disk full.");
229}
230
231// Get the next sector from a file or (root/sub)directory
232// If no next sector then 0 is returned
233unsigned MSXtar::getNextSector(unsigned sector)
234{
235 if (sector <= rootDirLast) {
236 // sector is part of the root directory
237 return (sector == rootDirLast) ? 0 : sector + 1;
238 }
239 unsigned currCluster = sectorToCluster(sector);
240 if (currCluster == sectorToCluster(sector + 1)) {
241 // next sector of cluster
242 return sector + 1;
243 } else {
244 // first sector in next cluster
245 unsigned nextCl = readFAT(currCluster);
246 return (nextCl == EOF_FAT) ? 0 : clusterToSector(nextCl);
247 }
248}
249
250// get start cluster from a directory entry,
251// also takes care of BAD_FAT and EOF_FAT-range.
252static unsigned getStartCluster(const MSXDirEntry& entry)
253{
254 return normalizeFAT(entry.startCluster);
255}
256
257// If there are no more free entries in a subdirectory, the subdir is
258// expanded with an extra cluster. This function gets the free cluster,
259// clears it and updates the fat for the subdir
260// returns: the first sector in the newly appended cluster
261// @throws When disk is full
262unsigned MSXtar::appendClusterToSubdir(unsigned sector)
263{
264 unsigned nextCl = findFirstFreeCluster();
265 unsigned nextSector = clusterToSector(nextCl);
266
267 // clear this cluster
268 SectorBuffer buf;
269 memset(&buf, 0, sizeof(buf));
270 for (auto i : xrange(sectorsPerCluster)) {
271 writeLogicalSector(i + nextSector, buf);
272 }
273
274 unsigned curCl = sectorToCluster(sector);
275 assert(readFAT(curCl) == EOF_FAT);
276 writeFAT(curCl, nextCl);
277 writeFAT(nextCl, EOF_FAT);
278 return nextSector;
279}
280
281
282// Returns the index of a free (or with deleted file) entry
283// In: The current dir sector
284// Out: index number, if no index is found then -1 is returned
285unsigned MSXtar::findUsableIndexInSector(unsigned sector)
286{
287 SectorBuffer buf;
288 readLogicalSector(sector, buf);
289
290 // find a not used (0x00) or delete entry (0xE5)
291 for (auto i : xrange(16)) {
292 if (buf.dirEntry[i].filename[0] == one_of(0x00, char(0xE5))) {
293 return i;
294 }
295 }
296 return unsigned(-1);
297}
298
299// This function returns the sector and dirindex for a new directory entry
300// if needed the involved subdirectroy is expanded by an extra cluster
301// returns: a DirEntry containing sector and index
302// @throws When either root dir is full or disk is full
303MSXtar::DirEntry MSXtar::addEntryToDir(unsigned sector)
304{
305 // this routine adds the msx name to a directory sector, if needed (and
306 // possible) the directory is extened with an extra cluster
307 DirEntry result;
308 result.sector = sector;
309
310 if (sector <= rootDirLast) {
311 // add to the root directory
312 for (/* */ ; result.sector <= rootDirLast; result.sector++) {
313 result.index = findUsableIndexInSector(result.sector);
314 if (result.index != unsigned(-1)) {
315 return result;
316 }
317 }
318 throw MSXException("Root directory full.");
319
320 } else {
321 // add to a subdir
322 while (true) {
323 result.index = findUsableIndexInSector(result.sector);
324 if (result.index != unsigned(-1)) {
325 return result;
326 }
327 unsigned nextSector = getNextSector(result.sector);
328 if (nextSector == 0) {
329 nextSector = appendClusterToSubdir(result.sector);
330 }
331 result.sector = nextSector;
332 }
333 }
334}
335
336// create an MSX filename 8.3 format, if needed in vfat like abbreviation
337static char toMSXChr(char a)
338{
339 a = toupper(a);
340 if (a == one_of(' ', '.')) {
341 a = '_';
342 }
343 return a;
344}
345
346// Transform a long hostname in a 8.3 uppercase filename as used in the
347// direntries on an MSX
348static string makeSimpleMSXFileName(string_view fullFilename)
349{
350 auto [dir, fullFile] = StringOp::splitOnLast(fullFilename, '/');
351
352 // handle special case '.' and '..' first
353 string result(8 + 3, ' ');
354 if (fullFile == one_of(".", "..")) {
355 memcpy(result.data(), fullFile.data(), fullFile.size());
356 return result;
357 }
358
359 auto [file, ext] = StringOp::splitOnLast(fullFile, '.');
360 if (file.empty()) std::swap(file, ext);
361
362 StringOp::trimRight(file, ' ');
363 StringOp::trimRight(ext, ' ');
364
365 // put in major case and create '_' if needed
366 string fileS(file.data(), std::min<size_t>(8, file.size()));
367 string extS (ext .data(), std::min<size_t>(3, ext .size()));
368 transform_in_place(fileS, toMSXChr);
369 transform_in_place(extS, toMSXChr);
370
371 // add correct number of spaces
372 memcpy(result.data() + 0, fileS.data(), fileS.size());
373 memcpy(result.data() + 8, extS .data(), extS .size());
374 return result;
375}
376
377// This function creates a new MSX subdir with given date 'd' and time 't'
378// in the subdir pointed at by 'sector'. In the newly
379// created subdir the entries for '.' and '..' are created
380// returns: the first sector of the new subdir
381// @throws in case no directory could be created
382unsigned MSXtar::addSubdir(
383 std::string_view msxName, unsigned t, unsigned d, unsigned sector)
384{
385 // returns the sector for the first cluster of this subdir
386 DirEntry result = addEntryToDir(sector);
387
388 // load the sector
389 SectorBuffer buf;
390 readLogicalSector(result.sector, buf);
391
392 auto& dirEntry = buf.dirEntry[result.index];
393 dirEntry.attrib = T_MSX_DIR;
394 dirEntry.time = t;
395 dirEntry.date = d;
396 memcpy(&dirEntry, makeSimpleMSXFileName(msxName).data(), 11);
397
398 // dirEntry.filesize = fsize;
399 unsigned curCl = findFirstFreeCluster();
400 dirEntry.startCluster = curCl;
401 writeFAT(curCl, EOF_FAT);
402
403 // save the sector again
404 writeLogicalSector(result.sector, buf);
405
406 // clear this cluster
407 unsigned logicalSector = clusterToSector(curCl);
408 memset(&buf, 0, sizeof(buf));
409 for (auto i : xrange(sectorsPerCluster)) {
410 writeLogicalSector(i + logicalSector, buf);
411 }
412
413 // now add the '.' and '..' entries!!
414 memset(&buf.dirEntry[0], 0, sizeof(MSXDirEntry));
415 memset(&buf.dirEntry[0], ' ', 11);
416 memset(&buf.dirEntry[0], '.', 1);
417 buf.dirEntry[0].attrib = T_MSX_DIR;
418 buf.dirEntry[0].time = t;
419 buf.dirEntry[0].date = d;
420 buf.dirEntry[0].startCluster = curCl;
421
422 memset(&buf.dirEntry[1], 0, sizeof(MSXDirEntry));
423 memset(&buf.dirEntry[1], ' ', 11);
424 memset(&buf.dirEntry[1], '.', 2);
425 buf.dirEntry[1].attrib = T_MSX_DIR;
426 buf.dirEntry[1].time = t;
427 buf.dirEntry[1].date = d;
428 buf.dirEntry[1].startCluster = sectorToCluster(sector);
429
430 // and save this in the first sector of the new subdir
431 writeLogicalSector(logicalSector, buf);
432
433 return logicalSector;
434}
435
436struct TimeDate {
437 unsigned time, date;
438};
439static TimeDate getTimeDate(time_t totalSeconds)
440{
441 if (tm* mtim = localtime(&totalSeconds)) {
442 unsigned time = (mtim->tm_sec >> 1) + (mtim->tm_min << 5) +
443 (mtim->tm_hour << 11);
444 unsigned date = mtim->tm_mday + ((mtim->tm_mon + 1) << 5) +
445 ((mtim->tm_year + 1900 - 1980) << 9);
446 return {time, date};
447 }
448 return {0, 0};
449}
450
451// Get the time/date from a host file in MSX format
452static TimeDate getTimeDate(zstring_view filename)
453{
454 struct stat st;
455 if (stat(filename.c_str(), &st)) {
456 // stat failed
457 return {0, 0};
458 } else {
459 // Some info indicates that st.st_mtime could be useless on win32 with vfat.
460 // On Android 'st_mtime' is 'unsigned long' instead of 'time_t'
461 // (like on linux), so we require a reinterpret_cast. That cast
462 // is fine (but redundant) on linux.
463 return getTimeDate(reinterpret_cast<time_t&>(st.st_mtime));
464 }
465}
466
467// Add an MSXsubdir with the time properties from the HOST-OS subdir
468// @throws when subdir could not be created
469unsigned MSXtar::addSubdirToDSK(zstring_view hostName, std::string_view msxName,
470 unsigned sector)
471{
472 auto [time, date] = getTimeDate(hostName);
473 return addSubdir(msxName, time, date, sector);
474}
475
476// This file alters the filecontent of a given file
477// It only changes the file content (and the filesize in the msxDirEntry)
478// It doesn't changes timestamps nor filename, filetype etc.
479// @throws when something goes wrong
480void MSXtar::alterFileInDSK(MSXDirEntry& msxDirEntry, const string& hostName)
481{
482 // get host file size
483 struct stat st;
484 if (stat(hostName.c_str(), &st)) {
485 throw MSXException("Error reading host file: ", hostName);
486 }
487 unsigned hostSize = st.st_size;
488 unsigned remaining = hostSize;
489
490 // open host file for reading
491 File file(hostName, "rb");
492
493 // copy host file to image
494 unsigned prevCl = 0;
495 unsigned curCl = getStartCluster(msxDirEntry);
496 while (remaining) {
497 // allocate new cluster if needed
498 try {
499 if (curCl == one_of(0u, EOF_FAT)) {
500 unsigned newCl = findFirstFreeCluster();
501 if (prevCl == 0) {
502 msxDirEntry.startCluster = newCl;
503 } else {
504 writeFAT(prevCl, newCl);
505 }
506 writeFAT(newCl, EOF_FAT);
507 curCl = newCl;
508 }
509 } catch (MSXException&) {
510 // no more free clusters
511 break;
512 }
513
514 // fill cluster
515 unsigned logicalSector = clusterToSector(curCl);
516 for (unsigned j = 0; (j < sectorsPerCluster) && remaining; ++j) {
517 SectorBuffer buf;
518 memset(&buf, 0, sizeof(buf));
519 unsigned chunkSize = std::min(SECTOR_SIZE, remaining);
520 file.read(&buf, chunkSize);
521 writeLogicalSector(logicalSector + j, buf);
522 remaining -= chunkSize;
523 }
524
525 // advance to next cluster
526 prevCl = curCl;
527 curCl = readFAT(curCl);
528 }
529
530 // terminate FAT chain
531 if (prevCl == 0) {
532 msxDirEntry.startCluster = 0;
533 } else {
534 writeFAT(prevCl, EOF_FAT);
535 }
536
537 // free rest of FAT chain
538 while (curCl != one_of(EOF_FAT, 0u)) {
539 unsigned nextCl = readFAT(curCl);
540 writeFAT(curCl, 0);
541 curCl = nextCl;
542 }
543
544 // write (possibly truncated) file size
545 msxDirEntry.size = hostSize - remaining;
546
547 if (remaining) {
548 throw MSXException("Disk full, ", hostName, " truncated.");
549 }
550}
551
552// Find the dir entry for 'name' in subdir starting at the given 'sector'
553// with given 'index'
554// returns: a DirEntry with sector and index filled in
555// sector is 0 if no match was found
556MSXtar::DirEntry MSXtar::findEntryInDir(
557 const string& name, unsigned sector, SectorBuffer& buf)
558{
559 DirEntry result;
560 result.sector = sector;
561 result.index = 0; // avoid warning (only some gcc versions complain)
562 while (result.sector) {
563 // read sector and scan 16 entries
564 readLogicalSector(result.sector, buf);
565 for (result.index = 0; result.index < 16; ++result.index) {
566 if (string_view(buf.dirEntry[result.index].filename, 11) == name) {
567 return result;
568 }
569 }
570 // try next sector
571 result.sector = getNextSector(result.sector);
572 }
573 return result;
574}
575
576// Add file to the MSX disk in the subdir pointed to by 'sector'
577// @throws when file could not be added
578string MSXtar::addFileToDSK(const string& fullHostName, unsigned rootSector)
579{
580 auto [directory, hostName] = StringOp::splitOnLast(fullHostName, "/\\");
581 string msxName = makeSimpleMSXFileName(hostName);
582
583 // first find out if the filename already exists in current dir
584 SectorBuffer dummy;
585 DirEntry fullMsxDirEntry = findEntryInDir(msxName, rootSector, dummy);
586 if (fullMsxDirEntry.sector != 0) {
587 // TODO implement overwrite option
588 return strCat("Warning: preserving entry ", hostName, '\n');
589 }
590
591 SectorBuffer buf;
592 DirEntry entry = addEntryToDir(rootSector);
593 readLogicalSector(entry.sector, buf);
594 auto& dirEntry = buf.dirEntry[entry.index];
595 memset(&dirEntry, 0, sizeof(dirEntry));
596 memcpy(&dirEntry, msxName.data(), 11);
597 dirEntry.attrib = T_MSX_REG;
598
599 // compute time/date stamps
600 auto [time, date] = getTimeDate(fullHostName);
601 dirEntry.time = time;
602 dirEntry.date = date;
603
604 try {
605 alterFileInDSK(dirEntry, fullHostName);
606 } catch (MSXException&) {
607 // still write directory entry
608 writeLogicalSector(entry.sector, buf);
609 throw;
610 }
611 writeLogicalSector(entry.sector, buf);
612 return {};
613}
614
615// Transfer directory and all its subdirectories to the MSX diskimage
616// @throws when an error occurs
617string MSXtar::recurseDirFill(string_view dirName, unsigned sector)
618{
619 string messages;
620
621 auto fileAction = [&](const string& path) {
622 // add new file
623 messages += addFileToDSK(path, sector);
624 };
625 auto dirAction = [&](const string& path, std::string_view name) {
626 string msxFileName = makeSimpleMSXFileName(name);
627 SectorBuffer buf;
628 DirEntry entry = findEntryInDir(msxFileName, sector, buf);
629 if (entry.sector != 0) {
630 // entry already exists ..
631 auto& msxDirEntry = buf.dirEntry[entry.index];
632 if (msxDirEntry.attrib & T_MSX_DIR) {
633 // .. and is a directory
634 unsigned nextSector = clusterToSector(
635 getStartCluster(msxDirEntry));
636 messages += recurseDirFill(path, nextSector);
637 } else {
638 // .. but is NOT a directory
639 strAppend(messages,
640 "MSX file ", msxFileName,
641 " is not a directory.\n");
642 }
643 } else {
644 // add new directory
645 unsigned nextSector = addSubdirToDSK(path, name, sector);
646 messages += recurseDirFill(path, nextSector);
647 }
648 };
649 foreach_file_and_directory(std::string(dirName), fileAction, dirAction);
650
651 return messages;
652}
653
654
655static string condensName(const MSXDirEntry& dirEntry)
656{
657 string result;
658 for (unsigned i = 0; (i < 8) && (dirEntry.name.base[i] != ' '); ++i) {
659 result += char(tolower(dirEntry.name.base[i]));
660 }
661 if (dirEntry.name.ext[0] != ' ') {
662 result += '.';
663 for (unsigned i = 0; (i < 3) && (dirEntry.name.ext[i] != ' '); ++i) {
664 result += char(tolower(dirEntry.name.ext[i]));
665 }
666 }
667 return result;
668}
669
670
671// Set the entries from dirEntry to the timestamp of resultFile
672static void changeTime(zstring_view resultFile, const MSXDirEntry& dirEntry)
673{
674 unsigned t = dirEntry.time;
675 unsigned d = dirEntry.date;
676 struct tm mTim;
677 struct utimbuf uTim;
678 mTim.tm_sec = (t & 0x001f) << 1;
679 mTim.tm_min = (t & 0x07e0) >> 5;
680 mTim.tm_hour = (t & 0xf800) >> 11;
681 mTim.tm_mday = (d & 0x001f);
682 mTim.tm_mon = ((d & 0x01e0) >> 5) - 1;
683 mTim.tm_year = ((d & 0xfe00) >> 9) + 80;
684 mTim.tm_isdst = -1;
685 uTim.actime = mktime(&mTim);
686 uTim.modtime = mktime(&mTim);
687 utime(resultFile.c_str(), &uTim);
688}
689
691{
692 string result;
693 for (unsigned sector = chrootSector; sector != 0; sector = getNextSector(sector)) {
694 SectorBuffer buf;
695 readLogicalSector(sector, buf);
696 for (auto& dirEntry : buf.dirEntry) {
697 if ((dirEntry.filename[0] == one_of(char(0xe5), char(0x00))) ||
698 (dirEntry.attrib == T_MSX_LFN)) continue;
699
700 // filename first (in condensed form for human readability)
701 string tmp = condensName(dirEntry);
702 tmp.resize(13, ' ');
703 strAppend(result, tmp,
704 // attributes
705 (dirEntry.attrib & T_MSX_DIR ? 'd' : '-'),
706 (dirEntry.attrib & T_MSX_READ ? 'r' : '-'),
707 (dirEntry.attrib & T_MSX_HID ? 'h' : '-'),
708 (dirEntry.attrib & T_MSX_VOL ? 'v' : '-'), // TODO check if this is the output of files,l
709 (dirEntry.attrib & T_MSX_ARC ? 'a' : '-'), // TODO check if this is the output of files,l
710 " ",
711 // filesize
712 dirEntry.size << '\n');
713 }
714 }
715 return result;
716}
717
718// routines to update the global vars: chrootSector
719void MSXtar::chdir(string_view newRootDir)
720{
721 chroot(newRootDir, false);
722}
723
724void MSXtar::mkdir(string_view newRootDir)
725{
726 unsigned tmpMSXchrootSector = chrootSector;
727 chroot(newRootDir, true);
728 chrootSector = tmpMSXchrootSector;
729}
730
731void MSXtar::chroot(string_view newRootDir, bool createDir)
732{
733 if (newRootDir.starts_with('/') || newRootDir.starts_with('\\')) {
734 // absolute path, reset chrootSector
735 chrootSector = rootDirStart;
736 StringOp::trimLeft(newRootDir, "/\\");
737 }
738
739 while (!newRootDir.empty()) {
740 auto [firstPart, lastPart] = StringOp::splitOnFirst(newRootDir, "/\\");
741 newRootDir = lastPart;
742 StringOp::trimLeft(newRootDir, "/\\");
743
744 // find 'firstPart' directory or create it if requested
745 SectorBuffer buf;
746 string simple = makeSimpleMSXFileName(firstPart);
747 DirEntry entry = findEntryInDir(simple, chrootSector, buf);
748 if (entry.sector == 0) {
749 if (!createDir) {
750 throw MSXException("Subdirectory ", firstPart,
751 " not found.");
752 }
753 // creat new subdir
754 time_t now;
755 time(&now);
756 auto [t, d] = getTimeDate(now);
757 chrootSector = addSubdir(simple, t, d, chrootSector);
758 } else {
759 auto& dirEntry = buf.dirEntry[entry.index];
760 if (!(dirEntry.attrib & T_MSX_DIR)) {
761 throw MSXException(firstPart, " is not a directory.");
762 }
763 chrootSector = clusterToSector(getStartCluster(dirEntry));
764 }
765 }
766}
767
768void MSXtar::fileExtract(const string& resultFile, const MSXDirEntry& dirEntry)
769{
770 unsigned size = dirEntry.size;
771 unsigned sector = clusterToSector(getStartCluster(dirEntry));
772
773 File file(resultFile, "wb");
774 while (size && sector) {
775 SectorBuffer buf;
776 readLogicalSector(sector, buf);
777 unsigned savesize = std::min(size, SECTOR_SIZE);
778 file.write(&buf, savesize);
779 size -= savesize;
780 sector = getNextSector(sector);
781 }
782 // now change the access time
783 changeTime(resultFile, dirEntry);
784}
785
786// extracts a single item (file or directory) from the msx image to the host OS
787string MSXtar::singleItemExtract(string_view dirName, string_view itemName,
788 unsigned sector)
789{
790 // first find out if the filename exists in current dir
791 SectorBuffer buf;
792 string msxName = makeSimpleMSXFileName(itemName);
793 DirEntry entry = findEntryInDir(msxName, sector, buf);
794 if (entry.sector == 0) {
795 return strCat(itemName, " not found!\n");
796 }
797
798 auto& msxDirEntry = buf.dirEntry[entry.index];
799 // create full name for local filesystem
800 string fullName = strCat(dirName, '/', condensName(msxDirEntry));
801
802 // ...and extract
803 if (msxDirEntry.attrib & T_MSX_DIR) {
804 // recursive extract this subdir
805 FileOperations::mkdirp(fullName);
806 recurseDirExtract(
807 fullName,
808 clusterToSector(getStartCluster(msxDirEntry)));
809 } else {
810 // it is a file
811 fileExtract(fullName, msxDirEntry);
812 }
813 return {};
814}
815
816
817// extracts the contents of the directory (at sector) and all its subdirs to the host OS
818void MSXtar::recurseDirExtract(string_view dirName, unsigned sector)
819{
820 for (/* */ ; sector != 0; sector = getNextSector(sector)) {
821 SectorBuffer buf;
822 readLogicalSector(sector, buf);
823 for (auto& dirEntry : buf.dirEntry) {
824 if (dirEntry.filename[0] == one_of(char(0xe5), char(0x00), '.')) {
825 continue;
826 }
827 string filename = condensName(dirEntry);
828 string fullName = filename;
829 if (!dirName.empty()) {
830 fullName = strCat(dirName, '/', filename);
831 }
832 if (dirEntry.attrib != T_MSX_DIR) { // TODO
833 fileExtract(fullName, dirEntry);
834 }
835 if (dirEntry.attrib == T_MSX_DIR) {
836 FileOperations::mkdirp(fullName);
837 // now change the access time
838 changeTime(fullName, dirEntry);
839 recurseDirExtract(
840 fullName,
841 clusterToSector(getStartCluster(dirEntry)));
842 }
843 }
844 }
845}
846
847string MSXtar::addDir(string_view rootDirName)
848{
849 return recurseDirFill(rootDirName, chrootSector);
850}
851
852string MSXtar::addFile(const string& filename)
853{
854 return addFileToDSK(filename, chrootSector);
855}
856
857string MSXtar::getItemFromDir(string_view rootDirName, string_view itemName)
858{
859 return singleItemExtract(rootDirName, itemName, chrootSector);
860}
861
862void MSXtar::getDir(string_view rootDirName)
863{
864 recurseDirExtract(rootDirName, chrootSector);
865}
866
867} // namespace openmsx
TclObject t
Definition: one_of.hh:7
std::string getItemFromDir(std::string_view rootDirName, std::string_view itemName)
Definition: MSXtar.cc:857
MSXtar(SectorAccessibleDisk &disk)
Definition: MSXtar.cc:135
void getDir(std::string_view rootDirName)
Definition: MSXtar.cc:862
std::string addFile(const std::string &filename)
Definition: MSXtar.cc:852
void chdir(std::string_view newRootDir)
Definition: MSXtar.cc:719
void mkdir(std::string_view newRootDir)
Definition: MSXtar.cc:724
std::string addDir(std::string_view rootDirName)
Definition: MSXtar.cc:847
std::string dir()
Definition: MSXtar.cc:690
void readSector(size_t sector, SectorBuffer &buf)
void readSectors(SectorBuffer *buffers, size_t startSector, size_t nbSectors)
static constexpr size_t SECTOR_SIZE
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.
Definition: zstring_view.hh:22
constexpr const char * c_str() const
Definition: zstring_view.hh:49
constexpr double e
Definition: Math.hh:18
std::pair< string_view, string_view > splitOnLast(string_view str, string_view chars)
Definition: StringOp.cc:108
void trimRight(string &str, const char *chars)
Definition: StringOp.cc:29
std::pair< string_view, string_view > splitOnFirst(string_view str, string_view chars)
Definition: StringOp.cc:91
void trimLeft(string &str, const char *chars)
Definition: StringOp.cc:58
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:265
void mkdirp(string path)
Acts like the unix command "mkdir -p".
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr unsigned SECTOR_SIZE
Definition: DirAsDSK.cc:23
constexpr unsigned EOF_FAT
Definition: DirAsDSK.cc:38
constexpr byte T_MSX_READ
Definition: MSXtar.cc:35
constexpr byte T_MSX_SYS
Definition: MSXtar.cc:37
constexpr const char *const filename
constexpr byte T_MSX_ARC
Definition: MSXtar.cc:40
constexpr unsigned BAD_FAT
Definition: DirAsDSK.cc:37
constexpr byte T_MSX_REG
Definition: MSXtar.cc:34
constexpr byte T_MSX_VOL
Definition: MSXtar.cc:38
constexpr byte T_MSX_HID
Definition: MSXtar.cc:36
constexpr byte T_MSX_DIR
Definition: MSXtar.cc:39
constexpr byte T_MSX_LFN
Definition: MSXtar.cc:44
bool foreach_file_and_directory(std::string path, FileAction fileAction, DirAction dirAction)
void swap(openmsx::MemBuffer< T > &l, openmsx::MemBuffer< T > &r) noexcept
Definition: MemBuffer.hh:202
size_t size(std::string_view utf8)
auto transform_in_place(ForwardRange &&range, UnaryOperation op)
Definition: stl.hh:187
std::string strCat(Ts &&...ts)
Definition: strCat.hh:549
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:627
unsigned date
Definition: MSXtar.cc:437
unsigned time
Definition: MSXtar.cc:437
MSXBootSector bootSector
MSXDirEntry dirEntry[16]
constexpr auto xrange(T e)
Definition: xrange.hh:133