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