| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). |
| ** All rights reserved. |
| ** Contact: Nokia Corporation (qt-info@nokia.com) |
| ** |
| ** This file is part of the Qt3Support module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** GNU Lesser General Public License Usage |
| ** This file may be used under the terms of the GNU Lesser General Public |
| ** License version 2.1 as published by the Free Software Foundation and |
| ** appearing in the file LICENSE.LGPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU Lesser |
| ** General Public License version 2.1 requirements will be met: |
| ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
| ** |
| ** In addition, as a special exception, Nokia gives you certain additional |
| ** rights. These rights are described in the Nokia Qt LGPL Exception |
| ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU General |
| ** Public License version 3.0 as published by the Free Software Foundation |
| ** and appearing in the file LICENSE.GPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU General |
| ** Public License version 3.0 requirements will be met: |
| ** http://www.gnu.org/copyleft/gpl.html. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qplatformdefs.h" |
| #include "q3process.h" |
| |
| #ifndef QT_NO_PROCESS |
| |
| #include "qapplication.h" |
| #include "q3cstring.h" |
| #include "q3ptrqueue.h" |
| #include "qtimer.h" |
| #include "qregexp.h" |
| #include "private/q3membuf_p.h" |
| #include "qt_windows.h" |
| |
| #ifdef Q_OS_WINCE |
| #define STARTF_USESTDHANDLES 1 |
| #endif |
| |
| QT_BEGIN_NAMESPACE |
| |
| //#define QT_Q3PROCESS_DEBUG |
| |
| /*********************************************************************** |
| * |
| * Q3ProcessPrivate |
| * |
| **********************************************************************/ |
| class Q3ProcessPrivate |
| { |
| public: |
| Q3ProcessPrivate( Q3Process *proc ) |
| { |
| stdinBufRead = 0; |
| pipeStdin[0] = 0; |
| pipeStdin[1] = 0; |
| pipeStdout[0] = 0; |
| pipeStdout[1] = 0; |
| pipeStderr[0] = 0; |
| pipeStderr[1] = 0; |
| exitValuesCalculated = false; |
| |
| lookup = new QTimer( proc ); |
| qApp->connect( lookup, SIGNAL(timeout()), |
| proc, SLOT(timeout()) ); |
| |
| pid = 0; |
| } |
| |
| ~Q3ProcessPrivate() |
| { |
| reset(); |
| } |
| |
| void reset() |
| { |
| while ( !stdinBuf.isEmpty() ) { |
| delete stdinBuf.dequeue(); |
| } |
| closeHandles(); |
| stdinBufRead = 0; |
| pipeStdin[0] = 0; |
| pipeStdin[1] = 0; |
| pipeStdout[0] = 0; |
| pipeStdout[1] = 0; |
| pipeStderr[0] = 0; |
| pipeStderr[1] = 0; |
| exitValuesCalculated = false; |
| |
| deletePid(); |
| } |
| |
| void closeHandles() |
| { |
| if( pipeStdin[1] != 0 ) { |
| CloseHandle( pipeStdin[1] ); |
| pipeStdin[1] = 0; |
| } |
| if( pipeStdout[0] != 0 ) { |
| CloseHandle( pipeStdout[0] ); |
| pipeStdout[0] = 0; |
| } |
| if( pipeStderr[0] != 0 ) { |
| CloseHandle( pipeStderr[0] ); |
| pipeStderr[0] = 0; |
| } |
| } |
| |
| void deletePid() |
| { |
| if ( pid ) { |
| CloseHandle( pid->hProcess ); |
| CloseHandle( pid->hThread ); |
| delete pid; |
| pid = 0; |
| } |
| } |
| |
| void newPid() |
| { |
| deletePid(); |
| pid = new PROCESS_INFORMATION; |
| memset( pid, 0, sizeof(PROCESS_INFORMATION) ); |
| } |
| |
| Q3Membuf bufStdout; |
| Q3Membuf bufStderr; |
| |
| Q3PtrQueue<QByteArray> stdinBuf; |
| |
| HANDLE pipeStdin[2]; |
| HANDLE pipeStdout[2]; |
| HANDLE pipeStderr[2]; |
| QTimer *lookup; |
| |
| PROCESS_INFORMATION *pid; |
| uint stdinBufRead; |
| |
| bool exitValuesCalculated; |
| }; |
| |
| |
| /*********************************************************************** |
| * |
| * Q3Process |
| * |
| **********************************************************************/ |
| void Q3Process::init() |
| { |
| d = new Q3ProcessPrivate( this ); |
| exitStat = 0; |
| exitNormal = false; |
| } |
| |
| void Q3Process::reset() |
| { |
| d->reset(); |
| exitStat = 0; |
| exitNormal = false; |
| d->bufStdout.clear(); |
| d->bufStderr.clear(); |
| } |
| |
| Q3Membuf* Q3Process::membufStdout() |
| { |
| if( d->pipeStdout[0] != 0 ) |
| socketRead( 1 ); |
| return &d->bufStdout; |
| } |
| |
| Q3Membuf* Q3Process::membufStderr() |
| { |
| if( d->pipeStderr[0] != 0 ) |
| socketRead( 2 ); |
| return &d->bufStderr; |
| } |
| |
| Q3Process::~Q3Process() |
| { |
| delete d; |
| } |
| |
| bool Q3Process::start( QStringList *env ) |
| { |
| #if defined(QT_Q3PROCESS_DEBUG) |
| qDebug( "Q3Process::start()" ); |
| #endif |
| reset(); |
| |
| if ( _arguments.isEmpty() ) |
| return false; |
| |
| // Open the pipes. Make non-inheritable copies of input write and output |
| // read handles to avoid non-closable handles (this is done by the |
| // DuplicateHandle() call). |
| SECURITY_ATTRIBUTES secAtt = { sizeof( SECURITY_ATTRIBUTES ), NULL, TRUE }; |
| #ifndef Q_OS_WINCE |
| // I guess there is no stdin stdout and stderr on Q_OS_WINCE to dup |
| // CreatePipe and DupilcateHandle aren't available for Q_OS_WINCE |
| HANDLE tmpStdin, tmpStdout, tmpStderr; |
| if ( comms & Stdin ) { |
| if ( !CreatePipe( &d->pipeStdin[0], &tmpStdin, &secAtt, 0 ) ) { |
| d->closeHandles(); |
| return false; |
| } |
| if ( !DuplicateHandle( GetCurrentProcess(), tmpStdin, GetCurrentProcess(), &d->pipeStdin[1], 0, FALSE, DUPLICATE_SAME_ACCESS ) ) { |
| d->closeHandles(); |
| return false; |
| } |
| if ( !CloseHandle( tmpStdin ) ) { |
| d->closeHandles(); |
| return false; |
| } |
| } |
| if ( comms & Stdout ) { |
| if ( !CreatePipe( &tmpStdout, &d->pipeStdout[1], &secAtt, 0 ) ) { |
| d->closeHandles(); |
| return false; |
| } |
| if ( !DuplicateHandle( GetCurrentProcess(), tmpStdout, GetCurrentProcess(), &d->pipeStdout[0], 0, FALSE, DUPLICATE_SAME_ACCESS ) ) { |
| d->closeHandles(); |
| return false; |
| } |
| if ( !CloseHandle( tmpStdout ) ) { |
| d->closeHandles(); |
| return false; |
| } |
| } |
| if ( comms & Stderr ) { |
| if ( !CreatePipe( &tmpStderr, &d->pipeStderr[1], &secAtt, 0 ) ) { |
| d->closeHandles(); |
| return false; |
| } |
| if ( !DuplicateHandle( GetCurrentProcess(), tmpStderr, GetCurrentProcess(), &d->pipeStderr[0], 0, FALSE, DUPLICATE_SAME_ACCESS ) ) { |
| d->closeHandles(); |
| return false; |
| } |
| if ( !CloseHandle( tmpStderr ) ) { |
| d->closeHandles(); |
| return false; |
| } |
| } |
| if ( comms & DupStderr ) { |
| CloseHandle( d->pipeStderr[1] ); |
| d->pipeStderr[1] = d->pipeStdout[1]; |
| } |
| #endif |
| |
| // construct the arguments for CreateProcess() |
| QString args; |
| QString appName; |
| QStringList::Iterator it = _arguments.begin(); |
| args = *it; |
| ++it; |
| if ( args.endsWith( QLatin1String(".bat") ) && args.contains( QLatin1Char(' ') ) ) { |
| // CreateProcess() seems to have a strange semantics (see also |
| // http://www.experts-exchange.com/Programming/Programming_Platforms/Win_Prog/Q_11138647.html): |
| // If you start a batch file with spaces in the filename, the first |
| // argument to CreateProcess() must be the name of the batchfile |
| // without quotes, but the second argument must start with the same |
| // argument with quotes included. But if the same approach is used for |
| // .exe files, it doesn't work. |
| appName = args; |
| args = QLatin1Char('"') + args + QLatin1Char('"'); |
| } |
| for ( ; it != _arguments.end(); ++it ) { |
| QString tmp = *it; |
| // escape a single " because the arguments will be parsed |
| tmp.replace( QLatin1Char('\"'), QLatin1String("\\\"") ); |
| if ( tmp.isEmpty() || tmp.contains( QLatin1Char(' ') ) || tmp.contains( QLatin1Char('\t') ) ) { |
| // The argument must not end with a \ since this would be interpreted |
| // as escaping the quote -- rather put the \ behind the quote: e.g. |
| // rather use "foo"\ than "foo\" |
| QString endQuote( QLatin1String("\"") ); |
| int i = tmp.length(); |
| while ( i>0 && tmp.at( i-1 ) == QLatin1Char('\\') ) { |
| --i; |
| endQuote += QLatin1Char('\\'); |
| } |
| args += QLatin1String(" \"") + tmp.left( i ) + endQuote; |
| } else { |
| args += QLatin1Char(' ') + tmp; |
| } |
| } |
| #if defined(QT_Q3PROCESS_DEBUG) |
| qDebug( "Q3Process::start(): args [%s]", args.latin1() ); |
| #endif |
| |
| // CreateProcess() |
| bool success; |
| d->newPid(); |
| |
| STARTUPINFOW startupInfo = { |
| sizeof( STARTUPINFO ), 0, 0, 0, |
| (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT, |
| 0, 0, 0, |
| STARTF_USESTDHANDLES, |
| 0, 0, 0, |
| d->pipeStdin[0], d->pipeStdout[1], d->pipeStderr[1] |
| }; |
| wchar_t *applicationName; |
| if ( appName.isNull() ) |
| applicationName = 0; |
| else |
| applicationName = _wcsdup( (wchar_t*)appName.utf16() ); |
| wchar_t *commandLine = _wcsdup( (wchar_t*)args.utf16() ); |
| QByteArray envlist; |
| if ( env != 0 ) { |
| int pos = 0; |
| // add PATH if necessary (for DLL loading) |
| QByteArray path = qgetenv( "PATH" ); |
| if ( env->grep( QRegExp(QLatin1String("^PATH="),FALSE) ).empty() && !path.isNull() ) { |
| QString tmp = QString::fromLatin1("PATH=%1").arg(QLatin1String(path.constData())); |
| uint tmpSize = sizeof(wchar_t) * (tmp.length() + 1); |
| envlist.resize( envlist.size() + tmpSize ); |
| memcpy( envlist.data() + pos, tmp.utf16(), tmpSize ); |
| pos += tmpSize; |
| } |
| // add the user environment |
| for ( QStringList::Iterator it = env->begin(); it != env->end(); it++ ) { |
| QString tmp = *it; |
| uint tmpSize = sizeof(wchar_t) * (tmp.length() + 1); |
| envlist.resize( envlist.size() + tmpSize ); |
| memcpy( envlist.data() + pos, tmp.utf16(), tmpSize ); |
| pos += tmpSize; |
| } |
| // add the 2 terminating 0 (actually 4, just to be on the safe side) |
| envlist.resize( envlist.size()+4 ); |
| envlist[pos++] = 0; |
| envlist[pos++] = 0; |
| envlist[pos++] = 0; |
| envlist[pos++] = 0; |
| } |
| success = CreateProcess( applicationName, commandLine, |
| 0, 0, TRUE, ( comms == 0 ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW ) |
| #ifndef Q_OS_WINCE |
| | CREATE_UNICODE_ENVIRONMENT |
| #endif |
| , env == 0 ? 0 : envlist.data(), |
| (wchar_t*)QDir::toNativeSeparators(workingDir.absPath()).utf16(), |
| &startupInfo, d->pid ); |
| |
| free( applicationName ); |
| free( commandLine ); |
| |
| if ( !success ) { |
| d->deletePid(); |
| return false; |
| } |
| |
| #ifndef Q_OS_WINCE |
| if ( comms & Stdin ) |
| CloseHandle( d->pipeStdin[0] ); |
| if ( comms & Stdout ) |
| CloseHandle( d->pipeStdout[1] ); |
| if ( (comms & Stderr) && !(comms & DupStderr) ) |
| CloseHandle( d->pipeStderr[1] ); |
| #endif |
| |
| if ( ioRedirection || notifyOnExit ) { |
| d->lookup->start( 100 ); |
| } |
| |
| // cleanup and return |
| return true; |
| } |
| |
| static BOOL QT_WIN_CALLBACK qt_terminateApp( HWND hwnd, LPARAM procId ) |
| { |
| DWORD procId_win; |
| GetWindowThreadProcessId( hwnd, &procId_win ); |
| if( procId_win == (DWORD)procId ) |
| PostMessage( hwnd, WM_CLOSE, 0, 0 ); |
| |
| return TRUE; |
| } |
| |
| void Q3Process::tryTerminate() const |
| { |
| if ( d->pid ) |
| EnumWindows( qt_terminateApp, (LPARAM)d->pid->dwProcessId ); |
| } |
| |
| void Q3Process::kill() const |
| { |
| if ( d->pid ) |
| TerminateProcess( d->pid->hProcess, 0xf291 ); |
| } |
| |
| bool Q3Process::isRunning() const |
| { |
| if ( !d->pid ) |
| return false; |
| |
| if ( WaitForSingleObject( d->pid->hProcess, 0) == WAIT_OBJECT_0 ) { |
| // there might be data to read |
| Q3Process *that = (Q3Process*)this; |
| that->socketRead( 1 ); // try stdout |
| that->socketRead( 2 ); // try stderr |
| // compute the exit values |
| if ( !d->exitValuesCalculated ) { |
| DWORD exitCode; |
| if ( GetExitCodeProcess( d->pid->hProcess, &exitCode ) ) { |
| if ( exitCode != STILL_ACTIVE ) { // this should ever be true? |
| that->exitNormal = exitCode != 0xf291; |
| that->exitStat = exitCode; |
| } |
| } |
| d->exitValuesCalculated = true; |
| } |
| d->deletePid(); |
| d->closeHandles(); |
| return false; |
| } else { |
| return true; |
| } |
| } |
| |
| bool Q3Process::canReadLineStdout() const |
| { |
| if( !d->pipeStdout[0] ) |
| return d->bufStdout.size() != 0; |
| |
| Q3Process *that = (Q3Process*)this; |
| return that->membufStdout()->scanNewline( 0 ); |
| } |
| |
| bool Q3Process::canReadLineStderr() const |
| { |
| if( !d->pipeStderr[0] ) |
| return d->bufStderr.size() != 0; |
| |
| Q3Process *that = (Q3Process*)this; |
| return that->membufStderr()->scanNewline( 0 ); |
| } |
| |
| void Q3Process::writeToStdin( const QByteArray& buf ) |
| { |
| d->stdinBuf.enqueue( new QByteArray(buf) ); |
| socketWrite( 0 ); |
| } |
| |
| void Q3Process::closeStdin( ) |
| { |
| if ( d->pipeStdin[1] != 0 ) { |
| CloseHandle( d->pipeStdin[1] ); |
| d->pipeStdin[1] = 0; |
| } |
| } |
| |
| void Q3Process::socketRead( int fd ) |
| { |
| // fd == 1: stdout, fd == 2: stderr |
| HANDLE dev; |
| if ( fd == 1 ) { |
| dev = d->pipeStdout[0]; |
| } else if ( fd == 2 ) { |
| dev = d->pipeStderr[0]; |
| } else { |
| return; |
| } |
| #ifndef Q_OS_WINCE |
| // get the number of bytes that are waiting to be read |
| unsigned long i, r; |
| char dummy; |
| if ( !PeekNamedPipe( dev, &dummy, 1, &r, &i, 0 ) ) { |
| return; // ### is it worth to dig for the reason of the error? |
| } |
| #else |
| unsigned long i = 1000; |
| #endif |
| if ( i > 0 ) { |
| Q3Membuf *buffer; |
| if ( fd == 1 ) |
| buffer = &d->bufStdout; |
| else |
| buffer = &d->bufStderr; |
| |
| QByteArray *ba = new QByteArray( i ); |
| uint sz = readStddev( dev, ba->data(), i ); |
| if ( sz != i ) |
| ba->resize( i ); |
| |
| if ( sz == 0 ) { |
| delete ba; |
| return; |
| } |
| buffer->append( ba ); |
| if ( fd == 1 ) |
| emit readyReadStdout(); |
| else |
| emit readyReadStderr(); |
| } |
| } |
| |
| void Q3Process::socketWrite( int ) |
| { |
| DWORD written; |
| while ( !d->stdinBuf.isEmpty() && isRunning() ) { |
| if ( !WriteFile( d->pipeStdin[1], |
| d->stdinBuf.head()->data() + d->stdinBufRead, |
| qMin( 8192, int(d->stdinBuf.head()->size() - d->stdinBufRead) ), |
| &written, 0 ) ) { |
| d->lookup->start( 100 ); |
| return; |
| } |
| d->stdinBufRead += written; |
| if ( d->stdinBufRead == (DWORD)d->stdinBuf.head()->size() ) { |
| d->stdinBufRead = 0; |
| delete d->stdinBuf.dequeue(); |
| if ( wroteToStdinConnected && d->stdinBuf.isEmpty() ) |
| emit wroteToStdin(); |
| } |
| } |
| } |
| |
| void Q3Process::flushStdin() |
| { |
| socketWrite( 0 ); |
| } |
| |
| /* |
| Use a timer for polling misc. stuff. |
| */ |
| void Q3Process::timeout() |
| { |
| // Disable the timer temporary since one of the slots that are connected to |
| // the readyRead...(), etc. signals might trigger recursion if |
| // processEvents() is called. |
| d->lookup->stop(); |
| |
| // try to write pending data to stdin |
| if ( !d->stdinBuf.isEmpty() ) |
| socketWrite( 0 ); |
| |
| if ( ioRedirection ) { |
| socketRead( 1 ); // try stdout |
| socketRead( 2 ); // try stderr |
| } |
| |
| if ( isRunning() ) { |
| // enable timer again, if needed |
| if ( !d->stdinBuf.isEmpty() || ioRedirection || notifyOnExit ) |
| d->lookup->start( 100 ); |
| } else if ( notifyOnExit ) { |
| emit processExited(); |
| } |
| } |
| |
| /* |
| read on the pipe |
| */ |
| uint Q3Process::readStddev( HANDLE dev, char *buf, uint bytes ) |
| { |
| if ( bytes > 0 ) { |
| ulong r; |
| if ( ReadFile( dev, buf, bytes, &r, 0 ) ) |
| return r; |
| } |
| return 0; |
| } |
| |
| /* |
| Used by connectNotify() and disconnectNotify() to change the value of |
| ioRedirection (and related behaviour) |
| */ |
| void Q3Process::setIoRedirection( bool value ) |
| { |
| ioRedirection = value; |
| if ( !ioRedirection && !notifyOnExit ) |
| d->lookup->stop(); |
| if ( ioRedirection ) { |
| if ( isRunning() ) |
| d->lookup->start( 100 ); |
| } |
| } |
| |
| /* |
| Used by connectNotify() and disconnectNotify() to change the value of |
| notifyOnExit (and related behaviour) |
| */ |
| void Q3Process::setNotifyOnExit( bool value ) |
| { |
| notifyOnExit = value; |
| if ( !ioRedirection && !notifyOnExit ) |
| d->lookup->stop(); |
| if ( notifyOnExit ) { |
| if ( isRunning() ) |
| d->lookup->start( 100 ); |
| } |
| } |
| |
| /* |
| Used by connectNotify() and disconnectNotify() to change the value of |
| wroteToStdinConnected (and related behaviour) |
| */ |
| void Q3Process::setWroteStdinConnected( bool value ) |
| { |
| wroteToStdinConnected = value; |
| } |
| |
| Q3Process::PID Q3Process::processIdentifier() |
| { |
| return d->pid; |
| } |
| |
| QT_END_NAMESPACE |
| |
| #endif // QT_NO_PROCESS |