openMSX
NowindCommand.cc
Go to the documentation of this file.
1 #include "NowindCommand.hh"
2 #include "NowindRomDisk.hh"
3 #include "NowindHost.hh"
4 #include "DiskChanger.hh"
5 #include "DSKDiskImage.hh"
6 #include "DiskPartition.hh"
7 #include "FileContext.hh"
8 #include "StringOp.hh"
9 #include "FileOperations.hh"
10 #include "CommandException.hh"
11 #include "memory.hh"
12 #include "unreachable.hh"
13 #include <cassert>
14 #include <deque>
15 
16 using std::unique_ptr;
17 using std::deque;
18 using std::set;
19 using std::string;
20 using std::vector;
21 
22 namespace openmsx {
23 
24 NowindCommand::NowindCommand(const string& basename,
25  CommandController& commandController,
26  NowindInterface& interface_)
27  : Command(commandController, basename)
28  , interface(interface_)
29 {
30 }
31 
32 unique_ptr<DiskChanger> NowindCommand::createDiskChanger(
33  const string& basename, unsigned n, MSXMotherBoard& motherBoard) const
34 {
35  string name = StringOp::Builder() << basename << n + 1;
36  return make_unique<DiskChanger>(motherBoard, name, false);
37 }
38 
39 unsigned NowindCommand::searchRomdisk(const NowindInterface::Drives& drives) const
40 {
41  for (unsigned i = 0; i < drives.size(); ++i) {
42  if (drives[i]->isRomdisk()) {
43  return i;
44  }
45  }
46  return 255;
47 }
48 
49 void NowindCommand::processHdimage(
50  const string& hdimage, NowindInterface::Drives& drives) const
51 {
52  MSXMotherBoard& motherboard = interface.getMotherBoard();
53 
54  // Possible formats are:
55  // <filename> or <filename>:<range>
56  // Though <filename> itself can contain ':' characters. To solve this
57  // disambiguity we will always interpret the string as <filename> if
58  // it is an existing filename.
59  set<unsigned> partitions;
60  auto pos = hdimage.find_last_of(':');
61  if ((pos != string::npos) && !FileOperations::exists(hdimage)) {
62  partitions = StringOp::parseRange(
63  string_ref(hdimage).substr(pos + 1), 1, 31);
64  }
65 
66  auto wholeDisk = std::make_shared<DSKDiskImage>(Filename(hdimage));
67  bool failOnError = true;
68  if (partitions.empty()) {
69  // insert all partitions
70  failOnError = false;
71  for (unsigned i = 1; i <= 31; ++i) {
72  partitions.insert(i);
73  }
74  }
75 
76  for (auto& p : partitions) {
77  try {
78  auto partition = make_unique<DiskPartition>(
79  *wholeDisk, p, wholeDisk);
80  auto drive = createDiskChanger(
81  interface.basename, unsigned(drives.size()),
82  motherboard);
83  drive->changeDisk(unique_ptr<Disk>(std::move(partition)));
84  drives.push_back(std::move(drive));
85  } catch (MSXException&) {
86  if (failOnError) throw;
87  }
88  }
89 }
90 
91 string NowindCommand::execute(const vector<string>& tokens)
92 {
93  NowindHost& host = *interface.host;
94  NowindInterface::Drives& drives = interface.drives;
95  unsigned oldRomdisk = searchRomdisk(drives);
96 
97  if (tokens.size() == 1) {
98  // no arguments, show general status
99  assert(!drives.empty());
100  StringOp::Builder result;
101  for (unsigned i = 0; i < drives.size(); ++i) {
102  result << "nowind" << i + 1 << ": ";
103  if (dynamic_cast<NowindRomDisk*>(drives[i].get())) {
104  result << "romdisk\n";
105  } else if (auto changer = dynamic_cast<DiskChanger*>(
106  drives[i].get())) {
107  string filename = changer->getDiskName().getOriginal();
108  result << (filename.empty() ? "--empty--" : filename)
109  << '\n';
110  } else {
111  UNREACHABLE;
112  }
113  }
114  result << "phantom drives: "
115  << (host.getEnablePhantomDrives() ? "enabled" : "disabled")
116  << '\n';
117  result << "allow other diskroms: "
118  << (host.getAllowOtherDiskroms() ? "yes" : "no")
119  << '\n';
120  return result;
121  }
122 
123  // first parse complete commandline and store state in these local vars
124  bool enablePhantom = false;
125  bool disablePhantom = false;
126  bool allowOther = false;
127  bool disallowOther = false;
128  bool changeDrives = false;
129  unsigned romdisk = 255;
130  NowindInterface::Drives tmpDrives;
131  string error;
132 
133  // actually parse the commandline
134  deque<string> args(tokens.begin() + 1, tokens.end());
135  while (error.empty() && !args.empty()) {
136  bool createDrive = false;
137  string image;
138 
139  string arg = std::move(args.front());
140  args.pop_front();
141  if ((arg == "--ctrl") || (arg == "-c")) {
142  enablePhantom = false;
143  disablePhantom = true;
144  } else if ((arg == "--no-ctrl") || (arg == "-C")) {
145  enablePhantom = true;
146  disablePhantom = false;
147  } else if ((arg == "--allow") || (arg == "-a")) {
148  allowOther = true;
149  disallowOther = false;
150  } else if ((arg == "--no-allow") || (arg == "-A")) {
151  allowOther = false;
152  disallowOther = true;
153 
154  } else if ((arg == "--romdisk") || (arg == "-j")) {
155  if (romdisk != 255) {
156  error = "Can only have one romdisk";
157  } else {
158  romdisk = unsigned(tmpDrives.size());
159  tmpDrives.push_back(make_unique<NowindRomDisk>());
160  changeDrives = true;
161  }
162 
163  } else if ((arg == "--image") || (arg == "-i")) {
164  if (args.empty()) {
165  error = "Missing argument for option: " + arg;
166  } else {
167  image = std::move(args.front());
168  args.pop_front();
169  createDrive = true;
170  }
171 
172  } else if ((arg == "--hdimage") || (arg == "-m")) {
173  if (args.empty()) {
174  error = "Missing argument for option: " + arg;
175  } else {
176  try {
177  string hdimage = std::move(args.front());
178  args.pop_front();
179  processHdimage(hdimage, tmpDrives);
180  changeDrives = true;
181  } catch (MSXException& e) {
182  error = e.getMessage();
183  }
184  }
185 
186  } else {
187  // everything else is interpreted as an image name
188  image = arg;
189  createDrive = true;
190  }
191 
192  if (createDrive) {
193  auto drive = createDiskChanger(
194  interface.basename, unsigned(tmpDrives.size()),
195  interface.getMotherBoard());
196  changeDrives = true;
197  if (!image.empty()) {
198  if (drive->insertDisk(image)) {
199  error = "Invalid disk image: " + image;
200  }
201  }
202  tmpDrives.push_back(std::move(drive));
203  }
204  }
205  if (tmpDrives.size() > 8) {
206  error = "Can't have more than 8 drives";
207  }
208 
209  // if there was no error, apply the changes
210  bool optionsChanged = false;
211  if (error.empty()) {
212  if (enablePhantom && !host.getEnablePhantomDrives()) {
213  host.setEnablePhantomDrives(true);
214  optionsChanged = true;
215  }
216  if (disablePhantom && host.getEnablePhantomDrives()) {
217  host.setEnablePhantomDrives(false);
218  optionsChanged = true;
219  }
220  if (allowOther && !host.getAllowOtherDiskroms()) {
221  host.setAllowOtherDiskroms(true);
222  optionsChanged = true;
223  }
224  if (disallowOther && host.getAllowOtherDiskroms()) {
225  host.setAllowOtherDiskroms(false);
226  optionsChanged = true;
227  }
228  if (changeDrives) {
229  std::swap(tmpDrives, drives);
230  }
231  }
232 
233  // cleanup tmpDrives, this contains either
234  // - the old drives (when command was successful)
235  // - the new drives (when there was an error)
236  auto prevSize = tmpDrives.size();
237  tmpDrives.clear();
238  for (auto& d : drives) {
239  if (auto disk = dynamic_cast<DiskChanger*>(d.get())) {
240  disk->createCommand();
241  }
242  }
243 
244  if (!error.empty()) {
245  throw CommandException(error);
246  }
247 
248  // calculate result string
249  string result;
250  if (changeDrives && (prevSize != drives.size())) {
251  result += "Number of drives changed. ";
252  }
253  if (changeDrives && (romdisk != oldRomdisk)) {
254  if (oldRomdisk == 255) {
255  result += "Romdisk added. ";
256  } else if (romdisk == 255) {
257  result += "Romdisk removed. ";
258  } else {
259  result += "Romdisk changed position. ";
260  }
261  }
262  if (optionsChanged) {
263  result += "Boot options changed. ";
264  }
265  if (!result.empty()) {
266  result += "You may need to reset the MSX for the changes to take effect.";
267  }
268  return result;
269 }
270 
271 string NowindCommand::help(const vector<string>& /*tokens*/) const
272 {
273  return "Similar to the disk<x> commands there is a nowind<x> command "
274  "for each nowind interface. This command is modeled after the "
275  "'usbhost' command of the real nowind interface. Though only a "
276  "subset of the options is supported. Here's a short overview.\n"
277  "\n"
278  "Command line options\n"
279  " long short explanation\n"
280  "--image -i specify disk image\n"
281  "--hdimage -m specify harddisk image\n"
282  "--romdisk -j enable romdisk\n"
283  // "--flash -f update firmware\n"
284  "--ctrl -c no phantom disks\n"
285  "--no-ctrl -C enable phantom disks\n"
286  "--allow -a allow other diskroms to initialize\n"
287  "--no-allow -A don't allow other diskroms to initialize\n"
288  //"--dsk2rom -z converts a 360kB disk to romdisk.bin\n"
289  //"--debug -d enable libnowind debug info\n"
290  //"--test -t testmode\n"
291  //"--help -h help message\n"
292  "\n"
293  "If you don't pass any arguments to this command, you'll get "
294  "an overview of the current nowind status.\n"
295  "\n"
296  "This command will create a certain amount of drives on the "
297  "nowind interface and (optionally) insert diskimages in those "
298  "drives. For each of these drives there will also be a "
299  "'nowind<1..8>' command created. Those commands are similar to "
300  "e.g. the diska command. They can be used to access the more "
301  "advanced diskimage insertion options. See 'help nowind<1..8>' "
302  "for details.\n"
303  "\n"
304  "In some cases it is needed to reboot the MSX before the "
305  "changes take effect. In those cases you'll get a message "
306  "that warns about this.\n"
307  "\n"
308  "Examples:\n"
309  "nowinda -a image.dsk -j Image.dsk is inserted into drive A: and the romdisk\n"
310  " will be drive B:. Other diskroms will be able to\n"
311  " install drives as well. For example when the MSX has\n"
312  " an internal diskdrive, drive C: en D: will be\n"
313  " available as well.\n"
314  "nowinda disk1.dsk disk2.dsk The two images will be inserted in A: and B:\n"
315  " respectively.\n"
316  "nowinda -m hdimage.dsk Inserts a harddisk image. All available partitions\n"
317  " will be mounted as drives.\n"
318  "nowinda -m hdimage.dsk:1 Inserts the first partition only.\n"
319  "nowinda -m hdimage.dsk:2-4 Inserts the 2nd, 3th and 4th partition as drive A:\n"
320  " B: and C:.\n";
321 }
322 
323 void NowindCommand::tabCompletion(vector<string>& tokens) const
324 {
325  static const char* const extra[] = {
326  "-c", "--ctrl",
327  "-C", "--no-ctrl",
328  "-a", "--allow",
329  "-A", "--no-allow",
330  "-j", "--romdisk",
331  "-i", "--image",
332  "-m", "--hdimage",
333  };
334  completeFileName(tokens, UserFileContext(), extra);
335 }
336 
337 } // namespace openmsx