1/* Proxy shell designed for use with Emacs on Windows 95 and NT.
2   Copyright (C) 1997, 2001, 2002, 2003, 2004, 2005,
3      2006, 2007  Free Software Foundation, Inc.
4
5   Accepts subset of Unix sh(1) command-line options, for compatability
6   with elisp code written for Unix.  When possible, executes external
7   programs directly (a common use of /bin/sh by Emacs), otherwise
8   invokes the user-specified command processor to handle built-in shell
9   commands, batch files and interactive mode.
10
11   The main function is simply to process the "-c string" option in the
12   way /bin/sh does, since the standard Windows command shells use the
13   convention that everything after "/c" (the Windows equivalent of
14   "-c") is the input string.
15
16This file is part of GNU Emacs.
17
18GNU Emacs is free software; you can redistribute it and/or modify
19it under the terms of the GNU General Public License as published by
20the Free Software Foundation; either version 2, or (at your option)
21any later version.
22
23GNU Emacs is distributed in the hope that it will be useful,
24but WITHOUT ANY WARRANTY; without even the implied warranty of
25MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26GNU General Public License for more details.
27
28You should have received a copy of the GNU General Public License
29along with GNU Emacs; see the file COPYING.  If not, write to
30the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
31Boston, MA 02110-1301, USA.  */
32
33#include <windows.h>
34
35#include <stdarg.h>  /* va_args */
36#include <malloc.h>  /* alloca */
37#include <stdlib.h>  /* getenv */
38#include <string.h>  /* strlen */
39
40
41/*******  Mock C library routines  *********************************/
42
43/* These routines are used primarily to minimize the executable size.  */
44
45#define stdin  GetStdHandle (STD_INPUT_HANDLE)
46#define stdout GetStdHandle (STD_OUTPUT_HANDLE)
47#define stderr GetStdHandle (STD_ERROR_HANDLE)
48
49int
50vfprintf(HANDLE hnd, char * msg, va_list args)
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
60fprintf(HANDLE hnd, char * msg, ...)
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
73printf(char * msg, ...)
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
86fail (char * msg, ...)
87{
88  va_list args;
89
90  va_start (args, msg);
91  vfprintf (stderr, msg, args);
92  va_end (args);
93
94  exit (-1);
95}
96
97void
98warn (char * msg, ...)
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
124char *
125skip_space (char *str)
126{
127  while (isspace (*str)) str++;
128  return str;
129}
130
131char *
132skip_nonspace (char *str)
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
142get_next_token (char * buf, char ** pSrc)
143{
144  char * p = *pSrc;
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++;
162	      p++;
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.  */
211      char * p1 = skip_nonspace (p);
212      memcpy (o, p, p1 - p);
213      o += (p1 - p);
214      *o = '\0';
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
226search_dir (char *dir, char *exec, int bufsize, char *buffer)
227{
228  char *exts[] = {".bat", ".cmd", ".exe", ".com"};
229  int n_exts = sizeof (exts) / sizeof (char *);
230  char *dummy;
231  int i, rc;
232
233  /* Search the directory for the program.  */
234  for (i = 0; i < n_exts; i++)
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
244/* Return the absolute name of executable file PROG, including
245   any file extensions.  If an absolute name for PROG cannot be found,
246   return NULL.  */
247char *
248make_absolute (char *prog)
249{
250  char absname[MAX_PATH];
251  char dir[MAX_PATH];
252  char curdir[MAX_PATH];
253  char *p, *fname;
254  char *path;
255  int i;
256
257  /* At least partial absolute path specified; search there.  */
258  if ((isalpha (prog[0]) && prog[1] == ':') ||
259      (prog[0] == '\\'))
260    {
261      /* Split the directory from the filename.  */
262      fname = strrchr (prog, '\\');
263      if (!fname)
264	/* Only a drive specifier is given.  */
265	fname = prog + 2;
266      strncpy (dir, prog, fname - prog);
267      dir[fname - prog] = '\0';
268
269      /* Search the directory for the program.  */
270      if (search_dir (dir, prog, MAX_PATH, absname) > 0)
271	return strdup (absname);
272      else
273	return NULL;
274    }
275
276  if (GetCurrentDirectory (MAX_PATH, curdir) <= 0)
277    return NULL;
278
279  /* Relative path; search in current dir. */
280  if (strpbrk (prog, "\\"))
281    {
282      if (search_dir (curdir, prog, MAX_PATH, absname) > 0)
283	return strdup (absname);
284      else
285	return NULL;
286    }
287
288  /* Just filename; search current directory then PATH.  */
289  path = alloca (strlen (getenv ("PATH")) + strlen (curdir) + 2);
290  strcpy (path, curdir);
291  strcat (path, ";");
292  strcat (path, getenv ("PATH"));
293
294  while (*path)
295    {
296      /* Get next directory from path.  */
297      p = path;
298      while (*p && *p != ';') p++;
299      strncpy (dir, path, p - path);
300      dir[p - path] = '\0';
301
302      /* Search the directory for the program.  */
303      if (search_dir (dir, prog, MAX_PATH, absname) > 0)
304	return strdup (absname);
305
306      /* Move to the next directory.  */
307      path = p + 1;
308    }
309
310  return NULL;
311}
312
313/*****************************************************************/
314
315#if 0
316char ** _argv;
317int     _argc;
318
319/* Parse commandline into argv array, allowing proper quoting of args.  */
320void
321setup_argv (void)
322{
323  char * cmdline = GetCommandLine ();
324  int arg_bytes = 0;
325
326
327}
328#endif
329
330/* Information about child proc is global, to allow for automatic
331   termination when interrupted.  At the moment, only one child process
332   can be running at any one time.  */
333
334PROCESS_INFORMATION child;
335int interactive = TRUE;
336
337BOOL
338console_event_handler (DWORD event)
339{
340  switch (event)
341    {
342    case CTRL_C_EVENT:
343    case CTRL_BREAK_EVENT:
344      if (!interactive)
345	{
346	  /* Both command.com and cmd.exe have the annoying behaviour of
347	     prompting "Terminate batch job (y/n)?" when interrupted
348	     while running a batch file, even if running in
349	     non-interactive (-c) mode.  Try to make up for this
350	     deficiency by forcibly terminating the subprocess if
351	     running non-interactively.  */
352	  if (child.hProcess &&
353	      WaitForSingleObject (child.hProcess, 500) != WAIT_OBJECT_0)
354	    TerminateProcess (child.hProcess, 0);
355	  exit (STATUS_CONTROL_C_EXIT);
356	}
357      break;
358
359#if 0
360    default:
361      /* CLOSE, LOGOFF and SHUTDOWN events - actually we don't get these
362         under Windows 95.  */
363      fail ("cmdproxy: received %d event\n", event);
364      if (child.hProcess)
365	TerminateProcess (child.hProcess, 0);
366#endif
367    }
368  return TRUE;
369}
370
371/* Change from normal usage; return value indicates whether spawn
372   succeeded or failed - program return code is returned separately.  */
373int
374spawn (char * progname, char * cmdline, char * dir, int * retcode)
375{
376  BOOL success = FALSE;
377  SECURITY_ATTRIBUTES sec_attrs;
378  STARTUPINFO start;
379  /* In theory, passing NULL for the environment block to CreateProcess
380     is the same as passing the value of GetEnvironmentStrings, but
381     doing this explicitly seems to cure problems running DOS programs
382     in some cases.  */
383  char * envblock = GetEnvironmentStrings ();
384
385  sec_attrs.nLength = sizeof (sec_attrs);
386  sec_attrs.lpSecurityDescriptor = NULL;
387  sec_attrs.bInheritHandle = FALSE;
388
389  memset (&start, 0, sizeof (start));
390  start.cb = sizeof (start);
391
392  if (CreateProcess (progname, cmdline, &sec_attrs, NULL, TRUE,
393		     0, envblock, dir, &start, &child))
394  {
395    success = TRUE;
396    /* wait for completion and pass on return code */
397    WaitForSingleObject (child.hProcess, INFINITE);
398    if (retcode)
399      GetExitCodeProcess (child.hProcess, (DWORD *)retcode);
400    CloseHandle (child.hThread);
401    CloseHandle (child.hProcess);
402    child.hProcess = NULL;
403  }
404
405  FreeEnvironmentStrings (envblock);
406
407  return success;
408}
409
410/* Return size of current environment block.  */
411int
412get_env_size ()
413{
414  char * start = GetEnvironmentStrings ();
415  char * tmp = start;
416
417  while (tmp[0] || tmp[1])
418    ++tmp;
419  FreeEnvironmentStrings (start);
420  return  tmp + 2 - start;
421}
422
423/*******  Main program  ********************************************/
424
425int
426main (int argc, char ** argv)
427{
428  int rc;
429  int need_shell;
430  char * cmdline;
431  char * progname;
432  int envsize;
433  char **pass_through_args;
434  int num_pass_through_args;
435  char modname[MAX_PATH];
436  char path[MAX_PATH];
437  char dir[MAX_PATH];
438
439
440  interactive = TRUE;
441
442  SetConsoleCtrlHandler ((PHANDLER_ROUTINE) console_event_handler, TRUE);
443
444  if (!GetCurrentDirectory (sizeof (dir), dir))
445    fail ("error: GetCurrentDirectory failed\n");
446
447  /* We serve double duty: we can be called either as a proxy for the
448     real shell (that is, because we are defined to be the user shell),
449     or in our role as a helper application for running DOS programs.
450     In the former case, we interpret the command line options as if we
451     were a Unix shell, but in the latter case we simply pass our
452     command line to CreateProcess.  We know which case we are dealing
453     with by whether argv[0] refers to ourself or to some other program.
454     (This relies on an arcane feature of CreateProcess, where we can
455     specify cmdproxy as the module to run, but specify a different
456     program in the command line - the MSVC startup code sets argv[0]
457     from the command line.)  */
458
459  if (!GetModuleFileName (NULL, modname, sizeof (modname)))
460    fail ("error: GetModuleFileName failed\n");
461
462  /* Change directory to location of .exe so startup directory can be
463     deleted.  */
464  progname = strrchr (modname, '\\');
465  *progname = '\0';
466  SetCurrentDirectory (modname);
467  *progname = '\\';
468
469  /* Although Emacs always sets argv[0] to an absolute pathname, we
470     might get run in other ways as well, so convert argv[0] to an
471     absolute name before comparing to the module name.  Don't get
472     caught out by mixed short and long names.  */
473  GetShortPathName (modname, modname, sizeof (modname));
474  path[0] = '\0';
475  if (!SearchPath (NULL, argv[0], ".exe", sizeof (path), path, &progname)
476      || !GetShortPathName (path, path, sizeof (path))
477      || stricmp (modname, path) != 0)
478    {
479      /* We are being used as a helper to run a DOS app; just pass
480	 command line to DOS app without change.  */
481      /* TODO: fill in progname.  */
482      if (spawn (NULL, GetCommandLine (), dir, &rc))
483	return rc;
484      fail ("Could not run %s\n", GetCommandLine ());
485    }
486
487  /* Process command line.  If running interactively (-c or /c not
488     specified) then spawn a real command shell, passing it the command
489     line arguments.
490
491     If not running interactively, then attempt to execute the specified
492     command directly.  If necessary, spawn a real shell to execute the
493     command.
494
495  */
496
497  progname = NULL;
498  cmdline = NULL;
499  /* If no args, spawn real shell for interactive use.  */
500  need_shell = TRUE;
501  interactive = TRUE;
502  /* Ask command.com to create an environment block with a reasonable
503     amount of free space.  */
504  envsize = get_env_size () + 300;
505  pass_through_args = (char **) alloca (argc * sizeof(char *));
506  num_pass_through_args = 0;
507
508  while (--argc > 0)
509    {
510      ++argv;
511      /* Act on switches we recognize (mostly single letter switches,
512	 except for -e); all unrecognised switches and extra args are
513	 passed on to real shell if used (only really of benefit for
514	 interactive use, but allow for batch use as well).  Accept / as
515	 switch char for compatability with cmd.exe.  */
516      if (((*argv)[0] == '-' || (*argv)[0] == '/') && (*argv)[1] != '\0')
517	{
518	  if (((*argv)[1] == 'c' || (*argv)[1] == 'C') && ((*argv)[2] == '\0'))
519	    {
520	      if (--argc == 0)
521		fail ("error: expecting arg for %s\n", *argv);
522	      cmdline = *(++argv);
523	      interactive = FALSE;
524	    }
525	  else if (((*argv)[1] == 'i' || (*argv)[1] == 'I') && ((*argv)[2] == '\0'))
526	    {
527	      if (cmdline)
528		warn ("warning: %s ignored because of -c\n", *argv);
529	    }
530	  else if (((*argv)[1] == 'e' || (*argv)[1] == 'E') && ((*argv)[2] == ':'))
531	    {
532	      int requested_envsize = atoi (*argv + 3);
533	      /* Enforce a reasonable minimum size, as above.  */
534	      if (requested_envsize > envsize)
535		envsize = requested_envsize;
536	      /* For sanity, enforce a reasonable maximum.  */
537	      if (envsize > 32768)
538		envsize = 32768;
539	    }
540	  else
541	    {
542	      /* warn ("warning: unknown option %s ignored", *argv); */
543	      pass_through_args[num_pass_through_args++] = *argv;
544	    }
545	}
546      else
547	break;
548    }
549
550#if 0
551  /* I think this is probably not useful - cmd.exe ignores extra
552     (non-switch) args in interactive mode, and they cannot be passed on
553     when -c was given.  */
554
555  /* Collect any remaining args after (initial) switches.  */
556  while (argc-- > 0)
557    {
558      pass_through_args[num_pass_through_args++] = *argv++;
559    }
560#else
561  /* Probably a mistake for there to be extra args; not fatal.  */
562  if (argc > 0)
563    warn ("warning: extra args ignored after '%s'\n", argv[-1]);
564#endif
565
566  pass_through_args[num_pass_through_args] = NULL;
567
568  /* If -c option, determine if we must spawn a real shell, or if we can
569     execute the command directly ourself.  */
570  if (cmdline)
571    {
572      /* If no redirection or piping, and if program can be found, then
573	 run program directly.  Otherwise invoke a real shell. */
574
575      static char copout_chars[] = "|<>&";
576
577      if (strpbrk (cmdline, copout_chars) == NULL)
578	{
579 	  char *args;
580
581	  /* The program name is the first token of cmdline.  Since
582	     filenames cannot legally contain embedded quotes, the value
583	     of escape_char doesn't matter.  */
584	  args = cmdline;
585	  if (!get_next_token (path, &args))
586	    fail ("error: no program name specified.\n");
587
588	  canon_filename (path);
589	  progname = make_absolute (path);
590
591	  /* If we found the program, run it directly (if not found it
592             might be an internal shell command, so don't fail).  */
593	  if (progname != NULL)
594	    need_shell = FALSE;
595	}
596    }
597
598 pass_to_shell:
599  if (need_shell)
600    {
601      char * p;
602      int    extra_arg_space = 0;
603      int    run_command_dot_com;
604
605      progname = getenv ("COMSPEC");
606      if (!progname)
607	fail ("error: COMSPEC is not set\n");
608
609      canon_filename (progname);
610      progname = make_absolute (progname);
611
612      if (progname == NULL || strchr (progname, '\\') == NULL)
613	fail ("error: the program %s could not be found.\n", getenv ("COMSPEC"));
614
615      /* Need to set environment size when running command.com.  */
616      run_command_dot_com =
617	(stricmp (strrchr (progname, '\\'), "command.com") == 0);
618
619      /* Work out how much extra space is required for
620         pass_through_args.  */
621      for (argv = pass_through_args; *argv != NULL; ++argv)
622	/* We don't expect to have to quote switches.  */
623	extra_arg_space += strlen (*argv) + 2;
624
625      if (cmdline)
626	{
627	  char * buf;
628
629	  /* Convert to syntax expected by cmd.exe/command.com for
630	     running non-interactively.  Always quote program name in
631	     case path contains spaces (fortunately it can't contain
632	     quotes, since they are illegal in path names).  */
633
634	  buf = p = alloca (strlen (progname) + extra_arg_space +
635			    strlen (cmdline) + 16);
636
637	  /* Quote progname in case it contains spaces.  */
638	  p += wsprintf (p, "\"%s\"", progname);
639
640	  /* Include pass_through_args verbatim; these are just switches
641             so should not need quoting.  */
642	  for (argv = pass_through_args; *argv != NULL; ++argv)
643	    p += wsprintf (p, " %s", *argv);
644
645	  if (run_command_dot_com)
646	    wsprintf(p, " /e:%d /c %s", envsize, cmdline);
647	  else
648	    wsprintf(p, " /c %s", cmdline);
649	  cmdline = buf;
650	}
651      else
652	{
653	  if (run_command_dot_com)
654	    {
655	      /* Provide dir arg expected by command.com when first
656		 started interactively (the "command search path").  To
657		 avoid potential problems with spaces in command dir
658		 (which cannot be quoted - command.com doesn't like it),
659		 we always use the 8.3 form.  */
660	      GetShortPathName (progname, path, sizeof (path));
661	      p = strrchr (path, '\\');
662	      /* Trailing slash is acceptable, so always leave it.  */
663	      *(++p) = '\0';
664	    }
665	  else
666	    path[0] = '\0';
667
668	  cmdline = p = alloca (strlen (progname) + extra_arg_space +
669				strlen (path) + 13);
670
671	  /* Quote progname in case it contains spaces.  */
672	  p += wsprintf (p, "\"%s\" %s", progname, path);
673
674	  /* Include pass_through_args verbatim; these are just switches
675             so should not need quoting.  */
676	  for (argv = pass_through_args; *argv != NULL; ++argv)
677	    p += wsprintf (p, " %s", *argv);
678
679	  if (run_command_dot_com)
680	    wsprintf (p, " /e:%d", envsize);
681	}
682    }
683
684  if (!progname)
685    fail ("Internal error: program name not defined\n");
686
687  if (!cmdline)
688    cmdline = progname;
689
690  if (spawn (progname, cmdline, dir, &rc))
691    return rc;
692
693  if (!need_shell)
694    {
695      need_shell = TRUE;
696      goto pass_to_shell;
697    }
698
699  fail ("Could not run %s\n", progname);
700
701  return 0;
702}
703
704/* arch-tag: 88678d93-07ac-4e2f-ad63-d4a740ca69ac
705   (do not change this comment) */
706