view lib-src/i.c @ 1346:01c57eb70ae9

[xemacs-hg @ 2003-03-09 02:27:27 by ben] To: xemacs-patches@xemacs.org i.c: Sleep between calls to check for I/O, since these calls are non-blocking. behavior.el: Allow other keywords for forward compatibility. cl-macs.el: Rewrite to eliminate byte-compiler warning when `return' is used without `finally'. cmdloop.el: Avoid truncated error messages for `end-of-file' and the like. cmdloop.el: Avoid char-int error after syncing. files.el: Eliminate byte-compile warnings. printer.el: Fix line-width calculations. #### This used to work. Someone's changes (perhaps by Michael Sperber?) seem to have messed something up. simple.el: Use new clear-left-side functions to avoid messages ending up on the same line as other output. xemacs.mak: Add override for info/ as well when separate source/build dirs. xemacs.mak: Order sections in main build process and add comments. Add additional dependencies to try and prevent later steps from happening when failures in earlier steps have occurred. Makefile.in.in: Order sections in main build process and add comments. Add additional dependencies to try and prevent later steps from happening when failures in earlier steps have occurred. alloc.c: Don't arbitrarily clear Vconfigure_info_directory since it messes up separate build/source dirs. console.c, console.h, device-msw.c, device.c: Add accidentally omitted msprinter console and data descriptions. print.c, console-msw.c: Add clear-left-side functionality to help keep stdio/stderr output from separate sources on separate lines. Generalize the different kinds of debugging output. Add dpa(). profile.c: Add better docs on Unix/Windows differences. regex.c: Fix problems with rel-alloc compilation caused by previous patch. emacs.c: Seg fault rather than abort on Cygwin, since gdb doesn't trap aborts properly. console-gtk-impl.h, console-gtk.h, console-msw.h, console-x-impl.h, console-x.h, dialog-gtk.c, dialog-x.c, event-msw.c, frame-gtk.c, frame-x.c, frameslots.h, glyphs-gtk.c, glyphs-x.c, gui-gtk.c, gui-x.c, inline.c, menubar-gtk.c, menubar-msw.c, menubar-x.c, scrollbar-gtk.c, scrollbar-x.c, ui-gtk.c: Delete popup-data object. Delete menubar_data field from frames, since its usage is frame-specific. Delete menubar-msw.h, gui-x.h, gui-gtk.h. Clean up handling of lwlib callback data GCPRO'ing and add missing GCPRO recomputation in widget code.
author ben
date Sun, 09 Mar 2003 02:27:46 +0000
parents 6728e641994e
children bb3f73fbbda8
line wrap: on
line source

/* I-connector utility
   Copyright (C) 2000 Kirill M. Katsnelson
   Copyright (C) 2002, 2003 Ben Wing.

This file is part of XEmacs.

XEmacs 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.

XEmacs 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 XEmacs; see the file COPYING.  If not, write to
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

/* When run with an argument, i treats it as a command line, and pipes
command stdin, stdout and stderr to its own respective streams. How
silly it should sound, but windowed program in Win32 cannot do output
to the console from which it has been started, and should be run using
this utility.

This utility is for running [tx]emacs as part of make process so that
its output goes to the same console as the rest of the make output
does.  It can be used also when xemacs should be run as a batch
command ina script, especially when its standart output should be
obtained programmatically. */

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <tchar.h>

typedef struct
{
  HANDLE source;
  HANDLE drain;
} I_connector;

/* 
 * Make new handle as that pointed to by PH but
 * inheritable, substitute PH with it, and close the
 * original one
 */
static void
make_inheritable (HANDLE* ph)
{
  HANDLE htmp;
  DuplicateHandle (GetCurrentProcess(), *ph, GetCurrentProcess(), &htmp,
		   0, TRUE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
  *ph = htmp;
}

/*
 * Worker thread proc. Reads source, pumps into drain,
 * till either clogs.
 */
static DWORD CALLBACK
pump (LPVOID pv_i)
{
  I_connector* pi = (I_connector*) pv_i;
  BYTE buffer [256];
  DWORD really_read, unused;

  /* The docs for ReadFile claim:

  The ReadFile function returns when one of the following is true: a write
  operation completes on the write end of the pipe, the number of bytes
  requested has been read, or an error occurs.

  But this is just not true.  ReadFile never seems to block, and unless we
  Sleep(), we will chew up all the CPU time. --ben
  */
  while (ReadFile (pi->source, buffer, sizeof (buffer), &really_read, NULL) &&
	 WriteFile (pi->drain, buffer, really_read, &unused, NULL))
    Sleep (100);

  return 0;
}

/*
 * Launch a pump for the given I-connector
 */
static void
start_pump (I_connector* pi)
{
  DWORD unused;
  HANDLE h_thread = CreateThread (NULL, 0, pump, (void*)pi, 0, &unused);
  CloseHandle (h_thread);
}

static HANDLE external_event;

static BOOL
ctrl_c_handler (unsigned long type)
{
  SetEvent (external_event);
  return FALSE;
}

/* Skip over the executable name in the given command line.  Correctly
   handles quotes in the name.  Return NULL upon error.  If
   REQUIRE_FOLLOWING is non-zero, it's an error if no argument follows the
   executable name. */

static LPTSTR
skip_executable_name (LPTSTR cl, int require_following)
{
  int ix;

  while (1)
    {
      ix = _tcscspn (cl, _T(" \t\""));
      if (cl[ix] == '\"')
	{
	  cl = _tcschr (cl + ix + 1, '\"');
	  if (cl == NULL)
	    return NULL; /* Unmatched quote */
	  cl++;
	}
      else
	{
	  cl += ix;
	  cl += _tcsspn (cl, _T(" \t"));
	  if (!require_following)
	    return cl;
	  return *cl ? cl : NULL;
	}
    }
}

/*
 * Brew coffee and bring snickers
 */
void
usage (void)
{
  fprintf (stderr,
   "\n"
   "usage: i command\n"
   "i executes the command and reroutes its standard handles to the calling\n"
   "console.  Good for seeing output of GUI programs that use standard output."
   "\n");
}

int
main (void)
{
  STARTUPINFO si;
  PROCESS_INFORMATION pi;
  I_connector I_in, I_out, I_err;
  DWORD exit_code;
  LPTSTR command = skip_executable_name (GetCommandLine (), 1);
     
  if (command == NULL)
    {
      usage ();
      return 1;
    }

  ZeroMemory (&si, sizeof (si));
  si.dwFlags = STARTF_USESTDHANDLES;

  I_in.source = GetStdHandle (STD_INPUT_HANDLE);
  CreatePipe (&si.hStdInput, &I_in.drain, NULL, 0);
  make_inheritable (&si.hStdInput);

  I_out.drain = GetStdHandle (STD_OUTPUT_HANDLE);
  CreatePipe (&I_out.source, &si.hStdOutput, NULL, 0);
  make_inheritable (&si.hStdOutput);

  I_err.drain = GetStdHandle (STD_ERROR_HANDLE);
  CreatePipe (&I_err.source, &si.hStdError, NULL, 0);
  make_inheritable (&si.hStdError);

  {
    SECURITY_ATTRIBUTES sa;
    LPTSTR new_command =
      (LPTSTR) malloc (666 + sizeof (TCHAR) * _tcslen (command));
    LPTSTR past_exe;

    if (!new_command)
      {
	_ftprintf (stderr, _T ("Out of memory when launching `%s'\n"),
		   command);
	return 2;
      }

    past_exe = skip_executable_name (command, 0);
    if (!past_exe)
      {
	usage ();
	return 1;
      }

    /* Since XEmacs isn't a console application, it can't easily be
       terminated using ^C.  Therefore, we set up a communication path with
       it so that when a ^C is sent to us (using GenerateConsoleCtrlEvent),
       we in turn signals it to commit suicide. (This is cleaner than using
       TerminateProcess()).  This makes (e.g.) the "Stop Build" command
       from VC++ correctly terminate XEmacs.

       #### This will cause problems if i.exe is used for commands other
       than XEmacs.  We need to make behavior this a command-line
       option. */

    /* Create the event as inheritable so that we can use it to communicate
       with the child process */
    sa.nLength = sizeof (sa);
    sa.bInheritHandle = TRUE;
    sa.lpSecurityDescriptor = NULL;
    external_event = CreateEvent (&sa, FALSE, FALSE, NULL);
    if (!external_event)
      {
	_ftprintf (stderr, _T ("Error %d creating signal event for `%s'\n"),
		   GetLastError (), command);
	return 2;
      }

    SetConsoleCtrlHandler ((PHANDLER_ROUTINE) ctrl_c_handler, TRUE);
    _tcsncpy (new_command, command, past_exe - command);
    _stprintf (new_command + (past_exe - command),
	       /* start with space in case no args past command name */
	       " -mswindows-termination-handle %d ", (long) external_event);
    _tcscat (new_command, past_exe);
    
    if (CreateProcess (NULL, new_command, NULL, NULL, TRUE, 0,
		       NULL, NULL, &si, &pi) == 0)
      {
	_ftprintf (stderr, _T("Error %d launching `%s'\n"),
		   GetLastError (), command);
	return 2;
      }
    
    CloseHandle (pi.hThread);
  }


  /* Start pump in each I-connector */
  start_pump (&I_in);
  start_pump (&I_out);
  start_pump (&I_err);

  /* Wait for the process to complete */
  WaitForSingleObject (pi.hProcess, INFINITE);
  GetExitCodeProcess (pi.hProcess, &exit_code);
  CloseHandle (pi.hProcess);

  /* Make pump threads eventually die out. Looks rude, I agree */
  CloseHandle (GetStdHandle (STD_INPUT_HANDLE));
  CloseHandle (GetStdHandle (STD_OUTPUT_HANDLE));
  CloseHandle (GetStdHandle (STD_ERROR_HANDLE));

  return exit_code;
}