]> git.lyx.org Git - lyx.git/blob - src/support/Systemcall.cpp
Fix samba related crashes
[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         int timeout_min = 30;
229         if (!d.waitWhile(SystemcallPrivate::Running, process_events, timeout_min * 60 * 1000)) {
230                 LYXERR0("Systemcall: '" << cmd << "' did not finish!");
231                 LYXERR0("error " << d.errorMessage());
232                 LYXERR0("status " << d.exitStatusMessage());
233                 return 20;
234         }
235
236         int const exit_code = d.exitCode();
237         if (exit_code) {
238                 LYXERR0("Systemcall: '" << cmd << "' finished with exit code " << exit_code);
239         }
240
241         return exit_code;
242 }
243
244
245 SystemcallPrivate::SystemcallPrivate(const std::string& of) :
246                                 process_(new QProcess), 
247                                 out_index_(0),
248                                 err_index_(0),
249                                 out_file_(of), 
250                                 process_events_(false)
251 {
252         if (!out_file_.empty()) {
253                 // Check whether we have to simply throw away the output.
254                 if (out_file_ != os::nulldev())
255                         process_->setStandardOutputFile(toqstr(out_file_));
256         }
257
258         connect(process_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut()));
259         connect(process_, SIGNAL(readyReadStandardError()), SLOT(stdErr()));
260         connect(process_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
261         connect(process_, SIGNAL(started()), this, SLOT(processStarted()));
262         connect(process_, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
263 }
264
265
266
267 void SystemcallPrivate::startProcess(const QString& cmd)
268 {
269         cmd_ = cmd;
270         if (process_) {
271                 state = SystemcallPrivate::Starting;
272                 process_->start(cmd_);
273         }
274 }
275
276
277 void SystemcallPrivate::processEvents()
278 {
279         if(process_events_) {
280                 QCoreApplication::processEvents(/*QEventLoop::ExcludeUserInputEvents*/);
281         }
282 }
283
284
285 void SystemcallPrivate::waitAndProcessEvents()
286 {
287         Sleep::millisec(100);
288         processEvents();
289 }
290
291
292 bool SystemcallPrivate::waitWhile(State waitwhile, bool process_events, int timeout)
293 {
294         if (!process_)
295                 return false;
296
297         process_events_ = process_events;
298
299         // Block GUI while waiting,
300         // relay on QProcess' wait functions
301         if (!process_events_) {
302                 if (waitwhile == Starting)
303                         return process_->waitForStarted(timeout);
304                 if (waitwhile == Running)
305                         return process_->waitForFinished(timeout);
306                 return false;
307         }
308
309         // process events while waiting, no timeout
310         if (timeout == -1) {
311                 while (state == waitwhile && state != Error) {
312                         waitAndProcessEvents();
313                 }
314                 return state != Error;
315         } 
316
317         // process events while waiting whith timeout
318         QTime timer;
319         timer.start();
320         while (state == waitwhile && state != Error && timer.elapsed() < timeout) {
321                 waitAndProcessEvents();
322         }
323         return (state != Error) && (timer.elapsed() < timeout);
324 }
325
326
327 SystemcallPrivate::~SystemcallPrivate()
328 {
329         if (out_index_) {
330                 out_data_[out_index_] = '\0';
331                 out_index_ = 0;
332                 cout << out_data_;
333         }
334         cout.flush();
335         if (err_index_) {
336                 err_data_[err_index_] = '\0';
337                 err_index_ = 0;
338                 cerr << err_data_;
339         }
340         cerr.flush();
341
342         killProcess();
343 }
344
345
346 void SystemcallPrivate::stdOut()
347 {
348         if (process_) {
349                 char c;
350                 process_->setReadChannel(QProcess::StandardOutput);
351                 while (process_->getChar(&c)) {
352                         out_data_[out_index_++] = c;
353                         if (c == '\n' || out_index_ + 1 == buffer_size_) {
354                                 out_data_[out_index_] = '\0';
355                                 out_index_ = 0;
356                                 ProgressInterface::instance()->appendMessage(QString::fromLocal8Bit(out_data_));
357                                 cout << out_data_;
358                         }
359                 }
360         }
361 }
362
363
364 void SystemcallPrivate::stdErr()
365 {
366         if (process_) {
367                 char c;
368                 process_->setReadChannel(QProcess::StandardError);
369                 while (process_->getChar(&c)) {
370                         err_data_[err_index_++] = c;
371                         if (c == '\n' || err_index_ + 1 == buffer_size_) {
372                                 err_data_[err_index_] = '\0';
373                                 err_index_ = 0;
374                                 ProgressInterface::instance()->appendError(QString::fromLocal8Bit(err_data_));
375                                 cerr << err_data_;
376                         }
377                 }
378         }
379 }
380
381
382 void SystemcallPrivate::processStarted()
383 {
384         if (state != Running) {
385                 state = Running;
386                 ProgressInterface::instance()->processStarted(cmd_);
387         }
388 }
389
390
391 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
392 {
393         if (state != Finished) {
394                 state = Finished;
395                 ProgressInterface::instance()->processFinished(cmd_);
396         }
397 }
398
399
400 void SystemcallPrivate::processError(QProcess::ProcessError)
401 {
402         state = Error;
403         ProgressInterface::instance()->appendError(errorMessage());
404 }
405
406
407 QString SystemcallPrivate::errorMessage() const 
408 {
409         if (!process_)
410                 return "No QProcess available";
411
412         QString message;
413         switch (process_->error()) {
414                 case QProcess::FailedToStart:
415                         message = "The process failed to start. Either the invoked program is missing, "
416                                       "or you may have insufficient permissions to invoke the program.";
417                         break;
418                 case QProcess::Crashed:
419                         message = "The process crashed some time after starting successfully.";
420                         break;
421                 case QProcess::Timedout:
422                         message = "The process timed out. It might be restarted automatically.";
423                         break;
424                 case QProcess::WriteError:
425                         message = "An error occurred when attempting to write to the process-> For example, "
426                                       "the process may not be running, or it may have closed its input channel.";
427                         break;
428                 case QProcess::ReadError:
429                         message = "An error occurred when attempting to read from the process-> For example, "
430                                       "the process may not be running.";
431                         break;
432                 case QProcess::UnknownError:
433                 default:
434                         message = "An unknown error occured.";
435                         break;
436         }
437         return message;
438 }
439
440
441 QString SystemcallPrivate::exitStatusMessage() const
442 {
443         if (!process_)
444                 return "No QProcess available";
445
446         QString message;
447         switch (process_->exitStatus()) {
448                 case QProcess::NormalExit:
449                         message = "The process exited normally.";
450                         break;
451                 case QProcess::CrashExit:
452                         message = "The process crashed.";
453                         break;
454                 default:
455                         message = "Unknown exit state.";
456                         break;
457         }
458         return message;
459 }
460
461
462 int SystemcallPrivate::exitCode()
463 {
464         if (!process_)
465                 return -1;
466
467         return process_->exitCode();
468 }
469
470
471 QProcess* SystemcallPrivate::releaseProcess()
472 {
473         QProcess* released = process_;
474         process_ = 0;
475         return released;
476 }
477
478
479 void SystemcallPrivate::killProcess()
480 {
481         killProcess(process_);
482 }
483
484
485 void SystemcallPrivate::killProcess(QProcess * p)
486 {
487         if (p) {
488                 p->disconnect();
489                 p->closeReadChannel(QProcess::StandardOutput);
490                 p->closeReadChannel(QProcess::StandardError);
491                 p->close();
492                 delete p;
493         }
494 }
495
496
497
498 #include "moc_SystemcallPrivate.cpp"
499 #endif
500
501 } // namespace support
502 } // namespace lyx