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

Last change on this file since 168 was 168, checked in by dmik, 19 years ago

Kernel/QProcess: Use spawnvpe() instead of DosExecPgm() in order to make it possible for a child process to correctly inherit LIBC file descriptors. Note though that inheritance doesn't always work correctly by now (at least for socket descriptors, see http://svn.netlabs.org/qtapps/ticket/6). QT_QPROCESS_USE_DOSEXECPGM can be defined to restore the old behavior.

  • Property svn:keywords set to Id
File size: 52.1 KB
Line 
1/****************************************************************************
2** $Id: qprocess_pm.cpp 168 2007-01-06 18:38:01Z 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 us 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
689 processMonitor->removeProcess( this, pipe );
690 }
691 closePipe( pipe );
692 }
693
694 pipe->buf.append( ba );
695 return true;
696}
697
698/***********************************************************************
699 *
700 * QProcess
701 *
702 **********************************************************************/
703
704void QProcess::init()
705{
706 d = new QProcessPrivate( this );
707 exitStat = 0;
708 exitNormal = FALSE;
709
710#if defined(QT_QPROCESS_DEBUG)
711 qDebug( "QProcess::init(): d=%p", d );
712#endif
713}
714
715void QProcess::reset()
716{
717 // remove monitoring if the monitor is there (note that it's done before
718 // resetting QProcessPrivate which has fields protected by the
719 // QProcessMonitor lock)
720 if ( processMonitor )
721 processMonitor->removeProcess( d );
722
723 d->reset();
724 exitStat = 0;
725 exitNormal = FALSE;
726}
727
728QMembuf* QProcess::membufStdout()
729{
730 if( d->stdout.pipe != 0 )
731 d->readPipe( &d->stdout );
732 return &d->stdout.buf;
733}
734
735QMembuf* QProcess::membufStderr()
736{
737 if( d->stderr.pipe != 0 )
738 d->readPipe( &d->stderr );
739 return &d->stderr.buf;
740}
741
742QProcess::~QProcess()
743{
744#if defined(QT_QPROCESS_DEBUG)
745 qDebug( "~QProcess::QProcess(): d=%p", d );
746#endif
747
748 // remove monitoring if the monitor is there (note that it's done before
749 // resetting QProcessPrivate which has fields protected by the
750 // QProcessMonitor lock)
751 if ( processMonitor )
752 processMonitor->removeProcess( d );
753
754 delete d;
755}
756
757bool QProcess::start( QStringList *env )
758{
759#if defined(QT_QPROCESS_DEBUG)
760 qDebug( "QProcess::start()" );
761#endif
762
763 reset();
764
765 if ( _arguments.isEmpty() )
766 return FALSE;
767
768#if defined(QT_QPROCESS_USE_DOSEXECPGM)
769
770 // construct the arguments for DosExecPgm()
771
772 QCString appName, appArgs;
773 {
774 QString args;
775 for ( QStringList::ConstIterator it = _arguments.begin();
776 it != _arguments.end(); ++ it
777 ) {
778 if ( it == _arguments.begin() ) {
779 appName = (*it).local8Bit();
780 } else {
781 QString s = *it;
782 // quote the argument when it contains spaces
783 if ( s.contains( ' ' ) )
784 s = '"' + s + '"';
785 if ( args.isNull() )
786 args = s;
787 else
788 args += ' ' + s;
789 }
790 }
791 appArgs = args.local8Bit();
792
793#if defined(QT_QPROCESS_DEBUG)
794 qDebug( "QProcess::start(): app [%s], args [%s]",
795 appName.data(), appArgs.data() );
796#endif
797 }
798
799 QByteArray envlist;
800 if ( env != 0 ) {
801 uint pos = 0;
802 // inherit PATH if missing (for convenience)
803 // (note that BEGINLIBPATH and ENDLIBPATH, if any, are automatically
804 // inherited, while LIBPATH is always a global setting)
805 {
806 const char *path = getenv( "PATH" );
807 if ( env->grep( QRegExp( "^PATH=", FALSE ) ).empty() && path ) {
808 uint sz = strlen( path ) + 5 /* PATH= */ + 1;
809 envlist.resize( envlist.size() + sz );
810 sprintf( envlist.data() + pos, "PATH=%s", path );
811 pos += sz;
812 }
813 }
814 // inherit COMSPEC if missing (to let the child start .cmd and .bat)
815 {
816 const char *comspec = getenv( "COMSPEC" );
817 if ( env->grep( QRegExp( "^COMSPEC=", FALSE ) ).empty() && comspec ) {
818 uint sz = strlen( comspec ) + 8 /* COMSPEC= */ + 1;
819 envlist.resize( envlist.size() + sz );
820 sprintf( envlist.data() + pos, "COMSPEC=%s", comspec );
821 pos += sz;
822 }
823 }
824 // add the user environment
825 for ( QStringList::ConstIterator it = env->begin();
826 it != env->end(); ++ it
827 ) {
828 QCString var = (*it).local8Bit();
829 uint sz = var.length() + 1;
830 envlist.resize( envlist.size() + sz );
831 memcpy( envlist.data() + pos, var.data(), sz );
832 pos += sz;
833 }
834 // add the terminating 0
835 envlist.resize( envlist.size() + 1 );
836 envlist[ pos ++ ] = 0;
837 } else {
838 // copy the entire environment (to let all variables added using
839 // putenv() appear in the child process)
840 uint sz = 1;
841 for( const char * const *envp = environ; *envp != NULL; ++ envp )
842 sz += strlen( *envp ) + 1;
843 envlist.resize( sz );
844 uint pos = 0;
845 for( const char * const *envp = environ; *envp != NULL; ++ envp ) {
846 sz = strlen( *envp ) + 1;
847 memcpy( envlist.data() + pos, *envp, sz );
848 pos += sz;
849 }
850 // add the terminating 0
851 envlist[ pos ++ ] = 0;
852 }
853
854 // search for the app executable
855
856 const uint appNameLen = appName.length();
857 const uint appArgsLen = appArgs.length();
858
859 bool hasPath = appName[ strcspn( appName.data(), "/\\" ) ] != '\0';
860 bool hasSpaces = strchr( appName.data(), ' ' ) != NULL;
861
862 // list of executable file extensions, in order of CMD.EXE's precedence
863 // (the first runs directly, the rest requires CMD.EXE)
864 const char *exeExts[] = { ".exe", ".cmd", ".bat" };
865
866 // detect which exe extension do we have, if any
867 int ext = -1;
868 if ( appNameLen >= 4 ) {
869 for ( uint i = 0; ext < 0 && i < sizeof(exeExts) / sizeof(exeExts[0]); ++ i )
870 if ( stricmp( appName.data() + appNameLen - 4, exeExts[i] ) == 0 )
871 ext = i;
872 }
873
874 QByteArray buf;
875 QCString appNameFull;
876
877 APIRET rc = 0;
878 char pathBuf[ CCHMAXPATH ];
879
880 // run through all possible exe extension combinations (+ no extension case)
881 for ( uint i = 0; i <= sizeof(exeExts) / sizeof(exeExts[ 0 ]); ++ i ) {
882 if ( i == 0 ) {
883 // try the unmodified app name first if it contains a path spec
884 // or has one of predefined exe extensions
885 if ( hasPath || ext >= 0 ) {
886 if ( buf.size() < appNameLen + 1 )
887 buf.resize ( appNameLen + 1 );
888 strcpy( buf.data(), appName.data() );
889 } else
890 continue;
891 } else {
892 ext = i - 1;
893 const uint extLen = strlen( exeExts[ ext ] );
894 uint sz = appNameLen + extLen + 1;
895 if ( buf.size() < sz )
896 buf.resize( sz );
897 strcpy( buf.data(), appName.data() );
898 strcpy( buf.data() + appNameLen, exeExts[ ext ] );
899 }
900
901#if defined(QT_QPROCESS_DEBUG)
902 qDebug( "QProcess::start(): trying to find [%s]", buf.data() );
903#endif
904 if ( hasPath ) {
905 uint lastSep = strlen( buf.data() );
906 while ( lastSep-- && buf[ lastSep ] != '/' && buf[ lastSep ] != '\\' )
907 ;
908 buf[ lastSep ] = '\0';
909 rc = DosSearchPath( 0, buf.data(), buf.data() + lastSep + 1,
910 pathBuf, sizeof(pathBuf) );
911 } else {
912 rc = DosSearchPath( SEARCH_IGNORENETERRS | SEARCH_ENVIRONMENT |
913 SEARCH_CUR_DIRECTORY, "PATH",
914 buf.data(), pathBuf, sizeof(pathBuf) );
915 }
916
917 if ( rc == NO_ERROR ) {
918#if defined(QT_QPROCESS_DEBUG)
919 qDebug( "QProcess::start(): found [%s]", pathBuf );
920#endif
921 appNameFull = pathBuf;
922 break;
923 }
924
925 if ( rc != ERROR_FILE_NOT_FOUND && rc != ERROR_PATH_NOT_FOUND ) {
926#if defined(QT_QPROCESS_DEBUG)
927 qDebug( "QProcess::start(): found [%s], but got an error:", pathBuf );
928 qSystemWarning( "", rc );
929#endif
930 break;
931 }
932 }
933
934 if ( appNameFull.isNull() ) {
935#if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
936#if defined(QT_DEBUG)
937 qWarning( "[%s]:", appName.data() );
938#endif
939 qSystemWarning( "Failed to find the executable!", rc );
940#endif
941 return FALSE;
942 }
943
944 // final preparation of arguments
945
946 if ( ext <= 0 ) {
947 // run directly (args = <appName>\0\<appArgs>\0\0)
948 uint sz = appNameLen + 1 + appArgsLen + 2;
949 if ( buf.size() < sz )
950 buf.resize( sz );
951 strcpy( buf.data(), appName.data() );
952 strcpy( buf.data() + appNameLen + 1, appArgs.data() );
953 buf[ sz - 1 ] = '\0';
954 } else {
955 // run via shell (args = <shell>\0"/c "<appName>" "<appArgs>\0\0)
956 appNameFull = getenv( "COMSPEC" );
957 if ( appNameFull.isNull() ) {
958 ULONG bootDrv = 0;
959 rc = DosQuerySysInfo( QSV_BOOT_DRIVE, QSV_BOOT_DRIVE,
960 &bootDrv, sizeof(bootDrv) );
961 if ( rc == NO_ERROR && bootDrv >= 1 ) {
962 appNameFull = "?:\\OS2\\CMD.EXE";
963 appNameFull[ 0 ] = char( bootDrv ) + 'A' - 1;
964 }
965 }
966 if ( appNameFull.isNull() ) {
967#if defined(QT_QPROCESS_DEBUG)
968 qDebug( "QProcess::start(): OS/2 command line interpreter is not found!" );
969#endif
970 return FALSE;
971 }
972 const uint shellLen = strlen( appNameFull );
973 uint sz = shellLen + 1 + 3 + appNameLen + 1 + appArgsLen + 2;
974 if ( hasSpaces )
975 sz += 2; // for quotes
976 if ( !appArgsLen )
977 sz -= 1; // no need for space
978 if ( buf.size() < sz )
979 buf.resize( sz );
980 strcpy( buf.data(), appNameFull );
981 char *argsPtr = buf.data() + shellLen + 1;
982 sprintf( argsPtr, hasSpaces ? "/c \"%s\"" : "/c %s", appName.data() );
983 if ( appArgsLen ) {
984 strcat( argsPtr, " " );
985 strcat( argsPtr, appArgs.data() );
986 }
987 buf[ sz - 1 ] = '\0';
988 }
989
990#if defined(QT_QPROCESS_DEBUG)
991 qDebug( "QProcess::start(): will start [%s] as [%s] [%s]",
992 appNameFull.data(), buf.data(), buf.data() + strlen( buf.data() ) + 1 );
993#endif
994
995#else // QT_QPROCESS_USE_DOSEXECPGM
996
997 // construct the arguments for spawnvpe()
998
999 QCString appName;
1000 QStrList appArgs;
1001
1002 for ( QStringList::ConstIterator it = _arguments.begin();
1003 it != _arguments.end(); ++ it
1004 ) {
1005 if ( it == _arguments.begin() ) {
1006 appName = QFile::encodeName( *it );
1007 } else {
1008 appArgs.append( QFile::encodeName( *it ) );
1009 }
1010 }
1011
1012#if defined(QT_QPROCESS_DEBUG)
1013 qDebug( "QProcess::start(): [%s]",
1014 QFile::encodeName( _arguments.join( "] [" ) ).data() );
1015#endif
1016
1017 // prepare the environment
1018
1019 QStrList envList;
1020 if ( env != 0 ) {
1021 // add the user environment
1022 bool seenPATH = FALSE;
1023 bool seenCOMSPEC = FALSE;
1024 for ( QStringList::ConstIterator it = env->begin();
1025 it != env->end(); ++ it
1026 ) {
1027 envList.append( QFile::encodeName( *it ) );
1028 if ( !seenPATH )
1029 seenPATH = !strncmp( envList.current(), "PATH=", 4 );
1030 if ( !seenCOMSPEC )
1031 seenCOMSPEC = !strncmp( envList.current(), "COMSPEC=", 8 );
1032 }
1033 if ( !seenPATH ) {
1034 // inherit PATH if missing (for convenience)
1035 // (note that BEGINLIBPATH and ENDLIBPATH, if any, are automatically
1036 // inherited, while LIBPATH is always a global setting)
1037 const char *path = getenv( "PATH" );
1038 QCString str( 5 /* PATH= */ + strlen( path ) + 1 /* \0 */ );
1039 strcpy( str.data(), "PATH=" );
1040 strcat( str.data() + 5, path );
1041 envList.append( str );
1042 }
1043 // inherit COMSPEC if missing (to let the child start .cmd and .bat)
1044 if ( !seenCOMSPEC ) {
1045 const char *comspec = getenv( "COMSPEC" );
1046 QCString str( 8 /* COMSPEC= */ + strlen( comspec ) + 1 /* \0 */ );
1047 strcpy( str.data(), "COMSPEC=" );
1048 strcat( str.data() + 5, comspec );
1049 envList.append( str );
1050 }
1051 }
1052
1053 APIRET rc = 0;
1054 char pathBuf[ CCHMAXPATH ];
1055
1056#endif // QT_QPROCESS_USE_DOSEXECPGM
1057
1058 // create STDIN/OUT/ERR redirection pipes
1059
1060 if ( comms & (Stdout | Stderr) ) {
1061 // lazily create the process monitor
1062 if ( !processMonitor ) {
1063 processMonitor = new QProcessMonitor();
1064 Q_ASSERT( processMonitor );
1065 if ( !processMonitor )
1066 return FALSE;
1067 if ( !processMonitor->isOk() ) {
1068 QProcessMonitor_cleanup();
1069 return FALSE;
1070 }
1071 qAddPostRoutine( QProcessMonitor_cleanup );
1072 }
1073 }
1074
1075 HFILE tmpStdin = HF_NULL, tmpStdout = HF_NULL, tmpStderr = HF_NULL;
1076 HFILE realStdin = HF_STDIN, realStdout = HF_STDOUT, realStderr = HF_STDERR;
1077
1078 // we need the process identifier to guarantee pipe name unicity
1079 PPIB ppib = NULL;
1080 DosGetInfoBlocks( NULL, &ppib );
1081
1082 // use the custom Qt message handler to print errors/wranings
1083 // to the console when the real STDERR handle is redirected
1084 InstallQtMsgHandler();
1085
1086 if ( comms & Stdin ) {
1087 HFILE clientStdin = HF_NULL;
1088 do {
1089 // create a Stdin pipe
1090 sprintf( pathBuf, "\\pipe\\Qt\\%08lX\\QProcess\\%p\\Stdin",
1091 ppib->pib_ulpid, this );
1092 rc = DosCreateNPipe( pathBuf, &d->pipeStdin,
1093 NP_ACCESS_OUTBOUND, NP_NOWAIT | NP_TYPE_BYTE | 1,
1094 PIPE_SIZE_STDIN, 0, 0 );
1095 if ( rc != NO_ERROR )
1096 break;
1097 DosConnectNPipe( d->pipeStdin );
1098 // open a client end of the Stdout pipe
1099 ULONG action = 0;
1100 rc = DosOpen( pathBuf, &clientStdin, &action, 0, FILE_NORMAL, FILE_OPEN,
1101 OPEN_ACCESS_READONLY | OPEN_SHARE_DENYREADWRITE | OPEN_FLAGS_NOINHERIT,
1102 (PEAOP2) NULL);
1103 if ( rc != NO_ERROR )
1104 break;
1105 // save the real STDIN handle
1106 if ( (rc = DosDupHandle( HF_STDIN, &tmpStdin)) != NO_ERROR )
1107 break;
1108 // redirect the real STDIN handle to the client end of the pipe
1109 if ( (rc = DosDupHandle( clientStdin, &realStdin)) != NO_ERROR )
1110 break;
1111 } while( 0 );
1112 // close the client end anyway (no more necessary)
1113 if ( clientStdin != HF_NULL )
1114 DosClose ( clientStdin );
1115 // close all opened handles on error
1116 if ( rc != NO_ERROR ) {
1117#if defined(QT_QPROCESS_DEBUG)
1118 qDebug( "Failed to create a STDIN redirection (%s): SYS%04ld",
1119 pathBuf, rc );
1120#endif
1121 if ( tmpStdin != HF_NULL )
1122 DosClose ( tmpStdin );
1123 d->closeHandles();
1124 UninstallQtMsgHandler();
1125 return FALSE;
1126 }
1127 }
1128 if ( comms & Stdout ) {
1129 HFILE clientStdout = HF_NULL;
1130 do {
1131 // create a Stdout pipe
1132 sprintf( pathBuf, "\\pipe\\Qt\\%08lX\\QProcess\\%p\\Stdout",
1133 ppib->pib_ulpid, this );
1134 rc = DosCreateNPipe( pathBuf, &d->stdout.pipe,
1135 NP_ACCESS_INBOUND, NP_NOWAIT | NP_TYPE_BYTE | 1,
1136 0, PIPE_SIZE_STDOUT, 0 );
1137 if ( rc != NO_ERROR )
1138 break;
1139 DosConnectNPipe( d->stdout.pipe );
1140 // open a client end of the Stdout pipe
1141 ULONG action = 0;
1142 rc = DosOpen( pathBuf, &clientStdout, &action, 0, FILE_NORMAL, FILE_OPEN,
1143 OPEN_ACCESS_WRITEONLY | OPEN_SHARE_DENYREADWRITE | OPEN_FLAGS_NOINHERIT,
1144 (PEAOP2) NULL);
1145 if ( rc != NO_ERROR )
1146 break;
1147 // save the real STDOUT handle
1148 if ( (rc = DosDupHandle( HF_STDOUT, &tmpStdout )) != NO_ERROR )
1149 break;
1150 // redirect the real STDOUT handle to the client end of the pipe
1151 if ( (rc = DosDupHandle( clientStdout, &realStdout )) != NO_ERROR )
1152 break;
1153 } while( 0 );
1154 // close the client end anyway (no more necessary)
1155 if ( clientStdout != HF_NULL )
1156 DosClose ( clientStdout );
1157 // close all opened handles on error
1158 if ( rc != NO_ERROR ) {
1159#if defined(QT_QPROCESS_DEBUG)
1160 qDebug( "Failed to create a STDOUT redirection (%s): SYS%04ld",
1161 pathBuf, rc );
1162#endif
1163 if ( tmpStdin != HF_NULL ) {
1164 DosDupHandle( tmpStdin, &realStdin );
1165 DosClose( tmpStdin );
1166 }
1167 if ( tmpStdout != HF_NULL )
1168 DosClose ( tmpStdout );
1169 d->closeHandles();
1170 UninstallQtMsgHandler();
1171 return FALSE;
1172 }
1173 }
1174 if ( (comms & Stderr) && !(comms & DupStderr) ) {
1175 HFILE clientStderr = HF_NULL;
1176 do {
1177 // create a Stderr pipe
1178 sprintf( pathBuf, "\\pipe\\Qt\\%08lX\\QProcess\\%p\\Stderr",
1179 ppib->pib_ulpid, this );
1180 rc = DosCreateNPipe( pathBuf, &d->stderr.pipe,
1181 NP_ACCESS_INBOUND, NP_NOWAIT | NP_TYPE_BYTE | 1,
1182 0, PIPE_SIZE_STDERR, 0 );
1183 if ( rc != NO_ERROR )
1184 break;
1185 DosConnectNPipe( d->stderr.pipe );
1186 // open a client end of the Stderr pipe
1187 ULONG action = 0;
1188 rc = DosOpen( pathBuf, &clientStderr, &action, 0, FILE_NORMAL, FILE_OPEN,
1189 OPEN_ACCESS_WRITEONLY | OPEN_SHARE_DENYREADWRITE | OPEN_FLAGS_NOINHERIT,
1190 (PEAOP2) NULL);
1191 if ( rc != NO_ERROR )
1192 break;
1193 // save the real STDERR handle
1194 if ( (rc = DosDupHandle( HF_STDERR, &tmpStderr )) != NO_ERROR )
1195 break;
1196 // redirect the real STDERR handle to the client end of the pipe
1197 if ( (rc = DosDupHandle( clientStderr, &realStderr )) != NO_ERROR )
1198 break;
1199 } while( 0 );
1200 // close the client end anyway (no more necessary)
1201 if ( clientStderr != HF_NULL )
1202 DosClose ( clientStderr );
1203 // close all opened handles on error
1204 if ( rc != NO_ERROR ) {
1205#if defined(QT_QPROCESS_DEBUG)
1206 qDebug( "Failed to create a STDERR redirection (%s): SYS%04ld",
1207 pathBuf, rc );
1208#endif
1209 if ( tmpStdin != HF_NULL ) {
1210 DosDupHandle( tmpStdin, &realStdin );
1211 DosClose( tmpStdin );
1212 }
1213 if ( tmpStdout != HF_NULL ) {
1214 DosDupHandle( tmpStdout, &realStdout );
1215 DosClose( tmpStdout );
1216 }
1217 if ( tmpStderr != HF_NULL )
1218 DosClose ( tmpStderr );
1219 d->closeHandles();
1220 UninstallQtMsgHandler();
1221 return FALSE;
1222 }
1223 }
1224 if ( comms & DupStderr ) {
1225 // leave d->stderr.pipe equal to HP_NULL
1226 do {
1227 // save the real STDERR handle
1228 if ( (rc = DosDupHandle( HF_STDERR, &tmpStderr )) != NO_ERROR )
1229 break;
1230 // redirect STDERR to STDOUT
1231 if ( (rc = DosDupHandle( HF_STDOUT, &realStderr )) != NO_ERROR )
1232 break;
1233 } while( 0 );
1234 // close all opened handles on error
1235 if ( rc != NO_ERROR ) {
1236#if defined(QT_QPROCESS_DEBUG)
1237 qDebug( "Failed to redirect STDERR to STDOUT: SYS%04ld",
1238 rc );
1239#endif
1240 if ( tmpStdin != HF_NULL ) {
1241 DosDupHandle( tmpStdin, &realStdin );
1242 DosClose( tmpStdin );
1243 }
1244 if ( tmpStdout != HF_NULL ) {
1245 DosDupHandle( tmpStdout, &realStdout );
1246 DosClose( tmpStdout );
1247 }
1248 if ( tmpStderr != HF_NULL )
1249 DosClose ( tmpStderr );
1250 d->closeHandles();
1251 UninstallQtMsgHandler();
1252 return FALSE;
1253 }
1254 }
1255
1256 // add this process to the monitor
1257 if ( !processMonitor->addProcess( this ) ) {
1258 if ( tmpStdin != HF_NULL ) {
1259 DosDupHandle( tmpStdin, &realStdin );
1260 DosClose( tmpStdin );
1261 }
1262 if ( tmpStdout != HF_NULL ) {
1263 DosDupHandle( tmpStdout, &realStdout );
1264 DosClose( tmpStdout );
1265 }
1266 if ( tmpStderr != HF_NULL ) {
1267 DosDupHandle( tmpStderr, &realStderr );
1268 DosClose ( tmpStderr );
1269 }
1270 d->closeHandles();
1271 UninstallQtMsgHandler();
1272 return FALSE;
1273 }
1274
1275 // set the working directory
1276 QString curDir = QDir::currentDirPath();
1277 QDir::setCurrent( workingDir.path() );
1278#if defined(QT_QPROCESS_DEBUG)
1279 qDebug( "QProcess::start(): curDir='%s', workingDir='%s'",
1280 curDir.local8Bit().data(),
1281 QDir::currentDirPath().local8Bit().data() );
1282#endif
1283
1284#if defined(QT_QPROCESS_USE_DOSEXECPGM)
1285
1286 // DosExecPgm()
1287
1288 RESULTCODES resc = { 0 };
1289
1290 rc = DosExecPgm( pathBuf, sizeof(pathBuf), EXEC_ASYNCRESULT,
1291 buf.data(), envlist.data(), &resc, appNameFull.data() );
1292
1293#else // defined(QT_QPROCESS_USE_DOSEXECPGM)
1294
1295 // start the application
1296
1297 int i = 0;
1298
1299 char **argv = new char *[ appArgs.count() + 2 ];
1300 argv[ i++ ] = appName.data();
1301 for ( appArgs.first(); appArgs.current(); appArgs.next() )
1302 argv[ i++ ] = appArgs.current();
1303 argv[ i ] = NULL;
1304
1305 char **envv = NULL;
1306 if ( envList.count() ) {
1307 i = 0;
1308 envv = new char *[ envList.count() + 1 ];
1309 for ( envList.first(); envList.current(); envList.next() )
1310 envv[ i++ ] = envList.current();
1311 envv[ i ] = NULL;
1312 } else {
1313 envv = environ;
1314 }
1315
1316 int pid = spawnvpe( P_NOWAIT | P_DEFAULT, appName.data(), argv, envv );
1317
1318 // if spawnvpe() couldn't find the executable, try .CMD and .BAT
1319 // extensions (in order of CMD.EXE precedence); spawnvpe() knows how to
1320 // locate and run them (i.e. using CMD.EXE /c)
1321 if ( pid == -1 && errno == ENOENT ) {
1322 appName += ".cmd";
1323 pid = spawnvpe( P_NOWAIT | P_DEFAULT, appName.data(), argv, envv );
1324 if ( pid == -1 && errno == ENOENT ) {
1325 strcpy( appName.data() + appName.length() - 4, ".bat" );
1326 pid = spawnvpe( P_NOWAIT | P_DEFAULT, appName.data(), argv, envv );
1327 }
1328 }
1329
1330 if ( envv != environ )
1331 delete[] envv;
1332 delete[] argv;
1333
1334#endif // defined(QT_QPROCESS_USE_DOSEXECPGM)
1335
1336 // restore the current directory
1337 QDir::setCurrent( curDir );
1338
1339 // cancel STDIN/OUT/ERR redirections
1340 if ( tmpStdin != HF_NULL ) {
1341 DosDupHandle( tmpStdin, &realStdin );
1342 DosClose( tmpStdin );
1343 }
1344 if ( tmpStdout != HF_NULL ) {
1345 DosDupHandle( tmpStdout, &realStdout );
1346 DosClose( tmpStdout );
1347 }
1348 if ( tmpStderr != HF_NULL ) {
1349 DosDupHandle( tmpStderr, &realStderr );
1350 DosClose( tmpStderr );
1351 }
1352
1353 UninstallQtMsgHandler();
1354
1355#if defined(QT_QPROCESS_USE_DOSEXECPGM)
1356
1357 if ( rc != NO_ERROR ) {
1358#if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
1359#if defined(QT_DEBUG)
1360 qWarning( "[%s] [%s]\n(%s):",
1361 buf.data(), buf.data() + strlen( buf.data() ) + 1,
1362 pathBuf );
1363#endif
1364 qSystemWarning( "Failed to start a new process!", rc );
1365#endif
1366 processMonitor->removeProcess( d );
1367 d->closeHandles();
1368 return FALSE;
1369 }
1370
1371 // memporize PID of the started process
1372 d->pid = resc.codeTerminate;
1373
1374#else // defined(QT_QPROCESS_USE_DOSEXECPGM)
1375
1376 if ( pid == -1 ) {
1377#if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
1378 qWarning( "Failed to start a new process [%s]\n"
1379 "errno=%d (%s)",
1380 QFile::encodeName( _arguments.join( "] [" ) ).data(),
1381 errno, strerror( errno ) );
1382#endif
1383 processMonitor->removeProcess( d );
1384 d->closeHandles();
1385 return FALSE;
1386 }
1387
1388 // memporize PID of the started process
1389 d->pid = pid;
1390
1391#endif // defined(QT_QPROCESS_USE_DOSEXECPGM)
1392
1393#if defined(QT_QPROCESS_DEBUG)
1394 qDebug( "QProcess::start(): started PID %lu (0x%08lX)", d->pid, d->pid );
1395#endif
1396
1397 // timer is not necessary for ioRedirection (we use the monitor thread)
1398 if ( /* ioRedirection || */ notifyOnExit ) {
1399 d->lookup->start( POLL_TIMER );
1400 }
1401
1402 return TRUE;
1403}
1404
1405void QProcess::tryTerminate() const
1406{
1407 if ( d->pid == PID_NULL )
1408 return;
1409
1410 HSWITCH hswitch = WinQuerySwitchHandle( NULL, d->pid );
1411 if ( hswitch != NULLHANDLE ) {
1412 SWCNTRL swcntrl = { 0 };
1413 APIRET rc = WinQuerySwitchEntry( hswitch, &swcntrl );
1414 // WinQuerySwitchEntry will return a switch entry of the parent
1415 // process if the specfied one doesn't have a separate session
1416 // (running a plain CMD.EXE is an example); ignore this case.
1417 if ( rc == NO_ERROR && swcntrl.idProcess == d->pid )
1418 {
1419 // first, ensure that the Close action is enabled in the main frame
1420 // window (otherwise WM_SYSCOMMAND/SC_CLOSE will be ignored)
1421 HWND hwndSysMenu = WinWindowFromID( swcntrl.hwnd, FID_SYSMENU );
1422 if (hwndSysMenu) {
1423 WinPostMsg( hwndSysMenu, MM_SETITEMATTR,
1424 MPFROM2SHORT( SC_CLOSE, TRUE ),
1425 MPFROM2SHORT( MIA_DISABLED, 0 ) );
1426 }
1427 WinPostMsg( swcntrl.hwnd, WM_SYSCOMMAND,
1428 MPFROM2SHORT( SC_CLOSE, CMDSRC_OTHER ),
1429 MPFROMLONG( FALSE ) );
1430 }
1431 }
1432}
1433
1434void QProcess::kill() const
1435{
1436 if ( d->pid != PID_NULL )
1437 DosKillProcess( DKP_PROCESS, d->pid );
1438}
1439
1440bool QProcess::isRunning() const
1441{
1442 if ( d->pid == PID_NULL )
1443 return FALSE;
1444
1445#if defined(QT_QPROCESS_USE_DOSEXECPGM)
1446
1447 PID pidEnded = PID_NULL;
1448 RESULTCODES resc = { 0 };
1449 APIRET rc = DosWaitChild( DCWA_PROCESS, DCWW_NOWAIT, &resc,
1450 &pidEnded, d->pid );
1451
1452 if ( rc == ERROR_CHILD_NOT_COMPLETE )
1453 return TRUE;
1454
1455#else // defined(QT_QPROCESS_USE_DOSEXECPGM)
1456
1457 int stat = 0;
1458 int pid = waitpid( d->pid, &stat, WNOHANG );
1459 if ( (pid == 0) ||
1460 ((PID) pid == d->pid &&
1461 !WIFEXITED( stat ) && !WIFSIGNALED( stat ) && !WIFSTOPPED( stat ) ) )
1462 return TRUE;
1463
1464 if ( pid == -1 ) {
1465#if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
1466 qWarning( "Failed to wait for a child process\n"
1467 "errno=%d (%s)", errno, strerror( errno ) );
1468#endif
1469 }
1470
1471#endif // defined(QT_QPROCESS_USE_DOSEXECPGM)
1472
1473 QProcess *that = (QProcess *) this;
1474
1475 // There might be data to read, but WM_U_PIPE_RDATA messages won't be
1476 // converted to signals after removeProcess() is called below. Therefore,
1477 // emit signals ourselved if necessary.
1478
1479 if ( d->readPipe( &d->stdout ) )
1480 emit that->readyReadStdout();
1481 if ( d->readPipe( &d->stderr ) )
1482 emit that->readyReadStderr();
1483
1484 // compute the exit values
1485 if ( !d->exitValuesCalculated ) {
1486#if defined(QT_QPROCESS_USE_DOSEXECPGM)
1487 that->exitNormal = resc.codeTerminate == TC_EXIT;
1488 that->exitStat = resc.codeResult;
1489#else // defined(QT_QPROCESS_USE_DOSEXECPGM)
1490 that->exitNormal = pid != -1 && WIFEXITED( stat );
1491 that->exitStat = WEXITSTATUS( stat );
1492#endif // defined(QT_QPROCESS_USE_DOSEXECPGM)
1493 d->exitValuesCalculated = TRUE;
1494 }
1495
1496 processMonitor->removeProcess( d );
1497
1498 d->pid = PID_NULL;
1499 d->closeHandles();
1500 return FALSE;
1501}
1502
1503bool QProcess::canReadLineStdout() const
1504{
1505 if( d->stdout.pipe == HP_NULL )
1506 return d->stdout.buf.size() != 0;
1507
1508 QProcess *that = (QProcess *) this;
1509 return that->membufStdout()->scanNewline( 0 );
1510}
1511
1512bool QProcess::canReadLineStderr() const
1513{
1514 if( d->stderr.pipe == HP_NULL )
1515 return d->stderr.buf.size() != 0;
1516
1517 QProcess *that = (QProcess *) this;
1518 return that->membufStderr()->scanNewline( 0 );
1519}
1520
1521void QProcess::writeToStdin( const QByteArray& buf )
1522{
1523 d->stdinBuf.enqueue( new QByteArray(buf) );
1524 socketWrite( 0 );
1525}
1526
1527void QProcess::closeStdin( )
1528{
1529 if ( d->pipeStdin != HP_NULL ) {
1530 DosDisConnectNPipe( d->pipeStdin );
1531 DosClose( d->pipeStdin );
1532 d->pipeStdin = HP_NULL;
1533 }
1534}
1535
1536/* dummy, since MOC doesn't understand preprocessor defines */
1537void QProcess::socketRead( int fd )
1538{
1539}
1540
1541void QProcess::socketWrite( int )
1542{
1543 if ( d->pipeStdin == HP_NULL )
1544 return;
1545
1546 while ( !d->stdinBuf.isEmpty() && isRunning() ) {
1547 ULONG written = 0;
1548 APIRET rc = DosWrite( d->pipeStdin,
1549 d->stdinBuf.head()->data() + d->stdinBufRead,
1550 QMIN( PIPE_SIZE_STDIN,
1551 d->stdinBuf.head()->size() - d->stdinBufRead ),
1552 &written );
1553 if ( rc != NO_ERROR ) {
1554#if defined(QT_CHECK_STATE) || defined(QT_QPROCESS_DEBUG)
1555 qSystemWarning( "Failed to write to the pipe!", rc );
1556#endif
1557 d->lookup->start( POLL_TIMER );
1558 return;
1559 }
1560
1561 d->stdinBufRead += written;
1562 if ( d->stdinBufRead == d->stdinBuf.head()->size() ) {
1563 d->stdinBufRead = 0;
1564 delete d->stdinBuf.dequeue();
1565 if ( wroteToStdinConnected && d->stdinBuf.isEmpty() )
1566 emit wroteToStdin();
1567 }
1568 }
1569}
1570
1571void QProcess::flushStdin()
1572{
1573 socketWrite( 0 );
1574}
1575
1576/*
1577 Use a timer for polling misc. stuff.
1578*/
1579void QProcess::timeout()
1580{
1581 // Disable the timer temporary since one of the slots that are connected to
1582 // the readyRead...(), etc. signals might trigger recursion if
1583 // processEvents() is called.
1584 d->lookup->stop();
1585
1586 // try to write pending data to stdin
1587 if ( !d->stdinBuf.isEmpty() )
1588 socketWrite( 0 );
1589
1590 if ( isRunning() ) {
1591 // enable timer again, if needed
1592 // timer is not necessary for ioRedirection (we use the monitor thread)
1593 if ( !d->stdinBuf.isEmpty() || /* ioRedirection || */ notifyOnExit )
1594 d->lookup->start( POLL_TIMER );
1595 } else if ( notifyOnExit ) {
1596 emit processExited();
1597 }
1598}
1599
1600/*
1601 Used by connectNotify() and disconnectNotify() to change the value of
1602 ioRedirection (and related behaviour)
1603*/
1604void QProcess::setIoRedirection( bool value )
1605{
1606 ioRedirection = value;
1607 // timer is not necessary for ioRedirection (we use the monitor thread)
1608#if 0
1609 if ( !ioRedirection && !notifyOnExit )
1610 d->lookup->stop();
1611 if ( ioRedirection ) {
1612 if ( isRunning() )
1613 d->lookup->start( POLL_TIMER );
1614 }
1615#endif
1616}
1617
1618/*
1619 Used by connectNotify() and disconnectNotify() to change the value of
1620 notifyOnExit (and related behaviour)
1621*/
1622void QProcess::setNotifyOnExit( bool value )
1623{
1624 notifyOnExit = value;
1625 // timer is not necessary for ioRedirection (we use the monitor thread)
1626 if ( /* !ioRedirection && */ !notifyOnExit )
1627 d->lookup->stop();
1628 if ( notifyOnExit ) {
1629 if ( isRunning() )
1630 d->lookup->start( POLL_TIMER );
1631 }
1632}
1633
1634/*
1635 Used by connectNotify() and disconnectNotify() to change the value of
1636 wroteToStdinConnected (and related behaviour)
1637*/
1638void QProcess::setWroteStdinConnected( bool value )
1639{
1640 wroteToStdinConnected = value;
1641}
1642
1643QProcess::PID QProcess::processIdentifier()
1644{
1645 return d->pid;
1646}
1647
1648#endif // QT_NO_PROCESS
Note: See TracBrowser for help on using the repository browser.