summaryrefslogtreecommitdiffstats
path: root/src/qt3support/other/q3process_win.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qt3support/other/q3process_win.cpp')
-rw-r--r--src/qt3support/other/q3process_win.cpp676
1 files changed, 676 insertions, 0 deletions
diff --git a/src/qt3support/other/q3process_win.cpp b/src/qt3support/other/q3process_win.cpp
new file mode 100644
index 0000000..3c862ee
--- /dev/null
+++ b/src/qt3support/other/q3process_win.cpp
@@ -0,0 +1,676 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the Qt3Support module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.0, 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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( QLatin1String("\""), 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 += QLatin1String("\\");
+ }
+ args += QString( 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();
+#ifdef UNICODE
+ if (!(QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based)) {
+ 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]
+ };
+ TCHAR *applicationName;
+ if ( appName.isNull() )
+ applicationName = 0;
+ else
+ applicationName = _wcsdup( (TCHAR*)appName.ucs2() );
+ TCHAR *commandLine = _wcsdup( (TCHAR*)args.ucs2() );
+ 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( QLatin1String("PATH=%1") ).arg(QString::fromLatin1(path.constData()));
+ uint tmpSize = sizeof(TCHAR) * (tmp.length()+1);
+ envlist.resize( envlist.size() + tmpSize );
+ memcpy( envlist.data()+pos, tmp.ucs2(), tmpSize );
+ pos += tmpSize;
+ }
+ // add the user environment
+ for ( QStringList::Iterator it = env->begin(); it != env->end(); it++ ) {
+ QString tmp = *it;
+ uint tmpSize = sizeof(TCHAR) * (tmp.length()+1);
+ envlist.resize( envlist.size() + tmpSize );
+ memcpy( envlist.data()+pos, tmp.ucs2(), 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 = CreateProcessW( 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(),
+ (TCHAR*)QDir::toNativeSeparators(workingDir.absPath()).ucs2(),
+ &startupInfo, d->pid );
+ free( applicationName );
+ free( commandLine );
+ } else
+#endif // UNICODE
+ {
+#ifndef Q_OS_WINCE
+ STARTUPINFOA startupInfo = { sizeof( STARTUPINFOA ), 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]
+ };
+ 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() ) {
+ Q3CString tmp = QString( QLatin1String("PATH=%1") ).arg(QString::fromLatin1(path.constData())).local8Bit();
+ uint tmpSize = tmp.length() + 1;
+ envlist.resize( envlist.size() + tmpSize );
+ memcpy( envlist.data()+pos, tmp.data(), tmpSize );
+ pos += tmpSize;
+ }
+ // add the user environment
+ for ( QStringList::Iterator it = env->begin(); it != env->end(); it++ ) {
+ Q3CString tmp = (*it).local8Bit();
+ uint tmpSize = tmp.length() + 1;
+ envlist.resize( envlist.size() + tmpSize );
+ memcpy( envlist.data()+pos, tmp.data(), tmpSize );
+ pos += tmpSize;
+ }
+ // add the terminating 0 (actually 2, just to be on the safe side)
+ envlist.resize( envlist.size()+2 );
+ envlist[pos++] = 0;
+ envlist[pos++] = 0;
+ }
+ char *applicationName;
+ if ( appName.isNull() )
+ applicationName = 0;
+ else
+ applicationName = const_cast<char *>(appName.toLocal8Bit().data());
+ success = CreateProcessA( applicationName,
+ const_cast<char *>(args.toLocal8Bit().data()),
+ 0, 0, TRUE, comms==0 ? CREATE_NEW_CONSOLE : DETACHED_PROCESS,
+ env==0 ? 0 : envlist.data(),
+ (const char*)QDir::toNativeSeparators(workingDir.absPath()).local8Bit(),
+ &startupInfo, d->pid );
+#endif // Q_OS_WINCE
+ }
+ 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 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