]> git.lyx.org Git - lyx.git/blob - src/support/Systemcall.cpp
1a23f93c06078cd855d0cf525e0b8b823c044a0c
[lyx.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
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                 if (!os::is_terminal(os::STDOUT) && outfile.empty())
352                         cout << fromqstr(QString::fromLocal8Bit(
353                                                 proc_->readAllStandardOutput().data()));
354                 if (!os::is_terminal(os::STDERR))
355                         cerr << fromqstr(QString::fromLocal8Bit(
356                                                 proc_->readAllStandardError().data()));
357         }
358 }
359
360
361 void SystemcallPrivate::stdOut()
362 {
363         if (proc_) {
364                 char c;
365                 proc_->setReadChannel(QProcess::StandardOutput);
366                 while (proc_->getChar(&c)) {
367                         outdata_[outindex_++] = c;
368                         if (c == '\n' || outindex_ + 1 == bufsize_) {
369                                 outdata_[outindex_] = '\0';
370                                 outindex_ = 0;
371                                 if (showout_)
372                                         cout << outdata_;
373                         }
374                 }
375         }
376         const QString data = QString::fromLocal8Bit(outdata_);
377         if (!data.isEmpty()) {
378                 // TODO No good messages from the processes. Disable batch mode?
379                 //ProgressInterface::instance()->appendMessage(data);
380         }
381         processEvents();
382 }
383
384
385 void SystemcallPrivate::stdErr()
386 {
387         if (proc_) {
388                 char c;
389                 proc_->setReadChannel(QProcess::StandardError);
390                 while (proc_->getChar(&c)) {
391                         errdata_[errindex_++] = c;
392                         if (c == '\n' || errindex_ + 1 == bufsize_) {
393                                 errdata_[errindex_] = '\0';
394                                 errindex_ = 0;
395                                 if (showerr_)
396                                         cerr << errdata_;
397                         }
398                 }
399         }
400         const QString data = QString::fromLocal8Bit(errdata_);
401         if (!data.isEmpty())
402                 ProgressInterface::instance()->appendError(data);
403         processEvents();
404 }
405
406
407 void SystemcallPrivate::processStarted()
408 {
409         if (state != Running) {
410             state = Running;
411             ProgressInterface::instance()->processStarted(cmd_);
412         }
413 }
414
415
416 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
417 {
418         state = Finished;
419         ProgressInterface::instance()->processFinished(cmd_);
420 }
421
422
423 void SystemcallPrivate::processError(QProcess::ProcessError)
424 {
425         state = Error;
426         ProgressInterface::instance()->appendError(errorMessage());
427 }
428
429
430 QString SystemcallPrivate::errorMessage() const 
431 {
432         if (!proc_)
433                 return "No QProcess available";
434
435         QString message;
436         switch (proc_->error()) {
437                 case QProcess::FailedToStart:
438                         message = "The process failed to start. Either the invoked program is missing, "
439                                       "or you may have insufficient permissions to invoke the program.";
440                         break;
441                 case QProcess::Crashed:
442                         message = "The process crashed some time after starting successfully.";
443                         break;
444                 case QProcess::Timedout:
445                         message = "The process timed out. It might be restarted automatically.";
446                         break;
447                 case QProcess::WriteError:
448                         message = "An error occurred when attempting to write to the process-> For example, "
449                                       "the process may not be running, or it may have closed its input channel.";
450                         break;
451                 case QProcess::ReadError:
452                         message = "An error occurred when attempting to read from the process-> For example, "
453                                       "the process may not be running.";
454                         break;
455                 case QProcess::UnknownError:
456                 default:
457                         message = "An unknown error occured.";
458                         break;
459         }
460         return message;
461 }
462
463
464 QString SystemcallPrivate::exitStatusMessage() const
465 {
466         if (!proc_)
467                 return "No QProcess available";
468
469         QString message;
470         switch (proc_->exitStatus()) {
471                 case QProcess::NormalExit:
472                         message = "The process exited normally.";
473                         break;
474                 case QProcess::CrashExit:
475                         message = "The process crashed.";
476                         break;
477                 default:
478                         message = "Unknown exit state.";
479                         break;
480         }
481         return message;
482 }
483
484
485 int SystemcallPrivate::exitCode()
486 {
487         if (!proc_)
488                 return -1;
489
490         return proc_->exitCode();
491 }
492
493
494 QProcess* SystemcallPrivate::releaseProcess()
495 {
496         QProcess* released = proc_;
497         proc_ = 0;
498         return released;
499 }
500
501
502 void SystemcallPrivate::killProcess()
503 {
504         killProcess(proc_);
505 }
506
507
508 void SystemcallPrivate::killProcess(QProcess * p)
509 {
510         if (p) {
511                 p->disconnect();
512                 p->closeReadChannel(QProcess::StandardOutput);
513                 p->closeReadChannel(QProcess::StandardError);
514                 p->close();
515                 delete p;
516         }
517 }
518
519
520
521 #include "moc_SystemcallPrivate.cpp"
522 #endif
523
524 } // namespace support
525 } // namespace lyx