]> git.lyx.org Git - features.git/blob - src/support/Systemcall.cpp
Do not change, even if temporarily, the environment of the main process
[features.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 void SystemcallPrivate::startProcess(QString const & cmd, string const & path)
288 {
289         cmd_ = cmd;
290         if (process_) {
291                 string cmd_prefix;
292                 if (!path.empty() && !lyxrc.texinputs_prefix.empty()) {
293                         string const texinputs_prefix = os::latex_path_list(
294                                 replaceCurdirPath(path, lyxrc.texinputs_prefix));
295                         string const sep = string(1,
296                                         os::path_separator(os::TEXENGINE));
297                         string const env = getEnv("TEXINPUTS");
298                         string const texinputs = "." + sep + texinputs_prefix
299                                                      + sep + env;
300                         if (os::shell() == os::UNIX)
301                                 cmd_prefix = "env 'TEXINPUTS="
302                                                 + texinputs + "' ";
303                         else
304                                 cmd_prefix = "cmd /d /c set TEXINPUTS="
305                                                 + texinputs + " & ";
306                 }
307                 state = SystemcallPrivate::Starting;
308                 process_->start(toqstr(cmd_prefix) + 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 (out_index_) {
366                 out_data_[out_index_] = '\0';
367                 out_index_ = 0;
368                 cout << out_data_;
369         }
370         cout.flush();
371         if (err_index_) {
372                 err_data_[err_index_] = '\0';
373                 err_index_ = 0;
374                 cerr << err_data_;
375         }
376         cerr.flush();
377
378         killProcess();
379 }
380
381
382 void SystemcallPrivate::stdOut()
383 {
384         if (process_) {
385                 char c;
386                 process_->setReadChannel(QProcess::StandardOutput);
387                 while (process_->getChar(&c)) {
388                         out_data_[out_index_++] = c;
389                         if (c == '\n' || out_index_ + 1 == buffer_size_) {
390                                 out_data_[out_index_] = '\0';
391                                 out_index_ = 0;
392                                 ProgressInterface::instance()->appendMessage(QString::fromLocal8Bit(out_data_));
393                                 cout << out_data_;
394                         }
395                 }
396         }
397 }
398
399
400 void SystemcallPrivate::stdErr()
401 {
402         if (process_) {
403                 char c;
404                 process_->setReadChannel(QProcess::StandardError);
405                 while (process_->getChar(&c)) {
406                         err_data_[err_index_++] = c;
407                         if (c == '\n' || err_index_ + 1 == buffer_size_) {
408                                 err_data_[err_index_] = '\0';
409                                 err_index_ = 0;
410                                 ProgressInterface::instance()->appendError(QString::fromLocal8Bit(err_data_));
411                                 cerr << err_data_;
412                         }
413                 }
414         }
415 }
416
417
418 void SystemcallPrivate::processStarted()
419 {
420         if (state != Running) {
421                 state = Running;
422                 ProgressInterface::instance()->processStarted(cmd_);
423         }
424 }
425
426
427 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
428 {
429         if (state != Finished) {
430                 state = Finished;
431                 ProgressInterface::instance()->processFinished(cmd_);
432         }
433 }
434
435
436 void SystemcallPrivate::processError(QProcess::ProcessError)
437 {
438         state = Error;
439         ProgressInterface::instance()->appendError(errorMessage());
440 }
441
442
443 QString SystemcallPrivate::errorMessage() const 
444 {
445         if (!process_)
446                 return "No QProcess available";
447
448         QString message;
449         switch (process_->error()) {
450                 case QProcess::FailedToStart:
451                         message = "The process failed to start. Either the invoked program is missing, "
452                                       "or you may have insufficient permissions to invoke the program.";
453                         break;
454                 case QProcess::Crashed:
455                         message = "The process crashed some time after starting successfully.";
456                         break;
457                 case QProcess::Timedout:
458                         message = "The process timed out. It might be restarted automatically.";
459                         break;
460                 case QProcess::WriteError:
461                         message = "An error occurred when attempting to write to the process-> For example, "
462                                       "the process may not be running, or it may have closed its input channel.";
463                         break;
464                 case QProcess::ReadError:
465                         message = "An error occurred when attempting to read from the process-> For example, "
466                                       "the process may not be running.";
467                         break;
468                 case QProcess::UnknownError:
469                 default:
470                         message = "An unknown error occured.";
471                         break;
472         }
473         return message;
474 }
475
476
477 QString SystemcallPrivate::exitStatusMessage() const
478 {
479         if (!process_)
480                 return "No QProcess available";
481
482         QString message;
483         switch (process_->exitStatus()) {
484                 case QProcess::NormalExit:
485                         message = "The process exited normally.";
486                         break;
487                 case QProcess::CrashExit:
488                         message = "The process crashed.";
489                         break;
490                 default:
491                         message = "Unknown exit state.";
492                         break;
493         }
494         return message;
495 }
496
497
498 int SystemcallPrivate::exitCode()
499 {
500         if (!process_)
501                 return -1;
502
503         return process_->exitCode();
504 }
505
506
507 QProcess* SystemcallPrivate::releaseProcess()
508 {
509         QProcess* released = process_;
510         process_ = 0;
511         return released;
512 }
513
514
515 void SystemcallPrivate::killProcess()
516 {
517         killProcess(process_);
518 }
519
520
521 void SystemcallPrivate::killProcess(QProcess * p)
522 {
523         if (p) {
524                 p->disconnect();
525                 p->closeReadChannel(QProcess::StandardOutput);
526                 p->closeReadChannel(QProcess::StandardError);
527                 p->close();
528                 delete p;
529         }
530 }
531
532
533
534 #include "moc_SystemcallPrivate.cpp"
535 #endif
536
537 } // namespace support
538 } // namespace lyx