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 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 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());
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);
301 }
else if (subCmd ==
"import") {
302 auto& settings = getDriveSettings(tokens[2].getString());
304 bool overwrite =
false;
305 std::array info = {
flagArg(
"-overwrite", overwrite)};
307 if (lists.empty())
throw SyntaxError();
309 result = imprt(settings, lists, overwrite);
311 }
else if (subCmd ==
"savedsk") {
312 auto& settings = getDriveSettings(tokens[2].getString());
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));
321 result = chdir(settings, tokens[3].getString());
324 }
else if (subCmd ==
"mkdir") {
325 auto& settings = getDriveSettings(tokens[2].getString());
326 mkdir(settings, tokens[3].getString());
328 }
else if (subCmd ==
"create") {
331 }
else if (subCmd ==
"partition") {
334 }
else if (subCmd ==
"format") {
337 }
else if (subCmd ==
"dir") {
338 auto& settings = getDriveSettings(tokens[2].getString());
339 result = dir(settings);
341 }
else if (subCmd ==
"delete") {
342 auto& settings = getDriveSettings(tokens[2].getString());
343 result = deleteEntry(settings, tokens[3].getString());
345 }
else if (subCmd ==
"rename") {
346 auto& settings = getDriveSettings(tokens[2].getString());
347 result = rename(settings, tokens[3].getString(), tokens[4].getString());
350 throw CommandException(
"Unknown subcommand: ", subCmd);
354string DiskManipulator::help(std::span<const TclObject> tokens)
const
357 if (tokens.size() >= 2) {
358 if (tokens[1] ==
"import") {
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") {
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") {
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") {
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") {
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") {
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") {
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") {
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") {
413 "diskmanipulator dir <disk name>\n"
414 "Shows the content of the current directory on <disk name>\n";
415 }
else if (tokens[1] ==
"delete") {
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") {
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";
428 helpText =
strCat(
"Unknown diskmanipulator subcommand: ", tokens[1].getString());
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";
450void DiskManipulator::tabCompletion(std::vector<string>& tokens)
const
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,
461 }
else if ((tokens.size() == 3) && (tokens[1] ==
"create")) {
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");
471 for (
const auto& d : drives) {
472 const auto& name1 = d.driveName;
473 const auto& name2 = d.drive->getContainerName();
474 append(names, {name1, std::string(name2)});
477 if (
auto* disk = d.drive->getSectorAccessibleDisk()) {
478 for (
unsigned i = 1;
true; ++i) {
482 }
catch (MSXException&) {
489 }
catch (MSXException&) {
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,
511void DiskManipulator::savedsk(
const DriveSettings& driveData,
512 string filename)
const
514 auto partition = getPartition(driveData);
517 for (
auto i :
xrange(partition.getNbSectors())) {
518 partition.readSector(i, buf);
523static std::pair<MSXBootSectorType, std::vector<unsigned>> parsePartitionSizes(
auto tokens)
526 std::vector<unsigned> sizes;
528 for (
const auto& token_ : tokens) {
529 if (
auto t = parseBootSectorType(token_.getString())) {
531 }
else if (
size_t sectors = parseSectorSize(token_.getString());
532 sectors <= std::numeric_limits<unsigned>::max()) {
533 sizes.push_back(narrow<unsigned>(sectors));
535 throw CommandException(
"Partition size too large.");
539 return {bootType, sizes};
544 auto [bootType, sizes] = parsePartitionSizes(
view::drop(tokens, 3));
546 create(filename, bootType, sizes);
551 size_t totalSectors =
sum(sizes, [](
size_t s) {
return s; });
552 if (totalSectors == 0) {
567 if (sizes.size() > 1) {
569 static_cast<std::span<const unsigned>
>(sizes), bootType);
570 if (partitionCount != sizes.size()) {
572 partitionCount,
" of ", sizes.size(),
" created.");
580void DiskManipulator::partition(std::span<const TclObject> tokens)
582 auto [bootType, sizes] = parsePartitionSizes(
view::drop(tokens, 3));
585 auto& settings = getDriveSettings(tokens[2].getString());
586 if (settings.partition > 0) {
589 auto*
image = settings.drive->getSectorAccessibleDisk();
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.");
598void DiskManipulator::format(std::span<const TclObject> tokens)
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())) {
607 drive = token_.getString();
609 size = parseSectorSize(token_.getString());
611 throw CommandException(
"Incorrect number of parameters");
615 auto& driveData = getDriveSettings(*drive);
616 auto partition = getPartition(driveData);
622 driveData.setWorkingDir(driveData.partition,
"/");
624 throw CommandException(
"Incorrect number of parameters");
628MSXtar DiskManipulator::getMSXtar(
629 SectorAccessibleDisk& disk, DriveSettings& driveData)
const
632 throw CommandException(
"Please select partition number.");
636 string cwd = driveData.getWorkingDir(driveData.partition);
639 }
catch (MSXException&) {
640 driveData.setWorkingDir(driveData.partition,
"/");
641 throw CommandException(
643 " doesn't exist anymore. Went back to root "
644 "directory. Command aborted, please retry.");
649string DiskManipulator::dir(DriveSettings& driveData)
const
651 auto partition = getPartition(driveData);
652 auto workhorse = getMSXtar(partition, driveData);
653 return workhorse.dir();
656std::string DiskManipulator::deleteEntry(DriveSettings& driveData, std::string_view entry)
const
658 auto partition = getPartition(driveData);
659 auto workhorse = getMSXtar(partition, driveData);
660 return workhorse.deleteItem(entry);
663std::string DiskManipulator::rename(DriveSettings& driveData, std::string_view oldName, std::string_view newName)
const
665 auto partition = getPartition(driveData);
666 auto workhorse = getMSXtar(partition, driveData);
667 return workhorse.renameItem(oldName, newName);
670string DiskManipulator::chdir(DriveSettings& driveData, string_view filename)
const
672 auto partition = getPartition(driveData);
673 auto workhorse = getMSXtar(partition, driveData);
675 workhorse.chdir(filename);
676 }
catch (MSXException& e) {
677 throw CommandException(
"chdir failed: ",
e.getMessage());
680 string cwd = driveData.getWorkingDir(driveData.partition);
681 if (filename.starts_with(
'/')) {
684 if (!cwd.ends_with(
'/')) cwd +=
'/';
685 cwd.append(filename.data(), filename.size());
687 driveData.setWorkingDir(driveData.partition, cwd);
688 return "New working directory: " + cwd;
691void DiskManipulator::mkdir(DriveSettings& driveData, string_view filename)
const
693 auto partition = getPartition(driveData);
694 auto workhorse = getMSXtar(partition, driveData);
696 workhorse.mkdir(filename);
697 }
catch (MSXException& e) {
698 throw CommandException(std::move(e).getMessage());
702string DiskManipulator::imprt(DriveSettings& driveData,
703 std::span<const TclObject> lists,
bool overwrite)
const
705 auto partition = getPartition(driveData);
706 auto workhorse = getMSXtar(partition, driveData);
712 for (
const auto& l : lists) {
713 for (
auto i :
xrange(l.getListLength(interp))) {
718 throw CommandException(
"Non-existing file ", s);
721 messages += workhorse.addDir(s, add);
723 messages += workhorse.addFile(s, add);
726 strAppend(messages,
"Ignoring ", s,
'\n');
728 }
catch (MSXException& e) {
729 throw CommandException(std::move(e).getMessage());
736void DiskManipulator::exprt(DriveSettings& driveData, string_view dirname,
737 std::span<const TclObject> lists)
const
739 auto partition = getPartition(driveData);
740 auto workhorse = getMSXtar(partition, driveData);
744 workhorse.getDir(dirname);
746 for (
const auto& l : lists) {
747 workhorse.getItemFromDir(dirname, l.getString());
750 }
catch (MSXException& e) {
751 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)
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:
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)
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)