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