KPty

kptydevice.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2007 Oswald Buddenhagen <ossi@kde.org>
4 SPDX-FileCopyrightText: 2010 KDE e.V. <kde-ev-board@kde.org>
5 SPDX-FileContributor: 2010 Adriaan de Groot <groot@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "kptydevice.h"
11#include "kpty_p.h"
12
13#include <config-pty.h>
14
15#include <QSocketNotifier>
16
17#include <KLocalizedString>
18
19#include <cerrno>
20#include <fcntl.h>
21#include <signal.h>
22#include <sys/ioctl.h>
23#include <termios.h>
24#include <unistd.h>
25#if HAVE_SYS_FILIO_H
26#include <sys/filio.h>
27#endif
28#if HAVE_SYS_TIME_H
29#include <sys/time.h>
30#endif
31
32#if defined(Q_OS_FREEBSD) || defined(Q_OS_MAC)
33// "the other end's output queue size" -- that is is our end's input
34#define PTY_BYTES_AVAILABLE TIOCOUTQ
35#elif defined(TIOCINQ)
36// "our end's input queue size"
37#define PTY_BYTES_AVAILABLE TIOCINQ
38#else
39// likewise. more generic ioctl (theoretically)
40#define PTY_BYTES_AVAILABLE FIONREAD
41#endif
42
43#define KMAXINT ((int)(~0U >> 1))
44
45/////////////////////////////////////////////////////
46// Helper. Remove when QRingBuffer becomes public. //
47/////////////////////////////////////////////////////
48
49#include <QByteArray>
50#include <QList>
51
52#define CHUNKSIZE 4096
53
54class KRingBuffer
55{
56public:
57 KRingBuffer()
58 {
59 clear();
60 }
61
62 void clear()
63 {
64 buffers.clear();
65 QByteArray tmp;
66 tmp.resize(CHUNKSIZE);
67 buffers << tmp;
68 head = tail = 0;
69 totalSize = 0;
70 }
71
72 inline bool isEmpty() const
73 {
74 return buffers.count() == 1 && !tail;
75 }
76
77 inline int size() const
78 {
79 return totalSize;
80 }
81
82 inline int readSize() const
83 {
84 return (buffers.count() == 1 ? tail : buffers.first().size()) - head;
85 }
86
87 inline const char *readPointer() const
88 {
89 Q_ASSERT(totalSize > 0);
90 return buffers.first().constData() + head;
91 }
92
93 void free(int bytes)
94 {
95 totalSize -= bytes;
96 Q_ASSERT(totalSize >= 0);
97
98 for (;;) {
99 int nbs = readSize();
100
101 if (bytes < nbs) {
102 head += bytes;
103 if (head == tail && buffers.count() == 1) {
104 buffers.first().resize(CHUNKSIZE);
105 head = tail = 0;
106 }
107 break;
108 }
109
110 bytes -= nbs;
111 if (buffers.count() == 1) {
112 buffers.first().resize(CHUNKSIZE);
113 head = tail = 0;
114 break;
115 }
116
117 buffers.removeFirst();
118 head = 0;
119 }
120 }
121
122 char *reserve(int bytes)
123 {
124 totalSize += bytes;
125
126 char *ptr;
127 if (tail + bytes <= buffers.last().size()) {
128 ptr = buffers.last().data() + tail;
129 tail += bytes;
130 } else {
131 buffers.last().resize(tail);
132 QByteArray tmp;
133 tmp.resize(qMax(CHUNKSIZE, bytes));
134 ptr = tmp.data();
135 buffers << tmp;
136 tail = bytes;
137 }
138 return ptr;
139 }
140
141 // release a trailing part of the last reservation
142 inline void unreserve(int bytes)
143 {
144 totalSize -= bytes;
145 tail -= bytes;
146 }
147
148 inline void write(const char *data, int len)
149 {
150 memcpy(reserve(len), data, len);
151 }
152
153 // Find the first occurrence of c and return the index after it.
154 // If c is not found until maxLength, maxLength is returned, provided
155 // it is smaller than the buffer size. Otherwise -1 is returned.
156 int indexAfter(char c, int maxLength = KMAXINT) const
157 {
158 int index = 0;
159 int start = head;
161 for (;;) {
162 if (!maxLength) {
163 return index;
164 }
165 if (index == size()) {
166 return -1;
167 }
168 const QByteArray &buf = *it;
169 ++it;
170 int len = qMin((it == buffers.end() ? tail : buf.size()) - start, maxLength);
171 const char *ptr = buf.data() + start;
172 if (const char *rptr = (const char *)memchr(ptr, c, len)) {
173 return index + (rptr - ptr) + 1;
174 }
175 index += len;
176 maxLength -= len;
177 start = 0;
178 }
179 }
180
181 inline int lineSize(int maxLength = KMAXINT) const
182 {
183 return indexAfter('\n', maxLength);
184 }
185
186 inline bool canReadLine() const
187 {
188 return lineSize() != -1;
189 }
190
191 int read(char *data, int maxLength)
192 {
193 int bytesToRead = qMin(size(), maxLength);
194 int readSoFar = 0;
195 while (readSoFar < bytesToRead) {
196 const char *ptr = readPointer();
197 int bs = qMin(bytesToRead - readSoFar, readSize());
198 memcpy(data + readSoFar, ptr, bs);
199 readSoFar += bs;
200 free(bs);
201 }
202 return readSoFar;
203 }
204
205 int readLine(char *data, int maxLength)
206 {
207 return read(data, lineSize(qMin(maxLength, size())));
208 }
209
210private:
211 QList<QByteArray> buffers;
212 int head, tail;
213 int totalSize;
214};
215
216//////////////////
217// private data //
218//////////////////
219
220// Lifted from Qt. I don't think they would mind. ;)
221// Re-lift again from Qt whenever a proper replacement for pthread_once appears
222static void qt_ignore_sigpipe()
223{
224 static QBasicAtomicInt atom = Q_BASIC_ATOMIC_INITIALIZER(0);
225 if (atom.testAndSetRelaxed(0, 1)) {
226 struct sigaction noaction;
227 memset(&noaction, 0, sizeof(noaction));
228 noaction.sa_handler = SIG_IGN;
229 sigaction(SIGPIPE, &noaction, nullptr);
230 }
231}
232
233/* clang-format off */
234#define NO_INTR(ret, func) \
235 do { \
236 ret = func; \
237 } while (ret < 0 && errno == EINTR)
238/* clang-format on */
239
240class KPtyDevicePrivate : public KPtyPrivate
241{
242 Q_DECLARE_PUBLIC(KPtyDevice)
243public:
244 KPtyDevicePrivate(KPty *parent)
245 : KPtyPrivate(parent)
246 , emittedReadyRead(false)
247 , emittedBytesWritten(false)
248 , readNotifier(nullptr)
249 , writeNotifier(nullptr)
250 {
251 }
252
253 bool _k_canRead();
254 bool _k_canWrite();
255
256 bool doWait(int msecs, bool reading);
257 void finishOpen(QIODevice::OpenMode mode);
258
259 bool emittedReadyRead;
260 bool emittedBytesWritten;
261 QSocketNotifier *readNotifier;
262 QSocketNotifier *writeNotifier;
263 KRingBuffer readBuffer;
264 KRingBuffer writeBuffer;
265};
266
267bool KPtyDevicePrivate::_k_canRead()
268{
269 Q_Q(KPtyDevice);
270 qint64 readBytes = 0;
271
272 int available;
273 if (!::ioctl(q->masterFd(), PTY_BYTES_AVAILABLE, (char *)&available)) {
274 char *ptr = readBuffer.reserve(available);
275 NO_INTR(readBytes, read(q->masterFd(), ptr, available));
276 if (readBytes < 0) {
277 readBuffer.unreserve(available);
278 q->setErrorString(i18n("Error reading from PTY"));
279 return false;
280 }
281 readBuffer.unreserve(available - readBytes); // *should* be a no-op
282 }
283
284 if (!readBytes) {
285 readNotifier->setEnabled(false);
286 Q_EMIT q->readEof();
287 return false;
288 } else {
289 if (!emittedReadyRead) {
290 emittedReadyRead = true;
291 Q_EMIT q->readyRead();
292 emittedReadyRead = false;
293 }
294 return true;
295 }
296}
297
298bool KPtyDevicePrivate::_k_canWrite()
299{
300 Q_Q(KPtyDevice);
301
302 writeNotifier->setEnabled(false);
303 if (writeBuffer.isEmpty()) {
304 return false;
305 }
306
307 qt_ignore_sigpipe();
308 int wroteBytes;
309 NO_INTR(wroteBytes, write(q->masterFd(), writeBuffer.readPointer(), writeBuffer.readSize()));
310 if (wroteBytes < 0) {
311 q->setErrorString(i18n("Error writing to PTY"));
312 return false;
313 }
314 writeBuffer.free(wroteBytes);
315
316 if (!emittedBytesWritten) {
317 emittedBytesWritten = true;
318 Q_EMIT q->bytesWritten(wroteBytes);
319 emittedBytesWritten = false;
320 }
321
322 if (!writeBuffer.isEmpty()) {
323 writeNotifier->setEnabled(true);
324 }
325 return true;
326}
327
328#ifndef timeradd
329// Lifted from GLIBC
330/* clang-format off */
331#define timeradd(a, b, result) \
332 do { \
333 (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \
334 (result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \
335 if ((result)->tv_usec >= 1000000) { \
336 ++(result)->tv_sec; \
337 (result)->tv_usec -= 1000000; \
338 } \
339 } while (0)
340
341#define timersub(a, b, result) \
342 do { \
343 (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
344 (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
345 if ((result)->tv_usec < 0) { \
346 --(result)->tv_sec; \
347 (result)->tv_usec += 1000000; \
348 } \
349 } while (0)
350#endif
351/* clang-format on */
352
353bool KPtyDevicePrivate::doWait(int msecs, bool reading)
354{
355 Q_Q(KPtyDevice);
356#ifndef Q_OS_LINUX
357 struct timeval etv;
358#endif
359 struct timeval tv;
360 struct timeval *tvp;
361
362 if (msecs < 0) {
363 tvp = nullptr;
364 } else {
365 tv.tv_sec = msecs / 1000;
366 tv.tv_usec = (msecs % 1000) * 1000;
367#ifndef Q_OS_LINUX
368 gettimeofday(&etv, nullptr);
369 timeradd(&tv, &etv, &etv);
370#endif
371 tvp = &tv;
372 }
373
374 while (reading ? readNotifier->isEnabled() : !writeBuffer.isEmpty()) {
375 fd_set rfds;
376 fd_set wfds;
377
378 FD_ZERO(&rfds);
379 FD_ZERO(&wfds);
380
381 if (readNotifier->isEnabled()) {
382 FD_SET(q->masterFd(), &rfds);
383 }
384 if (!writeBuffer.isEmpty()) {
385 FD_SET(q->masterFd(), &wfds);
386 }
387
388#ifndef Q_OS_LINUX
389 if (tvp) {
390 gettimeofday(&tv, nullptr);
391 timersub(&etv, &tv, &tv);
392 if (tv.tv_sec < 0) {
393 tv.tv_sec = tv.tv_usec = 0;
394 }
395 }
396#endif
397
398 switch (select(q->masterFd() + 1, &rfds, &wfds, nullptr, tvp)) {
399 case -1:
400 if (errno == EINTR) {
401 break;
402 }
403 return false;
404 case 0:
405 q->setErrorString(i18n("PTY operation timed out"));
406 return false;
407 default:
408 if (FD_ISSET(q->masterFd(), &rfds)) {
409 bool canRead = _k_canRead();
410 if (reading && canRead) {
411 return true;
412 }
413 }
414 if (FD_ISSET(q->masterFd(), &wfds)) {
415 bool canWrite = _k_canWrite();
416 if (!reading) {
417 return canWrite;
418 }
419 }
420 break;
421 }
422 }
423 return false;
424}
425
426void KPtyDevicePrivate::finishOpen(QIODevice::OpenMode mode)
427{
428 Q_Q(KPtyDevice);
429
430 q->QIODevice::open(mode);
431 fcntl(q->masterFd(), F_SETFL, O_NONBLOCK);
432 readBuffer.clear();
433 readNotifier = new QSocketNotifier(q->masterFd(), QSocketNotifier::Read, q);
434 writeNotifier = new QSocketNotifier(q->masterFd(), QSocketNotifier::Write, q);
435 QObject::connect(readNotifier, SIGNAL(activated(int)), q, SLOT(_k_canRead()));
436 QObject::connect(writeNotifier, SIGNAL(activated(int)), q, SLOT(_k_canWrite()));
437 readNotifier->setEnabled(true);
438}
439
440/////////////////////////////
441// public member functions //
442/////////////////////////////
443
445 : QIODevice(parent)
446 , KPty(new KPtyDevicePrivate(this))
447{
448}
449
454
455bool KPtyDevice::open(OpenMode mode)
456{
458
459 if (masterFd() >= 0) {
460 return true;
461 }
462
463 if (!KPty::open()) {
464 setErrorString(i18n("Error opening PTY"));
465 return false;
466 }
467
468 d->finishOpen(mode);
469
470 return true;
471}
472
473bool KPtyDevice::open(int fd, OpenMode mode)
474{
476
477 if (!KPty::open(fd)) {
478 setErrorString(i18n("Error opening PTY"));
479 return false;
480 }
481
482 d->finishOpen(mode);
483
484 return true;
485}
486
488{
490
491 if (masterFd() < 0) {
492 return;
493 }
494
495 delete d->readNotifier;
496 delete d->writeNotifier;
497
499
500 KPty::close();
501}
502
504{
505 return true;
506}
507
509{
510 Q_D(const KPtyDevice);
511 return QIODevice::canReadLine() || d->readBuffer.canReadLine();
512}
513
515{
516 Q_D(const KPtyDevice);
517 return QIODevice::atEnd() && d->readBuffer.isEmpty();
518}
519
521{
522 Q_D(const KPtyDevice);
523 return QIODevice::bytesAvailable() + d->readBuffer.size();
524}
525
527{
528 Q_D(const KPtyDevice);
529 return d->writeBuffer.size();
530}
531
532bool KPtyDevice::waitForReadyRead(int msecs)
533{
535 return d->doWait(msecs, true);
536}
537
538bool KPtyDevice::waitForBytesWritten(int msecs)
539{
541 return d->doWait(msecs, false);
542}
543
544void KPtyDevice::setSuspended(bool suspended)
545{
547 d->readNotifier->setEnabled(!suspended);
548}
549
551{
552 Q_D(const KPtyDevice);
553 return !d->readNotifier->isEnabled();
554}
555
556// protected
557qint64 KPtyDevice::readData(char *data, qint64 maxlen)
558{
560 return d->readBuffer.read(data, (int)qMin<qint64>(maxlen, KMAXINT));
561}
562
563// protected
564qint64 KPtyDevice::readLineData(char *data, qint64 maxlen)
565{
567 return d->readBuffer.readLine(data, (int)qMin<qint64>(maxlen, KMAXINT));
568}
569
570// protected
571qint64 KPtyDevice::writeData(const char *data, qint64 len)
572{
574 Q_ASSERT(len <= KMAXINT);
575
576 d->writeBuffer.write(data, len);
577 d->writeNotifier->setEnabled(true);
578 return len;
579}
580
581#include "moc_kptydevice.cpp"
Encapsulates KPty into a QIODevice, so it can be used with Q*Stream, etc.
Definition kptydevice.h:21
void setSuspended(bool suspended)
Sets whether the KPtyDevice monitors the pty for incoming data.
qint64 bytesAvailable() const override
bool isSequential() const override
bool canReadLine() const override
KPtyDevice(QObject *parent=nullptr)
Constructor.
qint64 bytesToWrite() const override
bool isSuspended() const
Returns true if the KPtyDevice is not monitoring the pty for incoming data.
void close() override
Close the pty master/slave pair.
bool atEnd() const override
~KPtyDevice() override
Destructor:
Provides primitives for opening & closing a pseudo TTY pair, assigning the controlling TTY,...
Definition kpty.h:26
int masterFd() const
Definition kpty.cpp:669
bool open()
Create a pty master/slave pair.
Definition kpty.cpp:186
void close()
Close the pty master/slave pair.
Definition kpty.cpp:398
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18n(const char *text, const TYPE &arg...)
QVariant read(const QByteArray &data, int versionOverride=0)
char * data()
void resize(qsizetype newSize, char c)
virtual bool atEnd() const const
virtual qint64 bytesAvailable() const const
virtual bool canReadLine() const const
virtual void close()
void setErrorString(const QString &str)
typedef OpenMode
iterator begin()
void clear()
qsizetype count() const const
iterator end()
T & first()
T & last()
void removeFirst()
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool isEnabled() const const
void setEnabled(bool enable)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:29 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.