lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


I actually think that simple non-blocking I/O would suffice with a
proper popen.
I have no problem with the cost of starting (and maintaining) a new
process to do I/O. The real problem is that you can't make the pipe from popen non-blocking
on Win32.
I might look into a module that would allow non-blocking pipes.... and a
silent background process
(ie no "dos box") on Win32...

I recently needed the ability for io.popen() to not show the command prompt window (dos box) on Windows. I found code on the lua-users wiki which did this for Lua 4 and got it to work with the current Windows SDK (with some caveats), please see the attached file.

 - Peter Odding

PS. This requires recompiling Lua (which wasn't a problem for me fortunately) with the following changes to luaconf.h:

/*
@@ lua_popen spawns a new process connected to the current one through
@* the file streams.
** CHANGE it if you have a way to implement it in your system.
*/
#if defined(LUA_USE_POPEN)

#define lua_popen(L,c,m)	((void)L, fflush(NULL), popen(c,m))
#define lua_pclose(L,file)	((void)L, (pclose(file) != -1))

#elif defined(LUA_WIN)

/* Modification to support hidden console windows based on
 * http://lua-users.org/wiki/PipesOnWindows. */

#include <stdio.h> /* we need FILE */
FILE* pt_popen(const char *, const char *);
int pt_pclose(FILE *);

#define lua_popen(L,c,m)	((void)L, pt_popen(c,m))
#define lua_pclose(L,file)	((void)L, (pt_pclose(file) != -1))

/* End of modification (but see "popen.c"). */

#else

#define lua_popen(L,c,m)	((void)((void)c, m),  \
		luaL_error(L, LUA_QL("popen") " not supported"), (FILE*)0)
#define lua_pclose(L,file)		((void)((void)L, file), 0)

#endif
/* I found the following code at http://home.mweb.co.za/sd/sdonovan/popen.zip
 * via http://lua-users.org/wiki/PipesOnWindows. Apparently it was written for
 * an earlier version of Lua and it needed some changes to pt_pipe() but apart
 * from only supporting binary mode pipes this works fine with Lua 5.1.4 :-).
 * The changes to pt_pipe() are as follows:
 *
 *  - STARTF_USESTDHANDLES -> STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW
 *  - si.wShowWindow = SW_HIDE
 *  - DETACHED_PROCESS -> CREATE_NEW_CONSOLE
 * 
 * I've also cleaned up the code a little. For future reference the following
 * article in the Microsoft Knowledge Base is really useful:
 *   http://support.microsoft.com/kb/190351
 *
 * See also the accompanying changes in the file luaconf.h.
 *  - Peter Odding <peter@peterodding.com>
 *
 * Original file header follows:
 *
 * popen.c
 * RunSilent() is by Steven Szelei,
 * and pt_popen()/pt_pclose() is by Kurt Keller
 * Modified and comments translated by Steve Donovan
 *
 * ... <outdated integration instructions snipped by Peter> ...
 */

#include <windows.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <io.h>

/* TODO: Use a dynamically resized buffer for this! */
#define CMDLINE_BUFSIZE 4096

char *buildcmdline(const char *cmdline, char *buffer)
{
  /* Launch the external command using the default command interpreter. */
  const char *interpreter = getenv("COMSPEC");
  if (!interpreter) interpreter = "CMD.EXE";
  strcpy(buffer, interpreter);
  /* CMD /C carries out the specified command line, then terminates. */
  strcat(buffer, " /C ");
  strcat(buffer, cmdline);
  return buffer;
}

DWORD RunSilent(const char* cmdline)
{
  STARTUPINFO si;
  PROCESS_INFORMATION pi;
  char buffer[CMDLINE_BUFSIZE];
  char *interpreter;
  ULONG returncode;

  /* Initialize the required structures. */
  ZeroMemory(&pi, sizeof(pi));
  ZeroMemory(&si, sizeof(si));
  si.cb = sizeof(si);
  si.dwFlags = STARTF_USESHOWWINDOW;
  si.wShowWindow = SW_HIDE;

  /* Build the command line. */
  buildcmdline(cmdline, buffer);

  if (!CreateProcess(
        NULL,               /* Name of module to be executed.        */
        buffer,             /* Command line to be executed.          */
        NULL,               /* Process security attributes.          */
        NULL,               /* Primary thread security attributes.   */
        FALSE,              /* Handles are inherited.                */
        CREATE_NEW_CONSOLE, /* Creation flags.                       */
        NULL,               /* Use parent process environment.       */
        NULL,               /* Use parent process working directory. */
        &si,                /* Startup information structure.        */
        &pi))               /* Process information structure.        */
    /* TODO This doesn't really make sense does it?! */
    return GetLastError();

  /* Wait for the external command to finish. */
  WaitForSingleObject(pi.hProcess, INFINITE);

  /* Get the return code of the external command. */
  if (!GetExitCodeProcess(pi.hProcess, &returncode))
    /* TODO If this fails we may need to raise(GetLastError())?! */
    returncode = 0;

  /* Cleanup parent references to child process. */
  CloseHandle(pi.hThread);
  CloseHandle(pi.hProcess);

  return returncode;
}

/*------------------------------------------------------------------------------
  Globals for the Routines pt_popen() / pt_pclose()
------------------------------------------------------------------------------*/
static HANDLE my_pipein[2], my_pipeout[2], my_pipeerr[2];
static char   my_popenmode = ' ';

static int
my_pipe(HANDLE *readwrite)
{
  SECURITY_ATTRIBUTES sa;

  sa.nLength = sizeof(sa);          /* Length in bytes. */
  sa.bInheritHandle = 1;            /* Child must inherit these handles. */
  sa.lpSecurityDescriptor = NULL;

  if (!CreatePipe(&readwrite[0], &readwrite[1], &sa, 1 << 13))
  {
    errno = -1; /* EMFILE; que? */
    return -1;
  }

  return 0;
}

/* Replacement for popen() under Windows. Note that if the command line
 * contains the string 2>&1 we connect standard error to standard output. */

FILE * pt_popen(const char *cmdline, const char *mode)
{
  PROCESS_INFORMATION pi;
  STARTUPINFO si;
  char buffer[CMDLINE_BUFSIZE];
  FILE *fptr = (FILE *)0;
  char *err2out;
  int redirect_error = 0;

  /* Build the command line. */
  buildcmdline(cmdline, buffer);

  /* Initialize the global pipe handle structures. */
  my_pipein[0]  = INVALID_HANDLE_VALUE;
  my_pipein[1]  = INVALID_HANDLE_VALUE;
  my_pipeout[0] = INVALID_HANDLE_VALUE;
  my_pipeout[1] = INVALID_HANDLE_VALUE;
  my_pipeerr[0] = INVALID_HANDLE_VALUE;
  my_pipeerr[1] = INVALID_HANDLE_VALUE;

  /* Validate mode argument. */
  if (!(mode && (mode[0] == 'r' || mode[0] == 'w')))
    goto finito;
  my_popenmode = mode[0];

  /* Shall we redirect standard error to standard output? */
  if ((err2out = strstr("2>&1", buffer)) != NULL) {
     /* This option doesn't apply to Windows shells, so we clear it out! */
     strncpy(err2out, "    ", 4);
     redirect_error = 1;
  }

  /* Create the pipes. */
  if (my_pipe(my_pipein)  == -1 || my_pipe(my_pipeout) == -1)
    goto finito;
  if (!redirect_error && my_pipe(my_pipeerr) == -1)
    goto finito;

  /* Now create the child process. */
  ZeroMemory(&si, sizeof(si));
  si.cb = sizeof(si);
  /* XXX Peter Odding: I've changed the following two lines. */
  si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
  si.wShowWindow = SW_HIDE;
  si.hStdInput = my_pipein[0];
  si.hStdOutput = my_pipeout[1];
  si.hStdError = redirect_error ? my_pipeout[1] : my_pipeerr[1];
  if (!CreateProcess(
        NULL,             /* Name of module to be executed.        */
        (LPTSTR)buffer,   /* Command line to be executed.          */
        NULL,             /* Process security attributes.          */
        NULL,             /* Primary thread security attributes.   */
        TRUE,             /* Handles are inherited.                */
        CREATE_NEW_CONSOLE, /* Creation flags: without window (?).   */
        NULL,             /* Use parent process environment.       */
        NULL,             /* Use parent process working directory. */
        &si,              /* Startup information structure.        */
        &pi))             /* Process information structure.        */
    goto finito;

  /* These handles listen to the child process. */
  CloseHandle(my_pipein[0]);
  my_pipein[0] = INVALID_HANDLE_VALUE;
  CloseHandle(my_pipeout[1]);
  my_pipeout[1] = INVALID_HANDLE_VALUE;
  CloseHandle(my_pipeerr[1]);
  my_pipeerr[1] = INVALID_HANDLE_VALUE;

  if (my_popenmode == 'r')
    fptr = _fdopen(_open_osfhandle((long)my_pipeout[0], _O_BINARY), "r");
  else
    fptr = _fdopen(_open_osfhandle((long)my_pipein[1], _O_BINARY), "w");

finito:
  if (!fptr) {
    if (my_pipein[0]  != INVALID_HANDLE_VALUE)
      CloseHandle(my_pipein[0]);
    if (my_pipein[1]  != INVALID_HANDLE_VALUE)
      CloseHandle(my_pipein[1]);
    if (my_pipeout[0] != INVALID_HANDLE_VALUE)
      CloseHandle(my_pipeout[0]);
    if (my_pipeout[1] != INVALID_HANDLE_VALUE)
      CloseHandle(my_pipeout[1]);
    if (my_pipeerr[0] != INVALID_HANDLE_VALUE)
      CloseHandle(my_pipeerr[0]);
    if (my_pipeerr[1] != INVALID_HANDLE_VALUE)
      CloseHandle(my_pipeerr[1]);
  }
  return fptr;
}

/* Replacement for pclose() under Windows. */

int pt_pclose(FILE *handle)
{
  if (handle) {
    (void)fclose(handle);
    CloseHandle(my_pipeerr[0]);
    if (my_popenmode == 'r')
      CloseHandle(my_pipein[1]);
    else
     CloseHandle(my_pipeout[0]);
    return 0;
  }
  return -1;
}

/* vim: set ts=2 sw=2 et : */