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