This is the mail archive of the cygwin mailing list for the Cygwin project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: Redirecting bash stdin


>>>According to Dave on 4/21/2006 11:45 AM:
>>>
>>>>I'm trying to get a mingw GUI application to pipe commands to
>>>>cygwins bash by redirecting its stdin as described here
>>>><http://support.microsoft.com/?id=190351>.

Buchbinder, Barry (NIH/NIAID) [E] wrote:
Have you tried piping into "bash -s"?  Probably won't be any different
than the other piping you've tried, but maybe it will.

I've just tried this - it doesn't affect the behaviour.


>>Eric Blake wrote:
>>> A simple test case is a necessity if you expect help debugging this.

I've finally got the test case to show the behaviour I'm seeing in my application. Find it attached.

Instructions to reproduce
-------------------------

1. Compile the testcase as:

g++ -g -mno-cygwin -pedantic -Wall -W -o min_tc.exe min_tc.cc

2. Confirm you have Windows notepad in your path :)

which notepad

3. Identify a text file in your home directory to open. I'll use gdbtk.ini

4. Execute the 'working' case:

./min_tc notepad gdbtk.ini

- a few seconds pause while the bash login shell is starting
- notepad opens with your textfile (or a messagebox saying it can't fnid it). You can leave this open, or close it.
- 10 second pause while the test case sleeps
- returned to your prompt


5. Execute the 'non-working' case:

./min_tc notepad \`cygpath -u gdbtk.ini\`

   - a long pause (bash startup + 10s sleep)
   - almost simultaneous opening of notepad and return to shell

NB In order to clearly see the difference in behaviour, it is useful
to run the command from a shell which only displays its prompt after
the command has completed. This rules out command.com and cmd.exe,
although they exhibit the same issue.
-------------

Any help figuring out what's causing the delayed execution is much appreciated.

Thanks,

Dave.
PS now running bash 3.1-6
PPS yes, that is as minimal as I could get the test case. Apologies.
#include <stdio.h>
#include <windows.h>

/* Macros normally mapped to some logging function */
/* Just print on sterr */
#define SYSTEM_ERROR(text, err, pri) \
    fprintf(stderr,text)
#define APP_ERROR(text, pri) \
    fprintf(stderr,text)

/** Handle of the write end of the pipe
 *  connected to the standard input of the shell
 */
HANDLE sh_stdin;
/** Handle of the read end of the pipe
 *  connected to the standard output of the shell
 */
HANDLE sh_stdout;

/** TRUE is a shell is running waiting for input */
bool bShell_running;

static bool LaunchRedirectedChild( HANDLE hChildStdOut,
				   HANDLE hChildStdIn,
				   HANDLE hChildStdErr,
				   char* cmd)
{
    PROCESS_INFORMATION pi;
    STARTUPINFO si;
    bool rv = true;

    // Set up the start up info struct.
    memset(&si,0,sizeof(si));
    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
    si.hStdOutput = NULL;//hChildStdOut;
    si.hStdInput  = hChildStdIn;
    si.hStdError  = hChildStdErr;
    si.wShowWindow = SW_HIDE; // Don't show the login shell

    if (!CreateProcess(NULL,cmd,NULL,NULL,TRUE,
                        CREATE_NEW_CONSOLE,NULL,NULL,&si,&pi))
    {
	char* msg = new char[strlen(cmd)+ 77];
	sprintf(msg,
		"LaunchRedirectedChild "
		"CreateProcess failed to start child process.\n"
		"Command: %s\n",
		cmd);
        SYSTEM_ERROR(msg,
		     GetLastError(),
		     UNRECOVERABLE);
	delete[] msg;
        rv = false;
    }
    else
    {
        // Set global child process handle to cause threads to exit.
        //hChildProcess = pi.hProcess;

        // Close any unnecessary handles.
        if (!CloseHandle(pi.hThread))
        {
            SYSTEM_ERROR("LaunchRedirectedChild "
			 "CloseHandle can't close child thread handle\n",
			 GetLastError(),
			 RECOVERABLE);
        }
        if (!CloseHandle(pi.hProcess))
        {
            SYSTEM_ERROR("LaunchRedirectedChild "
			 "CloseHandle can't close child process handle\n",
			 GetLastError(),
			 RECOVERABLE);
        }
    }
    return rv;
}

bool start_shell(void)
{
    char login_cmd[MAX_PATH + FILENAME_MAX] =
	"c:\\cygwin\\bin\\bash.exe -l";

    HANDLE hOutputReadTmp,hOutputRead,hOutputWrite;
    HANDLE hInputWriteTmp,hInputRead,hInputWrite;
    HANDLE hErrorWrite;
    HANDLE hThread;
    DWORD ThreadId;
    SECURITY_ATTRIBUTES sa;

    /* Create an anonymous pipe and attach it to stdin
     * Then start the login shell
     *
     * Think about redirecting output somewhere as well.
     */

    // Set up the security attributes struct.
    sa.nLength= sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    // Create the child output pipe.
    if (!CreatePipe(&hOutputReadTmp,
                    &hOutputWrite,
                    &sa,
                    0))
    {
        SYSTEM_ERROR("Shell::start_shell "
		     "CreatePipe can't create output pipe\n",
		     GetLastError(),
		     UNRECOVERABLE);
    }

    // Create a duplicate of the output write handle for the std error
    // write handle. This is necessary in case the child application
    // closes one of its std output handles.
    if (!DuplicateHandle(GetCurrentProcess(),
			 hOutputWrite,
			 GetCurrentProcess(),
			 &hErrorWrite,
			 0,
			 TRUE,
			 DUPLICATE_SAME_ACCESS))
    {
        SYSTEM_ERROR("Shell::start_shell "
		     "DuplicateHandle can't duplicate output write handle\n",
		     GetLastError(),
		     UNRECOVERABLE);
    }

    // Create the child input pipe.
    if (!CreatePipe(&hInputRead,
                    &hInputWriteTmp,
                    &sa,
                    0))
    {
        SYSTEM_ERROR("Shell::start_shell "
		     "CreatePipe can't create input pipe\n",
		     GetLastError(),
		     UNRECOVERABLE);
    }

    // Create new output read handle and the input write handles. Set
    // the Properties to FALSE. Otherwise, the child inherits the
    // properties and, as a result, non-closeable handles to the pipes
    // are created.
    if (!DuplicateHandle(GetCurrentProcess(),
			 hOutputReadTmp,
			 GetCurrentProcess(),
			 &hOutputRead, // Address of new handle.
			 0,
			 FALSE, // Make it uninheritable.
			 DUPLICATE_SAME_ACCESS))
    {
        SYSTEM_ERROR("Shell::start_shell "
		     "DuplicateHandle can't duplicate output read handle\n",
		     GetLastError(),
		     UNRECOVERABLE);
    }

    if (!DuplicateHandle(GetCurrentProcess(),
			 hInputWriteTmp,
			 GetCurrentProcess(),
			 &hInputWrite, // Address of new handle.
			 0,
			 FALSE, // Make it uninheritable.
			 DUPLICATE_SAME_ACCESS))
    {
        SYSTEM_ERROR("Shell::start_shell "
		     "DuplicateHandle can't duplicate input write handle\n",
		     GetLastError(),
		     UNRECOVERABLE);
    }

    // Close inheritable copies of the handles you do not want to be
    // inherited.
    if (!CloseHandle(hOutputReadTmp))
    {
        SYSTEM_ERROR("Shell::start "
		     "CloseHandle can't close output read handle\n",
		     GetLastError(),
		     RECOVERABLE);
    }
    if (!CloseHandle(hInputWriteTmp))
    {
        SYSTEM_ERROR("Shell::start "
		     "CloseHandle can't close input write handle\n",
		     GetLastError(),
		     RECOVERABLE);
    }

    bShell_running = LaunchRedirectedChild(hOutputWrite,
					   hInputRead,
					   hErrorWrite,
					   login_cmd);

    sh_stdin = hInputWrite;
    sh_stdout = hOutputRead;

    // Close pipe handles (do not continue to modify the parent).
    // You need to make sure that no handles to the write end of the
    // output pipe are maintained in this process or else the pipe will
    // not close when the child process exits and the ReadFile will hang.
    if (!CloseHandle(hOutputWrite))
    {
	SYSTEM_ERROR("Shell::start "
		     "CloseHandle can't close the output write handle\n",
		     GetLastError(),
		     RECOVERABLE);
    }
    if (!CloseHandle(hInputRead))
    {
	SYSTEM_ERROR("Shell::start "
		     "CloseHandle can't close the input read handle\n",
		     GetLastError(),
		     RECOVERABLE);
    }
    if (!CloseHandle(hErrorWrite))
    {
	SYSTEM_ERROR("Shell::start "
		     "CloseHandle can't close the error write handle\n",
		     GetLastError(),
		     RECOVERABLE);
    }

    return bShell_running;
}

bool send_command(const char* cmd)
{
    bool rv = true;

    if (bShell_running)
    {
	DWORD len = strlen(cmd) + 1; /* send the null terminator */
	DWORD written;

	if (! WriteFile(sh_stdin, cmd, len,
			&written, NULL))
	{
	    INT err = GetLastError();
	    if (err == ERROR_NO_DATA)
	    {
		/* Pipe was closed */
		APP_ERROR("Shell::send_command "
			  "Pipe was closed.",
			  UNRECOVERABLE);
	    }
	    else
	    {
		SYSTEM_ERROR("Shell::send_command "
			     "WriteFile failed to write to pipe.\n",
			     GetLastError(),
			     UNRECOVERABLE);
	    }
	    rv = false;
	}

	/** \todo for some reason the buffer isn't flushing on the client side.
	 * Try flush this end.
	 */
	FlushFileBuffers(sh_stdin);

	fprintf (stderr,
		 "Command sent to shell: %s\n",
		 cmd);
    }
    else
    {
        /* shell not available */
        rv = false;
    }

    return rv;
}

void exit_shell(void)
{
    if (bShell_running)
    {
        if(send_command("exit"))
        {
            bShell_running = false;
        }
        else
        {
            APP_ERROR("Shell::exit_shell "
		      "send_command failed",
		      RECOVERABLE);
        }
    }
    
}

bool run_command(const char* cmd)
{
    bool rv;

    /* All commands need to be run in the background,
     * so make sure that an & is appended */
    INT len = strlen(cmd);
    INT n = len;
    char *cmd_ptr = NULL;

    /* First find the last character in the command */
    do
    {
	n--;
    }
    while ((n >= 0) &&
	   ((cmd[n] == ' ') ||
	    (cmd[n] == '\t') ||
	    (cmd[n] == '\n')));
    n++;

    /* Ensure we're terminated properly */
    if (n > 0)
    {
	cmd_ptr = new char[n+3];
	strncpy(cmd_ptr, cmd, n);
	if (cmd[n-1] != '&')
	{
	    /* Need to append & */
	    cmd_ptr[n++]='&';
	}
	cmd_ptr[n++]='\n'; /* Run command */
	cmd_ptr[n]='\0'; /* Terminate string */

	rv = send_command(cmd_ptr);

	delete[] cmd_ptr;
    }
    else
    {
	/* no command to run */
	rv = true;
    }

    return rv;
}

int main (int argc, char** argv)
{
    char command[1024] = "";
    int n;

    /* Set up the command to run straight off our command line */
    for (n = 1; n < argc; n++)
    {
	fprintf(stderr, "Arg %d is %s\n", n, argv[n]);
	strcat(command, argv[n]);
	strcat(command, " ");
    }
    fprintf(stderr, "Command to be piped is: %s\n",
	    command);

    /* Start a shell */
    if (start_shell())
    {
	/* and run the command */
	run_command(command);

	/* wait 10s */
	Sleep(10000);

	/* Clean up */
	exit_shell();
    }
    else
    {
	APP_ERROR("Couldn't start shell",
		  UNRECOVERABLE);
    }

    return 0;
}

--
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple
Problem reports:       http://cygwin.com/problems.html
Documentation:         http://cygwin.com/docs.html
FAQ:                   http://cygwin.com/faq/

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]