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