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