Added Wine command-line interpreter.
[wine/wine-kai.git] / programs / wcmd / wcmdmain.c
blob4fc0d7233a0f6f571b2fc68fbbb97c85a042a73a
1 /*
2 * WCMD - Wine-compatible command line interface.
4 * (C) 1999 D A Pickles
5 */
7 /*
8 * FIXME:
9 * - No support for redirection, pipes, batch commands
10 * - 32-bit limit on file sizes in DIR command
11 * - Cannot handle parameters in quotes
12 * - Lots of functionality missing from builtins
15 #include "wcmd.h"
17 #ifdef WINELIB
18 /* external declaration here because we don't want to depend on Wine headers */
19 #ifdef __cplusplus
20 extern "C" HINSTANCE MAIN_WinelibInit( int *argc, char *argv[] );
21 #else
22 extern HINSTANCE MAIN_WinelibInit( int *argc, char *argv[] );
23 #endif
24 #endif /* WINELIB */
26 char *inbuilt[] = {"ATTRIB", "CALL", "CD", "CHDIR", "CLS", "COPY", "CTTY",
27 "DATE", "DEL", "DIR", "ECHO", "ERASE", "FOR", "GOTO",
28 "HELP", "IF", "LABEL", "MD", "MKDIR", "MOVE", "PATH", "PAUSE",
29 "PROMPT", "REM", "REN", "RENAME", "RD", "RMDIR", "SET", "SHIFT",
30 "TIME", "TYPE", "VERIFY", "VER", "VOL", "EXIT"};
32 HANDLE STDin, STDout;
33 HINSTANCE hinst;
34 int echo_mode = 1;
35 char nyi[] = "Not Yet Implemented\n\n";
36 char newline[] = "\n";
37 char version_string[] = "WCMD Version 0.10\n\n";
38 char anykey[] = "Press any key to continue: ";
39 char quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
41 /*****************************************************************************
42 * Main entry point. This is a console application so we have a main() not a
43 * winmain().
47 int main (int argc, char *argv[]) {
49 char string[1024], args[MAX_PATH], param[MAX_PATH];
50 int status, i;
51 DWORD count;
52 HANDLE h;
54 #ifdef WINELIB
55 if (!(hinst = MAIN_WinelibInit( &argc, argv ))) return 0;
56 #else
57 hinst = 0;
58 #endif
60 args[0] = param[0] = '\0';
61 if (argc > 1) {
62 for (i=1; i<argc; i++) {
63 if (argv[i][0] == '/') {
64 strcat (args, argv[i]);
66 else {
67 strcat (param, argv[i]);
68 strcat (param, " ");
74 * Allocate a console and set it up.
77 status = FreeConsole ();
78 if (!status) WCMD_print_error();
79 status = AllocConsole();
80 if (!status) WCMD_print_error();
81 STDout = GetStdHandle (STD_OUTPUT_HANDLE);
82 STDin = GetStdHandle (STD_INPUT_HANDLE);
83 SetConsoleMode (STDin, ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT |
84 ENABLE_PROCESSED_INPUT);
87 * Execute any command-line options.
90 if (strstr(args, "/q") != NULL) {
91 WCMD_echo ("OFF");
94 if (strstr(args, "/c") != NULL) {
95 WCMD_process_command (param);
96 return 0;
99 if (strstr(args, "/k") != NULL) {
100 WCMD_process_command (param);
104 * If there is an AUTOEXEC.BAT file, try to execute it.
107 GetFullPathName ("\\autoexec.bat", sizeof(string), string, NULL);
108 h = CreateFile (string, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
109 if (h != INVALID_HANDLE_VALUE) {
110 CloseHandle (h);
111 // WCMD_batch (string, " ");
115 * Loop forever getting commands and executing them.
118 WCMD_version ();
119 while (TRUE) {
120 WCMD_show_prompt ();
121 ReadFile (STDin, string, sizeof(string), &count, NULL);
122 if (count > 1) {
123 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
124 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
125 if (lstrlen (string) != 0) {
126 WCMD_process_command (string);
133 /*****************************************************************************
134 * Process one command. If the command is EXIT this routine does not return.
135 * We will recurse through here executing batch files.
139 void WCMD_process_command (char *command) {
141 char cmd[1024];
142 char *p;
143 int status, i;
144 DWORD count;
147 * Throw away constructs we don't support yet
150 if ((strchr(command,'<') != NULL) || (strchr(command,'>') != NULL)) {
151 WCMD_output ("Redirection not yet implemented\n");
152 return;
154 if (strchr(command,'|') != NULL) {
155 WCMD_output ("Pipes not yet implemented\n");
156 return;
160 * Expand up environment variables.
163 status = ExpandEnvironmentStrings (command, cmd, sizeof(cmd));
164 if (!status) {
165 WCMD_print_error ();
166 return;
170 * Changing default drive has to be handled as a special case.
173 if ((cmd[1] == ':') && IsCharAlpha (cmd[0]) && (strlen(cmd) == 2)) {
174 status = SetCurrentDirectory (cmd);
175 if (!status) WCMD_print_error ();
176 return;
178 WCMD_output (newline);
181 * Check if the command entered is internal. If it is, pass the rest of the
182 * line down to the command. If not try to run a program.
185 count = 0;
186 while (IsCharAlphaNumeric(cmd[count])) {
187 count++;
189 for (i=0; i<=WCMD_EXIT; i++) {
190 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
191 cmd, count, inbuilt[i], -1) == 2) break;
193 p = WCMD_strtrim_leading_spaces (&cmd[count]);
194 WCMD_parse (p, quals, param1, param2);
195 switch (i) {
197 case WCMD_ATTRIB:
198 WCMD_setshow_attrib ();
199 break;
200 case WCMD_CALL:
201 WCMD_batch (param1, p);
202 break;
203 case WCMD_CD:
204 case WCMD_CHDIR:
205 WCMD_setshow_default ();
206 break;
207 case WCMD_CLS:
208 WCMD_clear_screen ();
209 break;
210 case WCMD_COPY:
211 WCMD_copy ();
212 break;
213 case WCMD_CTTY:
214 WCMD_change_tty ();
215 break;
216 case WCMD_DATE:
217 WCMD_setshow_date ();
218 break;
219 case WCMD_DEL:
220 case WCMD_ERASE:
221 WCMD_delete (0);
222 break;
223 case WCMD_DIR:
224 WCMD_directory ();
225 break;
226 case WCMD_ECHO:
227 WCMD_echo (p);
228 break;
229 case WCMD_FOR:
230 WCMD_for ();
231 break;
232 case WCMD_GOTO:
233 break;
234 case WCMD_HELP:
235 WCMD_give_help (p);
236 break;
237 case WCMD_IF:
238 WCMD_if ();
239 break;
240 case WCMD_LABEL:
241 WCMD_volume (1, p);
242 break;
243 case WCMD_MD:
244 case WCMD_MKDIR:
245 WCMD_create_dir ();
246 break;
247 case WCMD_MOVE:
248 WCMD_move ();
249 break;
250 case WCMD_PATH:
251 WCMD_setshow_path ();
252 break;
253 case WCMD_PAUSE:
254 WCMD_pause ();
255 break;
256 case WCMD_PROMPT:
257 WCMD_setshow_prompt ();
258 break;
259 case WCMD_REM:
260 break;
261 case WCMD_REN:
262 case WCMD_RENAME:
263 WCMD_rename ();
264 break;
265 case WCMD_RD:
266 case WCMD_RMDIR:
267 WCMD_remove_dir ();
268 break;
269 case WCMD_SET:
270 WCMD_setshow_env (p);
271 break;
272 case WCMD_SHIFT:
273 WCMD_shift ();
274 break;
275 case WCMD_TIME:
276 WCMD_setshow_time ();
277 break;
278 case WCMD_TYPE:
279 WCMD_type ();
280 break;
281 case WCMD_VER:
282 WCMD_version ();
283 break;
284 case WCMD_VERIFY:
285 WCMD_verify ();
286 break;
287 case WCMD_VOL:
288 WCMD_volume (0, p);
289 break;
290 case WCMD_EXIT:
291 ExitProcess (0);
292 default:
293 WCMD_run_program (cmd);
297 /******************************************************************************
298 * WCMD_run_program
300 * Execute a command line as an external program. If no extension given then
301 * precedence is given to .BAT files. Must allow recursion.
303 * FIXME: Case sensitivity in suffixes!
306 void WCMD_run_program (char *command) {
308 STARTUPINFO st;
309 PROCESS_INFORMATION pe;
310 BOOL status;
311 HANDLE h;
312 char filetorun[MAX_PATH];
314 WCMD_parse (command, quals, param1, param2); /* Quick way to get the filename */
315 if (strpbrk (param1, "\\:") == NULL) { /* No explicit path given */
316 if ((strchr (param1, '.') == NULL) || (strstr (param1, ".bat") != NULL)) {
317 if (SearchPath (NULL, param1, ".bat", sizeof(filetorun), filetorun, NULL)) {
318 WCMD_batch (filetorun, command);
319 return;
323 else { /* Explicit path given */
324 if (strstr (param1, ".bat") != NULL) {
325 WCMD_batch (param1, command);
326 return;
328 if (strchr (param1, '.') == NULL) {
329 strcpy (filetorun, param1);
330 strcat (filetorun, ".bat");
331 h = CreateFile (filetorun, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
332 if (h != INVALID_HANDLE_VALUE) {
333 CloseHandle (h);
334 WCMD_batch (param1, command);
335 return;
340 /* No batch file found, assume executable */
342 ZeroMemory (&st, sizeof(STARTUPINFO));
343 st.cb = sizeof(STARTUPINFO);
344 status = CreateProcess (NULL, command, NULL, NULL, FALSE,
345 0, NULL, NULL, &st, &pe);
346 if (!status) {
347 WCMD_print_error ();
351 /******************************************************************************
352 * WCMD_show_prompt
354 * Display the prompt on STDout
358 void WCMD_show_prompt () {
360 int status;
361 char out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
362 char *p, *q;
364 status = GetEnvironmentVariable ("PROMPT", prompt_string, sizeof(prompt_string));
365 if ((status == 0) || (status > sizeof(prompt_string))) {
366 lstrcpy (prompt_string, "$N$G");
368 p = prompt_string;
369 q = out_string;
370 *q = '\0';
371 while (*p != '\0') {
372 if (*p != '$') {
373 *q++ = *p++;
374 *q = '\0';
376 else {
377 p++;
378 switch (toupper(*p)) {
379 case '$':
380 *q++ = '$';
381 break;
382 case 'B':
383 *q++ = '|';
384 break;
385 case 'D':
386 GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
387 while (*q) q++;
388 break;
389 case 'E':
390 *q++ = '\E';
391 break;
392 case 'G':
393 *q++ = '>';
394 break;
395 case 'L':
396 *q++ = '<';
397 break;
398 case 'N':
399 status = GetCurrentDirectory (sizeof(curdir), curdir);
400 if (status) {
401 *q++ = curdir[0];
403 break;
404 case 'P':
405 status = GetCurrentDirectory (sizeof(curdir), curdir);
406 if (status) {
407 lstrcat (q, curdir);
408 while (*q) q++;
410 break;
411 case 'Q':
412 *q++ = '=';
413 break;
414 case 'T':
415 GetTimeFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
416 while (*q) q++;
417 break;
418 case '_':
419 *q++ = '\n';
420 break;
422 p++;
423 *q = '\0';
426 WCMD_output (out_string);
429 /****************************************************************************
430 * WCMD_print_error
432 * Print the message for GetLastError - not much use yet as Wine doesn't have
433 * the messages available, so we show meaningful messages for the most likely.
436 void WCMD_print_error () {
437 LPVOID lpMsgBuf;
438 DWORD error_code;
440 error_code = GetLastError ();
441 switch (error_code) {
442 case ERROR_FILE_NOT_FOUND:
443 WCMD_output ("File Not Found\n");
444 break;
445 case ERROR_PATH_NOT_FOUND:
446 WCMD_output ("Path Not Found\n");
447 break;
448 default:
449 FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
450 NULL, error_code,
451 MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),
452 (LPTSTR) &lpMsgBuf, 0, NULL);
453 WCMD_output (lpMsgBuf);
454 LocalFree ((HLOCAL)lpMsgBuf);
456 return;
459 /*******************************************************************
460 * WCMD_parse - parse a command into parameters and qualifiers.
462 * On exit, all qualifiers are concatenated into q, the first string
463 * not beginning with "/" is in p1 and the
464 * second in p2. Any subsequent non-qualifier strings are lost.
465 * Parameters in quotes are handled.
468 void WCMD_parse (char *s, char *q, char *p1, char *p2) {
470 int p = 0;
472 *q = *p1 = *p2 = '\0';
473 while (TRUE) {
474 switch (*s) {
475 case '/':
476 *q++ = *s++;
477 while ((*s != '\0') && (*s != ' ') && *s != '/') {
478 *q++ = toupper (*s++);
480 *q = '\0';
481 break;
482 case ' ':
483 s++;
484 break;
485 case '"':
486 s++;
487 while ((*s != '\0') && (*s != '"')) {
488 if (p == 0) *p1++ = *s++;
489 else if (p == 1) *p2++ = *s++;
490 else s++;
492 if (p == 0) *p1 = '\0';
493 if (p == 1) *p2 = '\0';
494 p++;
495 if (*s == '"') s++;
496 break;
497 case '\0':
498 return;
499 default:
500 while ((*s != '\0') && (*s != ' ') && (*s != '/')) {
501 if (p == 0) *p1++ = *s++;
502 else if (p == 1) *p2++ = *s++;
503 else s++;
505 if (p == 0) *p1 = '\0';
506 if (p == 1) *p2 = '\0';
507 p++;
512 /*******************************************************************
513 * WCMD_output - send output to current standard output device.
517 void WCMD_output (char *format, ...) {
519 va_list ap;
520 char string[1024];
521 DWORD count;
523 va_start(ap,format);
524 vsprintf (string, format, ap);
525 WriteFile (STDout, string, lstrlen(string), &count, NULL);
526 va_end(ap);
530 /* Remove leading spaces from a string. Return a pointer to the first
531 * non-space character. Does not modify the input string
534 char *WCMD_strtrim_leading_spaces (char *string) {
536 char *ptr;
538 ptr = string;
539 while (*ptr == ' ') ptr++;
540 return ptr;
543 /* Remove trailing spaces from a string. This routine modifies the input
544 * string by placing a null after the last non-space character
547 void WCMD_strtrim_trailing_spaces (char *string) {
549 char *ptr;
551 ptr = string + lstrlen (string) - 1;
552 while ((*ptr == ' ') && (ptr >= string)) {
553 *ptr = '\0';
554 ptr--;