Initial revision
authorGeoff Voelker <voelker@cs.washington.edu>
Sat, 9 Aug 1997 02:53:48 +0000 (02:53 +0000)
committerGeoff Voelker <voelker@cs.washington.edu>
Sat, 9 Aug 1997 02:53:48 +0000 (02:53 +0000)
nt/cmdproxy.c [new file with mode: 0644]
nt/debug.bat [new file with mode: 0644]
nt/ftime.bat [new file with mode: 0644]

diff --git a/nt/cmdproxy.c b/nt/cmdproxy.c
new file mode 100644 (file)
index 0000000..4a270cf
--- /dev/null
@@ -0,0 +1,580 @@
+/* Proxy shell designed for use with Emacs on Windows 95 and NT.
+   Copyright (C) 1997 Free Software Foundation, Inc.
+
+   Accepts subset of Unix sh(1) command-line options, for compatability
+   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.
+
+GNU Emacs is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+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
+along with GNU Emacs; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.  */
+
+#include <windows.h>
+
+#include <stdarg.h>  /* va_args */
+#include <malloc.h>  /* alloca */
+#include <stdlib.h>  /* getenv */
+#include <string.h>  /* strlen */
+
+
+/*******  Mock C library routines  *********************************/
+
+/* These routines are used primarily to minimize the executable size.  */
+
+#define stdin  GetStdHandle (STD_INPUT_HANDLE)
+#define stdout GetStdHandle (STD_OUTPUT_HANDLE)
+#define stderr GetStdHandle (STD_ERROR_HANDLE)
+
+int
+vfprintf(HANDLE hnd, char * msg, va_list args)
+{
+  DWORD bytes_written;
+  char buf[1024];
+
+  wvsprintf (buf, msg, args);
+  return WriteFile (hnd, buf, strlen (buf), &bytes_written, NULL);
+}
+
+int
+fprintf(HANDLE hnd, char * msg, ...)
+{
+  va_list args;
+  int rc;
+
+  va_start (args, msg);
+  rc = vfprintf (hnd, msg, args);
+  va_end (args);
+
+  return rc;
+}
+
+int
+printf(char * msg, ...)
+{
+  va_list args;
+  int rc;
+
+  va_start (args, msg);
+  rc = vfprintf (stdout, msg, args);
+  va_end (args);
+
+  return rc;
+}
+
+void
+fail (char * msg, ...)
+{
+  va_list args;
+
+  va_start (args, msg);
+  vfprintf (stderr, msg, args);
+  va_end (args);
+
+  exit (1);
+}
+
+void
+warn (char * msg, ...)
+{
+  va_list args;
+
+  va_start (args, msg);
+  vfprintf (stderr, msg, args);
+  va_end (args);
+}
+
+/******************************************************************/
+
+char *
+canon_filename (char *fname)
+{
+  char *p = fname;
+
+  while (*p)
+    {
+      if (*p == '/')
+       *p = '\\';
+      p++;
+    }
+
+  return fname;
+}
+
+char *
+skip_space (char *str)
+{
+  while (isspace (*str)) str++;
+  return str;
+}
+
+char *
+skip_nonspace (char *str)
+{
+  while (*str && !isspace (*str)) str++;
+  return str;
+}
+
+int escape_char = '\\';
+
+/* Get next token from input, advancing pointer.  */
+int
+get_next_token (char * buf, char ** pSrc)
+{
+  char * p = *pSrc;
+  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++;
+             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.  */
+             *o = '\0';
+             break;
+           }
+         else
+           {
+             *o++ = *p++;
+           }
+       }
+    }
+  else
+    {
+      /* Next token is delimited by whitespace.  */
+      char * p1 = skip_nonspace (p);
+      memcpy (o, p, p1 - p);
+      o += (p1 - p);
+      p = p1;
+    }
+
+  *pSrc = p;
+
+  return o - buf;
+}
+
+/* Search for EXEC file in DIR.  If EXEC does not have an extension,
+   DIR is searched for EXEC with the standard extensions appended.  */
+int
+search_dir (char *dir, char *exec, int bufsize, char *buffer)
+{
+  char *exts[] = {".bat", ".cmd", ".exe", ".com"};
+  int n_exts = sizeof (exts) / sizeof (char *);
+  char *dummy;
+  int i, rc;
+
+  /* Search the directory for the program.  */
+  for (i = 0; i < n_exts; i++) 
+    {
+      rc = SearchPath (dir, exec, exts[i], bufsize, buffer, &dummy);
+      if (rc > 0)
+       return rc;
+    }
+
+  return 0;
+}
+
+/* Return the absolute name of executable file PROG, including 
+   any file extensions.  If an absolute name for PROG cannot be found,
+   return NULL.  */
+char *
+make_absolute (char *prog)
+{
+  char absname[MAX_PATH];
+  char dir[MAX_PATH];
+  char curdir[MAX_PATH];
+  char *p, *fname;
+  char *path;
+  int i;
+
+  /* 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;
+    }
+
+  if (GetCurrentDirectory (MAX_PATH, curdir) <= 0) 
+    return NULL;
+
+  /* Relative path; search in current dir. */
+  if (strpbrk (prog, "\\")) 
+    {
+      if (search_dir (curdir, prog, MAX_PATH, absname) > 0)
+       return strdup (absname);
+      else 
+       return NULL;
+    }
+  
+  /* 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)
+    {
+      /* Get next directory from path.  */
+      p = path;
+      while (*p && *p != ';') p++;
+      strncpy (dir, path, p - path);
+      dir[p - path] = '\0';
+
+      /* 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;
+    } 
+
+  return NULL;
+}
+
+/*****************************************************************/
+
+#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;
+
+  
+}
+#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;
+
+BOOL
+console_event_handler (DWORD event)
+{
+  switch (event)
+    {
+    case CTRL_C_EVENT:
+    case CTRL_BREAK_EVENT:
+      if (!interactive)
+       {
+         /* Both command.com and cmd.exe have the annoying behaviour of
+            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;
+}
+
+int
+spawn (char * progname, char * cmdline)
+{
+  DWORD rc = 0xff;
+  SECURITY_ATTRIBUTES sec_attrs;
+  STARTUPINFO start;
+
+  sec_attrs.nLength = sizeof (sec_attrs);
+  sec_attrs.lpSecurityDescriptor = NULL;
+  sec_attrs.bInheritHandle = FALSE;
+  
+  memset (&start, 0, sizeof (start));
+  start.cb = sizeof (start);
+
+  if (CreateProcess (progname, cmdline, &sec_attrs, NULL, TRUE,
+                    0, NULL, NULL, &start, &child))
+  {
+    /* wait for completion and pass on return code */
+    WaitForSingleObject (child.hProcess, INFINITE);
+    GetExitCodeProcess (child.hProcess, &rc);
+    CloseHandle (child.hThread);
+    CloseHandle (child.hProcess);
+    child.hProcess = NULL;
+  }
+
+  return (int) rc;
+}
+
+/*******  Main program  ********************************************/
+
+int
+main (int argc, char ** argv)
+{
+  int rc;
+  int need_shell;
+  char * cmdline;
+  char * progname;
+  int envsize;
+  char modname[MAX_PATH];
+  char path[MAX_PATH];
+
+
+  interactive = TRUE;
+
+  SetConsoleCtrlHandler ((PHANDLER_ROUTINE) console_event_handler, TRUE);
+
+  /* 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)))
+    fail ("GetModuleFileName failed");
+
+  /* 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
+     absolute name before comparing to the module name.  */
+  if (!SearchPath (NULL, argv[0], ".exe", sizeof (path), path, &progname)
+      || stricmp (modname, path) != 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.  */
+      return spawn (NULL, GetCommandLine ());
+    }
+
+  /* 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;
+  /* Ask for a reasonable size environment for command.com.  */
+  envsize = 1024;
+
+  while (--argc > 0)
+    {
+      ++argv;
+      /* Only support single letter switches (except for -e); allow / as
+        switch char for compatability with cmd.exe.  */
+      if ( ((*argv)[0] == '-' || (*argv)[0] == '/')
+          && (*argv)[1] != '\0' && (*argv)[2] == '\0' )
+       {
+         if ( ((*argv)[1] == 'c') && ((*argv)[2] == '\0')  )
+           {
+             if (--argc == 0)
+               fail ("error: expecting arg for %s", *argv);
+             cmdline = *(++argv);
+             interactive = FALSE;
+           }
+         else if ( ((*argv)[1] == 'i') && ((*argv)[2] == '\0')  )
+           {
+             if (cmdline)
+               warn ("warning: %s ignored because of -c", *argv);
+           }
+         else if ( ((*argv)[1] == 'e') && ((*argv)[2] == ':')  )
+           {
+             envsize = atoi (*argv + 3);
+             /* Enforce a reasonable minimum size.  */
+             if (envsize < 256)
+               envsize = 256;
+           }
+         else
+           {
+             warn ("warning: unknown option %s ignored", *argv);
+           }
+       }
+      else
+       break;
+    }
+
+  /* If -c option, determine if we must spawn a real shell, or if we can
+     execute the command directly ourself.  */
+  if (cmdline)
+    {
+      /* If no redirection or piping, and if program can be found, then
+        run program directly.  Otherwise invoke a real shell. */
+
+      static char copout_chars[] = "|<>&";
+
+      if (strpbrk (cmdline, copout_chars) == NULL)
+       {
+         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, run it directly (if not found it
+             might be an internal shell command, so don't fail).  */
+         if (progname != NULL)
+           need_shell = FALSE;
+       }
+    }
+
+  if (need_shell)
+    {
+      char * p;
+
+      progname = getenv ("COMSPEC");
+      if (!progname)
+       fail ("error: COMSPEC is not set");
+
+      canon_filename (progname);
+      progname = make_absolute (progname);
+
+      if (progname == NULL || strchr (progname, '\\') == NULL)
+       fail ("make_absolute failed");
+
+      if (cmdline)
+       {
+         /* 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).  */
+         wsprintf (p = alloca (strlen (cmdline) + strlen (progname) + 7),
+                   "\"%s\" /c %s", progname, cmdline);
+         cmdline = p;
+       }
+      else
+       {
+         /* Provide dir arg expected by command.com when first started
+            interactively (the "command search path").  cmd.exe does
+            not require it, but accepts it silently - presumably other
+            DOS compatible shells do the same.  To 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.  */
+         p++;
+
+         /* Set environment size - again cmd.exe ignores this silently.  */
+         wsprintf (p, " /e:%d", envsize);
+
+         /* Quote progname in case it contains spaces.  */
+         wsprintf (cmdline = alloca (strlen (progname) + strlen (path) + 4),
+                   "\"%s\" %s", progname, path);
+       }
+    }
+
+  if (!progname)
+    fail ("Internal error: program name not defined\n");
+
+  if (!cmdline)
+    cmdline = progname;
+
+  rc = spawn (progname, cmdline);
+
+  return rc;
+}
diff --git a/nt/debug.bat b/nt/debug.bat
new file mode 100644 (file)
index 0000000..1cb3a85
--- /dev/null
@@ -0,0 +1,40 @@
+@echo off \r
+set emacs_dir=c:\emacs\r
+\r
+REM Here begins emacs.bat.in\r
+\r
+REM Set OS specific values.\r
+set ARCH_SAVE=%PROCESSOR_ARCHITECTURE%\r
+set PROCESSOR_ARCHITECTURE=\r
+if "%ARCH_SAVE%" == "%PROCESSOR_ARCHITECTURE%" goto win95\r
+set PROCESSOR_ARCHITECTURE=%ARCH_SAVE%\r
+set SHELL=cmd\r
+goto next\r
+\r
+:win95\r
+set SHELL=command\r
+\r
+:next\r
+\r
+set EMACSLOADPATH=%emacs_dir%\lisp\r
+set EMACSDATA=%emacs_dir%\etc\r
+set EMACSPATH=%emacs_dir%\bin\r
+set EMACSLOCKDIR=%emacs_dir%\lock\r
+set INFOPATH=%emacs_dir%\info\r
+set EMACSDOC=%emacs_dir%\etc\r
+set TERM=CMD\r
+\r
+REM The variable HOME is used to find the startup file, ~\_emacs.  Ideally,\r
+REM this will not be set in this file but should already be set before\r
+REM this file is invoked.  If HOME is not set, use some generic default.\r
+\r
+set HOME_SAVE=%HOME%\r
+set HOME_EXISTS=yes\r
+set HOME_DEFAULT=C:\\r
+set HOME=\r
+if "%HOME%" == "%HOME_SAVE%" set HOME_EXISTS=no\r
+if "%HOME_EXISTS%" == "yes" set HOME=%HOME_SAVE%\r
+if "%HOME_EXISTS%" == "no" set HOME=%HOME_DEFAULT%\r
+if "%HOME_EXISTS%" == "no" echo HOME is not set!  Using %HOME% as a default...\r
+\r
+start c:\msdev\bin\msdev -nologo %emacs_dir%\bin\emacs.exe %1 %2 %3 %4 %5 %6 %7 %8 %9\r
diff --git a/nt/ftime.bat b/nt/ftime.bat
new file mode 100644 (file)
index 0000000..1e35f2e
--- /dev/null
@@ -0,0 +1,10 @@
+@echo off\r
+set emacs_dir=c:\emacs\r
+prep /om /ft %emacs_dir%\src\obj\i386\emacs\r
+if errorlevel 1 goto done\r
+profile %emacs_dir%\src\obj\i386\emacs %1 %2 %3 %4 %5 %6 %7 %8 %9\r
+if errorlevel 1 goto done\r
+prep /m  %emacs_dir%\src\obj\i386\emacs\r
+if errorlevel 1 goto done\r
+plist  %emacs_dir%\src\obj\i386\emacs > info/emacs.prof\r
+:done\r