source: trunk/src/kernel/qprocess_pm.cpp@ 186

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

Kernel: QPrcess: Make sure the pipe key is not used after the assoiated pipe is closed (ticket:51).

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