views:

65

answers:

1

When I use CreateProcess to create process adb.exe, It will Block in ReadFile.

void KillAdbProcess()
{
    DWORD aProcesses[1024], cbNeeded, cProcesses;
    unsigned int i;

    if ( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded ) )
        return;

    cProcesses = cbNeeded / sizeof(DWORD);

    for ( i = 0; i < cProcesses; i++ )
        if( aProcesses[i] != 0 ){
            bool shouldKill =false;
            wchar_t szProcessName[MAX_PATH] = L"<unknown>";

                //Get a handle to the process.
                HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |
                                   PROCESS_VM_READ | PROCESS_TERMINATE,
                                   FALSE, aProcesses[i] );
                if (NULL != hProcess )
                {
                    HMODULE hMod;
                    DWORD cbNeeded;

                    if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), 
                         &cbNeeded) )
                    {
                        GetModuleFileNameExW( hProcess, hMod, szProcessName, 
                                           sizeof(szProcessName)/sizeof(TCHAR));
                        int len = wcslen(szProcessName);
                        if(!wcscmp(L"\\adb.exe",szProcessName+len-8)){
                            shouldKill = true;
                        }

                    }
                }

                if(shouldKill) TerminateProcess(hProcess,0);
                CloseHandle( hProcess );
        }

} 

int testadb(){
    KillAdbProcess();
    char buff[4096] = {0};
    int len = sizeof(buff);
    DWORD exitCode = 0;

    SECURITY_ATTRIBUTES   sa;   
    ZeroMemory(&sa, sizeof(sa)); 
    sa.bInheritHandle = TRUE;   
    sa.lpSecurityDescriptor = NULL;   
    sa.nLength = sizeof(sa);

    HANDLE hOutputReadTmp,hOutputRead,hOutputWrite;
    // Create the child output pipe.
    if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,&sa,0))
        return false;

    // 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))
        return false;

    // Close inheritable copies of the handles you do not want to be
    // inherited.
    if (!CloseHandle(hOutputReadTmp)) return false;


    PROCESS_INFORMATION  pi; 
    ZeroMemory(&pi, sizeof(pi));   
    STARTUPINFOW  si;
    GetStartupInfoW(&si);

    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESTDHANDLES;
    si.wShowWindow = SW_HIDE;
    si.hStdInput   = NULL;
    if(buff) {
        si.hStdOutput   =   hOutputWrite;   
        si.hStdError = hOutputWrite;
    } else {
        si.hStdOutput   =  NULL;   
        si.hStdError = NULL;
    }

    wchar_t cmdBuf[512] = L"adb.exe start-server";
    if( !::CreateProcessW(NULL, cmdBuf, NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &si, &pi) )
    {
        exitCode = -1;
        goto exit;
    }

    ::CloseHandle(hOutputWrite);
    hOutputWrite = NULL;

    len--; //keep it for string end char.
    DWORD dwBytes = 0;
    DWORD dwHasRead = 0;
    while(::ReadFile(hOutputRead, buff+dwHasRead, len-dwHasRead, &dwBytes, NULL))
    {
        printf("read byte=%d\n",dwBytes);
        if(0 == dwBytes) break;
        dwHasRead += dwBytes;
        //GetExitCodeProcess(pi.hProcess, &exitCode);
        //if(STILL_ACTIVE != exitCode) break;
        if(dwHasRead >= len) break;
    }
    buff[dwHasRead] = 0;


    ::GetExitCodeProcess(pi.hProcess, &exitCode);

exit:


    if(hOutputRead) ::CloseHandle(hOutputRead);
    if(hOutputWrite) ::CloseHandle(hOutputWrite);

    ::CloseHandle(pi.hProcess);
    ::CloseHandle(pi.hThread);
    return 0;
}

If I change code to

while(::ReadFile(hOutputRead, buff+dwHasRead, len-dwHasRead, &dwBytes, NULL))
{
    printf("read byte=%d\n",dwBytes);
    if(0 == dwBytes) break;
    dwHasRead += dwBytes;
    GetExitCodeProcess(pi.hProcess, &exitCode);
    if(STILL_ACTIVE != exitCode) break;
    if(dwHasRead >= len) break;
}

it works, but when I delete printf code, it will block again.

while(::ReadFile(hOutputRead, buff+dwHasRead, len-dwHasRead, &dwBytes, NULL))
{
    if(0 == dwBytes) break;
    dwHasRead += dwBytes;
    GetExitCodeProcess(pi.hProcess, &exitCode);
    if(STILL_ACTIVE != exitCode) break;
    if(dwHasRead >= len) break;
}

In the code of adb.exe, I see some code like belows:

#if ADB_HOST
int launch_server()
{
#ifdef HAVE_WIN32_PROC
    /* we need to start the server in the background                    */
    /* we create a PIPE that will be used to wait for the server's "OK" */
    /* message since the pipe handles must be inheritable, we use a     */
    /* security attribute                                               */
    HANDLE                pipe_read, pipe_write;
    SECURITY_ATTRIBUTES   sa;
    STARTUPINFO           startup;
    PROCESS_INFORMATION   pinfo;
    char                  program_path[ MAX_PATH ];
    int                   ret;

    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    /* create pipe, and ensure its read handle isn't inheritable */
    ret = CreatePipe( &pipe_read, &pipe_write, &sa, 0 );
    if (!ret) {
        fprintf(stderr, "CreatePipe() failure, error %ld\n", GetLastError() );
        return -1;
    }

    SetHandleInformation( pipe_read, HANDLE_FLAG_INHERIT, 0 );

    ZeroMemory( &startup, sizeof(startup) );
    startup.cb = sizeof(startup);
    startup.hStdInput  = GetStdHandle( STD_INPUT_HANDLE );
    startup.hStdOutput = pipe_write;
    startup.hStdError  = GetStdHandle( STD_ERROR_HANDLE );
    startup.dwFlags    = STARTF_USESTDHANDLES;

    ZeroMemory( &pinfo, sizeof(pinfo) );

    /* get path of current program */
    GetModuleFileName( NULL, program_path, sizeof(program_path) );

    ret = CreateProcess(
            program_path,                              /* program path  */
            "adb fork-server server",
                                    /* the fork-server argument will set the
                                       debug = 2 in the child           */
            NULL,                   /* process handle is not inheritable */
            NULL,                    /* thread handle is not inheritable */
            TRUE,                          /* yes, inherit some handles */
            DETACHED_PROCESS, /* the new process doesn't have a console */
            NULL,                     /* use parent's environment block */
            NULL,                    /* use parent's starting directory */
            &startup,                 /* startup info, i.e. std handles */
            &pinfo );

    CloseHandle( pipe_write );

    if (!ret) {
        fprintf(stderr, "CreateProcess failure, error %ld\n", GetLastError() );
        CloseHandle( pipe_read );
        return -1;
    }

    CloseHandle( pinfo.hProcess );
    CloseHandle( pinfo.hThread );

    /* wait for the "OK\n" message */
    {
        char  temp[3];
        DWORD  count;

        ret = ReadFile( pipe_read, temp, 3, &count, NULL );
        CloseHandle( pipe_read );
        if ( !ret ) {
            fprintf(stderr, "could not read ok from ADB Server, error = %ld\n", GetLastError() );
            return -1;
        }
        if (count != 3 || temp[0] != 'O' || temp[1] != 'K' || temp[2] != '\n') {
            fprintf(stderr, "ADB server didn't ACK\n" );
            return -1;
        }
    }
#elif defined(HAVE_FORKEXEC)
    char    path[PATH_MAX];
    int     fd[2];

    // set up a pipe so the child can tell us when it is ready.
    // fd[0] will be parent's end, and fd[1] will get mapped to stderr in the child.
    if (pipe(fd)) {
        fprintf(stderr, "pipe failed in launch_server, errno: %d\n", errno);
        return -1;
    }
    get_my_path(path);
    pid_t pid = fork();
    if(pid < 0) return -1;

    if (pid == 0) {
        // child side of the fork

        // redirect stderr to the pipe
        // we use stderr instead of stdout due to stdout's buffering behavior.
        adb_close(fd[0]);
        dup2(fd[1], STDERR_FILENO);
        adb_close(fd[1]);

        // child process
        int result = execl(path, "adb", "fork-server", "server", NULL);
        // this should not return
        fprintf(stderr, "OOPS! execl returned %d, errno: %d\n", result, errno);
    } else  {
        // parent side of the fork

        char  temp[3];

        temp[0] = 'A'; temp[1] = 'B'; temp[2] = 'C';
        // wait for the "OK\n" message
        adb_close(fd[1]);
        int ret = adb_read(fd[0], temp, 3);
        adb_close(fd[0]);
        if (ret < 0) {
            fprintf(stderr, "could not read ok from ADB Server, errno = %d\n", errno);
            return -1;
        }
        if (ret != 3 || temp[0] != 'O' || temp[1] != 'K' || temp[2] != '\n') {
            fprintf(stderr, "ADB server didn't ACK\n" );
            return -1;
        }

        setsid();
    }
#else
#error "cannot implement background server start on this platform"
#endif
    return 0;
}
#endif

I think the child process of adb.exe inherit the handle of adb.exe, if the child process of adb.exe doesn't exit, ReadFile will block for ever. But when I exec "adb.exe start-server" in command, all is Ok. So how does windows command call CreateProcess and ReadFile?

A: 

I have found the answer:http://www.codeproject.com/KB/threads/redir.aspx.

The technique of redirecting the input/output of a console process is very sample: The CreateProcess() API through the STARTUPINFO structure enables us to redirect the standard handles of a child console based process. So we can set these handles to either a pipe handle, file handle, or any handle that we can read and write. The detail of this technique has been described clearly in MSDN: HOWTO: Spawn Console Processes with Redirected Standard Handles.

However, MSDN's sample code has two big problem. First, it assumes the child process will send output at first, then wait for input, then flush the output buffer and exit. If the child process doesn't behave like that, the parent process will be hung up. The reason of this is the ReadFile() function remains blocked untill the child process sends some output, or exits.

Second, It has problem to redirect a 16-bit console (including console based MS-DOS applications.) On Windows 9x, ReadFile remains blocked even after the child process has terminated; On Windows NT/XP, ReadFile always returns FALSE with error code set to ERROR_BROKEN_PIPE if the child process is a DOS application.

Solving the block problem of ReadFile

To prevent the parent process from being blocked by ReadFile, we can simply pass a file handle as stdout to the child process, then monitor this file. A more simple way is to call PeekNamedPipe() function before calling ReadFile(). The PeekNamedPipe function checks information about data in the pipe, then returns immediately. If there's no data available in the pipe, don't call ReadFile.

By calling PeekNamedPipe before ReadFile, we also solve the block problem of redirecting a 16-bit console on Windows 9x.

xufan