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