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