source: trunk/src/kernel/qprocess_pm.cpp

Last change on this file was 188, checked in by dmik, 17 years ago

Kernel: QPrcess: Another attempt to fix unexpected pipe handle reusal (ticket:51).

  • Property svn:keywords set to Id
File size: 54.0 KB
Line 
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.
7** Copyright (C) 2005 netlabs.org. OS/2 Version.
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"
46#include "qdir.h"
47#include "qthread.h"
48#include "qintdict.h"
49#include "qmutex.h"
50#include "qfile.h"
51#include "private/qinternal_p.h"
52#include "qt_os2.h"
53
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
59#include <string.h>
60
61#if !defined(QT_QPROCESS_USE_DOSEXECPGM)
62#include <process.h>
63#include <sys/wait.h>
64#endif
65
66//#define QT_QPROCESS_DEBUG
67
68#define HF_STDIN HFILE( 0 )
69#define HF_STDOUT HFILE( 1 )
70#define HF_STDERR HFILE( 2 )
71#define HF_NULL HFILE( ~0 )
72
73#define HP_NULL HPIPE( ~0 )
74#define KEY_NULL USHORT( ~0 )
75
76#define PID_NULL PID( ~0 )
77
78enum
79{
80 PIPE_SIZE_STDIN = 65520, // max
81 PIPE_SIZE_STDOUT = 65520, // max
82 PIPE_SIZE_STDERR = 4096,
83
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
91#if defined(QT_QPROCESS_DEBUG)
92#include <stdarg.h>
93static HFILE StdErrHandle = HF_NULL;
94QtMsgHandler OldMsgHandler = NULL;
95static 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)
120#endif
121
122/***********************************************************************
123 *
124 * QProcessPrivate
125 *
126 **********************************************************************/
127class QProcessPrivate
128{
129public:
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
143 QProcessPrivate( QProcess *proc )
144 {
145 stdinBufRead = 0;
146 pipeStdin = HP_NULL;
147 pid = PID_NULL;
148 exitValuesCalculated = FALSE;
149
150 lookup = new QTimer( proc );
151 qApp->connect( lookup, SIGNAL(timeout()), proc, SLOT(timeout()) );
152 }
153
154 ~QProcessPrivate()
155 {
156 reset();
157 }
158
159 void reset()
160 {
161 while ( !stdinBuf.isEmpty() ) {
162 delete stdinBuf.dequeue();
163 }
164 stdinBufRead = 0;
165 closeHandles();
166 stdout.buf.clear();
167 stderr.buf.clear();
168 pid = PID_NULL;
169 exitValuesCalculated = FALSE;
170 }
171
172 Pipe *findPipe( USHORT key )
173 {
174 if ( stdout.key == key ) return &stdout;
175 if ( stderr.key == key ) return &stderr;
176 return NULL;
177 }
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
193 void closeHandles()
194 {
195 if( pipeStdin != HP_NULL ) {
196 DosDisConnectNPipe( pipeStdin );
197 DosClose( pipeStdin );
198 pipeStdin = HP_NULL;
199 }
200 closePipe( &stdout );
201 closePipe( &stderr );
202 }
203
204 QPtrQueue <QByteArray> stdinBuf;
205 uint stdinBufRead;
206
207 HPIPE pipeStdin;
208
209 Pipe stdout;
210 Pipe stderr;
211
212 PID pid;
213
214 QTimer *lookup;
215
216 bool exitValuesCalculated : 1;
217};
218
219class QProcessMonitor : public QPMObjectWindow
220{
221public:
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,
239 QProcessPrivate::Pipe *pipe = NULL,
240 bool inMsgHandler = false);
241
242 void monitor();
243 MRESULT message( ULONG msg, MPARAM mp1, MPARAM mp2 );
244
245private:
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;
287};
288
289QProcessMonitor::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}
303
304QProcessMonitor::~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
321bool 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
349void 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
361bool 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
407void QProcessMonitor::removeProcess( QProcessPrivate *d,
408 QProcessPrivate::Pipe *pipe /* = NULL */,
409 bool inMsgHandler /* = false */)
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 }
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).
439
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
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 */
483void 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
581MRESULT 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 );
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 ) ) {
590#if defined(QT_QPROCESS_DEBUG)
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 );
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 }
603#if defined(QT_QPROCESS_DEBUG)
604 else {
605 qDebug( "QProcessMonitor::WM_U_PIPE_RDATA: proc=%p (invalid)",
606 proc );
607 }
608#endif
609 break;
610 }
611 case WM_U_PIPE_CLOSE: {
612 QProcess *proc = (QProcess *) PVOIDFROMMP( mp1 );
613 USHORT key = SHORT1FROMMP( mp2 );
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 ) ) {
617#if defined(QT_QPROCESS_DEBUG)
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 );
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
628 removeProcess( d, pipe, TRUE /* inMsgHandler */ );
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 }
635#if defined(QT_QPROCESS_DEBUG)
636 else {
637 qDebug( "QProcessMonitor::WM_U_PIPE_CLOSE: proc=%p (invalid)",
638 proc );
639 }
640#endif
641 break;
642 }
643 default:
644 break;
645 }
646
647 return FALSE;
648}
649
650static QProcessMonitor *processMonitor = NULL;
651
652void 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 */
662bool 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.
677 // 2. If pipe->key is KEY_NULL, it means GUI thread has already processed
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 ) {
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 );
721 }
722 else
723 closePipe( pipe );
724 }
725
726 pipe->buf.append( ba );
727 return true;
728}
729
730/***********************************************************************
731 *
732 * QProcess
733 *
734 **********************************************************************/
735
736void QProcess::init()
737{
738 d = new QProcessPrivate( this );
739 exitStat = 0;
740 exitNormal = FALSE;
741
742#if defined(QT_QPROCESS_DEBUG)
743 qDebug( "QProcess::init(): d=%p", d );
744#endif
745}
746
747void QProcess::reset()
748{
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
755 d->reset();
756 exitStat = 0;
757 exitNormal = FALSE;
758}
759
760QMembuf* QProcess::membufStdout()
761{
762 if( d->stdout.pipe != 0 )
763 d->readPipe( &d->stdout );
764 return &d->stdout.buf;
765}
766
767QMembuf* QProcess::membufStderr()
768{
769 if( d->stderr.pipe != 0 )
770 d->readPipe( &d->stderr );
771 return &d->stderr.buf;
772}
773
774QProcess::~QProcess()
775{
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
786 delete d;
787}
788
789bool QProcess::start( QStringList *env )
790{
791#if defined(QT_QPROCESS_DEBUG)
792 qDebug( "QProcess::start()" );
793#endif
794
795 reset();
796
797 if ( _arguments.isEmpty() )
798 return FALSE;
799
800#if defined(QT_QPROCESS_USE_DOSEXECPGM)
801
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;
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;
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 );
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 }
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
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
1090 // create STDIN/OUT/ERR redirection pipes
1091
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;
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
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
1118 if ( comms & Stdin ) {
1119 HFILE clientStdin = HF_NULL;
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,
1126 PIPE_SIZE_STDIN, 0, 0 );
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)
1145 if ( clientStdin != HF_NULL )
1146 DosClose ( clientStdin );
1147 // close all opened handles on error
1148 if ( rc != NO_ERROR ) {
1149#if defined(QT_QPROCESS_DEBUG)
1150 qDebug( "Failed to create a STDIN redirection (%s): SYS%04ld",
1151 pathBuf, rc );
1152#endif
1153 if ( tmpStdin != HF_NULL )
1154 DosClose ( tmpStdin );
1155 d->closeHandles();
1156 UninstallQtMsgHandler();
1157 return FALSE;
1158 }
1159 }
1160 if ( comms & Stdout ) {
1161 HFILE clientStdout = HF_NULL;
1162 do {
1163 // create a Stdout pipe
1164 sprintf( pathBuf, "\\pipe\\Qt\\%08lX\\QProcess\\%p\\Stdout",
1165 ppib->pib_ulpid, this );
1166 rc = DosCreateNPipe( pathBuf, &d->stdout.pipe,
1167 NP_ACCESS_INBOUND, NP_NOWAIT | NP_TYPE_BYTE | 1,
1168 0, PIPE_SIZE_STDOUT, 0 );
1169 if ( rc != NO_ERROR )
1170 break;
1171 DosConnectNPipe( d->stdout.pipe );
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)
1187 if ( clientStdout != HF_NULL )
1188 DosClose ( clientStdout );
1189 // close all opened handles on error
1190 if ( rc != NO_ERROR ) {
1191#if defined(QT_QPROCESS_DEBUG)
1192 qDebug( "Failed to create a STDOUT redirection (%s): SYS%04ld",
1193 pathBuf, rc );
1194#endif
1195 if ( tmpStdin != HF_NULL ) {
1196 DosDupHandle( tmpStdin, &realStdin );
1197 DosClose( tmpStdin );
1198 }
1199 if ( tmpStdout != HF_NULL )
1200 DosClose ( tmpStdout );
1201 d->closeHandles();
1202 UninstallQtMsgHandler();
1203 return FALSE;
1204 }
1205 }
1206 if ( (comms & Stderr) && !(comms & DupStderr) ) {
1207 HFILE clientStderr = HF_NULL;
1208 do {
1209 // create a Stderr pipe
1210 sprintf( pathBuf, "\\pipe\\Qt\\%08lX\\QProcess\\%p\\Stderr",
1211 ppib->pib_ulpid, this );
1212 rc = DosCreateNPipe( pathBuf, &d->stderr.pipe,
1213 NP_ACCESS_INBOUND, NP_NOWAIT | NP_TYPE_BYTE | 1,
1214 0, PIPE_SIZE_STDERR, 0 );
1215 if ( rc != NO_ERROR )
1216 break;
1217 DosConnectNPipe( d->stderr.pipe );
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)
1233 if ( clientStderr != HF_NULL )
1234 DosClose ( clientStderr );
1235 // close all opened handles on error
1236 if ( rc != NO_ERROR ) {
1237#if defined(QT_QPROCESS_DEBUG)
1238 qDebug( "Failed to create a STDERR redirection (%s): SYS%04ld",
1239 pathBuf, rc );
1240#endif
1241 if ( tmpStdin != HF_NULL ) {
1242 DosDupHandle( tmpStdin, &realStdin );
1243 DosClose( tmpStdin );
1244 }
1245 if ( tmpStdout != HF_NULL ) {
1246 DosDupHandle( tmpStdout, &realStdout );
1247 DosClose( tmpStdout );
1248 }
1249 if ( tmpStderr != HF_NULL )
1250 DosClose ( tmpStderr );
1251 d->closeHandles();
1252 UninstallQtMsgHandler();
1253 return FALSE;
1254 }
1255 }
1256 if ( comms & DupStderr ) {
1257 // leave d->stderr.pipe equal to HP_NULL
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)
1269 qDebug( "Failed to redirect STDERR to STDOUT: SYS%04ld",
1270 rc );
1271#endif
1272 if ( tmpStdin != HF_NULL ) {
1273 DosDupHandle( tmpStdin, &realStdin );
1274 DosClose( tmpStdin );
1275 }
1276 if ( tmpStdout != HF_NULL ) {
1277 DosDupHandle( tmpStdout, &realStdout );
1278 DosClose( tmpStdout );
1279 }
1280 if ( tmpStderr != HF_NULL )
1281 DosClose ( tmpStderr );
1282 d->closeHandles();
1283 UninstallQtMsgHandler();
1284 return FALSE;
1285 }
1286 }
1287
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
1307 // set the working directory
1308 QString curDir = QDir::currentDirPath();
1309 QDir::setCurrent( workingDir.path() );
1310#if defined(QT_QPROCESS_DEBUG)
1311 qDebug( "QProcess::start(): curDir='%s', workingDir='%s'",
1312 curDir.local8Bit().data(),
1313 QDir::currentDirPath().local8Bit().data() );
1314#endif
1315
1316#if defined(QT_QPROCESS_USE_DOSEXECPGM)
1317
1318 // DosExecPgm()
1319
1320 RESULTCODES resc = { 0 };
1321
1322 rc = DosExecPgm( pathBuf, sizeof(pathBuf), EXEC_ASYNCRESULT,
1323 buf.data(), envlist.data(), &resc, appNameFull.data() );
1324
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
1368 // restore the current directory
1369 QDir::setCurrent( curDir );
1370
1371 // cancel STDIN/OUT/ERR redirections
1372 if ( tmpStdin != HF_NULL ) {
1373 DosDupHandle( tmpStdin, &realStdin );
1374 DosClose( tmpStdin );
1375 }
1376 if ( tmpStdout != HF_NULL ) {
1377 DosDupHandle( tmpStdout, &realStdout );
1378 DosClose( tmpStdout );
1379 }
1380 if ( tmpStderr != HF_NULL ) {
1381 DosDupHandle( tmpStderr, &realStderr );
1382 DosClose( tmpStderr );
1383 }
1384
1385 UninstallQtMsgHandler();
1386
1387#if defined(QT_QPROCESS_USE_DOSEXECPGM)
1388
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
1398 processMonitor->removeProcess( d );
1399 d->closeHandles();
1400 return FALSE;
1401 }
1402
1403 // memporize PID of the started process
1404 d->pid = resc.codeTerminate;
1405
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
1429 // timer is not necessary for ioRedirection (we use the monitor thread)
1430 if ( /* ioRedirection || */ notifyOnExit ) {
1431 d->lookup->start( POLL_TIMER );
1432 }
1433
1434 return TRUE;
1435}
1436
1437void 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
1466void QProcess::kill() const
1467{
1468 if ( d->pid != PID_NULL )
1469 DosKillProcess( DKP_PROCESS, d->pid );
1470}
1471
1472bool QProcess::isRunning() const
1473{
1474 if ( d->pid == PID_NULL )
1475 return FALSE;
1476
1477#if defined(QT_QPROCESS_USE_DOSEXECPGM)
1478
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;
1486
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
1505 QProcess *that = (QProcess *) this;
1506
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
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();
1519#endif
1520
1521 // compute the exit values
1522 if ( !d->exitValuesCalculated ) {
1523#if defined(QT_QPROCESS_USE_DOSEXECPGM)
1524 that->exitNormal = resc.codeTerminate == TC_EXIT;
1525 that->exitStat = resc.codeResult;
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)
1530 d->exitValuesCalculated = TRUE;
1531 }
1532
1533 processMonitor->removeProcess( d );
1534
1535 d->pid = PID_NULL;
1536 d->closeHandles();
1537 return FALSE;
1538}
1539
1540bool QProcess::canReadLineStdout() const
1541{
1542 if( d->stdout.pipe == HP_NULL )
1543 return d->stdout.buf.size() != 0;
1544
1545 QProcess *that = (QProcess *) this;
1546 return that->membufStdout()->scanNewline( 0 );
1547}
1548
1549bool QProcess::canReadLineStderr() const
1550{
1551 if( d->stderr.pipe == HP_NULL )
1552 return d->stderr.buf.size() != 0;
1553
1554 QProcess *that = (QProcess *) this;
1555 return that->membufStderr()->scanNewline( 0 );
1556}
1557
1558void QProcess::writeToStdin( const QByteArray& buf )
1559{
1560 d->stdinBuf.enqueue( new QByteArray(buf) );
1561 socketWrite( 0 );
1562}
1563
1564void QProcess::closeStdin( )
1565{
1566 if ( d->pipeStdin != HP_NULL ) {
1567 DosDisConnectNPipe( d->pipeStdin );
1568 DosClose( d->pipeStdin );
1569 d->pipeStdin = HP_NULL;
1570 }
1571}
1572
1573/* dummy, since MOC doesn't understand preprocessor defines */
1574void QProcess::socketRead( int fd )
1575{
1576}
1577
1578void QProcess::socketWrite( int )
1579{
1580 if ( d->pipeStdin == HP_NULL )
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,
1587 QMIN( PIPE_SIZE_STDIN,
1588 d->stdinBuf.head()->size() - d->stdinBufRead ),
1589 &written );
1590 if ( rc != NO_ERROR ) {
1591#if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
1592 qSystemWarning( "Failed to write to the pipe!", rc );
1593#endif
1594 d->lookup->start( POLL_TIMER );
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
1608void QProcess::flushStdin()
1609{
1610 socketWrite( 0 );
1611}
1612
1613/*
1614 Use a timer for polling misc. stuff.
1615*/
1616void 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
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 );
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*/
1641void QProcess::setIoRedirection( bool value )
1642{
1643 ioRedirection = value;
1644 // timer is not necessary for ioRedirection (we use the monitor thread)
1645#if 0
1646 if ( !ioRedirection && !notifyOnExit )
1647 d->lookup->stop();
1648 if ( ioRedirection ) {
1649 if ( isRunning() )
1650 d->lookup->start( POLL_TIMER );
1651 }
1652#endif
1653}
1654
1655/*
1656 Used by connectNotify() and disconnectNotify() to change the value of
1657 notifyOnExit (and related behaviour)
1658*/
1659void QProcess::setNotifyOnExit( bool value )
1660{
1661 notifyOnExit = value;
1662 // timer is not necessary for ioRedirection (we use the monitor thread)
1663 if ( /* !ioRedirection && */ !notifyOnExit )
1664 d->lookup->stop();
1665 if ( notifyOnExit ) {
1666 if ( isRunning() )
1667 d->lookup->start( POLL_TIMER );
1668 }
1669}
1670
1671/*
1672 Used by connectNotify() and disconnectNotify() to change the value of
1673 wroteToStdinConnected (and related behaviour)
1674*/
1675void QProcess::setWroteStdinConnected( bool value )
1676{
1677 wroteToStdinConnected = value;
1678}
1679
1680QProcess::PID QProcess::processIdentifier()
1681{
1682 return d->pid;
1683}
1684
1685#endif // QT_NO_PROCESS
Note: See TracBrowser for help on using the repository browser.