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