[41] | 1 | /****************************************************************************
|
---|
| 2 | ** $Id: qprocess_pm.cpp 188 2008-11-21 03:36:19Z dmik $
|
---|
| 3 | **
|
---|
| 4 | ** Implementation of QProcess class for OS/2
|
---|
| 5 | **
|
---|
| 6 | ** Copyright (C) 1992-2001 Trolltech AS. All rights reserved.
|
---|
[44] | 7 | ** Copyright (C) 2005 netlabs.org. OS/2 Version.
|
---|
[41] | 8 | **
|
---|
| 9 | ** This file is part of the kernel module of the Qt GUI Toolkit.
|
---|
| 10 | **
|
---|
| 11 | ** This file may be distributed under the terms of the Q Public License
|
---|
| 12 | ** as defined by Trolltech AS of Norway and appearing in the file
|
---|
| 13 | ** LICENSE.QPL included in the packaging of this file.
|
---|
| 14 | **
|
---|
| 15 | ** This file may be distributed and/or modified under the terms of the
|
---|
| 16 | ** GNU General Public License version 2 as published by the Free Software
|
---|
| 17 | ** Foundation and appearing in the file LICENSE.GPL included in the
|
---|
| 18 | ** packaging of this file.
|
---|
| 19 | **
|
---|
| 20 | ** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
|
---|
| 21 | ** licenses may use this file in accordance with the Qt Commercial License
|
---|
| 22 | ** Agreement provided with the Software.
|
---|
| 23 | **
|
---|
| 24 | ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
---|
| 25 | ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
---|
| 26 | **
|
---|
| 27 | ** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
|
---|
| 28 | ** information about Qt Commercial License Agreements.
|
---|
| 29 | ** See http://www.trolltech.com/qpl/ for QPL licensing information.
|
---|
| 30 | ** See http://www.trolltech.com/gpl/ for GPL licensing information.
|
---|
| 31 | **
|
---|
| 32 | ** Contact info@trolltech.com if any conditions of this licensing are
|
---|
| 33 | ** not clear to you.
|
---|
| 34 | **
|
---|
| 35 | **********************************************************************/
|
---|
| 36 |
|
---|
| 37 | #include "qplatformdefs.h"
|
---|
| 38 | #include "qprocess.h"
|
---|
| 39 |
|
---|
| 40 | #ifndef QT_NO_PROCESS
|
---|
| 41 |
|
---|
| 42 | #include "qapplication.h"
|
---|
| 43 | #include "qptrqueue.h"
|
---|
| 44 | #include "qtimer.h"
|
---|
| 45 | #include "qregexp.h"
|
---|
[51] | 46 | #include "qdir.h"
|
---|
[134] | 47 | #include "qthread.h"
|
---|
| 48 | #include "qintdict.h"
|
---|
| 49 | #include "qmutex.h"
|
---|
[168] | 50 | #include "qfile.h"
|
---|
[41] | 51 | #include "private/qinternal_p.h"
|
---|
| 52 | #include "qt_os2.h"
|
---|
| 53 |
|
---|
[168] | 54 | // When QT_QPROCESS_USE_DOSEXECPGM is not defined, we use spawnvpe() instead of
|
---|
| 55 | // DosExecPgm() to let LIBC file (pipe, socket) descriptors be properly
|
---|
| 56 | // inherited if the child uses the same LIBC version.
|
---|
| 57 | //#define QT_QPROCESS_USE_DOSEXECPGM
|
---|
| 58 |
|
---|
[41] | 59 | #include <string.h>
|
---|
| 60 |
|
---|
[168] | 61 | #if !defined(QT_QPROCESS_USE_DOSEXECPGM)
|
---|
| 62 | #include <process.h>
|
---|
| 63 | #include <sys/wait.h>
|
---|
| 64 | #endif
|
---|
| 65 |
|
---|
[41] | 66 | //#define QT_QPROCESS_DEBUG
|
---|
| 67 |
|
---|
[134] | 68 | #define HF_STDIN HFILE( 0 )
|
---|
| 69 | #define HF_STDOUT HFILE( 1 )
|
---|
| 70 | #define HF_STDERR HFILE( 2 )
|
---|
| 71 | #define HF_NULL HFILE( ~0 )
|
---|
[41] | 72 |
|
---|
[134] | 73 | #define HP_NULL HPIPE( ~0 )
|
---|
| 74 | #define KEY_NULL USHORT( ~0 )
|
---|
[41] | 75 |
|
---|
[134] | 76 | #define PID_NULL PID( ~0 )
|
---|
[41] | 77 |
|
---|
[134] | 78 | enum
|
---|
| 79 | {
|
---|
| 80 | PIPE_SIZE_STDIN = 65520, // max
|
---|
| 81 | PIPE_SIZE_STDOUT = 65520, // max
|
---|
| 82 | PIPE_SIZE_STDERR = 4096,
|
---|
[41] | 83 |
|
---|
[134] | 84 | POLL_TIMER = 100,
|
---|
| 85 |
|
---|
| 86 | // new pipe data notification
|
---|
| 87 | WM_U_PIPE_RDATA = WM_USER + 0,
|
---|
| 88 | WM_U_PIPE_CLOSE = WM_USER + 1,
|
---|
| 89 | };
|
---|
| 90 |
|
---|
[41] | 91 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 92 | #include <stdarg.h>
|
---|
[134] | 93 | static HFILE StdErrHandle = HF_NULL;
|
---|
| 94 | QtMsgHandler OldMsgHandler = NULL;
|
---|
| 95 | static void StdErrMsgHandler( QtMsgType type, const char *msg )
|
---|
| 96 | {
|
---|
| 97 | if ( OldMsgHandler == NULL ) {
|
---|
| 98 | size_t len = strlen( msg );
|
---|
| 99 | ULONG written = 0;
|
---|
| 100 | DosWrite( StdErrHandle, msg, len, &written );
|
---|
| 101 | const char *EOL = "\n\r";
|
---|
| 102 | DosWrite( StdErrHandle, EOL, 2, &written );
|
---|
| 103 | } else {
|
---|
| 104 | OldMsgHandler( type, msg );
|
---|
| 105 | }
|
---|
| 106 | }
|
---|
| 107 | #define InstallQtMsgHandler() \
|
---|
| 108 | do { \
|
---|
| 109 | DosDupHandle( HF_STDERR, &StdErrHandle ); \
|
---|
| 110 | qInstallMsgHandler( StdErrMsgHandler ); \
|
---|
| 111 | } while (0)
|
---|
| 112 | #define UninstallQtMsgHandler() \
|
---|
| 113 | do { \
|
---|
| 114 | qInstallMsgHandler( OldMsgHandler ); \
|
---|
| 115 | DosClose( StdErrHandle ); \
|
---|
| 116 | } while (0)
|
---|
| 117 | #else
|
---|
| 118 | #define InstallQtMsgHandler() do {} while (0)
|
---|
| 119 | #define UninstallQtMsgHandler() do {} while (0)
|
---|
[41] | 120 | #endif
|
---|
| 121 |
|
---|
| 122 | /***********************************************************************
|
---|
| 123 | *
|
---|
| 124 | * QProcessPrivate
|
---|
| 125 | *
|
---|
| 126 | **********************************************************************/
|
---|
| 127 | class QProcessPrivate
|
---|
| 128 | {
|
---|
| 129 | public:
|
---|
[134] | 130 | struct Pipe
|
---|
| 131 | {
|
---|
| 132 | Pipe () : pipe( HP_NULL ), key( KEY_NULL ), pending( 0 )
|
---|
| 133 | , closed( false ) {}
|
---|
| 134 | HPIPE pipe;
|
---|
| 135 | USHORT key;
|
---|
| 136 | QMembuf buf;
|
---|
| 137 | // note: when QProcess is watched by QProcessMonitor, the below fields
|
---|
| 138 | // must be accessed from under the QProcessMonitor lock
|
---|
| 139 | uint pending;
|
---|
| 140 | bool closed : 1;
|
---|
| 141 | };
|
---|
| 142 |
|
---|
[41] | 143 | QProcessPrivate( QProcess *proc )
|
---|
| 144 | {
|
---|
[188] | 145 | stdinBufRead = 0;
|
---|
[134] | 146 | pipeStdin = HP_NULL;
|
---|
| 147 | pid = PID_NULL;
|
---|
[188] | 148 | exitValuesCalculated = FALSE;
|
---|
[41] | 149 |
|
---|
[188] | 150 | lookup = new QTimer( proc );
|
---|
| 151 | qApp->connect( lookup, SIGNAL(timeout()), proc, SLOT(timeout()) );
|
---|
[41] | 152 | }
|
---|
| 153 |
|
---|
| 154 | ~QProcessPrivate()
|
---|
| 155 | {
|
---|
[188] | 156 | reset();
|
---|
[41] | 157 | }
|
---|
| 158 |
|
---|
| 159 | void reset()
|
---|
| 160 | {
|
---|
[188] | 161 | while ( !stdinBuf.isEmpty() ) {
|
---|
| 162 | delete stdinBuf.dequeue();
|
---|
| 163 | }
|
---|
| 164 | stdinBufRead = 0;
|
---|
| 165 | closeHandles();
|
---|
[134] | 166 | stdout.buf.clear();
|
---|
| 167 | stderr.buf.clear();
|
---|
[188] | 168 | pid = PID_NULL;
|
---|
| 169 | exitValuesCalculated = FALSE;
|
---|
[134] | 170 | }
|
---|
[41] | 171 |
|
---|
[134] | 172 | Pipe *findPipe( USHORT key )
|
---|
| 173 | {
|
---|
| 174 | if ( stdout.key == key ) return &stdout;
|
---|
| 175 | if ( stderr.key == key ) return &stderr;
|
---|
| 176 | return NULL;
|
---|
[41] | 177 | }
|
---|
[134] | 178 |
|
---|
| 179 | void closePipe( Pipe *pipe )
|
---|
| 180 | {
|
---|
| 181 | if ( pipe->pipe != HP_NULL ) {
|
---|
| 182 | Q_ASSERT( pipe->key == KEY_NULL );
|
---|
| 183 | DosDisConnectNPipe( pipe->pipe );
|
---|
| 184 | DosClose( pipe->pipe );
|
---|
| 185 | pipe->pipe = HP_NULL;
|
---|
| 186 | pipe->pending = 0;
|
---|
| 187 | pipe->closed = FALSE;
|
---|
| 188 | }
|
---|
| 189 | }
|
---|
| 190 |
|
---|
| 191 | bool readPipe( Pipe *pipe );
|
---|
| 192 |
|
---|
[41] | 193 | void closeHandles()
|
---|
| 194 | {
|
---|
[186] | 195 | if( pipeStdin != HP_NULL ) {
|
---|
[41] | 196 | DosDisConnectNPipe( pipeStdin );
|
---|
| 197 | DosClose( pipeStdin );
|
---|
[134] | 198 | pipeStdin = HP_NULL;
|
---|
[186] | 199 | }
|
---|
[134] | 200 | closePipe( &stdout );
|
---|
| 201 | closePipe( &stderr );
|
---|
[41] | 202 | }
|
---|
| 203 |
|
---|
| 204 | QPtrQueue <QByteArray> stdinBuf;
|
---|
[134] | 205 | uint stdinBufRead;
|
---|
[41] | 206 |
|
---|
| 207 | HPIPE pipeStdin;
|
---|
[134] | 208 |
|
---|
| 209 | Pipe stdout;
|
---|
| 210 | Pipe stderr;
|
---|
[41] | 211 |
|
---|
| 212 | PID pid;
|
---|
| 213 |
|
---|
| 214 | QTimer *lookup;
|
---|
| 215 |
|
---|
[134] | 216 | bool exitValuesCalculated : 1;
|
---|
| 217 | };
|
---|
[41] | 218 |
|
---|
[134] | 219 | class QProcessMonitor : public QPMObjectWindow
|
---|
| 220 | {
|
---|
| 221 | public:
|
---|
| 222 | class Thread : public QThread
|
---|
| 223 | {
|
---|
| 224 | public:
|
---|
| 225 | Thread( QProcessMonitor *m ) : mon( m ) {}
|
---|
| 226 | void run() { mon->monitor(); }
|
---|
| 227 | QProcessMonitor *mon;
|
---|
| 228 | };
|
---|
| 229 |
|
---|
| 230 | QProcessMonitor();
|
---|
| 231 | ~QProcessMonitor();
|
---|
| 232 |
|
---|
| 233 | QMutex &mlock() { return lock; }
|
---|
| 234 |
|
---|
| 235 | bool isOk() const { return pipeSem != 0 && thread != NULL; }
|
---|
| 236 |
|
---|
| 237 | bool addProcess( QProcess *proc );
|
---|
| 238 | void removeProcess( QProcessPrivate *d,
|
---|
[188] | 239 | QProcessPrivate::Pipe *pipe = NULL,
|
---|
| 240 | bool inMsgHandler = false);
|
---|
[134] | 241 |
|
---|
| 242 | void monitor();
|
---|
| 243 | MRESULT message( ULONG msg, MPARAM mp1, MPARAM mp2 );
|
---|
| 244 |
|
---|
| 245 | private:
|
---|
| 246 |
|
---|
| 247 | struct PipeStates
|
---|
| 248 | {
|
---|
| 249 | PipeStates() {
|
---|
| 250 | size = 4;
|
---|
| 251 | arr = new PIPESEMSTATE[ size ];
|
---|
| 252 | }
|
---|
| 253 | ~PipeStates() {
|
---|
| 254 | delete[] arr;
|
---|
| 255 | }
|
---|
| 256 | void ensure( size_t sz ) {
|
---|
| 257 | // best size for sz pipes is sz * 2 (to be able to store both
|
---|
| 258 | // NPSS_RDATA & NPSS_CLOSE for every pipe) + one for NPSS_EOI
|
---|
| 259 | size_t newSize = sz * 2 + 1;
|
---|
| 260 | newSize = ((newSize + 5 - 1) / 5) * 5;
|
---|
| 261 | if ( newSize != size ) {
|
---|
| 262 | delete[] arr;
|
---|
| 263 | size = newSize;
|
---|
| 264 | arr = new PIPESEMSTATE[ size ];
|
---|
| 265 | }
|
---|
| 266 | }
|
---|
| 267 | size_t dataSize() { return size * sizeof(PIPESEMSTATE); }
|
---|
| 268 | PIPESEMSTATE *data() { return arr; }
|
---|
| 269 | PIPESEMSTATE &operator[]( size_t i ) {
|
---|
| 270 | Q_ASSERT( i < size );
|
---|
| 271 | return arr[i];
|
---|
| 272 | }
|
---|
| 273 | private:
|
---|
| 274 | PIPESEMSTATE *arr;
|
---|
| 275 | size_t size;
|
---|
| 276 | };
|
---|
| 277 |
|
---|
| 278 | bool addPipe( QProcess *proc, QProcessPrivate::Pipe *pipe );
|
---|
| 279 | void removePipe( QProcessPrivate::Pipe *pipe );
|
---|
| 280 |
|
---|
| 281 | HEV pipeSem;
|
---|
| 282 | Thread *thread;
|
---|
| 283 | bool threadRunning;
|
---|
| 284 | QIntDict<QProcess> pipeKeys;
|
---|
| 285 | PipeStates pipeStates;
|
---|
| 286 | QMutex lock;
|
---|
[41] | 287 | };
|
---|
| 288 |
|
---|
[134] | 289 | QProcessMonitor::QProcessMonitor()
|
---|
| 290 | : pipeSem( 0 ), thread( NULL ), threadRunning( FALSE )
|
---|
| 291 | {
|
---|
| 292 | APIRET rc = DosCreateEventSem( NULL, &pipeSem, DC_SEM_SHARED, 0 );
|
---|
| 293 | if ( rc != NO_ERROR ) {
|
---|
| 294 | #if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
|
---|
| 295 | qSystemWarning( "Failed to create a semaphore!", rc );
|
---|
| 296 | #endif
|
---|
| 297 | return;
|
---|
| 298 | }
|
---|
| 299 |
|
---|
| 300 | thread = new Thread( this );
|
---|
| 301 | Q_ASSERT( thread != NULL );
|
---|
| 302 | }
|
---|
[41] | 303 |
|
---|
[134] | 304 | QProcessMonitor::~QProcessMonitor()
|
---|
| 305 | {
|
---|
| 306 | if ( thread ) {
|
---|
| 307 | lock.lock();
|
---|
| 308 | if ( threadRunning ) {
|
---|
| 309 | threadRunning = FALSE;
|
---|
| 310 | DosPostEventSem( pipeSem );
|
---|
| 311 | }
|
---|
| 312 | lock.unlock();
|
---|
| 313 | thread->wait();
|
---|
| 314 | delete thread;
|
---|
| 315 | }
|
---|
| 316 |
|
---|
| 317 | if ( pipeSem )
|
---|
| 318 | DosCloseEventSem( pipeSem );
|
---|
| 319 | }
|
---|
| 320 |
|
---|
| 321 | bool QProcessMonitor::addPipe( QProcess *proc, QProcessPrivate::Pipe *pipe )
|
---|
| 322 | {
|
---|
| 323 | // Note: we use HPIPE as pipe keys in DosSetNPipeSem. However, HPIPE
|
---|
| 324 | // is 32-bit (ulong), while usKey in PIPESEMSTATE is 16-bit (USHORT)
|
---|
| 325 | // We unreasonably assume that the system will never assign HPIPE >= 0xFFFF,
|
---|
| 326 | // and just cast HPIPE to USHORT. There is an assertion checking this
|
---|
| 327 | // condition, so this method should simply return FALSE once our assumption
|
---|
| 328 | // breaks.
|
---|
| 329 |
|
---|
| 330 | Q_ASSERT( pipe->pipe < HPIPE( KEY_NULL ) );
|
---|
| 331 | if ( pipe->pipe >= HPIPE( KEY_NULL ) )
|
---|
| 332 | return FALSE;
|
---|
| 333 |
|
---|
| 334 | Q_ASSERT( pipe->pipe != HP_NULL && pipe->key == KEY_NULL );
|
---|
| 335 |
|
---|
| 336 | pipe->key = USHORT( pipe->pipe );
|
---|
| 337 | APIRET rc = DosSetNPipeSem( pipe->pipe, (HSEM) pipeSem, pipe->key );
|
---|
| 338 | if ( rc != NO_ERROR ) {
|
---|
| 339 | #if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
|
---|
| 340 | qSystemWarning( "Failed to set the pipe semaphore!", rc );
|
---|
| 341 | #endif
|
---|
| 342 | return FALSE;
|
---|
| 343 | }
|
---|
| 344 |
|
---|
| 345 | pipeKeys.insert( pipe->key, proc );
|
---|
| 346 | return TRUE;
|
---|
| 347 | }
|
---|
| 348 |
|
---|
| 349 | void QProcessMonitor::removePipe( QProcessPrivate::Pipe *pipe )
|
---|
| 350 | {
|
---|
| 351 | Q_ASSERT( pipe->pipe != HP_NULL && pipe->key != KEY_NULL );
|
---|
| 352 |
|
---|
| 353 | /// @todo (r=dmik) is this really necessary to detach pipeSem?
|
---|
| 354 | DosSetNPipeSem( pipe->pipe, 0, 0 );
|
---|
| 355 | bool ok = pipeKeys.remove( pipe->key );
|
---|
| 356 | pipe->key = KEY_NULL;
|
---|
| 357 | Q_ASSERT( ok );
|
---|
| 358 | Q_UNUSED( ok );
|
---|
| 359 | }
|
---|
| 360 |
|
---|
| 361 | bool QProcessMonitor::addProcess( QProcess *proc )
|
---|
| 362 | {
|
---|
| 363 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 364 | qDebug( "QProcessMonitor::addProcess(): proc=%p d=%p", proc, proc->d );
|
---|
| 365 | #endif
|
---|
| 366 |
|
---|
| 367 | QProcessPrivate *d = proc->d;
|
---|
| 368 |
|
---|
| 369 | // check if we need to monitor this process before entering the lock
|
---|
| 370 | if ( d->stdout.pipe == HP_NULL && d->stderr.pipe == HP_NULL )
|
---|
| 371 | return TRUE;
|
---|
| 372 |
|
---|
| 373 | uint newPipes = 0;
|
---|
| 374 |
|
---|
| 375 | QMutexLocker locker( &lock );
|
---|
| 376 |
|
---|
| 377 | if ( d->stdout.pipe != HP_NULL ) {
|
---|
| 378 | if ( !addPipe( proc, &d->stdout ) )
|
---|
| 379 | return FALSE;
|
---|
| 380 | ++ newPipes;
|
---|
| 381 | }
|
---|
| 382 |
|
---|
| 383 | if ( d->stderr.pipe != HP_NULL ) {
|
---|
| 384 | if ( !addPipe( proc, &d->stderr ) ) {
|
---|
| 385 | removePipe( &d->stderr );
|
---|
| 386 | return FALSE;
|
---|
| 387 | }
|
---|
| 388 | ++ newPipes;
|
---|
| 389 | }
|
---|
| 390 |
|
---|
| 391 | Q_ASSERT( newPipes > 0 );
|
---|
| 392 |
|
---|
| 393 | pipeStates.ensure( pipeKeys.count() );
|
---|
| 394 |
|
---|
| 395 | // start the monitor thread if necessary
|
---|
| 396 | if ( pipeKeys.count() == newPipes ) {
|
---|
| 397 | threadRunning = TRUE;
|
---|
| 398 | thread->start();
|
---|
| 399 | }
|
---|
| 400 |
|
---|
| 401 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 402 | qDebug( "QProcessMonitor::addProcess(): added %d pipes", newPipes );
|
---|
| 403 | #endif
|
---|
| 404 | return TRUE;
|
---|
| 405 | }
|
---|
| 406 |
|
---|
| 407 | void QProcessMonitor::removeProcess( QProcessPrivate *d,
|
---|
[188] | 408 | QProcessPrivate::Pipe *pipe /* = NULL */,
|
---|
| 409 | bool inMsgHandler /* = false */)
|
---|
[134] | 410 | {
|
---|
| 411 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 412 | qDebug( "QProcessMonitor::removeProcess(): d=%p pipe=%p key=%04hX",
|
---|
| 413 | d, pipe, pipe ? pipe->key : KEY_NULL );
|
---|
| 414 | #endif
|
---|
| 415 |
|
---|
| 416 | // check if we monitor this process before entering the lock
|
---|
| 417 | if ( d->stdout.pipe == HP_NULL && d->stderr.pipe == HP_NULL )
|
---|
| 418 | return;
|
---|
| 419 |
|
---|
| 420 | lock.lock();
|
---|
| 421 |
|
---|
| 422 | if ( pipeKeys.count() == 0 ) {
|
---|
| 423 | // Nothing to do. This is an outdated general removeProcess (d, NULL)
|
---|
| 424 | // call from the QProcess destructor or from isRunning(). Just return.
|
---|
| 425 | lock.unlock();
|
---|
| 426 | return;
|
---|
| 427 | }
|
---|
[188] | 428 |
|
---|
| 429 | if ( !inMsgHandler ) {
|
---|
| 430 | // process all messages in the message queue related to QProcessMonitor.
|
---|
| 431 | // A failure to do so may lead to a situation when another QProcess
|
---|
| 432 | // started before these messages are processed but after our ends of
|
---|
| 433 | // pipes are closed (for example, by QProcessPrivate::reset()) gets the
|
---|
| 434 | // same pipe handles from the system. Since pipe handles are also used
|
---|
| 435 | // as keys, the delayed messages will then refer to the new QProcess
|
---|
| 436 | // object they were not intended for. The fix is to remove "our"
|
---|
| 437 | // messages from the queue now, before closing pipes (which will usually
|
---|
| 438 | // be done right after this method).
|
---|
[134] | 439 |
|
---|
[188] | 440 | QMSG qmsg;
|
---|
| 441 | while (WinPeekMsg (0, &qmsg, hwnd(), WM_U_PIPE_RDATA, WM_U_PIPE_CLOSE,
|
---|
| 442 | PM_REMOVE)) {
|
---|
| 443 | qDebug( "msg=%08x", qmsg.msg );
|
---|
| 444 | if ( qmsg.msg == WM_U_PIPE_CLOSE ) {
|
---|
| 445 | QProcess *proc = (QProcess *) PVOIDFROMMP( qmsg.mp1 );
|
---|
| 446 | USHORT key = SHORT1FROMMP( qmsg.mp2 );
|
---|
| 447 | if ( proc == pipeKeys.find( key ) && proc->d == d ) {
|
---|
| 448 | // skip the close message for ourselves as we will do all
|
---|
| 449 | // the necessary stuff
|
---|
| 450 | continue;
|
---|
| 451 | }
|
---|
| 452 | }
|
---|
| 453 | WinDispatchMsg( 0, &qmsg );
|
---|
| 454 | }
|
---|
| 455 | }
|
---|
| 456 |
|
---|
[134] | 457 | if ( pipe ) {
|
---|
| 458 | removePipe( pipe );
|
---|
| 459 | } else {
|
---|
| 460 | if ( d->stdout.pipe != HP_NULL && d->stdout.key != KEY_NULL )
|
---|
| 461 | removePipe( &d->stdout );
|
---|
| 462 | if ( d->stderr.pipe != HP_NULL && d->stderr.key != KEY_NULL )
|
---|
| 463 | removePipe( &d->stderr );
|
---|
| 464 | }
|
---|
| 465 |
|
---|
| 466 | pipeStates.ensure( pipeKeys.count() );
|
---|
| 467 |
|
---|
| 468 | // stop the monitor thread if no more necessary
|
---|
| 469 | if ( pipeKeys.count() == 0 ) {
|
---|
| 470 | Q_ASSERT( threadRunning );
|
---|
| 471 | if ( threadRunning ) {
|
---|
| 472 | threadRunning = FALSE;
|
---|
| 473 | DosPostEventSem( pipeSem );
|
---|
| 474 | }
|
---|
| 475 | lock.unlock();
|
---|
| 476 | thread->wait();
|
---|
| 477 | } else {
|
---|
| 478 | lock.unlock();
|
---|
| 479 | }
|
---|
| 480 | }
|
---|
| 481 |
|
---|
| 482 | /** Monitor thread function */
|
---|
| 483 | void QProcessMonitor::monitor()
|
---|
| 484 | {
|
---|
| 485 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 486 | qDebug( "QProcessMonitor::monitor() ENTER" );
|
---|
| 487 | #endif
|
---|
| 488 |
|
---|
| 489 | lock.lock();
|
---|
| 490 | while( 1 ) {
|
---|
| 491 | lock.unlock();
|
---|
| 492 | APIRET rc = DosWaitEventSem( pipeSem, SEM_INDEFINITE_WAIT );
|
---|
| 493 | lock.lock();
|
---|
| 494 | if ( rc != NO_ERROR ) {
|
---|
| 495 | #if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
|
---|
| 496 | qSystemWarning( "Failed to wait on event semaphore!", rc );
|
---|
| 497 | #endif
|
---|
| 498 | break;
|
---|
| 499 | }
|
---|
| 500 |
|
---|
| 501 | ULONG posts = 0;
|
---|
| 502 | DosResetEventSem( pipeSem, &posts );
|
---|
| 503 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 504 | qDebug( "QProcessMonitor::monitor(): got semaphore (posts=%ld)", posts );
|
---|
| 505 | #endif
|
---|
| 506 |
|
---|
| 507 | if ( !threadRunning )
|
---|
| 508 | break;
|
---|
| 509 |
|
---|
| 510 | rc = DosQueryNPipeSemState( (HSEM) pipeSem, pipeStates.data(),
|
---|
| 511 | pipeStates.dataSize() );
|
---|
| 512 | if ( rc != NO_ERROR ) {
|
---|
| 513 | #if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
|
---|
| 514 | qSystemWarning( "Failed to query pipe semaphore state!", rc );
|
---|
| 515 | #endif
|
---|
| 516 | continue;
|
---|
| 517 | }
|
---|
| 518 |
|
---|
| 519 | // In the returned information array, CLOSE and READ records for the
|
---|
| 520 | // same pipe key may be mixed. We need CLOSE messages to be posted after
|
---|
| 521 | // READ messages, so we do two passes.
|
---|
| 522 |
|
---|
| 523 | int pass = 0;
|
---|
| 524 | for ( int i = 0; pass < 2; ++ i ) {
|
---|
| 525 | if ( pipeStates[i].fStatus == NPSS_EOI ) {
|
---|
| 526 | ++ pass;
|
---|
| 527 | i = -1;
|
---|
| 528 | continue;
|
---|
| 529 | }
|
---|
| 530 | if ( pass == 0 && pipeStates[i].fStatus != NPSS_RDATA )
|
---|
| 531 | continue;
|
---|
| 532 | if ( pass == 1 && pipeStates[i].fStatus != NPSS_CLOSE )
|
---|
| 533 | continue;
|
---|
| 534 |
|
---|
| 535 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 536 | qDebug( " %d/%d: fStatus=%u fFlag=%02X usKey=%04hX usAvail=%hu",
|
---|
| 537 | pass, i, (uint) pipeStates[i].fStatus,
|
---|
| 538 | (uint) pipeStates[i].fFlag, pipeStates[i].usKey,
|
---|
| 539 | pipeStates[i].usAvail );
|
---|
| 540 | #endif
|
---|
| 541 | QProcess *proc = pipeKeys.find( pipeStates[i].usKey );
|
---|
| 542 | Q_ASSERT( proc );
|
---|
| 543 | if ( !proc )
|
---|
| 544 | continue;
|
---|
| 545 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 546 | qDebug( " proc=%p (%s/%s) d=%p",
|
---|
| 547 | proc, proc->name(), proc->className(), proc->d );
|
---|
| 548 | #endif
|
---|
| 549 | QProcessPrivate::Pipe *pipe = proc->d->findPipe( pipeStates[i].usKey );
|
---|
| 550 | Q_ASSERT( pipe );
|
---|
| 551 | if ( !pipe )
|
---|
| 552 | continue;
|
---|
| 553 |
|
---|
| 554 | if ( pipeStates[i].fStatus == NPSS_RDATA ) {
|
---|
| 555 | bool wasPending = pipe->pending > 0;
|
---|
| 556 | pipe->pending = pipeStates[i].usAvail;
|
---|
| 557 | // inform the GUI thread that there is new data
|
---|
| 558 | if ( !wasPending )
|
---|
| 559 | WinPostMsg( hwnd(), WM_U_PIPE_RDATA, MPFROMP( proc ),
|
---|
| 560 | MPFROMSHORT( pipe->key ) );
|
---|
| 561 | } else if ( pipeStates[i].fStatus == NPSS_CLOSE ) {
|
---|
| 562 | // there may be repeated CLOSE records for the same pipe
|
---|
| 563 | // in subsequent DosQueryNPipeSemState() calls
|
---|
| 564 | if ( pipe->closed == FALSE ) {
|
---|
| 565 | pipe->closed = TRUE;
|
---|
| 566 | // inform the GUI thread that the client's pipe end
|
---|
| 567 | // was closed
|
---|
| 568 | WinPostMsg( hwnd(), WM_U_PIPE_CLOSE, MPFROMP( proc ),
|
---|
| 569 | MPFROMSHORT( pipe->key ) );
|
---|
| 570 | }
|
---|
| 571 | }
|
---|
| 572 | }
|
---|
| 573 | }
|
---|
| 574 | lock.unlock();
|
---|
| 575 |
|
---|
| 576 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 577 | qDebug( "QProcessMonitor::monitor() LEAVE" );
|
---|
| 578 | #endif
|
---|
| 579 | }
|
---|
| 580 |
|
---|
| 581 | MRESULT QProcessMonitor::message( ULONG msg, MPARAM mp1, MPARAM mp2 )
|
---|
| 582 | {
|
---|
| 583 | switch ( msg ) {
|
---|
| 584 | case WM_U_PIPE_RDATA: {
|
---|
| 585 | QProcess *proc = (QProcess *) PVOIDFROMMP( mp1 );
|
---|
| 586 | USHORT key = SHORT1FROMMP( mp2 );
|
---|
[159] | 587 | // check parameter validity (we can safely do it outside the lock
|
---|
| 588 | // because the GUI thread is the only that can modify pipeKeys)
|
---|
| 589 | if ( proc == pipeKeys.find( key ) ) {
|
---|
[134] | 590 | #if defined(QT_QPROCESS_DEBUG)
|
---|
[188] | 591 | qDebug( "QProcessMonitor::WM_U_PIPE_RDATA: proc=%p (%s/%s) d=%p "
|
---|
| 592 | "key=%04hX",
|
---|
| 593 | proc, proc->name(), proc->className(), proc->d, key );
|
---|
[134] | 594 | #endif
|
---|
| 595 | QProcessPrivate *d = proc->d;
|
---|
| 596 | if ( d->stdout.key == key ) {
|
---|
| 597 | emit proc->readyReadStdout();
|
---|
| 598 | } else {
|
---|
| 599 | Q_ASSERT( d->stderr.key == key );
|
---|
| 600 | emit proc->readyReadStderr();
|
---|
| 601 | }
|
---|
| 602 | }
|
---|
[159] | 603 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 604 | else {
|
---|
| 605 | qDebug( "QProcessMonitor::WM_U_PIPE_RDATA: proc=%p (invalid)",
|
---|
| 606 | proc );
|
---|
| 607 | }
|
---|
| 608 | #endif
|
---|
[134] | 609 | break;
|
---|
| 610 | }
|
---|
| 611 | case WM_U_PIPE_CLOSE: {
|
---|
| 612 | QProcess *proc = (QProcess *) PVOIDFROMMP( mp1 );
|
---|
| 613 | USHORT key = SHORT1FROMMP( mp2 );
|
---|
[159] | 614 | // check parameter validity (we can safely do it outside the lock
|
---|
| 615 | // because the GUI thread is the only that can modify pipeKeys)
|
---|
| 616 | if ( proc == pipeKeys.find( key ) ) {
|
---|
[134] | 617 | #if defined(QT_QPROCESS_DEBUG)
|
---|
[186] | 618 | qDebug( "QProcessMonitor::WM_U_PIPE_CLOSE: proc=%p (%s/%s) d=%p "
|
---|
| 619 | "key=%04hX",
|
---|
| 620 | proc, proc->name(), proc->className(), proc->d, key );
|
---|
[134] | 621 | #endif
|
---|
| 622 | QProcessPrivate *d = proc->d;
|
---|
| 623 | QProcessPrivate::Pipe *pipe = d->findPipe( key );
|
---|
| 624 | Q_ASSERT( pipe );
|
---|
| 625 | if ( pipe ) {
|
---|
| 626 | Q_ASSERT( pipe->closed );
|
---|
| 627 | // remove the single pipe from watching
|
---|
[188] | 628 | removeProcess( d, pipe, TRUE /* inMsgHandler */ );
|
---|
[134] | 629 | // close the pipe since no more necessary
|
---|
| 630 | // (pipe is not watched anymore, no need to lock access)
|
---|
| 631 | if ( pipe->pending == 0 )
|
---|
| 632 | d->closePipe( pipe );
|
---|
| 633 | }
|
---|
| 634 | }
|
---|
[159] | 635 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 636 | else {
|
---|
| 637 | qDebug( "QProcessMonitor::WM_U_PIPE_CLOSE: proc=%p (invalid)",
|
---|
| 638 | proc );
|
---|
| 639 | }
|
---|
| 640 | #endif
|
---|
[134] | 641 | break;
|
---|
| 642 | }
|
---|
| 643 | default:
|
---|
| 644 | break;
|
---|
| 645 | }
|
---|
| 646 |
|
---|
| 647 | return FALSE;
|
---|
| 648 | }
|
---|
| 649 |
|
---|
| 650 | static QProcessMonitor *processMonitor = NULL;
|
---|
| 651 |
|
---|
| 652 | void QProcessMonitor_cleanup()
|
---|
| 653 | {
|
---|
| 654 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 655 | qDebug( "QProcessMonitor_cleanup()" );
|
---|
| 656 | #endif
|
---|
| 657 | delete processMonitor;
|
---|
| 658 | processMonitor = NULL;
|
---|
| 659 | }
|
---|
| 660 |
|
---|
| 661 | /** Returns true if some data was successfully read */
|
---|
| 662 | bool QProcessPrivate::readPipe( Pipe *pipe )
|
---|
| 663 | {
|
---|
| 664 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 665 | qDebug( "QProcessPrivate::readPipe(): this=%p key=%04hX", this, pipe->key );
|
---|
| 666 | #endif
|
---|
| 667 |
|
---|
| 668 | if ( pipe->pipe == HP_NULL )
|
---|
| 669 | return false;
|
---|
| 670 |
|
---|
| 671 | // Pipe::pending and Pipe::closed need the lock
|
---|
| 672 | QMutex *lock = NULL;
|
---|
| 673 |
|
---|
| 674 | // Notes:
|
---|
| 675 | // 1. If procesMonitor is NULL, it which means we're somewhere inside
|
---|
| 676 | // the QApplication destruction procedure.
|
---|
[186] | 677 | // 2. If pipe->key is KEY_NULL, it means GUI thread has already processed
|
---|
[134] | 678 | // the WM_U_PIPE_CLOSE message and we're free to access fields w/o a
|
---|
| 679 | // lock. pipe->key is assigned only on the GUI thread, so it's safe
|
---|
| 680 | // to check it here w/o the lock.
|
---|
| 681 |
|
---|
| 682 | if ( processMonitor && pipe->key != KEY_NULL )
|
---|
| 683 | lock = &processMonitor->mlock();
|
---|
| 684 |
|
---|
| 685 | QByteArray *ba = NULL;
|
---|
| 686 | bool close = FALSE;
|
---|
| 687 |
|
---|
| 688 | {
|
---|
| 689 | QMutexLocker locker( lock );
|
---|
| 690 |
|
---|
| 691 | if ( pipe->pending == 0 )
|
---|
| 692 | return false;
|
---|
| 693 |
|
---|
| 694 | ba = new QByteArray( pipe->pending );
|
---|
| 695 |
|
---|
| 696 | ULONG read = 0;
|
---|
| 697 | APIRET rc = DosRead( pipe->pipe, ba->data(), pipe->pending, &read );
|
---|
| 698 | if ( rc != NO_ERROR ) {
|
---|
| 699 | delete ba;
|
---|
| 700 | #if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
|
---|
| 701 | qSystemWarning( "Failed to read from the pipe!", rc );
|
---|
| 702 | #endif
|
---|
| 703 | return false;
|
---|
| 704 | }
|
---|
| 705 |
|
---|
| 706 | Q_ASSERT( read == pipe->pending );
|
---|
| 707 | if ( read < pipe->pending )
|
---|
| 708 | ba->resize( read );
|
---|
| 709 |
|
---|
| 710 | pipe->pending -= read;
|
---|
| 711 |
|
---|
| 712 | close = pipe->pending == 0 && pipe->closed;
|
---|
| 713 | }
|
---|
| 714 |
|
---|
| 715 | if ( close ) {
|
---|
| 716 | // the client's end of pipe has been closed, so close our end as well
|
---|
| 717 | if ( processMonitor && pipe->key != KEY_NULL ) {
|
---|
[188] | 718 | // WM_U_PIPE_CLOSE has been posted but not yet processed
|
---|
| 719 | // (the following call make sure it will be processed)
|
---|
| 720 | processMonitor->removeProcess( this, pipe );
|
---|
[134] | 721 | }
|
---|
[188] | 722 | else
|
---|
[186] | 723 | closePipe( pipe );
|
---|
[134] | 724 | }
|
---|
| 725 |
|
---|
| 726 | pipe->buf.append( ba );
|
---|
| 727 | return true;
|
---|
| 728 | }
|
---|
| 729 |
|
---|
[41] | 730 | /***********************************************************************
|
---|
| 731 | *
|
---|
| 732 | * QProcess
|
---|
| 733 | *
|
---|
| 734 | **********************************************************************/
|
---|
| 735 |
|
---|
| 736 | void QProcess::init()
|
---|
| 737 | {
|
---|
| 738 | d = new QProcessPrivate( this );
|
---|
| 739 | exitStat = 0;
|
---|
| 740 | exitNormal = FALSE;
|
---|
[134] | 741 |
|
---|
| 742 | #if defined(QT_QPROCESS_DEBUG)
|
---|
[159] | 743 | qDebug( "QProcess::init(): d=%p", d );
|
---|
[134] | 744 | #endif
|
---|
[41] | 745 | }
|
---|
| 746 |
|
---|
| 747 | void QProcess::reset()
|
---|
| 748 | {
|
---|
[134] | 749 | // remove monitoring if the monitor is there (note that it's done before
|
---|
| 750 | // resetting QProcessPrivate which has fields protected by the
|
---|
| 751 | // QProcessMonitor lock)
|
---|
| 752 | if ( processMonitor )
|
---|
| 753 | processMonitor->removeProcess( d );
|
---|
| 754 |
|
---|
[41] | 755 | d->reset();
|
---|
| 756 | exitStat = 0;
|
---|
| 757 | exitNormal = FALSE;
|
---|
| 758 | }
|
---|
| 759 |
|
---|
| 760 | QMembuf* QProcess::membufStdout()
|
---|
| 761 | {
|
---|
[134] | 762 | if( d->stdout.pipe != 0 )
|
---|
| 763 | d->readPipe( &d->stdout );
|
---|
| 764 | return &d->stdout.buf;
|
---|
[41] | 765 | }
|
---|
| 766 |
|
---|
| 767 | QMembuf* QProcess::membufStderr()
|
---|
| 768 | {
|
---|
[134] | 769 | if( d->stderr.pipe != 0 )
|
---|
| 770 | d->readPipe( &d->stderr );
|
---|
| 771 | return &d->stderr.buf;
|
---|
[41] | 772 | }
|
---|
| 773 |
|
---|
| 774 | QProcess::~QProcess()
|
---|
| 775 | {
|
---|
[134] | 776 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 777 | qDebug( "~QProcess::QProcess(): d=%p", d );
|
---|
| 778 | #endif
|
---|
| 779 |
|
---|
| 780 | // remove monitoring if the monitor is there (note that it's done before
|
---|
| 781 | // resetting QProcessPrivate which has fields protected by the
|
---|
| 782 | // QProcessMonitor lock)
|
---|
| 783 | if ( processMonitor )
|
---|
| 784 | processMonitor->removeProcess( d );
|
---|
| 785 |
|
---|
[41] | 786 | delete d;
|
---|
| 787 | }
|
---|
| 788 |
|
---|
| 789 | bool QProcess::start( QStringList *env )
|
---|
| 790 | {
|
---|
| 791 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 792 | qDebug( "QProcess::start()" );
|
---|
| 793 | #endif
|
---|
[134] | 794 |
|
---|
[41] | 795 | reset();
|
---|
| 796 |
|
---|
| 797 | if ( _arguments.isEmpty() )
|
---|
[188] | 798 | return FALSE;
|
---|
[41] | 799 |
|
---|
[168] | 800 | #if defined(QT_QPROCESS_USE_DOSEXECPGM)
|
---|
| 801 |
|
---|
[41] | 802 | // construct the arguments for DosExecPgm()
|
---|
| 803 |
|
---|
| 804 | QCString appName, appArgs;
|
---|
| 805 | {
|
---|
| 806 | QString args;
|
---|
| 807 | for ( QStringList::ConstIterator it = _arguments.begin();
|
---|
| 808 | it != _arguments.end(); ++ it
|
---|
| 809 | ) {
|
---|
| 810 | if ( it == _arguments.begin() ) {
|
---|
| 811 | appName = (*it).local8Bit();
|
---|
| 812 | } else {
|
---|
| 813 | QString s = *it;
|
---|
| 814 | // quote the argument when it contains spaces
|
---|
| 815 | if ( s.contains( ' ' ) )
|
---|
| 816 | s = '"' + s + '"';
|
---|
| 817 | if ( args.isNull() )
|
---|
| 818 | args = s;
|
---|
| 819 | else
|
---|
| 820 | args += ' ' + s;
|
---|
| 821 | }
|
---|
| 822 | }
|
---|
| 823 | appArgs = args.local8Bit();
|
---|
| 824 |
|
---|
| 825 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 826 | qDebug( "QProcess::start(): app [%s], args [%s]",
|
---|
| 827 | appName.data(), appArgs.data() );
|
---|
| 828 | #endif
|
---|
| 829 | }
|
---|
| 830 |
|
---|
| 831 | QByteArray envlist;
|
---|
| 832 | if ( env != 0 ) {
|
---|
| 833 | uint pos = 0;
|
---|
| 834 | // inherit PATH if missing (for convenience)
|
---|
| 835 | // (note that BEGINLIBPATH and ENDLIBPATH, if any, are automatically
|
---|
| 836 | // inherited, while LIBPATH is always a global setting)
|
---|
| 837 | {
|
---|
| 838 | const char *path = getenv( "PATH" );
|
---|
| 839 | if ( env->grep( QRegExp( "^PATH=", FALSE ) ).empty() && path ) {
|
---|
| 840 | uint sz = strlen( path ) + 5 /* PATH= */ + 1;
|
---|
| 841 | envlist.resize( envlist.size() + sz );
|
---|
| 842 | sprintf( envlist.data() + pos, "PATH=%s", path );
|
---|
| 843 | pos += sz;
|
---|
| 844 | }
|
---|
| 845 | }
|
---|
| 846 | // inherit COMSPEC if missing (to let the child start .cmd and .bat)
|
---|
| 847 | {
|
---|
| 848 | const char *comspec = getenv( "COMSPEC" );
|
---|
| 849 | if ( env->grep( QRegExp( "^COMSPEC=", FALSE ) ).empty() && comspec ) {
|
---|
| 850 | uint sz = strlen( comspec ) + 8 /* COMSPEC= */ + 1;
|
---|
| 851 | envlist.resize( envlist.size() + sz );
|
---|
| 852 | sprintf( envlist.data() + pos, "COMSPEC=%s", comspec );
|
---|
| 853 | pos += sz;
|
---|
| 854 | }
|
---|
| 855 | }
|
---|
| 856 | // add the user environment
|
---|
| 857 | for ( QStringList::ConstIterator it = env->begin();
|
---|
| 858 | it != env->end(); ++ it
|
---|
| 859 | ) {
|
---|
| 860 | QCString var = (*it).local8Bit();
|
---|
| 861 | uint sz = var.length() + 1;
|
---|
| 862 | envlist.resize( envlist.size() + sz );
|
---|
| 863 | memcpy( envlist.data() + pos, var.data(), sz );
|
---|
| 864 | pos += sz;
|
---|
| 865 | }
|
---|
| 866 | // add the terminating 0
|
---|
| 867 | envlist.resize( envlist.size() + 1 );
|
---|
| 868 | envlist[ pos ++ ] = 0;
|
---|
[51] | 869 | } else {
|
---|
| 870 | // copy the entire environment (to let all variables added using
|
---|
| 871 | // putenv() appear in the child process)
|
---|
| 872 | uint sz = 1;
|
---|
| 873 | for( const char * const *envp = environ; *envp != NULL; ++ envp )
|
---|
| 874 | sz += strlen( *envp ) + 1;
|
---|
| 875 | envlist.resize( sz );
|
---|
| 876 | uint pos = 0;
|
---|
| 877 | for( const char * const *envp = environ; *envp != NULL; ++ envp ) {
|
---|
| 878 | sz = strlen( *envp ) + 1;
|
---|
| 879 | memcpy( envlist.data() + pos, *envp, sz );
|
---|
| 880 | pos += sz;
|
---|
| 881 | }
|
---|
| 882 | // add the terminating 0
|
---|
| 883 | envlist[ pos ++ ] = 0;
|
---|
[41] | 884 | }
|
---|
| 885 |
|
---|
| 886 | // search for the app executable
|
---|
| 887 |
|
---|
| 888 | const uint appNameLen = appName.length();
|
---|
| 889 | const uint appArgsLen = appArgs.length();
|
---|
| 890 |
|
---|
| 891 | bool hasPath = appName[ strcspn( appName.data(), "/\\" ) ] != '\0';
|
---|
| 892 | bool hasSpaces = strchr( appName.data(), ' ' ) != NULL;
|
---|
| 893 |
|
---|
| 894 | // list of executable file extensions, in order of CMD.EXE's precedence
|
---|
| 895 | // (the first runs directly, the rest requires CMD.EXE)
|
---|
| 896 | const char *exeExts[] = { ".exe", ".cmd", ".bat" };
|
---|
| 897 |
|
---|
| 898 | // detect which exe extension do we have, if any
|
---|
| 899 | int ext = -1;
|
---|
| 900 | if ( appNameLen >= 4 ) {
|
---|
| 901 | for ( uint i = 0; ext < 0 && i < sizeof(exeExts) / sizeof(exeExts[0]); ++ i )
|
---|
| 902 | if ( stricmp( appName.data() + appNameLen - 4, exeExts[i] ) == 0 )
|
---|
| 903 | ext = i;
|
---|
| 904 | }
|
---|
| 905 |
|
---|
| 906 | QByteArray buf;
|
---|
| 907 | QCString appNameFull;
|
---|
| 908 |
|
---|
| 909 | APIRET rc = 0;
|
---|
| 910 | char pathBuf[ CCHMAXPATH ];
|
---|
| 911 |
|
---|
| 912 | // run through all possible exe extension combinations (+ no extension case)
|
---|
| 913 | for ( uint i = 0; i <= sizeof(exeExts) / sizeof(exeExts[ 0 ]); ++ i ) {
|
---|
| 914 | if ( i == 0 ) {
|
---|
| 915 | // try the unmodified app name first if it contains a path spec
|
---|
| 916 | // or has one of predefined exe extensions
|
---|
| 917 | if ( hasPath || ext >= 0 ) {
|
---|
| 918 | if ( buf.size() < appNameLen + 1 )
|
---|
| 919 | buf.resize ( appNameLen + 1 );
|
---|
| 920 | strcpy( buf.data(), appName.data() );
|
---|
| 921 | } else
|
---|
| 922 | continue;
|
---|
| 923 | } else {
|
---|
| 924 | ext = i - 1;
|
---|
| 925 | const uint extLen = strlen( exeExts[ ext ] );
|
---|
| 926 | uint sz = appNameLen + extLen + 1;
|
---|
| 927 | if ( buf.size() < sz )
|
---|
| 928 | buf.resize( sz );
|
---|
| 929 | strcpy( buf.data(), appName.data() );
|
---|
| 930 | strcpy( buf.data() + appNameLen, exeExts[ ext ] );
|
---|
| 931 | }
|
---|
| 932 |
|
---|
| 933 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 934 | qDebug( "QProcess::start(): trying to find [%s]", buf.data() );
|
---|
| 935 | #endif
|
---|
| 936 | if ( hasPath ) {
|
---|
| 937 | uint lastSep = strlen( buf.data() );
|
---|
| 938 | while ( lastSep-- && buf[ lastSep ] != '/' && buf[ lastSep ] != '\\' )
|
---|
| 939 | ;
|
---|
| 940 | buf[ lastSep ] = '\0';
|
---|
| 941 | rc = DosSearchPath( 0, buf.data(), buf.data() + lastSep + 1,
|
---|
| 942 | pathBuf, sizeof(pathBuf) );
|
---|
| 943 | } else {
|
---|
| 944 | rc = DosSearchPath( SEARCH_IGNORENETERRS | SEARCH_ENVIRONMENT |
|
---|
| 945 | SEARCH_CUR_DIRECTORY, "PATH",
|
---|
| 946 | buf.data(), pathBuf, sizeof(pathBuf) );
|
---|
| 947 | }
|
---|
| 948 |
|
---|
| 949 | if ( rc == NO_ERROR ) {
|
---|
| 950 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 951 | qDebug( "QProcess::start(): found [%s]", pathBuf );
|
---|
| 952 | #endif
|
---|
| 953 | appNameFull = pathBuf;
|
---|
| 954 | break;
|
---|
| 955 | }
|
---|
| 956 |
|
---|
| 957 | if ( rc != ERROR_FILE_NOT_FOUND && rc != ERROR_PATH_NOT_FOUND ) {
|
---|
| 958 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 959 | qDebug( "QProcess::start(): found [%s], but got an error:", pathBuf );
|
---|
| 960 | qSystemWarning( "", rc );
|
---|
| 961 | #endif
|
---|
| 962 | break;
|
---|
| 963 | }
|
---|
| 964 | }
|
---|
| 965 |
|
---|
| 966 | if ( appNameFull.isNull() ) {
|
---|
| 967 | #if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
|
---|
| 968 | #if defined(QT_DEBUG)
|
---|
| 969 | qWarning( "[%s]:", appName.data() );
|
---|
| 970 | #endif
|
---|
| 971 | qSystemWarning( "Failed to find the executable!", rc );
|
---|
| 972 | #endif
|
---|
| 973 | return FALSE;
|
---|
| 974 | }
|
---|
| 975 |
|
---|
| 976 | // final preparation of arguments
|
---|
| 977 |
|
---|
| 978 | if ( ext <= 0 ) {
|
---|
| 979 | // run directly (args = <appName>\0\<appArgs>\0\0)
|
---|
| 980 | uint sz = appNameLen + 1 + appArgsLen + 2;
|
---|
| 981 | if ( buf.size() < sz )
|
---|
| 982 | buf.resize( sz );
|
---|
| 983 | strcpy( buf.data(), appName.data() );
|
---|
| 984 | strcpy( buf.data() + appNameLen + 1, appArgs.data() );
|
---|
| 985 | buf[ sz - 1 ] = '\0';
|
---|
| 986 | } else {
|
---|
| 987 | // run via shell (args = <shell>\0"/c "<appName>" "<appArgs>\0\0)
|
---|
| 988 | appNameFull = getenv( "COMSPEC" );
|
---|
| 989 | if ( appNameFull.isNull() ) {
|
---|
| 990 | ULONG bootDrv = 0;
|
---|
| 991 | rc = DosQuerySysInfo( QSV_BOOT_DRIVE, QSV_BOOT_DRIVE,
|
---|
| 992 | &bootDrv, sizeof(bootDrv) );
|
---|
| 993 | if ( rc == NO_ERROR && bootDrv >= 1 ) {
|
---|
| 994 | appNameFull = "?:\\OS2\\CMD.EXE";
|
---|
| 995 | appNameFull[ 0 ] = char( bootDrv ) + 'A' - 1;
|
---|
| 996 | }
|
---|
| 997 | }
|
---|
| 998 | if ( appNameFull.isNull() ) {
|
---|
| 999 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 1000 | qDebug( "QProcess::start(): OS/2 command line interpreter is not found!" );
|
---|
| 1001 | #endif
|
---|
| 1002 | return FALSE;
|
---|
| 1003 | }
|
---|
| 1004 | const uint shellLen = strlen( appNameFull );
|
---|
| 1005 | uint sz = shellLen + 1 + 3 + appNameLen + 1 + appArgsLen + 2;
|
---|
| 1006 | if ( hasSpaces )
|
---|
| 1007 | sz += 2; // for quotes
|
---|
| 1008 | if ( !appArgsLen )
|
---|
| 1009 | sz -= 1; // no need for space
|
---|
| 1010 | if ( buf.size() < sz )
|
---|
| 1011 | buf.resize( sz );
|
---|
| 1012 | strcpy( buf.data(), appNameFull );
|
---|
[159] | 1013 | char *argsPtr = buf.data() + shellLen + 1;
|
---|
| 1014 | sprintf( argsPtr, hasSpaces ? "/c \"%s\"" : "/c %s", appName.data() );
|
---|
| 1015 | if ( appArgsLen ) {
|
---|
| 1016 | strcat( argsPtr, " " );
|
---|
| 1017 | strcat( argsPtr, appArgs.data() );
|
---|
| 1018 | }
|
---|
[41] | 1019 | buf[ sz - 1 ] = '\0';
|
---|
| 1020 | }
|
---|
| 1021 |
|
---|
| 1022 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 1023 | qDebug( "QProcess::start(): will start [%s] as [%s] [%s]",
|
---|
| 1024 | appNameFull.data(), buf.data(), buf.data() + strlen( buf.data() ) + 1 );
|
---|
| 1025 | #endif
|
---|
[168] | 1026 |
|
---|
| 1027 | #else // QT_QPROCESS_USE_DOSEXECPGM
|
---|
| 1028 |
|
---|
| 1029 | // construct the arguments for spawnvpe()
|
---|
| 1030 |
|
---|
| 1031 | QCString appName;
|
---|
| 1032 | QStrList appArgs;
|
---|
| 1033 |
|
---|
| 1034 | for ( QStringList::ConstIterator it = _arguments.begin();
|
---|
| 1035 | it != _arguments.end(); ++ it
|
---|
| 1036 | ) {
|
---|
| 1037 | if ( it == _arguments.begin() ) {
|
---|
| 1038 | appName = QFile::encodeName( *it );
|
---|
| 1039 | } else {
|
---|
| 1040 | appArgs.append( QFile::encodeName( *it ) );
|
---|
| 1041 | }
|
---|
| 1042 | }
|
---|
| 1043 |
|
---|
| 1044 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 1045 | qDebug( "QProcess::start(): [%s]",
|
---|
| 1046 | QFile::encodeName( _arguments.join( "] [" ) ).data() );
|
---|
| 1047 | #endif
|
---|
| 1048 |
|
---|
| 1049 | // prepare the environment
|
---|
| 1050 |
|
---|
| 1051 | QStrList envList;
|
---|
| 1052 | if ( env != 0 ) {
|
---|
| 1053 | // add the user environment
|
---|
| 1054 | bool seenPATH = FALSE;
|
---|
| 1055 | bool seenCOMSPEC = FALSE;
|
---|
| 1056 | for ( QStringList::ConstIterator it = env->begin();
|
---|
| 1057 | it != env->end(); ++ it
|
---|
| 1058 | ) {
|
---|
| 1059 | envList.append( QFile::encodeName( *it ) );
|
---|
| 1060 | if ( !seenPATH )
|
---|
| 1061 | seenPATH = !strncmp( envList.current(), "PATH=", 4 );
|
---|
| 1062 | if ( !seenCOMSPEC )
|
---|
| 1063 | seenCOMSPEC = !strncmp( envList.current(), "COMSPEC=", 8 );
|
---|
| 1064 | }
|
---|
| 1065 | if ( !seenPATH ) {
|
---|
| 1066 | // inherit PATH if missing (for convenience)
|
---|
| 1067 | // (note that BEGINLIBPATH and ENDLIBPATH, if any, are automatically
|
---|
| 1068 | // inherited, while LIBPATH is always a global setting)
|
---|
| 1069 | const char *path = getenv( "PATH" );
|
---|
| 1070 | QCString str( 5 /* PATH= */ + strlen( path ) + 1 /* \0 */ );
|
---|
| 1071 | strcpy( str.data(), "PATH=" );
|
---|
| 1072 | strcat( str.data() + 5, path );
|
---|
| 1073 | envList.append( str );
|
---|
| 1074 | }
|
---|
| 1075 | // inherit COMSPEC if missing (to let the child start .cmd and .bat)
|
---|
| 1076 | if ( !seenCOMSPEC ) {
|
---|
| 1077 | const char *comspec = getenv( "COMSPEC" );
|
---|
| 1078 | QCString str( 8 /* COMSPEC= */ + strlen( comspec ) + 1 /* \0 */ );
|
---|
| 1079 | strcpy( str.data(), "COMSPEC=" );
|
---|
| 1080 | strcat( str.data() + 5, comspec );
|
---|
| 1081 | envList.append( str );
|
---|
| 1082 | }
|
---|
| 1083 | }
|
---|
| 1084 |
|
---|
| 1085 | APIRET rc = 0;
|
---|
| 1086 | char pathBuf[ CCHMAXPATH ];
|
---|
| 1087 |
|
---|
| 1088 | #endif // QT_QPROCESS_USE_DOSEXECPGM
|
---|
| 1089 |
|
---|
[41] | 1090 | // create STDIN/OUT/ERR redirection pipes
|
---|
| 1091 |
|
---|
[134] | 1092 | if ( comms & (Stdout | Stderr) ) {
|
---|
| 1093 | // lazily create the process monitor
|
---|
| 1094 | if ( !processMonitor ) {
|
---|
| 1095 | processMonitor = new QProcessMonitor();
|
---|
| 1096 | Q_ASSERT( processMonitor );
|
---|
| 1097 | if ( !processMonitor )
|
---|
| 1098 | return FALSE;
|
---|
| 1099 | if ( !processMonitor->isOk() ) {
|
---|
| 1100 | QProcessMonitor_cleanup();
|
---|
| 1101 | return FALSE;
|
---|
| 1102 | }
|
---|
| 1103 | qAddPostRoutine( QProcessMonitor_cleanup );
|
---|
| 1104 | }
|
---|
| 1105 | }
|
---|
| 1106 |
|
---|
| 1107 | HFILE tmpStdin = HF_NULL, tmpStdout = HF_NULL, tmpStderr = HF_NULL;
|
---|
[41] | 1108 | HFILE realStdin = HF_STDIN, realStdout = HF_STDOUT, realStderr = HF_STDERR;
|
---|
| 1109 |
|
---|
| 1110 | // we need the process identifier to guarantee pipe name unicity
|
---|
| 1111 | PPIB ppib = NULL;
|
---|
| 1112 | DosGetInfoBlocks( NULL, &ppib );
|
---|
| 1113 |
|
---|
[134] | 1114 | // use the custom Qt message handler to print errors/wranings
|
---|
| 1115 | // to the console when the real STDERR handle is redirected
|
---|
| 1116 | InstallQtMsgHandler();
|
---|
| 1117 |
|
---|
[41] | 1118 | if ( comms & Stdin ) {
|
---|
[134] | 1119 | HFILE clientStdin = HF_NULL;
|
---|
[41] | 1120 | do {
|
---|
| 1121 | // create a Stdin pipe
|
---|
| 1122 | sprintf( pathBuf, "\\pipe\\Qt\\%08lX\\QProcess\\%p\\Stdin",
|
---|
| 1123 | ppib->pib_ulpid, this );
|
---|
| 1124 | rc = DosCreateNPipe( pathBuf, &d->pipeStdin,
|
---|
| 1125 | NP_ACCESS_OUTBOUND, NP_NOWAIT | NP_TYPE_BYTE | 1,
|
---|
[134] | 1126 | PIPE_SIZE_STDIN, 0, 0 );
|
---|
[41] | 1127 | if ( rc != NO_ERROR )
|
---|
| 1128 | break;
|
---|
| 1129 | DosConnectNPipe( d->pipeStdin );
|
---|
| 1130 | // open a client end of the Stdout pipe
|
---|
| 1131 | ULONG action = 0;
|
---|
| 1132 | rc = DosOpen( pathBuf, &clientStdin, &action, 0, FILE_NORMAL, FILE_OPEN,
|
---|
| 1133 | OPEN_ACCESS_READONLY | OPEN_SHARE_DENYREADWRITE | OPEN_FLAGS_NOINHERIT,
|
---|
| 1134 | (PEAOP2) NULL);
|
---|
| 1135 | if ( rc != NO_ERROR )
|
---|
| 1136 | break;
|
---|
| 1137 | // save the real STDIN handle
|
---|
| 1138 | if ( (rc = DosDupHandle( HF_STDIN, &tmpStdin)) != NO_ERROR )
|
---|
| 1139 | break;
|
---|
| 1140 | // redirect the real STDIN handle to the client end of the pipe
|
---|
| 1141 | if ( (rc = DosDupHandle( clientStdin, &realStdin)) != NO_ERROR )
|
---|
| 1142 | break;
|
---|
| 1143 | } while( 0 );
|
---|
| 1144 | // close the client end anyway (no more necessary)
|
---|
[134] | 1145 | if ( clientStdin != HF_NULL )
|
---|
[41] | 1146 | DosClose ( clientStdin );
|
---|
| 1147 | // close all opened handles on error
|
---|
| 1148 | if ( rc != NO_ERROR ) {
|
---|
| 1149 | #if defined(QT_QPROCESS_DEBUG)
|
---|
[134] | 1150 | qDebug( "Failed to create a STDIN redirection (%s): SYS%04ld",
|
---|
| 1151 | pathBuf, rc );
|
---|
[41] | 1152 | #endif
|
---|
[134] | 1153 | if ( tmpStdin != HF_NULL )
|
---|
[41] | 1154 | DosClose ( tmpStdin );
|
---|
| 1155 | d->closeHandles();
|
---|
[134] | 1156 | UninstallQtMsgHandler();
|
---|
[41] | 1157 | return FALSE;
|
---|
| 1158 | }
|
---|
| 1159 | }
|
---|
| 1160 | if ( comms & Stdout ) {
|
---|
[134] | 1161 | HFILE clientStdout = HF_NULL;
|
---|
[41] | 1162 | do {
|
---|
| 1163 | // create a Stdout pipe
|
---|
| 1164 | sprintf( pathBuf, "\\pipe\\Qt\\%08lX\\QProcess\\%p\\Stdout",
|
---|
| 1165 | ppib->pib_ulpid, this );
|
---|
[134] | 1166 | rc = DosCreateNPipe( pathBuf, &d->stdout.pipe,
|
---|
[41] | 1167 | NP_ACCESS_INBOUND, NP_NOWAIT | NP_TYPE_BYTE | 1,
|
---|
[134] | 1168 | 0, PIPE_SIZE_STDOUT, 0 );
|
---|
[41] | 1169 | if ( rc != NO_ERROR )
|
---|
| 1170 | break;
|
---|
[134] | 1171 | DosConnectNPipe( d->stdout.pipe );
|
---|
[41] | 1172 | // open a client end of the Stdout pipe
|
---|
| 1173 | ULONG action = 0;
|
---|
| 1174 | rc = DosOpen( pathBuf, &clientStdout, &action, 0, FILE_NORMAL, FILE_OPEN,
|
---|
| 1175 | OPEN_ACCESS_WRITEONLY | OPEN_SHARE_DENYREADWRITE | OPEN_FLAGS_NOINHERIT,
|
---|
| 1176 | (PEAOP2) NULL);
|
---|
| 1177 | if ( rc != NO_ERROR )
|
---|
| 1178 | break;
|
---|
| 1179 | // save the real STDOUT handle
|
---|
| 1180 | if ( (rc = DosDupHandle( HF_STDOUT, &tmpStdout )) != NO_ERROR )
|
---|
| 1181 | break;
|
---|
| 1182 | // redirect the real STDOUT handle to the client end of the pipe
|
---|
| 1183 | if ( (rc = DosDupHandle( clientStdout, &realStdout )) != NO_ERROR )
|
---|
| 1184 | break;
|
---|
| 1185 | } while( 0 );
|
---|
| 1186 | // close the client end anyway (no more necessary)
|
---|
[134] | 1187 | if ( clientStdout != HF_NULL )
|
---|
[41] | 1188 | DosClose ( clientStdout );
|
---|
| 1189 | // close all opened handles on error
|
---|
| 1190 | if ( rc != NO_ERROR ) {
|
---|
| 1191 | #if defined(QT_QPROCESS_DEBUG)
|
---|
[134] | 1192 | qDebug( "Failed to create a STDOUT redirection (%s): SYS%04ld",
|
---|
| 1193 | pathBuf, rc );
|
---|
[41] | 1194 | #endif
|
---|
[134] | 1195 | if ( tmpStdin != HF_NULL ) {
|
---|
[41] | 1196 | DosDupHandle( tmpStdin, &realStdin );
|
---|
| 1197 | DosClose( tmpStdin );
|
---|
| 1198 | }
|
---|
[134] | 1199 | if ( tmpStdout != HF_NULL )
|
---|
[41] | 1200 | DosClose ( tmpStdout );
|
---|
| 1201 | d->closeHandles();
|
---|
[134] | 1202 | UninstallQtMsgHandler();
|
---|
[41] | 1203 | return FALSE;
|
---|
| 1204 | }
|
---|
| 1205 | }
|
---|
| 1206 | if ( (comms & Stderr) && !(comms & DupStderr) ) {
|
---|
[134] | 1207 | HFILE clientStderr = HF_NULL;
|
---|
[41] | 1208 | do {
|
---|
| 1209 | // create a Stderr pipe
|
---|
| 1210 | sprintf( pathBuf, "\\pipe\\Qt\\%08lX\\QProcess\\%p\\Stderr",
|
---|
| 1211 | ppib->pib_ulpid, this );
|
---|
[134] | 1212 | rc = DosCreateNPipe( pathBuf, &d->stderr.pipe,
|
---|
[41] | 1213 | NP_ACCESS_INBOUND, NP_NOWAIT | NP_TYPE_BYTE | 1,
|
---|
[134] | 1214 | 0, PIPE_SIZE_STDERR, 0 );
|
---|
[41] | 1215 | if ( rc != NO_ERROR )
|
---|
| 1216 | break;
|
---|
[134] | 1217 | DosConnectNPipe( d->stderr.pipe );
|
---|
[41] | 1218 | // open a client end of the Stderr pipe
|
---|
| 1219 | ULONG action = 0;
|
---|
| 1220 | rc = DosOpen( pathBuf, &clientStderr, &action, 0, FILE_NORMAL, FILE_OPEN,
|
---|
| 1221 | OPEN_ACCESS_WRITEONLY | OPEN_SHARE_DENYREADWRITE | OPEN_FLAGS_NOINHERIT,
|
---|
| 1222 | (PEAOP2) NULL);
|
---|
| 1223 | if ( rc != NO_ERROR )
|
---|
| 1224 | break;
|
---|
| 1225 | // save the real STDERR handle
|
---|
| 1226 | if ( (rc = DosDupHandle( HF_STDERR, &tmpStderr )) != NO_ERROR )
|
---|
| 1227 | break;
|
---|
| 1228 | // redirect the real STDERR handle to the client end of the pipe
|
---|
| 1229 | if ( (rc = DosDupHandle( clientStderr, &realStderr )) != NO_ERROR )
|
---|
| 1230 | break;
|
---|
| 1231 | } while( 0 );
|
---|
| 1232 | // close the client end anyway (no more necessary)
|
---|
[134] | 1233 | if ( clientStderr != HF_NULL )
|
---|
[41] | 1234 | DosClose ( clientStderr );
|
---|
| 1235 | // close all opened handles on error
|
---|
| 1236 | if ( rc != NO_ERROR ) {
|
---|
| 1237 | #if defined(QT_QPROCESS_DEBUG)
|
---|
[134] | 1238 | qDebug( "Failed to create a STDERR redirection (%s): SYS%04ld",
|
---|
| 1239 | pathBuf, rc );
|
---|
[41] | 1240 | #endif
|
---|
[134] | 1241 | if ( tmpStdin != HF_NULL ) {
|
---|
[41] | 1242 | DosDupHandle( tmpStdin, &realStdin );
|
---|
| 1243 | DosClose( tmpStdin );
|
---|
| 1244 | }
|
---|
[134] | 1245 | if ( tmpStdout != HF_NULL ) {
|
---|
[41] | 1246 | DosDupHandle( tmpStdout, &realStdout );
|
---|
| 1247 | DosClose( tmpStdout );
|
---|
| 1248 | }
|
---|
[134] | 1249 | if ( tmpStderr != HF_NULL )
|
---|
[41] | 1250 | DosClose ( tmpStderr );
|
---|
| 1251 | d->closeHandles();
|
---|
[134] | 1252 | UninstallQtMsgHandler();
|
---|
[41] | 1253 | return FALSE;
|
---|
| 1254 | }
|
---|
| 1255 | }
|
---|
| 1256 | if ( comms & DupStderr ) {
|
---|
[134] | 1257 | // leave d->stderr.pipe equal to HP_NULL
|
---|
[41] | 1258 | do {
|
---|
| 1259 | // save the real STDERR handle
|
---|
| 1260 | if ( (rc = DosDupHandle( HF_STDERR, &tmpStderr )) != NO_ERROR )
|
---|
| 1261 | break;
|
---|
| 1262 | // redirect STDERR to STDOUT
|
---|
| 1263 | if ( (rc = DosDupHandle( HF_STDOUT, &realStderr )) != NO_ERROR )
|
---|
| 1264 | break;
|
---|
| 1265 | } while( 0 );
|
---|
| 1266 | // close all opened handles on error
|
---|
| 1267 | if ( rc != NO_ERROR ) {
|
---|
| 1268 | #if defined(QT_QPROCESS_DEBUG)
|
---|
[134] | 1269 | qDebug( "Failed to redirect STDERR to STDOUT: SYS%04ld",
|
---|
| 1270 | rc );
|
---|
[41] | 1271 | #endif
|
---|
[134] | 1272 | if ( tmpStdin != HF_NULL ) {
|
---|
[41] | 1273 | DosDupHandle( tmpStdin, &realStdin );
|
---|
| 1274 | DosClose( tmpStdin );
|
---|
| 1275 | }
|
---|
[134] | 1276 | if ( tmpStdout != HF_NULL ) {
|
---|
[41] | 1277 | DosDupHandle( tmpStdout, &realStdout );
|
---|
| 1278 | DosClose( tmpStdout );
|
---|
| 1279 | }
|
---|
[134] | 1280 | if ( tmpStderr != HF_NULL )
|
---|
[41] | 1281 | DosClose ( tmpStderr );
|
---|
| 1282 | d->closeHandles();
|
---|
[134] | 1283 | UninstallQtMsgHandler();
|
---|
[41] | 1284 | return FALSE;
|
---|
| 1285 | }
|
---|
| 1286 | }
|
---|
| 1287 |
|
---|
[134] | 1288 | // add this process to the monitor
|
---|
| 1289 | if ( !processMonitor->addProcess( this ) ) {
|
---|
| 1290 | if ( tmpStdin != HF_NULL ) {
|
---|
| 1291 | DosDupHandle( tmpStdin, &realStdin );
|
---|
| 1292 | DosClose( tmpStdin );
|
---|
| 1293 | }
|
---|
| 1294 | if ( tmpStdout != HF_NULL ) {
|
---|
| 1295 | DosDupHandle( tmpStdout, &realStdout );
|
---|
| 1296 | DosClose( tmpStdout );
|
---|
| 1297 | }
|
---|
| 1298 | if ( tmpStderr != HF_NULL ) {
|
---|
| 1299 | DosDupHandle( tmpStderr, &realStderr );
|
---|
| 1300 | DosClose ( tmpStderr );
|
---|
| 1301 | }
|
---|
| 1302 | d->closeHandles();
|
---|
| 1303 | UninstallQtMsgHandler();
|
---|
| 1304 | return FALSE;
|
---|
| 1305 | }
|
---|
| 1306 |
|
---|
[51] | 1307 | // set the working directory
|
---|
| 1308 | QString curDir = QDir::currentDirPath();
|
---|
| 1309 | QDir::setCurrent( workingDir.path() );
|
---|
| 1310 | #if defined(QT_QPROCESS_DEBUG)
|
---|
[134] | 1311 | qDebug( "QProcess::start(): curDir='%s', workingDir='%s'",
|
---|
| 1312 | curDir.local8Bit().data(),
|
---|
| 1313 | QDir::currentDirPath().local8Bit().data() );
|
---|
[51] | 1314 | #endif
|
---|
| 1315 |
|
---|
[168] | 1316 | #if defined(QT_QPROCESS_USE_DOSEXECPGM)
|
---|
| 1317 |
|
---|
[41] | 1318 | // DosExecPgm()
|
---|
| 1319 |
|
---|
| 1320 | RESULTCODES resc = { 0 };
|
---|
| 1321 |
|
---|
| 1322 | rc = DosExecPgm( pathBuf, sizeof(pathBuf), EXEC_ASYNCRESULT,
|
---|
| 1323 | buf.data(), envlist.data(), &resc, appNameFull.data() );
|
---|
[51] | 1324 |
|
---|
[168] | 1325 | #else // defined(QT_QPROCESS_USE_DOSEXECPGM)
|
---|
| 1326 |
|
---|
| 1327 | // start the application
|
---|
| 1328 |
|
---|
| 1329 | int i = 0;
|
---|
| 1330 |
|
---|
| 1331 | char **argv = new char *[ appArgs.count() + 2 ];
|
---|
| 1332 | argv[ i++ ] = appName.data();
|
---|
| 1333 | for ( appArgs.first(); appArgs.current(); appArgs.next() )
|
---|
| 1334 | argv[ i++ ] = appArgs.current();
|
---|
| 1335 | argv[ i ] = NULL;
|
---|
| 1336 |
|
---|
| 1337 | char **envv = NULL;
|
---|
| 1338 | if ( envList.count() ) {
|
---|
| 1339 | i = 0;
|
---|
| 1340 | envv = new char *[ envList.count() + 1 ];
|
---|
| 1341 | for ( envList.first(); envList.current(); envList.next() )
|
---|
| 1342 | envv[ i++ ] = envList.current();
|
---|
| 1343 | envv[ i ] = NULL;
|
---|
| 1344 | } else {
|
---|
| 1345 | envv = environ;
|
---|
| 1346 | }
|
---|
| 1347 |
|
---|
| 1348 | int pid = spawnvpe( P_NOWAIT | P_DEFAULT, appName.data(), argv, envv );
|
---|
| 1349 |
|
---|
| 1350 | // if spawnvpe() couldn't find the executable, try .CMD and .BAT
|
---|
| 1351 | // extensions (in order of CMD.EXE precedence); spawnvpe() knows how to
|
---|
| 1352 | // locate and run them (i.e. using CMD.EXE /c)
|
---|
| 1353 | if ( pid == -1 && errno == ENOENT ) {
|
---|
| 1354 | appName += ".cmd";
|
---|
| 1355 | pid = spawnvpe( P_NOWAIT | P_DEFAULT, appName.data(), argv, envv );
|
---|
| 1356 | if ( pid == -1 && errno == ENOENT ) {
|
---|
| 1357 | strcpy( appName.data() + appName.length() - 4, ".bat" );
|
---|
| 1358 | pid = spawnvpe( P_NOWAIT | P_DEFAULT, appName.data(), argv, envv );
|
---|
| 1359 | }
|
---|
| 1360 | }
|
---|
| 1361 |
|
---|
| 1362 | if ( envv != environ )
|
---|
| 1363 | delete[] envv;
|
---|
| 1364 | delete[] argv;
|
---|
| 1365 |
|
---|
| 1366 | #endif // defined(QT_QPROCESS_USE_DOSEXECPGM)
|
---|
| 1367 |
|
---|
[51] | 1368 | // restore the current directory
|
---|
| 1369 | QDir::setCurrent( curDir );
|
---|
[41] | 1370 |
|
---|
| 1371 | // cancel STDIN/OUT/ERR redirections
|
---|
[134] | 1372 | if ( tmpStdin != HF_NULL ) {
|
---|
[41] | 1373 | DosDupHandle( tmpStdin, &realStdin );
|
---|
| 1374 | DosClose( tmpStdin );
|
---|
| 1375 | }
|
---|
[134] | 1376 | if ( tmpStdout != HF_NULL ) {
|
---|
[41] | 1377 | DosDupHandle( tmpStdout, &realStdout );
|
---|
| 1378 | DosClose( tmpStdout );
|
---|
| 1379 | }
|
---|
[134] | 1380 | if ( tmpStderr != HF_NULL ) {
|
---|
[41] | 1381 | DosDupHandle( tmpStderr, &realStderr );
|
---|
| 1382 | DosClose( tmpStderr );
|
---|
| 1383 | }
|
---|
| 1384 |
|
---|
[134] | 1385 | UninstallQtMsgHandler();
|
---|
| 1386 |
|
---|
[168] | 1387 | #if defined(QT_QPROCESS_USE_DOSEXECPGM)
|
---|
| 1388 |
|
---|
[41] | 1389 | if ( rc != NO_ERROR ) {
|
---|
| 1390 | #if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
|
---|
| 1391 | #if defined(QT_DEBUG)
|
---|
| 1392 | qWarning( "[%s] [%s]\n(%s):",
|
---|
| 1393 | buf.data(), buf.data() + strlen( buf.data() ) + 1,
|
---|
| 1394 | pathBuf );
|
---|
| 1395 | #endif
|
---|
| 1396 | qSystemWarning( "Failed to start a new process!", rc );
|
---|
| 1397 | #endif
|
---|
[134] | 1398 | processMonitor->removeProcess( d );
|
---|
[41] | 1399 | d->closeHandles();
|
---|
| 1400 | return FALSE;
|
---|
| 1401 | }
|
---|
| 1402 |
|
---|
| 1403 | // memporize PID of the started process
|
---|
| 1404 | d->pid = resc.codeTerminate;
|
---|
| 1405 |
|
---|
[168] | 1406 | #else // defined(QT_QPROCESS_USE_DOSEXECPGM)
|
---|
| 1407 |
|
---|
| 1408 | if ( pid == -1 ) {
|
---|
| 1409 | #if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
|
---|
| 1410 | qWarning( "Failed to start a new process [%s]\n"
|
---|
| 1411 | "errno=%d (%s)",
|
---|
| 1412 | QFile::encodeName( _arguments.join( "] [" ) ).data(),
|
---|
| 1413 | errno, strerror( errno ) );
|
---|
| 1414 | #endif
|
---|
| 1415 | processMonitor->removeProcess( d );
|
---|
| 1416 | d->closeHandles();
|
---|
| 1417 | return FALSE;
|
---|
| 1418 | }
|
---|
| 1419 |
|
---|
| 1420 | // memporize PID of the started process
|
---|
| 1421 | d->pid = pid;
|
---|
| 1422 |
|
---|
| 1423 | #endif // defined(QT_QPROCESS_USE_DOSEXECPGM)
|
---|
| 1424 |
|
---|
| 1425 | #if defined(QT_QPROCESS_DEBUG)
|
---|
| 1426 | qDebug( "QProcess::start(): started PID %lu (0x%08lX)", d->pid, d->pid );
|
---|
| 1427 | #endif
|
---|
| 1428 |
|
---|
[134] | 1429 | // timer is not necessary for ioRedirection (we use the monitor thread)
|
---|
| 1430 | if ( /* ioRedirection || */ notifyOnExit ) {
|
---|
| 1431 | d->lookup->start( POLL_TIMER );
|
---|
[41] | 1432 | }
|
---|
| 1433 |
|
---|
| 1434 | return TRUE;
|
---|
| 1435 | }
|
---|
| 1436 |
|
---|
| 1437 | void QProcess::tryTerminate() const
|
---|
| 1438 | {
|
---|
| 1439 | if ( d->pid == PID_NULL )
|
---|
| 1440 | return;
|
---|
| 1441 |
|
---|
| 1442 | HSWITCH hswitch = WinQuerySwitchHandle( NULL, d->pid );
|
---|
| 1443 | if ( hswitch != NULLHANDLE ) {
|
---|
| 1444 | SWCNTRL swcntrl = { 0 };
|
---|
| 1445 | APIRET rc = WinQuerySwitchEntry( hswitch, &swcntrl );
|
---|
| 1446 | // WinQuerySwitchEntry will return a switch entry of the parent
|
---|
| 1447 | // process if the specfied one doesn't have a separate session
|
---|
| 1448 | // (running a plain CMD.EXE is an example); ignore this case.
|
---|
| 1449 | if ( rc == NO_ERROR && swcntrl.idProcess == d->pid )
|
---|
| 1450 | {
|
---|
| 1451 | // first, ensure that the Close action is enabled in the main frame
|
---|
| 1452 | // window (otherwise WM_SYSCOMMAND/SC_CLOSE will be ignored)
|
---|
| 1453 | HWND hwndSysMenu = WinWindowFromID( swcntrl.hwnd, FID_SYSMENU );
|
---|
| 1454 | if (hwndSysMenu) {
|
---|
| 1455 | WinPostMsg( hwndSysMenu, MM_SETITEMATTR,
|
---|
| 1456 | MPFROM2SHORT( SC_CLOSE, TRUE ),
|
---|
| 1457 | MPFROM2SHORT( MIA_DISABLED, 0 ) );
|
---|
| 1458 | }
|
---|
| 1459 | WinPostMsg( swcntrl.hwnd, WM_SYSCOMMAND,
|
---|
| 1460 | MPFROM2SHORT( SC_CLOSE, CMDSRC_OTHER ),
|
---|
| 1461 | MPFROMLONG( FALSE ) );
|
---|
| 1462 | }
|
---|
| 1463 | }
|
---|
| 1464 | }
|
---|
| 1465 |
|
---|
| 1466 | void QProcess::kill() const
|
---|
| 1467 | {
|
---|
| 1468 | if ( d->pid != PID_NULL )
|
---|
| 1469 | DosKillProcess( DKP_PROCESS, d->pid );
|
---|
| 1470 | }
|
---|
| 1471 |
|
---|
| 1472 | bool QProcess::isRunning() const
|
---|
| 1473 | {
|
---|
| 1474 | if ( d->pid == PID_NULL )
|
---|
| 1475 | return FALSE;
|
---|
| 1476 |
|
---|
[168] | 1477 | #if defined(QT_QPROCESS_USE_DOSEXECPGM)
|
---|
| 1478 |
|
---|
[41] | 1479 | PID pidEnded = PID_NULL;
|
---|
| 1480 | RESULTCODES resc = { 0 };
|
---|
| 1481 | APIRET rc = DosWaitChild( DCWA_PROCESS, DCWW_NOWAIT, &resc,
|
---|
| 1482 | &pidEnded, d->pid );
|
---|
| 1483 |
|
---|
| 1484 | if ( rc == ERROR_CHILD_NOT_COMPLETE )
|
---|
| 1485 | return TRUE;
|
---|
[134] | 1486 |
|
---|
[168] | 1487 | #else // defined(QT_QPROCESS_USE_DOSEXECPGM)
|
---|
| 1488 |
|
---|
| 1489 | int stat = 0;
|
---|
| 1490 | int pid = waitpid( d->pid, &stat, WNOHANG );
|
---|
| 1491 | if ( (pid == 0) ||
|
---|
| 1492 | ((PID) pid == d->pid &&
|
---|
| 1493 | !WIFEXITED( stat ) && !WIFSIGNALED( stat ) && !WIFSTOPPED( stat ) ) )
|
---|
| 1494 | return TRUE;
|
---|
| 1495 |
|
---|
| 1496 | if ( pid == -1 ) {
|
---|
| 1497 | #if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
|
---|
| 1498 | qWarning( "Failed to wait for a child process\n"
|
---|
| 1499 | "errno=%d (%s)", errno, strerror( errno ) );
|
---|
| 1500 | #endif
|
---|
| 1501 | }
|
---|
| 1502 |
|
---|
| 1503 | #endif // defined(QT_QPROCESS_USE_DOSEXECPGM)
|
---|
| 1504 |
|
---|
[41] | 1505 | QProcess *that = (QProcess *) this;
|
---|
[134] | 1506 |
|
---|
[188] | 1507 | /// @todo the below comment is not relevant any more since removeProcess()
|
---|
| 1508 | // will dispatch all related messages including WM_U_PIPE_RDATA. Therefore,
|
---|
| 1509 | // this code should not be needed anymore.
|
---|
| 1510 | #if 0
|
---|
[134] | 1511 | // There might be data to read, but WM_U_PIPE_RDATA messages won't be
|
---|
| 1512 | // converted to signals after removeProcess() is called below. Therefore,
|
---|
| 1513 | // emit signals ourselved if necessary.
|
---|
| 1514 |
|
---|
| 1515 | if ( d->readPipe( &d->stdout ) )
|
---|
| 1516 | emit that->readyReadStdout();
|
---|
| 1517 | if ( d->readPipe( &d->stderr ) )
|
---|
| 1518 | emit that->readyReadStderr();
|
---|
[188] | 1519 | #endif
|
---|
[41] | 1520 |
|
---|
| 1521 | // compute the exit values
|
---|
| 1522 | if ( !d->exitValuesCalculated ) {
|
---|
[168] | 1523 | #if defined(QT_QPROCESS_USE_DOSEXECPGM)
|
---|
[41] | 1524 | that->exitNormal = resc.codeTerminate == TC_EXIT;
|
---|
| 1525 | that->exitStat = resc.codeResult;
|
---|
[168] | 1526 | #else // defined(QT_QPROCESS_USE_DOSEXECPGM)
|
---|
| 1527 | that->exitNormal = pid != -1 && WIFEXITED( stat );
|
---|
| 1528 | that->exitStat = WEXITSTATUS( stat );
|
---|
| 1529 | #endif // defined(QT_QPROCESS_USE_DOSEXECPGM)
|
---|
[41] | 1530 | d->exitValuesCalculated = TRUE;
|
---|
| 1531 | }
|
---|
[134] | 1532 |
|
---|
| 1533 | processMonitor->removeProcess( d );
|
---|
[41] | 1534 |
|
---|
| 1535 | d->pid = PID_NULL;
|
---|
| 1536 | d->closeHandles();
|
---|
| 1537 | return FALSE;
|
---|
| 1538 | }
|
---|
| 1539 |
|
---|
| 1540 | bool QProcess::canReadLineStdout() const
|
---|
| 1541 | {
|
---|
[134] | 1542 | if( d->stdout.pipe == HP_NULL )
|
---|
| 1543 | return d->stdout.buf.size() != 0;
|
---|
[41] | 1544 |
|
---|
| 1545 | QProcess *that = (QProcess *) this;
|
---|
| 1546 | return that->membufStdout()->scanNewline( 0 );
|
---|
| 1547 | }
|
---|
| 1548 |
|
---|
| 1549 | bool QProcess::canReadLineStderr() const
|
---|
| 1550 | {
|
---|
[134] | 1551 | if( d->stderr.pipe == HP_NULL )
|
---|
| 1552 | return d->stderr.buf.size() != 0;
|
---|
[41] | 1553 |
|
---|
| 1554 | QProcess *that = (QProcess *) this;
|
---|
| 1555 | return that->membufStderr()->scanNewline( 0 );
|
---|
| 1556 | }
|
---|
| 1557 |
|
---|
| 1558 | void QProcess::writeToStdin( const QByteArray& buf )
|
---|
| 1559 | {
|
---|
| 1560 | d->stdinBuf.enqueue( new QByteArray(buf) );
|
---|
| 1561 | socketWrite( 0 );
|
---|
| 1562 | }
|
---|
| 1563 |
|
---|
| 1564 | void QProcess::closeStdin( )
|
---|
| 1565 | {
|
---|
[134] | 1566 | if ( d->pipeStdin != HP_NULL ) {
|
---|
[41] | 1567 | DosDisConnectNPipe( d->pipeStdin );
|
---|
| 1568 | DosClose( d->pipeStdin );
|
---|
[134] | 1569 | d->pipeStdin = HP_NULL;
|
---|
[41] | 1570 | }
|
---|
| 1571 | }
|
---|
| 1572 |
|
---|
[134] | 1573 | /* dummy, since MOC doesn't understand preprocessor defines */
|
---|
| 1574 | void QProcess::socketRead( int fd )
|
---|
[41] | 1575 | {
|
---|
| 1576 | }
|
---|
| 1577 |
|
---|
| 1578 | void QProcess::socketWrite( int )
|
---|
| 1579 | {
|
---|
[134] | 1580 | if ( d->pipeStdin == HP_NULL )
|
---|
[41] | 1581 | return;
|
---|
| 1582 |
|
---|
| 1583 | while ( !d->stdinBuf.isEmpty() && isRunning() ) {
|
---|
| 1584 | ULONG written = 0;
|
---|
| 1585 | APIRET rc = DosWrite( d->pipeStdin,
|
---|
| 1586 | d->stdinBuf.head()->data() + d->stdinBufRead,
|
---|
[134] | 1587 | QMIN( PIPE_SIZE_STDIN,
|
---|
| 1588 | d->stdinBuf.head()->size() - d->stdinBufRead ),
|
---|
[41] | 1589 | &written );
|
---|
| 1590 | if ( rc != NO_ERROR ) {
|
---|
[134] | 1591 | #if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
|
---|
[41] | 1592 | qSystemWarning( "Failed to write to the pipe!", rc );
|
---|
| 1593 | #endif
|
---|
[134] | 1594 | d->lookup->start( POLL_TIMER );
|
---|
[41] | 1595 | return;
|
---|
| 1596 | }
|
---|
| 1597 |
|
---|
| 1598 | d->stdinBufRead += written;
|
---|
| 1599 | if ( d->stdinBufRead == d->stdinBuf.head()->size() ) {
|
---|
| 1600 | d->stdinBufRead = 0;
|
---|
| 1601 | delete d->stdinBuf.dequeue();
|
---|
| 1602 | if ( wroteToStdinConnected && d->stdinBuf.isEmpty() )
|
---|
| 1603 | emit wroteToStdin();
|
---|
| 1604 | }
|
---|
| 1605 | }
|
---|
| 1606 | }
|
---|
| 1607 |
|
---|
| 1608 | void QProcess::flushStdin()
|
---|
| 1609 | {
|
---|
| 1610 | socketWrite( 0 );
|
---|
| 1611 | }
|
---|
| 1612 |
|
---|
| 1613 | /*
|
---|
| 1614 | Use a timer for polling misc. stuff.
|
---|
| 1615 | */
|
---|
| 1616 | void QProcess::timeout()
|
---|
| 1617 | {
|
---|
| 1618 | // Disable the timer temporary since one of the slots that are connected to
|
---|
| 1619 | // the readyRead...(), etc. signals might trigger recursion if
|
---|
| 1620 | // processEvents() is called.
|
---|
| 1621 | d->lookup->stop();
|
---|
| 1622 |
|
---|
| 1623 | // try to write pending data to stdin
|
---|
| 1624 | if ( !d->stdinBuf.isEmpty() )
|
---|
| 1625 | socketWrite( 0 );
|
---|
| 1626 |
|
---|
| 1627 | if ( isRunning() ) {
|
---|
| 1628 | // enable timer again, if needed
|
---|
[134] | 1629 | // timer is not necessary for ioRedirection (we use the monitor thread)
|
---|
| 1630 | if ( !d->stdinBuf.isEmpty() || /* ioRedirection || */ notifyOnExit )
|
---|
| 1631 | d->lookup->start( POLL_TIMER );
|
---|
[41] | 1632 | } else if ( notifyOnExit ) {
|
---|
| 1633 | emit processExited();
|
---|
| 1634 | }
|
---|
| 1635 | }
|
---|
| 1636 |
|
---|
| 1637 | /*
|
---|
| 1638 | Used by connectNotify() and disconnectNotify() to change the value of
|
---|
| 1639 | ioRedirection (and related behaviour)
|
---|
| 1640 | */
|
---|
| 1641 | void QProcess::setIoRedirection( bool value )
|
---|
| 1642 | {
|
---|
| 1643 | ioRedirection = value;
|
---|
[134] | 1644 | // timer is not necessary for ioRedirection (we use the monitor thread)
|
---|
| 1645 | #if 0
|
---|
[41] | 1646 | if ( !ioRedirection && !notifyOnExit )
|
---|
| 1647 | d->lookup->stop();
|
---|
| 1648 | if ( ioRedirection ) {
|
---|
| 1649 | if ( isRunning() )
|
---|
[134] | 1650 | d->lookup->start( POLL_TIMER );
|
---|
[41] | 1651 | }
|
---|
[134] | 1652 | #endif
|
---|
[41] | 1653 | }
|
---|
| 1654 |
|
---|
| 1655 | /*
|
---|
| 1656 | Used by connectNotify() and disconnectNotify() to change the value of
|
---|
| 1657 | notifyOnExit (and related behaviour)
|
---|
| 1658 | */
|
---|
| 1659 | void QProcess::setNotifyOnExit( bool value )
|
---|
| 1660 | {
|
---|
| 1661 | notifyOnExit = value;
|
---|
[134] | 1662 | // timer is not necessary for ioRedirection (we use the monitor thread)
|
---|
| 1663 | if ( /* !ioRedirection && */ !notifyOnExit )
|
---|
[41] | 1664 | d->lookup->stop();
|
---|
| 1665 | if ( notifyOnExit ) {
|
---|
| 1666 | if ( isRunning() )
|
---|
[134] | 1667 | d->lookup->start( POLL_TIMER );
|
---|
[41] | 1668 | }
|
---|
| 1669 | }
|
---|
| 1670 |
|
---|
| 1671 | /*
|
---|
| 1672 | Used by connectNotify() and disconnectNotify() to change the value of
|
---|
| 1673 | wroteToStdinConnected (and related behaviour)
|
---|
| 1674 | */
|
---|
| 1675 | void QProcess::setWroteStdinConnected( bool value )
|
---|
| 1676 | {
|
---|
| 1677 | wroteToStdinConnected = value;
|
---|
| 1678 | }
|
---|
| 1679 |
|
---|
| 1680 | QProcess::PID QProcess::processIdentifier()
|
---|
| 1681 | {
|
---|
| 1682 | return d->pid;
|
---|
| 1683 | }
|
---|
| 1684 |
|
---|
| 1685 | #endif // QT_NO_PROCESS
|
---|