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