From dadc34655c3ab961b0b0b94a10eaaba710f0b5e8 Mon Sep 17 00:00:00 2001 From: tpearson Date: Mon, 4 Jul 2011 22:38:03 +0000 Subject: Added kmymoney git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kmymoney@1239792 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- libkgpgfile/Makefile.am | 12 + libkgpgfile/kgpgfile.cpp | 698 +++++++++++++++++++++++++++++++++++++++++++++++ libkgpgfile/kgpgfile.h | 185 +++++++++++++ 3 files changed, 895 insertions(+) create mode 100644 libkgpgfile/Makefile.am create mode 100644 libkgpgfile/kgpgfile.cpp create mode 100644 libkgpgfile/kgpgfile.h (limited to 'libkgpgfile') diff --git a/libkgpgfile/Makefile.am b/libkgpgfile/Makefile.am new file mode 100644 index 0000000..885750c --- /dev/null +++ b/libkgpgfile/Makefile.am @@ -0,0 +1,12 @@ +KDE_OPTIONS = noautodist + +INCLUDES = $(all_includes) -I$(top_srcdir) +METASOURCES = AUTO + +noinst_LTLIBRARIES = libkgpgfile.la +libkgpgfile_la_SOURCES = kgpgfile.cpp +libkgpgfile_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) +#libkgpgfile_la_LIBADD = $(top_builddir)/libkdepim/libkdepim.la + +instdir=$(includedir)/kmymoney +inst_HEADERS = kgpgfile.h diff --git a/libkgpgfile/kgpgfile.cpp b/libkgpgfile/kgpgfile.cpp new file mode 100644 index 0000000..a7fc338 --- /dev/null +++ b/libkgpgfile/kgpgfile.cpp @@ -0,0 +1,698 @@ +/*************************************************************************** + kgpgfile.cpp + ------------------- + begin : Fri Jan 23 2004 + copyright : (C) 2004,2005 by Thomas Baumgart + email : thb@net-bembel.de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifdef HAVE_CONFIG +#include +#endif + +#include "kdecompat.h" + +// ---------------------------------------------------------------------------- +// QT Includes + +#include +#include +#include + +#if QT_IS_VERSION(3,3,0) +#include +#endif + + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "kgpgfile.h" + +#if 0 +class KGPGFileFactory : public KLibFactory +{ +public: + KGPGFileFactory() : KLibFactory() {} + ~KGPGFileFactory(){} + QObject *createObject( QObject *, const char *, const char*, const QStringList & ) + { + return new KGPGFile; + } +}; + +extern "C" { + void *init_libkgpgfile() + { + return new KGPGFileFactory; + } +} +#endif + +KGPGFile::KGPGFile(const QString& fn, const QString& homedir, const QString& options) : + m_options(options), + m_homedir(homedir), + m_readRemain(0), + m_needExitLoop(false) +{ + setName(fn); + m_exitStatus = -2; + m_comment = "created by KGPGFile"; + // qDebug("ungetchbuffer %d", m_ungetchBuffer.length()); +} + +KGPGFile::~KGPGFile() +{ + close(); +} + +void KGPGFile::init(void) +{ + setFlags(IO_Sequential); + setStatus(IO_Ok); + setState(0); +} + +void KGPGFile::setName(const QString& fn) +{ + m_fn = fn; + if(fn[0] == '~') { + m_fn = QDir::homeDirPath()+fn.mid(1); + + } else if(QDir::isRelativePath(m_fn)) { + QDir dir(fn); + m_fn = dir.absPath(); + } + // qDebug("setName: '%s'", m_fn.data()); +} + +void KGPGFile::flush(void) +{ + // no functionality +} + +void KGPGFile::addRecipient(const QCString& recipient) +{ + m_recipient << recipient; +} + +bool KGPGFile::open(int mode) +{ + return open(mode, QString(), false); +} + +bool KGPGFile::open(int mode, const QString& cmdArgs, bool skipPasswd) +{ + bool useOwnPassphrase = (getenv("GPG_AGENT_INFO") == 0); + + // qDebug("KGPGFile::open(%d)", mode); + m_errmsg.resize(1); + if(isOpen()) { + // qDebug("File already open"); + return false; + } + + // qDebug("check filename empty"); + if(m_fn.isEmpty()) + return false; + + // qDebug("setup file structures"); + init(); + setMode(mode); + + // qDebug("check valid access mode"); + if(!(isReadable() || isWritable())) + return false; + + if(isWritable()) { + // qDebug("check recipient count"); + if(m_recipient.count() == 0) + return false; + // qDebug("check access rights"); + if(!checkAccess(m_fn, W_OK)) + return false; + } + + QStringList args; + if(cmdArgs.isEmpty()) { + args << "--homedir" << QString("\"%1\"").arg(m_homedir) + << "-q" + << "--batch"; + + if(isWritable()) { + args << "-ea" + << "-z" << "6" + << "--comment" << QString("\"%1\"").arg(m_comment) + << "--trust-model=always" + << "-o" << QString("\"%1\"").arg(m_fn); + QValueList::Iterator it; + for(it = m_recipient.begin(); it != m_recipient.end(); ++it) + args << "-r" << QString("\"%1\"").arg(*it); + + // some versions of GPG had trouble to replace a file + // so we delete it first + QFile::remove(m_fn); + } else { + args << "-da"; + if(useOwnPassphrase) + args << "--passphrase-fd" << "0"; + else + args << "--use-agent"; + args << "--no-default-recipient" << QString("\"%1\"").arg(m_fn); + } + } else { + args = QStringList::split(" ", cmdArgs); + } + + QCString pwd; + if(isReadable() && useOwnPassphrase && !skipPasswd) { + KPasswordDialog dlg(KPasswordDialog::Password,false,0); + dlg.setPrompt(i18n("Enter passphrase")); + dlg.addLine(i18n("File"), m_fn); + dlg.adjustSize(); + if (dlg.exec() == QDialog::Rejected) + return false; + pwd = QCString(dlg.password()); + } + + // qDebug("starting GPG process"); + if(!startProcess(args)) + return false; + + // qDebug("check GPG process running"); + if(!m_process) { + // if the process is not present anymore, we have to check + // if it was a read operation and we might already have data + // and the process finished normally. In that case, we + // just continue. + if(isReadable()) { + if(m_ungetchBuffer.isEmpty()) + return false; + } else + return false; + } + + if(isReadable() && useOwnPassphrase && !skipPasswd) { + // qDebug("Passphrase is '%s'", pwd.data()); + if(_writeBlock(pwd.data(), pwd.length()) == -1) { + // qDebug("Sending passphrase failed"); + return false; + } + m_process->closeStdin(); + } + + setState( IO_Open ); + ioIndex = 0; + // qDebug("File open"); + return true; +} + +bool KGPGFile::startProcess(const QStringList& args) +{ + // now start the KProcess with GPG + m_process = new KShellProcess(); + *m_process << "gpg"; + *m_process << args; + + // QString arglist = args.join(":"); + // qDebug("gpg '%s'", arglist.data()); + + connect(m_process, SIGNAL(processExited(KProcess *)), + this, SLOT(slotGPGExited(KProcess *))); + + connect(m_process, SIGNAL(receivedStdout(KProcess*, char*, int)), + this, SLOT(slotDataFromGPG(KProcess*, char*, int))); + + connect(m_process, SIGNAL(receivedStderr(KProcess*, char*, int)), + this, SLOT(slotErrorFromGPG(KProcess*, char*, int))); + + connect(m_process, SIGNAL(wroteStdin(KProcess *)), + this, SLOT(slotSendDataToGPG(KProcess *))); + + if(!m_process->start(KProcess::NotifyOnExit, (KProcess::Communication)(KProcess::Stdin|KProcess::Stdout|KProcess::Stderr))) { + // qDebug("m_process->start failed"); + delete m_process; + m_process = 0; + return false; + } + + // let the process settle and see if it starts and survives ;-) + kapp->processEvents(100); + return true; +} + +void KGPGFile::close(void) +{ + // qDebug("KGPGFile::close()"); + if(!isOpen()) { + // qDebug("File not open"); + return; + } + + // finish the KProcess and clean up things + if(m_process) { + if(isWritable()) { + // qDebug("Finish writing"); + if(m_process->isRunning()) { + m_process->closeStdin(); + // now wait for GPG to finish + m_needExitLoop = true; + qApp->enter_loop(); + } else + m_process->kill(); + + } else if(isReadable()) { + // qDebug("Finish reading"); + if(m_process->isRunning()) { + m_process->closeStdout(); + // now wait for GPG to finish + m_needExitLoop = true; + qApp->enter_loop(); + } else + m_process->kill(); + } + } + m_ungetchBuffer = QCString(); + setState(0); + m_recipient.clear(); + // qDebug("File closed"); +} + +int KGPGFile::getch(void) +{ + if(!isOpen()) + return EOF; + if(!isReadable()) + return EOF; + + int ch; + + if(!m_ungetchBuffer.isEmpty()) { + ch = (m_ungetchBuffer)[0] & 0xff; + m_ungetchBuffer.remove(0, 1); + + } else { + char buf[1]; + ch = (readBlock(buf,1) == 1) ? (buf[0] & 0xff) : EOF; + } + + // qDebug("getch returns 0x%02X", ch); + return ch; +} + +int KGPGFile::ungetch(int ch) +{ + if(!isOpen()) + return EOF; + if(!isReadable()) + return EOF; + + if(ch != EOF) { + // qDebug("store 0x%02X in ungetchbuffer", ch & 0xff); + m_ungetchBuffer.insert(0, ch & 0xff); + } + + return ch; +} + +int KGPGFile::putch(int c) +{ + char buf[1]; + buf[0] = c; + if(writeBlock(buf, 1) != EOF) + return c; + return EOF; +} + +Q_LONG KGPGFile::writeBlock(const char *data, Q_ULONG maxlen) +{ + if(!isOpen()) + return EOF; + if(!isWritable()) + return EOF; + + return _writeBlock(data, maxlen); +} + +Q_LONG KGPGFile::_writeBlock(const char *data, Q_ULONG maxlen) +{ + if(!m_process) + return EOF; + if(!m_process->isRunning()) + return EOF; + + if(m_process->writeStdin(data, maxlen)) { + // wait until the data has been written + m_needExitLoop = true; + qApp->enter_loop(); + if(!m_process) + return EOF; + return maxlen; + + } else + return EOF; +} + +Q_LONG KGPGFile::readBlock(char *data, Q_ULONG maxlen) +{ + // char *oridata = data; + if(maxlen == 0) + return 0; + + if(!isOpen()) + return EOF; + if(!isReadable()) + return EOF; + + Q_ULONG nread = 0; + if(!m_ungetchBuffer.isEmpty()) { + unsigned l = m_ungetchBuffer.length(); + if(maxlen < l) + l = maxlen; + memcpy(data, m_ungetchBuffer, l); + nread += l; + data = &data[l]; + m_ungetchBuffer.remove(0, l); + + if(!m_process) { + // qDebug("read %d bytes from unget buffer", nread); + // dumpBuffer(oridata, nread); + return nread; + } + } + + // check for EOF + if(!m_process) { + // qDebug("EOF (no process)"); + return EOF; + } + + m_readRemain = maxlen - nread; + m_ptrRemain = data; + if(m_readRemain) { + m_process->resume(); + m_needExitLoop = true; + qApp->enter_loop(); + } + // if nothing has been read (maxlen-m_readRemain == 0) then we assume EOF + if((maxlen - m_readRemain) == 0) { + // qDebug("EOF (nothing read)"); + return EOF; + } + // qDebug("return %d bytes", maxlen - m_readRemain); + // dumpBuffer(oridata, maxlen - m_readRemain); + return maxlen - m_readRemain; +} + +QByteArray KGPGFile::readAll(void) +{ + // use a larger blocksize than in the QIODevice version + const int blocksize = 8192; + int nread = 0; + QByteArray ba; + while ( !atEnd() ) { + ba.resize( nread + blocksize ); + int r = readBlock( ba.data()+nread, blocksize ); + if ( r < 0 ) + return QByteArray(); + nread += r; + } + ba.resize( nread ); + return ba; +} + +void KGPGFile::slotGPGExited(KProcess* ) +{ + // qDebug("GPG finished"); + if(m_process) { + if(m_process->normalExit()) { + m_exitStatus = m_process->exitStatus(); + if(m_exitStatus != 0) + setStatus(IO_UnspecifiedError); + } else { + m_exitStatus = -1; + } + delete m_process; + m_process = 0; + } + + if(m_needExitLoop) { + m_needExitLoop = false; + qApp->exit_loop(); + } +} + +void KGPGFile::slotDataFromGPG(KProcess* proc, char* buf, int len) +{ + // qDebug("Received %d bytes on stdout", len); + + // copy current buffer to application + int copylen; + copylen = m_readRemain < len ? m_readRemain : len; + if(copylen != 0) { + memcpy(m_ptrRemain, buf, copylen); + m_ptrRemain += copylen; + buf += copylen; + m_readRemain -= copylen; + len -= copylen; + } + + // store rest of buffer in ungetch buffer + while(len--) { + m_ungetchBuffer += *buf++; + } + + // if we have all the data the app requested, we can safely suspend + if(m_readRemain == 0) { + proc->suspend(); + // wake up the recipient + if(m_needExitLoop) { + m_needExitLoop = false; + qApp->exit_loop(); + } + } + // qDebug("end slotDataFromGPG"); +} + +void KGPGFile::slotErrorFromGPG(KProcess *, char *buf, int len) +{ + // qDebug("Received %d bytes on stderr", len); + QCString msg; + msg.setRawData(buf, len); + m_errmsg += msg; + msg.resetRawData(buf, len); +} + +void KGPGFile::slotSendDataToGPG(KProcess *) +{ + // qDebug("wrote stdin"); + if(m_needExitLoop) { + m_needExitLoop = false; + qApp->exit_loop(); + } +} + +bool KGPGFile::GPGAvailable(void) +{ + QString output; + char buffer[1024]; + Q_LONG len; + + KGPGFile file; + file.open(IO_ReadOnly, "--version", true); + while((len = file.readBlock(buffer, sizeof(buffer)-1)) != EOF) { + buffer[len] = 0; + output += QString(buffer); + } + file.close(); + return !output.isEmpty(); +} + +bool KGPGFile::keyAvailable(const QString& name) +{ + QStringList list; + publicKeyList(list, name); + return !list.isEmpty(); +} + +void KGPGFile::publicKeyList(QStringList& list, const QString& pattern) +{ + QMap map; + QString output; + char buffer[1024]; + Q_LONG len; + + list.clear(); + KGPGFile file; + QString args("--list-keys --with-colons"); + if(!pattern.isEmpty()) + args += QString(" %1").arg(pattern); + file.open(IO_ReadOnly, args, true); + while((len = file.readBlock(buffer, sizeof(buffer)-1)) != EOF) { + buffer[len] = 0; + output += QString(buffer); + } + file.close(); + + // now parse the data. it looks like: + /* + tru::0:1210616414:1214841688:3:1:5 + pub:u:1024:17:9C59DB40B75DD3BA:2001-06-23:::u:Thomas Baumgart ::scaESCA: + uid:u::::2001-11-29::63493BF182C494227E198FE5DA00ACDF63961AFB::Thomas Baumgart : + uid:u::::2001-11-29::00A393737BC120C98A6402B921599F6D72058DD8::Thomas Baumgart : + sub:u:1024:16:85968A70D1F83C2B:2001-06-23::::::e: + */ + QStringList lines = QStringList::split("\n", output); + QStringList::iterator it; + QString currentKey; + for(it = lines.begin(); it != lines.end(); ++it) { + // qDebug("Parsing: '%s'", (*it).data()); + QStringList fields = QStringList::split(":", (*it), true); + QString val; + if(fields[0] == "pub") { + QDate expiration = QDate::fromString(fields[6], Qt::ISODate); + if(expiration > QDate::currentDate()) { + currentKey = fields[4]; + val = QString("%1:%2").arg(currentKey).arg(fields[9]); + map[val] = val; + } else { + qDebug("'%s' is expired", fields[9].data()); + } + } else if(fields[0] == "uid") { + val = QString("%1:%2").arg(currentKey).arg(fields[9]); + map[val] = val; + } + } + list = map.values(); +} + + +void KGPGFile::secretKeyList(QStringList& list) +{ + QString output; + char buffer[1024]; + Q_LONG len; + + list.clear(); + KGPGFile file; + file.open(IO_ReadOnly, "--list-secret-keys --with-colons", true); + while((len = file.readBlock(buffer, sizeof(buffer)-1)) != EOF) { + buffer[len] = 0; + output += QString(buffer); + } + file.close(); + + // now parse the data. it looks like: + /* + sec::1024:17:9C59DB40B75DD3BA:2001-06-23::::Thomas Baumgart ::: + uid:::::::::Thomas Baumgart : + ssb::1024:16:85968A70D1F83C2B:2001-06-23::::::: + sec::1024:17:59B0F826D2B08440:2005-01-03:2010-01-02:::KMyMoney emergency data recovery ::: + ssb::2048:16:B3DABDC48C0FE2F3:2005-01-03::::::: + */ + QStringList lines = QStringList::split("\n", output); + QStringList::iterator it; + QString currentKey; + for(it = lines.begin(); it != lines.end(); ++it) { + // qDebug("Parsing: '%s'", (*it).data()); + QStringList fields = QStringList::split(":", (*it), true); + if(fields[0] == "sec") { + currentKey = fields[4]; + list << QString("%1:%2").arg(currentKey).arg(fields[9]); + } else if(fields[0] == "uid") { + list << QString("%1:%2").arg(currentKey).arg(fields[9]); + } + } +} + +/* +// key generation + char * gpg_input = + g_strdup_printf("Key-Type: DSA\n" + "Key-Length: 1024\n" + "Subkey-Type: ELG-E\n" + "Subkey-Length: 1024\n" + "Name-Real: %s\n" + "Name-Comment: %s\n" + "Name-Email: %s\n" + "Passphrase: %s\n" + "%%commit\n", + username ? username : "", + idstring ? idstring : "", + email ? email : "", + passphrase ? passphrase : ""); + char * argv [] = + { "gpg", + "--batch", + "-q", + "--gen-key", + "--keyring", + "~/.gnucash/gnucash.pub", + "--secret-keyring", + "~/.gnucash/gnucash.sec", + NULL + }; + + char * retval = gnc_gpg_transform(gpg_input, strlen(gpg_input), NULL, argv); + g_free(gpg_input); + return retval; + + */ + +#if KMM_DEBUG +void KGPGFile::dumpBuffer(char *s, int len) const +{ + QString data, tmp, chars; + unsigned long addr = 0x0; + + while(1) { + if(addr && !(addr & 0x0f)) { + qDebug("%s %s", data.data(), chars.data()); + if(!len) + break; + } + if(!(addr & 0x0f)) { + data = tmp.sprintf("%08lX", addr); + chars = QString(); + } + if(!(addr & 0x03)) { + data += " "; + } + ++addr; + + if(!len) { + data += " "; + chars += " "; + continue; + } + + data += tmp.sprintf("%02X", *s & 0xff); + if(*s >= ' ' && *s <= '~') + chars += *s & 0xff; + else + chars += '.'; + ++s; + --len; + } +} +#endif + +#include "kgpgfile.moc" diff --git a/libkgpgfile/kgpgfile.h b/libkgpgfile/kgpgfile.h new file mode 100644 index 0000000..ba5c7d2 --- /dev/null +++ b/libkgpgfile/kgpgfile.h @@ -0,0 +1,185 @@ +/*************************************************************************** + kgpgfile.h + ------------------- + begin : Fri Jan 23 2004 + copyright : (C) 2004,2005 by Thomas Baumgart + email : thb@net-bembel.de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 KGPGFILE_H +#define KGPGFILE_H + +#include +#include +#include +#include + +/** + * @author Thomas Baumgart + */ + +class KShellProcess; +class KProcess; + +/** + * A class for reading and writing data to/from an + * encrypted e.g. file. + * + * This class presents a QFile based object to the application + * but reads/writes data from/to the file through an instance of GPG. + * + * @code + * + * +------------------+ write +-----------+ stdin +-------+ +--------+ + * | |--------->|\ |---------->| |---->| | + * | Application code | read | QFile | stdout | GPG | | File | + * | |<---------|/ |<----------| |<----| | + * +------------------+ | KGPGFile | +-------+ +--------+ + * | control| | + * +-------------->| | + * +-----------+ + * @endcode + * + * The @p write interface contains methods as writeBlock() and putch(), the @p read + * interface the methods readBlock(), getch() and ungetch(). The @p control interface + * special methods only available with KGPGFile e.g. addRecipient(), keyAvailable() and + * GPGAvailable(). Other, more general methods such as open(), close() and flush() are + * not shown in the above picture. + */ +class KGPGFile : public QObject, public QFile +{ + Q_OBJECT + +public: + KGPGFile(const QString& fname = "", + const QString& homedir = "~/.gnupg", + const QString& options = ""); + + ~KGPGFile(); + + virtual bool open(int mode); + virtual void close(void); + virtual void flush(void); + + virtual Offset size(void) const { return 0; }; + + virtual Q_LONG readBlock(char *data, Q_ULONG maxlen); + virtual Q_LONG writeBlock(const char *data, Q_ULONG maxlen); + virtual QByteArray readAll(void); + + virtual int getch(void); + virtual int putch(int c); + virtual int ungetch(int c); + + /** + * Adds a recipient for whom the file should be encrypted. + * At least one recipient must be specified using this + * method before the file can be written to. @p recipient + * must contain a valid name as defined by GPG. See the + * GPG documentation for more information. + * + * @param recipient recipients identification (e.g. e-mail address) + */ + void addRecipient(const QCString& recipient); + + /** + * sets the name of the file to @p fn. This method must be + * called prior to open(). + */ + void setName(const QString& fn); + void setComment(const QString& txt); + + const QCString errmsg(void) const { return m_errmsg; }; + int exitStatus(void) const { return m_exitStatus; }; + + /** + * Checks whether GPG is available or not + * + * @retval true GPG can be started and returns a version number + * @retval false GPG is not available + */ + static bool GPGAvailable(void); + + /** + * Checks whether a key for a given user-id @p name exists. + * + * @param name the user-id to be checked. @p name can be + * any reference understood by GPG (e.g. an e-mail + * address or a key-id) + * @retval true key for user-id @p name was found + * @retval false key for user-id @p not available + */ + static bool keyAvailable(const QString& name); + + /** + * This function returns a list of the secret keys contained + * in the keyring. Each list item is devided into two fields + * separated by a colon (':'). The first field contains the + * key id, the second field the name. The list may contain + * multiple entries with the same key-id and different names. + * + * Example of an entry in the list: + * + * "9C59DB40B75DD3BA:Thomas Baumgart " + */ + static void secretKeyList(QStringList& list); + + /** + * This function returns a list of the public keys contained + * in the keyring. Each list item is devided into two fields + * separated by a colon (':'). The first field contains the + * key id, the second field the name. The list may contain + * multiple entries with the same key-id and different names. + * + * Example of an entry in the list: + * + * "9C59DB40B75DD3BA:Thomas Baumgart " + */ + static void publicKeyList(QStringList& list, const QString& pattern = QString()); + +#ifdef KMM_DEBUG + void dumpUngetBuffer(void); + void dumpBuffer(char *s, int len) const; +#endif + +protected slots: + void slotGPGExited(KProcess *); + void slotDataFromGPG(KProcess *, char *buf, int len); + void slotErrorFromGPG(KProcess *, char *buf, int len); + void slotSendDataToGPG(KProcess *); + +private: + void init(void); + bool startProcess(const QStringList& args); + Q_LONG _writeBlock(const char *data, Q_ULONG maxlen); + bool open(int mode, const QString&, bool skipPasswd); + +private: + QString m_fn; + QString m_pubring; + QString m_secring; + QString m_options; + QString m_comment; + QString m_homedir; + + KShellProcess* m_process; + + QValueList m_recipient; + QCString m_ungetchBuffer; + QCString m_errmsg; + int m_exitStatus; + Q_LONG m_readRemain; + char* m_ptrRemain; + bool m_needExitLoop; +}; + +#endif -- cgit v1.2.3