Issue with spawnv (tried spawnl, spawnlp and spawnvp, too)

Dear all:

I ran into an odd behavior, and I wonder whether anyone has encountered it before and possibly knows a fix/workaround/correction. Rather than post lots of code right away, I will describe it first.

The program is a .C source with a regular int main(). If I use spawnv directly with the no wait flag, the path/command and the arguments (not interested in the env part), it works nicely. However, if I try to put the same logic into a subroutine, it works sometimes. My goal is to feel the PGP.EXE command line which has switches, e.g., '-h' for help. Some output goes to stdout, some to stderr. I use something close to Microsoft's help example on _pipe to capture both outputs. I already moved the program to the same directory to avoid path and directory name issues. Submitting PGP.EXE works, and I get the output to use '-h' for help. Submitting PGP.EXE with parameter 'abc' works, and I get the error that it's not a valid file. However, PGP.EXE with parameter '-h' hangs up and does not come back. Again, if I have that logic in the main() function directly, it works in all cases.

I do not assume that everyone has PGP.EXE, so my more general question is: Are some characters like '-' restricted, and why does it make a difference whether I use argv directly or the passed address from within the form

In case it matters, I reroute stdout with one pipe and stderr with another before the spawn, then release it after. I view the captured output in the debugger with a breakpoint on the final return (0) statement after reading the buffers into strings. When I try to run 'PGP.EXE -h', the program hangs on the spawnv command, so it is not whatever I coded afterwards.

I will try to streamline the logic to narrow it down to the essentials before posting the code, and I will try to make it as identical as possible (main vs. subroutine) to narrow down sources of errors. But after six straight hours of trying it on my own, I resort to this forum for good advice. Someone who has dealt with this problem may know right away where to look.

My compiler version is C++ .net 2002, but the code is ANSI C as a console application without any extras (no ATL, no MFC).

Thanks in advance,

Wolfgang



Answer this question

Issue with spawnv (tried spawnl, spawnlp and spawnvp, too)

  • Ken Tucker MVP

    Dear Brian:

    Thanks for the reply and the test. I only used 'cmd' or 'cmd.exe', so it probably did not find it using spawnv. The choice of spawnv or spawnvp was just the latest snapshot of a couple of tests, should have made it the same. In my application, I will know the exact path. I wonder though whether I can detect that the program exists and not submit it blindly where it may hang up...

    PGP is a licensed program I can use at/for work. Use www.pgp.com to find out more, but it is not shareware and it is not exactly cheap for end users either. It turns out though that the key to my above problem was the buffer with P_WAIT. The help put out more than 512 bytes and less than 4096, so one worked and the other one did not. The best explanation I have - since I am new to pipes - is that the parent waits for the child and the child cannot write to the buffer, so I deadlock. I used the P_NOWAIT example coding in the help on '_pipe' in VC++ and it appears to work. I ran into issues, too, where a string/argv and argv/string conversion forces me to put in double backslashes so that C interprets it right as I want to pass the parameters to the routine as a string, not pointer array. Little tedious stuff that took hours out of my life but is hardly worth mentioning.

    I really only use the C part of this compiler, and I may want to port the app to UNIX or some other platform later. Of course, I will run into portability issues there, too.

    Help on help: Is there a way to mark this posting as complete or solved :-)

    Regards,

    Wolfgang


  • Sianny

    Wolfgang,

    I've had my share of struggles with this kind of code also. 

    I don't have pgp.exe, but running either version of main() runs correctly with cmd.exe.  I needed to provided a fully-qualified path to cmd.exe to the spawnv version, however. 

    Here's the correct output for the first version of main(), which surrounds the received stdout and stderr with exclamation points.

    E:\tempprojects\wolfgang\debug>wolfgang C:\WINDOWS\system32\cmd.exe /c dir
    Out: ! Volume in drive E is Disk2
     Volume Serial Number is 54CA-9EDD

     Directory of E:\tempprojects\wolfgang\debug

    03/15/2006  04:22 PM    <DIR>          .
    03/15/2006  04:22 PM    <DIR>          ..
    03/15/2006  04:30 PM            49,152 wolfgang.exe
    03/15/2006  04:30 PM           434,240 wolfgang.ilk
    03/15/2006  04:30 PM           551,936 wolfgang.pdb
                   3 File(s)      1,035,328 bytes
                   2 Dir(s)  40,581,881,856 bytes free!
    Err: !!

    Does it hang on cmd.exe for you, or just on pgp.exe   Could you point me to pgp.exe  

    I don't believe there is any special processing of hyphen characters.

    To address your concern about the size of stdout and stderr text, I would make no assumption about the size of the output.  I would enter a loop to read 4096 (whatever) characters at a time, null-terminating each chunk, and appending the result to a std::string object.  Looking back at your code, I can see that you've attempted something like this, but your out_res is a fixed buffer.  The advantage of using std::string is that it will automatically grow when you use the append() function.

    Finally, I'm unclear on why you use spawnv in one version and spawnvp in another.  Spawnvp is usually a better choice since it uses the PATH environment variable to locate the executable.

    Brian

     


  • duck123

    Hi Wolfgang,

    I guess you are doing something related with security (encryption), so lets see.

    What happens if you call any other command line program with a switch like yours (-h) and in the same conditions (using spanwv with no wait, from within a subroutine and with the stdoutput redirection)

    This is just to see if this occurs in a generic fashion.

    If the program doesn't hang, the problem could be a conflict with your PGP.EXE module.

    If hangs again, we can focus on spawn functions and stdouput redirection to see if there is any weird issue.

    =)



  • Mircea Cimpoi

    In my version, I use P_NOWAIT, then pipe in any stuff into the process' stdin, then do a _cwait, then read the stderr/stdout.

    I agree with you... this process/pipe stuff is tedious and requires guesswork to really get it right.  I've gotten deadlock problems also.

    I've written a couple classes ProcessBase and PipeClass (Pipe was taken), that hopefully soon will work against Cygwin and Linux--I've had to put in some temporary hacks because ironically the Windows version (as I've written it) runs more reliably than the *nix one.  ProcessBase is meant to be a base class for a class that wants to wrap an app (trying to take old crickety programs written in Fortran and pretend that it can play nice in a .NET ecosystem).  It also has an option to send and receive text interactively.  PipeClass encapsulates all the busywork for pipes.  I am not really sure if it's ready to give out, but if you're desperate you can email me and we can talk it over.

    Moderators can mark this thread as solved.  I've been recently anointed as such, so I'll go ahead and do that.

    Brian

     


  • alinx

    Dear Harold:

    Thanks for the fast reply. I can pass the string '-h' to a 'hello world' program I wrote, but I don't parse the command line for flags, and I don't write to stderr (pgp -h writes to stderr only), so there may be more to recreating the error with another program. At this point I am not sure whether it's the spawnv, the pipes or the combination thereof. It just baffles me that it works from main() and not from say sub(), which indicates that the program I submit does not play much of a role. I thought I could say something like:

    int main(int argc, char *argv[]) {

    sub( argv );

    }

    void sub( char **argv ) {

    spawnv( P_WAIT, argv[1], &argv[1] );

    }

    which would be equivalent to:

    int main(int argc, char *argv[]) {

    spawnv( P_WAIT, argv[1], &argv[1] );

    }

    (Note: I just typed this without syntax check, so I may be a little off.)

    I also noticed that 'cmd /c dir' or 'cmd /c dir *.*' does not return anything, but at least it does not hang up. I am trying to submit more or less any program with parameters from a routine other than main() - pgp.exe (the command line version) is just one example.

    Here is the code. It runs as is, but if you rename main to xmain and vv., it hangs up. The command parameters are 'pgp.exe -h'.

    #include <windows.h>

    #include <process.h>

    #include <fcntl.h>

    #include <stdio.h>

    #include <io.h>

    #define OUT_BUFF_SIZE 1024

    #define ERR_BUFF_SIZE 1024

    #define READ_HANDLE 0

    #define WRITE_HANDLE 1

    int spawn_command( char **, char *, int, char *, int );

    int xmain(int argc, char* argv[])

    {

    int

    ret_code;

    char

    out_result[32*OUT_BUFF_SIZE+1],

    err_result[16*ERR_BUFF_SIZE+1];

    if( argc >= 2 )

    {

    ret_code = spawn_command( argv, out_result, 32, err_result, 16 );

    printf( "Out: !%s!\n", out_result );

    printf( "Err: !%s!\n", err_result );

    return ret_code;

    }

    else

    return -1;

    }

    int spawn_command( char* argv[], char *out_res, int out_size, char *err_res, int err_size )

    {

    int

    i,

    hStdOut,

    hStdOutPipe[2],

    hStdErr,

    hStdErrPipe[2],

    nRead;

    char

    *ptr,

    szBufferOut[OUT_BUFF_SIZE+1],

    szBufferErr[ERR_BUFF_SIZE+1];

    HANDLE

    hProcess;

    // Initialize

    *out_res = 0;

    *err_res = 0;

    // Create the pipes

    if( _pipe( hStdOutPipe, 512, O_BINARY | O_NOINHERIT ) == -1 )

    return 1;

    if( _pipe( hStdErrPipe, 512, O_BINARY | O_NOINHERIT ) == -1 )

    return 2;

    // Store handles of original stdout and stderr

    hStdOut = _dup( _fileno( stdout ) );

    hStdErr = _dup( _fileno( stderr ) );

    // Duplicate write end of pipe to stdout handle

    if( _dup2( hStdOutPipe[WRITE_HANDLE], _fileno( stdout ) ) != 0 )

    return 3;

    if( _dup2( hStdErrPipe[WRITE_HANDLE], _fileno( stderr ) ) != 0 )

    return 4;

    // Close write end of local pipes

    close( hStdOutPipe[WRITE_HANDLE] );

    close( hStdErrPipe[WRITE_HANDLE] );

    // Spawn process

    hProcess = (HANDLE) spawnv( P_WAIT, argv[1], (const char* const*) &argv[1] );

    // Restore original handles

    if( _dup2( hStdOut, _fileno( stdout ) ) != 0 )

    return 5;

    if( _dup2( hStdErr, _fileno( stderr ) ) != 0 )

    return 6;

    // Close local copies of original stdout and stderr

    close( hStdOut );

    close( hStdErr );

    // Copy stdout buffer to string

    for( i = 0, ptr = out_res; i < out_size; i++ )

    {

    nRead = read( hStdOutPipe[READ_HANDLE], szBufferOut, OUT_BUFF_SIZE );

    if( nRead )

    {

    memcpy( ptr, szBufferOut, nRead );

    ptr[nRead] = 0;

    ptr += nRead;

    }

    else

    break;

    }

    // Remove CR/LFs from the end

    --ptr;

    while( ( ptr >= out_res ) && ( *ptr == '\r' || *ptr == '\n' ) )

    *ptr-- = 0;

    // Copy stderr buffer to string

    for( i = 0, ptr = err_res; i < err_size; i++ )

    {

    nRead = read( hStdErrPipe[READ_HANDLE], szBufferErr, ERR_BUFF_SIZE );

    if( nRead )

    {

    memcpy( ptr, szBufferErr, nRead );

    ptr[nRead] = 0;

    ptr += nRead;

    }

    else

    break;

    }

    // Remove CR/LFs from the end

    --ptr;

    while( ( ptr >= err_res ) && ( *ptr == '\r' || *ptr == '\n' ) )

    *ptr-- = 0;

    // Done

    return 0;

    }

    int main(int argc, char* argv[])

    {

    int

    nExitCode = STILL_ACTIVE;

    if( argc >= 2 )

    {

    HANDLE

    hProcess;

    int

    hStdOut,

    hStdOutPipe[2],

    hStdErr,

    hStdErrPipe[2],

    nOutRead,

    nErrRead;

    char

    szBufferOut[OUT_BUFF_SIZE+1],

    szBufferErr[ERR_BUFF_SIZE+1];

    // Create the pipe

    if( _pipe( hStdOutPipe, 4096, O_BINARY | O_NOINHERIT ) == -1 )

    return 1;

    if( _pipe( hStdErrPipe, 4096, O_BINARY | O_NOINHERIT ) == -1 )

    return 1;

    // Initialize

    *szBufferOut = 0;

    *szBufferErr = 0;

    // Duplicate stdout handle (next line will close original)

    hStdOut = _dup( _fileno( stdout ) );

    hStdErr = _dup( _fileno( stderr ) );

    // Duplicate write end of pipe to stdout handle

    if( _dup2( hStdOutPipe[WRITE_HANDLE], _fileno( stdout ) ) != 0 )

    return 2;

    if( _dup2( hStdErrPipe[WRITE_HANDLE], _fileno( stderr ) ) != 0 )

    return 2;

    // Close original write end of pipe

    close( hStdOutPipe[WRITE_HANDLE] );

    close( hStdErrPipe[WRITE_HANDLE] );

    // Spawn process

    hProcess = (HANDLE) spawnvp( P_WAIT, argv[1], (const char* const*) &argv[1] );

    // Duplicate copy of original stdout back into stdout

    if( _dup2( hStdOut, _fileno( stdout ) ) != 0 )

    return 3;

    if( _dup2( hStdErr, _fileno( stderr ) ) != 0 )

    return 3;

    // Close duplicate copy of original stdout

    close( hStdOut );

    close( hStdErr );

    // Read pipes

    nErrRead = read( hStdErrPipe[READ_HANDLE], szBufferErr, ERR_BUFF_SIZE );

    nOutRead = read( hStdOutPipe[READ_HANDLE], szBufferOut, OUT_BUFF_SIZE );

    }

    return 0;

    }

    Thanks,

    Wolfgang


  • mausau

    One more thing: '-h' produces a lot of help, so what happens, if the buffer in the pipe is too small Would that cause a hang-up
  • Issue with spawnv (tried spawnl, spawnlp and spawnvp, too)