36using std::string_view;
42 :
Command(commandController_,
"diskmanipulator")
49 assert(drives.empty());
52string DiskManipulator::getMachinePrefix()
const
55 return id.empty() ?
string{} :
strCat(
id,
"::");
58const MsxChar2Unicode& DiskManipulator::getMsxChar2Unicode()
const
65 if (
MSXPPI* ppi =
dynamic_cast<MSXPPI*
>(board->findDevice(
"ppi"))) {
66 return ppi->getKeyboard().getMsxChar2Unicode();
69 }
catch (MSXException&) {
71 static const MsxChar2Unicode defaultMsxChars(
"MSXVID.TXT");
72 return defaultMsxChars;
78 assert(findDriveSettings(drive) ==
end(drives));
79 DriveSettings driveSettings;
80 driveSettings.drive = &drive;
82 driveSettings.partition = 0;
83 drives.push_back(driveSettings);
88 auto it = findDriveSettings(drive);
89 assert(it !=
end(drives));
93std::string DiskManipulator::DriveSettings::getWorkingDir(
unsigned p)
95 return p < workingDir.size() ? workingDir[p] :
"/";
98void DiskManipulator::DriveSettings::setWorkingDir(
unsigned p, std::string_view dir)
100 if (p >= workingDir.size()) {
101 workingDir.resize(p + 1,
"/");
106DiskManipulator::Drives::iterator DiskManipulator::findDriveSettings(
107 DiskContainer& drive)
109 return ranges::find(drives, &drive, &DriveSettings::drive);
112DiskManipulator::Drives::iterator DiskManipulator::findDriveSettings(
113 string_view driveName)
115 return ranges::find(drives, driveName, &DriveSettings::driveName);
118DiskManipulator::DriveSettings& DiskManipulator::getDriveSettings(
119 string_view diskName)
123 auto pos1 = diskName.find(
"::");
124 auto tmp1 = (pos1 == string_view::npos) ? diskName : diskName.substr(pos1);
125 auto pos2 = tmp1.find_first_of(
"0123456789");
126 auto pos1b = (pos1 == string_view::npos) ? 0 : pos1;
127 auto tmp2 = diskName.substr(0, pos2 + pos1b);
129 auto it = findDriveSettings(tmp2);
130 if (it ==
end(drives)) {
131 it = findDriveSettings(
tmpStrCat(getMachinePrefix(), tmp2));
132 if (it ==
end(drives)) {
133 throw CommandException(
"Unknown drive: ", tmp2);
137 auto* disk = it->drive->getSectorAccessibleDisk();
140 throw CommandException(
"Unsupported disk type.");
143 if (pos2 == string_view::npos) {
147 auto partitionName = diskName.substr(pos2);
148 auto partition = StringOp::stringToBase<10, unsigned>(partitionName);
150 throw CommandException(
"Invalid partition name: ", partitionName);
153 it->partition = *partition;
158DiskPartition DiskManipulator::getPartition(
159 const DriveSettings& driveData)
161 auto* disk = driveData.drive->getSectorAccessibleDisk();
163 return {*disk, driveData.partition};
166static std::optional<MSXBootSectorType> parseBootSectorType(std::string_view s) {
175 size_t bytes = strtoull(tok.
c_str(), &q, 0);
178 if ((q == tok.
c_str()) || *(q + 1)) {
179 throw CommandException(
"Invalid size: ", tok);
181 switch (tolower(*q)) {
195 throw CommandException(
"Invalid suffix: ", q);
205 return std::max(sectors,
size_t(720));
208void DiskManipulator::execute(std::span<const TclObject> tokens, TclObject& result)
210 if (tokens.size() == 1) {
211 throw CommandException(
"Missing argument");
214 string_view subCmd = tokens[1].getString();
215 if (((tokens.size() != 4) && (subCmd ==
one_of(
"savedsk",
"mkdir"))) ||
216 ((tokens.size() != 3) && (subCmd ==
"dir" )) ||
217 ((tokens.size() < 3 || tokens.size() > 4) && (subCmd ==
"chdir" )) ||
218 ((tokens.size() < 3 || tokens.size() > 5) && (subCmd ==
"format" )) ||
219 ((tokens.size() < 3) && (subCmd ==
"partition" )) ||
220 ((tokens.size() < 4) && (subCmd ==
one_of(
"export",
"import",
"create")))) {
221 throw CommandException(
"Incorrect number of parameters");
224 if (subCmd ==
"export") {
225 string_view dir = tokens[3].getString();
228 throw CommandException(dir,
" is not a directory");
230 auto& settings = getDriveSettings(tokens[2].getString());
234 exprt(settings, directory, lists);
236 }
else if (subCmd ==
"import") {
237 auto& settings = getDriveSettings(tokens[2].getString());
241 result =
import(settings, lists);
243 }
else if (subCmd ==
"savedsk") {
244 auto& settings = getDriveSettings(tokens[2].getString());
247 }
else if (subCmd ==
"chdir") {
248 auto& settings = getDriveSettings(tokens[2].getString());
249 if (tokens.size() == 3) {
250 result =
tmpStrCat(
"Current directory: ",
251 settings.getWorkingDir(settings.partition));
253 result = chdir(settings, tokens[3].getString());
256 }
else if (subCmd ==
"mkdir") {
257 auto& settings = getDriveSettings(tokens[2].getString());
258 mkdir(settings, tokens[3].getString());
260 }
else if (subCmd ==
"create") {
263 }
else if (subCmd ==
"partition") {
266 }
else if (subCmd ==
"format") {
269 }
else if (subCmd ==
"dir") {
270 auto& settings = getDriveSettings(tokens[2].getString());
271 result = dir(settings);
274 throw CommandException(
"Unknown subcommand: ", subCmd);
278string DiskManipulator::help(std::span<const TclObject> tokens)
const
281 if (tokens.size() >= 2) {
282 if (tokens[1] ==
"import") {
284 "diskmanipulator import <disk name> <host directory|host file>\n"
285 "Import all files and subdirs from the host OS as specified into the <disk name> in the\n"
286 "current MSX subdirectory as was specified with the last chdir command.\n";
287 }
else if (tokens[1] ==
"export") {
289 "diskmanipulator export <disk name> <host directory>\n"
290 "Extract all files and subdirs from the MSX subdirectory specified with the chdir command\n"
291 "from <disk name> to the host OS in <host directory>.\n";
292 }
else if (tokens[1] ==
"savedsk") {
294 "diskmanipulator savedsk <disk name> <dskfilename>\n"
295 "Save the complete drive content to <dskfilename>, it is not possible to save just one\n"
296 "partition. The main purpose of this command is to make it possible to save a 'ramdsk' into\n"
297 "a file and to take 'live backups' of dsk-files in use.\n";
298 }
else if (tokens[1] ==
"chdir") {
300 "diskmanipulator chdir <disk name> <MSX directory>\n"
301 "Change the working directory on <disk name>. This will be the directory were the 'import',\n"
302 "'export' and 'dir' commands will work on.\n"
303 "In case of a partitioned drive, each partition has its own working directory.\n";
304 }
else if (tokens[1] ==
"mkdir") {
306 "diskmanipulator mkdir <disk name> <MSX directory>\n"
307 "Create the specified directory on <disk name>. If needed, all missing parent directories\n"
308 "are created at the same time. Accepts both absolute and relative path names.\n";
309 }
else if (tokens[1] ==
"create") {
311 "diskmanipulator create <dskfilename> <size/option> [<size/option>...]\n"
312 "Create a formatted dsk file with the given size.\n"
313 "If multiple sizes are given, a partitioned disk image will be created with each partition\n"
314 "having the size as indicated. By default the sizes are expressed in kilobyte, add the\n"
315 "postfix M for megabyte.\n"
316 "When using the -dos1 option, the boot sector of the created image will be MSX-DOS1\n"
317 "compatible. When using the -nextor option, the boot sector and partition table will be\n"
318 "Nextor compatible, and FAT16 volumes can be created.\n";
319 }
else if (tokens[1] ==
"partition") {
321 "diskmanipulator partition <disk name> [<size/option>...]\n"
322 "Partitions and formats the current <disk name> to the indicated sizes. By default the\n"
323 "sizes are expressed in kilobyte, add the postfix M for megabyte.\n"
324 "When using the -dos1 option, the boot sector of the disk will be MSX-DOS1 compatible.\n"
325 "When using the -nextor option, the boot sector and partition table will be Nextor\n"
326 "compatible, and FAT16 volumes can be created.\n";
327 }
else if (tokens[1] ==
"format") {
329 "diskmanipulator format <disk name>\n"
330 "Formats the current (partition on) <disk name>. By default, it will create a regular\n"
331 "FAT12 MSX file system with an MSX-DOS2 boot sector, or, when the -dos1 option is\n"
332 "specified, with an MSX-DOS1 boot sector. When the -nextor option is specified, it\n"
333 "will create a FAT12 or FAT16 file system, with a Nextor boot sector.\n";
334 }
else if (tokens[1] ==
"dir") {
336 "diskmanipulator dir <disk name>\n"
337 "Shows the content of the current directory on <disk name>\n";
339 helpText =
strCat(
"Unknown diskmanipulator subcommand: ", tokens[1].getString());
343 "diskmanipulator create <fn> <sz> [<sz> ...] : create a formatted dsk file with name <fn>\n"
344 " having the given (partition) size(s)\n"
345 "diskmanipulator partition <dn> [<sz> ...] : partition and format <disk name>\n"
346 "diskmanipulator savedsk <disk name> <fn> : save <disk name> as dsk file named as <fn>\n"
347 "diskmanipulator format <disk name> [<sz>] : format (a partition on) <disk name>\n"
348 "diskmanipulator chdir <disk name> <MSX dir> : change directory on <disk name>\n"
349 "diskmanipulator mkdir <disk name> <MSX dir> : create directory on <disk name>\n"
350 "diskmanipulator dir <disk name> : long format file listing of current\n"
351 " directory on <disk name>\n"
352 "diskmanipulator import <disk> <dir/file> ... : import files and subdirs from <dir/file>\n"
353 "diskmanipulator export <disk> <host dir> : export all files on <disk> to <host dir>\n"
354 "For more info use 'help diskmanipulator <subcommand>'.\n";
359void DiskManipulator::tabCompletion(std::vector<string>& tokens)
const
361 using namespace std::literals;
362 if (tokens.size() == 2) {
363 static constexpr std::array cmds = {
364 "import"sv,
"export"sv,
"savedsk"sv,
"dir"sv,
"create"sv,
365 "partition"sv,
"format"sv,
"chdir"sv,
"mkdir"sv,
369 }
else if ((tokens.size() == 3) && (tokens[1] ==
"create")) {
372 }
else if (tokens.size() == 3) {
373 std::vector<string> names;
374 if (tokens[1] ==
one_of(
"partition",
"format")) {
375 names.emplace_back(
"-dos1");
376 names.emplace_back(
"-dos2");
377 names.emplace_back(
"-nextor");
379 for (
const auto& d : drives) {
380 const auto& name1 = d.driveName;
381 const auto& name2 = d.drive->getContainerName();
382 append(names, {name1, std::string(name2)});
385 if (
auto* disk = d.drive->getSectorAccessibleDisk()) {
386 for (
unsigned i = 1;
true; ++i) {
390 }
catch (MSXException&) {
397 }
catch (MSXException&) {
405 }
else if (tokens.size() >= 4) {
406 if (tokens[1] ==
one_of(
"savedsk",
"import",
"export")) {
408 }
else if (tokens[1] ==
one_of(
"create",
"partition",
"format")) {
409 static constexpr std::array cmds = {
410 "360"sv,
"720"sv,
"32M"sv,
"-dos1"sv,
"-dos2"sv,
"-nextor"sv,
417void DiskManipulator::savedsk(
const DriveSettings& driveData,
420 auto partition = getPartition(driveData);
423 for (
auto i :
xrange(partition.getNbSectors())) {
424 partition.readSector(i, buf);
429static std::pair<MSXBootSectorType, std::vector<unsigned>> parsePartitionSizes(
auto tokens)
432 std::vector<unsigned> sizes;
434 for (
const auto& token_ : tokens) {
435 if (
auto t = parseBootSectorType(token_.getString())) {
437 }
else if (
size_t sectors = parseSectorSize(token_.getString());
439 sizes.push_back(narrow<unsigned>(sectors));
441 throw CommandException(
"Partition size too large.");
445 return {bootType, sizes};
448void DiskManipulator::create(std::span<const TclObject> tokens)
450 auto [bootType, sizes] = parsePartitionSizes(
view::drop(tokens, 3));
452 size_t totalSectors =
sum(sizes, [](
size_t s) {
return s; });
453 if (totalSectors == 0) {
454 throw CommandException(
"No size(s) given.");
462 }
catch (FileException&
e) {
463 throw CommandException(
"Couldn't create image: ",
e.getMessage());
467 DSKDiskImage
image(filename);
468 if (sizes.size() > 1) {
470 static_cast<std::span<const unsigned>
>(sizes), bootType);
471 if (partitionCount != sizes.size()) {
472 throw CommandException(
"Could not create all partitions; ",
473 partitionCount,
" of ", sizes.size(),
" created.");
481void DiskManipulator::partition(std::span<const TclObject> tokens)
483 auto [bootType, sizes] = parsePartitionSizes(
view::drop(tokens, 3));
486 auto& settings = getDriveSettings(tokens[2].getString());
487 if (settings.partition > 0) {
488 throw CommandException(
"Disk name must not have partition number.");
490 auto*
image = settings.drive->getSectorAccessibleDisk();
492 static_cast<std::span<const unsigned>
>(sizes), bootType);
493 if (partitionCount != sizes.size()) {
494 throw CommandException(
"Could not create all partitions; ",
495 partitionCount,
" of ", sizes.size(),
" created.");
499void DiskManipulator::format(std::span<const TclObject> tokens)
502 std::optional<string> drive;
503 std::optional<size_t>
size;
504 for (
const auto& token_ :
view::drop(tokens, 2)) {
505 if (
auto t = parseBootSectorType(token_.getString())) {
508 drive = token_.getString();
510 size = parseSectorSize(token_.getString());
512 throw CommandException(
"Incorrect number of parameters");
516 auto& driveData = getDriveSettings(*drive);
517 auto partition = getPartition(driveData);
523 driveData.setWorkingDir(driveData.partition,
"/");
525 throw CommandException(
"Incorrect number of parameters");
529MSXtar DiskManipulator::getMSXtar(
530 SectorAccessibleDisk& disk, DriveSettings& driveData)
533 throw CommandException(
"Please select partition number.");
536 MSXtar result(disk, getMsxChar2Unicode());
537 string cwd = driveData.getWorkingDir(driveData.partition);
540 }
catch (MSXException&) {
541 driveData.setWorkingDir(driveData.partition,
"/");
542 throw CommandException(
544 " doesn't exist anymore. Went back to root "
545 "directory. Command aborted, please retry.");
550string DiskManipulator::dir(DriveSettings& driveData)
552 auto partition = getPartition(driveData);
553 auto workhorse = getMSXtar(partition, driveData);
554 return workhorse.dir();
557string DiskManipulator::chdir(DriveSettings& driveData, string_view filename)
559 auto partition = getPartition(driveData);
560 auto workhorse = getMSXtar(partition, driveData);
562 workhorse.chdir(filename);
563 }
catch (MSXException&
e) {
564 throw CommandException(
"chdir failed: ",
e.getMessage());
567 string cwd = driveData.getWorkingDir(driveData.partition);
568 if (filename.starts_with(
'/')) {
571 if (!cwd.ends_with(
'/')) cwd +=
'/';
572 cwd.append(filename.data(), filename.size());
574 driveData.setWorkingDir(driveData.partition, cwd);
575 return "New working directory: " + cwd;
578void DiskManipulator::mkdir(DriveSettings& driveData, string_view filename)
580 auto partition = getPartition(driveData);
581 auto workhorse = getMSXtar(partition, driveData);
583 workhorse.mkdir(filename);
584 }
catch (MSXException&
e) {
585 throw CommandException(std::move(
e).getMessage());
589string DiskManipulator::import(DriveSettings& driveData,
590 std::span<const TclObject> lists)
592 auto partition = getPartition(driveData);
593 auto workhorse = getMSXtar(partition, driveData);
597 for (
const auto& l : lists) {
598 for (
auto i :
xrange(l.getListLength(interp))) {
603 throw CommandException(
"Non-existing file ", s);
606 messages += workhorse.addDir(s);
608 messages += workhorse.addFile(s);
611 strAppend(messages,
"Ignoring ", s,
'\n');
613 }
catch (MSXException&
e) {
614 throw CommandException(std::move(
e).getMessage());
621void DiskManipulator::exprt(DriveSettings& driveData, string_view dirname,
622 std::span<const TclObject> lists)
624 auto partition = getPartition(driveData);
625 auto workhorse = getMSXtar(partition, driveData);
629 workhorse.getDir(dirname);
631 for (
const auto& l : lists) {
632 workhorse.getItemFromDir(dirname, l.getString());
635 }
catch (MSXException&
e) {
636 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)
DiskManipulator(CommandController &commandController, Reactor &reactor)
void registerDrive(DiskContainer &drive, std::string_view prefix)
Contains the main loop of openMSX.
MSXMotherBoard * getMotherBoard() const
std::string_view getMachineID() 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 vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
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:
FileContext userFileContext(string_view savePath)
auto find(InputRange &&range, const T &value)
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)
std::string strCat(Ts &&...ts)
void strAppend(std::string &result, Ts &&...ts)
constexpr auto xrange(T e)
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)