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"
10 #include "SectorAccessibleDisk.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 
25 using std::string;
26 using std::string_view;
27 
28 namespace openmsx {
29 
30 constexpr unsigned BAD_FAT = 0xFF7;
31 constexpr unsigned EOF_FAT = 0xFFF; // actually 0xFF8-0xFFF, signals EOF in FAT12
33 
34 constexpr byte T_MSX_REG = 0x00; // Normal file
35 constexpr byte T_MSX_READ = 0x01; // Read-Only file
36 constexpr byte T_MSX_HID = 0x02; // Hidden file
37 constexpr byte T_MSX_SYS = 0x04; // System file
38 constexpr byte T_MSX_VOL = 0x08; // filename is Volume Label
39 constexpr byte T_MSX_DIR = 0x10; // entry is a subdir
40 constexpr 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
44 constexpr byte T_MSX_LFN = 0x0F; // LFN entry (long files names)
45 
49 unsigned MSXtar::clusterToSector(unsigned cluster) const
50 {
51  return 1 + rootDirLast + sectorsPerCluster * (cluster - 2);
52 }
53 
57 unsigned MSXtar::sectorToCluster(unsigned sector) const
58 {
59  return 2 + ((sector - (1 + rootDirLast)) / sectorsPerCluster);
60 }
61 
62 
65 void 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 
108 void 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 
122 void 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)
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)
185 static 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
191 unsigned 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
203 void 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
221 unsigned 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
233 unsigned 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.
252 static 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
262 unsigned 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
285 unsigned 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
303 MSXtar::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
337 static 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
348 static 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
382 unsigned 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 
436 struct TimeDate {
437  unsigned time, date;
438 };
439 static 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
452 static 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
469 unsigned 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
480 void 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
556 MSXtar::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
578 string 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
617 string 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 
655 static 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
672 static 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 
690 string MSXtar::dir()
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
719 void MSXtar::chdir(string_view newRootDir)
720 {
721  chroot(newRootDir, false);
722 }
723 
724 void MSXtar::mkdir(string_view newRootDir)
725 {
726  unsigned tmpMSXchrootSector = chrootSector;
727  chroot(newRootDir, true);
728  chrootSector = tmpMSXchrootSector;
729 }
730 
731 void MSXtar::chroot(string_view newRootDir, bool createDir)
732 {
733  if (StringOp::startsWith(newRootDir, '/') || StringOp::startsWith(newRootDir, '\\')) {
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 
768 void 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
787 string 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
818 void 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 
847 string MSXtar::addDir(string_view rootDirName)
848 {
849  return recurseDirFill(rootDirName, chrootSector);
850 }
851 
852 string MSXtar::addFile(const string& filename)
853 {
854  return addFileToDSK(filename, chrootSector);
855 }
856 
857 string MSXtar::getItemFromDir(string_view rootDirName, string_view itemName)
858 {
859  return singleItemExtract(rootDirName, itemName, chrootSector);
860 }
861 
862 void MSXtar::getDir(string_view rootDirName)
863 {
864  recurseDirExtract(rootDirName, chrootSector);
865 }
866 
867 } // namespace openmsx
void swap(openmsx::MemBuffer< T > &l, openmsx::MemBuffer< T > &r) noexcept
Definition: MemBuffer.hh:202
TclObject t
Definition: one_of.hh:7
const std::string & getMessage() const &
Definition: MSXException.hh:23
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
std::pair< string_view, string_view > splitOnLast(string_view str, string_view chars)
Definition: StringOp.cc:128
void trimRight(string &str, const char *chars)
Definition: StringOp.cc:49
std::pair< string_view, string_view > splitOnFirst(string_view str, string_view chars)
Definition: StringOp.cc:111
void trimLeft(string &str, const char *chars)
Definition: StringOp.cc:78
bool startsWith(string_view total, string_view part)
Definition: StringOp.cc:29
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:269
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)
size_t size(std::string_view utf8)
auto transform_in_place(ForwardRange &&range, UnaryOperation op)
Definition: stl.hh:196
std::string strCat(Ts &&...ts)
Definition: strCat.hh:591
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:669
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:155