openMSX
HD.cc
Go to the documentation of this file.
1 #include "HD.hh"
2 #include "FileContext.hh"
3 #include "FilePool.hh"
4 #include "DeviceConfig.hh"
5 #include "CliComm.hh"
6 #include "HDImageCLI.hh"
7 #include "MSXMotherBoard.hh"
8 #include "Reactor.hh"
9 #include "Display.hh"
10 #include "GlobalSettings.hh"
11 #include "MSXException.hh"
12 #include "Timer.hh"
13 #include "serialize.hh"
14 #include "tiger.hh"
15 #include <cassert>
16 #include <memory>
17 
18 namespace openmsx {
19 
20 using std::string;
21 
22 HD::HD(const DeviceConfig& config)
23  : motherBoard(config.getMotherBoard())
24  , name("hdX")
25 {
26  hdInUse = motherBoard.getSharedStuff<HDInUse>("hdInUse");
27 
28  unsigned id = 0;
29  while ((*hdInUse)[id]) {
30  ++id;
31  if (id == MAX_HD) {
32  throw MSXException("Too many HDs");
33  }
34  }
35  // for exception safety, set hdInUse only at the end
36  name[2] = char('a' + id);
37 
38  // For the initial hd image, savestate should only try exactly this
39  // (resolved) filename. For user-specified hd images (commandline or
40  // via hda command) savestate will try to re-resolve the filename.
41  auto mode = File::NORMAL;
42  string cliImage = HDImageCLI::getImageForId(id);
43  if (cliImage.empty()) {
44  const auto& original = config.getChildData("filename");
45  filename = Filename(config.getFileContext().resolveCreate(original));
46  mode = File::CREATE;
47  } else {
48  filename = Filename(std::move(cliImage), userFileContext());
49  }
50 
51  file = File(filename, mode);
52  filesize = file.getSize();
53  if (mode == File::CREATE && filesize == 0) {
54  // OK, the file was just newly created. Now make sure the file
55  // is of the right (default) size
56  file.truncate(size_t(config.getChildDataAsInt("size", 0)) * 1024 * 1024);
57  filesize = file.getSize();
58  }
59  tigerTree.emplace(*this, filesize, filename.getResolved());
60 
61  (*hdInUse)[id] = true;
62  hdCommand.emplace(
63  motherBoard.getCommandController(),
64  motherBoard.getStateChangeDistributor(),
65  motherBoard.getScheduler(),
66  *this,
67  motherBoard.getReactor().getGlobalSettings().getPowerSetting());
68 
69  motherBoard.getMSXCliComm().update(CliComm::HARDWARE, name, "add");
70 }
71 
73 {
74  motherBoard.getMSXCliComm().update(CliComm::HARDWARE, name, "remove");
75 
76  unsigned id = name[2] - 'a';
77  assert((*hdInUse)[id]);
78  (*hdInUse)[id] = false;
79 }
80 
81 void HD::switchImage(const Filename& newFilename)
82 {
83  file = File(newFilename);
84  filename = newFilename;
85  filesize = file.getSize();
86  tigerTree.emplace(*this, filesize, filename.getResolved());
87  motherBoard.getMSXCliComm().update(CliComm::MEDIA, getName(),
88  filename.getResolved());
89 }
90 
91 size_t HD::getNbSectorsImpl() const
92 {
93  return filesize / sizeof(SectorBuffer);
94 }
95 
96 void HD::readSectorsImpl(
97  SectorBuffer* buffers, size_t startSector, size_t num)
98 {
99  file.seek(startSector * sizeof(SectorBuffer));
100  file.read(buffers, num * sizeof(SectorBuffer));
101 }
102 
103 void HD::writeSectorImpl(size_t sector, const SectorBuffer& buf)
104 {
105  file.seek(sector * sizeof(buf));
106  file.write(&buf, sizeof(buf));
107  tigerTree->notifyChange(sector * sizeof(buf), sizeof(buf),
108  file.getModificationDate());
109 }
110 
111 bool HD::isWriteProtectedImpl() const
112 {
113  return file.isReadOnly();
114 }
115 
116 Sha1Sum HD::getSha1SumImpl(FilePool& filePool)
117 {
118  if (hasPatches()) {
119  return SectorAccessibleDisk::getSha1SumImpl(filePool);
120  }
121  return filePool.getSha1Sum(file);
122 }
123 
124 void HD::showProgress(size_t position, size_t maxPosition)
125 {
126  // only show progress iff:
127  // - 1 second has passed since last progress OR
128  // - we reach completion and did progress before (to show the 100%)
129  // This avoids showing any progress if the operation would take less than 1 second.
130  auto now = Timer::getTime();
131  if (((now - lastProgressTime) > 1000000) ||
132  ((position == maxPosition) && everDidProgress)) {
133  lastProgressTime = now;
134  int percentage = int((100 * position) / maxPosition);
135  motherBoard.getMSXCliComm().printProgress(
136  "Calculating hash for ", filename.getResolved(),
137  "... ", percentage, '%');
138  motherBoard.getReactor().getDisplay().repaint();
139  everDidProgress = true;
140  }
141 }
142 
143 std::string HD::getTigerTreeHash()
144 {
145  lastProgressTime = Timer::getTime();
146  everDidProgress = false;
147  auto callback = [this](size_t p, size_t t) { showProgress(p, t); };
148  return tigerTree->calcHash(callback).toString(); // calls HD::getData()
149 }
150 
151 uint8_t* HD::getData(size_t offset, size_t size)
152 {
153  assert(size <= TigerTree::BLOCK_SIZE);
154  assert((offset % sizeof(SectorBuffer)) == 0);
155  assert((size % sizeof(SectorBuffer)) == 0);
156 
157  struct Work {
158  char extra; // at least one byte before 'bufs'
159  // likely here are padding bytes in between
161  };
162  static Work work; // not reentrant
163 
164  size_t sector = offset / sizeof(SectorBuffer);
165  size_t num = size / sizeof(SectorBuffer);
166  readSectors(work.bufs, sector, num); // This possibly applies IPS patches.
167  return work.bufs[0].raw;
168 }
169 
170 bool HD::isCacheStillValid(time_t& cacheTime)
171 {
172  time_t fileTime = file.getModificationDate();
173  bool result = fileTime == cacheTime;
174  cacheTime = fileTime;
175  return result;
176 }
177 
178 SectorAccessibleDisk* HD::getSectorAccessibleDisk()
179 {
180  return this;
181 }
182 
183 std::string_view HD::getContainerName() const
184 {
185  return getName();
186 }
187 
188 bool HD::diskChanged()
189 {
190  return false; // TODO not implemented
191 }
192 
193 int HD::insertDisk(const std::string& newFilename)
194 {
195  try {
196  switchImage(Filename(newFilename));
197  return 0;
198  } catch (MSXException&) {
199  return -1;
200  }
201 }
202 
203 // version 1: initial version
204 // version 2: replaced 'checksum'(=sha1) with 'tthsum`
205 template<typename Archive>
206 void HD::serialize(Archive& ar, unsigned version)
207 {
208  Filename tmp = file.is_open() ? filename : Filename();
209  ar.serialize("filename", tmp);
210  if constexpr (Archive::IS_LOADER) {
211  if (tmp.empty()) {
212  // Lazily open file specified in config. And close if
213  // it was already opened (in the constructor). The
214  // latter can occur in the following scenario:
215  // - The hd image doesn't exist yet
216  // - Reverse creates savestates, these still have
217  // tmp="" (because file=nullptr)
218  // - At some later point the hd image gets created
219  // (e.g. on first access to the image)
220  // - Now reverse to some point in EmuTime before the
221  // first disk access
222  // - The loadstate re-constructs this HD object, but
223  // because the hd image does exist now, it gets
224  // opened in the constructor (file!=nullptr).
225  // - So to get in the same state as the initial
226  // savestate we again close the file. Otherwise the
227  // checksum-check code below goes wrong.
228  file.close();
229  } else {
230  tmp.updateAfterLoadState();
231  if (filename != tmp) switchImage(tmp);
232  assert(file.is_open());
233  }
234  }
235 
236  // store/check checksum
237  if (file.is_open()) {
238  bool mismatch = false;
239 
240  if (ar.versionAtLeast(version, 2)) {
241  // use tiger-tree-hash
242  string oldTiger;
243  if constexpr (!Archive::IS_LOADER) {
244  oldTiger = getTigerTreeHash();
245  }
246  ar.serialize("tthsum", oldTiger);
247  if constexpr (Archive::IS_LOADER) {
248  string newTiger = getTigerTreeHash();
249  mismatch = oldTiger != newTiger;
250  }
251  } else {
252  // use sha1
253  auto& filepool = motherBoard.getReactor().getFilePool();
254  Sha1Sum oldChecksum;
255  if constexpr (!Archive::IS_LOADER) {
256  oldChecksum = getSha1Sum(filepool);
257  }
258  string oldChecksumStr = oldChecksum.empty()
259  ? string{}
260  : oldChecksum.toString();
261  ar.serialize("checksum", oldChecksumStr);
262  oldChecksum = oldChecksumStr.empty()
263  ? Sha1Sum()
264  : Sha1Sum(oldChecksumStr);
265 
266  if constexpr (Archive::IS_LOADER) {
267  Sha1Sum newChecksum = getSha1Sum(filepool);
268  mismatch = oldChecksum != newChecksum;
269  }
270  }
271 
272  if constexpr (Archive::IS_LOADER) {
273  if (mismatch) {
274  motherBoard.getMSXCliComm().printWarning(
275  "The content of the harddisk ",
276  tmp.getResolved(),
277  " has changed since the time this savestate was "
278  "created. This might result in emulation problems "
279  "or even diskcorruption. To prevent the latter, "
280  "the harddisk is now write-protected.");
282  }
283  }
284  }
285 }
287 
288 } // namespace openmsx
uintptr_t id
Definition: Interpreter.cc:26
TclObject t
void printProgress(std::string_view message)
Definition: CliComm.cc:20
virtual void update(UpdateType type, std::string_view name, std::string_view value)=0
void printWarning(std::string_view message)
Definition: CliComm.cc:10
const FileContext & getFileContext() const
Definition: DeviceConfig.cc:9
int getChildDataAsInt(std::string_view name, int defaultValue) const
Definition: DeviceConfig.cc:57
std::string_view getChildData(std::string_view name) const
Definition: DeviceConfig.cc:48
void repaint()
Redraw the display.
Definition: Display.cc:369
std::string resolveCreate(std::string_view filename) const
Definition: FileContext.cc:88
void close()
Close the current file.
Definition: File.cc:86
void seek(size_t pos)
Move read/write pointer to the specified position.
Definition: File.cc:116
bool isReadOnly() const
Check if this file is readonly.
Definition: File.cc:152
void read(void *buffer, size_t num)
Read from file.
Definition: File.cc:91
time_t getModificationDate()
Get the date/time of last modification.
Definition: File.cc:157
void truncate(size_t size)
Truncate file size.
Definition: File.cc:126
bool is_open() const
Return true iff this file handle refers to an open file.
Definition: File.hh:63
size_t getSize()
Returns the size of this file.
Definition: File.cc:111
void write(const void *buffer, size_t num)
Write to file.
Definition: File.cc:96
This class represents a filename.
Definition: Filename.hh:18
bool empty() const
Convenience method to test for empty filename.
Definition: Filename.cc:21
const std::string & getResolved() const &
Definition: Filename.hh:47
void updateAfterLoadState()
After a loadstate we prefer to use the exact same file as before savestate.
Definition: Filename.cc:8
BooleanSetting & getPowerSetting()
static std::string getImageForId(int id)
Definition: HDImageCLI.cc:32
const std::string & getName() const
Definition: HD.hh:27
void switchImage(const Filename &filename)
Definition: HD.cc:81
~HD() override
Definition: HD.cc:72
HD(const DeviceConfig &config)
Definition: HD.cc:22
std::string getTigerTreeHash()
Definition: HD.cc:143
void serialize(Archive &ar, unsigned version)
Definition: HD.cc:206
StateChangeDistributor & getStateChangeDistributor()
CommandController & getCommandController()
std::shared_ptr< T > getSharedStuff(std::string_view name, Args &&...args)
Some MSX device parts are shared between several MSX devices (e.g.
GlobalSettings & getGlobalSettings()
Definition: Reactor.hh:104
Display & getDisplay()
Definition: Reactor.hh:86
FilePool & getFilePool()
Definition: Reactor.hh:91
void readSectors(SectorBuffer *buffers, size_t startSector, size_t nbSectors)
Sha1Sum getSha1Sum(FilePool &filepool)
Calculate SHA1 of the content of this disk.
virtual Sha1Sum getSha1SumImpl(FilePool &filepool)
This class represents the result of a sha1 calculation (a 160-bit value).
Definition: sha1.hh:22
bool empty() const
Definition: utils/sha1.cc:242
std::string toString() const
Definition: utils/sha1.cc:230
static constexpr size_t BLOCK_SIZE
Definition: TigerTree.hh:75
uint64_t getTime()
Get current (real) time in us.
Definition: Timer.cc:7
This file implemented 3 utility functions:
Definition: Autofire.cc:9
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:173
size_t size(std::string_view utf8)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:998