0

I have written a program that implements a tiny shell to process commands from the user. If the entered command is recognised as internal command, my program executes this command.

These commands are implemented as internal functions and their output is being processed by another internal function that is able to send the text to the console and / or to file for logging purposes.

If the entered command is not recognised, I try to execute the entered command as part of the windows command shell, e.g. : cmd dir would execute the dir command and the output gets printed on the console. This is done via CreateProcess. Until now I did not specify the members hStdError, hStdOutput and hStdInput of the STARTUPINFO parameter.

I tried to implement and adapt the example of Creating a Child Process with Redirected Input and Output.

I did not use their implementation of the child process, but tried to get the output of the dir command into my application:

#include "pch.h"
#include <windows.h>

#define BUFSIZE 512

HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;



PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
void CreateChildProcess()
// Create a child process that uses the previously created pipes for STDIN and STDOUT.
{
    TCHAR szCmdline[] = TEXT("cmd /c dir q:\\Sicherung\\Bilder /s");
    BOOL bSuccess = FALSE;

    // Set up members of the PROCESS_INFORMATION structure. 

    ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));

    // Set up members of the STARTUPINFO structure. 
    // This structure specifies the STDIN and STDOUT handles for redirection.

    ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
    siStartInfo.cb = sizeof(STARTUPINFO);
    siStartInfo.hStdError = g_hChildStd_OUT_Wr;
    siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
    siStartInfo.hStdInput = g_hChildStd_IN_Rd;
    siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

    // Create the child process. 

    bSuccess = CreateProcess(NULL,
        szCmdline,     // command line 
        NULL,          // process security attributes 
        NULL,          // primary thread security attributes 
        TRUE,          // handles are inherited 
        0,             // creation flags 
        NULL,          // use parent's environment 
        NULL,          // use parent's current directory 
        &siStartInfo,  // STARTUPINFO pointer 
        &piProcInfo);  // receives PROCESS_INFORMATION 

     // If an error occurs, exit the application. 
    if (!bSuccess)
        return; // ErrorExit(("CreateProcess"));
    else
    {
        // Close handles to the child process and its primary thread.
        // Some applications might keep these handles to monitor the status
        // of the child process, for example. 

        //CloseHandle(piProcInfo.hProcess);
        //CloseHandle(piProcInfo.hThread);
    }
}
void ReadFromPipe(void)
// Read output from the child process's pipe for STDOUT
// and write to the parent process's pipe for STDOUT. 
// Stop when there is no more data. 
{
    DWORD dwRead, dwWritten;
    CHAR chBuf[BUFSIZE];
    BOOL bSuccess = FALSE;
    HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

    for (;;)
    {
        DWORD objectstat = WAIT_TIMEOUT;
        //do
        //{
        //  objectstat = WaitForSingleObject(piProcInfo.hProcess, 0);
        //} while (objectstat != WAIT_OBJECT_0);
        memset(&chBuf[0], 0x00, BUFSIZE);
        bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
        if (!bSuccess)
            break;

        bSuccess = WriteFile(hParentStdOut, chBuf,
            dwRead, &dwWritten, NULL);
        if (!bSuccess) 
            break;
        if (dwRead == 0)
            break;
    }
}
int main()
{
    SECURITY_ATTRIBUTES saAttr;

    printf("\n->Start of parent execution.\n");

    // Set the bInheritHandle flag so pipe handles are inherited. 

    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;

    // Create a pipe for the child process's STDOUT. 
    if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
        return -1;// ErrorExit("StdoutRd CreatePipe");

    // Ensure the read handle to the pipe for STDOUT is not inherited.

    if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
        return -2;// ErrorExit(("Stdout SetHandleInformation"));

    // Create a pipe for the child process's STDIN. 

    if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
        return -3 ;// ErrorExit(("Stdin CreatePipe"));

    // Ensure the write handle to the pipe for STDIN is not inherited. 

    if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
        return -4;// ErrorExit(("Stdin SetHandleInformation"));

    // Create the child process. 
    CreateChildProcess();
    ReadFromPipe();
    CloseHandle(piProcInfo.hProcess);
    CloseHandle(piProcInfo.hThread);
    return 0;
}

I know, that the problem has to be with ReadFile. I can not determine when all output of the dir command has been processed. Checking dwRead for 0 or for BUFSIZE does not work. dwReadnever becomes 0, and it can happen that it is less than BUFSIZE, because the dir command is not fast enough.

So, how am i supposed to end processing of the pipe data?

Wolfgang Roth
  • 451
  • 4
  • 18
  • This is clearly meant to be code for C, except for `#include ` (whose provided features are not used). which language are you actually using? – François Andrieux Oct 02 '19 at 18:23
  • 1
    I would point out that there are more idiomatic ways of enumerating the contents of a directory in Windows, namely, [`FindFirstFileW`](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilew), [`FindNextFileW`](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findnextfilew) and [`FindClose`](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findclose). You could just take note of the current directory and the fact that the user typed `dir` and process the contents that way. – Govind Parmar Oct 02 '19 at 18:26
  • You say `dwRead` never becomes 0. If that is the case, what are you getting after the `dir` command stops outputting? – Jim Rhodes Oct 02 '19 at 18:29
  • The only way to know when the output is finished is to detect when the program that is generating the output is finished, i.e. it exits. – Mark Ransom Oct 02 '19 at 18:30
  • @JimRhodes: I can compare the output of my program with the output of the dir command issued on a command line itself. When my program has read all characters (or bytes) of the pipe, it simply blocks in the function ReadFile(). – Wolfgang Roth Oct 02 '19 at 18:48
  • @FrançoisAndrieux: I copied the code from the given link, and did not think much about eliminating unnecessary includes :-) But you are right, it should work with plain C – Wolfgang Roth Oct 02 '19 at 18:49
  • @GovindParmar: I know, that the dir command is not the best example to being used here, but it gives a good example on what I am trying to achieve. – Wolfgang Roth Oct 02 '19 at 18:50
  • @MarkRansom: I tried to use WaitForSingleObject on the process handle, but it seems like the process does never exit (at least not in my example here). I suppose, when I close the pipe, the process would exit. But closing the pipe before all data have arrrived is not an option. And I cant know when all data has arrived. – Wolfgang Roth Oct 02 '19 at 18:52
  • In order to make the `ReadFile` call non-blocking, you will have to use overlapped I/O. That is, the handle needs to be opened with FILE_FLAG_OVERLAPPED. I don't think `CreatePipe` will let you do that but `CreateNamedPipe` will. – Jim Rhodes Oct 02 '19 at 19:04
  • @JimRhodes: `CreatePipe` can not be used asynchronously, thats right. I tried to use a Named pipe, but it seems like the pipe waits for the other end to connect (`GetLastError` returns 536, 0x218)... – Wolfgang Roth Oct 02 '19 at 19:36
  • @WolfgangRoth yes. You can either 1) `CreateNamedPipe()` a listening pipe with a unique name, then `CreateFile()` a connecting pipe to it; 2) `NtOpenFile()` to create an unnamed pipe and `ZwCreateNamedPipeFile()` to connect to it. Either way will give you the two pipe handles you need for `CreateProcess()`. See [Overlapped I/O on anonymous pipe](https://stackoverflow.com/questions/60645/) – Remy Lebeau Oct 02 '19 at 19:53
  • @RemyLebeau: please see my answer to the question... – Wolfgang Roth Oct 02 '19 at 19:59
  • @WolfgangRoth I see no answer from you. I was replying to your "*it seems like the pipe waits for the other end to connect*" comment. If you use `CreateNamedPipe()` then you need to use `CreateFile()` as well. – Remy Lebeau Oct 02 '19 at 20:03
  • @RemyLebeau: try now, it took me a while to edit. – Wolfgang Roth Oct 02 '19 at 20:08

1 Answers1

0

Ok, after i searched some different terms in google, I came up with this link to stackoverflow ;) : How to read output from cmd.exe using CreateProcess() and CreatePipe()

Ian Boyd wrote there :

Once you've launched your child process: be sure to close those ends of the pipe you no longer need.

result = CreateProcess(...);



//CreateProcess demands that we close these two populated handles when we're done with them. We're done with them.

CloseHandle(pi.hProcess);

CloseHandle(pi.hThread);



/*

   We've given the console app the writable end of the pipe during CreateProcess; we don't need it anymore.

   We do keep the handle for the *readable* end of the pipe; as we still need to read from it.

   The other reason to close the writable-end handle now is so that there's only one out-standing reference to the writeable end: held by the console app.

   When the app closes, it will close the pipe, and ReadFile will return code 109 (The pipe has been ended).

   That's how we'll know the console app is done. (no need to wait on process handles with buggy infinite waits)

*/

CloseHandle(g_hChildStd_OUT_Wr);

g_hChildStd_OUT_Wr = 0;

CloseHandle(g_hChildStd_IN_Rd);

g_hChildStd_OUT_Wr = 0;

The common problem with most solutions is that people try to wait on a process handle. There are many problems with this; the main one being that if you wait for the child the terminate, the child will never be able to terminate.

After closing the unneeded handles ReadFile works as expected.

Wolfgang Roth
  • 451
  • 4
  • 18