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