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 const 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 const 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 std::span<const TclObject> lists(std::begin(tokens) + 4, std::end(tokens));
297 exprt(settings, directory, lists);
298
299 } else if (subCmd == "import") {
300 auto& settings = getDriveSettings(tokens[2].getString());
301
302 bool overwrite = false;
303 std::array info = {flagArg("-overwrite", overwrite)};
304 auto lists = parseTclArgs(getInterpreter(), tokens.subspan(3), info);
305 if (lists.empty()) throw SyntaxError();
306
307 result = imprt(settings, lists, overwrite);
308
309 } else if (subCmd == "savedsk") {
310 const auto& settings = getDriveSettings(tokens[2].getString());
311 savedsk(settings, FileOperations::expandTilde(string(tokens[3].getString())));
312
313 } else if (subCmd == "chdir") {
314 auto& settings = getDriveSettings(tokens[2].getString());
315 if (tokens.size() == 3) {
316 result = tmpStrCat("Current directory: ",
317 settings.getWorkingDir(settings.partition));
318 } else {
319 result = chdir(settings, tokens[3].getString());
320 }
321
322 } else if (subCmd == "mkdir") {
323 auto& settings = getDriveSettings(tokens[2].getString());
324 mkdir(settings, tokens[3].getString());
325
326 } else if (subCmd == "create") {
327 create(tokens);
328
329 } else if (subCmd == "partition") {
330 partition(tokens);
331
332 } else if (subCmd == "format") {
333 format(tokens);
334
335 } else if (subCmd == "dir") {
336 auto& settings = getDriveSettings(tokens[2].getString());
337 result = dir(settings);
338
339 } else if (subCmd == "delete") {
340 auto& settings = getDriveSettings(tokens[2].getString());
341 result = deleteEntry(settings, tokens[3].getString());
342
343 } else if (subCmd == "rename") {
344 auto& settings = getDriveSettings(tokens[2].getString());
345 result = rename(settings, tokens[3].getString(), tokens[4].getString());
346
347 } else {
348 throw CommandException("Unknown subcommand: ", subCmd);
349 }
350}
351
352string DiskManipulator::help(std::span<const TclObject> tokens) const
353{
354 string helpText;
355 if (tokens.size() >= 2) {
356 if (tokens[1] == "import") {
357 helpText =
358 "diskmanipulator import <disk name> [-overwrite] <host directory|host file>\n"
359 "Import all files and subdirs from the host OS as specified into the <disk name> in the\n"
360 "current MSX subdirectory as was specified with the last chdir command.\n"
361 "By default already existing entries are not overwritten, unless the -overwrite option is used.";
362 } else if (tokens[1] == "export") {
363 helpText =
364 "diskmanipulator export <disk name> <host directory>\n"
365 "Extract all files and subdirs from the MSX subdirectory specified with the chdir command\n"
366 "from <disk name> to the host OS in <host directory>.\n";
367 } else if (tokens[1] == "savedsk") {
368 helpText =
369 "diskmanipulator savedsk <disk name> <dskfilename>\n"
370 "Save the complete drive content to <dskfilename>, it is not possible to save just one\n"
371 "partition. The main purpose of this command is to make it possible to save a 'ramdsk' into\n"
372 "a file and to take 'live backups' of dsk-files in use.\n";
373 } else if (tokens[1] == "chdir") {
374 helpText =
375 "diskmanipulator chdir <disk name> <MSX directory>\n"
376 "Change the working directory on <disk name>. This will be the directory were the 'import',\n"
377 "'export' and 'dir' commands will work on.\n"
378 "In case of a partitioned drive, each partition has its own working directory.\n";
379 } else if (tokens[1] == "mkdir") {
380 helpText =
381 "diskmanipulator mkdir <disk name> <MSX directory>\n"
382 "Create the specified directory on <disk name>. If needed, all missing parent directories\n"
383 "are created at the same time. Accepts both absolute and relative path names.\n";
384 } else if (tokens[1] == "create") {
385 helpText =
386 "diskmanipulator create <dskfilename> <size/option> [<size/option>...]\n"
387 "Create a formatted dsk file with the given size.\n"
388 "If multiple sizes are given, a partitioned disk image will be created with each partition\n"
389 "having the size as indicated. By default the sizes are expressed in kilobyte, add the\n"
390 "postfix M for megabyte.\n"
391 "When using the -dos1 option, the boot sector of the created image will be MSX-DOS1\n"
392 "compatible. When using the -nextor option, the boot sector and partition table will be\n"
393 "Nextor compatible, and FAT16 volumes can be created.\n";
394 } else if (tokens[1] == "partition") {
395 helpText =
396 "diskmanipulator partition <disk name> [<size/option>...]\n"
397 "Partitions and formats the current <disk name> to the indicated sizes. By default the\n"
398 "sizes are expressed in kilobyte, add the postfix M for megabyte.\n"
399 "When using the -dos1 option, the boot sector of the disk will be MSX-DOS1 compatible.\n"
400 "When using the -nextor option, the boot sector and partition table will be Nextor\n"
401 "compatible, and FAT16 volumes can be created.\n";
402 } else if (tokens[1] == "format") {
403 helpText =
404 "diskmanipulator format <disk name>\n"
405 "Formats the current (partition on) <disk name>. By default, it will create a regular\n"
406 "FAT12 MSX file system with an MSX-DOS2 boot sector, or, when the -dos1 option is\n"
407 "specified, with an MSX-DOS1 boot sector. When the -nextor option is specified, it\n"
408 "will create a FAT12 or FAT16 file system, with a Nextor boot sector.\n";
409 } else if (tokens[1] == "dir") {
410 helpText =
411 "diskmanipulator dir <disk name>\n"
412 "Shows the content of the current directory on <disk name>\n";
413 } else if (tokens[1] == "delete") {
414 helpText =
415 "diskmanipulator delete <disk name> <MSX entry>\n"
416 "Delete the given entry (a file or a directory) from disk, freeing up disk space.\n"
417 "The entry is searched in the directory specified with the chdir command.\n"
418 "In case of a directory (recursively) also all entries in that directory are deleted.\n";
419 } else if (tokens[1] == "rename") {
420 helpText =
421 "diskmanipulator rename <disk name> <old> <new>\n"
422 "Search the entry (file or directory) with name <old> and give it the name <new>.\n"
423 "The entry is searched in the directory specified with the chdir command.\n"
424 "The new name may not already exist.\n";
425 } else {
426 helpText = strCat("Unknown diskmanipulator subcommand: ", tokens[1].getString());
427 }
428 } else {
429 helpText =
430 "diskmanipulator create <fn> <sz> [<sz> ...] : create a formatted dsk file with name <fn>\n"
431 " having the given (partition) size(s)\n"
432 "diskmanipulator partition <dn> [<sz> ...] : partition and format <disk name>\n"
433 "diskmanipulator savedsk <disk name> <fn> : save <disk name> as dsk file named as <fn>\n"
434 "diskmanipulator format <disk name> [<sz>] : format (a partition on) <disk name>\n"
435 "diskmanipulator chdir <disk name> <MSX dir> : change directory on <disk name>\n"
436 "diskmanipulator mkdir <disk name> <MSX dir> : create directory on <disk name>\n"
437 "diskmanipulator dir <disk name> : long format file listing of current\n"
438 " directory on <disk name>\n"
439 "diskmanipulator import <disk> <dir/file> ... : import files and subdirs from <dir/file>\n"
440 "diskmanipulator export <disk> <host dir> : export all files on <disk> to <host dir>\n"
441 "diskmanipulator delete <disk> <MSX entry> : delete the specified file or directory\n"
442 "diskmanipulator rename <disk> <old> <new> : rename file or directory from <old> to <new>\n"
443 "For more info use 'help diskmanipulator <subcommand>'.\n";
444 }
445 return helpText;
446}
447
448void DiskManipulator::tabCompletion(std::vector<string>& tokens) const
449{
450 using namespace std::literals;
451 if (tokens.size() == 2) {
452 static constexpr std::array cmds = {
453 "import"sv, "export"sv, "savedsk"sv, "dir"sv, "create"sv,
454 "partition"sv, "format"sv, "chdir"sv, "mkdir"sv, "delete"sv,
455 "rename"sv
456 };
457 completeString(tokens, cmds);
458
459 } else if ((tokens.size() == 3) && (tokens[1] == "create")) {
461
462 } else if (tokens.size() == 3) {
463 std::vector<string> names;
464 if (tokens[1] == one_of("partition", "format")) {
465 names.emplace_back("-dos1");
466 names.emplace_back("-dos2");
467 names.emplace_back("-nextor");
468 }
469 for (const auto& d : drives) {
470 const auto& name1 = d.driveName; // with prefix
471 const auto& name2 = d.drive->getContainerName(); // without prefix
472 append(names, {name1, std::string(name2)});
473 // if it has partitions then we also add the partition
474 // numbers to the autocompletion
475 if (const auto* disk = d.drive->getSectorAccessibleDisk()) {
476 for (unsigned i = 1; true; ++i) {
477 try {
478 SectorBuffer buf;
479 DiskImageUtils::getPartition(*disk, i, buf);
480 } catch (MSXException&) {
481 break;
482 }
483 try {
485 append(names,
486 {strCat(name1, i), strCat(name2, i)});
487 } catch (MSXException&) {
488 // skip invalid partition
489 }
490 }
491 }
492 }
493 completeString(tokens, names);
494
495 } else if (tokens.size() >= 4) {
496 if (tokens[1] == one_of("savedsk", "import", "export")) {
497 static constexpr std::array extra = {"-overwrite"sv};
499 (tokens[1] == "import") ? extra : std::span<const std::string_view>{});
500 } else if (tokens[1] == one_of("create", "partition", "format")) {
501 static constexpr std::array cmds = {
502 "360"sv, "720"sv, "32M"sv, "-dos1"sv, "-dos2"sv, "-nextor"sv,
503 };
504 completeString(tokens, cmds);
505 }
506 }
507}
508
509void DiskManipulator::savedsk(const DriveSettings& driveData,
510 string filename) const
511{
512 auto partition = getPartition(driveData);
513 SectorBuffer buf;
514 File file(std::move(filename), File::OpenMode::CREATE);
515 for (auto i : xrange(partition.getNbSectors())) {
516 partition.readSector(i, buf);
517 file.write(buf.raw);
518 }
519}
520
521static std::pair<MSXBootSectorType, std::vector<unsigned>> parsePartitionSizes(auto tokens)
522{
524 std::vector<unsigned> sizes;
525
526 for (const auto& token_ : tokens) {
527 if (auto t = parseBootSectorType(token_.getString())) {
528 bootType = *t;
529 } else if (size_t sectors = parseSectorSize(token_.getString());
530 sectors <= std::numeric_limits<unsigned>::max()) {
531 sizes.push_back(narrow<unsigned>(sectors));
532 } else {
533 throw CommandException("Partition size too large.");
534 }
535 }
536
537 return {bootType, sizes};
538}
539
540void DiskManipulator::create(std::span<const TclObject> tokens) const
541{
542 auto [bootType, sizes] = parsePartitionSizes(view::drop(tokens, 3));
543 auto filename = FileOperations::expandTilde(string(tokens[2].getString()));
544 create(filename, bootType, sizes);
545}
546
547void DiskManipulator::create(const std::string& filename_, MSXBootSectorType bootType, const std::vector<unsigned>& sizes) const
548{
549 size_t totalSectors = sum(sizes, [](size_t s) { return s; });
550 if (totalSectors == 0) {
551 throw CommandException("No size(s) given.");
552 }
553
554 // create file with correct size
555 Filename filename(filename_);
556 try {
557 File file(filename, File::OpenMode::CREATE);
558 file.truncate(totalSectors * SectorBasedDisk::SECTOR_SIZE);
559 } catch (FileException& e) {
560 throw CommandException("Couldn't create image: ", e.getMessage());
561 }
562
563 // initialize (create partition tables and format partitions)
564 DSKDiskImage image(filename);
565 if (sizes.size() > 1) {
566 unsigned partitionCount = DiskImageUtils::partition(image,
567 static_cast<std::span<const unsigned>>(sizes), bootType);
568 if (partitionCount != sizes.size()) {
569 throw CommandException("Could not create all partitions; ",
570 partitionCount, " of ", sizes.size(), " created.");
571 }
572 } else {
573 // only one partition specified, don't create partition table
574 DiskImageUtils::format(image, bootType);
575 }
576}
577
578void DiskManipulator::partition(std::span<const TclObject> tokens)
579{
580 auto [bootType, sizes] = parsePartitionSizes(view::drop(tokens, 3));
581
582 // initialize (create partition tables and format partitions)
583 auto& settings = getDriveSettings(tokens[2].getString());
584 if (settings.partition > 0) {
585 throw CommandException("Disk name must not have partition number.");
586 }
587 auto* image = settings.drive->getSectorAccessibleDisk();
588 unsigned partitionCount = DiskImageUtils::partition(*image,
589 static_cast<std::span<const unsigned>>(sizes), bootType);
590 if (partitionCount != sizes.size()) {
591 throw CommandException("Could not create all partitions; ",
592 partitionCount, " of ", sizes.size(), " created.");
593 }
594}
595
596void DiskManipulator::format(std::span<const TclObject> tokens)
597{
599 std::optional<string> drive;
600 std::optional<size_t> size;
601 for (const auto& token_ : view::drop(tokens, 2)) {
602 if (auto t = parseBootSectorType(token_.getString())) {
603 bootType = *t;
604 } else if (!drive) {
605 drive = token_.getString();
606 } else if (!size) {
607 size = parseSectorSize(token_.getString());
608 } else {
609 throw CommandException("Incorrect number of parameters");
610 }
611 }
612 if (drive) {
613 auto& driveData = getDriveSettings(*drive);
614 auto partition = getPartition(driveData);
615 if (size) {
616 DiskImageUtils::format(partition, bootType, *size);
617 } else {
618 DiskImageUtils::format(partition, bootType);
619 }
620 driveData.setWorkingDir(driveData.partition, "/");
621 } else {
622 throw CommandException("Incorrect number of parameters");
623 }
624}
625
626MSXtar DiskManipulator::getMSXtar(
627 SectorAccessibleDisk& disk, DriveSettings& driveData) const
628{
630 throw CommandException("Please select partition number.");
631 }
632
633 MSXtar result(disk, reactor.getMsxChar2Unicode());
634 string cwd = driveData.getWorkingDir(driveData.partition);
635 try {
636 result.chdir(cwd);
637 } catch (MSXException&) {
638 driveData.setWorkingDir(driveData.partition, "/");
639 throw CommandException(
640 "Directory ", cwd,
641 " doesn't exist anymore. Went back to root "
642 "directory. Command aborted, please retry.");
643 }
644 return result;
645}
646
647string DiskManipulator::dir(DriveSettings& driveData) const
648{
649 auto partition = getPartition(driveData);
650 auto workhorse = getMSXtar(partition, driveData);
651 return workhorse.dir();
652}
653
654std::string DiskManipulator::deleteEntry(DriveSettings& driveData, std::string_view entry) const
655{
656 auto partition = getPartition(driveData);
657 auto workhorse = getMSXtar(partition, driveData);
658 return workhorse.deleteItem(entry);
659}
660
661std::string DiskManipulator::rename(DriveSettings& driveData, std::string_view oldName, std::string_view newName) const
662{
663 auto partition = getPartition(driveData);
664 auto workhorse = getMSXtar(partition, driveData);
665 return workhorse.renameItem(oldName, newName);
666}
667
668string DiskManipulator::chdir(DriveSettings& driveData, string_view filename) const
669{
670 auto partition = getPartition(driveData);
671 auto workhorse = getMSXtar(partition, driveData);
672 try {
673 workhorse.chdir(filename);
674 } catch (MSXException& e) {
675 throw CommandException("chdir failed: ", e.getMessage());
676 }
677 // TODO clean-up this temp hack, used to enable relative paths
678 string cwd = driveData.getWorkingDir(driveData.partition);
679 if (filename.starts_with('/')) {
680 cwd = filename;
681 } else {
682 if (!cwd.ends_with('/')) cwd += '/';
683 cwd.append(filename.data(), filename.size());
684 }
685 driveData.setWorkingDir(driveData.partition, cwd);
686 return "New working directory: " + cwd;
687}
688
689void DiskManipulator::mkdir(DriveSettings& driveData, string_view filename) const
690{
691 auto partition = getPartition(driveData);
692 auto workhorse = getMSXtar(partition, driveData);
693 try {
694 workhorse.mkdir(filename);
695 } catch (MSXException& e) {
696 throw CommandException(std::move(e).getMessage());
697 }
698}
699
700string DiskManipulator::imprt(DriveSettings& driveData,
701 std::span<const TclObject> lists, bool overwrite) const
702{
703 auto partition = getPartition(driveData);
704 auto workhorse = getMSXtar(partition, driveData);
705
706 string messages;
707 auto& interp = getInterpreter();
708 auto add = overwrite ? MSXtar::Add::OVERWRITE
710 for (const auto& l : lists) {
711 for (auto i : xrange(l.getListLength(interp))) {
712 auto s = FileOperations::expandTilde(string(l.getListIndex(interp, i).getString()));
713 try {
714 auto st = FileOperations::getStat(s);
715 if (!st) {
716 throw CommandException("Non-existing file ", s);
717 }
719 messages += workhorse.addDir(s, add);
720 } else if (FileOperations::isRegularFile(*st)) {
721 messages += workhorse.addFile(s, add);
722 } else {
723 // ignore other stuff (sockets, device nodes, ..)
724 strAppend(messages, "Ignoring ", s, '\n');
725 }
726 } catch (MSXException& e) {
727 throw CommandException(std::move(e).getMessage());
728 }
729 }
730 }
731 return messages;
732}
733
734void DiskManipulator::exprt(DriveSettings& driveData, string_view dirname,
735 std::span<const TclObject> lists) const
736{
737 auto partition = getPartition(driveData);
738 auto workhorse = getMSXtar(partition, driveData);
739 try {
740 if (lists.empty()) {
741 // export all
742 workhorse.getDir(dirname);
743 } else {
744 for (const auto& l : lists) {
745 workhorse.getItemFromDir(dirname, l.getString());
746 }
747 }
748 } catch (MSXException& e) {
749 throw CommandException(std::move(e).getMessage());
750 }
751}
752
753} // 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:152
static void completeString(std::vector< std::string > &tokens, ITER begin, ITER end, bool caseSensitive=true)
Definition Completer.hh:138
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:75
std::string_view getMachineID() const
Definition Reactor.cc:415
const MsxChar2Unicode & getMsxChar2Unicode() const
Definition Reactor.cc:374
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:302
constexpr mat4 scale(const vec3 &xyz)
bool hasPartitionTable(const SectorAccessibleDisk &disk)
Check whether the given disk is partitioned.
void checkSupportedPartition(const SectorAccessibleDisk &disk, unsigned partition)
Check whether partition is of type FAT12 or FAT16.
void format(SectorAccessibleDisk &disk, MSXBootSectorType bootType)
Format the given disk (= a single partition).
Partition & getPartition(const SectorAccessibleDisk &disk, unsigned partition, SectorBuffer &buf)
Gets the requested 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:162
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
constexpr auto sum(InputRange &&range, Proj proj={})
Definition stl.hh:248
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition stl.hh:137
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)