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