Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

786 lines
17 KiB

  1. /*++
  2. Copyright (c) 1992-2000 Microsoft Corporation
  3. Module Name:
  4. doskey.cxx
  5. Abstract:
  6. Edits command lines, recalls Windows 2000 commands, and creates macros.
  7. Author:
  8. Norbert P. Kusters (norbertk) 02-April-1992
  9. Revision History:
  10. --*/
  11. #include "ulib.hxx"
  12. #include "error.hxx"
  13. #include "arg.hxx"
  14. #include "smsg.hxx"
  15. #include "rtmsg.h"
  16. #include "wstring.hxx"
  17. #include "path.hxx"
  18. #include "filestrm.hxx"
  19. #include "system.hxx"
  20. #include "file.hxx"
  21. #include "ulibcl.hxx"
  22. extern "C" {
  23. #include "conapi.h"
  24. #include <ctype.h>
  25. #ifdef FE_SB // isspace patch
  26. // isspace() causes access violation when x is Unicode char
  27. // that is not included in ASCII charset.
  28. #define isspace(x) ( (x) == ' ' ) ? TRUE : FALSE
  29. #endif
  30. };
  31. VOID
  32. StripQuotesFromString(
  33. IN PWSTRING String
  34. )
  35. /*++
  36. Routine Description:
  37. This routine removes leading and trailing quote marks (if
  38. present) from a quoted string. If the string is not a quoted
  39. string, it is left unchanged.
  40. --*/
  41. {
  42. if( String->QueryChCount() >= 2 &&
  43. String->QueryChAt( 0 ) == '\"' &&
  44. String->QueryChAt( String->QueryChCount() - 1 ) == '\"' ) {
  45. String->DeleteChAt( String->QueryChCount() - 1 );
  46. String->DeleteChAt( 0 );
  47. }
  48. }
  49. BOOLEAN
  50. DisplayPackedString(
  51. IN PCWSTR PackedStrings,
  52. IN ULONG PackedStringsLength,
  53. IN BOOLEAN IndentStrings,
  54. IN OUT PMESSAGE Message
  55. )
  56. /*++
  57. Routine Description:
  58. This routine outputs the given strings. One line per string.
  59. Arguments:
  60. PackedStrings - Supplies the null-separated strings to output.
  61. PackedStringsLength - Supplies the number of characters in the strings.
  62. NumIndentSpaces - Supplies whether or not to indent the strings.
  63. Message - Supplies the outlet for the strings.
  64. Return Value:
  65. FALSE - Failure.
  66. TRUE - Success.
  67. --*/
  68. {
  69. DSTRING display_string;
  70. DSTRING raw_string;
  71. ULONG i;
  72. PCWSTR p;
  73. p = PackedStrings;
  74. for (i = 0; i < PackedStringsLength; i++) {
  75. if (i > 0 && p[i - 1]) {
  76. continue;
  77. }
  78. if (IndentStrings) {
  79. if (!display_string.Initialize(" ")) {
  80. return FALSE;
  81. }
  82. } else {
  83. if (!display_string.Initialize("")) {
  84. return FALSE;
  85. }
  86. }
  87. if (!raw_string.Initialize(&p[i]) ||
  88. !display_string.Strcat(&raw_string)) {
  89. return FALSE;
  90. }
  91. Message->Set(MSG_ONE_STRING_NEWLINE);
  92. Message->Display("%W", &display_string);
  93. }
  94. return TRUE;
  95. }
  96. BOOLEAN
  97. QuerySourceAndTarget(
  98. IN PCWSTRING MacroLine,
  99. OUT PWSTR* Source,
  100. OUT PWSTR* Target
  101. )
  102. /*++
  103. Routine Description:
  104. This routine computes the sources and target string from the given macro line
  105. by isolating the '=' and then making sure that the source is a single token.
  106. Arguments:
  107. MacroLine - Supplies the macro.
  108. Source - Returns the source part of the macro.
  109. Target - Returns the target part of the macro.
  110. Return Value:
  111. FALSE - Failure.
  112. TRUE - Success.
  113. --*/
  114. {
  115. LONG src_start, src_end, dst_start;
  116. ULONG i, n;
  117. WCHAR w;
  118. *Source = NULL;
  119. *Target = NULL;
  120. i = 0;
  121. n = MacroLine->QueryChCount();
  122. for (; i < n; i++) {
  123. w = MacroLine->QueryChAt(i);
  124. if (!isspace(w)) {
  125. break;
  126. }
  127. }
  128. src_start = i;
  129. for (; i < n; i++) {
  130. w = MacroLine->QueryChAt(i);
  131. if (isspace(w) || w == '=') {
  132. break;
  133. }
  134. }
  135. src_end = i;
  136. if (src_start == src_end) {
  137. return FALSE;
  138. }
  139. for (; i < n; i++) {
  140. w = MacroLine->QueryChAt(i);
  141. if (!isspace(w)) {
  142. break;
  143. }
  144. }
  145. if (w != '=') {
  146. return FALSE;
  147. }
  148. i++;
  149. for (; i < n; i++) {
  150. w = MacroLine->QueryChAt(i);
  151. if (!isspace(w)) {
  152. break;
  153. }
  154. }
  155. dst_start = i;
  156. *Source = MacroLine->QueryWSTR(src_start, src_end - src_start);
  157. *Target = MacroLine->QueryWSTR(dst_start);
  158. if (!*Source || !*Target) {
  159. DELETE(*Source);
  160. DELETE(*Target);
  161. return FALSE;
  162. }
  163. return TRUE;
  164. }
  165. BOOLEAN
  166. DisplayMacros(
  167. IN PWSTR TargetExe,
  168. IN BOOLEAN IndentString,
  169. IN OUT PMESSAGE Message
  170. )
  171. /*++
  172. Routine Description:
  173. This routine displays the macros for the given exe.
  174. Arguments:
  175. TargetExe - Supplies the exe for which to display the macros.
  176. IndentStrings - Supplies whether or not to indent the macro strings.
  177. Message - Supplies an outlet for the macro strings.
  178. Return Value:
  179. FALSE - Failure.
  180. TRUE - Success.
  181. --*/
  182. {
  183. ULONG buffer_length = 0;
  184. PWSTR buffer;
  185. buffer_length = GetConsoleAliasesLength(TargetExe);
  186. if (!(buffer = (PWCHAR) MALLOC(buffer_length + 1))) {
  187. Message->Set(MSG_CHK_NO_MEMORY);
  188. Message->Display();
  189. return FALSE;
  190. }
  191. if (buffer_length) {
  192. buffer_length = GetConsoleAliases(buffer, buffer_length, TargetExe);
  193. }
  194. if (!DisplayPackedString(buffer, buffer_length/sizeof(WCHAR),
  195. IndentString, Message)) {
  196. Message->Set(MSG_CHK_NO_MEMORY);
  197. Message->Display();
  198. return FALSE;
  199. }
  200. FREE(buffer);
  201. return TRUE;
  202. }
  203. BOOLEAN
  204. DisplayAllMacros(
  205. IN OUT PMESSAGE Message
  206. )
  207. /*++
  208. Routine Description:
  209. This routine displays all of the macros for all of the exes which have macros
  210. defined for them.
  211. Arguments:
  212. Message - Supplies an outlet for the macros.
  213. Return Value:
  214. FALSE - Failure.
  215. TRUE - Success.
  216. --*/
  217. {
  218. PWSTR exes_buffer;
  219. ULONG exes_length;
  220. ULONG i;
  221. DSTRING exe_name;
  222. DSTRING display_name;
  223. exes_length = GetConsoleAliasExesLength();
  224. if (!(exes_buffer = (PWSTR) MALLOC(exes_length + 1))) {
  225. Message->Set(MSG_CHK_NO_MEMORY);
  226. Message->Display();
  227. return FALSE;
  228. }
  229. if (exes_length) {
  230. exes_length = GetConsoleAliasExes(exes_buffer, exes_length);
  231. }
  232. exes_length /= sizeof(WCHAR);
  233. for (i = 0; i < exes_length; i++) {
  234. if (i > 0 && exes_buffer[i - 1]) {
  235. continue;
  236. }
  237. if (!exe_name.Initialize(&exes_buffer[i]) ||
  238. !display_name.Initialize("[") ||
  239. !display_name.Strcat(&exe_name) ||
  240. !exe_name.Initialize("]") ||
  241. !display_name.Strcat(&exe_name)) {
  242. Message->Set(MSG_CHK_NO_MEMORY);
  243. Message->Display();
  244. return FALSE;
  245. }
  246. Message->Set(MSG_ONE_STRING_NEWLINE);
  247. Message->Display("%W", &display_name);
  248. if (!DisplayMacros(&exes_buffer[i], TRUE, Message)) {
  249. return FALSE;
  250. }
  251. Message->Set(MSG_BLANK_LINE);
  252. Message->Display();
  253. }
  254. return TRUE;
  255. }
  256. BOOLEAN
  257. ReadInMacrosFromFile(
  258. IN PPATH FilePath,
  259. IN PWSTR TargetExe,
  260. IN OUT PMESSAGE Message
  261. )
  262. /*++
  263. Routine Description:
  264. This routine reads in the macros in the given file. The file must have the same
  265. format as the output from DOSKEY /macros or DOSKEY /macros:all.
  266. Arguments:
  267. FilePath - Supplies the file with the macros.
  268. TargetExe - Supplies the exe for which the unclaimed macros are.
  269. Message - Supplies an outlet for messages.
  270. Return Value:
  271. FALSE - Failure.
  272. TRUE - Success.
  273. --*/
  274. {
  275. PFSN_FILE file;
  276. PFILE_STREAM file_stream;
  277. DSTRING line;
  278. PWSTR target_exe;
  279. DSTRING target_string;
  280. PWSTR source, target;
  281. if (!(file = SYSTEM::QueryFile(FilePath)) ||
  282. !(file_stream = file->QueryStream(READ_ACCESS))) {
  283. DELETE(file);
  284. Message->Set(MSG_ATTRIB_FILE_NOT_FOUND);
  285. Message->Display("%W", FilePath->GetPathString());
  286. return FALSE;
  287. }
  288. // Set up the target exe.
  289. if (!line.Initialize("") ||
  290. !target_string.Initialize(TargetExe) ||
  291. !(target_exe = target_string.QueryWSTR())) {
  292. Message->Set(MSG_CHK_NO_MEMORY);
  293. Message->Display();
  294. return FALSE;
  295. }
  296. while (!file_stream->IsAtEnd() && file_stream->ReadLine(&line)) {
  297. // First see if the current line will define a new exe name.
  298. if (!line.QueryChCount()) {
  299. continue;
  300. }
  301. if (line.QueryChAt(0) == '[' &&
  302. line.Strchr(']') != INVALID_CHNUM &&
  303. line.Strchr(']') > 1) {
  304. DELETE(target_exe);
  305. if (!target_string.Initialize(&line, 1, line.Strchr(']') - 1) ||
  306. !(target_exe = target_string.QueryWSTR())) {
  307. Message->Set(MSG_CHK_NO_MEMORY);
  308. Message->Display();
  309. return FALSE;
  310. }
  311. continue;
  312. }
  313. if (!QuerySourceAndTarget(&line, &source, &target) ||
  314. !AddConsoleAlias(source, *target ? target : NULL, target_exe)) {
  315. Message->Set(MSG_DOSKEY_INVALID_MACRO_DEFINITION);
  316. Message->Display();
  317. continue;
  318. }
  319. }
  320. DELETE(file_stream);
  321. DELETE(file);
  322. return TRUE;
  323. }
  324. int __cdecl
  325. main(
  326. )
  327. /*++
  328. Routine Description:
  329. This routine provides equivalent functionality to DOS 5's DOSKEY utility.
  330. Arguments:
  331. None.
  332. Return Value:
  333. 0 - Success.
  334. 1 - Failure.
  335. --*/
  336. {
  337. STREAM_MESSAGE msg;
  338. ARGUMENT_LEXEMIZER arglex;
  339. ARRAY lex_array;
  340. ARRAY arg_array;
  341. STRING_ARGUMENT progname;
  342. FLAG_ARGUMENT help;
  343. FLAG_ARGUMENT reinstall;
  344. LONG_ARGUMENT bufsize;
  345. LONG_ARGUMENT listsize;
  346. STRING_ARGUMENT m_plus;
  347. FLAG_ARGUMENT m;
  348. STRING_ARGUMENT macros_plus;
  349. FLAG_ARGUMENT macros;
  350. FLAG_ARGUMENT history;
  351. FLAG_ARGUMENT h;
  352. FLAG_ARGUMENT insert;
  353. FLAG_ARGUMENT overstrike;
  354. STRING_ARGUMENT exename;
  355. PATH_ARGUMENT filename;
  356. REST_OF_LINE_ARGUMENT macro;
  357. PWSTRING pwstring;
  358. PWSTR target_exe;
  359. PWSTR source, target;
  360. PWSTR buffer;
  361. ULONG buffer_length;
  362. DSTRING all_string;
  363. //
  364. // DOSKEY /INSERT | /OVERSTRIKE changes the keyboard mode
  365. // and we do not want the keyboard mode to be restored on exit
  366. //
  367. Get_Standard_Input_Stream()->DoNotRestoreConsoleMode();
  368. // Initialize the error stack and the stream message object.
  369. if (!msg.Initialize(Get_Standard_Output_Stream(),
  370. Get_Standard_Input_Stream())) {
  371. return 1;
  372. }
  373. // Initialize the parsing machinery.
  374. if (!lex_array.Initialize() ||
  375. !arg_array.Initialize() ||
  376. !arglex.Initialize(&lex_array)) {
  377. return 1;
  378. }
  379. arglex.SetCaseSensitive(FALSE);
  380. arglex.PutStartQuotes( "\"" );
  381. arglex.PutEndQuotes( "\"" );
  382. arglex.PutSeparators( " \t" );
  383. // Tokenize the command line.
  384. if (!arglex.PrepareToParse()) {
  385. return 1;
  386. }
  387. // Initialize the argument patterns to be accepted.
  388. if (!progname.Initialize("*") ||
  389. !help.Initialize("/?") ||
  390. !reinstall.Initialize("/reinstall") ||
  391. !bufsize.Initialize("/bufsize=*") ||
  392. !listsize.Initialize("/listsize=*") ||
  393. !macros_plus.Initialize("/macros:*") ||
  394. !m_plus.Initialize("/m:*") ||
  395. !macros.Initialize("/macros") ||
  396. !m.Initialize("/m") ||
  397. !history.Initialize("/history") ||
  398. !h.Initialize("/h") ||
  399. !insert.Initialize("/insert") ||
  400. !overstrike.Initialize("/overstrike") ||
  401. !exename.Initialize("/exename=*") ||
  402. !filename.Initialize("/macrofile=*") ||
  403. !macro.Initialize()) {
  404. return 1;
  405. }
  406. // Feed the arguments into the argument array.
  407. if (!arg_array.Put(&progname) ||
  408. !arg_array.Put(&help) ||
  409. !arg_array.Put(&reinstall) ||
  410. !arg_array.Put(&bufsize) ||
  411. !arg_array.Put(&listsize) ||
  412. !arg_array.Put(&macros_plus) ||
  413. !arg_array.Put(&m_plus) ||
  414. !arg_array.Put(&macros) ||
  415. !arg_array.Put(&m) ||
  416. !arg_array.Put(&history) ||
  417. !arg_array.Put(&h) ||
  418. !arg_array.Put(&insert) ||
  419. !arg_array.Put(&overstrike) ||
  420. !arg_array.Put(&exename) ||
  421. !arg_array.Put(&filename) ||
  422. !arg_array.Put(&macro)) {
  423. return 1;
  424. }
  425. // Parse the command line.
  426. if (!arglex.DoParsing(&arg_array)) {
  427. msg.Set(MSG_INVALID_PARAMETER);
  428. msg.Display("%W", pwstring = arglex.QueryInvalidArgument());
  429. DELETE(pwstring);
  430. return 1;
  431. }
  432. // Interpret the command line.
  433. if (bufsize.IsValueSet()) {
  434. msg.Set(MSG_DOSKEY_CANT_DO_BUFSIZE);
  435. msg.Display();
  436. }
  437. if (help.QueryFlag()) {
  438. msg.Set(MSG_DOSKEY_HELP);
  439. msg.Display();
  440. return 0;
  441. }
  442. if ((m.QueryFlag() || macros.QueryFlag()) &&
  443. (macros_plus.IsValueSet() || m_plus.IsValueSet()) ||
  444. (macros_plus.IsValueSet() && m_plus.IsValueSet())) {
  445. msg.Set(MSG_INCOMPATIBLE_PARAMETERS);
  446. msg.Display();
  447. return 1;
  448. }
  449. // Compute the target exe name.
  450. if (exename.IsValueSet()) {
  451. StripQuotesFromString( (PWSTRING)exename.GetString() );
  452. if (!(target_exe = exename.GetString()->QueryWSTR())) {
  453. return 1;
  454. }
  455. } else {
  456. target_exe = (PWSTR) L"cmd.exe";
  457. }
  458. // Interpret reinstall switch.
  459. if (reinstall.QueryFlag()) {
  460. ExpungeConsoleCommandHistory(target_exe);
  461. }
  462. // Interpret list size switch.
  463. if (listsize.IsValueSet()) {
  464. if (!SetConsoleNumberOfCommands(listsize.QueryLong(), target_exe)) {
  465. msg.Set(MSG_DOSKEY_CANT_SIZE_LIST);
  466. msg.Display();
  467. return 1;
  468. }
  469. }
  470. // Interpret insert and overstrike switches.
  471. if (insert.QueryFlag()) {
  472. SetConsoleCommandHistoryMode(0);
  473. }
  474. if (overstrike.QueryFlag()) {
  475. SetConsoleCommandHistoryMode(CONSOLE_OVERSTRIKE);
  476. }
  477. // Interpret the macro if any.
  478. if (macro.IsValueSet()) {
  479. if (!QuerySourceAndTarget(macro.GetRestOfLine(), &source, &target) ||
  480. !AddConsoleAlias(source, *target ? target : NULL, target_exe)) {
  481. msg.Set(MSG_DOSKEY_INVALID_MACRO_DEFINITION);
  482. msg.Display();
  483. }
  484. DELETE(source);
  485. DELETE(target);
  486. }
  487. // pull the stuff out from the file if provided.
  488. if (filename.IsValueSet()) {
  489. StripQuotesFromString((PWSTRING)filename.GetPath()->GetPathString());
  490. if (!ReadInMacrosFromFile(filename.GetPath(), target_exe, &msg)) {
  491. return 1;
  492. }
  493. }
  494. // Print out history buffer.
  495. if (history.QueryFlag() || h.QueryFlag()) {
  496. buffer_length = GetConsoleCommandHistoryLength(target_exe);
  497. if (!(buffer = (PWSTR) MALLOC(buffer_length))) {
  498. msg.Set(MSG_CHK_NO_MEMORY);
  499. msg.Display();
  500. return 1;
  501. }
  502. if (buffer_length) {
  503. buffer_length = GetConsoleCommandHistory(buffer,
  504. buffer_length,
  505. target_exe);
  506. }
  507. if (!DisplayPackedString(buffer, buffer_length/sizeof(WCHAR),
  508. FALSE, &msg)) {
  509. msg.Set(MSG_CHK_NO_MEMORY);
  510. msg.Display();
  511. return 1;
  512. }
  513. FREE(buffer);
  514. }
  515. // Echo macros for target_exe.
  516. if (macros.QueryFlag() || m.QueryFlag()) {
  517. if (!DisplayMacros(target_exe, FALSE, &msg)) {
  518. return 1;
  519. }
  520. }
  521. // Echo macros for specific exe.
  522. if (macros_plus.IsValueSet()) {
  523. StripQuotesFromString(macros_plus.GetString());
  524. if (!all_string.Initialize("all")) {
  525. return 1;
  526. }
  527. if (!macros_plus.GetString()->Stricmp(&all_string)) {
  528. if (!DisplayAllMacros(&msg)) {
  529. return 1;
  530. }
  531. } else {
  532. target_exe = macros_plus.GetString()->QueryWSTR();
  533. if (!DisplayMacros(target_exe, FALSE, &msg)) {
  534. return 1;
  535. }
  536. DELETE(target_exe);
  537. }
  538. }
  539. // Echo macros for specific exe.
  540. if (m_plus.IsValueSet()) {
  541. StripQuotesFromString(m_plus.GetString());
  542. if (!all_string.Initialize("all")) {
  543. return 1;
  544. }
  545. if (!m_plus.GetString()->Stricmp(&all_string)) {
  546. if (!DisplayAllMacros(&msg)) {
  547. return 1;
  548. }
  549. } else {
  550. target_exe = m_plus.GetString()->QueryWSTR();
  551. if (!DisplayMacros(target_exe, FALSE, &msg)) {
  552. return 1;
  553. }
  554. DELETE(target_exe);
  555. }
  556. }
  557. return 0;
  558. }