37using std::string_view;
43 :
Command(commandController_,
"diskmanipulator")
50 assert(drives.empty());
53string DiskManipulator::getMachinePrefix()
const
56 return id.empty() ?
string{} :
strCat(
id,
"::");
62 assert(findDriveSettings(drive) ==
end(drives));
63 DriveSettings driveSettings;
64 driveSettings.drive = &drive;
66 driveSettings.partition = 0;
67 drives.push_back(driveSettings);
72 auto it = findDriveSettings(drive);
73 assert(it !=
end(drives));
79 std::vector<std::string> result;
80 result.emplace_back(
"virtual_drive");
82 auto prefix = getMachinePrefix();
83 if (prefix.empty())
return result;
85 for (
const auto& drive : drives) {
86 if (!drive.driveName.starts_with(prefix))
continue;
88 const auto* disk = drive.drive->getSectorAccessibleDisk();
90 for (
unsigned i = 1;
true; ++i) {
100 drive.driveName.substr(prefix.size()), i));
107 result.emplace_back(drive.driveName.substr(prefix.size()));
116 auto pos = fullName.find_first_of(
"0123456789");
117 auto driveName = (pos != std::string_view::npos)
118 ? fullName.substr(0, pos)
121 auto it =
ranges::find(drives, driveName, &DriveSettings::driveName);
122 if (it ==
end(drives)) {
124 if (it ==
end(drives)) {
128 auto* drive = it->drive;
136 if (!drive)
return {};
137 auto* disk = drive->getSectorAccessibleDisk();
138 if (!disk)
return {};
141 auto pos = fullName.find_first_of(
"0123456789");
142 unsigned partitionNum = 0;
143 if (pos != std::string_view::npos) {
144 auto num = StringOp::stringToBase<10, unsigned>(fullName.substr(pos));
151 std::make_unique<DiskPartition>(*disk, partitionNum)};
157std::string DiskManipulator::DriveSettings::getWorkingDir(
unsigned p)
159 return p < workingDir.size() ? workingDir[p] :
"/";
162void DiskManipulator::DriveSettings::setWorkingDir(
unsigned p, std::string_view dir)
164 if (p >= workingDir.size()) {
165 workingDir.resize(p + 1,
"/");
170DiskManipulator::Drives::iterator DiskManipulator::findDriveSettings(
171 DiskContainer& drive)
173 return ranges::find(drives, &drive, &DriveSettings::drive);
176DiskManipulator::Drives::iterator DiskManipulator::findDriveSettings(
177 string_view driveName)
179 return ranges::find(drives, driveName, &DriveSettings::driveName);
182DiskManipulator::DriveSettings& DiskManipulator::getDriveSettings(
183 string_view diskName)
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);
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);
201 const auto* disk = it->drive->getSectorAccessibleDisk();
204 throw CommandException(
"Unsupported disk type.");
207 if (pos2 == string_view::npos) {
211 auto partitionName = diskName.substr(pos2);
212 auto partition = StringOp::stringToBase<10, unsigned>(partitionName);
214 throw CommandException(
"Invalid partition name: ", partitionName);
217 it->partition = *partition;
222DiskPartition DiskManipulator::getPartition(
223 const DriveSettings& driveData)
225 auto* disk = driveData.drive->getSectorAccessibleDisk();
227 return {*disk, driveData.partition};
230static std::optional<MSXBootSectorType> parseBootSectorType(std::string_view s) {
239 size_t bytes = strtoull(tok.
c_str(), &q, 0);
242 if ((q == tok.
c_str()) || *(q + 1)) {
243 throw CommandException(
"Invalid size: ", tok);
245 switch (tolower(*q)) {
259 throw CommandException(
"Invalid suffix: ", q);
269 return std::max(sectors,
size_t(720));
272void DiskManipulator::execute(std::span<const TclObject> tokens, TclObject& result)
274 if (tokens.size() == 1) {
275 throw CommandException(
"Missing argument");
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");
289 if (subCmd ==
"export") {
290 string_view dir = tokens[3].getString();
293 throw CommandException(dir,
" is not a directory");
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);
299 }
else if (subCmd ==
"import") {
300 auto& settings = getDriveSettings(tokens[2].getString());
302 bool overwrite =
false;
303 std::array info = {
flagArg(
"-overwrite", overwrite)};
305 if (lists.empty())
throw SyntaxError();
307 result = imprt(settings, lists, overwrite);
309 }
else if (subCmd ==
"savedsk") {
310 const auto& settings = getDriveSettings(tokens[2].getString());
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));
319 result = chdir(settings, tokens[3].getString());
322 }
else if (subCmd ==
"mkdir") {
323 auto& settings = getDriveSettings(tokens[2].getString());
324 mkdir(settings, tokens[3].getString());
326 }
else if (subCmd ==
"create") {
329 }
else if (subCmd ==
"partition") {
332 }
else if (subCmd ==
"format") {
335 }
else if (subCmd ==
"dir") {
336 auto& settings = getDriveSettings(tokens[2].getString());
337 result = dir(settings);
339 }
else if (subCmd ==
"delete") {
340 auto& settings = getDriveSettings(tokens[2].getString());
341 result = deleteEntry(settings, tokens[3].getString());
343 }
else if (subCmd ==
"rename") {
344 auto& settings = getDriveSettings(tokens[2].getString());
345 result = rename(settings, tokens[3].getString(), tokens[4].getString());
348 throw CommandException(
"Unknown subcommand: ", subCmd);
352string DiskManipulator::help(std::span<const TclObject> tokens)
const
355 if (tokens.size() >= 2) {
356 if (tokens[1] ==
"import") {
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") {
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") {
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") {
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") {
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") {
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") {
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") {
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") {
411 "diskmanipulator dir <disk name>\n"
412 "Shows the content of the current directory on <disk name>\n";
413 }
else if (tokens[1] ==
"delete") {
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") {
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";
426 helpText =
strCat(
"Unknown diskmanipulator subcommand: ", tokens[1].getString());
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";
448void DiskManipulator::tabCompletion(std::vector<string>& tokens)
const
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,
459 }
else if ((tokens.size() == 3) && (tokens[1] ==
"create")) {
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");
469 for (
const auto& d : drives) {
470 const auto& name1 = d.driveName;
471 const auto& name2 = d.drive->getContainerName();
472 append(names, {name1, std::string(name2)});
475 if (
const auto* disk = d.drive->getSectorAccessibleDisk()) {
476 for (
unsigned i = 1;
true; ++i) {
480 }
catch (MSXException&) {
487 }
catch (MSXException&) {
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,
509void DiskManipulator::savedsk(
const DriveSettings& driveData,
510 string filename)
const
512 auto partition = getPartition(driveData);
515 for (
auto i :
xrange(partition.getNbSectors())) {
516 partition.readSector(i, buf);
521static std::pair<MSXBootSectorType, std::vector<unsigned>> parsePartitionSizes(
auto tokens)
524 std::vector<unsigned> sizes;
526 for (
const auto& token_ : tokens) {
527 if (
auto t = parseBootSectorType(token_.getString())) {
529 }
else if (
size_t sectors = parseSectorSize(token_.getString());
530 sectors <= std::numeric_limits<unsigned>::max()) {
531 sizes.push_back(narrow<unsigned>(sectors));
533 throw CommandException(
"Partition size too large.");
537 return {bootType, sizes};
542 auto [bootType, sizes] = parsePartitionSizes(
view::drop(tokens, 3));
544 create(filename, bootType, sizes);
549 size_t totalSectors =
sum(sizes, [](
size_t s) {
return s; });
550 if (totalSectors == 0) {
565 if (sizes.size() > 1) {
567 static_cast<std::span<const unsigned>
>(sizes), bootType);
568 if (partitionCount != sizes.size()) {
570 partitionCount,
" of ", sizes.size(),
" created.");
578void DiskManipulator::partition(std::span<const TclObject> tokens)
580 auto [bootType, sizes] = parsePartitionSizes(
view::drop(tokens, 3));
583 auto& settings = getDriveSettings(tokens[2].getString());
584 if (settings.partition > 0) {
587 auto*
image = settings.drive->getSectorAccessibleDisk();
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.");
596void DiskManipulator::format(std::span<const TclObject> tokens)
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())) {
605 drive = token_.getString();
607 size = parseSectorSize(token_.getString());
609 throw CommandException(
"Incorrect number of parameters");
613 auto& driveData = getDriveSettings(*drive);
614 auto partition = getPartition(driveData);
620 driveData.setWorkingDir(driveData.partition,
"/");
622 throw CommandException(
"Incorrect number of parameters");
626MSXtar DiskManipulator::getMSXtar(
627 SectorAccessibleDisk& disk, DriveSettings& driveData)
const
630 throw CommandException(
"Please select partition number.");
634 string cwd = driveData.getWorkingDir(driveData.partition);
637 }
catch (MSXException&) {
638 driveData.setWorkingDir(driveData.partition,
"/");
639 throw CommandException(
641 " doesn't exist anymore. Went back to root "
642 "directory. Command aborted, please retry.");
647string DiskManipulator::dir(DriveSettings& driveData)
const
649 auto partition = getPartition(driveData);
650 auto workhorse = getMSXtar(partition, driveData);
651 return workhorse.dir();
654std::string DiskManipulator::deleteEntry(DriveSettings& driveData, std::string_view entry)
const
656 auto partition = getPartition(driveData);
657 auto workhorse = getMSXtar(partition, driveData);
658 return workhorse.deleteItem(entry);
661std::string DiskManipulator::rename(DriveSettings& driveData, std::string_view oldName, std::string_view newName)
const
663 auto partition = getPartition(driveData);
664 auto workhorse = getMSXtar(partition, driveData);
665 return workhorse.renameItem(oldName, newName);
668string DiskManipulator::chdir(DriveSettings& driveData, string_view filename)
const
670 auto partition = getPartition(driveData);
671 auto workhorse = getMSXtar(partition, driveData);
673 workhorse.chdir(filename);
674 }
catch (MSXException& e) {
675 throw CommandException(
"chdir failed: ",
e.getMessage());
678 string cwd = driveData.getWorkingDir(driveData.partition);
679 if (filename.starts_with(
'/')) {
682 if (!cwd.ends_with(
'/')) cwd +=
'/';
683 cwd.append(filename.data(), filename.size());
685 driveData.setWorkingDir(driveData.partition, cwd);
686 return "New working directory: " + cwd;
689void DiskManipulator::mkdir(DriveSettings& driveData, string_view filename)
const
691 auto partition = getPartition(driveData);
692 auto workhorse = getMSXtar(partition, driveData);
694 workhorse.mkdir(filename);
695 }
catch (MSXException& e) {
696 throw CommandException(std::move(e).getMessage());
700string DiskManipulator::imprt(DriveSettings& driveData,
701 std::span<const TclObject> lists,
bool overwrite)
const
703 auto partition = getPartition(driveData);
704 auto workhorse = getMSXtar(partition, driveData);
710 for (
const auto& l : lists) {
711 for (
auto i :
xrange(l.getListLength(interp))) {
716 throw CommandException(
"Non-existing file ", s);
719 messages += workhorse.addDir(s, add);
721 messages += workhorse.addFile(s, add);
724 strAppend(messages,
"Ignoring ", s,
'\n');
726 }
catch (MSXException& e) {
727 throw CommandException(std::move(e).getMessage());
734void DiskManipulator::exprt(DriveSettings& driveData, string_view dirname,
735 std::span<const TclObject> lists)
const
737 auto partition = getPartition(driveData);
738 auto workhorse = getMSXtar(partition, driveData);
742 workhorse.getDir(dirname);
744 for (
const auto& l : lists) {
745 workhorse.getItemFromDir(dirname, l.getString());
748 }
catch (MSXException& e) {
749 throw CommandException(std::move(e).getMessage());
Interpreter & getInterpreter() const final
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
static void completeString(std::vector< std::string > &tokens, ITER begin, ITER end, bool caseSensitive=true)
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.
This class represents a filename.
Contains the main loop of openMSX.
std::string_view getMachineID() const
const MsxChar2Unicode & getMsxChar2Unicode() const
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 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:
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)
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)
constexpr auto drop(Range &&range, size_t n)
constexpr auto sum(InputRange &&range, Proj proj={})
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
TemporaryString tmpStrCat(Ts &&... ts)
void strAppend(std::string &result, Ts &&...ts)
constexpr auto xrange(T e)
constexpr auto end(const zstring_view &x)