1//
2// Copyright (c) Microsoft. All rights reserved.
3// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4//
5
6//----------------------------------------------------------
7// CommandLine.cpp - tiny very specific command line parser
8//----------------------------------------------------------
9
10#include "standardpch.h"
11#include "commandline.h"
12#include "logging.h"
13#include "mclist.h"
14
15void CommandLine::DumpHelp(const char* program)
16{
17 printf("MCS is a utility for examining and manipulating SuperPMI MC files.\n");
18 printf("\n");
19 printf("Usage: %s [options] {verb} {verb options}", program);
20 printf("\n");
21 printf("Options:\n");
22 printf("\n");
23 printf(" -v[erbosity] messagetypes\n");
24 printf(" Controls which types of messages MCS logs. Specify a string of\n");
25 printf(" characters representing message categories to enable, where:\n");
26 printf(" e - errors (internal fatal errors that are non-recoverable)\n");
27 printf(" w - warnings (internal conditions that are unusual, but not serious)\n");
28 printf(" m - missing (failures due to missing JIT-EE interface details)\n");
29 printf(" n - information (notifications/summaries, e.g. 'Loaded 42, Saved 23')\n");
30 printf(" v - verbose (status messages, e.g. 'Jit startup took 151.12ms')\n");
31 printf(" d - debug (lots of detailed output)\n");
32 printf(" a - all (enable all message types; overrides other enable message types)\n");
33 printf(" q - quiet (disable all output; overrides all others)\n");
34 printf(" e.g. '-v ew' only writes error and warning messages to the console.\n");
35 printf(" 'q' takes precedence over any other message type specified.\n");
36 printf(" Default set of messages enabled is 'ewmnv'.\n");
37 printf("\n");
38 printf(" -writeLogFile logfile\n");
39 printf(" Write log messages to the specified file.\n");
40 printf("\n");
41 printf("Verbs:\n");
42 printf("\n");
43 printf(" -ASMDump {optional range} inputfile outputfile\n");
44 printf(" Dump out the asm file for each input methodContext.\n");
45 printf(" inputfile is read and output is written to outputfile.\n");
46 printf(" e.g. -ASMDump a.mc a.asm\n");
47 printf("\n");
48 printf(" -concat file1 file2\n");
49 printf(" Concatenate two files without regard to internal formatting.\n");
50 printf(" file2 is appended to file1.\n");
51 printf(" e.g. -concat a.mch b.mch\n");
52 printf("\n");
53 printf(" -copy range file1 file2\n");
54 printf(" Copy methodContext numbers in range from file1 to file2.\n");
55 printf(" file1 is read and file2 is written\n");
56 printf(" e.g. -copy a.mch b.mch\n");
57 printf("\n");
58 printf(" -dump {optional range} inputfile\n");
59 printf(" Dump details for each methodContext\n");
60 printf(" e.g. -dump a.mc\n");
61 printf("\n");
62 printf(" -dumpMap inputfile\n");
63 printf(" Dump a map from MC index to function name to the console, in CSV format\n");
64 printf(" e.g. -dumpMap a.mc\n");
65 printf("\n");
66 printf(" -dumpToc inputfile\n");
67 printf(" Dump a TOC file\n");
68 printf(" e.g. -dumpToc a.mct\n");
69 printf("\n");
70 printf(" -fracture range inputfile outputfile\n");
71 printf(" Break the input file into chunks sized by range.\n");
72 printf(" If '-thin' is also passed, CompileResults are stripped from the input file when written.\n");
73 printf(" e.g. '-fracture 3 a.mch b-' leads to b-0.mch, b-1.mch, etc., with 3 mc's in each file.\n");
74 printf("\n");
75 printf(" -ildump {optional range} inputfile\n");
76 printf(" Dump raw IL for each methodContext\n");
77 printf(" e.g. -ildump a.mc\n");
78 printf("\n");
79 printf(" -integ inputfile\n");
80 printf(" Check the integrity of each methodContext\n");
81 printf(" e.g. -integ a.mc\n");
82 printf("\n");
83 printf(" -merge outputfile pattern\n");
84 printf(" Merge all the input files matching the pattern.\n");
85 printf(" e.g. -merge a.mch *.mc\n");
86 printf(" e.g. -merge a.mch c:\\foo\\bar\\*.mc\n");
87 printf(" e.g. -merge a.mch relpath\\*.mc\n");
88 printf(" e.g. -merge a.mch .\n");
89 printf(" e.g. -merge a.mch onedir\n");
90 printf("\n");
91 printf(" -merge outputfile pattern -recursive\n");
92 printf(" Merge all the input files matching the pattern, in the specified and all child directories.\n");
93 printf(" e.g. -merge a.mch *.mc -recursive\n");
94 printf("\n");
95 printf(" -removeDup inputfile outputfile\n");
96 printf(" Copy methodContexts from inputfile to outputfile, skipping duplicates.\n");
97 printf(" e.g. -removeDup a.mc b.mc\n");
98 printf("\n");
99 printf(" -removeDup -legacy inputfile outputfile\n");
100 printf(" Copy methodContexts from inputfile to outputfile, skipping duplicates.\n");
101 printf(" Comparisons are performed using the legacy method and may take much longer\n");
102 printf(" e.g. -removeDup -legacy a.mc b.mc\n");
103 printf("\n");
104 printf(" -removeDup -thin inputfile outputfile\n");
105 printf(" Copy methodContexts from inputfile to outputfile, skipping duplicates.\n");
106 printf(" CompileResults are stripped from the input file when written.\n");
107 printf(" e.g. -removeDup -thin a.mc b.mc\n");
108 printf(" e.g. -removeDup -legacy -thin a.mc b.mc\n");
109 printf("\n");
110 printf(" -stat {optional range} inputfile outputfile\n");
111 printf(" Report various statistics per method context.\n");
112 printf(" inputfile is read and statistics are written into outputfile\n");
113 printf(" e.g. -stat a.mc a.csv\n");
114 printf("\n");
115 printf(" -strip range inputfile outputfile\n");
116 printf(" Copy method contexts from one file to another, skipping ranged items.\n");
117 printf(" inputfile is read and records not in range are written to outputfile.\n");
118 printf(" e.g. -strip 2 a.mc b.mc\n");
119 printf("\n");
120 printf(" -toc inputfile\n");
121 printf(" Create a Table of Contents file for inputfile to allow better random access\n");
122 printf(" to the mch file.\n");
123 printf(" e.g. '-toc a.mch' creates a.mch.mct\n");
124 printf("\n");
125 printf("Range descriptions are either a single number, or a text file with .mcl extension\n");
126 printf("containing a sorted list of line delimited numbers.\n");
127 printf(" e.g. -strip 2 a.mc b.mc\n");
128 printf(" e.g. -strip list.mcl a.mc b.mc\n");
129 printf("\n");
130 printf("Note: Inputs are case insensitive.\n");
131}
132
133// Assumption: All inputs are initialized to default or real value. we'll just set the stuff in what we see on the
134// command line. Assumption: Single byte names are passed in.. mb stuff doesnt cause an obvious problem... but it might
135// have issues... Assumption: Values larger than 2^31 aren't expressible from the commandline.... (atoi) Unless you pass
136// in negatives.. :-|
137bool CommandLine::Parse(int argc, char* argv[], /* OUT */ Options* o)
138{
139 size_t argLen = 0;
140 size_t tempLen = 0;
141
142 bool foundVerb = false;
143 bool foundFile1 = false;
144 bool foundFile2 = false;
145
146 if (argc == 1) // Print help when no args are passed
147 {
148 DumpHelp(argv[0]);
149 return false;
150 }
151
152 for (int i = 1; i < argc; i++)
153 {
154 bool isASwitch = (argv[i][0] == '-');
155#ifndef FEATURE_PAL
156 if (argv[i][0] == '/') // Also accept "/" on Windows
157 {
158 isASwitch = true;
159 }
160#endif // !FEATURE_PAL
161
162 // Process a switch
163 if (isASwitch)
164 {
165 argLen = strlen(argv[i]);
166
167 if (argLen > 1)
168 argLen--; // adjust for leading switch
169 else
170 {
171 DumpHelp(argv[0]);
172 return false;
173 }
174
175 if ((_strnicmp(&argv[i][1], "help", argLen) == 0) || (_strnicmp(&argv[i][1], "?", argLen) == 0))
176 {
177 DumpHelp(argv[0]);
178 return false;
179 }
180 else if ((_strnicmp(&argv[i][1], "ASMDump", argLen) == 0))
181 {
182 tempLen = strlen(argv[i]);
183 foundVerb = true;
184 o->actionASMDump = true;
185 if (i + 1 < argc) // Peek to see if we have an mcl file or an integer next
186 goto processMCL;
187 }
188 else if ((_strnicmp(&argv[i][1], "concat", argLen) == 0))
189 {
190 tempLen = strlen(argv[i]);
191 foundVerb = true;
192 o->actionConcat = true;
193 }
194 else if ((_strnicmp(&argv[i][1], "copy", argLen) == 0))
195 {
196 tempLen = strlen(argv[i]);
197 foundVerb = true;
198 o->actionCopy = true;
199 if (i + 1 < argc) // Peek to see if we have an mcl file or an integer next
200 goto processMCL;
201 }
202 else if ((_strnicmp(&argv[i][1], "dump", argLen) == 0))
203 {
204 tempLen = strlen(argv[i]);
205 foundVerb = true;
206 o->actionDump = true;
207 if (i + 1 < argc) // Peek to see if we have an mcl file or an integer next
208 goto processMCL;
209 }
210 else if ((_strnicmp(&argv[i][1], "fracture", argLen) == 0))
211 {
212 tempLen = strlen(argv[i]);
213 foundVerb = true;
214 o->actionFracture = true;
215 if (i + 1 < argc) // Peek to see if we have an mcl file or an integer next
216 goto processMCL;
217 }
218 else if ((_strnicmp(&argv[i][1], "dumpmap", argLen) == 0))
219 {
220 tempLen = strlen(argv[i]);
221 foundVerb = true;
222 o->actionDumpMap = true;
223 }
224 else if ((_strnicmp(&argv[i][1], "dumptoc", argLen) == 0))
225 {
226 tempLen = strlen(argv[i]);
227 foundVerb = true;
228 o->actionDumpToc = true;
229 }
230 else if ((_strnicmp(&argv[i][1], "ildump", argLen) == 0))
231 {
232 tempLen = strlen(argv[i]);
233 foundVerb = true;
234 o->actionILDump = true;
235 if (i + 1 < argc) // Peek to see if we have an mcl file or an integer next
236 goto processMCL;
237 }
238 else if ((_strnicmp(&argv[i][1], "merge", argLen) == 0))
239 {
240 tempLen = strlen(argv[i]);
241 foundVerb = true;
242 o->actionMerge = true;
243 }
244 else if ((_strnicmp(&argv[i][1], "recursive", argLen) == 0))
245 {
246 tempLen = strlen(argv[i]);
247 o->recursive = true;
248 }
249 else if ((_strnicmp(&argv[i][1], "toc", argLen) == 0))
250 {
251 tempLen = strlen(argv[i]);
252 foundVerb = true;
253 o->actionTOC = true;
254 }
255 else if ((_strnicmp(&argv[i][1], "input", argLen) == 0))
256 {
257 if (++i >= argc)
258 {
259 DumpHelp(argv[0]);
260 return false;
261 }
262
263 processInput:
264
265 tempLen = strlen(argv[i]);
266 if (tempLen == 0)
267 {
268 printf("ERROR: CommandLine::Parse() Arg '%s' is invalid, name of file missing.\n", argv[i]);
269 DumpHelp(argv[0]);
270 return false;
271 }
272 if (foundFile1 == false)
273 {
274 o->nameOfFile1 = new char[tempLen + 1];
275 strcpy_s(o->nameOfFile1, tempLen + 1, argv[i]);
276 foundFile1 = true;
277 }
278 else if (foundFile2 == false)
279 {
280 o->nameOfFile2 = new char[tempLen + 1];
281 strcpy_s(o->nameOfFile2, tempLen + 1, argv[i]);
282 foundFile2 = true;
283 }
284 else
285 {
286 printf("ERROR: CommandLine::Parse() Arg '%s' is invalid, too many files given.\n", argv[i]);
287 DumpHelp(argv[0]);
288 return false;
289 }
290 }
291 else if ((_strnicmp(&argv[i][1], "integ", argLen) == 0))
292 {
293 tempLen = strlen(argv[i]);
294 foundVerb = true;
295 o->actionInteg = true;
296 }
297 else if ((_strnicmp(&argv[i][1], "mcl", argLen) == 0))
298 {
299 if (i + 1 >= argc)
300 {
301 DumpHelp(argv[0]);
302 return false;
303 }
304
305 processMCL:
306 i++;
307 processMCL2:
308
309 bool isValidList = MCList::processArgAsMCL(argv[i], &o->indexCount, &o->indexes);
310 if (!isValidList)
311 i--;
312 }
313 else if ((_strnicmp(&argv[i][1], "removeDup", argLen) == 0))
314 {
315 tempLen = strlen(argv[i]);
316 foundVerb = true;
317 o->actionRemoveDup = true;
318 }
319 else if ((_strnicmp(&argv[i][1], "stat", argLen) == 0))
320 {
321 tempLen = strlen(argv[i]);
322 foundVerb = true;
323 o->actionStat = true;
324 if (i + 1 < argc) // Peek to see if we have an mcl file or an integer next
325 goto processMCL;
326 }
327 else if ((_strnicmp(&argv[i][1], "strip", argLen) == 0))
328 {
329 tempLen = strlen(argv[i]);
330 foundVerb = true;
331 o->actionStrip = true;
332 if (i + 1 < argc) // Peek to see if we have an mcl file or an integer next
333 goto processMCL;
334 }
335 else if ((_strnicmp(&argv[i][1], "thin", argLen) == 0))
336 {
337 o->stripCR = true;
338 }
339 else if ((_strnicmp(&argv[i][1], "legacy", argLen) == 0))
340 {
341 o->legacyCompare = true;
342 }
343 else if ((_strnicmp(&argv[i][1], "verbosity", argLen) == 0))
344 {
345 if (++i >= argc)
346 {
347 DumpHelp(argv[0]);
348 return false;
349 }
350
351 Logger::SetLogLevel(Logger::ParseLogLevelString(argv[i]));
352 }
353 else if ((_strnicmp(&argv[i][1], "writeLogFile", argLen) == 0))
354 {
355 if (++i >= argc)
356 {
357 DumpHelp(argv[0]);
358 return false;
359 }
360
361 Logger::OpenLogFile(argv[i]);
362 }
363 else
364 {
365 LogError("CommandLine::Parse() - Unknown verb '%s'", argv[i]);
366 DumpHelp(argv[0]);
367 return false;
368 }
369 }
370 // Process an input filename
371 else
372 {
373 char* lastdot = strrchr(argv[i], '.');
374 if (lastdot != nullptr)
375 {
376 if (_stricmp(lastdot, ".mcl") == 0)
377 goto processMCL2;
378 }
379 goto processInput;
380 }
381 }
382
383 if (o->recursive)
384 {
385 if (!o->actionMerge)
386 {
387 LogError("CommandLine::Parse() '-recursive' requires -merge.");
388 DumpHelp(argv[0]);
389 return false;
390 }
391 }
392
393 if (o->actionASMDump)
394 {
395 if ((!foundFile1) || (!foundFile2))
396 {
397 LogError("CommandLine::Parse() -ASMDump needs one input file and one output file.");
398 DumpHelp(argv[0]);
399 return false;
400 }
401 return true;
402 }
403 if (o->actionConcat)
404 {
405 if ((!foundFile1) || (!foundFile2))
406 {
407 LogError("CommandLine::Parse() '-concat' needs two input files (second will be used as output).");
408 DumpHelp(argv[0]);
409 return false;
410 }
411 return true;
412 }
413 if (o->actionMerge)
414 {
415 if ((!foundFile1) || (!foundFile2))
416 {
417 LogError("CommandLine::Parse() '-merge' needs an output file (the first) and a file pattern (the second).");
418 DumpHelp(argv[0]);
419 return false;
420 }
421 return true;
422 }
423 if (o->actionCopy)
424 {
425 if ((!foundFile1) || (!foundFile2))
426 {
427 LogError("CommandLine::Parse() '-copy' needs one input and one output.");
428 DumpHelp(argv[0]);
429 return false;
430 }
431 if (o->indexCount == 0)
432 {
433 LogError("CommandLine::Parse() -copy requires a range.");
434 DumpHelp(argv[0]);
435 return false;
436 }
437 return true;
438 }
439 if (o->actionDump)
440 {
441 if (!foundFile1)
442 {
443 LogError("CommandLine::Parse() '-dump' needs one input file, but didn't see one.");
444 DumpHelp(argv[0]);
445 return false;
446 }
447 return true;
448 }
449 if (o->actionFracture)
450 {
451 if ((!foundFile1) || (!foundFile2))
452 {
453 LogError("CommandLine::Parse() '-fracture' needs one input and one output.");
454 DumpHelp(argv[0]);
455 return false;
456 }
457 if (o->indexCount == 0)
458 {
459 LogError("CommandLine::Parse() -fracture requires a range.");
460 DumpHelp(argv[0]);
461 return false;
462 }
463 if (o->indexCount > 1)
464 {
465 LogWarning("CommandLine::Parse() -fracture found multiple ranges, we'll use the first one.");
466 }
467 return true;
468 }
469 if (o->actionDumpMap)
470 {
471 if (!foundFile1)
472 {
473 LogError("CommandLine::Parse() '-dumpMap' needs one input.");
474 DumpHelp(argv[0]);
475 return false;
476 }
477 return true;
478 }
479 if (o->actionDumpToc)
480 {
481 if (!foundFile1)
482 {
483 LogError("CommandLine::Parse() '-dumpToc' needs one input.");
484 DumpHelp(argv[0]);
485 return false;
486 }
487 return true;
488 }
489 if (o->actionILDump)
490 {
491 if (!foundFile1)
492 {
493 LogError("CommandLine::Parse() '-ildump' needs one input.");
494 DumpHelp(argv[0]);
495 return false;
496 }
497 return true;
498 }
499 if (o->actionTOC)
500 {
501 if (!foundFile1)
502 {
503 LogError("CommandLine::Parse() '-toc' needs one input.");
504 DumpHelp(argv[0]);
505 return false;
506 }
507 return true;
508 }
509 if (o->actionInteg)
510 {
511 if (!foundFile1)
512 {
513 LogError("CommandLine::Parse() '-integ' needs one input file, but didn't see one.");
514 DumpHelp(argv[0]);
515 return false;
516 }
517 return true;
518 }
519 if (o->actionRemoveDup)
520 {
521 if ((!foundFile1) || (!foundFile2))
522 {
523 LogError("CommandLine::Parse() -removeDup needs one input file and one output file.");
524 DumpHelp(argv[0]);
525 return false;
526 }
527 return true;
528 }
529 if (o->actionStat)
530 {
531 if ((!foundFile1) || (!foundFile2))
532 {
533 LogError("CommandLine::Parse() '-stat' needs one input file and one output file.");
534 DumpHelp(argv[0]);
535 return false;
536 }
537 return true;
538 }
539 if (o->actionStrip)
540 {
541 if ((!foundFile1) || (!foundFile2))
542 {
543 LogError("CommandLine::Parse() -strip needs one input file and one output file.");
544 DumpHelp(argv[0]);
545 return false;
546 }
547 if (o->indexCount == 0)
548 {
549 LogError("CommandLine::Parse() -strip requires a range.");
550 DumpHelp(argv[0]);
551 return false;
552 }
553 return true;
554 }
555
556 DumpHelp(argv[0]);
557 return false;
558}
559