30 using std::string_view;
36 :
Command(commandController_,
"diskmanipulator")
43 assert(drives.empty());
46 string DiskManipulator::getMachinePrefix()
const
49 return id.empty() ?
string{} :
strCat(
id,
"::");
55 assert(findDriveSettings(drive) ==
end(drives));
56 DriveSettings driveSettings;
57 driveSettings.drive = &drive;
59 driveSettings.partition = 0;
60 for (
unsigned i = 0; i <= MAX_PARTITIONS; ++i) {
61 driveSettings.workingDir[i] =
'/';
63 drives.push_back(driveSettings);
68 auto it = findDriveSettings(drive);
69 assert(it !=
end(drives));
73 DiskManipulator::Drives::iterator DiskManipulator::findDriveSettings(
76 return ranges::find(drives, &drive, &DriveSettings::drive);
79 DiskManipulator::Drives::iterator DiskManipulator::findDriveSettings(
80 string_view driveName)
82 return ranges::find(drives, driveName, &DriveSettings::driveName);
85 DiskManipulator::DriveSettings& DiskManipulator::getDriveSettings(
90 auto pos1 = diskname.find(
"::");
91 auto tmp1 = (pos1 == string_view::npos) ? diskname : diskname.substr(pos1);
92 auto pos2 = tmp1.find_first_of(
"0123456789");
93 auto pos1b = (pos1 == string_view::npos) ? 0 : pos1;
94 auto tmp2 = diskname.substr(0, pos2 + pos1b);
96 auto it = findDriveSettings(tmp2);
97 if (it ==
end(drives)) {
98 it = findDriveSettings(
tmpStrCat(getMachinePrefix(), tmp2));
99 if (it ==
end(drives)) {
100 throw CommandException(
"Unknown drive: ", tmp2);
104 auto* disk = it->drive->getSectorAccessibleDisk();
107 throw CommandException(
"Unsupported disk type.");
110 if (pos2 == string_view::npos) {
114 auto partitionName = diskname.substr(pos2);
115 auto partition = StringOp::stringToBase<10, unsigned>(partitionName);
117 throw CommandException(
"Invalid partition name: ", partitionName);
125 DiskPartition DiskManipulator::getPartition(
126 const DriveSettings& driveData)
128 auto* disk = driveData.drive->getSectorAccessibleDisk();
130 return {*disk, driveData.partition};
134 void DiskManipulator::execute(std::span<const TclObject> tokens, TclObject& result)
136 if (tokens.size() == 1) {
137 throw CommandException(
"Missing argument");
140 string_view subcmd = tokens[1].getString();
141 if (((tokens.size() != 4) && (subcmd ==
one_of(
"savedsk",
"mkdir"))) ||
142 ((tokens.size() != 3) && (subcmd ==
"dir" )) ||
143 ((tokens.size() < 3 || tokens.size() > 4) && (subcmd ==
one_of(
"format",
"chdir"))) ||
144 ((tokens.size() < 4) && (subcmd ==
one_of(
"export",
"import",
"create")))) {
145 throw CommandException(
"Incorrect number of parameters");
148 if (subcmd ==
"export") {
149 string_view dir = tokens[3].getString();
152 throw CommandException(dir,
" is not a directory");
154 auto& settings = getDriveSettings(tokens[2].getString());
158 exprt(settings, directory, lists);
160 }
else if (subcmd ==
"import") {
161 auto& settings = getDriveSettings(tokens[2].getString());
165 result =
import(settings, lists);
167 }
else if (subcmd ==
"savedsk") {
168 auto& settings = getDriveSettings(tokens[2].getString());
171 }
else if (subcmd ==
"chdir") {
172 auto& settings = getDriveSettings(tokens[2].getString());
173 if (tokens.size() == 3) {
174 result =
tmpStrCat(
"Current directory: ",
175 settings.workingDir[settings.partition]);
177 result = chdir(settings, tokens[3].getString());
180 }
else if (subcmd ==
"mkdir") {
181 auto& settings = getDriveSettings(tokens[2].getString());
182 mkdir(settings, tokens[3].getString());
184 }
else if (subcmd ==
"create") {
187 }
else if (subcmd ==
"format") {
189 string_view drive = tokens[2].getString();
190 if (tokens.size() == 4) {
191 if (drive ==
"-dos1") {
193 drive = tokens[3].getString();
194 }
else if (tokens[3] ==
"-dos1") {
198 auto& settings = getDriveSettings(drive);
199 format(settings, dos1);
201 }
else if (subcmd ==
"dir") {
202 auto& settings = getDriveSettings(tokens[2].getString());
203 result = dir(settings);
206 throw CommandException(
"Unknown subcommand: ", subcmd);
210 string DiskManipulator::help(std::span<const TclObject> tokens)
const
213 if (tokens.size() >= 2) {
214 if (tokens[1] ==
"import") {
216 "diskmanipulator import <disk name> <host directory|host file>\n"
217 "Import all files and subdirs from the host OS as specified into the <disk name> in the\n"
218 "current MSX subdirectory as was specified with the last chdir command.\n";
219 }
else if (tokens[1] ==
"export") {
221 "diskmanipulator export <disk name> <host directory>\n"
222 "Extract all files and subdirs from the MSX subdirectory specified with the chdir command\n"
223 "from <disk name> to the host OS in <host directory>.\n";
224 }
else if (tokens[1] ==
"savedsk") {
226 "diskmanipulator savedsk <disk name> <dskfilename>\n"
227 "Save the complete drive content to <dskfilename>, it is not possible to save just one\n"
228 "partition. The main purpose of this command is to make it possible to save a 'ramdsk' into\n"
229 "a file and to take 'live backups' of dsk-files in use.\n";
230 }
else if (tokens[1] ==
"chdir") {
232 "diskmanipulator chdir <disk name> <MSX directory>\n"
233 "Change the working directory on <disk name>. This will be the directory were the 'import',\n"
234 "'export' and 'dir' commands will work on.\n"
235 "In case of a partitioned drive, each partition has its own working directory.\n";
236 }
else if (tokens[1] ==
"mkdir") {
238 "diskmanipulator mkdir <disk name> <MSX directory>\n"
239 "Create the specified directory on <disk name>. If needed, all missing parent directories\n"
240 "are created at the same time. Accepts both absolute and relative path names.\n";
241 }
else if (tokens[1] ==
"create") {
243 "diskmanipulator create <dskfilename> <size/option> [<size/option>...]\n"
244 "Create a formatted dsk file with the given size.\n"
245 "If multiple sizes are given, a partitioned disk image will be created with each partition\n"
246 "having the size as indicated. By default the sizes are expressed in kilobyte, add the\n"
247 "postfix M for megabyte.\n"
248 "When using the -dos1 option, the boot sector of the created image will be MSX-DOS1\n"
250 }
else if (tokens[1] ==
"format") {
252 "diskmanipulator format <disk name>\n"
253 "formats the current (partition on) <disk name> with a regular FAT12 MSX filesystem with an\n"
254 "MSX-DOS2 boot sector, or, when the -dos1 option is specified, with an MSX-DOS1 boot sector.\n";
255 }
else if (tokens[1] ==
"dir") {
257 "diskmanipulator dir <disk name>\n"
258 "Shows the content of the current directory on <disk name>\n";
260 helptext =
strCat(
"Unknown diskmanipulator subcommand: ", tokens[1].getString());
264 "diskmanipulator create <fn> <sz> [<sz> ...] : create a formatted dsk file with name <fn>\n"
265 " having the given (partition) size(s)\n"
266 "diskmanipulator savedsk <disk name> <fn> : save <disk name> as dsk file named as <fn>\n"
267 "diskmanipulator format <disk name> : format (a partition) on <disk name>\n"
268 "diskmanipulator chdir <disk name> <MSX dir> : change directory on <disk name>\n"
269 "diskmanipulator mkdir <disk name> <MSX dir> : create directory on <disk name>\n"
270 "diskmanipulator dir <disk name> : long format file listing of current\n"
271 " directory on <disk name>\n"
272 "diskmanipulator import <disk> <dir/file> ... : import files and subdirs from <dir/file>\n"
273 "diskmanipulator export <disk> <host dir> : export all files on <disk> to <host dir>\n"
274 "For more info use 'help diskmanipulator <subcommand>'.\n";
279 void DiskManipulator::tabCompletion(std::vector<string>& tokens)
const
281 using namespace std::literals;
282 if (tokens.size() == 2) {
283 static constexpr std::array cmds = {
284 "import"sv,
"export"sv,
"savedsk"sv,
"dir"sv,
"create"sv,
285 "format"sv,
"chdir"sv,
"mkdir"sv,
289 }
else if ((tokens.size() == 3) && (tokens[1] ==
"create")) {
292 }
else if (tokens.size() == 3) {
293 std::vector<string> names;
294 if (tokens[1] ==
one_of(
"format",
"create")) {
295 names.emplace_back(
"-dos1");
297 for (
const auto& d : drives) {
298 const auto& name1 = d.driveName;
299 const auto& name2 = d.drive->getContainerName();
300 append(names, {name1, std::string(name2)});
303 if (
auto* disk = d.drive->getSectorAccessibleDisk()) {
304 for (
unsigned i = 1; i <= MAX_PARTITIONS; ++i) {
309 }
catch (MSXException&) {
317 }
else if (tokens.size() >= 4) {
318 if (tokens[1] ==
one_of(
"savedsk",
"import",
"export")) {
320 }
else if (tokens[1] ==
"create") {
321 static constexpr std::array cmds = {
322 "360"sv,
"720"sv,
"32M"sv,
"-dos1"sv,
325 }
else if (tokens[1] ==
"format") {
326 static constexpr std::array cmds = {
"-dos1"sv};
332 void DiskManipulator::savedsk(
const DriveSettings& driveData,
335 auto partition = getPartition(driveData);
340 file.write(&buf,
sizeof(buf));
344 void DiskManipulator::create(std::span<const TclObject> tokens)
347 unsigned totalSectors = 0;
350 for (
const auto& token :
view::drop(tokens, 3)) {
351 if (token ==
"-dos1") {
356 if (sizes.
size() >= MAX_PARTITIONS) {
357 throw CommandException(
358 "Maximum number of partitions is ", MAX_PARTITIONS);
360 auto tok = token.getString();
362 int sectors = strtol(tok.c_str(), &q, 0);
365 if ((q == tok.c_str()) || *(q + 1)) {
366 throw CommandException(
"Invalid size: ", tok);
368 switch (tolower(*q)) {
382 throw CommandException(
"Invalid suffix: ", q);
389 if (sectors > 65535) sectors = 65535;
396 if (sectors < 720) sectors = 720;
399 totalSectors += sectors;
402 throw CommandException(
"No size(s) given.");
404 if (sizes.
size() > 1) {
414 }
catch (FileException&
e) {
415 throw CommandException(
"Couldn't create image: ",
e.getMessage());
420 if (sizes.
size() > 1) {
428 void DiskManipulator::format(DriveSettings& driveData,
bool dos1)
430 auto partition = getPartition(driveData);
432 driveData.workingDir[driveData.partition] =
'/';
435 MSXtar DiskManipulator::getMSXtar(
436 SectorAccessibleDisk& disk, DriveSettings& driveData)
439 throw CommandException(
"Please select partition number.");
444 result.chdir(driveData.workingDir[driveData.partition]);
445 }
catch (MSXException&) {
446 driveData.workingDir[driveData.partition] =
'/';
447 throw CommandException(
448 "Directory ", driveData.workingDir[driveData.partition],
449 " doesn't exist anymore. Went back to root "
450 "directory. Command aborted, please retry.");
455 string DiskManipulator::dir(DriveSettings& driveData)
457 auto partition = getPartition(driveData);
458 auto workhorse = getMSXtar(
partition, driveData);
459 return workhorse.dir();
462 string DiskManipulator::chdir(DriveSettings& driveData, string_view
filename)
464 auto partition = getPartition(driveData);
465 auto workhorse = getMSXtar(
partition, driveData);
468 }
catch (MSXException&
e) {
469 throw CommandException(
"chdir failed: ",
e.getMessage());
472 string& cwd = driveData.workingDir[driveData.partition];
476 if (!cwd.ends_with(
'/')) cwd +=
'/';
479 return "New working directory: " + cwd;
482 void DiskManipulator::mkdir(DriveSettings& driveData, string_view
filename)
484 auto partition = getPartition(driveData);
485 auto workhorse = getMSXtar(
partition, driveData);
488 }
catch (MSXException&
e) {
489 throw CommandException(std::move(
e).getMessage());
493 string DiskManipulator::import(DriveSettings& driveData,
494 std::span<const TclObject> lists)
496 auto partition = getPartition(driveData);
497 auto workhorse = getMSXtar(
partition, driveData);
501 for (
const auto& l : lists) {
502 for (
auto i :
xrange(l.getListLength(interp))) {
507 throw CommandException(
508 "Non-existing file ", s);
511 messages += workhorse.addDir(s);
513 messages += workhorse.addFile(s);
516 strAppend(messages,
"Ignoring ", s,
'\n');
518 }
catch (MSXException&
e) {
519 throw CommandException(std::move(
e).getMessage());
526 void DiskManipulator::exprt(DriveSettings& driveData, string_view dirname,
527 std::span<const TclObject> lists)
529 auto partition = getPartition(driveData);
530 auto workhorse = getMSXtar(
partition, driveData);
534 workhorse.getDir(dirname);
536 for (
const auto& l : lists) {
537 workhorse.getItemFromDir(dirname, l.getString());
540 }
catch (MSXException&
e) {
541 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.
std::string_view getMachineID() const
static constexpr size_t SECTOR_SIZE
constexpr size_t size() const
constexpr bool empty() const
constexpr void push_back(const T &a)
constexpr mat4 scale(const vec3 &xyz)
bool hasPartitionTable(SectorAccessibleDisk &disk)
Check whether the given disk is partitioned.
void partition(SectorAccessibleDisk &disk, std::span< const unsigned > sizes)
Write a partition table to the given disk and format each partition.
void format(SectorAccessibleDisk &disk, bool dos1)
Format the given disk (= a single partition).
void checkFAT12Partition(SectorAccessibleDisk &disk, unsigned partition)
Like above, but also check whether partition is of type FAT12.
string expandTilde(string path)
Expand the '~' character to the users home directory.
bool getStat(zstring_view filename, Stat &st)
Call stat() and return the stat structure.
bool isRegularFile(const Stat &st)
bool isDirectory(const Stat &st)
This file implemented 3 utility functions:
constexpr const char *const filename
FileContext userFileContext(string_view savePath)
auto find(InputRange &&range, const T &value)
constexpr auto drop(Range &&range, size_t n)
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)