openMSX
DiskManipulator.cc
Go to the documentation of this file.
1#include "DiskManipulator.hh"
2#include "DiskContainer.hh"
3#include "MSXtar.hh"
4#include "DiskImageUtils.hh"
5#include "DSKDiskImage.hh"
6#include "DiskPartition.hh"
7#include "CommandException.hh"
8#include "Reactor.hh"
9#include "File.hh"
10#include "Filename.hh"
11#include "FileContext.hh"
12#include "FileException.hh"
13#include "FileOperations.hh"
14#include "SectorBasedDisk.hh"
15#include "MsxChar2Unicode.hh"
16#include "MSXDevice.hh"
17#include "MSXMotherBoard.hh"
18#include "MSXPPI.hh"
19#include "Keyboard.hh"
20#include "StringOp.hh"
21#include "TclObject.hh"
22#include "narrow.hh"
23#include "one_of.hh"
24#include "ranges.hh"
25#include "static_vector.hh"
26#include "stl.hh"
27#include "strCat.hh"
28#include "view.hh"
29#include "xrange.hh"
30#include <array>
31#include <cassert>
32#include <cctype>
33#include <memory>
34
35using std::string;
36using std::string_view;
37
38namespace openmsx {
39
41 Reactor& reactor_)
42 : Command(commandController_, "diskmanipulator")
43 , reactor(reactor_)
44{
45}
46
48{
49 assert(drives.empty()); // all DiskContainers must be unregistered
50}
51
52string DiskManipulator::getMachinePrefix() const
53{
54 string_view id = reactor.getMachineID();
55 return id.empty() ? string{} : strCat(id, "::");
56}
57
58const MsxChar2Unicode& DiskManipulator::getMsxChar2Unicode() const
59{
60 // TODO cleanup this code. It should be easier to get a hold of the
61 // 'MsxChar2Unicode' object. Probably the 'Keyboard' class is not the
62 // right location to store it.
63 try {
64 if (MSXMotherBoard* board = reactor.getMotherBoard()) {
65 if (MSXPPI* ppi = dynamic_cast<MSXPPI*>(board->findDevice("ppi"))) {
66 return ppi->getKeyboard().getMsxChar2Unicode();
67 }
68 }
69 } catch (MSXException&) {
70 }
71 static const MsxChar2Unicode defaultMsxChars("MSXVID.TXT");
72 return defaultMsxChars;
73}
74
76 DiskContainer& drive, std::string_view prefix)
77{
78 assert(findDriveSettings(drive) == end(drives));
79 DriveSettings driveSettings;
80 driveSettings.drive = &drive;
81 driveSettings.driveName = strCat(prefix, drive.getContainerName());
82 driveSettings.partition = 0;
83 drives.push_back(driveSettings);
84}
85
87{
88 auto it = findDriveSettings(drive);
89 assert(it != end(drives));
90 move_pop_back(drives, it);
91}
92
93std::string DiskManipulator::DriveSettings::getWorkingDir(unsigned p)
94{
95 return p < workingDir.size() ? workingDir[p] : "/";
96}
97
98void DiskManipulator::DriveSettings::setWorkingDir(unsigned p, std::string_view dir)
99{
100 if (p >= workingDir.size()) {
101 workingDir.resize(p + 1, "/");
102 }
103 workingDir[p] = dir;
104}
105
106DiskManipulator::Drives::iterator DiskManipulator::findDriveSettings(
107 DiskContainer& drive)
108{
109 return ranges::find(drives, &drive, &DriveSettings::drive);
110}
111
112DiskManipulator::Drives::iterator DiskManipulator::findDriveSettings(
113 string_view driveName)
114{
115 return ranges::find(drives, driveName, &DriveSettings::driveName);
116}
117
118DiskManipulator::DriveSettings& DiskManipulator::getDriveSettings(
119 string_view diskName)
120{
121 // first split-off the end numbers (if present)
122 // these will be used as partition indication
123 auto pos1 = diskName.find("::");
124 auto tmp1 = (pos1 == string_view::npos) ? diskName : diskName.substr(pos1);
125 auto pos2 = tmp1.find_first_of("0123456789");
126 auto pos1b = (pos1 == string_view::npos) ? 0 : pos1;
127 auto tmp2 = diskName.substr(0, pos2 + pos1b);
128
129 auto it = findDriveSettings(tmp2);
130 if (it == end(drives)) {
131 it = findDriveSettings(tmpStrCat(getMachinePrefix(), tmp2));
132 if (it == end(drives)) {
133 throw CommandException("Unknown drive: ", tmp2);
134 }
135 }
136
137 auto* disk = it->drive->getSectorAccessibleDisk();
138 if (!disk) {
139 // not a SectorBasedDisk
140 throw CommandException("Unsupported disk type.");
141 }
142
143 if (pos2 == string_view::npos) {
144 // whole disk
145 it->partition = 0;
146 } else {
147 auto partitionName = diskName.substr(pos2);
148 auto partition = StringOp::stringToBase<10, unsigned>(partitionName);
149 if (!partition) {
150 throw CommandException("Invalid partition name: ", partitionName);
151 }
153 it->partition = *partition;
154 }
155 return *it;
156}
157
158DiskPartition DiskManipulator::getPartition(
159 const DriveSettings& driveData)
160{
161 auto* disk = driveData.drive->getSectorAccessibleDisk();
162 assert(disk);
163 return {*disk, driveData.partition};
164}
165
166static std::optional<MSXBootSectorType> parseBootSectorType(std::string_view s) {
167 if (s == "-dos1") return MSXBootSectorType::DOS1;
168 if (s == "-dos2") return MSXBootSectorType::DOS2;
169 if (s == "-nextor") return MSXBootSectorType::NEXTOR;
170 return {};
171};
172
173static size_t parseSectorSize(zstring_view tok) {
174 char* q;
175 size_t bytes = strtoull(tok.c_str(), &q, 0);
176 int scale = 1024; // default is kilobytes
177 if (*q) {
178 if ((q == tok.c_str()) || *(q + 1)) {
179 throw CommandException("Invalid size: ", tok);
180 }
181 switch (tolower(*q)) {
182 case 'b':
183 scale = 1;
184 break;
185 case 'k':
186 scale = 1024;
187 break;
188 case 'm':
189 scale = 1024 * 1024;
190 break;
191 case 's':
193 break;
194 default:
195 throw CommandException("Invalid suffix: ", q);
196 }
197 }
198 size_t sectors = (bytes * scale) / SectorBasedDisk::SECTOR_SIZE;
199
200 // TEMP FIX: the smallest boot sector we create in MSXtar is for
201 // a normal single sided disk.
202 // TODO: MSXtar must be altered and this temp fix must be set to
203 // the real smallest dsk possible (= boot sector + minimal fat +
204 // minimal dir + minimal data clusters)
205 return std::max(sectors, size_t(720));
206}
207
208void DiskManipulator::execute(std::span<const TclObject> tokens, TclObject& result)
209{
210 if (tokens.size() == 1) {
211 throw CommandException("Missing argument");
212 }
213
214 string_view subCmd = tokens[1].getString();
215 if (((tokens.size() != 4) && (subCmd == one_of("savedsk", "mkdir"))) ||
216 ((tokens.size() != 3) && (subCmd == "dir" )) ||
217 ((tokens.size() < 3 || tokens.size() > 4) && (subCmd == "chdir" )) ||
218 ((tokens.size() < 3 || tokens.size() > 5) && (subCmd == "format" )) ||
219 ((tokens.size() < 3) && (subCmd == "partition" )) ||
220 ((tokens.size() < 4) && (subCmd == one_of("export", "import", "create")))) {
221 throw CommandException("Incorrect number of parameters");
222 }
223
224 if (subCmd == "export") {
225 string_view dir = tokens[3].getString();
226 auto directory = FileOperations::expandTilde(string(dir));
227 if (!FileOperations::isDirectory(directory)) {
228 throw CommandException(dir, " is not a directory");
229 }
230 auto& settings = getDriveSettings(tokens[2].getString());
231 // Workaround clang-13/libc++ bug
232 //std::span<const TclObject> lists(std::begin(tokens) + 4, std::end(tokens));
233 std::span<const TclObject> lists(&*(std::begin(tokens) + 4), std::end(tokens) - (std::begin(tokens) + 4));
234 exprt(settings, directory, lists);
235
236 } else if (subCmd == "import") {
237 auto& settings = getDriveSettings(tokens[2].getString());
238 // Workaround clang-13/libc++ bug
239 //std::span<const TclObject> lists(std::begin(tokens) + 3, std::end(tokens));
240 std::span<const TclObject> lists(&*(std::begin(tokens) + 3), std::end(tokens) - (std::begin(tokens) + 3));
241 result = import(settings, lists);
242
243 } else if (subCmd == "savedsk") {
244 auto& settings = getDriveSettings(tokens[2].getString());
245 savedsk(settings, FileOperations::expandTilde(string(tokens[3].getString())));
246
247 } else if (subCmd == "chdir") {
248 auto& settings = getDriveSettings(tokens[2].getString());
249 if (tokens.size() == 3) {
250 result = tmpStrCat("Current directory: ",
251 settings.getWorkingDir(settings.partition));
252 } else {
253 result = chdir(settings, tokens[3].getString());
254 }
255
256 } else if (subCmd == "mkdir") {
257 auto& settings = getDriveSettings(tokens[2].getString());
258 mkdir(settings, tokens[3].getString());
259
260 } else if (subCmd == "create") {
261 create(tokens);
262
263 } else if (subCmd == "partition") {
264 partition(tokens);
265
266 } else if (subCmd == "format") {
267 format(tokens);
268
269 } else if (subCmd == "dir") {
270 auto& settings = getDriveSettings(tokens[2].getString());
271 result = dir(settings);
272
273 } else {
274 throw CommandException("Unknown subcommand: ", subCmd);
275 }
276}
277
278string DiskManipulator::help(std::span<const TclObject> tokens) const
279{
280 string helpText;
281 if (tokens.size() >= 2) {
282 if (tokens[1] == "import") {
283 helpText =
284 "diskmanipulator import <disk name> <host directory|host file>\n"
285 "Import all files and subdirs from the host OS as specified into the <disk name> in the\n"
286 "current MSX subdirectory as was specified with the last chdir command.\n";
287 } else if (tokens[1] == "export") {
288 helpText =
289 "diskmanipulator export <disk name> <host directory>\n"
290 "Extract all files and subdirs from the MSX subdirectory specified with the chdir command\n"
291 "from <disk name> to the host OS in <host directory>.\n";
292 } else if (tokens[1] == "savedsk") {
293 helpText =
294 "diskmanipulator savedsk <disk name> <dskfilename>\n"
295 "Save the complete drive content to <dskfilename>, it is not possible to save just one\n"
296 "partition. The main purpose of this command is to make it possible to save a 'ramdsk' into\n"
297 "a file and to take 'live backups' of dsk-files in use.\n";
298 } else if (tokens[1] == "chdir") {
299 helpText =
300 "diskmanipulator chdir <disk name> <MSX directory>\n"
301 "Change the working directory on <disk name>. This will be the directory were the 'import',\n"
302 "'export' and 'dir' commands will work on.\n"
303 "In case of a partitioned drive, each partition has its own working directory.\n";
304 } else if (tokens[1] == "mkdir") {
305 helpText =
306 "diskmanipulator mkdir <disk name> <MSX directory>\n"
307 "Create the specified directory on <disk name>. If needed, all missing parent directories\n"
308 "are created at the same time. Accepts both absolute and relative path names.\n";
309 } else if (tokens[1] == "create") {
310 helpText =
311 "diskmanipulator create <dskfilename> <size/option> [<size/option>...]\n"
312 "Create a formatted dsk file with the given size.\n"
313 "If multiple sizes are given, a partitioned disk image will be created with each partition\n"
314 "having the size as indicated. By default the sizes are expressed in kilobyte, add the\n"
315 "postfix M for megabyte.\n"
316 "When using the -dos1 option, the boot sector of the created image will be MSX-DOS1\n"
317 "compatible. When using the -nextor option, the boot sector and partition table will be\n"
318 "Nextor compatible, and FAT16 volumes can be created.\n";
319 } else if (tokens[1] == "partition") {
320 helpText =
321 "diskmanipulator partition <disk name> [<size/option>...]\n"
322 "Partitions and formats the current <disk name> to the indicated sizes. By default the\n"
323 "sizes are expressed in kilobyte, add the postfix M for megabyte.\n"
324 "When using the -dos1 option, the boot sector of the disk will be MSX-DOS1 compatible.\n"
325 "When using the -nextor option, the boot sector and partition table will be Nextor\n"
326 "compatible, and FAT16 volumes can be created.\n";
327 } else if (tokens[1] == "format") {
328 helpText =
329 "diskmanipulator format <disk name>\n"
330 "Formats the current (partition on) <disk name>. By default, it will create a regular\n"
331 "FAT12 MSX file system with an MSX-DOS2 boot sector, or, when the -dos1 option is\n"
332 "specified, with an MSX-DOS1 boot sector. When the -nextor option is specified, it\n"
333 "will create a FAT12 or FAT16 file system, with a Nextor boot sector.\n";
334 } else if (tokens[1] == "dir") {
335 helpText =
336 "diskmanipulator dir <disk name>\n"
337 "Shows the content of the current directory on <disk name>\n";
338 } else {
339 helpText = strCat("Unknown diskmanipulator subcommand: ", tokens[1].getString());
340 }
341 } else {
342 helpText =
343 "diskmanipulator create <fn> <sz> [<sz> ...] : create a formatted dsk file with name <fn>\n"
344 " having the given (partition) size(s)\n"
345 "diskmanipulator partition <dn> [<sz> ...] : partition and format <disk name>\n"
346 "diskmanipulator savedsk <disk name> <fn> : save <disk name> as dsk file named as <fn>\n"
347 "diskmanipulator format <disk name> [<sz>] : format (a partition on) <disk name>\n"
348 "diskmanipulator chdir <disk name> <MSX dir> : change directory on <disk name>\n"
349 "diskmanipulator mkdir <disk name> <MSX dir> : create directory on <disk name>\n"
350 "diskmanipulator dir <disk name> : long format file listing of current\n"
351 " directory on <disk name>\n"
352 "diskmanipulator import <disk> <dir/file> ... : import files and subdirs from <dir/file>\n"
353 "diskmanipulator export <disk> <host dir> : export all files on <disk> to <host dir>\n"
354 "For more info use 'help diskmanipulator <subcommand>'.\n";
355 }
356 return helpText;
357}
358
359void DiskManipulator::tabCompletion(std::vector<string>& tokens) const
360{
361 using namespace std::literals;
362 if (tokens.size() == 2) {
363 static constexpr std::array cmds = {
364 "import"sv, "export"sv, "savedsk"sv, "dir"sv, "create"sv,
365 "partition"sv, "format"sv, "chdir"sv, "mkdir"sv,
366 };
367 completeString(tokens, cmds);
368
369 } else if ((tokens.size() == 3) && (tokens[1] == "create")) {
371
372 } else if (tokens.size() == 3) {
373 std::vector<string> names;
374 if (tokens[1] == one_of("partition", "format")) {
375 names.emplace_back("-dos1");
376 names.emplace_back("-dos2");
377 names.emplace_back("-nextor");
378 }
379 for (const auto& d : drives) {
380 const auto& name1 = d.driveName; // with prefix
381 const auto& name2 = d.drive->getContainerName(); // without prefix
382 append(names, {name1, std::string(name2)});
383 // if it has partitions then we also add the partition
384 // numbers to the autocompletion
385 if (auto* disk = d.drive->getSectorAccessibleDisk()) {
386 for (unsigned i = 1; true; ++i) {
387 try {
388 SectorBuffer buf;
389 DiskImageUtils::getPartition(*disk, i, buf);
390 } catch (MSXException&) {
391 break;
392 }
393 try {
395 append(names,
396 {strCat(name1, i), strCat(name2, i)});
397 } catch (MSXException&) {
398 // skip invalid partition
399 }
400 }
401 }
402 }
403 completeString(tokens, names);
404
405 } else if (tokens.size() >= 4) {
406 if (tokens[1] == one_of("savedsk", "import", "export")) {
408 } else if (tokens[1] == one_of("create", "partition", "format")) {
409 static constexpr std::array cmds = {
410 "360"sv, "720"sv, "32M"sv, "-dos1"sv, "-dos2"sv, "-nextor"sv,
411 };
412 completeString(tokens, cmds);
413 }
414 }
415}
416
417void DiskManipulator::savedsk(const DriveSettings& driveData,
418 string filename)
419{
420 auto partition = getPartition(driveData);
421 SectorBuffer buf;
422 File file(std::move(filename), File::CREATE);
423 for (auto i : xrange(partition.getNbSectors())) {
424 partition.readSector(i, buf);
425 file.write(buf.raw);
426 }
427}
428
429static std::pair<MSXBootSectorType, std::vector<unsigned>> parsePartitionSizes(auto tokens)
430{
432 std::vector<unsigned> sizes;
433
434 for (const auto& token_ : tokens) {
435 if (auto t = parseBootSectorType(token_.getString())) {
436 bootType = *t;
437 } else if (size_t sectors = parseSectorSize(token_.getString());
439 sizes.push_back(narrow<unsigned>(sectors));
440 } else {
441 throw CommandException("Partition size too large.");
442 }
443 }
444
445 return {bootType, sizes};
446}
447
448void DiskManipulator::create(std::span<const TclObject> tokens)
449{
450 auto [bootType, sizes] = parsePartitionSizes(view::drop(tokens, 3));
451
452 size_t totalSectors = sum(sizes, [](size_t s) { return s; });
453 if (totalSectors == 0) {
454 throw CommandException("No size(s) given.");
455 }
456
457 // create file with correct size
458 Filename filename(FileOperations::expandTilde(string(tokens[2].getString())));
459 try {
460 File file(filename, File::CREATE);
461 file.truncate(totalSectors * SectorBasedDisk::SECTOR_SIZE);
462 } catch (FileException& e) {
463 throw CommandException("Couldn't create image: ", e.getMessage());
464 }
465
466 // initialize (create partition tables and format partitions)
467 DSKDiskImage image(filename);
468 if (sizes.size() > 1) {
469 unsigned partitionCount = DiskImageUtils::partition(image,
470 static_cast<std::span<const unsigned>>(sizes), bootType);
471 if (partitionCount != sizes.size()) {
472 throw CommandException("Could not create all partitions; ",
473 partitionCount, " of ", sizes.size(), " created.");
474 }
475 } else {
476 // only one partition specified, don't create partition table
477 DiskImageUtils::format(image, bootType);
478 }
479}
480
481void DiskManipulator::partition(std::span<const TclObject> tokens)
482{
483 auto [bootType, sizes] = parsePartitionSizes(view::drop(tokens, 3));
484
485 // initialize (create partition tables and format partitions)
486 auto& settings = getDriveSettings(tokens[2].getString());
487 if (settings.partition > 0) {
488 throw CommandException("Disk name must not have partition number.");
489 }
490 auto* image = settings.drive->getSectorAccessibleDisk();
491 unsigned partitionCount = DiskImageUtils::partition(*image,
492 static_cast<std::span<const unsigned>>(sizes), bootType);
493 if (partitionCount != sizes.size()) {
494 throw CommandException("Could not create all partitions; ",
495 partitionCount, " of ", sizes.size(), " created.");
496 }
497}
498
499void DiskManipulator::format(std::span<const TclObject> tokens)
500{
502 std::optional<string> drive;
503 std::optional<size_t> size;
504 for (const auto& token_ : view::drop(tokens, 2)) {
505 if (auto t = parseBootSectorType(token_.getString())) {
506 bootType = *t;
507 } else if (!drive) {
508 drive = token_.getString();
509 } else if (!size) {
510 size = parseSectorSize(token_.getString());
511 } else {
512 throw CommandException("Incorrect number of parameters");
513 }
514 }
515 if (drive) {
516 auto& driveData = getDriveSettings(*drive);
517 auto partition = getPartition(driveData);
518 if (size) {
519 DiskImageUtils::format(partition, bootType, *size);
520 } else {
521 DiskImageUtils::format(partition, bootType);
522 }
523 driveData.setWorkingDir(driveData.partition, "/");
524 } else {
525 throw CommandException("Incorrect number of parameters");
526 }
527}
528
529MSXtar DiskManipulator::getMSXtar(
530 SectorAccessibleDisk& disk, DriveSettings& driveData)
531{
533 throw CommandException("Please select partition number.");
534 }
535
536 MSXtar result(disk, getMsxChar2Unicode());
537 string cwd = driveData.getWorkingDir(driveData.partition);
538 try {
539 result.chdir(cwd);
540 } catch (MSXException&) {
541 driveData.setWorkingDir(driveData.partition, "/");
542 throw CommandException(
543 "Directory ", cwd,
544 " doesn't exist anymore. Went back to root "
545 "directory. Command aborted, please retry.");
546 }
547 return result;
548}
549
550string DiskManipulator::dir(DriveSettings& driveData)
551{
552 auto partition = getPartition(driveData);
553 auto workhorse = getMSXtar(partition, driveData);
554 return workhorse.dir();
555}
556
557string DiskManipulator::chdir(DriveSettings& driveData, string_view filename)
558{
559 auto partition = getPartition(driveData);
560 auto workhorse = getMSXtar(partition, driveData);
561 try {
562 workhorse.chdir(filename);
563 } catch (MSXException& e) {
564 throw CommandException("chdir failed: ", e.getMessage());
565 }
566 // TODO clean-up this temp hack, used to enable relative paths
567 string cwd = driveData.getWorkingDir(driveData.partition);
568 if (filename.starts_with('/')) {
569 cwd = filename;
570 } else {
571 if (!cwd.ends_with('/')) cwd += '/';
572 cwd.append(filename.data(), filename.size());
573 }
574 driveData.setWorkingDir(driveData.partition, cwd);
575 return "New working directory: " + cwd;
576}
577
578void DiskManipulator::mkdir(DriveSettings& driveData, string_view filename)
579{
580 auto partition = getPartition(driveData);
581 auto workhorse = getMSXtar(partition, driveData);
582 try {
583 workhorse.mkdir(filename);
584 } catch (MSXException& e) {
585 throw CommandException(std::move(e).getMessage());
586 }
587}
588
589string DiskManipulator::import(DriveSettings& driveData,
590 std::span<const TclObject> lists)
591{
592 auto partition = getPartition(driveData);
593 auto workhorse = getMSXtar(partition, driveData);
594
595 string messages;
596 auto& interp = getInterpreter();
597 for (const auto& l : lists) {
598 for (auto i : xrange(l.getListLength(interp))) {
599 auto s = FileOperations::expandTilde(string(l.getListIndex(interp, i).getString()));
600 try {
601 auto st = FileOperations::getStat(s);
602 if (!st) {
603 throw CommandException("Non-existing file ", s);
604 }
606 messages += workhorse.addDir(s);
607 } else if (FileOperations::isRegularFile(*st)) {
608 messages += workhorse.addFile(s);
609 } else {
610 // ignore other stuff (sockets, device nodes, ..)
611 strAppend(messages, "Ignoring ", s, '\n');
612 }
613 } catch (MSXException& e) {
614 throw CommandException(std::move(e).getMessage());
615 }
616 }
617 }
618 return messages;
619}
620
621void DiskManipulator::exprt(DriveSettings& driveData, string_view dirname,
622 std::span<const TclObject> lists)
623{
624 auto partition = getPartition(driveData);
625 auto workhorse = getMSXtar(partition, driveData);
626 try {
627 if (lists.empty()) {
628 // export all
629 workhorse.getDir(dirname);
630 } else {
631 for (const auto& l : lists) {
632 workhorse.getItemFromDir(dirname, l.getString());
633 }
634 }
635 } catch (MSXException& e) {
636 throw CommandException(std::move(e).getMessage());
637 }
638}
639
640} // namespace openmsx
std::string image
Definition: HDImageCLI.cc:13
TclObject t
Definition: one_of.hh:7
Interpreter & getInterpreter() const final
Definition: Command.cc:38
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition: Completer.hh:147
static void completeString(std::vector< std::string > &tokens, ITER begin, ITER end, bool caseSensitive=true)
Definition: Completer.hh:133
virtual std::string_view getContainerName() const =0
void unregisterDrive(DiskContainer &drive)
DiskManipulator(CommandController &commandController, Reactor &reactor)
void registerDrive(DiskContainer &drive, std::string_view prefix)
Contains the main loop of openMSX.
Definition: Reactor.hh:68
MSXMotherBoard * getMotherBoard() const
Definition: Reactor.cc:375
std::string_view getMachineID() const
Definition: Reactor.cc:381
static constexpr size_t SECTOR_SIZE
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
Definition: zstring_view.hh:22
constexpr const char * c_str() const
Definition: zstring_view.hh:49
constexpr double e
Definition: Math.hh:21
void append(Result &)
Definition: stl.hh:290
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:285
constexpr mat4 scale(const vec3 &xyz)
Definition: gl_transform.hh:19
bool hasPartitionTable(SectorAccessibleDisk &disk)
Check whether the given disk is partitioned.
void checkSupportedPartition(SectorAccessibleDisk &disk, unsigned partition)
Check whether partition is of type FAT12 or FAT16.
Partition & getPartition(SectorAccessibleDisk &disk, unsigned partition, SectorBuffer &buf)
Gets the requested partition.
void format(SectorAccessibleDisk &disk, MSXBootSectorType bootType)
Format the given disk (= a single partition).
unsigned partition(SectorAccessibleDisk &disk, std::span< const unsigned > sizes, MSXBootSectorType bootType)
Write a partition table to the given disk and format each partition.
string expandTilde(string path)
Expand the '~' character to the users home directory.
bool isRegularFile(const Stat &st)
bool isDirectory(const Stat &st)
std::optional< Stat > getStat(zstring_view filename)
Call stat() and return the stat structure.
This file implemented 3 utility functions:
Definition: Autofire.cc:9
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:171
auto find(InputRange &&range, const T &value)
Definition: ranges.hh:160
size_t size(std::string_view utf8)
constexpr auto drop(Range &&range, size_t n)
Definition: view.hh:440
auto sum(InputRange &&range, Proj proj={})
Definition: stl.hh:236
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:125
TemporaryString tmpStrCat(Ts &&... ts)
Definition: strCat.hh:610
std::string strCat(Ts &&...ts)
Definition: strCat.hh:542
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:620
constexpr auto xrange(T e)
Definition: xrange.hh:132
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)