How to detect when subprocess asks for input in Windows

后端 未结 2 1315
名媛妹妹
名媛妹妹 2021-02-20 17:12

I have a subprocess that either quits with a returncode, or asks something and waits for user input.

I would like to detect when the process asks the question and quit i

相关标签:
2条回答
  • 2021-02-20 17:53

    if we not want let to child process process user input, but simply kill it in this case, solution can be next:

    • start child process with redirected stdin to pipe.
    • pipe server end we create in asynchronous mode and main set pipe buffer to 0 size
    • before start child - write 1 byte to this pipe.
    • because pipe buffer is 0 size - operation not complete, until another side not read this byte
    • after we write this 1 byte and operation in progress (pending) - start child process.
    • finally begin wait what complete first: write operation or child process ?
    • if write complete first - this mean, that child process begin read from stdin - so kill it at this point

    one possible implementation on c++:

    struct ReadWriteContext : public OVERLAPPED
    {
        enum OpType : char { e_write, e_read } _op;
        BOOLEAN _bCompleted;
    
        ReadWriteContext(OpType op) : _op(op), _bCompleted(false)
        {
        }
    };
    
    VOID WINAPI OnReadWrite(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, OVERLAPPED* lpOverlapped)
    {
        static_cast<ReadWriteContext*>(lpOverlapped)->_bCompleted = TRUE;
        DbgPrint("%u:%x %p\n", static_cast<ReadWriteContext*>(lpOverlapped)->_op, dwErrorCode, dwNumberOfBytesTransfered);
    }
    
    void nul(PCWSTR lpApplicationName)
    {
        ReadWriteContext wc(ReadWriteContext::e_write), rc(ReadWriteContext::e_read);
    
        static const WCHAR pipename[] = L"\\\\?\\pipe\\{221B9EC9-85E6-4b64-9B70-249026EFAEAF}";
    
        if (HANDLE hPipe = CreateNamedPipeW(pipename, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, 
            PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, 1, 0, 0, 0, 0))
        {
            static SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
            PROCESS_INFORMATION pi;
            STARTUPINFOW si = { sizeof(si)};
            si.dwFlags = STARTF_USESTDHANDLES;
            si.hStdInput = CreateFileW(pipename, FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0, &sa, OPEN_EXISTING, 0, 0);
    
            if (INVALID_HANDLE_VALUE != si.hStdInput)
            {
                char buf[256];
    
                if (WriteFileEx(hPipe, "\n", 1, &wc, OnReadWrite))
                {
                    si.hStdError = si.hStdOutput = si.hStdInput;
    
                    if (CreateProcessW(lpApplicationName, 0, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &si, &pi))
                    {
                        CloseHandle(pi.hThread);
    
                        BOOLEAN bQuit = true;
    
                        goto __read;
                        do 
                        {
                            bQuit = true;
    
                            switch (WaitForSingleObjectEx(pi.hProcess, INFINITE, TRUE))
                            {
                            case WAIT_OBJECT_0:
                                DbgPrint("child terminated\n");
                                break;
                            case WAIT_IO_COMPLETION:
                                if (wc._bCompleted)
                                {
                                    DbgPrint("child read from hStdInput!\n");
                                    TerminateProcess(pi.hProcess, 0);
                                }
                                else if (rc._bCompleted)
                                {
    __read:
                                    rc._bCompleted = false;
                                    if (ReadFileEx(hPipe, buf, sizeof(buf), &rc, OnReadWrite))
                                    {
                                        bQuit = false;
                                    }
                                }
                                break;
                            default:
                                __debugbreak();
                            }
                        } while (!bQuit);
    
                        CloseHandle(pi.hProcess);
                    }
                }
    
                CloseHandle(si.hStdInput);
    
                // let execute pending apc
                SleepEx(0, TRUE);
            }
    
            CloseHandle(hPipe);
        }
    }
    

    another variant of code - use event completion, instead apc. however this not affect final result. this variant of code give absolute the same result as first:

    void nul(PCWSTR lpApplicationName)
    {
        OVERLAPPED ovw = {}, ovr = {};
    
        if (ovr.hEvent = CreateEvent(0, 0, 0, 0))
        {
            if (ovw.hEvent = CreateEvent(0, 0, 0, 0))
            {
                static const WCHAR pipename[] = L"\\\\?\\pipe\\{221B9EC9-85E6-4b64-9B70-249026EFAEAF}";
    
                if (HANDLE hPipe = CreateNamedPipeW(pipename, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, 
                    PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, 1, 0, 0, 0, 0))
                {
                    static SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
                    PROCESS_INFORMATION pi;
                    STARTUPINFOW si = { sizeof(si)};
                    si.dwFlags = STARTF_USESTDHANDLES;
                    si.hStdInput = CreateFileW(pipename, FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0, &sa, OPEN_EXISTING, 0, 0);
    
                    if (INVALID_HANDLE_VALUE != si.hStdInput)
                    {
                        char buf[256];
    
                        if (!WriteFile(hPipe, "\n", 1, 0, &ovw) && GetLastError() == ERROR_IO_PENDING)
                        {
                            si.hStdError = si.hStdOutput = si.hStdInput;
    
                            if (CreateProcessW(lpApplicationName, 0, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &si, &pi))
                            {
                                CloseHandle(pi.hThread);
    
                                BOOLEAN bQuit = true;
    
                                HANDLE h[] = { ovr.hEvent, ovw.hEvent, pi.hProcess };
    
                                goto __read;
                                do 
                                {
                                    bQuit = true;
    
                                    switch (WaitForMultipleObjects(3, h, false, INFINITE))
                                    {
                                    case WAIT_OBJECT_0 + 0://read completed
    __read:
                                        if (ReadFile(hPipe, buf, sizeof(buf), 0, &ovr) || GetLastError() == ERROR_IO_PENDING)
                                        {
                                            bQuit = false;
                                        }
                                        break;
                                    case WAIT_OBJECT_0 + 1://write completed
                                        DbgPrint("child read from hStdInput!\n");
                                        TerminateProcess(pi.hProcess, 0);
                                        break;
                                    case WAIT_OBJECT_0 + 2://process terminated
                                        DbgPrint("child terminated\n");
                                        break;
                                    default:
                                        __debugbreak();
                                    }
                                } while (!bQuit);
    
                                CloseHandle(pi.hProcess);
                            }
                        }
    
                        CloseHandle(si.hStdInput);
                    }
    
                    CloseHandle(hPipe);
                    // all pending operation completed here.
                }
    
                CloseHandle(ovw.hEvent);
            }
    
            CloseHandle(ovr.hEvent);
        }
    }
    
    0 讨论(0)
  • 2021-02-20 18:00

    My idea to find out if the subprocess reads user input is to (ab)use the fact that file objects are stateful: if the process reads data from its stdin, we should be able to detect a change in the stdin's state.

    The procedure is as follows:

    1. Create a temporary file that'll be used as the subprocess's stdin
    2. Write some data to the file
    3. Start the process
    4. Wait a little while for the process to read the data (or not), then use the tell() method to find out if anything has been read from the file

    This is the code:

    import os
    import time
    import tempfile
    import subprocess
    
    # create a file that we can use as the stdin for the subprocess
    with tempfile.TemporaryFile() as proc_stdin:
        # write some data to the file for the subprocess to read
        proc_stdin.write(b'whatever\r\n')
        proc_stdin.seek(0)
    
        # start the thing
        cmd = ["python","ask.py"]
        proc = subprocess.Popen(cmd, stdin=proc_stdin, stdout=subprocess.PIPE)
    
        # wait for it to start up and do its thing
        time.sleep(1)
    
        # now check if the subprocess read any data from the file
        if proc_stdin.tell() == 0:
            print("it didn't take input")
        else:
            print("it took input")
    

    Ideally the temporary file could be replaced by some kind of pipe or something that doesn't write any data to disk, but unfortunately I couldn't find a way to make it work without a real on-disk file.

    0 讨论(0)
提交回复
热议问题