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  const string& original = config.getChildData("filename");
47  string resolved = config.getFileContext().resolveCreate(original);
48  filename = Filename(std::move(resolved));
49  mode = File::CREATE;
50  } else {
51  filename = Filename(std::move(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::readSectorsImpl(
102  SectorBuffer* buffers, size_t startSector, size_t num)
103 {
104  file.seek(startSector * sizeof(SectorBuffer));
105  file.read(buffers, num * sizeof(SectorBuffer));
106 }
107 
108 void HD::writeSectorImpl(size_t sector, const SectorBuffer& buf)
109 {
110  file.seek(sector * sizeof(buf));
111  file.write(&buf, sizeof(buf));
112  tigerTree->notifyChange(sector * sizeof(buf), sizeof(buf),
113  file.getModificationDate());
114 }
115 
116 bool HD::isWriteProtectedImpl() const
117 {
118  return file.isReadOnly();
119 }
120 
121 Sha1Sum HD::getSha1SumImpl(FilePool& filePool)
122 {
123  if (hasPatches()) {
124  return SectorAccessibleDisk::getSha1SumImpl(filePool);
125  }
126  return filePool.getSha1Sum(file);
127 }
128 
129 void HD::showProgress(size_t position, size_t maxPosition)
130 {
131  // only show progress iff:
132  // - 1 second has passed since last progress OR
133  // - we reach completion and did progress before (to show the 100%)
134  // This avoids showing any progress if the operation would take less than 1 second.
135  auto now = Timer::getTime();
136  if (((now - lastProgressTime) > 1000000) ||
137  ((position == maxPosition) && everDidProgress)) {
138  lastProgressTime = now;
139  int percentage = int((100 * position) / maxPosition);
140  motherBoard.getMSXCliComm().printProgress(
141  "Calculating hash for ", filename.getResolved(),
142  "... ", percentage, '%');
143  motherBoard.getReactor().getDisplay().repaint();
144  everDidProgress = true;
145  }
146 }
147 
148 std::string HD::getTigerTreeHash()
149 {
150  lastProgressTime = Timer::getTime();
151  everDidProgress = false;
152  auto callback = [this](size_t p, size_t t) { showProgress(p, t); };
153  return tigerTree->calcHash(callback).toString(); // calls HD::getData()
154 }
155 
156 uint8_t* HD::getData(size_t offset, size_t size)
157 {
158  assert(size <= TigerTree::BLOCK_SIZE);
159  assert((offset % sizeof(SectorBuffer)) == 0);
160  assert((size % sizeof(SectorBuffer)) == 0);
161 
162  struct Work {
163  char extra; // at least one byte before 'bufs'
164  // likely here are padding bytes in between
166  };
167  static Work work; // not reentrant
168 
169  size_t sector = offset / sizeof(SectorBuffer);
170  size_t num = size / sizeof(SectorBuffer);
171  readSectors(work.bufs, sector, num); // This possibly applies IPS patches.
172  return work.bufs[0].raw;
173 }
174 
175 bool HD::isCacheStillValid(time_t& cacheTime)
176 {
177  time_t fileTime = file.getModificationDate();
178  bool result = fileTime == cacheTime;
179  cacheTime = fileTime;
180  return result;
181 }
182 
183 SectorAccessibleDisk* HD::getSectorAccessibleDisk()
184 {
185  return this;
186 }
187 
188 std::string_view HD::getContainerName() const
189 {
190  return getName();
191 }
192 
193 bool HD::diskChanged()
194 {
195  return false; // TODO not implemented
196 }
197 
198 int HD::insertDisk(const std::string& newFilename)
199 {
200  try {
201  switchImage(Filename(newFilename));
202  return 0;
203  } catch (MSXException&) {
204  return -1;
205  }
206 }
207 
208 // version 1: initial version
209 // version 2: replaced 'checksum'(=sha1) with 'tthsum`
210 template<typename Archive>
211 void HD::serialize(Archive& ar, unsigned version)
212 {
213  Filename tmp = file.is_open() ? filename : Filename();
214  ar.serialize("filename", tmp);
215  if (ar.isLoader()) {
216  if (tmp.empty()) {
217  // Lazily open file specified in config. And close if
218  // it was already opened (in the constructor). The
219  // latter can occur in the following scenario:
220  // - The hd image doesn't exist yet
221  // - Reverse creates savestates, these still have
222  // tmp="" (because file=nullptr)
223  // - At some later point the hd image gets created
224  // (e.g. on first access to the image)
225  // - Now reverse to some point in EmuTime before the
226  // first disk access
227  // - The loadstate re-constructs this HD object, but
228  // because the hd image does exist now, it gets
229  // opened in the constructor (file!=nullptr).
230  // - So to get in the same state as the initial
231  // savestate we again close the file. Otherwise the
232  // checksum-check code below goes wrong.
233  file.close();
234  } else {
235  tmp.updateAfterLoadState();
236  if (filename != tmp) switchImage(tmp);
237  assert(file.is_open());
238  }
239  }
240 
241  // store/check checksum
242  if (file.is_open()) {
243  bool mismatch = false;
244 
245  if (ar.versionAtLeast(version, 2)) {
246  // use tiger-tree-hash
247  string oldTiger = ar.isLoader() ? string{} : getTigerTreeHash();
248  ar.serialize("tthsum", oldTiger);
249  if (ar.isLoader()) {
250  string newTiger = getTigerTreeHash();
251  mismatch = oldTiger != newTiger;
252  }
253  } else {
254  // use sha1
255  auto& filepool = motherBoard.getReactor().getFilePool();
256  Sha1Sum oldChecksum;
257  if (!ar.isLoader()) {
258  oldChecksum = getSha1Sum(filepool);
259  }
260  string oldChecksumStr = oldChecksum.empty()
261  ? string{}
262  : oldChecksum.toString();
263  ar.serialize("checksum", oldChecksumStr);
264  oldChecksum = oldChecksumStr.empty()
265  ? Sha1Sum()
266  : Sha1Sum(oldChecksumStr);
267 
268  if (ar.isLoader()) {
269  Sha1Sum newChecksum = getSha1Sum(filepool);
270  mismatch = oldChecksum != newChecksum;
271  }
272  }
273 
274  if (ar.isLoader() && mismatch) {
275  motherBoard.getMSXCliComm().printWarning(
276  "The content of the harddisk ",
277  tmp.getResolved(),
278  " has changed since the time this savestate was "
279  "created. This might result in emulation problems "
280  "or even diskcorruption. To prevent the latter, "
281  "the harddisk is now write-protected.");
283  }
284  }
285 }
287 
288 } // namespace openmsx
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
const std::string & getChildData(std::string_view name) const
Definition: DeviceConfig.cc:43
int getChildDataAsInt(std::string_view name, int defaultValue=0) const
Definition: DeviceConfig.cc:52
void repaint()
Redraw the display.
Definition: Display.cc:368
std::string resolveCreate(std::string_view filename) const
Definition: FileContext.cc:87
void close()
Close the current file.
Definition: File.cc:88
void seek(size_t pos)
Move read/write pointer to the specified position.
Definition: File.cc:118
bool isReadOnly() const
Check if this file is readonly.
Definition: File.cc:154
void read(void *buffer, size_t num)
Read from file.
Definition: File.cc:93
time_t getModificationDate()
Get the date/time of last modification.
Definition: File.cc:159
void truncate(size_t size)
Truncate file size.
Definition: File.cc:128
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:113
void write(const void *buffer, size_t num)
Write to file.
Definition: File.cc:98
This class represents a filename.
Definition: Filename.hh:18
bool empty() const
Convenience method to test for empty filename.
Definition: Filename.cc:23
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:10
BooleanSetting & getPowerSetting()
static std::string getImageForId(int id)
Definition: HDImageCLI.cc:29
const std::string & getName() const
Definition: HD.hh:27
void switchImage(const Filename &filename)
Definition: HD.cc:85
~HD() override
Definition: HD.cc:76
HD(const DeviceConfig &config)
Definition: HD.cc:24
std::string getTigerTreeHash()
Definition: HD.cc:148
void serialize(Archive &ar, unsigned version)
Definition: HD.cc:211
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:103
Display & getDisplay()
Definition: Reactor.hh:85
FilePool & getFilePool()
Definition: Reactor.hh:90
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:246
std::string toString() const
Definition: utils/sha1.cc:234
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:5
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:172
size_t size(std::string_view utf8)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:983