cmdproxy.c 25.9 KB
Newer Older
Geoff Voelker's avatar
Geoff Voelker committed
1
/* Proxy shell designed for use with Emacs on Windows 95 and NT.
Paul Eggert's avatar
Paul Eggert committed
2
   Copyright (C) 1997, 2001-2020 Free Software Foundation, Inc.
Geoff Voelker's avatar
Geoff Voelker committed
3

Glenn Morris's avatar
Glenn Morris committed
4
   Accepts subset of Unix sh(1) command-line options, for compatibility
Geoff Voelker's avatar
Geoff Voelker committed
5 6 7 8 9 10 11 12 13 14 15 16
   with elisp code written for Unix.  When possible, executes external
   programs directly (a common use of /bin/sh by Emacs), otherwise
   invokes the user-specified command processor to handle built-in shell
   commands, batch files and interactive mode.

   The main function is simply to process the "-c string" option in the
   way /bin/sh does, since the standard Windows command shells use the
   convention that everything after "/c" (the Windows equivalent of
   "-c") is the input string.

This file is part of GNU Emacs.

17
GNU Emacs is free software: you can redistribute it and/or modify
Geoff Voelker's avatar
Geoff Voelker committed
18
it under the terms of the GNU General Public License as published by
19 20
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
Geoff Voelker's avatar
Geoff Voelker committed
21 22 23 24 25 26 27

GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
Paul Eggert's avatar
Paul Eggert committed
28
along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
Geoff Voelker's avatar
Geoff Voelker committed
29 30 31 32 33 34 35

#include <windows.h>

#include <stdarg.h>  /* va_args */
#include <malloc.h>  /* alloca */
#include <stdlib.h>  /* getenv */
#include <string.h>  /* strlen */
36
#include <ctype.h>   /* isspace, isalpha */
Geoff Voelker's avatar
Geoff Voelker committed
37

38 39 40
/* We don't want to include stdio.h because we are already duplicating
   lots of it here */
extern int _snprintf (char *buffer, size_t count, const char *format, ...);
Geoff Voelker's avatar
Geoff Voelker committed
41 42 43 44 45 46 47 48

/*******  Mock C library routines  *********************************/

/* These routines are used primarily to minimize the executable size.  */

#define stdout GetStdHandle (STD_OUTPUT_HANDLE)
#define stderr GetStdHandle (STD_ERROR_HANDLE)

49 50 51 52 53 54 55 56 57 58
#if __GNUC__ + (__GNUC_MINOR__ >= 4) >= 5
void fail (const char *, ...) __attribute__((noreturn));
#else
void fail (const char *, ...);
#endif
int vfprintf (HANDLE, const char *, va_list);
int fprintf (HANDLE, const char *, ...);
int printf (const char *, ...);
void warn (const char *, ...);

Geoff Voelker's avatar
Geoff Voelker committed
59
int
60
vfprintf (HANDLE hnd, const char * msg, va_list args)
Geoff Voelker's avatar
Geoff Voelker committed
61 62 63 64 65 66 67 68 69
{
  DWORD bytes_written;
  char buf[1024];

  wvsprintf (buf, msg, args);
  return WriteFile (hnd, buf, strlen (buf), &bytes_written, NULL);
}

int
70
fprintf (HANDLE hnd, const char * msg, ...)
Geoff Voelker's avatar
Geoff Voelker committed
71 72 73 74 75 76 77 78 79 80 81 82
{
  va_list args;
  int rc;

  va_start (args, msg);
  rc = vfprintf (hnd, msg, args);
  va_end (args);

  return rc;
}

int
83
printf (const char * msg, ...)
Geoff Voelker's avatar
Geoff Voelker committed
84 85 86 87 88 89 90 91 92 93 94 95
{
  va_list args;
  int rc;

  va_start (args, msg);
  rc = vfprintf (stdout, msg, args);
  va_end (args);

  return rc;
}

void
96
fail (const char * msg, ...)
Geoff Voelker's avatar
Geoff Voelker committed
97 98 99 100 101 102 103
{
  va_list args;

  va_start (args, msg);
  vfprintf (stderr, msg, args);
  va_end (args);

104
  exit (-1);
Geoff Voelker's avatar
Geoff Voelker committed
105 106 107
}

void
108
warn (const char * msg, ...)
Geoff Voelker's avatar
Geoff Voelker committed
109 110 111 112 113 114 115 116 117 118
{
  va_list args;

  va_start (args, msg);
  vfprintf (stderr, msg, args);
  va_end (args);
}

/******************************************************************/

119
static char *
Geoff Voelker's avatar
Geoff Voelker committed
120 121 122 123 124 125 126 127 128 129 130 131 132 133
canon_filename (char *fname)
{
  char *p = fname;

  while (*p)
    {
      if (*p == '/')
	*p = '\\';
      p++;
    }

  return fname;
}

134
static const char *
135
skip_space (const char *str)
Geoff Voelker's avatar
Geoff Voelker committed
136 137 138 139 140
{
  while (isspace (*str)) str++;
  return str;
}

141
static const char *
142
skip_nonspace (const char *str)
Geoff Voelker's avatar
Geoff Voelker committed
143 144 145 146 147
{
  while (*str && !isspace (*str)) str++;
  return str;
}

148 149 150 151
/* This value is never changed by the code.  We keep the code that
   supports also the value of '"', but let's allow the compiler to
   optimize it out, until someone actually uses that.  */
const int escape_char = '\\';
Geoff Voelker's avatar
Geoff Voelker committed
152 153

/* Get next token from input, advancing pointer.  */
154
static int
155
get_next_token (char * buf, const char ** pSrc)
Geoff Voelker's avatar
Geoff Voelker committed
156
{
157
  const char * p = *pSrc;
Geoff Voelker's avatar
Geoff Voelker committed
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
  char * o = buf;

  p = skip_space (p);
  if (*p == '"')
    {
      int escape_char_run = 0;

      /* Go through src until an ending quote is found, unescaping
	 quotes along the way.  If the escape char is not quote, then do
	 special handling of multiple escape chars preceding a quote
	 char (ie. the reverse of what Emacs does to escape quotes).  */
      p++;
      while (1)
	{
	  if (p[0] == escape_char && escape_char != '"')
	    {
	      escape_char_run++;
175
	      p++;
Geoff Voelker's avatar
Geoff Voelker committed
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
	      continue;
	    }
	  else if (p[0] == '"')
	    {
	      while (escape_char_run > 1)
		{
		  *o++ = escape_char;
		  escape_char_run -= 2;
		}

	      if (escape_char_run > 0)
		{
		  /* escaped quote */
		  *o++ = *p++;
		  escape_char_run = 0;
		}
	      else if (p[1] == escape_char && escape_char == '"')
		{
		  /* quote escaped by doubling */
		  *o++ = *p;
		  p += 2;
		}
	      else
		{
		  /* The ending quote.  */
		  *o = '\0';
		  /* Leave input pointer after token.  */
		  p++;
		  break;
		}
	    }
	  else if (p[0] == '\0')
	    {
	      /* End of string, but no ending quote found.  We might want to
		 flag this as an error, but for now will consider the end as
		 the end of the token.  */
212 213 214 215 216 217 218 219 220 221 222 223
	      if (escape_char == '\\')
		{
		  /* Output literal backslashes.  Note that if the
		     token ends with an unpaired backslash, we eat it
		     up here.  But since this case invokes undefined
		     behavior anyway, it's okay.  */
		  while (escape_char_run > 1)
		    {
		      *o++ = escape_char;
		      escape_char_run -= 2;
		    }
		}
Geoff Voelker's avatar
Geoff Voelker committed
224 225 226 227 228
	      *o = '\0';
	      break;
	    }
	  else
	    {
229 230 231 232
	      if (escape_char == '\\')
		{
		  /* Output literal backslashes.  Note that we don't
		     treat a backslash as an escape character here,
Paul Eggert's avatar
Paul Eggert committed
233
		     since it doesn't precede a quote.  */
234 235 236
		  for ( ; escape_char_run > 0; escape_char_run--)
		    *o++ = escape_char;
		}
Geoff Voelker's avatar
Geoff Voelker committed
237 238 239 240 241 242 243
	      *o++ = *p++;
	    }
	}
    }
  else
    {
      /* Next token is delimited by whitespace.  */
244
      const char * p1 = skip_nonspace (p);
Geoff Voelker's avatar
Geoff Voelker committed
245 246
      memcpy (o, p, p1 - p);
      o += (p1 - p);
Geoff Voelker's avatar
Geoff Voelker committed
247
      *o = '\0';
Geoff Voelker's avatar
Geoff Voelker committed
248 249 250 251 252 253 254 255
      p = p1;
    }

  *pSrc = p;

  return o - buf;
}

256
/* Return TRUE if PROGNAME is a batch file. */
257
static BOOL
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
batch_file_p (const char *progname)
{
  const char *exts[] = {".bat", ".cmd"};
  int n_exts = sizeof (exts) / sizeof (char *);
  int i;

  const char *ext = strrchr (progname, '.');

  if (ext)
    {
      for (i = 0; i < n_exts; i++)
        {
          if (stricmp (ext, exts[i]) == 0)
            return TRUE;
        }
    }

  return FALSE;
}

Geoff Voelker's avatar
Geoff Voelker committed
278 279
/* Search for EXEC file in DIR.  If EXEC does not have an extension,
   DIR is searched for EXEC with the standard extensions appended.  */
280
static int
281
search_dir (const char *dir, const char *exec, int bufsize, char *buffer)
Geoff Voelker's avatar
Geoff Voelker committed
282
{
283
  const char *exts[] = {".bat", ".cmd", ".exe", ".com"};
Geoff Voelker's avatar
Geoff Voelker committed
284 285 286
  int n_exts = sizeof (exts) / sizeof (char *);
  char *dummy;
  int i, rc;
287 288 289 290 291 292
  const char *pext = strrchr (exec, '\\');

  /* Does EXEC already include an extension?  */
  if (!pext)
    pext = exec;
  pext = strchr (pext, '.');
Geoff Voelker's avatar
Geoff Voelker committed
293 294

  /* Search the directory for the program.  */
295
  if (pext)
Geoff Voelker's avatar
Geoff Voelker committed
296
    {
297 298 299 300 301 302 303 304
      /* SearchPath will not append an extension if the file already
	 has an extension, so we must append it ourselves.  */
      char exec_ext[MAX_PATH], *p;

      p = strcpy (exec_ext, exec) + strlen (exec);

      /* Search first without any extension; if found, we are done.  */
      rc = SearchPath (dir, exec_ext, NULL, bufsize, buffer, &dummy);
Geoff Voelker's avatar
Geoff Voelker committed
305 306
      if (rc > 0)
	return rc;
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324

      /* Try the known extensions.  */
      for (i = 0; i < n_exts; i++)
	{
	  strcpy (p, exts[i]);
	  rc = SearchPath (dir, exec_ext, NULL, bufsize, buffer, &dummy);
	  if (rc > 0)
	    return rc;
	}
    }
  else
    {
      for (i = 0; i < n_exts; i++)
	{
	  rc = SearchPath (dir, exec, exts[i], bufsize, buffer, &dummy);
	  if (rc > 0)
	    return rc;
	}
Geoff Voelker's avatar
Geoff Voelker committed
325 326 327 328 329
    }

  return 0;
}

330
/* Return the absolute name of executable file PROG, including
Geoff Voelker's avatar
Geoff Voelker committed
331 332
   any file extensions.  If an absolute name for PROG cannot be found,
   return NULL.  */
333
static char *
334
make_absolute (const char *prog)
Geoff Voelker's avatar
Geoff Voelker committed
335 336 337 338
{
  char absname[MAX_PATH];
  char dir[MAX_PATH];
  char curdir[MAX_PATH];
339 340
  char *p, *path;
  const char *fname;
Geoff Voelker's avatar
Geoff Voelker committed
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360

  /* At least partial absolute path specified; search there.  */
  if ((isalpha (prog[0]) && prog[1] == ':') ||
      (prog[0] == '\\'))
    {
      /* Split the directory from the filename.  */
      fname = strrchr (prog, '\\');
      if (!fname)
	/* Only a drive specifier is given.  */
	fname = prog + 2;
      strncpy (dir, prog, fname - prog);
      dir[fname - prog] = '\0';

      /* Search the directory for the program.  */
      if (search_dir (dir, prog, MAX_PATH, absname) > 0)
	return strdup (absname);
      else
	return NULL;
    }

361
  if (GetCurrentDirectory (MAX_PATH, curdir) <= 0)
Geoff Voelker's avatar
Geoff Voelker committed
362 363 364
    return NULL;

  /* Relative path; search in current dir. */
365
  if (strpbrk (prog, "\\"))
Geoff Voelker's avatar
Geoff Voelker committed
366 367 368
    {
      if (search_dir (curdir, prog, MAX_PATH, absname) > 0)
	return strdup (absname);
369
      else
Geoff Voelker's avatar
Geoff Voelker committed
370 371
	return NULL;
    }
372

Geoff Voelker's avatar
Geoff Voelker committed
373 374 375 376 377 378 379 380
  /* Just filename; search current directory then PATH.  */
  path = alloca (strlen (getenv ("PATH")) + strlen (curdir) + 2);
  strcpy (path, curdir);
  strcat (path, ";");
  strcat (path, getenv ("PATH"));

  while (*path)
    {
381 382
      size_t len;

Geoff Voelker's avatar
Geoff Voelker committed
383 384 385
      /* Get next directory from path.  */
      p = path;
      while (*p && *p != ';') p++;
386 387 388 389
      /* A broken PATH could have too long directory names in it.  */
      len = min (p - path, sizeof (dir) - 1);
      strncpy (dir, path, len);
      dir[len] = '\0';
Geoff Voelker's avatar
Geoff Voelker committed
390 391 392 393 394 395 396

      /* Search the directory for the program.  */
      if (search_dir (dir, prog, MAX_PATH, absname) > 0)
	return strdup (absname);

      /* Move to the next directory.  */
      path = p + 1;
397
    }
Geoff Voelker's avatar
Geoff Voelker committed
398 399 400 401

  return NULL;
}

402 403 404 405
/* Try to decode the given command line the way cmd would do it.  On
   success, return 1 with cmdline dequoted.  Otherwise, when we've
   found constructs only cmd can properly interpret, return 0 and
   leave cmdline unchanged.  */
406
static int
407 408 409 410 411
try_dequote_cmdline (char* cmdline)
{
  /* Dequoting can only subtract characters, so the length of the
     original command line is a bound on the amount of scratch space
     we need.  This length, in turn, is bounded by the 32k
Paul Eggert's avatar
Paul Eggert committed
412
     CreateProcess limit.  */
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
  char * old_pos = cmdline;
  char * new_cmdline = alloca (strlen(cmdline));
  char * new_pos = new_cmdline;
  char c;

  enum {
    NORMAL,
    AFTER_CARET,
    INSIDE_QUOTE
  } state = NORMAL;

  while ((c = *old_pos++))
    {
      switch (state)
        {
        case NORMAL:
          switch(c)
            {
            case '"':
              *new_pos++ = c;
              state = INSIDE_QUOTE;
              break;
            case '^':
              state = AFTER_CARET;
              break;
            case '<': case '>':
            case '&': case '|':
            case '(': case ')':
            case '%': case '!':
              /* We saw an unquoted shell metacharacter and we don't
                 understand it. Bail out.  */
              return 0;
            default:
              *new_pos++ = c;
              break;
            }
          break;
        case AFTER_CARET:
          *new_pos++ = c;
          state = NORMAL;
          break;
        case INSIDE_QUOTE:
455 456 457 458 459 460 461 462 463 464 465 466 467 468
          switch (c)
            {
            case '"':
              *new_pos++ = c;
              state = NORMAL;
              break;
            case '%':
            case '!':
              /* Variable substitution inside quote.  Bail out.  */
              return 0;
            default:
              *new_pos++ = c;
              break;
            }
469 470 471 472 473 474 475 476 477 478 479
          break;
        }
    }

  /* We were able to dequote the entire string.  Copy our scratch
     buffer on top of the original buffer and return success.  */
  memcpy (cmdline, new_cmdline, new_pos - new_cmdline);
  cmdline[new_pos - new_cmdline] = '\0';
  return 1;
}

Geoff Voelker's avatar
Geoff Voelker committed
480 481 482 483 484 485 486 487 488 489 490 491 492
/*****************************************************************/

#if 0
char ** _argv;
int     _argc;

/* Parse commandline into argv array, allowing proper quoting of args.  */
void
setup_argv (void)
{
  char * cmdline = GetCommandLine ();
  int arg_bytes = 0;

493

Geoff Voelker's avatar
Geoff Voelker committed
494 495 496 497 498 499 500 501 502 503
}
#endif

/* Information about child proc is global, to allow for automatic
   termination when interrupted.  At the moment, only one child process
   can be running at any one time.  */

PROCESS_INFORMATION child;
int interactive = TRUE;

504 505
BOOL console_event_handler (DWORD);

Geoff Voelker's avatar
Geoff Voelker committed
506 507 508 509 510 511 512 513 514
BOOL
console_event_handler (DWORD event)
{
  switch (event)
    {
    case CTRL_C_EVENT:
    case CTRL_BREAK_EVENT:
      if (!interactive)
	{
Glenn Morris's avatar
Glenn Morris committed
515
	  /* Both command.com and cmd.exe have the annoying behavior of
Geoff Voelker's avatar
Geoff Voelker committed
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
	     prompting "Terminate batch job (y/n)?" when interrupted
	     while running a batch file, even if running in
	     non-interactive (-c) mode.  Try to make up for this
	     deficiency by forcibly terminating the subprocess if
	     running non-interactively.  */
	  if (child.hProcess &&
	      WaitForSingleObject (child.hProcess, 500) != WAIT_OBJECT_0)
	    TerminateProcess (child.hProcess, 0);
	  exit (STATUS_CONTROL_C_EXIT);
	}
      break;

#if 0
    default:
      /* CLOSE, LOGOFF and SHUTDOWN events - actually we don't get these
         under Windows 95.  */
      fail ("cmdproxy: received %d event\n", event);
      if (child.hProcess)
	TerminateProcess (child.hProcess, 0);
#endif
    }
  return TRUE;
}

540 541
/* Change from normal usage; return value indicates whether spawn
   succeeded or failed - program return code is returned separately.  */
542
static int
543
spawn (const char *progname, char *cmdline, const char *dir, int *retcode)
Geoff Voelker's avatar
Geoff Voelker committed
544
{
545
  BOOL success = FALSE;
Geoff Voelker's avatar
Geoff Voelker committed
546 547
  SECURITY_ATTRIBUTES sec_attrs;
  STARTUPINFO start;
548 549 550 551
  /* In theory, passing NULL for the environment block to CreateProcess
     is the same as passing the value of GetEnvironmentStrings, but
     doing this explicitly seems to cure problems running DOS programs
     in some cases.  */
Geoff Voelker's avatar
Geoff Voelker committed
552
  char * envblock = GetEnvironmentStrings ();
Geoff Voelker's avatar
Geoff Voelker committed
553 554 555 556

  sec_attrs.nLength = sizeof (sec_attrs);
  sec_attrs.lpSecurityDescriptor = NULL;
  sec_attrs.bInheritHandle = FALSE;
557

Geoff Voelker's avatar
Geoff Voelker committed
558 559 560
  memset (&start, 0, sizeof (start));
  start.cb = sizeof (start);

561 562 563 564 565 566 567
  /* CreateProcess handles batch files as progname specially. This
     special handling fails when both the batch file and arguments are
     quoted.  We pass NULL as progname to avoid the special
     handling. */
  if (progname != NULL && cmdline[0] == '"' && batch_file_p (progname))
      progname = NULL;

Geoff Voelker's avatar
Geoff Voelker committed
568
  if (CreateProcess (progname, cmdline, &sec_attrs, NULL, TRUE,
569
		     0, envblock, dir, &start, &child))
Geoff Voelker's avatar
Geoff Voelker committed
570
  {
571
    success = TRUE;
Geoff Voelker's avatar
Geoff Voelker committed
572 573
    /* wait for completion and pass on return code */
    WaitForSingleObject (child.hProcess, INFINITE);
574 575
    if (retcode)
      GetExitCodeProcess (child.hProcess, (DWORD *)retcode);
Geoff Voelker's avatar
Geoff Voelker committed
576 577 578 579 580
    CloseHandle (child.hThread);
    CloseHandle (child.hProcess);
    child.hProcess = NULL;
  }

Geoff Voelker's avatar
Geoff Voelker committed
581 582
  FreeEnvironmentStrings (envblock);

583
  return success;
Geoff Voelker's avatar
Geoff Voelker committed
584 585
}

Geoff Voelker's avatar
Geoff Voelker committed
586
/* Return size of current environment block.  */
587
static int
588
get_env_size (void)
Geoff Voelker's avatar
Geoff Voelker committed
589 590 591 592 593 594 595 596 597 598
{
  char * start = GetEnvironmentStrings ();
  char * tmp = start;

  while (tmp[0] || tmp[1])
    ++tmp;
  FreeEnvironmentStrings (start);
  return  tmp + 2 - start;
}

Geoff Voelker's avatar
Geoff Voelker committed
599 600 601 602 603 604 605 606 607 608
/*******  Main program  ********************************************/

int
main (int argc, char ** argv)
{
  int rc;
  int need_shell;
  char * cmdline;
  char * progname;
  int envsize;
Geoff Voelker's avatar
Geoff Voelker committed
609 610
  char **pass_through_args;
  int num_pass_through_args;
Geoff Voelker's avatar
Geoff Voelker committed
611 612
  char modname[MAX_PATH];
  char path[MAX_PATH];
613
  char dir[MAX_PATH];
614
  int status;
Geoff Voelker's avatar
Geoff Voelker committed
615 616 617 618 619

  interactive = TRUE;

  SetConsoleCtrlHandler ((PHANDLER_ROUTINE) console_event_handler, TRUE);

620 621 622
  if (!GetCurrentDirectory (sizeof (dir), dir))
    fail ("error: GetCurrentDirectory failed\n");

Geoff Voelker's avatar
Geoff Voelker committed
623 624 625 626 627 628 629 630 631 632 633 634 635
  /* We serve double duty: we can be called either as a proxy for the
     real shell (that is, because we are defined to be the user shell),
     or in our role as a helper application for running DOS programs.
     In the former case, we interpret the command line options as if we
     were a Unix shell, but in the latter case we simply pass our
     command line to CreateProcess.  We know which case we are dealing
     with by whether argv[0] refers to ourself or to some other program.
     (This relies on an arcane feature of CreateProcess, where we can
     specify cmdproxy as the module to run, but specify a different
     program in the command line - the MSVC startup code sets argv[0]
     from the command line.)  */

  if (!GetModuleFileName (NULL, modname, sizeof (modname)))
Geoff Voelker's avatar
Geoff Voelker committed
636
    fail ("error: GetModuleFileName failed\n");
Geoff Voelker's avatar
Geoff Voelker committed
637

638 639 640 641 642 643 644
  /* Change directory to location of .exe so startup directory can be
     deleted.  */
  progname = strrchr (modname, '\\');
  *progname = '\0';
  SetCurrentDirectory (modname);
  *progname = '\\';

645 646 647
  /* Due to problems with interaction between API functions that use "OEM"
     codepage vs API functions that use the "ANSI" codepage, we need to
     make things consistent by choosing one and sticking with it.  */
648 649
  SetConsoleCP (GetACP ());
  SetConsoleOutputCP (GetACP ());
650

Geoff Voelker's avatar
Geoff Voelker committed
651 652
  /* Although Emacs always sets argv[0] to an absolute pathname, we
     might get run in other ways as well, so convert argv[0] to an
653
     absolute name before comparing to the module name.  */
654
  path[0] = '\0';
655 656 657 658 659 660
  /* The call to SearchPath will find argv[0] in the current
     directory, append ".exe" to it if needed, and also canonicalize
     it, to resolve references to ".", "..", etc.  */
  status = SearchPath (NULL, argv[0], ".exe", sizeof (path), path,
				  &progname);
  if (!(status > 0 && stricmp (modname, path) == 0))
Geoff Voelker's avatar
Geoff Voelker committed
661
    {
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719
      if (status <= 0)
	{
	  char *s;

	  /* Make sure we have argv[0] in path[], as the failed
	     SearchPath might not have copied it there.  */
	  strcpy (path, argv[0]);
	  /* argv[0] could include forward slashes; convert them all
	     to backslashes, for strrchr calls below to DTRT.  */
	  for (s = path; *s; s++)
	    if (*s == '/')
	      *s = '\\';
	}
      /* Perhaps MODNAME and PATH use mixed short and long file names.  */
      if (!(GetShortPathName (modname, modname, sizeof (modname))
	    && GetShortPathName (path, path, sizeof (path))
	    && stricmp (modname, path) == 0))
	{
	  /* Sometimes GetShortPathName fails because one or more
	     directories leading to argv[0] have issues with access
	     rights.  In that case, at least we can compare the
	     basenames.  Note: this disregards the improbable case of
	     invoking a program of the same name from another
	     directory, since the chances of that other executable to
	     be both our namesake and a 16-bit DOS application are nil.  */
	  char *p = strrchr (path, '\\');
	  char *q = strrchr (modname, '\\');
	  char *pdot, *qdot;

	  if (!p)
	    p = strchr (path, ':');
	  if (!p)
	    p = path;
	  else
	    p++;
	  if (!q)
	    q = strchr (modname, ':');
	  if (!q)
	    q = modname;
	  else
	    q++;

	  pdot = strrchr (p, '.');
	  if (!pdot || stricmp (pdot, ".exe") != 0)
	    pdot = p + strlen (p);
	  qdot = strrchr (q, '.');
	  if (!qdot || stricmp (qdot, ".exe") != 0)
	    qdot = q + strlen (q);
	  if (pdot - p != qdot - q || strnicmp (p, q, pdot - p) != 0)
	    {
	      /* We are being used as a helper to run a DOS app; just
		 pass command line to DOS app without change.  */
	      /* TODO: fill in progname.  */
	      if (spawn (NULL, GetCommandLine (), dir, &rc))
		return rc;
	      fail ("Could not run %s\n", GetCommandLine ());
	    }
	}
Geoff Voelker's avatar
Geoff Voelker committed
720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
    }

  /* Process command line.  If running interactively (-c or /c not
     specified) then spawn a real command shell, passing it the command
     line arguments.

     If not running interactively, then attempt to execute the specified
     command directly.  If necessary, spawn a real shell to execute the
     command.

  */

  progname = NULL;
  cmdline = NULL;
  /* If no args, spawn real shell for interactive use.  */
  need_shell = TRUE;
  interactive = TRUE;
Geoff Voelker's avatar
Geoff Voelker committed
737 738 739
  /* Ask command.com to create an environment block with a reasonable
     amount of free space.  */
  envsize = get_env_size () + 300;
740
  pass_through_args = (char **) alloca (argc * sizeof (char *));
Geoff Voelker's avatar
Geoff Voelker committed
741
  num_pass_through_args = 0;
Geoff Voelker's avatar
Geoff Voelker committed
742 743 744 745

  while (--argc > 0)
    {
      ++argv;
Geoff Voelker's avatar
Geoff Voelker committed
746
      /* Act on switches we recognize (mostly single letter switches,
Glenn Morris's avatar
Glenn Morris committed
747
	 except for -e); all unrecognized switches and extra args are
Geoff Voelker's avatar
Geoff Voelker committed
748 749
	 passed on to real shell if used (only really of benefit for
	 interactive use, but allow for batch use as well).  Accept / as
Glenn Morris's avatar
Glenn Morris committed
750
	 switch char for compatibility with cmd.exe.  */
751
      if (((*argv)[0] == '-' || (*argv)[0] == '/') && (*argv)[1] != '\0')
Geoff Voelker's avatar
Geoff Voelker committed
752
	{
753
	  if (((*argv)[1] == 'c' || (*argv)[1] == 'C') && ((*argv)[2] == '\0'))
Geoff Voelker's avatar
Geoff Voelker committed
754 755
	    {
	      if (--argc == 0)
Geoff Voelker's avatar
Geoff Voelker committed
756
		fail ("error: expecting arg for %s\n", *argv);
Geoff Voelker's avatar
Geoff Voelker committed
757 758 759
	      cmdline = *(++argv);
	      interactive = FALSE;
	    }
760
	  else if (((*argv)[1] == 'i' || (*argv)[1] == 'I') && ((*argv)[2] == '\0'))
Geoff Voelker's avatar
Geoff Voelker committed
761 762
	    {
	      if (cmdline)
Geoff Voelker's avatar
Geoff Voelker committed
763
		warn ("warning: %s ignored because of -c\n", *argv);
Geoff Voelker's avatar
Geoff Voelker committed
764
	    }
Geoff Voelker's avatar
Geoff Voelker committed
765
	  else if (((*argv)[1] == 'e' || (*argv)[1] == 'E') && ((*argv)[2] == ':'))
Geoff Voelker's avatar
Geoff Voelker committed
766
	    {
Geoff Voelker's avatar
Geoff Voelker committed
767 768 769 770 771 772 773
	      int requested_envsize = atoi (*argv + 3);
	      /* Enforce a reasonable minimum size, as above.  */
	      if (requested_envsize > envsize)
		envsize = requested_envsize;
	      /* For sanity, enforce a reasonable maximum.  */
	      if (envsize > 32768)
		envsize = 32768;
Geoff Voelker's avatar
Geoff Voelker committed
774 775 776
	    }
	  else
	    {
Geoff Voelker's avatar
Geoff Voelker committed
777 778
	      /* warn ("warning: unknown option %s ignored", *argv); */
	      pass_through_args[num_pass_through_args++] = *argv;
Geoff Voelker's avatar
Geoff Voelker committed
779 780 781 782 783 784
	    }
	}
      else
	break;
    }

Geoff Voelker's avatar
Geoff Voelker committed
785 786 787 788 789 790 791 792 793 794 795 796 797
#if 0
  /* I think this is probably not useful - cmd.exe ignores extra
     (non-switch) args in interactive mode, and they cannot be passed on
     when -c was given.  */

  /* Collect any remaining args after (initial) switches.  */
  while (argc-- > 0)
    {
      pass_through_args[num_pass_through_args++] = *argv++;
    }
#else
  /* Probably a mistake for there to be extra args; not fatal.  */
  if (argc > 0)
798
    warn ("warning: extra args ignored after '%s'\n", argv[-1]);
Geoff Voelker's avatar
Geoff Voelker committed
799 800 801 802
#endif

  pass_through_args[num_pass_through_args] = NULL;

Geoff Voelker's avatar
Geoff Voelker committed
803 804 805 806
  /* If -c option, determine if we must spawn a real shell, or if we can
     execute the command directly ourself.  */
  if (cmdline)
    {
807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826
      const char *args;

      /* The program name is the first token of cmdline.  Since
         filenames cannot legally contain embedded quotes, the value
         of escape_char doesn't matter.  */
      args = cmdline;
      if (!get_next_token (path, &args))
        fail ("error: no program name specified.\n");

      canon_filename (path);
      progname = make_absolute (path);

      /* If we found the program and the rest of the command line does
         not contain unquoted shell metacharacters, run the program
         directly (if not found it might be an internal shell command,
         so don't fail).  */
      if (progname != NULL && try_dequote_cmdline (cmdline))
        need_shell = FALSE;
      else
        progname = NULL;
Geoff Voelker's avatar
Geoff Voelker committed
827 828
    }

829
 pass_to_shell:
Geoff Voelker's avatar
Geoff Voelker committed
830 831 832
  if (need_shell)
    {
      char * p;
Geoff Voelker's avatar
Geoff Voelker committed
833
      int    extra_arg_space = 0;
834
      int    maxlen, remlen;
835
      int    run_command_dot_com;
Geoff Voelker's avatar
Geoff Voelker committed
836 837 838

      progname = getenv ("COMSPEC");
      if (!progname)
Geoff Voelker's avatar
Geoff Voelker committed
839
	fail ("error: COMSPEC is not set\n");
Geoff Voelker's avatar
Geoff Voelker committed
840 841 842 843 844

      canon_filename (progname);
      progname = make_absolute (progname);

      if (progname == NULL || strchr (progname, '\\') == NULL)
Geoff Voelker's avatar
Geoff Voelker committed
845 846
	fail ("error: the program %s could not be found.\n", getenv ("COMSPEC"));

847 848 849 850
      /* Need to set environment size when running command.com.  */
      run_command_dot_com =
	(stricmp (strrchr (progname, '\\'), "command.com") == 0);

Geoff Voelker's avatar
Geoff Voelker committed
851 852 853 854 855
      /* Work out how much extra space is required for
         pass_through_args.  */
      for (argv = pass_through_args; *argv != NULL; ++argv)
	/* We don't expect to have to quote switches.  */
	extra_arg_space += strlen (*argv) + 2;
Geoff Voelker's avatar
Geoff Voelker committed
856 857 858

      if (cmdline)
	{
Geoff Voelker's avatar
Geoff Voelker committed
859 860
	  char * buf;

Geoff Voelker's avatar
Geoff Voelker committed
861 862 863 864
	  /* Convert to syntax expected by cmd.exe/command.com for
	     running non-interactively.  Always quote program name in
	     case path contains spaces (fortunately it can't contain
	     quotes, since they are illegal in path names).  */
Geoff Voelker's avatar
Geoff Voelker committed
865

866
	  remlen = maxlen =
867
	    strlen (progname) + extra_arg_space + strlen (cmdline) + 16 + 2;
868
	  buf = p = alloca (maxlen + 1);
Geoff Voelker's avatar
Geoff Voelker committed
869 870

	  /* Quote progname in case it contains spaces.  */
871 872
	  p += _snprintf (p, remlen, "\"%s\"", progname);
	  remlen = maxlen - (p - buf);
Geoff Voelker's avatar
Geoff Voelker committed
873 874 875 876

	  /* Include pass_through_args verbatim; these are just switches
             so should not need quoting.  */
	  for (argv = pass_through_args; *argv != NULL; ++argv)
877 878 879 880
	    {
	      p += _snprintf (p, remlen, " %s", *argv);
	      remlen = maxlen - (p - buf);
	    }
Geoff Voelker's avatar
Geoff Voelker committed
881

882 883 884 885 886 887
	  /* Now that we know we will be invoking the shell, quote the
	     command line after the "/c" switch as the shell expects:
	     a single pair of quotes enclosing the entire command
	     tail, no matter whether quotes are used in the command
	     line, and how many of them are there.  See the output of
	     "cmd /?" for how cmd.exe treats quotes.  */
888
	  if (run_command_dot_com)
889
	    _snprintf (p, remlen, " /e:%d /c \"%s\"", envsize, cmdline);
890
	  else
891
	    _snprintf (p, remlen, " /c \"%s\"", cmdline);
Geoff Voelker's avatar
Geoff Voelker committed
892
	  cmdline = buf;
Geoff Voelker's avatar
Geoff Voelker committed
893 894 895
	}
      else
	{
896
	  if (run_command_dot_com)
897 898
	    {
	      /* Provide dir arg expected by command.com when first
899
		 started interactively (the "command search path").  To
900 901 902 903 904 905 906 907 908 909
		 avoid potential problems with spaces in command dir
		 (which cannot be quoted - command.com doesn't like it),
		 we always use the 8.3 form.  */
	      GetShortPathName (progname, path, sizeof (path));
	      p = strrchr (path, '\\');
	      /* Trailing slash is acceptable, so always leave it.  */
	      *(++p) = '\0';
	    }
	  else
	    path[0] = '\0';
Geoff Voelker's avatar
Geoff Voelker committed
910

911 912 913
	  remlen = maxlen =
	    strlen (progname) + extra_arg_space + strlen (path) + 13;
	  cmdline = p = alloca (maxlen + 1);
Geoff Voelker's avatar
Geoff Voelker committed
914 915

	  /* Quote progname in case it contains spaces.  */
916 917
	  p += _snprintf (p, remlen, "\"%s\" %s", progname, path);
	  remlen = maxlen - (p - cmdline);
Geoff Voelker's avatar
Geoff Voelker committed
918 919 920 921

	  /* Include pass_through_args verbatim; these are just switches
             so should not need quoting.  */
	  for (argv = pass_through_args; *argv != NULL; ++argv)
922 923 924 925
	    {
	      p += _snprintf (p, remlen, " %s", *argv);
	      remlen = maxlen - (p - cmdline);
	    }
Geoff Voelker's avatar
Geoff Voelker committed
926

927
	  if (run_command_dot_com)
928
	    _snprintf (p, remlen, " /e:%d", envsize);
Geoff Voelker's avatar
Geoff Voelker committed
929 930 931 932 933 934 935 936 937
	}
    }

  if (!progname)
    fail ("Internal error: program name not defined\n");

  if (!cmdline)
    cmdline = progname;

938
  if (spawn (progname, cmdline, dir, &rc))
939
    return rc;
Geoff Voelker's avatar
Geoff Voelker committed
940

941 942 943 944 945 946 947 948 949
  if (!need_shell)
    {
      need_shell = TRUE;
      goto pass_to_shell;
    }

  fail ("Could not run %s\n", progname);

  return 0;
Geoff Voelker's avatar
Geoff Voelker committed
950
}