]> git.lyx.org Git - lyx.git/blob - src/support/Systemcall.cpp
741feb233843834c95a596e06e2f361edfea39f7
[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                                 texinputs_(getEnv("TEXINPUTS")),
272                                 process_events_(false)
273 {
274         if (!out_file_.empty()) {
275                 // Check whether we have to simply throw away the output.
276                 if (out_file_ != os::nulldev())
277                         process_->setStandardOutputFile(toqstr(out_file_));
278         }
279
280         connect(process_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut()));
281         connect(process_, SIGNAL(readyReadStandardError()), SLOT(stdErr()));
282         connect(process_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
283         connect(process_, SIGNAL(started()), this, SLOT(processStarted()));
284         connect(process_, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
285 }
286
287
288
289 void SystemcallPrivate::startProcess(QString const & cmd, string const & path)
290 {
291         cmd_ = cmd;
292         if (process_) {
293                 if (!path.empty() && !lyxrc.texinputs_prefix.empty()) {
294                         string const texinputs = os::latex_path_list(
295                                 replaceCurdirPath(path, lyxrc.texinputs_prefix));
296                         string const sep = string(1,
297                                         os::path_separator(os::TEXENGINE));
298                         string const prefix = "." + sep + texinputs + sep;
299                         if (prefixIs(texinputs_, prefix))
300                                 texinputs_.clear();
301                         else
302                                 setEnv("TEXINPUTS", prefix + texinputs_);
303                 }
304                 state = SystemcallPrivate::Starting;
305                 if (os::shell() == os::CMD_EXE)
306                         process_->start(QLatin1String("cmd /d /c ") + cmd_);
307                 else
308                         process_->start(cmd_);
309         }
310 }
311
312
313 void SystemcallPrivate::processEvents()
314 {
315         if(process_events_) {
316                 QCoreApplication::processEvents(/*QEventLoop::ExcludeUserInputEvents*/);
317         }
318 }
319
320
321 void SystemcallPrivate::waitAndProcessEvents()
322 {
323         Sleep::millisec(100);
324         processEvents();
325 }
326
327
328 bool SystemcallPrivate::waitWhile(State waitwhile, bool process_events, int timeout)
329 {
330         if (!process_)
331                 return false;
332
333         process_events_ = process_events;
334
335         // Block GUI while waiting,
336         // relay on QProcess' wait functions
337         if (!process_events_) {
338                 if (waitwhile == Starting)
339                         return process_->waitForStarted(timeout);
340                 if (waitwhile == Running)
341                         return process_->waitForFinished(timeout);
342                 return false;
343         }
344
345         // process events while waiting, no timeout
346         if (timeout == -1) {
347                 while (state == waitwhile && state != Error) {
348                         waitAndProcessEvents();
349                 }
350                 return state != Error;
351         } 
352
353         // process events while waiting whith timeout
354         QTime timer;
355         timer.start();
356         while (state == waitwhile && state != Error && timer.elapsed() < timeout) {
357                 waitAndProcessEvents();
358         }
359         return (state != Error) && (timer.elapsed() < timeout);
360 }
361
362
363 SystemcallPrivate::~SystemcallPrivate()
364 {
365         if (!texinputs_.empty())
366                 setEnv("TEXINPUTS", texinputs_);
367
368         if (out_index_) {
369                 out_data_[out_index_] = '\0';
370                 out_index_ = 0;
371                 cout << out_data_;
372         }
373         cout.flush();
374         if (err_index_) {
375                 err_data_[err_index_] = '\0';
376                 err_index_ = 0;
377                 cerr << err_data_;
378         }
379         cerr.flush();
380
381         killProcess();
382 }
383
384
385 void SystemcallPrivate::stdOut()
386 {
387         if (process_) {
388                 char c;
389                 process_->setReadChannel(QProcess::StandardOutput);
390                 while (process_->getChar(&c)) {
391                         out_data_[out_index_++] = c;
392                         if (c == '\n' || out_index_ + 1 == buffer_size_) {
393                                 out_data_[out_index_] = '\0';
394                                 out_index_ = 0;
395                                 ProgressInterface::instance()->appendMessage(QString::fromLocal8Bit(out_data_));
396                                 cout << out_data_;
397                         }
398                 }
399         }
400 }
401
402
403 void SystemcallPrivate::stdErr()
404 {
405         if (process_) {
406                 char c;
407                 process_->setReadChannel(QProcess::StandardError);
408                 while (process_->getChar(&c)) {
409                         err_data_[err_index_++] = c;
410                         if (c == '\n' || err_index_ + 1 == buffer_size_) {
411                                 err_data_[err_index_] = '\0';
412                                 err_index_ = 0;
413                                 ProgressInterface::instance()->appendError(QString::fromLocal8Bit(err_data_));
414                                 cerr << err_data_;
415                         }
416                 }
417         }
418 }
419
420
421 void SystemcallPrivate::processStarted()
422 {
423         if (state != Running) {
424                 state = Running;
425                 ProgressInterface::instance()->processStarted(cmd_);
426         }
427 }
428
429
430 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
431 {
432         if (state != Finished) {
433                 state = Finished;
434                 ProgressInterface::instance()->processFinished(cmd_);
435         }
436 }
437
438
439 void SystemcallPrivate::processError(QProcess::ProcessError)
440 {
441         state = Error;
442         ProgressInterface::instance()->appendError(errorMessage());
443 }
444
445
446 QString SystemcallPrivate::errorMessage() const 
447 {
448         if (!process_)
449                 return "No QProcess available";
450
451         QString message;
452         switch (process_->error()) {
453                 case QProcess::FailedToStart:
454                         message = "The process failed to start. Either the invoked program is missing, "
455                                       "or you may have insufficient permissions to invoke the program.";
456                         break;
457                 case QProcess::Crashed:
458                         message = "The process crashed some time after starting successfully.";
459                         break;
460                 case QProcess::Timedout:
461                         message = "The process timed out. It might be restarted automatically.";
462                         break;
463                 case QProcess::WriteError:
464                         message = "An error occurred when attempting to write to the process-> For example, "
465                                       "the process may not be running, or it may have closed its input channel.";
466                         break;
467                 case QProcess::ReadError:
468                         message = "An error occurred when attempting to read from the process-> For example, "
469                                       "the process may not be running.";
470                         break;
471                 case QProcess::UnknownError:
472                 default:
473                         message = "An unknown error occured.";
474                         break;
475         }
476         return message;
477 }
478
479
480 QString SystemcallPrivate::exitStatusMessage() const
481 {
482         if (!process_)
483                 return "No QProcess available";
484
485         QString message;
486         switch (process_->exitStatus()) {
487                 case QProcess::NormalExit:
488                         message = "The process exited normally.";
489                         break;
490                 case QProcess::CrashExit:
491                         message = "The process crashed.";
492                         break;
493                 default:
494                         message = "Unknown exit state.";
495                         break;
496         }
497         return message;
498 }
499
500
501 int SystemcallPrivate::exitCode()
502 {
503         if (!process_)
504                 return -1;
505
506         return process_->exitCode();
507 }
508
509
510 QProcess* SystemcallPrivate::releaseProcess()
511 {
512         QProcess* released = process_;
513         process_ = 0;
514         return released;
515 }
516
517
518 void SystemcallPrivate::killProcess()
519 {
520         killProcess(process_);
521 }
522
523
524 void SystemcallPrivate::killProcess(QProcess * p)
525 {
526         if (p) {
527                 p->disconnect();
528                 p->closeReadChannel(QProcess::StandardOutput);
529                 p->closeReadChannel(QProcess::StandardError);
530                 p->close();
531                 delete p;
532         }
533 }
534
535
536
537 #include "moc_SystemcallPrivate.cpp"
538 #endif
539
540 } // namespace support
541 } // namespace lyx