OXYGENHUB
◆ main openmsx / src / fdc / DiskManipulator.cc
25.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "DiskManipulator.hh"
2
3
#include "CommandException.hh"
4
#include "DSKDiskImage.hh"
5
#include "DiskContainer.hh"
6
#include "DiskImageUtils.hh"
7
#include "DiskPartition.hh"
8
#include "File.hh"
9
#include "FileContext.hh"
10
#include "FileException.hh"
11
#include "FileOperations.hh"
12
#include "Filename.hh"
13
#include "Keyboard.hh"
14
#include "MSXDevice.hh"
15
#include "MSXMotherBoard.hh"
16
#include "MSXtar.hh"
17
#include "Reactor.hh"
18
#include "SectorBasedDisk.hh"
19
20
#include "StringOp.hh"
21
#include "TclArgParser.hh"
22
#include "TclObject.hh"
23
#include "narrow.hh"
24
#include "one_of.hh"
25
#include "stl.hh"
26
#include "strCat.hh"
27
#include "xrange.hh"
28
29
#include <algorithm>
30
#include <array>
31
#include <cassert>
32
#include <cctype>
33
#include <memory>
34
#include <ranges>
35
36
namespace openmsx {
37
38
DiskManipulator::DiskManipulator(CommandController& commandController_,
39
                                 Reactor& reactor_)
40
	: Command(commandController_, "diskmanipulator")
41
	, reactor(reactor_)
42
{
43
}
44
45
DiskManipulator::~DiskManipulator()
46
{
47
	assert(drives.empty()); // all DiskContainers must be unregistered
48
}
49
50
std::string DiskManipulator::getMachinePrefix() const
51
{
52
	std::string_view id = reactor.getMachineID();
53
	return id.empty() ? std::string{} : strCat(id, "::");
54
}
55
56
void DiskManipulator::registerDrive(
57
	DiskContainer& drive, std::string_view prefix)
58
{
59
	assert(findDriveSettings(drive) == end(drives));
60
	DriveSettings driveSettings;
61
	driveSettings.drive = &drive;
62
	driveSettings.driveName = strCat(prefix, drive.getContainerName());
63
	driveSettings.partition = 0;
64
	drives.push_back(driveSettings);
65
}
66
67
void DiskManipulator::unregisterDrive(DiskContainer& drive)
68
{
69
	auto it = findDriveSettings(drive);
70
	assert(it != end(drives));
71
	move_pop_back(drives, it);
72
}
73
74
std::vector<std::string> DiskManipulator::getDriveNamesForCurrentMachine() const
75
{
76
	std::vector<std::string> result;
77
	result.emplace_back("virtual_drive"); // always available
78
79
	auto prefix = getMachinePrefix();
80
	if (prefix.empty()) return result;
81
82
	for (const auto& drive : drives) {
83
		if (!drive.driveName.starts_with(prefix)) continue;
84
85
		auto* disk = drive.drive->getSectorAccessibleDisk();
86
		if (disk && DiskImageUtils::hasPartitionTable(*disk)) {
87
			for (unsigned i = 1; true; ++i) {
88
				try {
89
					SectorBuffer buf;
90
					DiskImageUtils::getPartition(*disk, i, buf);
91
				} catch (MSXException&) {
92
					break; // stop when the partition doesn't exist
93
				}
94
				try {
95
					DiskImageUtils::checkSupportedPartition(*disk, i);
96
					result.push_back(strCat(
97
						drive.driveName.substr(prefix.size()), i));
98
				} catch (MSXException&) {
99
					// skip invalid partition
100
				}
101
			}
102
		} else {
103
			// empty drive or no partition table
104
			result.emplace_back(drive.driveName.substr(prefix.size()));
105
		}
106
	}
107
	return result;
108
}
109
110
DiskContainer* DiskManipulator::getDrive(std::string_view fullName) const
111
{
112
	// input does not have machine prefix, but it may have a partition suffix
113
	auto pos = fullName.find_first_of("0123456789");
114
	auto driveName = (pos != std::string_view::npos)
115
		? fullName.substr(0, pos) // drop partition number
116
		: fullName;
117
118
	auto it = std::ranges::find(drives, driveName, &DriveSettings::driveName);
119
	if (it == end(drives)) {
120
		it = std::ranges::find(drives, strCat(getMachinePrefix(), driveName), &DriveSettings::driveName);
121
		if (it == end(drives)) {
122
			return {}; // drive doesn't exist
123
		}
124
	}
125
	auto* drive = it->drive;
126
	assert(drive);
127
	return drive;
128
}
129
130
std::optional<DiskManipulator::DriveAndPartition> DiskManipulator::getDriveAndDisk(std::string_view fullName) const
131
{
132
	auto* drive = getDrive(fullName);
133
	if (!drive) return {};
134
	auto* disk = drive->getSectorAccessibleDisk();
135
	if (!disk) return {};
136
137
	// input does not have machine prefix, but it may have a partition suffix
138
	auto pos = fullName.find_first_of("0123456789");
139
	unsigned partitionNum = 0; // full disk
140
	if (pos != std::string_view::npos) {
141
		auto num = StringOp::stringToBase<10, unsigned>(fullName.substr(pos));
142
		if (!num) return {}; // parse error
143
		partitionNum = *num;
144
	}
145
	try {
146
		return DriveAndPartition{
147
			.drive = drive,
148
			.partition = std::make_unique<DiskPartition>(*disk, partitionNum)};
149
	} catch (MSXException&) {
150
		return {}; // invalid partition?
151
	}
152
}
153
154
std::string DiskManipulator::DriveSettings::getWorkingDir(unsigned p)
155
{
156
	return p < workingDir.size() ? workingDir[p] : "/";
157
}
158
159
void DiskManipulator::DriveSettings::setWorkingDir(unsigned p, std::string_view dir)
160
{
161
	if (p >= workingDir.size()) {
162
		workingDir.resize(p + 1, "/");
163
	}
164
	workingDir[p] = dir;
165
}
166
167
DiskManipulator::Drives::iterator DiskManipulator::findDriveSettings(
168
	DiskContainer& drive)
169
{
170
	return std::ranges::find(drives, &drive, &DriveSettings::drive);
171
}
172
173
DiskManipulator::Drives::iterator DiskManipulator::findDriveSettings(
174
	std::string_view driveName)
175
{
176
	return std::ranges::find(drives, driveName, &DriveSettings::driveName);
177
}
178
179
DiskManipulator::DriveSettings& DiskManipulator::getDriveSettings(
180
	std::string_view diskName)
181
{
182
	// first split-off the end numbers (if present)
183
	// these will be used as partition indication
184
	auto pos1 = diskName.find("::");
185
	auto tmp1 = (pos1 == std::string_view::npos) ? diskName : diskName.substr(pos1);
186
	auto pos2 = tmp1.find_first_of("0123456789");
187
	auto pos1b = (pos1 == std::string_view::npos) ? 0 : pos1;
188
	auto tmp2 = diskName.substr(0, pos2 + pos1b);
189
190
	auto it = findDriveSettings(tmp2);
191
	if (it == end(drives)) {
192
		it = findDriveSettings(tmpStrCat(getMachinePrefix(), tmp2));
193
		if (it == end(drives)) {
194
			throw CommandException("Unknown drive: ", tmp2);
195
		}
196
	}
197
198
	auto* disk = it->drive->getSectorAccessibleDisk();
199
	if (!disk) {
200
		// not a SectorBasedDisk
201
		throw CommandException("Unsupported disk type.");
202
	}
203
204
	if (pos2 == std::string_view::npos) {
205
		// whole disk
206
		it->partition = 0;
207
	} else {
208
		auto partitionName = diskName.substr(pos2);
209
		auto partition = StringOp::stringToBase<10, unsigned>(partitionName);
210
		if (!partition) {
211
			throw CommandException("Invalid partition name: ", partitionName);
212
		}
213
		DiskImageUtils::checkSupportedPartition(*disk, *partition);
214
		it->partition = *partition;
215
	}
216
	return *it;
217
}
218
219
DiskPartition DiskManipulator::getPartition(
220
	const DriveSettings& driveData)
221
{
222
	auto* disk = driveData.drive->getSectorAccessibleDisk();
223
	assert(disk);
224
	return {*disk, driveData.partition};
225
}
226
227
static std::optional<MSXBootSectorType> parseBootSectorType(std::string_view s) {
228
	if (s == "-dos1")   return MSXBootSectorType::DOS1;
229
	if (s == "-dos2")   return MSXBootSectorType::DOS2;
230
	if (s == "-nextor") return MSXBootSectorType::NEXTOR;
231
	return {};
232
};
233
234
static size_t parseSectorSize(zstring_view tok) {
235
	char* q;
236
	size_t bytes = strtoull(tok.c_str(), &q, 0);
237
	int scale = 1024; // default is kilobytes
238
	if (*q) {
239
		if ((q == tok.c_str()) || *(q + 1)) {
240
			throw CommandException("Invalid size: ", tok);
241
		}
242
		switch (tolower(*q)) {
243
			case 'b':
244
				scale = 1;
245
				break;
246
			case 'k':
247
				scale = 1024;
248
				break;
249
			case 'm':
250
				scale = 1024 * 1024;
251
				break;
252
			case 's':
253
				scale = SectorBasedDisk::SECTOR_SIZE;
254
				break;
255
			default:
256
				throw CommandException("Invalid suffix: ", q);
257
		}
258
	}
259
	size_t sectors = (bytes * scale) / SectorBasedDisk::SECTOR_SIZE;
260
261
	// TEMP FIX: the smallest boot sector we create in MSXtar is for
262
	// a normal single sided disk.
263
	// TODO: MSXtar must be altered and this temp fix must be set to
264
	// the real smallest dsk possible (= boot sector + minimal fat +
265
	// minimal dir + minimal data clusters)
266
	return std::max(sectors, 720uz);
267
}
268
269
void DiskManipulator::execute(std::span<const TclObject> tokens, TclObject& result)
270
{
271
	if (tokens.size() == 1) {
272
		throw CommandException("Missing argument");
273
	}
274
275
	std::string_view subCmd = tokens[1].getString();
276
	if (((tokens.size() != 4)                     && (subCmd == one_of("savedsk", "mkdir", "delete"))) ||
277
	    ((tokens.size() != 5)                     && (subCmd ==        "rename"          ))  ||
278
	    ((tokens.size() != 3)                     && (subCmd ==        "dir"             ))  ||
279
	    ((tokens.size() < 3 || tokens.size() > 4) && (subCmd ==        "chdir"           ))  ||
280
	    ((tokens.size() < 3 || tokens.size() > 5) && (subCmd ==        "format"          ))  ||
281
	    ((tokens.size() < 3)                      && (subCmd ==        "partition"       ))  ||
282
	    ((tokens.size() < 4)                      && (subCmd == one_of("export", "import", "create")))) {
283
		throw CommandException("Incorrect number of parameters");
284
	}
285
286
	if (subCmd == "export") {
287
		std::string_view dir = tokens[3].getString();
288
		auto directory = FileOperations::expandTilde(std::string(dir));
289
		if (!FileOperations::isDirectory(directory)) {
290
			throw CommandException(dir, " is not a directory");
291
		}
292
		auto& settings = getDriveSettings(tokens[2].getString());
293
		std::span<const TclObject> lists(std::begin(tokens) + 4, std::end(tokens));
294
		exprt(settings, directory, lists);
295
296
	} else if (subCmd == "import") {
297
		auto& settings = getDriveSettings(tokens[2].getString());
298
299
		bool overwrite = false;
300
		std::array info = {flagArg("-overwrite", overwrite)};
301
		auto lists = parseTclArgs(getInterpreter(), tokens.subspan(3), info);
302
		if (lists.empty()) throw SyntaxError();
303
304
		result = imprt(settings, lists, overwrite);
305
306
	} else if (subCmd == "savedsk") {
307
		const auto& settings = getDriveSettings(tokens[2].getString());
308
		savedsk(settings, FileOperations::expandTilde(std::string(tokens[3].getString())));
309
310
	} else if (subCmd == "chdir") {
311
		auto& settings = getDriveSettings(tokens[2].getString());
312
		if (tokens.size() == 3) {
313
			result = tmpStrCat("Current directory: ",
314
			                   settings.getWorkingDir(settings.partition));
315
		} else {
316
			result = chdir(settings, tokens[3].getString());
317
		}
318
319
	} else if (subCmd == "mkdir") {
320
		auto& settings = getDriveSettings(tokens[2].getString());
321
		mkdir(settings, tokens[3].getString());
322
323
	} else if (subCmd == "create") {
324
		create(tokens);
325
326
	} else if (subCmd == "partition") {
327
		partition(tokens);
328
329
	} else if (subCmd == "format") {
330
		format(tokens);
331
332
	} else if (subCmd == "dir") {
333
		auto& settings = getDriveSettings(tokens[2].getString());
334
		result = dir(settings);
335
336
	} else if (subCmd == "delete") {
337
		auto& settings = getDriveSettings(tokens[2].getString());
338
		result = deleteEntry(settings, tokens[3].getString());
339
340
	} else if (subCmd == "rename") {
341
		auto& settings = getDriveSettings(tokens[2].getString());
342
		result = rename(settings, tokens[3].getString(), tokens[4].getString());
343
344
	} else {
345
		throw CommandException("Unknown subcommand: ", subCmd);
346
	}
347
}
348
349
std::string DiskManipulator::help(std::span<const TclObject> tokens) const
350
{
351
	std::string helpText;
352
	if (tokens.size() >= 2) {
353
	  if (tokens[1] == "import") {
354
	  helpText =
355
	    "diskmanipulator import <disk name> [-overwrite] <host directory|host file>\n"
356
	    "Import all files and subdirs from the host OS as specified into the <disk name> in the\n"
357
	    "current MSX subdirectory as was specified with the last chdir command.\n"
358
	    "By default already existing entries are not overwritten, unless the -overwrite option is used.";
359
	  } else if (tokens[1] == "export") {
360
	  helpText =
361
	    "diskmanipulator export <disk name> <host directory> [<file|subdir>...]\n"
362
	    "Extract <file> and <subdir> from the MSX subdirectory specified with the chdir command\n"
363
	    "from <disk name> to the host OS in <host directory>. If file or subdir is not specified,\n"
364
            "all the files and subdirectories in the current directory will be exported.";
365
	  } else if (tokens[1] == "savedsk") {
366
	  helpText =
367
	    "diskmanipulator savedsk <disk name> <dskfilename>\n"
368
	    "Save the complete drive content to <dskfilename>, it is not possible to save just one\n"
369
	    "partition. The main purpose of this command is to make it possible to save a 'ramdsk' into\n"
370
	    "a file and to take 'live backups' of dsk-files in use.\n";
371
	  } else if (tokens[1] == "chdir") {
372
	  helpText =
373
	    "diskmanipulator chdir <disk name> <MSX directory>\n"
374
	    "Change the working directory on <disk name>. This will be the directory were the 'import',\n"
375
	    "'export' and 'dir' commands will work on.\n"
376
	    "In case of a partitioned drive, each partition has its own working directory.\n";
377
	  } else if (tokens[1] == "mkdir") {
378
	  helpText =
379
	    "diskmanipulator mkdir <disk name> <MSX directory>\n"
380
	    "Create the specified directory on <disk name>. If needed, all missing parent directories\n"
381
	    "are created at the same time. Accepts both absolute and relative path names.\n";
382
	  } else if (tokens[1] == "create") {
383
	  helpText =
384
	    "diskmanipulator create <dskfilename> <size/option> [<size/option>...]\n"
385
	    "Create a formatted dsk file with the given size.\n"
386
	    "If multiple sizes are given, a partitioned disk image will be created with each partition\n"
387
	    "having the size as indicated. By default the sizes are expressed in kilobyte, add the\n"
388
	    "postfix M for megabyte.\n"
389
	    "When using the -dos1 option, the boot sector of the created image will be MSX-DOS1\n"
390
	    "compatible. When using the -nextor option, the boot sector and partition table will be\n"
391
		"Nextor compatible, and FAT16 volumes can be created.\n";
392
	  } else if (tokens[1] == "partition") {
393
	  helpText =
394
	    "diskmanipulator partition <disk name> [<size/option>...]\n"
395
	    "Partitions and formats the current <disk name> to the indicated sizes. By default the\n"
396
	    "sizes are expressed in kilobyte, add the postfix M for megabyte.\n"
397
	    "When using the -dos1 option, the boot sector of the disk will be MSX-DOS1 compatible.\n"
398
	    "When using the -nextor option, the boot sector and partition table will be Nextor\n"
399
		"compatible, and FAT16 volumes can be created.\n";
400
	  } else if (tokens[1] == "format") {
401
	  helpText =
402
	    "diskmanipulator format <disk name>\n"
403
	    "Formats the current (partition on) <disk name>. By default, it will create a regular\n"
404
	    "FAT12 MSX file system with an MSX-DOS2 boot sector, or, when the -dos1 option is\n"
405
		"specified, with an MSX-DOS1 boot sector. When the -nextor option is specified, it\n"
406
		"will create a FAT12 or FAT16 file system, with a Nextor boot sector.\n";
407
	  } else if (tokens[1] == "dir") {
408
	  helpText =
409
	    "diskmanipulator dir <disk name>\n"
410
	    "Shows the content of the current directory on <disk name>\n";
411
	  } else if (tokens[1] == "delete") {
412
	  helpText =
413
	    "diskmanipulator delete <disk name> <MSX entry>\n"
414
	    "Delete the given entry (a file or a directory) from disk, freeing up disk space.\n"
415
	    "The entry is searched in the directory specified with the chdir command.\n"
416
	    "In case of a directory (recursively) also all entries in that directory are deleted.\n";
417
	  } else if (tokens[1] == "rename") {
418
	  helpText =
419
	    "diskmanipulator rename <disk name> <old> <new>\n"
420
	    "Search the entry (file or directory) with name <old> and give it the name <new>.\n"
421
	    "The entry is searched in the directory specified with the chdir command.\n"
422
	    "The new name may not already exist.\n";
423
	  } else {
424
	  helpText = strCat("Unknown diskmanipulator subcommand: ", tokens[1].getString());
425
	  }
426
	} else {
427
	  helpText =
428
	    "diskmanipulator create <fn> <sz> [<sz> ...]  : create a formatted dsk file with name <fn>\n"
429
	    "                                               having the given (partition) size(s)\n"
430
	    "diskmanipulator partition <dn> [<sz> ...]    : partition and format <disk name>\n"
431
	    "diskmanipulator savedsk <disk name> <fn>     : save <disk name> as dsk file named as <fn>\n"
432
	    "diskmanipulator format <disk name> [<sz>]    : format (a partition on) <disk name>\n"
433
	    "diskmanipulator chdir <disk name> <MSX dir>  : change directory on <disk name>\n"
434
	    "diskmanipulator mkdir <disk name> <MSX dir>  : create directory on <disk name>\n"
435
	    "diskmanipulator dir <disk name>              : long format file listing of current\n"
436
	    "                                               directory on <disk name>\n"
437
	    "diskmanipulator import <disk> <dir/file> ... : import files and subdirs from <dir/file>\n"
438
	    "diskmanipulator export <disk> <host dir>     : export all files on <disk> to <host dir>\n"
439
	    "diskmanipulator delete <disk> <MSX entry>    : delete the specified file or directory\n"
440
	    "diskmanipulator rename <disk> <old> <new>    : rename file or directory from <old> to <new>\n"
441
	    "For more info use 'help diskmanipulator <subcommand>'.\n";
442
	}
443
	return helpText;
444
}
445
446
void DiskManipulator::tabCompletion(std::vector<std::string>& tokens) const
447
{
448
	using namespace std::literals;
449
	if (tokens.size() == 2) {
450
		static constexpr std::array cmds = {
451
			"import"sv, "export"sv, "savedsk"sv, "dir"sv, "create"sv,
452
			"partition"sv, "format"sv, "chdir"sv, "mkdir"sv, "delete"sv,
453
			"rename"sv
454
		};
455
		completeString(tokens, cmds);
456
457
	} else if ((tokens.size() == 3) && (tokens[1] == "create")) {
458
		completeFileName(tokens, userFileContext());
459
460
	} else if (tokens.size() == 3) {
461
		std::vector<std::string> names;
462
		if (tokens[1] == one_of("partition", "format")) {
463
			names.emplace_back("-dos1");
464
			names.emplace_back("-dos2");
465
			names.emplace_back("-nextor");
466
		}
467
		for (const auto& d : drives) {
468
			const auto& name1 = d.driveName; // with prefix
469
			const auto& name2 = d.drive->getContainerName(); // without prefix
470
			append(names, {name1, std::string(name2)});
471
			// if it has partitions then we also add the partition
472
			// numbers to the autocompletion
473
			if (auto* disk = d.drive->getSectorAccessibleDisk()) {
474
				for (unsigned i = 1; true; ++i) {
475
					try {
476
						SectorBuffer buf;
477
						DiskImageUtils::getPartition(*disk, i, buf);
478
					} catch (MSXException&) {
479
						break;
480
					}
481
					try {
482
						DiskImageUtils::checkSupportedPartition(*disk, i);
483
						append(names,
484
						       {strCat(name1, i), strCat(name2, i)});
485
					} catch (MSXException&) {
486
						// skip invalid partition
487
					}
488
				}
489
			}
490
		}
491
		completeString(tokens, names);
492
493
	} else if (tokens.size() >= 4) {
494
		if (tokens[1] == one_of("savedsk", "import", "export")) {
495
			static constexpr std::array extra = {"-overwrite"sv};
496
			completeFileName(tokens, userFileContext(),
497
				(tokens[1] == "import") ? extra : std::span<const std::string_view>{});
498
		} else if (tokens[1] == one_of("create", "partition", "format")) {
499
			static constexpr std::array cmds = {
500
				"360"sv, "720"sv, "32M"sv, "-dos1"sv, "-dos2"sv, "-nextor"sv,
501
			};
502
			completeString(tokens, cmds);
503
		}
504
	}
505
}
506
507
void DiskManipulator::savedsk(const DriveSettings& driveData,
508
                              std::string filename) const
509
{
510
	auto partition = getPartition(driveData);
511
	SectorBuffer buf;
512
	File file(std::move(filename), File::OpenMode::CREATE);
513
	for (auto i : xrange(partition.getNbSectors())) {
514
		partition.readSector(i, buf);
515
		file.write(buf.raw);
516
	}
517
}
518
519
static std::pair<MSXBootSectorType, std::vector<unsigned>> parsePartitionSizes(auto tokens)
520
{
521
	MSXBootSectorType bootType = MSXBootSectorType::DOS2;
522
	std::vector<unsigned> sizes;
523
524
	for (const auto& token_ : tokens) {
525
		if (auto t = parseBootSectorType(token_.getString())) {
526
			bootType = *t;
527
		} else if (size_t sectors = parseSectorSize(token_.getString());
528
		           sectors <= std::numeric_limits<unsigned>::max()) {
529
			sizes.push_back(narrow<unsigned>(sectors));
530
		} else {
531
			throw CommandException("Partition size too large.");
532
		}
533
	}
534
535
	return {bootType, sizes};
536
}
537
538
void DiskManipulator::create(std::span<const TclObject> tokens) const
539
{
540
	auto [bootType, sizes] = parsePartitionSizes(std::views::drop(tokens, 3));
541
	auto filename = FileOperations::expandTilde(std::string(tokens[2].getString()));
542
	create(filename, bootType, sizes);
543
}
544
545
void DiskManipulator::create(const std::string& filename, MSXBootSectorType bootType, const std::vector<unsigned>& sizes) const
546
{
547
	size_t totalSectors = sum(sizes, [](size_t s) { return s; });
548
	if (totalSectors == 0) {
549
		throw CommandException("No size(s) given.");
550
	}
551
552
	// create file with correct size
553
	try {
554
		File file(filename, File::OpenMode::CREATE);
555
		file.truncate(totalSectors * SectorBasedDisk::SECTOR_SIZE);
556
	} catch (FileException& e) {
557
		throw CommandException("Couldn't create image: ", e.getMessage());
558
	}
559
560
	// initialize (create partition tables and format partitions)
561
	DSKDiskImage image(Filename{filename});
562
	if (sizes.size() > 1) {
563
		unsigned partitionCount = DiskImageUtils::partition(image,
564
			static_cast<std::span<const unsigned>>(sizes), bootType);
565
		if (partitionCount != sizes.size()) {
566
			throw CommandException("Could not create all partitions; ",
567
				partitionCount, " of ", sizes.size(), " created.");
568
		}
569
	} else {
570
		// only one partition specified, don't create partition table
571
		DiskImageUtils::format(image, bootType);
572
	}
573
}
574
575
void DiskManipulator::partition(std::span<const TclObject> tokens)
576
{
577
	auto [bootType, sizes] = parsePartitionSizes(std::views::drop(tokens, 3));
578
579
	// initialize (create partition tables and format partitions)
580
	auto& settings = getDriveSettings(tokens[2].getString());
581
	if (settings.partition > 0) {
582
		throw CommandException("Disk name must not have partition number.");
583
	}
584
	auto* image = settings.drive->getSectorAccessibleDisk();
585
	unsigned partitionCount = DiskImageUtils::partition(*image,
586
		static_cast<std::span<const unsigned>>(sizes), bootType);
587
	if (partitionCount != sizes.size()) {
588
		throw CommandException("Could not create all partitions; ",
589
			partitionCount, " of ", sizes.size(), " created.");
590
	}
591
}
592
593
void DiskManipulator::format(std::span<const TclObject> tokens)
594
{
595
	MSXBootSectorType bootType = MSXBootSectorType::DOS2;
596
	std::optional<std::string> drive;
597
	std::optional<size_t> size;
598
	for (const auto& token_ : std::views::drop(tokens, 2)) {
599
		if (auto t = parseBootSectorType(token_.getString())) {
600
			bootType = *t;
601
		} else if (!drive) {
602
			drive = token_.getString();
603
		} else if (!size) {
604
			size = parseSectorSize(token_.getString());
605
		} else {
606
			throw CommandException("Incorrect number of parameters");
607
		}
608
	}
609
	if (drive) {
610
		auto& driveData = getDriveSettings(*drive);
611
		auto partition = getPartition(driveData);
612
		if (size) {
613
			DiskImageUtils::format(partition, bootType, *size);
614
		} else {
615
			DiskImageUtils::format(partition, bootType);
616
		}
617
		driveData.setWorkingDir(driveData.partition, "/");
618
	} else {
619
		throw CommandException("Incorrect number of parameters");
620
	}
621
}
622
623
MSXtar DiskManipulator::getMSXtar(
624
	SectorAccessibleDisk& disk, DriveSettings& driveData) const
625
{
626
	if (DiskImageUtils::hasPartitionTable(disk)) {
627
		throw CommandException("Please select partition number.");
628
	}
629
630
	MSXtar result(disk, reactor.getMsxChar2Unicode());
631
	std::string cwd = driveData.getWorkingDir(driveData.partition);
632
	try {
633
		result.chdir(cwd);
634
	} catch (MSXException&) {
635
		driveData.setWorkingDir(driveData.partition, "/");
636
		throw CommandException(
637
			"Directory ", cwd,
638
			" doesn't exist anymore. Went back to root "
639
			"directory. Command aborted, please retry.");
640
	}
641
	return result;
642
}
643
644
std::string DiskManipulator::dir(DriveSettings& driveData) const
645
{
646
	auto partition = getPartition(driveData);
647
	auto workhorse = getMSXtar(partition, driveData);
648
	return workhorse.dir();
649
}
650
651
std::string DiskManipulator::deleteEntry(DriveSettings& driveData, std::string_view entry) const
652
{
653
	auto partition = getPartition(driveData);
654
	auto workhorse = getMSXtar(partition, driveData);
655
	return workhorse.deleteItem(entry);
656
}
657
658
std::string DiskManipulator::rename(DriveSettings& driveData, std::string_view oldName, std::string_view newName) const
659
{
660
	auto partition = getPartition(driveData);
661
	auto workhorse = getMSXtar(partition, driveData);
662
	return workhorse.renameItem(oldName, newName);
663
}
664
665
std::string DiskManipulator::chdir(DriveSettings& driveData, std::string_view filename) const
666
{
667
	auto partition = getPartition(driveData);
668
	auto workhorse = getMSXtar(partition, driveData);
669
	try {
670
		workhorse.chdir(filename);
671
	} catch (MSXException& e) {
672
		throw CommandException("chdir failed: ", e.getMessage());
673
	}
674
	// TODO clean-up this temp hack, used to enable relative paths
675
	std::string cwd = driveData.getWorkingDir(driveData.partition);
676
	if (filename.starts_with('/')) {
677
		cwd = filename;
678
	} else {
679
		if (!cwd.ends_with('/')) cwd += '/';
680
		cwd.append(filename.data(), filename.size());
681
	}
682
	driveData.setWorkingDir(driveData.partition, cwd);
683
	return "New working directory: " + cwd;
684
}
685
686
void DiskManipulator::mkdir(DriveSettings& driveData, std::string_view filename) const
687
{
688
	auto partition = getPartition(driveData);
689
	auto workhorse = getMSXtar(partition, driveData);
690
	try {
691
		workhorse.mkdir(filename);
692
	} catch (MSXException& e) {
693
		throw CommandException(std::move(e).getMessage());
694
	}
695
}
696
697
std::string DiskManipulator::imprt(DriveSettings& driveData,
698
                              std::span<const TclObject> lists, bool overwrite) const
699
{
700
	auto partition = getPartition(driveData);
701
	auto workhorse = getMSXtar(partition, driveData);
702
703
	std::string messages;
704
	auto& interp = getInterpreter();
705
	auto add = overwrite ? MSXtar::Add::OVERWRITE
706
	                     : MSXtar::Add::PRESERVE;
707
	for (const auto& l : lists) {
708
		for (auto i : xrange(l.getListLength(interp))) {
709
			auto s = FileOperations::expandTilde(std::string(l.getListIndex(interp, i).getString()));
710
			try {
711
				auto st = FileOperations::getStat(s);
712
				if (!st) {
713
					throw CommandException("Non-existing file ", s);
714
				}
715
				if (FileOperations::isDirectory(*st)) {
716
					messages += workhorse.addDir(s, add);
717
				} else if (FileOperations::isRegularFile(*st)) {
718
					messages += workhorse.addFile(s, add);
719
				} else {
720
					// ignore other stuff (sockets, device nodes, ..)
721
					strAppend(messages, "Ignoring ", s, '\n');
722
				}
723
			} catch (MSXException& e) {
724
				throw CommandException(std::move(e).getMessage());
725
			}
726
		}
727
	}
728
	return messages;
729
}
730
731
void DiskManipulator::exprt(DriveSettings& driveData, std::string_view dirname,
732
                            std::span<const TclObject> lists) const
733
{
734
	auto partition = getPartition(driveData);
735
	auto workhorse = getMSXtar(partition, driveData);
736
	try {
737
		if (lists.empty()) {
738
			// export all
739
			workhorse.getDir(dirname);
740
		} else {
741
			for (const auto& l : lists) {
742
				workhorse.getItemFromDir(dirname, l.getString());
743
			}
744
		}
745
	} catch (MSXException& e) {
746
		throw CommandException(std::move(e).getMessage());
747
	}
748
}
749
750
} // namespace openmsx