Search code examples
cwindowshttp-redirectreadfile

How do I process the output of "dir"?


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?


Solution

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