2
* Copyright (C) 2003-2007 Justin Karneges <justin@affinix.com>
4
* This library is free software; you can redistribute it and/or
5
* modify it under the terms of the GNU Lesser General Public
6
* License as published by the Free Software Foundation; either
7
* version 2.1 of the License, or (at your option) any later version.
9
* This library is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
* Lesser General Public License for more details.
14
* You should have received a copy of the GNU Lesser General Public
15
* License along with this library; if not, write to the Free Software
16
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
28
#define QPROC_SIGNAL_RELAY
32
namespace gpgQCAPlugin {
34
class QProcessSignalRelay : public QObject
38
QProcessSignalRelay(QProcess *proc, QObject *parent = 0)
41
qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
42
connect(proc, SIGNAL(started()), SLOT(proc_started()), Qt::QueuedConnection);
43
connect(proc, SIGNAL(readyReadStandardOutput()), SLOT(proc_readyReadStandardOutput()), Qt::QueuedConnection);
44
connect(proc, SIGNAL(readyReadStandardError()), SLOT(proc_readyReadStandardError()), Qt::QueuedConnection);
45
connect(proc, SIGNAL(bytesWritten(qint64)), SLOT(proc_bytesWritten(qint64)), Qt::QueuedConnection);
46
connect(proc, SIGNAL(finished(int)), SLOT(proc_finished(int)), Qt::QueuedConnection);
47
connect(proc, SIGNAL(error(QProcess::ProcessError)), SLOT(proc_error(QProcess::ProcessError)), Qt::QueuedConnection);
52
void readyReadStandardOutput();
53
void readyReadStandardError();
54
void bytesWritten(qint64);
56
void error(QProcess::ProcessError);
64
void proc_readyReadStandardOutput()
66
emit readyReadStandardOutput();
69
void proc_readyReadStandardError()
71
emit readyReadStandardError();
74
void proc_bytesWritten(qint64 x)
79
void proc_finished(int x)
84
void proc_error(QProcess::ProcessError x)
90
//----------------------------------------------------------------------------
92
//----------------------------------------------------------------------------
96
ResetSessionAndData = 1,
100
class GPGProc::Private : public QObject
109
#ifdef QPROC_SIGNAL_RELAY
110
QProcessSignalRelay *proc_relay;
112
QPipe pipeAux, pipeCommand, pipeStatus;
113
QByteArray statusBuf;
114
QStringList statusLines;
115
GPGProc::Error error;
117
QTimer startTrigger, doneTrigger;
119
QByteArray pre_stdin, pre_aux;
121
SecureArray pre_command;
123
QByteArray pre_command;
125
bool pre_stdin_close, pre_aux_close, pre_command_close;
127
bool need_status, fin_process, fin_process_success, fin_status;
128
QByteArray leftover_stdout;
129
QByteArray leftover_stderr;
131
Private(GPGProc *_q) : QObject(_q), q(_q), pipeAux(this), pipeCommand(this), pipeStatus(this), startTrigger(this), doneTrigger(this)
133
qRegisterMetaType<gpgQCAPlugin::GPGProc::Error>("gpgQCAPlugin::GPGProc::Error");
136
#ifdef QPROC_SIGNAL_RELAY
139
startTrigger.setSingleShot(true);
140
doneTrigger.setSingleShot(true);
142
connect(&pipeAux.writeEnd(), SIGNAL(bytesWritten(int)), SLOT(aux_written(int)));
143
connect(&pipeAux.writeEnd(), SIGNAL(error(QCA::QPipeEnd::Error)), SLOT(aux_error(QCA::QPipeEnd::Error)));
144
connect(&pipeCommand.writeEnd(), SIGNAL(bytesWritten(int)), SLOT(command_written(int)));
145
connect(&pipeCommand.writeEnd(), SIGNAL(error(QCA::QPipeEnd::Error)), SLOT(command_error(QCA::QPipeEnd::Error)));
146
connect(&pipeStatus.readEnd(), SIGNAL(readyRead()), SLOT(status_read()));
147
connect(&pipeStatus.readEnd(), SIGNAL(error(QCA::QPipeEnd::Error)), SLOT(status_error(QCA::QPipeEnd::Error)));
148
connect(&startTrigger, SIGNAL(timeout()), SLOT(doStart()));
149
connect(&doneTrigger, SIGNAL(timeout()), SLOT(doTryDone()));
151
reset(ResetSessionAndData);
162
pipeAux.readEnd().reset();
163
pipeCommand.readEnd().reset();
164
pipeStatus.writeEnd().reset();
172
void reset(ResetMode mode)
180
proc->disconnect(this);
181
if(proc->state() != QProcess::NotRunning)
184
#ifdef QPROC_SIGNAL_RELAY
187
delete proc; // should be safe to do thanks to relay
204
pre_stdin_close = false;
205
pre_aux_close = false;
206
pre_command_close = false;
212
if(mode >= ResetSessionAndData)
216
leftover_stdout.clear();
217
leftover_stderr.clear();
218
error = GPGProc::FailedToStart;
223
bool setupPipes(bool makeAux)
225
if(makeAux && !pipeAux.create())
228
emit q->debug("Error creating pipeAux");
233
if(!pipeCommand.create(true)) // secure
235
if(!pipeCommand.create())
239
emit q->debug("Error creating pipeCommand");
243
if(!pipeStatus.create())
246
emit q->debug("Error creating pipeStatus");
253
void setupArguments()
255
QStringList fullargs;
256
fullargs += "--no-tty";
258
if(mode == ExtendedMode)
260
fullargs += "--enable-special-filenames";
262
fullargs += "--status-fd";
263
fullargs += QString::number(pipeStatus.writeEnd().idAsInt());
265
fullargs += "--command-fd";
266
fullargs += QString::number(pipeCommand.readEnd().idAsInt());
269
for(int n = 0; n < args.count(); ++n)
272
if(mode == ExtendedMode && a == "-&?")
273
fullargs += QString("-&") + QString::number(pipeAux.readEnd().idAsInt());
278
QString fullcmd = fullargs.join(" ");
279
emit q->debug(QString("Running: [") + bin + ' ' + fullcmd + ']');
288
// Note: for unix, inheritability is set in SProcess
289
if(pipeAux.readEnd().isValid())
290
pipeAux.readEnd().setInheritable(true);
291
if(pipeCommand.readEnd().isValid())
292
pipeCommand.readEnd().setInheritable(true);
293
if(pipeStatus.writeEnd().isValid())
294
pipeStatus.writeEnd().setInheritable(true);
299
proc->start(bin, args);
301
// FIXME: From reading the source to Qt on both windows
302
// and unix platforms, we know that fork/CreateProcess
303
// are called in start. However this is not guaranteed
304
// from an API perspective. We should probably call
305
// QProcess::waitForStarted() to synchronously ensure
306
// fork/CreateProcess are called before closing these
308
pipeAux.readEnd().close();
309
pipeCommand.readEnd().close();
310
pipeStatus.writeEnd().close();
313
void aux_written(int x)
315
emit q->bytesWrittenAux(x);
318
void aux_error(QCA::QPipeEnd::Error)
320
emit q->debug("Aux: Pipe error");
322
emit q->error(GPGProc::ErrorWrite);
325
void command_written(int x)
327
emit q->bytesWrittenCommand(x);
330
void command_error(QCA::QPipeEnd::Error)
332
emit q->debug("Command: Pipe error");
334
emit q->error(GPGProc::ErrorWrite);
339
if(readAndProcessStatusData())
340
emit q->readyReadStatusLines();
343
void status_error(QCA::QPipeEnd::Error e)
345
if(e == QPipeEnd::ErrorEOF)
346
emit q->debug("Status: Closed (EOF)");
348
emit q->debug("Status: Closed (gone)");
356
emit q->debug("Process started");
358
// Note: we don't close these here anymore. instead we
359
// do it just after calling proc->start().
360
// close these, we don't need them
361
/*pipeAux.readEnd().close();
362
pipeCommand.readEnd().close();
363
pipeStatus.writeEnd().close();*/
366
if(!pre_stdin.isEmpty())
368
proc->write(pre_stdin);
371
if(!pre_aux.isEmpty())
373
pipeAux.writeEnd().write(pre_aux);
376
if(!pre_command.isEmpty())
379
pipeCommand.writeEnd().writeSecure(pre_command);
381
pipeCommand.writeEnd().write(pre_command);
387
proc->closeWriteChannel();
389
pipeAux.writeEnd().close();
390
if(pre_command_close)
391
pipeCommand.writeEnd().close();
394
void proc_readyReadStandardOutput()
396
emit q->readyReadStdout();
399
void proc_readyReadStandardError()
401
emit q->readyReadStderr();
404
void proc_bytesWritten(qint64 lx)
407
emit q->bytesWrittenStdin(x);
410
void proc_finished(int x)
412
emit q->debug(QString("Process finished: %1").arg(x));
416
fin_process_success = true;
418
if(need_status && !fin_status)
420
pipeStatus.readEnd().finalize();
422
if(readAndProcessStatusData())
425
emit q->readyReadStatusLines();
433
void proc_error(QProcess::ProcessError x)
435
QMap<int, QString> errmap;
436
errmap[QProcess::FailedToStart] = "FailedToStart";
437
errmap[QProcess::Crashed] = "Crashed";
438
errmap[QProcess::Timedout] = "Timedout";
439
errmap[QProcess::WriteError] = "WriteError";
440
errmap[QProcess::ReadError] = "ReadError";
441
errmap[QProcess::UnknownError] = "UnknownError";
443
emit q->debug(QString("Process error: %1").arg(errmap[x]));
445
if(x == QProcess::FailedToStart)
446
error = GPGProc::FailedToStart;
447
else if(x == QProcess::WriteError)
448
error = GPGProc::ErrorWrite;
450
error = GPGProc::UnexpectedExit;
453
fin_process_success = false;
456
// If the process fails to start, then the ends of the pipes
457
// intended for the child process are still open. Some Mac
458
// users experience a lockup if we close our ends of the pipes
459
// when the child's ends are still open. If we ensure the
460
// child's ends are closed, we prevent this lockup. I have no
461
// idea why the problem even happens or why this fix should
463
pipeAux.readEnd().reset();
464
pipeCommand.readEnd().reset();
465
pipeStatus.writeEnd().reset();
468
if(need_status && !fin_status)
470
pipeStatus.readEnd().finalize();
472
if(readAndProcessStatusData())
475
emit q->readyReadStatusLines();
488
if(need_status && !fin_status)
491
emit q->debug("Done");
494
proc->setReadChannel(QProcess::StandardOutput);
495
leftover_stdout = proc->readAll();
497
proc->setReadChannel(QProcess::StandardError);
498
leftover_stderr = proc->readAll();
501
if(fin_process_success)
502
emit q->finished(exitCode);
504
emit q->error(error);
508
bool readAndProcessStatusData()
510
QByteArray buf = pipeStatus.readEnd().read();
514
return processStatusData(buf);
517
// return true if there are newly parsed lines available
518
bool processStatusData(const QByteArray &buf)
520
statusBuf.append(buf);
526
int n = statusBuf.indexOf('\n');
530
// extract the string from statusbuf
532
char *p = (char *)statusBuf.data();
534
int newsize = statusBuf.size() - n;
535
memmove(p, p + n, newsize);
536
statusBuf.resize(newsize);
538
// convert to string without newline
539
QString str = QString::fromUtf8(cs);
540
str.truncate(str.length() - 1);
542
// ensure it has a proper header
543
if(str.left(9) != "[GNUPG:] ")
561
GPGProc::GPGProc(QObject *parent)
564
d = new Private(this);
572
void GPGProc::reset()
577
bool GPGProc::isActive() const
579
return (d->proc ? true : false);
582
void GPGProc::start(const QString &bin, const QStringList &args, Mode mode)
585
d->reset(ResetSessionAndData);
587
if(mode == ExtendedMode)
589
if(!d->setupPipes(args.contains("-&?")))
591
d->error = FailedToStart;
594
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, Q_ARG(gpgQCAPlugin::GPGProc::Error, d->error));
598
d->need_status = true;
600
emit debug("Pipe setup complete");
603
d->proc = new SProcess(d);
607
if(d->pipeAux.readEnd().isValid())
608
plist += d->pipeAux.readEnd().id();
609
if(d->pipeCommand.readEnd().isValid())
610
plist += d->pipeCommand.readEnd().id();
611
if(d->pipeStatus.writeEnd().isValid())
612
plist += d->pipeStatus.writeEnd().id();
613
d->proc->setInheritPipeList(plist);
616
// enable the pipes we want
617
if(d->pipeAux.writeEnd().isValid())
618
d->pipeAux.writeEnd().enable();
619
if(d->pipeCommand.writeEnd().isValid())
620
d->pipeCommand.writeEnd().enable();
621
if(d->pipeStatus.readEnd().isValid())
622
d->pipeStatus.readEnd().enable();
624
#ifdef QPROC_SIGNAL_RELAY
625
d->proc_relay = new QProcessSignalRelay(d->proc, d);
626
connect(d->proc_relay, SIGNAL(started()), d, SLOT(proc_started()));
627
connect(d->proc_relay, SIGNAL(readyReadStandardOutput()), d, SLOT(proc_readyReadStandardOutput()));
628
connect(d->proc_relay, SIGNAL(readyReadStandardError()), d, SLOT(proc_readyReadStandardError()));
629
connect(d->proc_relay, SIGNAL(bytesWritten(qint64)), d, SLOT(proc_bytesWritten(qint64)));
630
connect(d->proc_relay, SIGNAL(finished(int)), d, SLOT(proc_finished(int)));
631
connect(d->proc_relay, SIGNAL(error(QProcess::ProcessError)), d, SLOT(proc_error(QProcess::ProcessError)));
633
connect(d->proc, SIGNAL(started()), d, SLOT(proc_started()));
634
connect(d->proc, SIGNAL(readyReadStandardOutput()), d, SLOT(proc_readyReadStandardOutput()));
635
connect(d->proc, SIGNAL(readyReadStandardError()), d, SLOT(proc_readyReadStandardError()));
636
connect(d->proc, SIGNAL(bytesWritten(qint64)), d, SLOT(proc_bytesWritten(qint64)));
637
connect(d->proc, SIGNAL(finished(int)), d, SLOT(proc_finished(int)));
638
connect(d->proc, SIGNAL(error(QProcess::ProcessError)), d, SLOT(proc_error(QProcess::ProcessError)));
644
d->startTrigger.start();
647
QByteArray GPGProc::readStdout()
651
d->proc->setReadChannel(QProcess::StandardOutput);
652
return d->proc->readAll();
656
QByteArray a = d->leftover_stdout;
657
d->leftover_stdout.clear();
662
QByteArray GPGProc::readStderr()
666
d->proc->setReadChannel(QProcess::StandardError);
667
return d->proc->readAll();
671
QByteArray a = d->leftover_stderr;
672
d->leftover_stderr.clear();
677
QStringList GPGProc::readStatusLines()
679
QStringList out = d->statusLines;
680
d->statusLines.clear();
684
void GPGProc::writeStdin(const QByteArray &a)
686
if(!d->proc || a.isEmpty())
689
if(d->proc->state() == QProcess::Running)
695
void GPGProc::writeAux(const QByteArray &a)
697
if(!d->proc || a.isEmpty())
700
if(d->proc->state() == QProcess::Running)
701
d->pipeAux.writeEnd().write(a);
707
void GPGProc::writeCommand(const SecureArray &a)
709
void GPGProc::writeCommand(const QByteArray &a)
712
if(!d->proc || a.isEmpty())
715
if(d->proc->state() == QProcess::Running)
717
d->pipeCommand.writeEnd().writeSecure(a);
719
d->pipeCommand.writeEnd().write(a);
725
void GPGProc::closeStdin()
730
if(d->proc->state() == QProcess::Running)
731
d->proc->closeWriteChannel();
733
d->pre_stdin_close = true;
736
void GPGProc::closeAux()
741
if(d->proc->state() == QProcess::Running)
742
d->pipeAux.writeEnd().close();
744
d->pre_aux_close = true;
747
void GPGProc::closeCommand()
752
if(d->proc->state() == QProcess::Running)
753
d->pipeCommand.writeEnd().close();
755
d->pre_command_close = true;
760
#include "gpgproc.moc"