diff options
author | nils <nils@34ebc366-c3a9-4b3c-9f84-69acf7962910> | 2012-08-02 00:05:44 +0000 |
---|---|---|
committer | nils <nils@34ebc366-c3a9-4b3c-9f84-69acf7962910> | 2012-08-02 00:05:44 +0000 |
commit | 4149c8712b45791605ea3502c00baa94b0948f33 (patch) | |
tree | e3e9d021b5d8e73a36ed5b2fda48b0fe2dc81e38 | |
parent | start removing gpgme (diff) | |
download | gpg4usb-4149c8712b45791605ea3502c00baa94b0948f33.tar.gz gpg4usb-4149c8712b45791605ea3502c00baa94b0948f33.zip |
try to integrate gpg wrapper from kgpg - broken now
git-svn-id: http://cpunk.de/svn/src/gpg4usb/branches/0.3.2-mac@921 34ebc366-c3a9-4b3c-9f84-69acf7962910
-rw-r--r-- | gpg4usb.pro | 18 | ||||
-rw-r--r-- | gpgcontext.cpp | 33 | ||||
-rw-r--r-- | gpgcontext.h | 3 | ||||
-rw-r--r-- | kgpg/gpgproc.cpp | 348 | ||||
-rw-r--r-- | kgpg/gpgproc.h | 156 | ||||
-rw-r--r-- | kgpg/kgpg.pro | 12 | ||||
-rw-r--r-- | kgpg/klinebufferedprocess.cpp | 112 | ||||
-rw-r--r-- | kgpg/klinebufferedprocess.h | 155 | ||||
-rw-r--r-- | kgpg/kprocess.cpp | 410 | ||||
-rw-r--r-- | kgpg/kprocess.h | 341 | ||||
-rw-r--r-- | kgpg/kprocess_p.h | 50 |
11 files changed, 1623 insertions, 15 deletions
diff --git a/gpg4usb.pro b/gpg4usb.pro index d621071..f8f9ace 100644 --- a/gpg4usb.pro +++ b/gpg4usb.pro @@ -15,6 +15,7 @@ INCLUDEPATH += . \ CONFIG += release static #CONFIG += release +#CONFIG += debug QT += network # Input HEADERS += attachments.h \ @@ -39,7 +40,10 @@ HEADERS += attachments.h \ verifykeydetailbox.h \ wizard.h \ helppage.h \ -# gpgproc.h \ + kgpg/gpgproc.h \ + kgpg/klinebufferedprocess.h \ + kgpg/kprocess.h \ + kgpg/kprocess_p.h \ gpgconstants.h SOURCES += attachments.cpp \ @@ -65,7 +69,9 @@ SOURCES += attachments.cpp \ verifykeydetailbox.cpp \ wizard.cpp \ helppage.cpp \ -# gpgproc.cpp \ + kgpg/gpgproc.cpp \ + kgpg/klinebufferedprocess.cpp \ + kgpg/kprocess.cpp \ gpgconstants.cpp RC_FILE = gpg4usb.rc @@ -73,12 +79,12 @@ RC_FILE = gpg4usb.rc RESOURCES = gpg4usb.qrc # comment out line below for static building -#LIBS += -lgpgme \ -# -lgpg-error \ +LIBS += -lgpgme \ + -lgpg-error \ # comment in for static buildding in windows -INCLUDEPATH += ./macbuild/include -LIBS +=./macbuild/lib/libgpgme.a ./macbuild/lib/libgpg-error.a +#INCLUDEPATH += ./macbuild/include +#LIBS +=./macbuild/lib/libgpgme.a ./macbuild/lib/libgpg-error.a DEFINES += _FILE_OFFSET_BITS=64 diff --git a/gpgcontext.cpp b/gpgcontext.cpp index 251d372..534ddea 100644 --- a/gpgcontext.cpp +++ b/gpgcontext.cpp @@ -20,7 +20,8 @@ */ #include "gpgcontext.h" - +#include "kgpg/gpgproc.h" +#include "kgpg/klinebufferedprocess.h" #ifdef _WIN32 #include <windows.h> #include <unistd.h> /* contains read/write */ @@ -43,33 +44,33 @@ GpgContext::GpgContext() * subsystem in GPGME. (from the info page) */ #ifdef Q_WS_WIN - QString gpgBin = appPath + "/bin/gpg.exe"; - QString gpgKeys = appPath + "/keydb"; + gpgBin = appPath + "/bin/gpg.exe"; + gpgKeys = appPath + "/keydb"; #endif #ifdef Q_WS_MAC - QString gpgBin = appPath + "/bin/gpg-mac.app"; + gpgBin = appPath + "/bin/gpg-mac.app"; - QString gpgKeys = appPath + "/keydb"; + gpgKeys = appPath + "/keydb"; qDebug() << "gpg bin:" << gpgBin; qDebug() << "gpg keydb: " << gpgKeys; #endif #ifdef Q_WS_X11 - QString gpgBin = appPath + "/bin/gpg"; - QString gpgKeys = appPath + "/keydb"; + gpgBin = appPath + "/bin/gpg"; + gpgKeys = appPath + "/keydb"; #endif QStringList args; args << "--homedir" << gpgKeys << "--list-keys"; - QProcess gpg; +/* QProcess gpg; gpg.setProcessChannelMode(QProcess::MergedChannels); gpg.start(gpgBin, args); gpg.waitForFinished(-1); qDebug() << "huhu" << gpg.readAll(); - +*/ connect(this,SIGNAL(keyDBChanged()),this,SLOT(refreshKeyList())); refreshKeyList(); } @@ -211,6 +212,20 @@ GpgKeyList GpgContext::listKeys() GpgKeyList keys; + GPGProc process(this, gpgBin); +/* process << + QLatin1String("--with-colons") << + QLatin1String("--with-fingerprint") << + QLatin1String("--fixed-list-mode") << + QLatin1String("--list-keys"); + + process.setOutputChannelMode(KProcess::MergedChannels); + + process.start(); + process.waitForFinished(-1);*/ + //while (item == process->) +// return readPublicKeysProcess(process, NULL); + //TODO dont run the loop more often than necessary // list all keys ( the 0 is for all ) diff --git a/gpgcontext.h b/gpgcontext.h index 4efc3fe..f9caf61 100644 --- a/gpgcontext.h +++ b/gpgcontext.h @@ -176,6 +176,9 @@ private: QByteArray *stdOut, QByteArray *stdErr); + QString gpgBin; + QString gpgKeys; + }; } // namespace GpgME diff --git a/kgpg/gpgproc.cpp b/kgpg/gpgproc.cpp new file mode 100644 index 0000000..291e8a2 --- /dev/null +++ b/kgpg/gpgproc.cpp @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2007,2008,2009,2010,2011,2012 Rolf Eike Beer <[email protected]> + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "gpgproc.h" + +//#include "kgpgsettings.h" + +//#include <KDebug> +#include <QDebug> +//#include <KProcess> +#include "kprocess.h" +//#include <KStandardDirs> +#include <QDir> +#include <QTextCodec> + +class GnupgBinary { +public: + GnupgBinary(); + + const QString &binary() const; + void setBinary(const QString &executable); + const QStringList &standardArguments() const; + unsigned int version() const; + bool supportsDebugLevel() const; + +private: + QString m_binary; + QStringList m_standardArguments; + unsigned int m_version; + bool m_useDebugLevel; +}; + +GnupgBinary::GnupgBinary() + : m_useDebugLevel(false) +{ +} + +const QString &GnupgBinary::binary() const +{ + return m_binary; +} + +/** + * @brief check if GnuPG returns an error for this arguments + * @param executable the GnuPG executable to call + * @param arguments the arguments to pass to executable + * + * The arguments will be used together with "--version", so they should not + * be any commands. + */ +static bool checkGnupgArguments(const QString &executable, const QStringList &arguments) +{ + KProcess gpg; + + // We ignore the output anyway, just make sure it doesn't clutter the output of + // the parent process. Simplify the handling by putting all trash in one can. + gpg.setOutputChannelMode(KProcess::MergedChannels); + + QStringList allArguments = arguments; + allArguments << QLatin1String("--version"); + gpg.setProgram(executable, allArguments); + + return (gpg.execute() == 0); +} + +static QString getGpgProcessHome(const QString &binary) +{ + GPGProc process(0, binary); + process << QLatin1String( "--version" ); + process.start(); + process.waitForFinished(-1); + + if (process.exitCode() == 255) { + return QString(); + } + + QString line; + while (process.readln(line) != -1) { + if (line.startsWith(QLatin1String("Home: "))) { + line.remove(0, 6); + return line; + } + } + + return QString(); +} + + +void GnupgBinary::setBinary(const QString &executable) +{ + qDebug() << "checking version of GnuPG executable" << executable; + // must be set first as gpgVersionString() uses GPGProc to parse the output + m_binary = executable; + const QString verstr = GPGProc::gpgVersionString(executable); + m_version = GPGProc::gpgVersion(verstr); + qDebug() << "version is" << verstr << m_version; + + m_useDebugLevel = (m_version > 0x20000); + + const QString gpgConfigFile = "";//KGpgSettings::gpgConfigPath(); + + m_standardArguments.clear(); + m_standardArguments << QLatin1String( "--no-secmem-warning" ) + << QLatin1String( "--no-tty" ) + << QLatin1String("--no-greeting"); + + if (!gpgConfigFile.isEmpty()) + m_standardArguments << QLatin1String("--options") + << gpgConfigFile; + + QStringList debugLevelArguments(QLatin1String("--debug-level")); + debugLevelArguments << QLatin1String("none"); + if (checkGnupgArguments(executable, debugLevelArguments)) + m_standardArguments << debugLevelArguments; +} + +const QStringList& GnupgBinary::standardArguments() const +{ + return m_standardArguments; +} + +unsigned int GnupgBinary::version() const +{ + return m_version; +} + +bool GnupgBinary::supportsDebugLevel() const +{ + return m_useDebugLevel; +} + +Q_GLOBAL_STATIC(GnupgBinary, lastBinary) + +GPGProc::GPGProc(QObject *parent, const QString &binary) + : KLineBufferedProcess(parent) +{ + resetProcess(binary); +} + +GPGProc::~GPGProc() +{ +} + +void +GPGProc::resetProcess(const QString &binary) +{ + GnupgBinary *bin;// = lastBinary; + QString executable; + + qDebug() << "bin:" << binary; + + /*if (binary.isEmpty()) + executable = KGpgSettings::gpgBinaryPath(); + else*/ + executable = binary; + + if (bin->binary() != executable) + bin->setBinary(executable); + + setProgram(executable, bin->standardArguments()); + + setOutputChannelMode(OnlyStdoutChannel); + + disconnect(SIGNAL(finished(int,QProcess::ExitStatus))); + disconnect(SIGNAL(lineReadyStandardOutput())); +} + +void GPGProc::start() +{ + // make sure there is exactly one connection from us to that signal + connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(finished()), Qt::UniqueConnection); + connect(this, SIGNAL(lineReadyStandardOutput()), this, SLOT(received()), Qt::UniqueConnection); + KProcess::start(); +} + +void GPGProc::received() +{ + emit readReady(); +} + +void GPGProc::finished() +{ + emit processExited(); +} + +int GPGProc::readln(QString &line, const bool colons) +{ + QByteArray a; + if (!readLineStandardOutput(&a)) + return -1; + + line = recode(a, colons); + + return line.length(); +} + +int GPGProc::readln(QStringList &l) +{ + QString s; + + int len = readln(s); + if (len < 0) + return len; + + l = s.split(QLatin1Char( ':' )); + + for (int i = 0; i < l.count(); ++i) + { + int j = 0; + while ((j = l[i].indexOf(QLatin1String( "\\x3a" ), j, Qt::CaseInsensitive)) >= 0) + { + l[i].replace(j, 4, QLatin1Char( ':' )); + j++; + } + } + + return l.count(); +} + +QString +GPGProc::recode(QByteArray a, const bool colons) +{ + int pos = 0; + + while ((pos = a.indexOf("\\x", pos)) >= 0) { + if (pos > a.length() - 4) + break; + + const QByteArray pattern(a.mid(pos, 4)); + const QByteArray hexnum(pattern.right(2)); + bool ok; + char n[2]; + n[0] = hexnum.toUShort(&ok, 16); + n[1] = '\0'; // to use n as a 0-terminated string + if (!ok) + continue; + + // QLatin1Char( ':' ) must be skipped, it is used as column delimiter + // since it is pure ascii it can be replaced in QString. + if (!colons && (n[0] == ':' )) { + pos += 3; + continue; + } + + // it is likely to find the same byte sequence more than once + int npos = pos; + do { + a.replace(npos, 4, n); + } while ((npos = a.indexOf(pattern, npos)) >= 0); + } + + return QTextCodec::codecForName("utf8")->toUnicode(a); +} + +int GPGProc::gpgVersion(const QString &vstr) +{ + if (vstr.isEmpty()) + return -1; + + QStringList values(vstr.split(QLatin1Char( '.' ))); + if (values.count() < 3) + return -2; + + return (0x10000 * values[0].toInt() + 0x100 * values[1].toInt() + values[2].toInt()); +} + +QString GPGProc::gpgVersionString(const QString &binary) +{ + GPGProc process(0, binary); + process << QLatin1String( "--version" ); + process.start(); + process.waitForFinished(-1); + + if (process.exitCode() == 255) + return QString(); + + QString line; + if (process.readln(line) != -1) + return line.simplified().section(QLatin1Char( ' ' ), -1); + else + return QString(); +} + +QString GPGProc::getGpgStartupError(const QString &binary) +{ + GPGProc process(0, binary); + process << QLatin1String( "--version" ); + process.start(); + process.waitForFinished(-1); + + QString result; + + while (process.hasLineStandardError()) { + QByteArray tmp; + process.readLineStandardError(&tmp); + tmp += '\n'; + result += QString::fromUtf8(tmp); + } + + return result; +} + +QString GPGProc::getGpgHome(const QString &binary) +{ + // First try: if environment is set GnuPG will use that directory + // We can use this directly without starting a new process + QByteArray env(qgetenv("GNUPGHOME")); + QString gpgHome; + if (!env.isEmpty()) { + gpgHome = QLatin1String( env ); + } else if (!binary.isEmpty()) { + // Second try: start GnuPG and ask what it is + gpgHome = getGpgProcessHome(binary); + } + + // Third try: guess what it is. + if (gpgHome.isEmpty()) { +#ifdef Q_OS_WIN32 //krazy:exclude=cpp + gpgHome = qgetenv("APPDATA") + QLatin1String( "/gnupg/" ); + gpgHome.replace(QLatin1Char( '\\' ), QLatin1Char( '/' )); +#else + gpgHome = QDir::homePath() + QLatin1String( "/.gnupg/" ); +#endif + } + + gpgHome.replace(QLatin1String( "//" ), QLatin1String( "/" )); + + if (!gpgHome.endsWith(QLatin1Char( '/' ))) + gpgHome.append(QLatin1Char( '/' )); + + if (gpgHome.startsWith(QLatin1Char( '~' ))) + gpgHome.replace(0, 1, QDir::homePath()); + + //KStandardDirs::makeDir(gpgHome, 0700); + return gpgHome; +} + +//#include "gpgproc.moc" diff --git a/kgpg/gpgproc.h b/kgpg/gpgproc.h new file mode 100644 index 0000000..ce868db --- /dev/null +++ b/kgpg/gpgproc.h @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2007 Rolf Eike Beer <[email protected]> + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef GPGPROC_H +#define GPGPROC_H + +#include <QString> +#include <QStringList> + +#include "klinebufferedprocess.h" + +/** + * @brief A interface to GnuPG handling UTF8 recoding correctly + * + * This class handles the GnuPG formatted UTF8 output correctly. + * GnuPG recodes some characters as \\xnn where nn is the hex representation + * of the character. This can't be fixed up simply when using QString as + * QString already did it's own UTF8 conversion. Therefore we replace this + * sequences by their corresponding character so QString will work just fine. + * + * As we know that GnuPG limits it's columns by QLatin1Char( ':' ) we skip \\x3a. Since this + * is an ascii character (single byte) the replacement can be done later without + * problems after the line has been split into pieces. + * + * @author Rolf Eike Beer + */ +class GPGProc : public KLineBufferedProcess +{ + Q_OBJECT + +public: + /** + * Constructor + * @param parent parent object + * @param binary path to GnuPG binary or QString() to use the configured + */ + explicit GPGProc(QObject *parent = 0, const QString &binary = QString()); + + /** + * Destructor + */ + ~GPGProc(); + + /** + * Starts the process + */ + void start(); + + /** + * Reads a line of text (excluding '\\n'). + * + * Use readln() in response to a readReady() signal. + * You may use it multiple times if more than one line of data is + * available. + * + * readln() never blocks. + * + * @param line is used to store the line that was read. + * @param colons recode also colons + * @return the number of characters read, or -1 if no data is available. + */ + int readln(QString &line, const bool colons = false); + + /** + * Reads a line of text and splits it into parts. + * + * Use readln() in response to a readReady() signal. + * You may use it multiple times if more than one line of data is + * available. + * + * readln() never blocks. + * + * @param l is used to store the parts of the line that was read. + * @return the number of characters read, or -1 if no data is available. + */ + int readln(QStringList &l); + + /** + * Recode a line from GnuPG encoding to UTF8 + * + * @param a data to recode + * @param colons recode also colons + * @return recoded string + */ + static QString recode(QByteArray a, const bool colons = true); + + /** + * Reset the class to the state it had right after creation + * @param binary path to GnuPG binary or empty string to use the configured one + */ + void resetProcess(const QString &binary = QString()); + + /** + * @brief parse GnuPG version string and return version as number + * @param vstr version string + * @return -1 if vstr is empty, -2 on parse error, parsed number on success + * + * The version string must be in format A.B.C with A, B, and C numbers. The + * returned number is A * 65536 + B * 256 + C. + */ + static int gpgVersion(const QString &vstr); + /** + * @brief get the GnuPG version string of the given binary + * @param binary name or path to GnuPG binary + * @return version string or empty string on error + * + * This starts a GnuPG process and asks the binary for version information. + * The returned string is the version information without any leading text. + */ + static QString gpgVersionString(const QString &binary); + /** + * @brief find users GnuPG directory + * @param binary name or path to GnuPG binary + * @return path to directory + * + * Use this function to find out where GnuPG would store it's configuration + * and data files. The returned path always ends with a '/'. + */ + static QString getGpgHome(const QString &binary); + + /** + * @brief run GnuPG and check if it complains about anything + * @param binary the GnuPG binary to run + * @return the error message GnuPG gave out (if any) + */ + static QString getGpgStartupError(const QString &binary); + +signals: + /** + * Emitted when the process is ready for reading. + * The signal is only emitted if at least one complete line of data is ready. + * @param p the process that emitted the signal + */ + void readReady(); + + /** + * Emitted when the process has finished + * @param p the process that emitted the signal + */ + void processExited(); + +protected slots: + void finished(); + void received(); +}; + +#endif // GPGPROC_H diff --git a/kgpg/kgpg.pro b/kgpg/kgpg.pro new file mode 100644 index 0000000..7dd6391 --- /dev/null +++ b/kgpg/kgpg.pro @@ -0,0 +1,12 @@ +###################################################################### +# Automatically generated by qmake (2.01a) Wed Aug 1 23:46:35 2012 +###################################################################### + +TEMPLATE = app +TARGET = +DEPENDPATH += . +INCLUDEPATH += . + +# Input +HEADERS += gpgproc.h klinebufferedprocess.h kprocess.h kprocess_p.h +SOURCES += gpgproc.cpp klinebufferedprocess.cpp kprocess.cpp diff --git a/kgpg/klinebufferedprocess.cpp b/kgpg/klinebufferedprocess.cpp new file mode 100644 index 0000000..00099af --- /dev/null +++ b/kgpg/klinebufferedprocess.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2008 Rolf Eike Beer <[email protected]> + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "klinebufferedprocess.h" + + +KLineBufferedProcessPrivate::KLineBufferedProcessPrivate(KLineBufferedProcess *parent) + : m_newlineInStdout(-1), + m_newlineInStderr(-1), + m_parent(parent), +#ifdef Q_OS_WIN32 //krazy:exclude=cpp + m_lineEnd("\r\n") +#else + m_lineEnd("\n") +#endif +{ +} + +KLineBufferedProcess::KLineBufferedProcess(QObject *parent) + : KProcess(parent), + d(new KLineBufferedProcessPrivate(this)) +{ + connect(this, SIGNAL(readyReadStandardOutput()), this, SLOT(_k_receivedStdout())); + connect(this, SIGNAL(readyReadStandardError()), this, SLOT(_k_receivedStderr())); +} + +KLineBufferedProcess::~KLineBufferedProcess() +{ + delete d; +} + +void KLineBufferedProcessPrivate::_k_receivedStdout() +{ + QByteArray ndata = m_parent->readAllStandardOutput(); + int oldBufferSize = m_stdoutBuffer.size(); + m_stdoutBuffer.append(ndata); + + if (m_newlineInStdout < 0) { + m_newlineInStdout = ndata.indexOf(m_lineEnd); + if (m_newlineInStdout >= 0) { + m_newlineInStdout += oldBufferSize; + emit m_parent->lineReadyStandardOutput(); + } + } +} + +void KLineBufferedProcessPrivate::_k_receivedStderr() +{ + QByteArray ndata = m_parent->readAllStandardError(); + int oldBufferSize = m_stderrBuffer.size(); + m_stderrBuffer.append(ndata); + + if (m_newlineInStderr < 0) { + m_newlineInStderr = ndata.indexOf(m_lineEnd); + if (m_newlineInStderr >= 0) { + m_newlineInStderr += oldBufferSize; + emit m_parent->lineReadyStandardError(); + } + } +} + +bool KLineBufferedProcess::readLineStandardOutput(QByteArray *line) +{ + if (d->m_newlineInStdout < 0) { + return false; + } + + // don't copy '\n' + *line = d->m_stdoutBuffer.left(d->m_newlineInStdout); + d->m_stdoutBuffer.remove(0, d->m_newlineInStdout + d->m_lineEnd.length()); + + d->m_newlineInStdout = d->m_stdoutBuffer.indexOf(d->m_lineEnd); + + return true; +} + +bool KLineBufferedProcess::readLineStandardError(QByteArray *line) +{ + if (d->m_newlineInStderr < 0) { + return false; + } + + // don't copy '\n' + *line = d->m_stderrBuffer.left(d->m_newlineInStderr); + d->m_stderrBuffer.remove(0, d->m_newlineInStderr + d->m_lineEnd.length()); + + d->m_newlineInStderr = d->m_stderrBuffer.indexOf(d->m_lineEnd); + + return true; +} + +bool KLineBufferedProcess::hasLineStandardOutput() const +{ + return d->m_newlineInStdout >= 0; +} + +bool KLineBufferedProcess::hasLineStandardError() const +{ + return d->m_newlineInStderr >= 0; +} + +//#include "moc_klinebufferedprocess.cpp" diff --git a/kgpg/klinebufferedprocess.h b/kgpg/klinebufferedprocess.h new file mode 100644 index 0000000..eab360b --- /dev/null +++ b/kgpg/klinebufferedprocess.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2008 Rolf Eike Beer <[email protected]> + */ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef KLINEBUFFEREDPROCESS_H +#define KLINEBUFFEREDPROCESS_H + +#include "kprocess.h" + +class QByteArray; +//class KLineBufferedProcessPrivate; +class KLineBufferedProcess; +class KLineBufferedProcessPrivate +{ +public: + KLineBufferedProcessPrivate(KLineBufferedProcess *parent); + +//private slot implementations + void _k_receivedStdout(); + void _k_receivedStderr(); + + QByteArray m_stdoutBuffer; + QByteArray m_stderrBuffer; + int m_newlineInStdout; + int m_newlineInStderr; + KLineBufferedProcess * const m_parent; + const QByteArray m_lineEnd; +}; + +/** + * Read output of a process split into lines + * + * This class reads the output of a process and splits it up into lines. This + * is especially useful if you try to parse the output of a command line tool. + * + * \b Usage \n + * + * The class is created and set up like a KProcess. After this you can do + * something like this: + * + * \code + * connect(m_linebufprocess, SIGNAL(lineReadyStandardOutput()), SLOT(dataStdout())); + * ... + * void myobj::dataStdout() + * { + * while (m_linebufprocess->hasLineStandardOutput()) { + * QByteArray line; + * m_linebufprocess->readLineStandardOutput(line); + * ... + * } + * } + * \endcode + * + * Never use the read functionality of KProcess with this class. This class + * needs to read all data from the process into an internal buffer first. If + * you try to use the read functions of the parent classes you would normally + * get no output at all. + * + * The write functions of the parent classes are not effected. You can use + * them exactly the same way as in KProcess. + * + * @author Rolf Eike Beer + */ +class KLineBufferedProcess : public KProcess +{ + Q_OBJECT + friend class KLineBufferedProcessPrivate; + +public: + /** + * Constructor + */ + explicit KLineBufferedProcess(QObject *parent = 0); + + /** + * Destructor + */ + ~KLineBufferedProcess(); + + /** + * Reads a line of text (excluding '\\n') from stdout. + * + * Use readLineStdout() in response to a lineReadyStdout() signal or + * when hasLineStdout() returns true. You may use it multiple times if + * more than one line of data is available. If no complete line is + * available the content of line is undefined and the function returns + * false. + * + * @param line is used to store the line that was read. + * @return if data was read or not + */ + bool readLineStandardOutput(QByteArray *line); + + /** + * Reads a line of text (excluding '\\n') from stderr. + * + * Use readLineStderr() in response to a lineReadyStderr() signal or + * when hasLineStderr() returns true. You may use it multiple times if + * more than one line of data is available. If no complete line is + * available the content of line is undefined and the function returns + * false. + * + * @param line is used to store the line that was read. + * @return if data was read or not + */ + bool readLineStandardError(QByteArray *line); + + /** + * Checks if a line is ready on stdout + * + * @return true if a complete line can be read + */ + bool hasLineStandardOutput() const; + + /** + * Checks if a line is ready on stdout + * + * @return true if a complete line can be read + */ + bool hasLineStandardError() const; + +signals: + /** + * Emitted when there is a line of data available from stdout when there was + * previously none. + * There may or may not be more than one line available for reading when this + * signal is emitted. + */ + void lineReadyStandardOutput(); + + /** + * Emitted when there is a line of data available from stderr when there was + * previously none. + * There may or may not be more than one line available for reading when this + * signal is emitted. + */ + void lineReadyStandardError(); + +private: + KLineBufferedProcessPrivate* const d; + + Q_PRIVATE_SLOT(d, void _k_receivedStdout()) + Q_PRIVATE_SLOT(d, void _k_receivedStderr()) +}; + +#endif // KLINEBUFFEREDPROCESS_H diff --git a/kgpg/kprocess.cpp b/kgpg/kprocess.cpp new file mode 100644 index 0000000..a5b8628 --- /dev/null +++ b/kgpg/kprocess.cpp @@ -0,0 +1,410 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kprocess_p.h" + +//#include <kstandarddirs.h> +//#include <kshell.h> +//#ifdef Q_OS_WIN +//# include <kshell_p.h> +//#endif + +#include <qfile.h> + +#ifdef Q_OS_WIN +# include <windows.h> +#else +# include <unistd.h> +# include <errno.h> +#endif + +#ifndef Q_OS_WIN +# define STD_OUTPUT_HANDLE 1 +# define STD_ERROR_HANDLE 2 +#endif + +#ifdef _WIN32_WCE +#include <stdio.h> +#endif + +void KProcessPrivate::writeAll(const QByteArray &buf, int fd) +{ +#ifdef Q_OS_WIN +#ifndef _WIN32_WCE + HANDLE h = GetStdHandle(fd); + if (h) { + DWORD wr; + WriteFile(h, buf.data(), buf.size(), &wr, 0); + } +#else + fwrite(buf.data(), 1, buf.size(), (FILE*)fd); +#endif +#else + int off = 0; + do { + int ret = ::write(fd, buf.data() + off, buf.size() - off); + if (ret < 0) { + if (errno != EINTR) + return; + } else { + off += ret; + } + } while (off < buf.size()); +#endif +} + +void KProcessPrivate::forwardStd(KProcess::ProcessChannel good, int fd) +{ + Q_Q(KProcess); + + QProcess::ProcessChannel oc = q->readChannel(); + q->setReadChannel(good); + writeAll(q->readAll(), fd); + q->setReadChannel(oc); +} + +void KProcessPrivate::_k_forwardStdout() +{ +#ifndef _WIN32_WCE + forwardStd(KProcess::StandardOutput, STD_OUTPUT_HANDLE); +#else + forwardStd(KProcess::StandardOutput, (int)stdout); +#endif +} + +void KProcessPrivate::_k_forwardStderr() +{ +#ifndef _WIN32_WCE + forwardStd(KProcess::StandardError, STD_ERROR_HANDLE); +#else + forwardStd(KProcess::StandardError, (int)stderr); +#endif +} + +///////////////////////////// +// public member functions // +///////////////////////////// + +KProcess::KProcess(QObject *parent) : + QProcess(parent), + d_ptr(new KProcessPrivate) +{ + d_ptr->q_ptr = this; + setOutputChannelMode(ForwardedChannels); +} + +KProcess::KProcess(KProcessPrivate *d, QObject *parent) : + QProcess(parent), + d_ptr(d) +{ + d_ptr->q_ptr = this; + setOutputChannelMode(ForwardedChannels); +} + +KProcess::~KProcess() +{ + delete d_ptr; +} + +void KProcess::setOutputChannelMode(OutputChannelMode mode) +{ + Q_D(KProcess); + + d->outputChannelMode = mode; + disconnect(this, SIGNAL(readyReadStandardOutput())); + disconnect(this, SIGNAL(readyReadStandardError())); + switch (mode) { + case OnlyStdoutChannel: + connect(this, SIGNAL(readyReadStandardError()), SLOT(_k_forwardStderr())); + break; + case OnlyStderrChannel: + connect(this, SIGNAL(readyReadStandardOutput()), SLOT(_k_forwardStdout())); + break; + default: + QProcess::setProcessChannelMode((ProcessChannelMode)mode); + return; + } + QProcess::setProcessChannelMode(QProcess::SeparateChannels); +} + +KProcess::OutputChannelMode KProcess::outputChannelMode() const +{ + Q_D(const KProcess); + + return d->outputChannelMode; +} + +void KProcess::setNextOpenMode(QIODevice::OpenMode mode) +{ + Q_D(KProcess); + + d->openMode = mode; +} + +#define DUMMYENV "_KPROCESS_DUMMY_=" + +void KProcess::clearEnvironment() +{ + setEnvironment(QStringList() << QString::fromLatin1(DUMMYENV)); +} + +void KProcess::setEnv(const QString &name, const QString &value, bool overwrite) +{ + QStringList env = environment(); + if (env.isEmpty()) { + env = systemEnvironment(); + env.removeAll(QString::fromLatin1(DUMMYENV)); + } + QString fname(name); + fname.append(QLatin1Char('=')); + for (QStringList::Iterator it = env.begin(); it != env.end(); ++it) + if ((*it).startsWith(fname)) { + if (overwrite) { + *it = fname.append(value); + setEnvironment(env); + } + return; + } + env.append(fname.append(value)); + setEnvironment(env); +} + +void KProcess::unsetEnv(const QString &name) +{ + QStringList env = environment(); + if (env.isEmpty()) { + env = systemEnvironment(); + env.removeAll(QString::fromLatin1(DUMMYENV)); + } + QString fname(name); + fname.append(QLatin1Char('=')); + for (QStringList::Iterator it = env.begin(); it != env.end(); ++it) + if ((*it).startsWith(fname)) { + env.erase(it); + if (env.isEmpty()) + env.append(QString::fromLatin1(DUMMYENV)); + setEnvironment(env); + return; + } +} + +void KProcess::setProgram(const QString &exe, const QStringList &args) +{ + Q_D(KProcess); + + d->prog = exe; + d->args = args; +#ifdef Q_OS_WIN + setNativeArguments(QString()); +#endif +} + +void KProcess::setProgram(const QStringList &argv) +{ + Q_D(KProcess); + + Q_ASSERT( !argv.isEmpty() ); + d->args = argv; + d->prog = d->args.takeFirst(); +#ifdef Q_OS_WIN + setNativeArguments(QString()); +#endif +} + +KProcess &KProcess::operator<<(const QString &arg) +{ + Q_D(KProcess); + + if (d->prog.isEmpty()) + d->prog = arg; + else + d->args << arg; + return *this; +} + +KProcess &KProcess::operator<<(const QStringList &args) +{ + Q_D(KProcess); + + if (d->prog.isEmpty()) + setProgram(args); + else + d->args << args; + return *this; +} + +void KProcess::clearProgram() +{ + Q_D(KProcess); + + d->prog.clear(); + d->args.clear(); +#ifdef Q_OS_WIN + setNativeArguments(QString()); +#endif +} + +/*void KProcess::setShellCommand(const QString &cmd) +{ + Q_D(KProcess); + + KShell::Errors err; + d->args = KShell::splitArgs( + cmd, KShell::AbortOnMeta | KShell::TildeExpand, &err); + if (err == KShell::NoError && !d->args.isEmpty()) { + d->prog = KStandardDirs::findExe(d->args[0]); + if (!d->prog.isEmpty()) { + d->args.removeFirst(); +#ifdef Q_OS_WIN + setNativeArguments(QString()); +#endif + return; + } + } + + d->args.clear(); + +#ifdef Q_OS_UNIX +// #ifdef NON_FREE // ... as they ship non-POSIX /bin/sh +# if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) && !defined(__GNU__) + // If /bin/sh is a symlink, we can be pretty sure that it points to a + // POSIX shell - the original bourne shell is about the only non-POSIX + // shell still in use and it is always installed natively as /bin/sh. + d->prog = QFile::symLinkTarget(QString::fromLatin1("/bin/sh")); + if (d->prog.isEmpty()) { + // Try some known POSIX shells. + d->prog = KStandardDirs::findExe(QString::fromLatin1("ksh")); + if (d->prog.isEmpty()) { + d->prog = KStandardDirs::findExe(QString::fromLatin1("ash")); + if (d->prog.isEmpty()) { + d->prog = KStandardDirs::findExe(QString::fromLatin1("bash")); + if (d->prog.isEmpty()) { + d->prog = KStandardDirs::findExe(QString::fromLatin1("zsh")); + if (d->prog.isEmpty()) + // We're pretty much screwed, to be honest ... + d->prog = QString::fromLatin1("/bin/sh"); + } + } + } + } +# else + d->prog = QString::fromLatin1("/bin/sh"); +# endif + + d->args << QString::fromLatin1("-c") << cmd; +#else // Q_OS_UNIX + // KMacroExpander::expandMacrosShellQuote(), KShell::quoteArg() and + // KShell::joinArgs() may generate these for security reasons. + setEnv(PERCENT_VARIABLE, QLatin1String("%")); + +#ifndef _WIN32_WCE + WCHAR sysdir[MAX_PATH + 1]; + UINT size = GetSystemDirectoryW(sysdir, MAX_PATH + 1); + d->prog = QString::fromUtf16((const ushort *) sysdir, size); + d->prog += QLatin1String("\\cmd.exe"); + setNativeArguments(QLatin1String("/V:OFF /S /C \"") + cmd + QLatin1Char('"')); +#else + d->prog = QLatin1String("\\windows\\cmd.exe"); + setNativeArguments(QLatin1String("/S /C \"") + cmd + QLatin1Char('"')); +#endif +#endif +}*/ + +QStringList KProcess::program() const +{ + Q_D(const KProcess); + + QStringList argv = d->args; + argv.prepend(d->prog); + return argv; +} + +void KProcess::start() +{ + Q_D(KProcess); + + QProcess::start(d->prog, d->args, d->openMode); +} + +int KProcess::execute(int msecs) +{ + start(); + if (!waitForFinished(msecs)) { + kill(); + waitForFinished(-1); + return -2; + } + return (exitStatus() == QProcess::NormalExit) ? exitCode() : -1; +} + +// static +int KProcess::execute(const QString &exe, const QStringList &args, int msecs) +{ + KProcess p; + p.setProgram(exe, args); + return p.execute(msecs); +} + +// static +int KProcess::execute(const QStringList &argv, int msecs) +{ + KProcess p; + p.setProgram(argv); + return p.execute(msecs); +} + +int KProcess::startDetached() +{ + Q_D(KProcess); + + qint64 pid; + if (!QProcess::startDetached(d->prog, d->args, workingDirectory(), &pid)) + return 0; + return (int) pid; +} + +// static +int KProcess::startDetached(const QString &exe, const QStringList &args) +{ + qint64 pid; + if (!QProcess::startDetached(exe, args, QString(), &pid)) + return 0; + return (int) pid; +} + +// static +int KProcess::startDetached(const QStringList &argv) +{ + QStringList args = argv; + QString prog = args.takeFirst(); + return startDetached(prog, args); +} + +int KProcess::pid() const +{ +#ifdef Q_OS_UNIX + return (int) QProcess::pid(); +#else + return QProcess::pid() ? QProcess::pid()->dwProcessId : 0; +#endif +} + +#include "moc_kprocess.cpp" diff --git a/kgpg/kprocess.h b/kgpg/kprocess.h new file mode 100644 index 0000000..6f28e24 --- /dev/null +++ b/kgpg/kprocess.h @@ -0,0 +1,341 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KPROCESS_H +#define KPROCESS_H + +//#include <kdecore_export.h> + +#include <QtCore/QProcess> + +class KProcessPrivate; +//#include "kprocess_p.h" +/** + * \class KProcess kprocess.h <KProcess> + * + * Child process invocation, monitoring and control. + * + * This class extends QProcess by some useful functionality, overrides + * some defaults with saner values and wraps parts of the API into a more + * accessible one. + * This is the preferred way of spawning child processes in KDE; don't + * use QProcess directly. + * + * @author Oswald Buddenhagen <[email protected]> + **/ +class KProcess : public QProcess +{ + Q_OBJECT + Q_DECLARE_PRIVATE(KProcess) + +public: + + /** + * Modes in which the output channels can be opened. + */ + enum OutputChannelMode { + SeparateChannels = QProcess::SeparateChannels, + /**< Standard output and standard error are handled by KProcess + as separate channels */ + MergedChannels = QProcess::MergedChannels, + /**< Standard output and standard error are handled by KProcess + as one channel */ + ForwardedChannels = QProcess::ForwardedChannels, + /**< Both standard output and standard error are forwarded + to the parent process' respective channel */ + OnlyStdoutChannel, + /**< Only standard output is handled; standard error is forwarded */ + OnlyStderrChannel /**< Only standard error is handled; standard output is forwarded */ + }; + + /** + * Constructor + */ + explicit KProcess(QObject *parent = 0); + + /** + * Destructor + */ + virtual ~KProcess(); + + /** + * Set how to handle the output channels of the child process. + * + * The default is ForwardedChannels, which is unlike in QProcess. + * Do not request more than you actually handle, as this output is + * simply lost otherwise. + * + * This function must be called before starting the process. + * + * @param mode the output channel handling mode + */ + void setOutputChannelMode(OutputChannelMode mode); + + /** + * Query how the output channels of the child process are handled. + * + * @return the output channel handling mode + */ + OutputChannelMode outputChannelMode() const; + + /** + * Set the QIODevice open mode the process will be opened in. + * + * This function must be called before starting the process, obviously. + * + * @param mode the open mode. Note that this mode is automatically + * "reduced" according to the channel modes and redirections. + * The default is QIODevice::ReadWrite. + */ + void setNextOpenMode(QIODevice::OpenMode mode); + + /** + * Adds the variable @p name to the process' environment. + * + * This function must be called before starting the process. + * + * @param name the name of the environment variable + * @param value the new value for the environment variable + * @param overwrite if @c false and the environment variable is already + * set, the old value will be preserved + */ + void setEnv(const QString &name, const QString &value, bool overwrite = true); + + /** + * Removes the variable @p name from the process' environment. + * + * This function must be called before starting the process. + * + * @param name the name of the environment variable + */ + void unsetEnv(const QString &name); + + /** + * Empties the process' environment. + * + * Note that LD_LIBRARY_PATH/DYLD_LIBRARY_PATH is automatically added + * on *NIX. + * + * This function must be called before starting the process. + */ + void clearEnvironment(); + + /** + * Set the program and the command line arguments. + * + * This function must be called before starting the process, obviously. + * + * @param exe the program to execute + * @param args the command line arguments for the program, + * one per list element + */ + void setProgram(const QString &exe, const QStringList &args = QStringList()); + + /** + * @overload + * + * @param argv the program to execute and the command line arguments + * for the program, one per list element + */ + void setProgram(const QStringList &argv); + + /** + * Append an element to the command line argument list for this process. + * + * If no executable is set yet, it will be set instead. + * + * For example, doing an "ls -l /usr/local/bin" can be achieved by: + * \code + * KProcess p; + * p << "ls" << "-l" << "/usr/local/bin"; + * ... + * \endcode + * + * This function must be called before starting the process, obviously. + * + * @param arg the argument to add + * @return a reference to this KProcess + */ + KProcess &operator<<(const QString& arg); + + /** + * @overload + * + * @param args the arguments to add + * @return a reference to this KProcess + */ + KProcess &operator<<(const QStringList& args); + + /** + * Clear the program and command line argument list. + */ + void clearProgram(); + + /** + * Set a command to execute through a shell (a POSIX sh on *NIX + * and cmd.exe on Windows). + * + * Using this for anything but user-supplied commands is usually a bad + * idea, as the command's syntax depends on the platform. + * Redirections including pipes, etc. are better handled by the + * respective functions provided by QProcess. + * + * If KProcess determines that the command does not really need a + * shell, it will trasparently execute it without one for performance + * reasons. + * + * This function must be called before starting the process, obviously. + * + * @param cmd the command to execute through a shell. + * The caller must make sure that all filenames etc. are properly + * quoted when passed as argument. Failure to do so often results in + * serious security holes. See KShell::quoteArg(). + */ + void setShellCommand(const QString &cmd); + + /** + * Obtain the currently set program and arguments. + * + * @return a list, the first element being the program, the remaining ones + * being command line arguments to the program. + */ + QStringList program() const; + + /** + * Start the process. + * + * @see QProcess::start(const QString &, const QStringList &, OpenMode) + */ + void start(); + + /** + * Start the process, wait for it to finish, and return the exit code. + * + * This method is roughly equivalent to the sequence: + * <code> + * start(); + * waitForFinished(msecs); + * return exitCode(); + * </code> + * + * Unlike the other execute() variants this method is not static, + * so the process can be parametrized properly and talked to. + * + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + int execute(int msecs = -1); + + /** + * @overload + * + * @param exe the program to execute + * @param args the command line arguments for the program, + * one per list element + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + static int execute(const QString &exe, const QStringList &args = QStringList(), int msecs = -1); + + /** + * @overload + * + * @param argv the program to execute and the command line arguments + * for the program, one per list element + * @param msecs time to wait for process to exit before killing it + * @return -2 if the process could not be started, -1 if it crashed, + * otherwise its exit code + */ + static int execute(const QStringList &argv, int msecs = -1); + + /** + * Start the process and detach from it. See QProcess::startDetached() + * for details. + * + * Unlike the other startDetached() variants this method is not static, + * so the process can be parametrized properly. + * @note Currently, only the setProgram()/setShellCommand() and + * setWorkingDirectory() parametrizations are supported. + * + * The KProcess object may be re-used immediately after calling this + * function. + * + * @return the PID of the started process or 0 on error + */ + int startDetached(); + + /** + * @overload + * + * @param exe the program to start + * @param args the command line arguments for the program, + * one per list element + * @return the PID of the started process or 0 on error + */ + static int startDetached(const QString &exe, const QStringList &args = QStringList()); + + /** + * @overload + * + * @param argv the program to start and the command line arguments + * for the program, one per list element + * @return the PID of the started process or 0 on error + */ + static int startDetached(const QStringList &argv); + + /** + * Obtain the process' ID as known to the system. + * + * Unlike with QProcess::pid(), this is a real PID also on Windows. + * + * This function can be called only while the process is running. + * It cannot be applied to detached processes. + * + * @return the process ID + */ + int pid() const; + +protected: + /** + * @internal + */ + KProcess(KProcessPrivate *d, QObject *parent); + + /** + * @internal + */ + KProcessPrivate * const d_ptr; + +private: + // hide those + using QProcess::setReadChannelMode; + using QProcess::readChannelMode; + using QProcess::setProcessChannelMode; + using QProcess::processChannelMode; + + Q_PRIVATE_SLOT(d_func(), void _k_forwardStdout()) + Q_PRIVATE_SLOT(d_func(), void _k_forwardStderr()) +}; + +#endif + diff --git a/kgpg/kprocess_p.h b/kgpg/kprocess_p.h new file mode 100644 index 0000000..11d3356 --- /dev/null +++ b/kgpg/kprocess_p.h @@ -0,0 +1,50 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2007 Oswald Buddenhagen <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KPROCESS_P_H +#define KPROCESS_P_H + +#include "kprocess.h" + +//class KProcess; + +class KProcessPrivate { + Q_DECLARE_PUBLIC(KProcess) +protected: + KProcessPrivate() : + openMode(QIODevice::ReadWrite) + { + } + void writeAll(const QByteArray &buf, int fd); + void forwardStd(KProcess::ProcessChannel good, int fd); + void _k_forwardStdout(); + void _k_forwardStderr(); + + QString prog; + QStringList args; + KProcess::OutputChannelMode outputChannelMode; + QIODevice::OpenMode openMode; + + KProcess *q_ptr; +}; + + +#endif |