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