]> git.lyx.org Git - lyx.git/blob - src/support/Systemcall.cpp
5fec0b8f039fa630d7b6e50dd298fb64d67b81a1
[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/lstrings.h"
18 #include "support/qstring_helpers.h"
19 #include "support/Systemcall.h"
20 #include "support/SystemcallPrivate.h"
21 #include "support/os.h"
22 #include "support/ProgressInterface.h"
23
24
25 #include <cstdlib>
26 #include <iostream>
27
28 #include <QProcess>
29 #include <QTime>
30 #include <QThread>
31 #include <QCoreApplication>
32 #include <QDebug>
33
34 #define USE_QPROCESS
35
36
37 struct Sleep : QThread
38 {
39         static void millisec(unsigned long ms) 
40         {
41                 QThread::usleep(ms * 1000);
42         }
43 };
44
45
46
47
48 using namespace std;
49
50 namespace lyx {
51 namespace support {
52
53
54 class ProgressDummy : public ProgressInterface 
55 {
56 public:
57         ProgressDummy() {}
58
59         void processStarted(QString const &) {}
60         void processFinished(QString const &) {}
61         void appendMessage(QString const &) {}
62         void appendError(QString const &) {}
63         void clearMessages() {}
64         void lyxerrFlush() {}
65
66         void lyxerrConnect() {}
67         void lyxerrDisconnect() {}
68
69         void warning(QString const &, QString const &) {}
70         void toggleWarning(QString const &, QString const &, QString const &) {}
71         void error(QString const &, QString const &) {}
72         void information(QString const &, QString const &) {}
73 };
74
75
76 static ProgressInterface* progress_instance = 0;
77
78 void ProgressInterface::setInstance(ProgressInterface* p)
79 {
80         progress_instance = p;
81 }
82
83
84 ProgressInterface* ProgressInterface::instance()
85 {
86         if (!progress_instance) {
87                 static ProgressDummy dummy;
88                 return &dummy;
89         }
90         return progress_instance;
91 }
92
93
94
95
96 // Reuse of instance
97 #ifndef USE_QPROCESS
98 int Systemcall::startscript(Starttype how, string const & what,
99                                                         bool /*process_events*/)
100 {
101         string command = what;
102
103         if (how == DontWait) {
104                 switch (os::shell()) {
105                 case os::UNIX:
106                         command += " &";
107                         break;
108                 case os::CMD_EXE:
109                         command = "start /min " + command;
110                         break;
111                 }
112         }
113
114         return ::system(command.c_str());
115 }
116
117 #else
118
119 namespace {
120
121 /*
122  * This is a parser that (mostly) mimics the behavior of a posix shell but
123  * its output is tailored for being processed by QProcess.
124  *
125  * The escape character is the backslash.
126  * A backslash that is not quoted preserves the literal value of the following
127  * character, with the exception of a double-quote '"'. If a double-quote
128  * follows a backslash, it will be replaced by three consecutive double-quotes
129  * (this is how the QProcess parser recognizes a '"' as a simple character
130  * instead of a quoting character). Thus, for example:
131  *     \\  ->  \
132  *     \a  ->  a
133  *     \"  ->  """
134  *
135  * Single-quotes.
136  * Characters enclosed in single-quotes ('') have their literal value preserved.
137  * A single-quote cannot occur within single-quotes. Indeed, a backslash cannot
138  * be used to escape a single-quote in a single-quoted string. In other words,
139  * anything enclosed in single-quotes is passed as is, but the single-quotes
140  * themselves are eliminated. Thus, for example:
141  *    '\'    ->  \
142  *    '\\'   ->  \\
143  *    '\a'   ->  \a
144  *    'a\"b' ->  a\"b
145  *
146  * Double-quotes.
147  * Characters enclosed in double-quotes ("") have their literal value preserved,
148  * with the exception of the backslash. The backslash retains its special
149  * meaning as an escape character only when followed by a double-quote.
150  * Contrarily to the behavior of a posix shell, the double-quotes themselves
151  * are *not* eliminated. Thus, for example:
152  *    "\\"   ->  "\\"
153  *    "\a"   ->  "\a"
154  *    "a\"b" ->  "a"""b"
155  */
156 string const parsecmd(string const & inputcmd, string & outfile)
157 {
158         bool in_single_quote = false;
159         bool in_double_quote = false;
160         bool escaped = false;
161         string cmd;
162
163         for (size_t i = 0; i < inputcmd.length(); ++i) {
164                 char c = inputcmd[i];
165                 if (c == '\'') {
166                         if (in_double_quote || escaped) {
167                                 if (in_double_quote && escaped)
168                                         cmd += '\\';
169                                 cmd += c;
170                         } else
171                                 in_single_quote = !in_single_quote;
172                         escaped = false;
173                         continue;
174                 }
175                 if (in_single_quote) {
176                         cmd += c;
177                         continue;
178                 }
179                 if (c == '"') {
180                         if (escaped) {
181                                 cmd += "\"\"\"";
182                                 escaped = false;
183                         } else {
184                                 cmd += c;
185                                 in_double_quote = !in_double_quote;
186                         }
187                 } else if (c == '\\' && !escaped) {
188                         escaped = !escaped;
189                 } else if (c == '>' && !(in_double_quote || escaped)) {
190                         outfile = trim(inputcmd.substr(i + 1), " \"");
191                         return trim(cmd);
192                 } else {
193                         if (escaped && in_double_quote)
194                                 cmd += '\\';
195                         cmd += c;
196                         escaped = false;
197                 }
198         }
199         outfile.erase();
200         return cmd;
201 }
202
203 } // namespace anon
204
205
206
207 int Systemcall::startscript(Starttype how, string const & what, bool process_events)
208 {
209         string outfile;
210         QString cmd = toqstr(parsecmd(what, outfile));
211
212         SystemcallPrivate d(outfile);
213
214
215         d.startProcess(cmd);
216         if (!d.waitWhile(SystemcallPrivate::Starting, process_events, -1)) {
217                 LYXERR0("Systemcall: '" << cmd << "' did not start!");
218                 LYXERR0("error " << d.errorMessage());
219                 return 10;
220         }
221
222         if (how == DontWait) {
223                 QProcess* released = d.releaseProcess();
224                 (void) released; // TODO who deletes it?
225                 return 0;
226         }
227
228         if (!d.waitWhile(SystemcallPrivate::Running, process_events, 180000)) {
229                 LYXERR0("Systemcall: '" << cmd << "' did not finished!");
230                 LYXERR0("error " << d.errorMessage());
231                 LYXERR0("status " << d.exitStatusMessage());
232                 return 20;
233         }
234
235         int const exit_code = d.exitCode();
236         if (exit_code) {
237                 LYXERR0("Systemcall: '" << cmd << "' finished with exit code " << exit_code);
238         }
239
240         return exit_code;
241 }
242
243
244 SystemcallPrivate::SystemcallPrivate(const std::string& of) :
245                                 process_(new QProcess), 
246                                 out_index_(0),
247                                 err_index_(0),
248                                 out_file_(of), 
249                                 use_stdout_(false),
250                                 use_stderr_(false),
251                                 process_events_(false)
252 {
253         if (!out_file_.empty()) {
254                 // Don't output to terminal if stdout is redirected
255                 use_stdout_ = false;
256                 // Check whether we have to simply throw away the output.
257                 if (out_file_ != os::nulldev())
258                         process_->setStandardOutputFile(toqstr(out_file_));
259         } else {
260                 // Output to terminal if stdout exists and is not redirected
261                 use_stdout_ = os::is_terminal(os::STDOUT);
262         }
263
264         // When there is a stderr use it
265         use_stderr_ = os::is_terminal(os::STDERR);
266
267         connect(process_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut()));
268         connect(process_, SIGNAL(readyReadStandardError()), SLOT(stdErr()));
269         connect(process_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
270         connect(process_, SIGNAL(started()), this, SLOT(processStarted()));
271         connect(process_, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
272 }
273
274
275
276 void SystemcallPrivate::startProcess(const QString& cmd)
277 {
278         cmd_ = cmd;
279         if (process_) {
280                 state = SystemcallPrivate::Starting;
281                 process_->start(cmd_);
282         }
283 }
284
285
286 void SystemcallPrivate::processEvents()
287 {
288         if(process_events_) {
289                 QCoreApplication::processEvents(/*QEventLoop::ExcludeUserInputEvents*/);
290         }
291 }
292
293
294 void SystemcallPrivate::waitAndProcessEvents()
295 {
296         Sleep::millisec(100);
297         processEvents();
298 }
299
300
301 bool SystemcallPrivate::waitWhile(State waitwhile, bool process_events, int timeout)
302 {
303         if (!process_)
304                 return false;
305
306         process_events_ = process_events;
307
308         // Block GUI while waiting,
309         // relay on QProcess' wait functions
310         if (!process_events_) {
311                 if (waitwhile == Starting)
312                         return process_->waitForStarted(timeout);
313                 if (waitwhile == Running)
314                         return process_->waitForFinished(timeout);
315                 return false;
316         }
317
318         // process events while waiting, no timeout
319         if (timeout == -1) {
320                 while (state == waitwhile && state != Error) {
321                         waitAndProcessEvents();
322                 }
323                 return state != Error;
324         } 
325
326         // process events while waiting whith timeout
327         QTime timer;
328         timer.start();
329         while (state == waitwhile && state != Error && timer.elapsed() < timeout) {
330                 waitAndProcessEvents();
331         }
332         return (state != Error) && (timer.elapsed() < timeout);
333 }
334
335
336 SystemcallPrivate::~SystemcallPrivate()
337 {
338         flush();
339
340         if (out_index_) {
341                 out_data_[out_index_] = '\0';
342                 out_index_ = 0;
343                 if (use_stdout_)
344                         cout << out_data_;
345         }
346         cout.flush();
347         if (err_index_) {
348                 err_data_[err_index_] = '\0';
349                 err_index_ = 0;
350                 if (use_stderr_)
351                         cerr << err_data_;
352         }
353         cerr.flush();
354
355         killProcess();
356 }
357
358
359 void SystemcallPrivate::flush()
360 {
361         if (process_) {
362                 // If the output has been redirected, we write it all at once.
363                 // Even if we are not running in a terminal, the output could go
364                 // to some log file, for example ~/.xsession-errors on *nix.
365                 
366                 QString data = QString::fromLocal8Bit(process_->readAllStandardOutput().data());
367                 ProgressInterface::instance()->appendMessage(data);
368                 if (!use_stdout_ && out_file_.empty())
369                         cout << fromqstr(data);
370                 
371                 data = QString::fromLocal8Bit(process_->readAllStandardError().data());
372                 ProgressInterface::instance()->appendError(data);
373                 if (!use_stderr_)
374                         cerr << fromqstr(data);
375         }
376 }
377
378
379 void SystemcallPrivate::stdOut()
380 {
381         if (process_) {
382                 char c;
383                 process_->setReadChannel(QProcess::StandardOutput);
384                 while (process_->getChar(&c)) {
385                         out_data_[out_index_++] = c;
386                         if (c == '\n' || out_index_ + 1 == max_buffer_size_) {
387                                 out_data_[out_index_] = '\0';
388                                 out_index_ = 0;
389                                 ProgressInterface::instance()->appendMessage(QString::fromLocal8Bit(out_data_));
390                                 if (use_stdout_)
391                                         cout << out_data_;
392                         }
393                 }
394         }
395 }
396
397
398 void SystemcallPrivate::stdErr()
399 {
400         if (process_) {
401                 char c;
402                 process_->setReadChannel(QProcess::StandardError);
403                 while (process_->getChar(&c)) {
404                         err_data_[err_index_++] = c;
405                         if (c == '\n' || err_index_ + 1 == max_buffer_size_) {
406                                 err_data_[err_index_] = '\0';
407                                 err_index_ = 0;
408                                 ProgressInterface::instance()->appendError(QString::fromLocal8Bit(err_data_));
409                                 if (use_stderr_)
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