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