root/nt/cmdproxy.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. vfprintf
  2. fprintf
  3. printf
  4. fail
  5. warn
  6. canon_filename
  7. skip_space
  8. skip_nonspace
  9. get_next_token
  10. batch_file_p
  11. search_dir
  12. make_absolute
  13. try_dequote_cmdline
  14. setup_argv
  15. console_event_handler
  16. spawn
  17. get_env_size
  18. main

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

/* [<][>][^][v][top][bottom][index][help] */