remove documentation-string reading hack
[bpt/emacs.git] / nt / cmdproxy.c
CommitLineData
8c012929 1/* Proxy shell designed for use with Emacs on Windows 95 and NT.
ba318903 2 Copyright (C) 1997, 2001-2014 Free Software Foundation, Inc.
8c012929 3
aecf7181 4 Accepts subset of Unix sh(1) command-line options, for compatibility
8c012929
GV
5 with elisp code written for Unix. When possible, executes external
6 programs directly (a common use of /bin/sh by Emacs), otherwise
7 invokes the user-specified command processor to handle built-in shell
8 commands, batch files and interactive mode.
9
10 The main function is simply to process the "-c string" option in the
11 way /bin/sh does, since the standard Windows command shells use the
12 convention that everything after "/c" (the Windows equivalent of
13 "-c") is the input string.
14
15This file is part of GNU Emacs.
16
eef0be9e 17GNU Emacs is free software: you can redistribute it and/or modify
8c012929 18it under the terms of the GNU General Public License as published by
eef0be9e
GM
19the Free Software Foundation, either version 3 of the License, or
20(at your option) any later version.
8c012929
GV
21
22GNU Emacs is distributed in the hope that it will be useful,
23but WITHOUT ANY WARRANTY; without even the implied warranty of
24MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25GNU General Public License for more details.
26
27You should have received a copy of the GNU General Public License
eef0be9e 28along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
8c012929
GV
29
30#include <windows.h>
31
32#include <stdarg.h> /* va_args */
33#include <malloc.h> /* alloca */
34#include <stdlib.h> /* getenv */
35#include <string.h> /* strlen */
9c88f339 36#include <ctype.h> /* isspace, isalpha */
8c012929 37
c5958e82
ÓF
38/* We don't want to include stdio.h because we are already duplicating
39 lots of it here */
40extern int _snprintf (char *buffer, size_t count, const char *format, ...);
8c012929
GV
41
42/******* Mock C library routines *********************************/
43
44/* These routines are used primarily to minimize the executable size. */
45
8c012929
GV
46#define stdout GetStdHandle (STD_OUTPUT_HANDLE)
47#define stderr GetStdHandle (STD_ERROR_HANDLE)
48
49int
0597ab06 50vfprintf (HANDLE hnd, const char * msg, va_list args)
8c012929
GV
51{
52 DWORD bytes_written;
53 char buf[1024];
54
55 wvsprintf (buf, msg, args);
56 return WriteFile (hnd, buf, strlen (buf), &bytes_written, NULL);
57}
58
59int
0597ab06 60fprintf (HANDLE hnd, const char * msg, ...)
8c012929
GV
61{
62 va_list args;
63 int rc;
64
65 va_start (args, msg);
66 rc = vfprintf (hnd, msg, args);
67 va_end (args);
68
69 return rc;
70}
71
72int
0597ab06 73printf (const char * msg, ...)
8c012929
GV
74{
75 va_list args;
76 int rc;
77
78 va_start (args, msg);
79 rc = vfprintf (stdout, msg, args);
80 va_end (args);
81
82 return rc;
83}
84
85void
0597ab06 86fail (const char * msg, ...)
8c012929
GV
87{
88 va_list args;
89
90 va_start (args, msg);
91 vfprintf (stderr, msg, args);
92 va_end (args);
93
52969220 94 exit (-1);
8c012929
GV
95}
96
97void
0597ab06 98warn (const char * msg, ...)
8c012929
GV
99{
100 va_list args;
101
102 va_start (args, msg);
103 vfprintf (stderr, msg, args);
104 va_end (args);
105}
106
107/******************************************************************/
108
109char *
110canon_filename (char *fname)
111{
112 char *p = fname;
113
114 while (*p)
115 {
116 if (*p == '/')
117 *p = '\\';
118 p++;
119 }
120
121 return fname;
122}
123
0597ab06
JB
124const char *
125skip_space (const char *str)
8c012929
GV
126{
127 while (isspace (*str)) str++;
128 return str;
129}
130
0597ab06
JB
131const char *
132skip_nonspace (const char *str)
8c012929
GV
133{
134 while (*str && !isspace (*str)) str++;
135 return str;
136}
137
138int escape_char = '\\';
139
140/* Get next token from input, advancing pointer. */
141int
0597ab06 142get_next_token (char * buf, const char ** pSrc)
8c012929 143{
0597ab06 144 const char * p = *pSrc;
8c012929
GV
145 char * o = buf;
146
147 p = skip_space (p);
148 if (*p == '"')
149 {
150 int escape_char_run = 0;
151
152 /* Go through src until an ending quote is found, unescaping
153 quotes along the way. If the escape char is not quote, then do
154 special handling of multiple escape chars preceding a quote
155 char (ie. the reverse of what Emacs does to escape quotes). */
156 p++;
157 while (1)
158 {
159 if (p[0] == escape_char && escape_char != '"')
160 {
161 escape_char_run++;
2afff93a 162 p++;
8c012929
GV
163 continue;
164 }
165 else if (p[0] == '"')
166 {
167 while (escape_char_run > 1)
168 {
169 *o++ = escape_char;
170 escape_char_run -= 2;
171 }
172
173 if (escape_char_run > 0)
174 {
175 /* escaped quote */
176 *o++ = *p++;
177 escape_char_run = 0;
178 }
179 else if (p[1] == escape_char && escape_char == '"')
180 {
181 /* quote escaped by doubling */
182 *o++ = *p;
183 p += 2;
184 }
185 else
186 {
187 /* The ending quote. */
188 *o = '\0';
189 /* Leave input pointer after token. */
190 p++;
191 break;
192 }
193 }
194 else if (p[0] == '\0')
195 {
196 /* End of string, but no ending quote found. We might want to
197 flag this as an error, but for now will consider the end as
198 the end of the token. */
199 *o = '\0';
200 break;
201 }
202 else
203 {
204 *o++ = *p++;
205 }
206 }
207 }
208 else
209 {
210 /* Next token is delimited by whitespace. */
0597ab06 211 const char * p1 = skip_nonspace (p);
8c012929
GV
212 memcpy (o, p, p1 - p);
213 o += (p1 - p);
baf7eeb5 214 *o = '\0';
8c012929
GV
215 p = p1;
216 }
217
218 *pSrc = p;
219
220 return o - buf;
221}
222
223/* Search for EXEC file in DIR. If EXEC does not have an extension,
224 DIR is searched for EXEC with the standard extensions appended. */
225int
0597ab06 226search_dir (const char *dir, const char *exec, int bufsize, char *buffer)
8c012929 227{
0597ab06 228 const char *exts[] = {".bat", ".cmd", ".exe", ".com"};
8c012929
GV
229 int n_exts = sizeof (exts) / sizeof (char *);
230 char *dummy;
231 int i, rc;
232
233 /* Search the directory for the program. */
177c0ea7 234 for (i = 0; i < n_exts; i++)
8c012929
GV
235 {
236 rc = SearchPath (dir, exec, exts[i], bufsize, buffer, &dummy);
237 if (rc > 0)
238 return rc;
239 }
240
241 return 0;
242}
243
177c0ea7 244/* Return the absolute name of executable file PROG, including
8c012929
GV
245 any file extensions. If an absolute name for PROG cannot be found,
246 return NULL. */
247char *
0597ab06 248make_absolute (const char *prog)
8c012929
GV
249{
250 char absname[MAX_PATH];
251 char dir[MAX_PATH];
252 char curdir[MAX_PATH];
0597ab06
JB
253 char *p, *path;
254 const char *fname;
8c012929
GV
255
256 /* At least partial absolute path specified; search there. */
257 if ((isalpha (prog[0]) && prog[1] == ':') ||
258 (prog[0] == '\\'))
259 {
260 /* Split the directory from the filename. */
261 fname = strrchr (prog, '\\');
262 if (!fname)
263 /* Only a drive specifier is given. */
264 fname = prog + 2;
265 strncpy (dir, prog, fname - prog);
266 dir[fname - prog] = '\0';
267
268 /* Search the directory for the program. */
269 if (search_dir (dir, prog, MAX_PATH, absname) > 0)
270 return strdup (absname);
271 else
272 return NULL;
273 }
274
177c0ea7 275 if (GetCurrentDirectory (MAX_PATH, curdir) <= 0)
8c012929
GV
276 return NULL;
277
278 /* Relative path; search in current dir. */
177c0ea7 279 if (strpbrk (prog, "\\"))
8c012929
GV
280 {
281 if (search_dir (curdir, prog, MAX_PATH, absname) > 0)
282 return strdup (absname);
177c0ea7 283 else
8c012929
GV
284 return NULL;
285 }
177c0ea7 286
8c012929
GV
287 /* Just filename; search current directory then PATH. */
288 path = alloca (strlen (getenv ("PATH")) + strlen (curdir) + 2);
289 strcpy (path, curdir);
290 strcat (path, ";");
291 strcat (path, getenv ("PATH"));
292
293 while (*path)
294 {
7ece6d40
EZ
295 size_t len;
296
8c012929
GV
297 /* Get next directory from path. */
298 p = path;
299 while (*p && *p != ';') p++;
7ece6d40
EZ
300 /* A broken PATH could have too long directory names in it. */
301 len = min (p - path, sizeof (dir) - 1);
302 strncpy (dir, path, len);
303 dir[len] = '\0';
8c012929
GV
304
305 /* Search the directory for the program. */
306 if (search_dir (dir, prog, MAX_PATH, absname) > 0)
307 return strdup (absname);
308
309 /* Move to the next directory. */
310 path = p + 1;
177c0ea7 311 }
8c012929
GV
312
313 return NULL;
314}
315
8f91bf93
DC
316/* Try to decode the given command line the way cmd would do it. On
317 success, return 1 with cmdline dequoted. Otherwise, when we've
318 found constructs only cmd can properly interpret, return 0 and
319 leave cmdline unchanged. */
320int
321try_dequote_cmdline (char* cmdline)
322{
323 /* Dequoting can only subtract characters, so the length of the
324 original command line is a bound on the amount of scratch space
325 we need. This length, in turn, is bounded by the 32k
c91c771d 326 CreateProcess limit. */
8f91bf93
DC
327 char * old_pos = cmdline;
328 char * new_cmdline = alloca (strlen(cmdline));
329 char * new_pos = new_cmdline;
330 char c;
331
332 enum {
333 NORMAL,
334 AFTER_CARET,
335 INSIDE_QUOTE
336 } state = NORMAL;
337
338 while ((c = *old_pos++))
339 {
340 switch (state)
341 {
342 case NORMAL:
343 switch(c)
344 {
345 case '"':
346 *new_pos++ = c;
347 state = INSIDE_QUOTE;
348 break;
349 case '^':
350 state = AFTER_CARET;
351 break;
352 case '<': case '>':
353 case '&': case '|':
354 case '(': case ')':
355 case '%': case '!':
356 /* We saw an unquoted shell metacharacter and we don't
357 understand it. Bail out. */
358 return 0;
359 default:
360 *new_pos++ = c;
361 break;
362 }
363 break;
364 case AFTER_CARET:
365 *new_pos++ = c;
366 state = NORMAL;
367 break;
368 case INSIDE_QUOTE:
fe9c230b
DC
369 switch (c)
370 {
371 case '"':
372 *new_pos++ = c;
373 state = NORMAL;
374 break;
375 case '%':
376 case '!':
377 /* Variable substitution inside quote. Bail out. */
378 return 0;
379 default:
380 *new_pos++ = c;
381 break;
382 }
8f91bf93
DC
383 break;
384 }
385 }
386
387 /* We were able to dequote the entire string. Copy our scratch
388 buffer on top of the original buffer and return success. */
389 memcpy (cmdline, new_cmdline, new_pos - new_cmdline);
390 cmdline[new_pos - new_cmdline] = '\0';
391 return 1;
392}
393
8c012929
GV
394/*****************************************************************/
395
396#if 0
397char ** _argv;
398int _argc;
399
400/* Parse commandline into argv array, allowing proper quoting of args. */
401void
402setup_argv (void)
403{
404 char * cmdline = GetCommandLine ();
405 int arg_bytes = 0;
406
177c0ea7 407
8c012929
GV
408}
409#endif
410
411/* Information about child proc is global, to allow for automatic
412 termination when interrupted. At the moment, only one child process
413 can be running at any one time. */
414
415PROCESS_INFORMATION child;
416int interactive = TRUE;
417
418BOOL
419console_event_handler (DWORD event)
420{
421 switch (event)
422 {
423 case CTRL_C_EVENT:
424 case CTRL_BREAK_EVENT:
425 if (!interactive)
426 {
fffa137c 427 /* Both command.com and cmd.exe have the annoying behavior of
8c012929
GV
428 prompting "Terminate batch job (y/n)?" when interrupted
429 while running a batch file, even if running in
430 non-interactive (-c) mode. Try to make up for this
431 deficiency by forcibly terminating the subprocess if
432 running non-interactively. */
433 if (child.hProcess &&
434 WaitForSingleObject (child.hProcess, 500) != WAIT_OBJECT_0)
435 TerminateProcess (child.hProcess, 0);
436 exit (STATUS_CONTROL_C_EXIT);
437 }
438 break;
439
440#if 0
441 default:
442 /* CLOSE, LOGOFF and SHUTDOWN events - actually we don't get these
443 under Windows 95. */
444 fail ("cmdproxy: received %d event\n", event);
445 if (child.hProcess)
446 TerminateProcess (child.hProcess, 0);
447#endif
448 }
449 return TRUE;
450}
451
52969220
GV
452/* Change from normal usage; return value indicates whether spawn
453 succeeded or failed - program return code is returned separately. */
8c012929 454int
0597ab06 455spawn (const char *progname, char *cmdline, const char *dir, int *retcode)
8c012929 456{
52969220 457 BOOL success = FALSE;
8c012929
GV
458 SECURITY_ATTRIBUTES sec_attrs;
459 STARTUPINFO start;
52969220
GV
460 /* In theory, passing NULL for the environment block to CreateProcess
461 is the same as passing the value of GetEnvironmentStrings, but
462 doing this explicitly seems to cure problems running DOS programs
463 in some cases. */
baf7eeb5 464 char * envblock = GetEnvironmentStrings ();
8c012929
GV
465
466 sec_attrs.nLength = sizeof (sec_attrs);
467 sec_attrs.lpSecurityDescriptor = NULL;
468 sec_attrs.bInheritHandle = FALSE;
177c0ea7 469
8c012929
GV
470 memset (&start, 0, sizeof (start));
471 start.cb = sizeof (start);
472
473 if (CreateProcess (progname, cmdline, &sec_attrs, NULL, TRUE,
d78e84f7 474 0, envblock, dir, &start, &child))
8c012929 475 {
52969220 476 success = TRUE;
8c012929
GV
477 /* wait for completion and pass on return code */
478 WaitForSingleObject (child.hProcess, INFINITE);
52969220
GV
479 if (retcode)
480 GetExitCodeProcess (child.hProcess, (DWORD *)retcode);
8c012929
GV
481 CloseHandle (child.hThread);
482 CloseHandle (child.hProcess);
483 child.hProcess = NULL;
484 }
485
baf7eeb5
GV
486 FreeEnvironmentStrings (envblock);
487
52969220 488 return success;
8c012929
GV
489}
490
baf7eeb5
GV
491/* Return size of current environment block. */
492int
7c3320d8 493get_env_size (void)
baf7eeb5
GV
494{
495 char * start = GetEnvironmentStrings ();
496 char * tmp = start;
497
498 while (tmp[0] || tmp[1])
499 ++tmp;
500 FreeEnvironmentStrings (start);
501 return tmp + 2 - start;
502}
503
8c012929
GV
504/******* Main program ********************************************/
505
506int
507main (int argc, char ** argv)
508{
509 int rc;
510 int need_shell;
511 char * cmdline;
512 char * progname;
513 int envsize;
baf7eeb5
GV
514 char **pass_through_args;
515 int num_pass_through_args;
8c012929
GV
516 char modname[MAX_PATH];
517 char path[MAX_PATH];
d78e84f7 518 char dir[MAX_PATH];
58b65bf5 519 int status;
8c012929
GV
520
521 interactive = TRUE;
522
523 SetConsoleCtrlHandler ((PHANDLER_ROUTINE) console_event_handler, TRUE);
524
d78e84f7
AI
525 if (!GetCurrentDirectory (sizeof (dir), dir))
526 fail ("error: GetCurrentDirectory failed\n");
527
8c012929
GV
528 /* We serve double duty: we can be called either as a proxy for the
529 real shell (that is, because we are defined to be the user shell),
530 or in our role as a helper application for running DOS programs.
531 In the former case, we interpret the command line options as if we
532 were a Unix shell, but in the latter case we simply pass our
533 command line to CreateProcess. We know which case we are dealing
534 with by whether argv[0] refers to ourself or to some other program.
535 (This relies on an arcane feature of CreateProcess, where we can
536 specify cmdproxy as the module to run, but specify a different
537 program in the command line - the MSVC startup code sets argv[0]
538 from the command line.) */
539
540 if (!GetModuleFileName (NULL, modname, sizeof (modname)))
baf7eeb5 541 fail ("error: GetModuleFileName failed\n");
8c012929 542
d78e84f7
AI
543 /* Change directory to location of .exe so startup directory can be
544 deleted. */
545 progname = strrchr (modname, '\\');
546 *progname = '\0';
547 SetCurrentDirectory (modname);
548 *progname = '\\';
549
7387d2a0
JR
550 /* Due to problems with interaction between API functions that use "OEM"
551 codepage vs API functions that use the "ANSI" codepage, we need to
552 make things consistent by choosing one and sticking with it. */
0597ab06
JB
553 SetConsoleCP (GetACP ());
554 SetConsoleOutputCP (GetACP ());
7387d2a0 555
8c012929
GV
556 /* Although Emacs always sets argv[0] to an absolute pathname, we
557 might get run in other ways as well, so convert argv[0] to an
58b65bf5 558 absolute name before comparing to the module name. */
a5e8ac59 559 path[0] = '\0';
58b65bf5
EZ
560 /* The call to SearchPath will find argv[0] in the current
561 directory, append ".exe" to it if needed, and also canonicalize
562 it, to resolve references to ".", "..", etc. */
563 status = SearchPath (NULL, argv[0], ".exe", sizeof (path), path,
564 &progname);
565 if (!(status > 0 && stricmp (modname, path) == 0))
8c012929 566 {
58b65bf5
EZ
567 if (status <= 0)
568 {
569 char *s;
570
571 /* Make sure we have argv[0] in path[], as the failed
572 SearchPath might not have copied it there. */
573 strcpy (path, argv[0]);
574 /* argv[0] could include forward slashes; convert them all
575 to backslashes, for strrchr calls below to DTRT. */
576 for (s = path; *s; s++)
577 if (*s == '/')
578 *s = '\\';
579 }
580 /* Perhaps MODNAME and PATH use mixed short and long file names. */
581 if (!(GetShortPathName (modname, modname, sizeof (modname))
582 && GetShortPathName (path, path, sizeof (path))
583 && stricmp (modname, path) == 0))
584 {
585 /* Sometimes GetShortPathName fails because one or more
586 directories leading to argv[0] have issues with access
587 rights. In that case, at least we can compare the
588 basenames. Note: this disregards the improbable case of
589 invoking a program of the same name from another
590 directory, since the chances of that other executable to
591 be both our namesake and a 16-bit DOS application are nil. */
592 char *p = strrchr (path, '\\');
593 char *q = strrchr (modname, '\\');
594 char *pdot, *qdot;
595
596 if (!p)
597 p = strchr (path, ':');
598 if (!p)
599 p = path;
600 else
601 p++;
602 if (!q)
603 q = strchr (modname, ':');
604 if (!q)
605 q = modname;
606 else
607 q++;
608
609 pdot = strrchr (p, '.');
610 if (!pdot || stricmp (pdot, ".exe") != 0)
611 pdot = p + strlen (p);
612 qdot = strrchr (q, '.');
613 if (!qdot || stricmp (qdot, ".exe") != 0)
614 qdot = q + strlen (q);
615 if (pdot - p != qdot - q || strnicmp (p, q, pdot - p) != 0)
616 {
617 /* We are being used as a helper to run a DOS app; just
618 pass command line to DOS app without change. */
619 /* TODO: fill in progname. */
620 if (spawn (NULL, GetCommandLine (), dir, &rc))
621 return rc;
622 fail ("Could not run %s\n", GetCommandLine ());
623 }
624 }
8c012929
GV
625 }
626
627 /* Process command line. If running interactively (-c or /c not
628 specified) then spawn a real command shell, passing it the command
629 line arguments.
630
631 If not running interactively, then attempt to execute the specified
632 command directly. If necessary, spawn a real shell to execute the
633 command.
634
635 */
636
637 progname = NULL;
638 cmdline = NULL;
639 /* If no args, spawn real shell for interactive use. */
640 need_shell = TRUE;
641 interactive = TRUE;
baf7eeb5
GV
642 /* Ask command.com to create an environment block with a reasonable
643 amount of free space. */
644 envsize = get_env_size () + 300;
0597ab06 645 pass_through_args = (char **) alloca (argc * sizeof (char *));
baf7eeb5 646 num_pass_through_args = 0;
8c012929
GV
647
648 while (--argc > 0)
649 {
650 ++argv;
baf7eeb5 651 /* Act on switches we recognize (mostly single letter switches,
aecf7181 652 except for -e); all unrecognized switches and extra args are
baf7eeb5
GV
653 passed on to real shell if used (only really of benefit for
654 interactive use, but allow for batch use as well). Accept / as
aecf7181 655 switch char for compatibility with cmd.exe. */
1d7d10d1 656 if (((*argv)[0] == '-' || (*argv)[0] == '/') && (*argv)[1] != '\0')
8c012929 657 {
1d7d10d1 658 if (((*argv)[1] == 'c' || (*argv)[1] == 'C') && ((*argv)[2] == '\0'))
8c012929
GV
659 {
660 if (--argc == 0)
baf7eeb5 661 fail ("error: expecting arg for %s\n", *argv);
8c012929
GV
662 cmdline = *(++argv);
663 interactive = FALSE;
664 }
1d7d10d1 665 else if (((*argv)[1] == 'i' || (*argv)[1] == 'I') && ((*argv)[2] == '\0'))
8c012929
GV
666 {
667 if (cmdline)
baf7eeb5 668 warn ("warning: %s ignored because of -c\n", *argv);
8c012929 669 }
f49f08cc 670 else if (((*argv)[1] == 'e' || (*argv)[1] == 'E') && ((*argv)[2] == ':'))
8c012929 671 {
baf7eeb5
GV
672 int requested_envsize = atoi (*argv + 3);
673 /* Enforce a reasonable minimum size, as above. */
674 if (requested_envsize > envsize)
675 envsize = requested_envsize;
676 /* For sanity, enforce a reasonable maximum. */
677 if (envsize > 32768)
678 envsize = 32768;
8c012929
GV
679 }
680 else
681 {
baf7eeb5
GV
682 /* warn ("warning: unknown option %s ignored", *argv); */
683 pass_through_args[num_pass_through_args++] = *argv;
8c012929
GV
684 }
685 }
686 else
687 break;
688 }
689
baf7eeb5
GV
690#if 0
691 /* I think this is probably not useful - cmd.exe ignores extra
692 (non-switch) args in interactive mode, and they cannot be passed on
693 when -c was given. */
694
695 /* Collect any remaining args after (initial) switches. */
696 while (argc-- > 0)
697 {
698 pass_through_args[num_pass_through_args++] = *argv++;
699 }
700#else
701 /* Probably a mistake for there to be extra args; not fatal. */
702 if (argc > 0)
ec025f9d 703 warn ("warning: extra args ignored after '%s'\n", argv[-1]);
baf7eeb5
GV
704#endif
705
706 pass_through_args[num_pass_through_args] = NULL;
707
8c012929
GV
708 /* If -c option, determine if we must spawn a real shell, or if we can
709 execute the command directly ourself. */
710 if (cmdline)
711 {
8f91bf93
DC
712 const char *args;
713
714 /* The program name is the first token of cmdline. Since
715 filenames cannot legally contain embedded quotes, the value
716 of escape_char doesn't matter. */
717 args = cmdline;
718 if (!get_next_token (path, &args))
719 fail ("error: no program name specified.\n");
720
721 canon_filename (path);
722 progname = make_absolute (path);
723
724 /* If we found the program and the rest of the command line does
725 not contain unquoted shell metacharacters, run the program
726 directly (if not found it might be an internal shell command,
727 so don't fail). */
728 if (progname != NULL && try_dequote_cmdline (cmdline))
729 need_shell = FALSE;
730 else
731 progname = NULL;
8c012929
GV
732 }
733
ec025f9d 734 pass_to_shell:
8c012929
GV
735 if (need_shell)
736 {
737 char * p;
baf7eeb5 738 int extra_arg_space = 0;
c5958e82 739 int maxlen, remlen;
8de1edce 740 int run_command_dot_com;
8c012929
GV
741
742 progname = getenv ("COMSPEC");
743 if (!progname)
baf7eeb5 744 fail ("error: COMSPEC is not set\n");
8c012929
GV
745
746 canon_filename (progname);
747 progname = make_absolute (progname);
748
749 if (progname == NULL || strchr (progname, '\\') == NULL)
baf7eeb5
GV
750 fail ("error: the program %s could not be found.\n", getenv ("COMSPEC"));
751
8de1edce
GV
752 /* Need to set environment size when running command.com. */
753 run_command_dot_com =
754 (stricmp (strrchr (progname, '\\'), "command.com") == 0);
755
baf7eeb5
GV
756 /* Work out how much extra space is required for
757 pass_through_args. */
758 for (argv = pass_through_args; *argv != NULL; ++argv)
759 /* We don't expect to have to quote switches. */
760 extra_arg_space += strlen (*argv) + 2;
8c012929
GV
761
762 if (cmdline)
763 {
baf7eeb5
GV
764 char * buf;
765
8c012929
GV
766 /* Convert to syntax expected by cmd.exe/command.com for
767 running non-interactively. Always quote program name in
768 case path contains spaces (fortunately it can't contain
769 quotes, since they are illegal in path names). */
baf7eeb5 770
c5958e82
ÓF
771 remlen = maxlen =
772 strlen (progname) + extra_arg_space + strlen (cmdline) + 16;
773 buf = p = alloca (maxlen + 1);
baf7eeb5
GV
774
775 /* Quote progname in case it contains spaces. */
c5958e82
ÓF
776 p += _snprintf (p, remlen, "\"%s\"", progname);
777 remlen = maxlen - (p - buf);
baf7eeb5
GV
778
779 /* Include pass_through_args verbatim; these are just switches
780 so should not need quoting. */
781 for (argv = pass_through_args; *argv != NULL; ++argv)
c5958e82
ÓF
782 {
783 p += _snprintf (p, remlen, " %s", *argv);
784 remlen = maxlen - (p - buf);
785 }
baf7eeb5 786
8de1edce 787 if (run_command_dot_com)
c5958e82 788 _snprintf (p, remlen, " /e:%d /c %s", envsize, cmdline);
ec025f9d 789 else
c5958e82 790 _snprintf (p, remlen, " /c %s", cmdline);
baf7eeb5 791 cmdline = buf;
8c012929
GV
792 }
793 else
794 {
8de1edce 795 if (run_command_dot_com)
ec025f9d
GV
796 {
797 /* Provide dir arg expected by command.com when first
8de1edce 798 started interactively (the "command search path"). To
ec025f9d
GV
799 avoid potential problems with spaces in command dir
800 (which cannot be quoted - command.com doesn't like it),
801 we always use the 8.3 form. */
802 GetShortPathName (progname, path, sizeof (path));
803 p = strrchr (path, '\\');
804 /* Trailing slash is acceptable, so always leave it. */
805 *(++p) = '\0';
806 }
807 else
ec025f9d 808 path[0] = '\0';
8c012929 809
c5958e82
ÓF
810 remlen = maxlen =
811 strlen (progname) + extra_arg_space + strlen (path) + 13;
812 cmdline = p = alloca (maxlen + 1);
8c012929
GV
813
814 /* Quote progname in case it contains spaces. */
c5958e82
ÓF
815 p += _snprintf (p, remlen, "\"%s\" %s", progname, path);
816 remlen = maxlen - (p - cmdline);
baf7eeb5
GV
817
818 /* Include pass_through_args verbatim; these are just switches
819 so should not need quoting. */
820 for (argv = pass_through_args; *argv != NULL; ++argv)
c5958e82
ÓF
821 {
822 p += _snprintf (p, remlen, " %s", *argv);
823 remlen = maxlen - (p - cmdline);
824 }
baf7eeb5 825
8de1edce 826 if (run_command_dot_com)
0597ab06 827 _snprintf (p, remlen, " /e:%d", envsize);
8c012929
GV
828 }
829 }
830
831 if (!progname)
832 fail ("Internal error: program name not defined\n");
833
834 if (!cmdline)
835 cmdline = progname;
836
d78e84f7 837 if (spawn (progname, cmdline, dir, &rc))
52969220 838 return rc;
8c012929 839
52969220
GV
840 if (!need_shell)
841 {
842 need_shell = TRUE;
843 goto pass_to_shell;
844 }
845
846 fail ("Could not run %s\n", progname);
847
848 return 0;
8c012929 849}