qt: Fix IODeviceDataProvider with Process
[gpgme.git] / lang / qt / src / dataprovider.cpp
1 /* dataprovider.cpp
2    Copyright (C) 2004 Klarävdalens Datakonsult AB
3     Copyright (c) 2016 by Bundesamt für Sicherheit in der Informationstechnik
4     Software engineering by Intevation GmbH
5
6    This file is part of QGPGME.
7
8    QGPGME is free software; you can redistribute it and/or modify it
9    under the terms of the GNU Library General Public License as published
10    by the Free Software Foundation; either version 2 of the License, or
11    (at your option) any later version.
12
13    QGPGME is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU Library General Public License for more details.
17
18    You should have received a copy of the GNU Library General Public License
19    along with QGPGME; see the file COPYING.LIB.  If not, write to the
20    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21    Boston, MA 02110-1301, USA. */
22
23 // -*- c++ -*-
24
25 #ifdef HAVE_CONFIG_H
26  #include "config.h"
27 #endif
28
29 #include <dataprovider.h>
30
31 #include <error.h>
32
33 #include <QIODevice>
34 #include <QProcess>
35
36 #include <cstdio>
37 #include <cstring>
38 #include <cassert>
39
40 using namespace QGpgME;
41 using namespace GpgME;
42
43 //
44 //
45 // QByteArrayDataProvider
46 //
47 //
48
49 static bool resizeAndInit(QByteArray &ba, size_t newSize)
50 {
51     const size_t oldSize = ba.size();
52     ba.resize(newSize);
53     const bool ok = (newSize == static_cast<size_t>(ba.size()));
54     if (ok) {
55         memset(ba.data() + oldSize, 0, newSize - oldSize);
56     }
57     return ok;
58 }
59
60 QByteArrayDataProvider::QByteArrayDataProvider()
61     : GpgME::DataProvider(), mOff(0) {}
62
63 QByteArrayDataProvider::QByteArrayDataProvider(const QByteArray &initialData)
64     : GpgME::DataProvider(), mArray(initialData), mOff(0) {}
65
66 QByteArrayDataProvider::~QByteArrayDataProvider() {}
67
68 ssize_t QByteArrayDataProvider::read(void *buffer, size_t bufSize)
69 {
70 #ifndef NDEBUG
71     //qDebug( "QByteArrayDataProvider::read( %p, %d )", buffer, bufSize );
72 #endif
73     if (bufSize == 0) {
74         return 0;
75     }
76     if (!buffer) {
77         Error::setSystemError(GPG_ERR_EINVAL);
78         return -1;
79     }
80     if (mOff >= mArray.size()) {
81         return 0; // EOF
82     }
83     size_t amount = qMin(bufSize, static_cast<size_t>(mArray.size() - mOff));
84     assert(amount > 0);
85     memcpy(buffer, mArray.data() + mOff, amount);
86     mOff += amount;
87     return amount;
88 }
89
90 ssize_t QByteArrayDataProvider::write(const void *buffer, size_t bufSize)
91 {
92 #ifndef NDEBUG
93     //qDebug( "QByteArrayDataProvider::write( %p, %lu )", buffer, static_cast<unsigned long>( bufSize ) );
94 #endif
95     if (bufSize == 0) {
96         return 0;
97     }
98     if (!buffer) {
99         Error::setSystemError(GPG_ERR_EINVAL);
100         return -1;
101     }
102     if (mOff >= mArray.size()) {
103         resizeAndInit(mArray, mOff + bufSize);
104     }
105     if (mOff >= mArray.size()) {
106         Error::setSystemError(GPG_ERR_EIO);
107         return -1;
108     }
109     assert(bufSize <= static_cast<size_t>(mArray.size()) - mOff);
110     memcpy(mArray.data() + mOff, buffer, bufSize);
111     mOff += bufSize;
112     return bufSize;
113 }
114
115 off_t QByteArrayDataProvider::seek(off_t offset, int whence)
116 {
117 #ifndef NDEBUG
118     //qDebug( "QByteArrayDataProvider::seek( %d, %d )", int(offset), whence );
119 #endif
120     int newOffset = mOff;
121     switch (whence) {
122     case SEEK_SET:
123         newOffset = offset;
124         break;
125     case SEEK_CUR:
126         newOffset += offset;
127         break;
128     case SEEK_END:
129         newOffset = mArray.size() + offset;
130         break;
131     default:
132         Error::setSystemError(GPG_ERR_EINVAL);
133         return (off_t) - 1;
134     }
135     return mOff = newOffset;
136 }
137
138 void QByteArrayDataProvider::release()
139 {
140 #ifndef NDEBUG
141     //qDebug( "QByteArrayDataProvider::release()" );
142 #endif
143     mArray = QByteArray();
144 }
145
146 //
147 //
148 // QIODeviceDataProvider
149 //
150 //
151
152 QIODeviceDataProvider::QIODeviceDataProvider(const std::shared_ptr<QIODevice> &io)
153     : GpgME::DataProvider(),
154       mIO(io),
155       mErrorOccurred(false),
156       mHaveQProcess(qobject_cast<QProcess *>(io.get()))
157 {
158     assert(mIO);
159 }
160
161 QIODeviceDataProvider::~QIODeviceDataProvider() {}
162
163 bool QIODeviceDataProvider::isSupported(Operation op) const
164 {
165     const QProcess *const proc = qobject_cast<QProcess *>(mIO.get());
166     bool canRead = true;
167     if (proc) {
168         canRead = proc->readChannel() == QProcess::StandardOutput;
169     }
170
171     switch (op) {
172     case Read:    return mIO->isReadable() && canRead;
173     case Write:   return mIO->isWritable();
174     case Seek:    return !mIO->isSequential();
175     case Release: return true;
176     default:      return false;
177     }
178 }
179
180 static qint64 blocking_read(const std::shared_ptr<QIODevice> &io, char *buffer, qint64 maxSize)
181 {
182     while (!io->bytesAvailable()) {
183         if (!io->waitForReadyRead(-1)) {
184             if (const QProcess *const p = qobject_cast<QProcess *>(io.get())) {
185                 if (p->error() == QProcess::UnknownError &&
186                         p->exitStatus() == QProcess::NormalExit &&
187                         p->exitCode() == 0) {
188                     if (io->atEnd()) {
189                         // EOF
190                         return 0;
191                     } // continue reading even if process ended to ensure
192                       // everything is read.
193                 } else {
194                     Error::setSystemError(GPG_ERR_EIO);
195                     return -1;
196                 }
197             } else {
198                 return 0; // assume EOF (loses error cases :/ )
199             }
200         }
201     }
202     return io->read(buffer, maxSize);
203 }
204
205 ssize_t QIODeviceDataProvider::read(void *buffer, size_t bufSize)
206 {
207 #ifndef NDEBUG
208     //qDebug( "QIODeviceDataProvider::read( %p, %lu )", buffer, bufSize );
209 #endif
210     if (bufSize == 0) {
211         return 0;
212     }
213     if (!buffer) {
214         Error::setSystemError(GPG_ERR_EINVAL);
215         return -1;
216     }
217     const qint64 numRead = mHaveQProcess
218                            ? blocking_read(mIO, static_cast<char *>(buffer), bufSize)
219                            : mIO->read(static_cast<char *>(buffer), bufSize);
220
221     //workaround: some QIODevices (known example: QProcess) might not return 0 (EOF), but immediately -1 when finished. If no
222     //errno is set, gpgme doesn't detect the error and loops forever. So return 0 on the very first -1 in case errno is 0
223
224     ssize_t rc = numRead;
225     if (numRead < 0 && !Error::hasSystemError()) {
226         if (mErrorOccurred) {
227             Error::setSystemError(GPG_ERR_EIO);
228         } else {
229             rc = 0;
230         }
231     }
232     if (numRead < 0) {
233         mErrorOccurred = true;
234     }
235     return rc;
236 }
237
238 ssize_t QIODeviceDataProvider::write(const void *buffer, size_t bufSize)
239 {
240 #ifndef NDEBUG
241     //qDebug( "QIODeviceDataProvider::write( %p, %lu )", buffer, static_cast<unsigned long>( bufSize ) );
242 #endif
243     if (bufSize == 0) {
244         return 0;
245     }
246     if (!buffer) {
247         Error::setSystemError(GPG_ERR_EINVAL);
248         return -1;
249     }
250
251     return mIO->write(static_cast<const char *>(buffer), bufSize);
252 }
253
254 off_t QIODeviceDataProvider::seek(off_t offset, int whence)
255 {
256 #ifndef NDEBUG
257     //qDebug( "QIODeviceDataProvider::seek( %d, %d )", int(offset), whence );
258 #endif
259     if (mIO->isSequential()) {
260         Error::setSystemError(GPG_ERR_ESPIPE);
261         return (off_t) - 1;
262     }
263     qint64 newOffset = mIO->pos();
264     switch (whence) {
265     case SEEK_SET:
266         newOffset = offset;
267         break;
268     case SEEK_CUR:
269         newOffset += offset;
270         break;
271     case SEEK_END:
272         newOffset = mIO->size() + offset;
273         break;
274     default:
275         Error::setSystemError(GPG_ERR_EINVAL);
276         return (off_t) - 1;
277     }
278     if (!mIO->seek(newOffset)) {
279         Error::setSystemError(GPG_ERR_EINVAL);
280         return (off_t) - 1;
281     }
282     return newOffset;
283 }
284
285 void QIODeviceDataProvider::release()
286 {
287 #ifndef NDEBUG
288     //qDebug( "QIODeviceDataProvider::release()" );
289 #endif
290     mIO->close();
291 }