]> git.lyx.org Git - features.git/blob - src/support/Systemcall.cpp
Improve the command-line parser meant to be used for QProcess.
[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/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                 //static int count = 0; qDebug() << count++ << ": waitAndProcessEvents";
236                 QCoreApplication::processEvents(QEventLoop::AllEvents);
237         }
238 }
239
240
241 void SystemcallPrivate::waitAndProcessEvents()
242 {
243         Sleep::millisec(100);
244         processEvents();
245 }
246
247
248 bool SystemcallPrivate::waitWhile(State waitwhile, bool proc_events, int timeout)
249 {
250         if (!proc_)
251                 return false;
252
253         process_events = proc_events;
254
255         // Block GUI while waiting,
256         // relay on QProcess' wait functions
257         if (!process_events) {
258                 if (waitwhile == Starting)
259                         return proc_->waitForStarted(timeout);
260                 if (waitwhile == Running)
261                         return proc_->waitForFinished(timeout);
262                 return false;
263         }
264
265         // process events while waiting, no timeout
266         if (timeout == -1) {
267                 while (state == waitwhile && state != Error) {
268                         waitAndProcessEvents();
269                 }
270                 return state != Error;
271         } 
272
273         // process events while waiting whith timeout
274         QTime timer;
275         timer.start();
276         while (state == waitwhile && state != Error && timer.elapsed() < timeout) {
277                 waitAndProcessEvents();
278         }
279         return (state != Error) && (timer.elapsed() < timeout);
280 }
281
282
283 SystemcallPrivate::~SystemcallPrivate()
284 {
285         flush();
286
287         if (outindex_) {
288                 outdata_[outindex_] = '\0';
289                 outindex_ = 0;
290                 cout << outdata_;
291         }
292         cout.flush();
293         if (errindex_) {
294                 errdata_[errindex_] = '\0';
295                 errindex_ = 0;
296                 cerr << errdata_;
297         }
298         cerr.flush();
299
300         killProcess();
301 }
302
303
304 void SystemcallPrivate::flush()
305 {
306         if (proc_) {
307                 // If the output has been redirected, we write it all at once.
308                 // Even if we are not running in a terminal, the output could go
309                 // to some log file, for example ~/.xsession-errors on *nix.
310                 if (!os::is_terminal(os::STDOUT) && outfile.empty())
311                         cout << fromqstr(QString::fromLocal8Bit(
312                                                 proc_->readAllStandardOutput().data()));
313                 if (!os::is_terminal(os::STDERR))
314                         cerr << fromqstr(QString::fromLocal8Bit(
315                                                 proc_->readAllStandardError().data()));
316         }
317 }
318
319
320 void SystemcallPrivate::stdOut()
321 {
322         if (proc_ && showout_) {
323                 char c;
324                 proc_->setReadChannel(QProcess::StandardOutput);
325                 while (proc_->getChar(&c)) {
326                         outdata_[outindex_++] = c;
327                         if (c == '\n' || outindex_ + 1 == bufsize_) {
328                                 outdata_[outindex_] = '\0';
329                                 outindex_ = 0;
330                                 cout << outdata_;
331                         }
332                 }
333         }
334         processEvents();
335 }
336
337
338 void SystemcallPrivate::stdErr()
339 {
340         if (proc_ && showerr_) {
341                 char c;
342                 proc_->setReadChannel(QProcess::StandardError);
343                 while (proc_->getChar(&c)) {
344                         errdata_[errindex_++] = c;
345                         if (c == '\n' || errindex_ + 1 == bufsize_) {
346                                 errdata_[errindex_] = '\0';
347                                 errindex_ = 0;
348                                 cerr << errdata_;
349                         }
350                 }
351         }
352         processEvents();
353 }
354
355
356 void SystemcallPrivate::processStarted()
357 {
358         state = Running;
359         // why do we get two started signals?
360         //disconnect(proc_, SIGNAL(started()), this, SLOT(processStarted()));
361 }
362
363
364 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
365 {
366         state = Finished;
367 }
368
369
370 void SystemcallPrivate::processError(QProcess::ProcessError)
371 {
372         state = Error;
373 }
374
375
376 QString SystemcallPrivate::errorMessage() const 
377 {
378         if (!proc_)
379                 return "No QProcess available";
380
381         QString message;
382         switch (proc_->error()) {
383                 case QProcess::FailedToStart:
384                         message = "The process failed to start. Either the invoked program is missing, "
385                                       "or you may have insufficient permissions to invoke the program.";
386                         break;
387                 case QProcess::Crashed:
388                         message = "The process crashed some time after starting successfully.";
389                         break;
390                 case QProcess::Timedout:
391                         message = "The process timed out. It might be restarted automatically.";
392                         break;
393                 case QProcess::WriteError:
394                         message = "An error occurred when attempting to write to the process-> For example, "
395                                       "the process may not be running, or it may have closed its input channel.";
396                         break;
397                 case QProcess::ReadError:
398                         message = "An error occurred when attempting to read from the process-> For example, "
399                                       "the process may not be running.";
400                         break;
401                 case QProcess::UnknownError:
402                 default:
403                         message = "An unknown error occured.";
404                         break;
405         }
406         return message;
407 }
408
409
410 QString SystemcallPrivate::exitStatusMessage() const
411 {
412         if (!proc_)
413                 return "No QProcess available";
414
415         QString message;
416         switch (proc_->exitStatus()) {
417                 case QProcess::NormalExit:
418                         message = "The process exited normally.";
419                         break;
420                 case QProcess::CrashExit:
421                         message = "The process crashed.";
422                         break;
423                 default:
424                         message = "Unknown exit state.";
425                         break;
426         }
427         return message;
428 }
429
430
431 int SystemcallPrivate::exitCode()
432 {
433         if (!proc_)
434                 return -1;
435
436         return proc_->exitCode();
437 }
438
439
440 QProcess* SystemcallPrivate::releaseProcess()
441 {
442         QProcess* released = proc_;
443         proc_ = 0;
444         return released;
445 }
446
447
448 void SystemcallPrivate::killProcess()
449 {
450         killProcess(proc_);
451 }
452
453
454 void SystemcallPrivate::killProcess(QProcess * p)
455 {
456         if (p) {
457                 p->disconnect();
458                 p->closeReadChannel(QProcess::StandardOutput);
459                 p->closeReadChannel(QProcess::StandardError);
460                 p->close();
461                 delete p;
462         }
463 }
464
465
466
467 #include "moc_SystemcallPrivate.cpp"
468 #endif
469
470 } // namespace support
471 } // namespace lyx