]> git.lyx.org Git - features.git/blob - src/support/Systemcall.cpp
cleanup process output
[features.git] / src / support / Systemcall.cpp
1 /**
2  * \file Systemcall.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Angus Leeming
8  * \author Enrico Forestieri
9  * \author Peter Kuemmel
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "support/debug.h"
17 #include "support/lstrings.h"
18 #include "support/qstring_helpers.h"
19 #include "support/Systemcall.h"
20 #include "support/SystemcallPrivate.h"
21 #include "support/os.h"
22 #include "support/ProgressInterface.h"
23
24
25 #include <cstdlib>
26 #include <iostream>
27
28 #include <QProcess>
29 #include <QTime>
30 #include <QThread>
31 #include <QCoreApplication>
32 #include <QDebug>
33
34 #define USE_QPROCESS
35 //#define DEBUG_SYSTEMCALL // this macro shows the Qt 4.5 & 4.6 bug on Linux: multiple started() signals
36
37 struct Sleep : QThread
38 {
39         static void millisec(unsigned long ms) 
40         {
41                 QThread::usleep(ms * 1000);
42         }
43 };
44
45
46
47
48 using namespace std;
49
50 namespace lyx {
51 namespace support {
52
53
54 class ProgressDummy : public ProgressInterface 
55 {
56 public:
57         ProgressDummy() {}
58
59         void processStarted(QString const &) {}
60         void processFinished(QString const &) {}
61         void appendMessage(QString const &) {}
62         void appendError(QString const &) {}
63         void clearMessages() {}
64
65         void warning(QString const &, QString const &) {}
66         void toggleWarning(QString const &, QString const &, QString const &) {}
67         void error(QString const &, QString const &) {}
68         void information(QString const &, QString const &) {}
69 };
70
71
72 static ProgressInterface* progress_instance = 0;
73
74 void ProgressInterface::setInstance(ProgressInterface* p)
75 {
76         progress_instance = p;
77 }
78
79
80 ProgressInterface* ProgressInterface::instance()
81 {
82         if (!progress_instance) {
83                 static ProgressDummy dummy;
84                 return &dummy;
85         }
86         return progress_instance;
87 }
88
89
90
91
92 // Reuse of instance
93 #ifndef USE_QPROCESS
94 int Systemcall::startscript(Starttype how, string const & what,
95                                                         bool /*process_events*/)
96 {
97         string command = what;
98
99         if (how == DontWait) {
100                 switch (os::shell()) {
101                 case os::UNIX:
102                         command += " &";
103                         break;
104                 case os::CMD_EXE:
105                         command = "start /min " + command;
106                         break;
107                 }
108         }
109
110         return ::system(command.c_str());
111 }
112
113 #else
114
115 namespace {
116
117 /*
118  * This is a parser that (mostly) mimics the behavior of a posix shell but
119  * its output is tailored for being processed by QProcess.
120  *
121  * The escape character is the backslash.
122  * A backslash that is not quoted preserves the literal value of the following
123  * character, with the exception of a double-quote '"'. If a double-quote
124  * follows a backslash, it will be replaced by three consecutive double-quotes
125  * (this is how the QProcess parser recognizes a '"' as a simple character
126  * instead of a quoting character). Thus, for example:
127  *     \\  ->  \
128  *     \a  ->  a
129  *     \"  ->  """
130  *
131  * Single-quotes.
132  * Characters enclosed in single-quotes ('') have their literal value preserved.
133  * A single-quote cannot occur within single-quotes. Indeed, a backslash cannot
134  * be used to escape a single-quote in a single-quoted string. In other words,
135  * anything enclosed in single-quotes is passed as is, but the single-quotes
136  * themselves are eliminated. Thus, for example:
137  *    '\'    ->  \
138  *    '\\'   ->  \\
139  *    '\a'   ->  \a
140  *    'a\"b' ->  a\"b
141  *
142  * Double-quotes.
143  * Characters enclosed in double-quotes ("") have their literal value preserved,
144  * with the exception of the backslash. The backslash retains its special
145  * meaning as an escape character only when followed by a double-quote.
146  * Contrarily to the behavior of a posix shell, the double-quotes themselves
147  * are *not* eliminated. Thus, for example:
148  *    "\\"   ->  "\\"
149  *    "\a"   ->  "\a"
150  *    "a\"b" ->  "a"""b"
151  */
152 string const parsecmd(string const & inputcmd, string & outfile)
153 {
154         bool in_single_quote = false;
155         bool in_double_quote = false;
156         bool escaped = false;
157         string cmd;
158
159         for (size_t i = 0; i < inputcmd.length(); ++i) {
160                 char c = inputcmd[i];
161                 if (c == '\'') {
162                         if (in_double_quote || escaped) {
163                                 if (in_double_quote && escaped)
164                                         cmd += '\\';
165                                 cmd += c;
166                         } else
167                                 in_single_quote = !in_single_quote;
168                         escaped = false;
169                         continue;
170                 }
171                 if (in_single_quote) {
172                         cmd += c;
173                         continue;
174                 }
175                 if (c == '"') {
176                         if (escaped) {
177                                 cmd += "\"\"\"";
178                                 escaped = false;
179                         } else {
180                                 cmd += c;
181                                 in_double_quote = !in_double_quote;
182                         }
183                 } else if (c == '\\' && !escaped) {
184                         escaped = !escaped;
185                 } else if (c == '>' && !(in_double_quote || escaped)) {
186                         outfile = trim(inputcmd.substr(i + 1), " \"");
187                         return trim(cmd);
188                 } else {
189                         if (escaped && in_double_quote)
190                                 cmd += '\\';
191                         cmd += c;
192                         escaped = false;
193                 }
194         }
195         outfile.erase();
196         return cmd;
197 }
198
199 } // namespace anon
200
201
202
203 int Systemcall::startscript(Starttype how, string const & what, bool process_events)
204 {
205         string outfile;
206         QString cmd = toqstr(parsecmd(what, outfile));
207
208         SystemcallPrivate d(outfile);
209
210
211         d.startProcess(cmd);
212         if (!d.waitWhile(SystemcallPrivate::Starting, process_events, -1)) {
213                 LYXERR0("Systemcall: '" << cmd << "' did not start!");
214                 LYXERR0("error " << d.errorMessage());
215                 return 10;
216         }
217
218         if (how == DontWait) {
219                 QProcess* released = d.releaseProcess();
220                 (void) released; // TODO who deletes it?
221                 return 0;
222         }
223
224         if (!d.waitWhile(SystemcallPrivate::Running, process_events, 180000)) {
225                 LYXERR0("Systemcall: '" << cmd << "' did not finished!");
226                 LYXERR0("error " << d.errorMessage());
227                 LYXERR0("status " << d.exitStatusMessage());
228                 return 20;
229         }
230
231         int const exit_code = d.exitCode();
232         if (exit_code) {
233                 LYXERR0("Systemcall: '" << cmd << "' finished with exit code " << exit_code);
234         }
235
236         return exit_code;
237 }
238
239
240 SystemcallPrivate::SystemcallPrivate(const std::string& of) : 
241                                 proc_(new QProcess), outindex_(0), errindex_(0),
242                                 outfile(of), showout_(false), showerr_(false), process_events(false)
243 {
244         if (!outfile.empty()) {
245                 // Check whether we have to simply throw away the output.
246                 if (outfile != os::nulldev())
247                         proc_->setStandardOutputFile(toqstr(outfile));
248         } else if (os::is_terminal(os::STDOUT))
249                 setShowOut(true);
250         if (os::is_terminal(os::STDERR))
251                 setShowErr(true);
252
253         connect(proc_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut()));
254         connect(proc_, SIGNAL(readyReadStandardError()), SLOT(stdErr()));
255         connect(proc_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
256         connect(proc_, SIGNAL(started()), this, SLOT(processStarted()));
257         connect(proc_, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
258 }
259
260
261
262 void SystemcallPrivate::startProcess(const QString& cmd)
263 {
264         cmd_ = cmd;
265         if (proc_) {
266                 state = SystemcallPrivate::Starting;
267                 proc_->start(cmd_);
268         }
269 }
270
271
272 void SystemcallPrivate::processEvents()
273 {
274         if(process_events) {
275                 QCoreApplication::processEvents(/*QEventLoop::ExcludeUserInputEvents*/);
276         }
277 }
278
279
280 void SystemcallPrivate::waitAndProcessEvents()
281 {
282         Sleep::millisec(100);
283         processEvents();
284 }
285
286
287 bool SystemcallPrivate::waitWhile(State waitwhile, bool proc_events, int timeout)
288 {
289         if (!proc_)
290                 return false;
291
292         process_events = proc_events;
293
294         // Block GUI while waiting,
295         // relay on QProcess' wait functions
296         if (!process_events) {
297                 if (waitwhile == Starting)
298                         return proc_->waitForStarted(timeout);
299                 if (waitwhile == Running)
300                         return proc_->waitForFinished(timeout);
301                 return false;
302         }
303
304         // process events while waiting, no timeout
305         if (timeout == -1) {
306                 while (state == waitwhile && state != Error) {
307                         waitAndProcessEvents();
308                 }
309                 return state != Error;
310         } 
311
312         // process events while waiting whith timeout
313         QTime timer;
314         timer.start();
315         while (state == waitwhile && state != Error && timer.elapsed() < timeout) {
316                 waitAndProcessEvents();
317         }
318         return (state != Error) && (timer.elapsed() < timeout);
319 }
320
321
322 SystemcallPrivate::~SystemcallPrivate()
323 {
324         flush();
325
326         if (outindex_) {
327                 outdata_[outindex_] = '\0';
328                 outindex_ = 0;
329                 if (showout_)
330                         cout << outdata_;
331         }
332         cout.flush();
333         if (errindex_) {
334                 errdata_[errindex_] = '\0';
335                 errindex_ = 0;
336                 if (showerr_)
337                         cerr << errdata_;
338         }
339         cerr.flush();
340
341         killProcess();
342 }
343
344
345 void SystemcallPrivate::flush()
346 {
347         if (proc_) {
348                 // If the output has been redirected, we write it all at once.
349                 // Even if we are not running in a terminal, the output could go
350                 // to some log file, for example ~/.xsession-errors on *nix.
351                 
352                 QString data = QString::fromLocal8Bit(proc_->readAllStandardOutput().data());
353                 if (showout_) 
354                         ProgressInterface::instance()->appendMessage(data);
355                 if (!os::is_terminal(os::STDOUT) && outfile.empty()) 
356                         cout << fromqstr(data);
357                 
358                 data = QString::fromLocal8Bit(proc_->readAllStandardError().data());
359                 if (showerr_) 
360                         ProgressInterface::instance()->appendError(data);
361                 if (!os::is_terminal(os::STDERR)) 
362                         cerr << fromqstr(data);                 
363                 
364         }
365 }
366
367
368 void SystemcallPrivate::stdOut()
369 {
370         if (proc_) {
371                 char c;
372                 proc_->setReadChannel(QProcess::StandardOutput);
373                 while (proc_->getChar(&c)) {
374                         outdata_[outindex_++] = c;
375                         if (c == '\n' || outindex_ + 1 == bufsize_) {
376                                 outdata_[outindex_] = '\0';
377                                 outindex_ = 0;
378                                 if (showout_) {
379                                         cout << outdata_;
380                                         ProgressInterface::instance()->appendMessage(QString::fromLocal8Bit(outdata_));         
381                                 }
382                         }
383                 }
384         }
385 }
386
387
388 void SystemcallPrivate::stdErr()
389 {
390         if (proc_) {
391                 char c;
392                 proc_->setReadChannel(QProcess::StandardError);
393                 while (proc_->getChar(&c)) {
394                         errdata_[errindex_++] = c;
395                         if (c == '\n' || errindex_ + 1 == bufsize_) {
396                                 errdata_[errindex_] = '\0';
397                                 errindex_ = 0;
398                                 if (showerr_) {
399                                         cerr << errdata_;
400                                         ProgressInterface::instance()->appendError(QString::fromLocal8Bit(errdata_));
401                                 }
402                         }
403                 }
404         }
405 }
406
407
408 void SystemcallPrivate::processStarted()
409 {
410 #ifdef DEBUG_SYSTEMCALL
411         cmd_ = cmd_ + " -- process: " + QString::number((quint64) proc_);
412         if (true) {
413 #else
414         if (state != Running) {
415 #endif
416                 state = Running;
417                 ProgressInterface::instance()->processStarted(cmd_);
418         }
419 }
420
421
422 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
423 {
424 #ifdef DEBUG_SYSTEMCALL
425         cmd_ = cmd_ + " -- process: " + QString::number((quint64) proc_);
426         if (true) {
427 #else
428         if (state != Finished) {
429 #endif
430                 state = Finished;
431                 ProgressInterface::instance()->processFinished(cmd_);
432         }
433 }
434
435
436 void SystemcallPrivate::processError(QProcess::ProcessError)
437 {
438         state = Error;
439         ProgressInterface::instance()->appendError(errorMessage());
440 }
441
442
443 QString SystemcallPrivate::errorMessage() const 
444 {
445         if (!proc_)
446                 return "No QProcess available";
447
448         QString message;
449         switch (proc_->error()) {
450                 case QProcess::FailedToStart:
451                         message = "The process failed to start. Either the invoked program is missing, "
452                                       "or you may have insufficient permissions to invoke the program.";
453                         break;
454                 case QProcess::Crashed:
455                         message = "The process crashed some time after starting successfully.";
456                         break;
457                 case QProcess::Timedout:
458                         message = "The process timed out. It might be restarted automatically.";
459                         break;
460                 case QProcess::WriteError:
461                         message = "An error occurred when attempting to write to the process-> For example, "
462                                       "the process may not be running, or it may have closed its input channel.";
463                         break;
464                 case QProcess::ReadError:
465                         message = "An error occurred when attempting to read from the process-> For example, "
466                                       "the process may not be running.";
467                         break;
468                 case QProcess::UnknownError:
469                 default:
470                         message = "An unknown error occured.";
471                         break;
472         }
473         return message;
474 }
475
476
477 QString SystemcallPrivate::exitStatusMessage() const
478 {
479         if (!proc_)
480                 return "No QProcess available";
481
482         QString message;
483         switch (proc_->exitStatus()) {
484                 case QProcess::NormalExit:
485                         message = "The process exited normally.";
486                         break;
487                 case QProcess::CrashExit:
488                         message = "The process crashed.";
489                         break;
490                 default:
491                         message = "Unknown exit state.";
492                         break;
493         }
494         return message;
495 }
496
497
498 int SystemcallPrivate::exitCode()
499 {
500         if (!proc_)
501                 return -1;
502
503         return proc_->exitCode();
504 }
505
506
507 QProcess* SystemcallPrivate::releaseProcess()
508 {
509         QProcess* released = proc_;
510         proc_ = 0;
511         return released;
512 }
513
514
515 void SystemcallPrivate::killProcess()
516 {
517         killProcess(proc_);
518 }
519
520
521 void SystemcallPrivate::killProcess(QProcess * p)
522 {
523         if (p) {
524                 p->disconnect();
525                 p->closeReadChannel(QProcess::StandardOutput);
526                 p->closeReadChannel(QProcess::StandardError);
527                 p->close();
528                 delete p;
529         }
530 }
531
532
533
534 #include "moc_SystemcallPrivate.cpp"
535 #endif
536
537 } // namespace support
538 } // namespace lyx