]> git.lyx.org Git - features.git/blob - src/support/Systemcall.cpp
Ticket 6266 Quoting Problem with QProcess: don't think we need any quotes (at least...
[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 string const parsecmd(string const & cmd, string & outfile)
79 {
80         bool inquote = false;
81         bool escaped = false;
82
83         for (size_t i = 0; i < cmd.length(); ++i) {
84                 char c = cmd[i];
85                 if ((c == '"' || c == '\'') && !escaped)
86                         inquote = !inquote;
87                 else if (c == '\\' && !escaped)
88                         escaped = !escaped;
89                 else if (c == '>' && !(inquote || escaped)) {
90                         outfile = trim(cmd.substr(i + 1), " \"");
91                         return trim(cmd.substr(0, i));
92                 } else
93                         escaped = false;
94         }
95         outfile.erase();
96         return cmd;
97 }
98
99 } // namespace anon
100
101
102
103 int Systemcall::startscript(Starttype how, string const & what, bool process_events)
104 {
105         string outfile;
106         QString cmd = toqstr(parsecmd(what, outfile));
107         if (cmd.contains("'")) {
108             LYXERR0("Systemcall: '" << cmd << "' contains single quotes ', please check configuration, ' will be replaced by \"");
109             cmd = cmd.replace("'","\"");
110         }
111
112         SystemcallPrivate d(outfile);
113
114
115         d.startProcess(cmd);
116         if (!d.waitWhile(SystemcallPrivate::Starting, process_events, -1)) {
117                 LYXERR0("Systemcall: '" << cmd << "' did not start!");
118                 LYXERR0("error " << d.errorMessage());
119                 return 10;
120         }
121
122         if (how == DontWait) {
123                 QProcess* released = d.releaseProcess();
124                 (void) released; // TODO who deletes it?
125                 return 0;
126         }
127
128         if (!d.waitWhile(SystemcallPrivate::Running, process_events, 180000)) {
129                 LYXERR0("Systemcall: '" << cmd << "' did not finished!");
130                 LYXERR0("error " << d.errorMessage());
131                 LYXERR0("status " << d.exitStatusMessage());
132                 return 20;
133         }
134
135         int const exit_code = d.exitCode();
136         if (exit_code) {
137                 LYXERR0("Systemcall: '" << cmd << "' finished with exit code " << exit_code);
138         }
139
140         return exit_code;
141 }
142
143
144 SystemcallPrivate::SystemcallPrivate(const std::string& of) : 
145                                 proc_(new QProcess), outindex_(0), errindex_(0),
146                                 outfile(of), showout_(false), showerr_(false), process_events(false)
147 {
148         if (!outfile.empty()) {
149                 // Check whether we have to simply throw away the output.
150                 if (outfile != os::nulldev())
151                         proc_->setStandardOutputFile(toqstr(outfile));
152         } else if (os::is_terminal(os::STDOUT))
153                 showout();
154         if (os::is_terminal(os::STDERR))
155                 showerr();
156
157         connect(proc_, SIGNAL(readyReadStandardOutput()), SLOT(stdOut()));
158         connect(proc_, SIGNAL(readyReadStandardError()), SLOT(stdErr()));
159         connect(proc_, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError)));
160         connect(proc_, SIGNAL(started()), this, SLOT(processStarted()));
161         connect(proc_, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
162 }
163
164
165
166 void SystemcallPrivate::startProcess(const QString& cmd)
167 {
168         if (proc_) {
169                 state = SystemcallPrivate::Starting;
170                 proc_->start(cmd);
171         }
172 }
173
174
175 void SystemcallPrivate::processEvents()
176 {
177         if(process_events) {
178                 //static int count = 0; qDebug() << count++ << ": waitAndProcessEvents";
179                 QCoreApplication::processEvents(QEventLoop::AllEvents);
180         }
181 }
182
183
184 void SystemcallPrivate::waitAndProcessEvents()
185 {
186         Sleep::millisec(100);
187         processEvents();
188 }
189
190
191 bool SystemcallPrivate::waitWhile(State waitwhile, bool proc_events, int timeout)
192 {
193         if (!proc_)
194                 return false;
195
196         process_events = proc_events;
197
198         // Block GUI while waiting,
199         // relay on QProcess' wait functions
200         if (!process_events) {
201                 if (waitwhile == Starting)
202                         return proc_->waitForStarted(timeout);
203                 if (waitwhile == Running)
204                         return proc_->waitForFinished(timeout);
205                 return false;
206         }
207
208         // process events while waiting, no timeout
209         if (timeout == -1) {
210                 while (state == waitwhile && state != Error) {
211                         waitAndProcessEvents();
212                 }
213                 return state != Error;
214         } 
215
216         // process events while waiting whith timeout
217         QTime timer;
218         timer.start();
219         while (state == waitwhile && state != Error && timer.elapsed() < timeout) {
220                 waitAndProcessEvents();
221         }
222         return (state != Error) && (timer.elapsed() < timeout);
223 }
224
225
226 SystemcallPrivate::~SystemcallPrivate()
227 {
228         flush();
229
230         if (outindex_) {
231                 outdata_[outindex_] = '\0';
232                 outindex_ = 0;
233                 cout << outdata_;
234         }
235         cout.flush();
236         if (errindex_) {
237                 errdata_[errindex_] = '\0';
238                 errindex_ = 0;
239                 cerr << errdata_;
240         }
241         cerr.flush();
242
243         killProcess();
244 }
245
246
247 void SystemcallPrivate::flush()
248 {
249         if (proc_) {
250                 // If the output has been redirected, we write it all at once.
251                 // Even if we are not running in a terminal, the output could go
252                 // to some log file, for example ~/.xsession-errors on *nix.
253                 if (!os::is_terminal(os::STDOUT) && outfile.empty())
254                         cout << fromqstr(QString::fromLocal8Bit(
255                                                 proc_->readAllStandardOutput().data()));
256                 if (!os::is_terminal(os::STDERR))
257                         cerr << fromqstr(QString::fromLocal8Bit(
258                                                 proc_->readAllStandardError().data()));
259         }
260 }
261
262
263 void SystemcallPrivate::stdOut()
264 {
265         if (proc_ && showout_) {
266                 char c;
267                 proc_->setReadChannel(QProcess::StandardOutput);
268                 while (proc_->getChar(&c)) {
269                         outdata_[outindex_++] = c;
270                         if (c == '\n' || outindex_ + 1 == bufsize_) {
271                                 outdata_[outindex_] = '\0';
272                                 outindex_ = 0;
273                                 cout << outdata_;
274                         }
275                 }
276         }
277         processEvents();
278 }
279
280
281 void SystemcallPrivate::stdErr()
282 {
283         if (proc_ && showerr_) {
284                 char c;
285                 proc_->setReadChannel(QProcess::StandardError);
286                 while (proc_->getChar(&c)) {
287                         errdata_[errindex_++] = c;
288                         if (c == '\n' || errindex_ + 1 == bufsize_) {
289                                 errdata_[errindex_] = '\0';
290                                 errindex_ = 0;
291                                 cerr << errdata_;
292                         }
293                 }
294         }
295         processEvents();
296 }
297
298
299 void SystemcallPrivate::processStarted()
300 {
301         state = Running;
302         // why do we get two started signals?
303         //disconnect(proc_, SIGNAL(started()), this, SLOT(processStarted()));
304 }
305
306
307 void SystemcallPrivate::processFinished(int, QProcess::ExitStatus)
308 {
309         state = Finished;
310 }
311
312
313 void SystemcallPrivate::processError(QProcess::ProcessError)
314 {
315         state = Error;
316 }
317
318
319 QString SystemcallPrivate::errorMessage() const 
320 {
321         if (!proc_)
322                 return "No QProcess available";
323
324         QString message;
325         switch (proc_->error()) {
326                 case QProcess::FailedToStart:
327                         message = "The process failed to start. Either the invoked program is missing, "
328                                       "or you may have insufficient permissions to invoke the program.";
329                         break;
330                 case QProcess::Crashed:
331                         message = "The process crashed some time after starting successfully.";
332                         break;
333                 case QProcess::Timedout:
334                         message = "The process timed out. It might be restarted automatically.";
335                         break;
336                 case QProcess::WriteError:
337                         message = "An error occurred when attempting to write to the process-> For example, "
338                                       "the process may not be running, or it may have closed its input channel.";
339                         break;
340                 case QProcess::ReadError:
341                         message = "An error occurred when attempting to read from the process-> For example, "
342                                       "the process may not be running.";
343                         break;
344                 case QProcess::UnknownError:
345                 default:
346                         message = "An unknown error occured.";
347                         break;
348         }
349         return message;
350 }
351
352
353 QString SystemcallPrivate::exitStatusMessage() const
354 {
355         if (!proc_)
356                 return "No QProcess available";
357
358         QString message;
359         switch (proc_->exitStatus()) {
360                 case QProcess::NormalExit:
361                         message = "The process exited normally.";
362                         break;
363                 case QProcess::CrashExit:
364                         message = "The process crashed.";
365                         break;
366                 default:
367                         message = "Unknown exit state.";
368                         break;
369         }
370         return message;
371 }
372
373
374 int SystemcallPrivate::exitCode()
375 {
376         if (!proc_)
377                 return -1;
378
379         return proc_->exitCode();
380 }
381
382
383 QProcess* SystemcallPrivate::releaseProcess()
384 {
385         QProcess* released = proc_;
386         proc_ = 0;
387         return released;
388 }
389
390
391 void SystemcallPrivate::killProcess()
392 {
393         killProcess(proc_);
394 }
395
396
397 void SystemcallPrivate::killProcess(QProcess * p)
398 {
399         if (p) {
400                 p->disconnect();
401                 p->closeReadChannel(QProcess::StandardOutput);
402                 p->closeReadChannel(QProcess::StandardError);
403                 p->close();
404                 delete p;
405         }
406 }
407
408
409
410 #include "moc_SystemcallPrivate.cpp"
411 #endif
412
413 } // namespace support
414 } // namespace lyx