40using std::string_view;
46 :
Command(commandController_,
"diskmanipulator")
53 assert(drives.empty());
56string DiskManipulator::getMachinePrefix()
const
59 return id.empty() ?
string{} :
strCat(
id,
"::");
65 assert(findDriveSettings(drive) ==
end(drives));
66 DriveSettings driveSettings;
67 driveSettings.drive = &drive;
69 driveSettings.partition = 0;
70 drives.push_back(driveSettings);
75 auto it = findDriveSettings(drive);
76 assert(it !=
end(drives));
82 std::vector<std::string> result;
83 result.push_back(
"virtual_drive");
85 auto prefix = getMachinePrefix();
86 if (prefix.empty())
return result;
88 for (
const auto& drive : drives) {
89 if (!drive.driveName.starts_with(prefix))
continue;
91 auto* disk = drive.drive->getSectorAccessibleDisk();
93 for (
unsigned i = 1;
true; ++i) {
103 drive.driveName.substr(prefix.size()), i));
110 result.emplace_back(drive.driveName.substr(prefix.size()));
119 auto pos = fullName.find_first_of(
"0123456789");
120 auto driveName = (pos != std::string_view::npos)
121 ? fullName.substr(0, pos)
124 auto it =
ranges::find(drives, driveName, &DriveSettings::driveName);
125 if (it ==
end(drives)) {
127 if (it ==
end(drives)) {
131 auto* drive = it->drive;
139 if (!drive)
return {};
140 auto* disk = drive->getSectorAccessibleDisk();
141 if (!disk)
return {};
144 auto pos = fullName.find_first_of(
"0123456789");
145 unsigned partitionNum = 0;
146 if (pos != std::string_view::npos) {
147 auto num = StringOp::stringToBase<10, unsigned>(fullName.substr(pos));
154 std::make_unique<DiskPartition>(*disk, partitionNum)};
160std::string DiskManipulator::DriveSettings::getWorkingDir(
unsigned p)
162 return p < workingDir.size() ? workingDir[p] :
"/";
165void DiskManipulator::DriveSettings::setWorkingDir(
unsigned p, std::string_view dir)
167 if (p >= workingDir.size()) {
168 workingDir.resize(p + 1,
"/");
173DiskManipulator::Drives::iterator DiskManipulator::findDriveSettings(
174 DiskContainer& drive)
176 return ranges::find(drives, &drive, &DriveSettings::drive);
179DiskManipulator::Drives::iterator DiskManipulator::findDriveSettings(
180 string_view driveName)
182 return ranges::find(drives, driveName, &DriveSettings::driveName);
185DiskManipulator::DriveSettings& DiskManipulator::getDriveSettings(
186 string_view diskName)
190 auto pos1 = diskName.find(
"::");
191 auto tmp1 = (pos1 == string_view::npos) ? diskName : diskName.
substr(pos1);
192 auto pos2 = tmp1.find_first_of(
"0123456789");
193 auto pos1b = (pos1 == string_view::npos) ? 0 : pos1;
194 auto tmp2 = diskName.substr(0, pos2 + pos1b);
196 auto it = findDriveSettings(tmp2);
197 if (it ==
end(drives)) {
198 it = findDriveSettings(
tmpStrCat(getMachinePrefix(), tmp2));
199 if (it ==
end(drives)) {
200 throw CommandException(
"Unknown drive: ", tmp2);
204 auto* disk = it->drive->getSectorAccessibleDisk();
207 throw CommandException(
"Unsupported disk type.");
210 if (pos2 == string_view::npos) {
214 auto partitionName = diskName.substr(pos2);
215 auto partition = StringOp::stringToBase<10, unsigned>(partitionName);
217 throw CommandException(
"Invalid partition name: ", partitionName);
220 it->partition = *partition;
225DiskPartition DiskManipulator::getPartition(
226 const DriveSettings& driveData)
228 auto* disk = driveData.drive->getSectorAccessibleDisk();
230 return {*disk, driveData.partition};
233static std::optional<MSXBootSectorType> parseBootSectorType(std::string_view s) {
242 size_t bytes = strtoull(tok.
c_str(), &q, 0);
245 if ((q == tok.
c_str()) || *(q + 1)) {
246 throw CommandException(
"Invalid size: ", tok);
248 switch (tolower(*q)) {
262 throw CommandException(
"Invalid suffix: ", q);
272 return std::max(sectors,
size_t(720));
275void DiskManipulator::execute(std::span<const TclObject> tokens, TclObject& result)
277 if (tokens.size() == 1) {
278 throw CommandException(
"Missing argument");
281 string_view subCmd = tokens[1].getString();
282 if (((tokens.size() != 4) && (subCmd ==
one_of(
"savedsk",
"mkdir",
"delete"))) ||
283 ((tokens.size() != 5) && (subCmd ==
"rename" )) ||
284 ((tokens.size() != 3) && (subCmd ==
"dir" )) ||
285 ((tokens.size() < 3 || tokens.size() > 4) && (subCmd ==
"chdir" )) ||
286 ((tokens.size() < 3 || tokens.size() > 5) && (subCmd ==
"format" )) ||
287 ((tokens.size() < 3) && (subCmd ==
"partition" )) ||
288 ((tokens.size() < 4) && (subCmd ==
one_of(
"export",
"import",
"create")))) {
289 throw CommandException(
"Incorrect number of parameters");
292 if (subCmd ==
"export") {
293 string_view dir = tokens[3].getString();
296 throw CommandException(dir,
" is not a directory");
298 auto& settings = getDriveSettings(tokens[2].getString());
301 std::span<const TclObject> lists(&*(std::begin(tokens) + 4), std::end(tokens) - (std::begin(tokens) + 4));
302 exprt(settings, directory, lists);
304 }
else if (subCmd ==
"import") {
305 auto& settings = getDriveSettings(tokens[2].getString());
307 bool overwrite =
false;
308 std::array info = {
flagArg(
"-overwrite", overwrite)};
310 if (lists.empty())
throw SyntaxError();
312 result =
import(settings, lists, overwrite);
314 }
else if (subCmd ==
"savedsk") {
315 auto& settings = getDriveSettings(tokens[2].getString());
318 }
else if (subCmd ==
"chdir") {
319 auto& settings = getDriveSettings(tokens[2].getString());
320 if (tokens.size() == 3) {
321 result =
tmpStrCat(
"Current directory: ",
322 settings.getWorkingDir(settings.partition));
324 result = chdir(settings, tokens[3].getString());
327 }
else if (subCmd ==
"mkdir") {
328 auto& settings = getDriveSettings(tokens[2].getString());
329 mkdir(settings, tokens[3].getString());
331 }
else if (subCmd ==
"create") {
334 }
else if (subCmd ==
"partition") {
337 }
else if (subCmd ==
"format") {
340 }
else if (subCmd ==
"dir") {
341 auto& settings = getDriveSettings(tokens[2].getString());
342 result = dir(settings);
344 }
else if (subCmd ==
"delete") {
345 auto& settings = getDriveSettings(tokens[2].getString());
346 result = deleteEntry(settings, tokens[3].getString());
348 }
else if (subCmd ==
"rename") {
349 auto& settings = getDriveSettings(tokens[2].getString());
350 result = rename(settings, tokens[3].getString(), tokens[4].getString());
353 throw CommandException(
"Unknown subcommand: ", subCmd);
357string DiskManipulator::help(std::span<const TclObject> tokens)
const
360 if (tokens.size() >= 2) {
361 if (tokens[1] ==
"import") {
363 "diskmanipulator import <disk name> [-overwrite] <host directory|host file>\n"
364 "Import all files and subdirs from the host OS as specified into the <disk name> in the\n"
365 "current MSX subdirectory as was specified with the last chdir command.\n"
366 "By default already existing entries are not overwritten, unless the -overwrite option is used.";
367 }
else if (tokens[1] ==
"export") {
369 "diskmanipulator export <disk name> <host directory>\n"
370 "Extract all files and subdirs from the MSX subdirectory specified with the chdir command\n"
371 "from <disk name> to the host OS in <host directory>.\n";
372 }
else if (tokens[1] ==
"savedsk") {
374 "diskmanipulator savedsk <disk name> <dskfilename>\n"
375 "Save the complete drive content to <dskfilename>, it is not possible to save just one\n"
376 "partition. The main purpose of this command is to make it possible to save a 'ramdsk' into\n"
377 "a file and to take 'live backups' of dsk-files in use.\n";
378 }
else if (tokens[1] ==
"chdir") {
380 "diskmanipulator chdir <disk name> <MSX directory>\n"
381 "Change the working directory on <disk name>. This will be the directory were the 'import',\n"
382 "'export' and 'dir' commands will work on.\n"
383 "In case of a partitioned drive, each partition has its own working directory.\n";
384 }
else if (tokens[1] ==
"mkdir") {
386 "diskmanipulator mkdir <disk name> <MSX directory>\n"
387 "Create the specified directory on <disk name>. If needed, all missing parent directories\n"
388 "are created at the same time. Accepts both absolute and relative path names.\n";
389 }
else if (tokens[1] ==
"create") {
391 "diskmanipulator create <dskfilename> <size/option> [<size/option>...]\n"
392 "Create a formatted dsk file with the given size.\n"
393 "If multiple sizes are given, a partitioned disk image will be created with each partition\n"
394 "having the size as indicated. By default the sizes are expressed in kilobyte, add the\n"
395 "postfix M for megabyte.\n"
396 "When using the -dos1 option, the boot sector of the created image will be MSX-DOS1\n"
397 "compatible. When using the -nextor option, the boot sector and partition table will be\n"
398 "Nextor compatible, and FAT16 volumes can be created.\n";
399 }
else if (tokens[1] ==
"partition") {
401 "diskmanipulator partition <disk name> [<size/option>...]\n"
402 "Partitions and formats the current <disk name> to the indicated sizes. By default the\n"
403 "sizes are expressed in kilobyte, add the postfix M for megabyte.\n"
404 "When using the -dos1 option, the boot sector of the disk will be MSX-DOS1 compatible.\n"
405 "When using the -nextor option, the boot sector and partition table will be Nextor\n"
406 "compatible, and FAT16 volumes can be created.\n";
407 }
else if (tokens[1] ==
"format") {
409 "diskmanipulator format <disk name>\n"
410 "Formats the current (partition on) <disk name>. By default, it will create a regular\n"
411 "FAT12 MSX file system with an MSX-DOS2 boot sector, or, when the -dos1 option is\n"
412 "specified, with an MSX-DOS1 boot sector. When the -nextor option is specified, it\n"
413 "will create a FAT12 or FAT16 file system, with a Nextor boot sector.\n";
414 }
else if (tokens[1] ==
"dir") {
416 "diskmanipulator dir <disk name>\n"
417 "Shows the content of the current directory on <disk name>\n";
418 }
else if (tokens[1] ==
"delete") {
420 "diskmanipulator delete <disk name> <MSX entry>\n"
421 "Delete the given entry (a file or a directory) from disk, freeing up disk space.\n"
422 "The entry is searched in the directory specified with the chdir command.\n"
423 "In case of a directory (recursively) also all entries in that directory are deleted.\n";
424 }
else if (tokens[1] ==
"rename") {
426 "diskmanipulator rename <disk name> <old> <new>\n"
427 "Search the entry (file or directory) with name <old> and give it the name <new>.\n"
428 "The entry is searched in the directory specified with the chdir command.\n"
429 "The new name may not already exist.\n";
431 helpText =
strCat(
"Unknown diskmanipulator subcommand: ", tokens[1].getString());
435 "diskmanipulator create <fn> <sz> [<sz> ...] : create a formatted dsk file with name <fn>\n"
436 " having the given (partition) size(s)\n"
437 "diskmanipulator partition <dn> [<sz> ...] : partition and format <disk name>\n"
438 "diskmanipulator savedsk <disk name> <fn> : save <disk name> as dsk file named as <fn>\n"
439 "diskmanipulator format <disk name> [<sz>] : format (a partition on) <disk name>\n"
440 "diskmanipulator chdir <disk name> <MSX dir> : change directory on <disk name>\n"
441 "diskmanipulator mkdir <disk name> <MSX dir> : create directory on <disk name>\n"
442 "diskmanipulator dir <disk name> : long format file listing of current\n"
443 " directory on <disk name>\n"
444 "diskmanipulator import <disk> <dir/file> ... : import files and subdirs from <dir/file>\n"
445 "diskmanipulator export <disk> <host dir> : export all files on <disk> to <host dir>\n"
446 "diskmanipulator delete <disk> <MSX entry> : delete the specified file or directory\n"
447 "diskmanipulator rename <disk> <old> <new> : rename file or directory from <old> to <new>\n"
448 "For more info use 'help diskmanipulator <subcommand>'.\n";
453void DiskManipulator::tabCompletion(std::vector<string>& tokens)
const
455 using namespace std::literals;
456 if (tokens.size() == 2) {
457 static constexpr std::array cmds = {
458 "import"sv,
"export"sv,
"savedsk"sv,
"dir"sv,
"create"sv,
459 "partition"sv,
"format"sv,
"chdir"sv,
"mkdir"sv,
"delete"sv,
464 }
else if ((tokens.size() == 3) && (tokens[1] ==
"create")) {
467 }
else if (tokens.size() == 3) {
468 std::vector<string> names;
469 if (tokens[1] ==
one_of(
"partition",
"format")) {
470 names.emplace_back(
"-dos1");
471 names.emplace_back(
"-dos2");
472 names.emplace_back(
"-nextor");
474 for (
const auto& d : drives) {
475 const auto& name1 = d.driveName;
476 const auto& name2 = d.drive->getContainerName();
477 append(names, {name1, std::string(name2)});
480 if (
auto* disk = d.drive->getSectorAccessibleDisk()) {
481 for (
unsigned i = 1;
true; ++i) {
485 }
catch (MSXException&) {
492 }
catch (MSXException&) {
500 }
else if (tokens.size() >= 4) {
501 if (tokens[1] ==
one_of(
"savedsk",
"import",
"export")) {
502 static constexpr std::array extra = {
"-overwrite"sv};
504 (tokens[1] ==
"import") ? extra :
std::span<const
std::string_view>{});
505 }
else if (tokens[1] ==
one_of(
"create",
"partition",
"format")) {
506 static constexpr std::array cmds = {
507 "360"sv,
"720"sv,
"32M"sv,
"-dos1"sv,
"-dos2"sv,
"-nextor"sv,
514void DiskManipulator::savedsk(
const DriveSettings& driveData,
517 auto partition = getPartition(driveData);
520 for (
auto i :
xrange(partition.getNbSectors())) {
521 partition.readSector(i, buf);
526static std::pair<MSXBootSectorType, std::vector<unsigned>> parsePartitionSizes(
auto tokens)
529 std::vector<unsigned> sizes;
531 for (
const auto& token_ : tokens) {
532 if (
auto t = parseBootSectorType(token_.getString())) {
534 }
else if (
size_t sectors = parseSectorSize(token_.getString());
535 sectors <= std::numeric_limits<unsigned>::max()) {
536 sizes.push_back(narrow<unsigned>(sectors));
538 throw CommandException(
"Partition size too large.");
542 return {bootType, sizes};
547 auto [bootType, sizes] = parsePartitionSizes(
view::drop(tokens, 3));
549 create(filename, bootType, sizes);
554 size_t totalSectors =
sum(sizes, [](
size_t s) {
return s; });
555 if (totalSectors == 0) {
570 if (sizes.size() > 1) {
572 static_cast<std::span<const unsigned>
>(sizes), bootType);
573 if (partitionCount != sizes.size()) {
575 partitionCount,
" of ", sizes.size(),
" created.");
583void DiskManipulator::partition(std::span<const TclObject> tokens)
585 auto [bootType, sizes] = parsePartitionSizes(
view::drop(tokens, 3));
588 auto& settings = getDriveSettings(tokens[2].getString());
589 if (settings.partition > 0) {
592 auto*
image = settings.drive->getSectorAccessibleDisk();
594 static_cast<std::span<const unsigned>
>(sizes), bootType);
595 if (partitionCount != sizes.size()) {
596 throw CommandException(
"Could not create all partitions; ",
597 partitionCount,
" of ", sizes.size(),
" created.");
601void DiskManipulator::format(std::span<const TclObject> tokens)
604 std::optional<string> drive;
605 std::optional<size_t>
size;
606 for (
const auto& token_ :
view::
drop(tokens, 2)) {
607 if (
auto t = parseBootSectorType(token_.getString())) {
610 drive = token_.getString();
612 size = parseSectorSize(token_.getString());
614 throw CommandException(
"Incorrect number of parameters");
618 auto& driveData = getDriveSettings(*drive);
619 auto partition = getPartition(driveData);
625 driveData.setWorkingDir(driveData.partition,
"/");
627 throw CommandException(
"Incorrect number of parameters");
631MSXtar DiskManipulator::getMSXtar(
632 SectorAccessibleDisk& disk, DriveSettings& driveData)
635 throw CommandException(
"Please select partition number.");
639 string cwd = driveData.getWorkingDir(driveData.partition);
642 }
catch (MSXException&) {
643 driveData.setWorkingDir(driveData.partition,
"/");
644 throw CommandException(
646 " doesn't exist anymore. Went back to root "
647 "directory. Command aborted, please retry.");
652string DiskManipulator::dir(DriveSettings& driveData)
654 auto partition = getPartition(driveData);
655 auto workhorse = getMSXtar(partition, driveData);
656 return workhorse.dir();
659std::string DiskManipulator::deleteEntry(DriveSettings& driveData, std::string_view entry)
661 auto partition = getPartition(driveData);
662 auto workhorse = getMSXtar(partition, driveData);
663 return workhorse.deleteItem(entry);
666std::string DiskManipulator::rename(DriveSettings& driveData, std::string_view oldName, std::string_view newName)
668 auto partition = getPartition(driveData);
669 auto workhorse = getMSXtar(partition, driveData);
670 return workhorse.renameItem(oldName, newName);
673string DiskManipulator::chdir(DriveSettings& driveData, string_view filename)
675 auto partition = getPartition(driveData);
676 auto workhorse = getMSXtar(partition, driveData);
678 workhorse.chdir(filename);
679 }
catch (MSXException& e) {
680 throw CommandException(
"chdir failed: ",
e.getMessage());
683 string cwd = driveData.getWorkingDir(driveData.partition);
684 if (filename.starts_with(
'/')) {
687 if (!cwd.ends_with(
'/')) cwd +=
'/';
688 cwd.append(filename.data(), filename.size());
690 driveData.setWorkingDir(driveData.partition, cwd);
691 return "New working directory: " + cwd;
694void DiskManipulator::mkdir(DriveSettings& driveData, string_view filename)
696 auto partition = getPartition(driveData);
697 auto workhorse = getMSXtar(partition, driveData);
699 workhorse.mkdir(filename);
700 }
catch (MSXException& e) {
701 throw CommandException(std::move(e).getMessage());
705string DiskManipulator::import(DriveSettings& driveData,
706 std::span<const TclObject> lists,
bool overwrite)
708 auto partition = getPartition(driveData);
709 auto workhorse = getMSXtar(partition, driveData);
715 for (
const auto& l : lists) {
716 for (
auto i :
xrange(l.getListLength(interp))) {
721 throw CommandException(
"Non-existing file ", s);
724 messages += workhorse.addDir(s, add);
726 messages += workhorse.addFile(s, add);
729 strAppend(messages,
"Ignoring ", s,
'\n');
731 }
catch (MSXException& e) {
732 throw CommandException(std::move(e).getMessage());
739void DiskManipulator::exprt(DriveSettings& driveData, string_view dirname,
740 std::span<const TclObject> lists)
742 auto partition = getPartition(driveData);
743 auto workhorse = getMSXtar(partition, driveData);
747 workhorse.getDir(dirname);
749 for (
const auto& l : lists) {
750 workhorse.getItemFromDir(dirname, l.getString());
753 }
catch (MSXException& e) {
754 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)
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(SectorAccessibleDisk &disk)
Check whether the given disk is partitioned.
void checkSupportedPartition(SectorAccessibleDisk &disk, unsigned partition)
Check whether partition is of type FAT12 or FAT16.
Partition & getPartition(SectorAccessibleDisk &disk, unsigned partition, SectorBuffer &buf)
Gets the requested partition.
void format(SectorAccessibleDisk &disk, MSXBootSectorType bootType)
Format the given disk (= a single partition).
unsigned partition(SectorAccessibleDisk &disk, std::span< const unsigned > sizes, MSXBootSectorType bootType)
Write a partition table to the given disk and format each partition.
string expandTilde(string path)
Expand the '~' character to the users home directory.
bool isRegularFile(const Stat &st)
bool isDirectory(const Stat &st)
std::optional< Stat > getStat(zstring_view filename)
Call stat() and return the stat structure.
This file implemented 3 utility functions:
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)