File indexing completed on 2024-04-28 04:38:36

0001 /*
0002     SPDX-FileCopyrightText: 1999 John Birch <jbb@kdevelop.org>
0003 
0004     This code was copied originally from the KDEStudio project:
0005     SPDX-FileCopyrightText: Judin Maxim
0006 
0007     It was then updated with later code from konsole (KDE).
0008 
0009     It has also been enhanced with an idea from the code in kdbg:
0010     SPDX-FileCopyrightText: Johannes Sixt<Johannes.Sixt@telecom.at>
0011 
0012     SPDX-License-Identifier: GPL-2.0-or-later
0013 */
0014 
0015 #ifdef HAVE_CONFIG_H
0016 #include <config.h>
0017 #endif
0018 
0019 #ifdef __osf__
0020 #define _XOPEN_SOURCE_EXTENDED
0021 #endif
0022 
0023 #include <sys/types.h>
0024 #ifndef _MSC_VER
0025 #include <sys/ioctl.h>
0026 #include <sys/wait.h>
0027 #include <sys/time.h>
0028 #include <sys/resource.h>
0029 #endif
0030 #include <sys/stat.h>
0031 
0032 #ifdef HAVE_SYS_STROPTS_H
0033 #include <sys/stropts.h>
0034 #define _NEW_TTY_CTRL
0035 #endif
0036 
0037 #include <cassert>
0038 #include <fcntl.h>
0039 #ifndef _MSC_VER
0040 #include <grp.h>
0041 #include <termios.h>
0042 #include <unistd.h>
0043 #endif
0044 #include <cerrno>
0045 #include <csignal>
0046 #include <cstdio>
0047 #include <cstdlib>
0048 #include <ctime>
0049 
0050 #if defined (_HPUX_SOURCE)
0051 #define _TERMIOS_INCLUDED
0052 #include <bsdtty.h>
0053 #endif
0054 
0055 #include <QSocketNotifier>
0056 #include <QString>
0057 #include <QFile>
0058 #include <QProcess>
0059 #include <QTemporaryFile>
0060 
0061 #include <KLocalizedString>
0062 
0063 #include <QCoreApplication>
0064 #include <QStandardPaths>
0065 
0066 #include "stty.h"
0067 #include "debuglog.h"
0068 
0069 #define PTY_FILENO 3
0070 #define BASE_CHOWN "konsole_grantpty"
0071 
0072 using namespace KDevMI;
0073 
0074 static int chownpty(int fd, int grant)
0075 // param fd: the fd of a master pty.
0076 // param grant: 1 to grant, 0 to revoke
0077 // returns 1 on success 0 on fail
0078 {
0079 #ifndef Q_OS_WIN
0080     void(*tmp)(int) = signal(SIGCHLD,SIG_DFL);
0081     pid_t pid = fork();
0082     if (pid < 0) {
0083         signal(SIGCHLD,tmp);
0084         return 0;
0085     }
0086     if (pid == 0) {
0087         /* We pass the master pseudo terminal as file descriptor PTY_FILENO. */
0088         if (fd != PTY_FILENO && dup2(fd, PTY_FILENO) < 0)
0089             ::exit(1);
0090 
0091         QString path = QStandardPaths::findExecutable(QStringLiteral(BASE_CHOWN));
0092         const QByteArray encodedPath = QFile::encodeName(path);
0093         execle(encodedPath.constData(), BASE_CHOWN, grant?"--grant":"--revoke", (void *)nullptr, NULL);
0094         ::exit(1); // should not be reached
0095     }
0096     if (pid > 0) {
0097         int w;
0098         //  retry:
0099         int rc = waitpid (pid, &w, 0);
0100         if (rc != pid)
0101             ::exit(1);
0102 
0103         //    { // signal from other child, behave like catchChild.
0104         //      // guess this gives quite some control chaos...
0105         //      Shell* sh = shells.indexOf(rc);
0106         //      if (sh) { shells.remove(rc); sh->doneShell(w); }
0107         //      goto retry;
0108         //    }
0109         signal(SIGCHLD,tmp);
0110         return (rc != -1 && WIFEXITED(w) && WEXITSTATUS(w) == 0);
0111     }
0112     signal(SIGCHLD,tmp);
0113 #endif
0114     return 0; //dummy.
0115 }
0116 
0117 // **************************************************************************
0118 
0119 STTY::STTY(bool ext, const QString &termAppName)
0120     : QObject(),
0121       m_externalTerminal(nullptr),
0122       external_(ext)
0123 {
0124     if (ext) {
0125         findExternalTTY(termAppName);
0126     } else {
0127         fout = findTTY();
0128         if (fout >= 0) {
0129             ttySlave = QString::fromLatin1(tty_slave);
0130             out = new QSocketNotifier(fout, QSocketNotifier::Read, this);
0131             connect( out, &QSocketNotifier::activated, this, &STTY::OutReceived );
0132         }
0133     }
0134 }
0135 
0136 // **************************************************************************
0137 
0138 STTY::~STTY()
0139 {
0140 #ifndef Q_OS_WIN
0141     if (out) {
0142         ::close(fout);
0143         delete out;
0144     }
0145 #endif
0146 }
0147 
0148 // **************************************************************************
0149 
0150 int STTY::findTTY()
0151 {
0152     int ptyfd = -1;
0153     bool needGrantPty = true;
0154 #ifndef Q_OS_WIN
0155     // Find a master pty that we can open ////////////////////////////////
0156 
0157 #ifdef __sgi__
0158     ptyfd = open("/dev/ptmx",O_RDWR);
0159 #elif defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
0160     ptyfd = posix_openpt(O_RDWR);
0161 #endif
0162 #if defined(__sgi__) || defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
0163     if (ptyfd == -1) {
0164         perror("Can't open a pseudo teletype");
0165         return(-1);
0166     } else if (ptyfd >= 0) {
0167         strncpy(tty_slave, ptsname(ptyfd), 50);
0168         grantpt(ptyfd);
0169         unlockpt(ptyfd);
0170         needGrantPty = false;
0171     }
0172 #endif
0173 
0174     // first we try UNIX PTY's
0175 #if defined(TIOCGPTN) && !defined(Q_OS_FREEBSD)
0176     strcpy(pty_master,"/dev/ptmx");
0177     strcpy(tty_slave,"/dev/pts/");
0178     ptyfd = open(pty_master,O_RDWR);
0179     if (ptyfd >= 0) { // got the master pty
0180         int ptyno;
0181         if (ioctl(ptyfd, TIOCGPTN, &ptyno) == 0) {
0182             struct stat sbuf;
0183             sprintf(tty_slave,"/dev/pts/%d",ptyno);
0184             if (stat(tty_slave,&sbuf) == 0 && S_ISCHR(sbuf.st_mode))
0185                 needGrantPty = false;
0186             else {
0187                 close(ptyfd);
0188                 ptyfd = -1;
0189             }
0190         } else {
0191             close(ptyfd);
0192             ptyfd = -1;
0193         }
0194     }
0195 #endif
0196 
0197 #if defined(_SCO_DS) || defined(__USLC__) /* SCO OSr5 and UnixWare */
0198     if (ptyfd < 0) {
0199         for (int idx = 0; idx < 256; idx++)
0200             { sprintf(pty_master, "/dev/ptyp%d", idx);
0201             sprintf(tty_slave, "/dev/ttyp%d", idx);
0202             if (access(tty_slave, F_OK) < 0) { idx = 256; break; }
0203             if ((ptyfd = open (pty_master, O_RDWR)) >= 0)
0204                 { if (access (tty_slave, R_OK|W_OK) == 0) break;
0205                 close(ptyfd); ptyfd = -1;
0206                 }
0207             }
0208     }
0209 #endif
0210     if (ptyfd < 0) { /// \FIXME Linux, Trouble on other systems?
0211         for (const char* s3 = "pqrstuvwxyzabcde"; *s3 != 0; s3++) {
0212             for (const char* s4 = "0123456789abcdef"; *s4 != 0; s4++) {
0213                 sprintf(pty_master,"/dev/pty%c%c",*s3,*s4);
0214                 sprintf(tty_slave,"/dev/tty%c%c",*s3,*s4);
0215                 if ((ptyfd = open(pty_master, O_RDWR)) >= 0) {
0216                     if (geteuid() == 0 || access(tty_slave, R_OK|W_OK) == 0)
0217                         break;
0218 
0219                     close(ptyfd);
0220                     ptyfd = -1;
0221                 }
0222             }
0223 
0224             if (ptyfd >= 0)
0225                 break;
0226         }
0227     }
0228 
0229     if (ptyfd >= 0) {
0230         if (needGrantPty && !chownpty(ptyfd, true)) {
0231             fprintf(stderr,"kdevelop: chownpty failed for device %s::%s.\n",pty_master,tty_slave);
0232             fprintf(stderr,"        : This means the session can be eavesdroped.\n");
0233             fprintf(stderr,"        : Make sure konsole_grantpty is installed and setuid root.\n");
0234         }
0235 
0236         ::fcntl(ptyfd, F_SETFL, O_NONBLOCK);
0237 #ifdef TIOCSPTLCK
0238         int flag = 0;
0239         ioctl(ptyfd, TIOCSPTLCK, &flag); // unlock pty
0240 #endif
0241     }
0242     if (ptyfd==-1) {
0243         m_lastError = i18n("Cannot use the tty* or pty* devices.\n"
0244                                     "Check the settings on /dev/tty* and /dev/pty*\n"
0245                                     "As root you may need to \"chmod ug+rw\" tty* and pty* devices "
0246                                     "and/or add the user to the tty group using "
0247                                     "\"usermod -aG tty username\".");
0248     }
0249 #endif
0250     return ptyfd;
0251 }
0252 
0253 // **************************************************************************
0254 
0255 void STTY::OutReceived(int f)
0256 {
0257 #ifndef Q_OS_WIN
0258     char buf[1024];
0259     int n;
0260 
0261     // read until socket is empty. We shouldn't be receiving a continuous
0262     // stream of data, so the loop is unlikely to cause problems.
0263     while ((n = ::read(f, buf, sizeof(buf)-1)) > 0) {
0264         *(buf+n) = 0;         // a standard string
0265         QByteArray ba(buf);
0266         emit OutOutput(ba);
0267     }
0268     // Note: for some reason, n can be 0 here.
0269     // I can understand that non-blocking read returns 0,
0270     // but I don't understand how OutReceived can be even
0271     // called when there's no input.
0272     if (n == 0 /* eof */
0273         || (n == -1 && errno != EAGAIN))
0274     {
0275         // Found eof or error. Disable socket notifier, otherwise Qt
0276         // will repeatedly call this method, eating CPU
0277         // cycles.
0278         out->setEnabled(false);
0279     }
0280 #endif
0281 }
0282 
0283 void STTY::readRemaining()
0284 {
0285     if (!external_)
0286         OutReceived(fout);
0287 }
0288 
0289 bool STTY::findExternalTTY(const QString& termApp)
0290 {
0291 #ifndef Q_OS_WIN
0292     QString appName(termApp.isEmpty() ? QStringLiteral("xterm") : termApp);
0293 
0294     if (QStandardPaths::findExecutable(appName).isEmpty()) {
0295         m_lastError = i18n("%1 is incorrect terminal name", termApp);
0296         return false;
0297     }
0298 
0299     QTemporaryFile file;
0300     if (!file.open()) {
0301         m_lastError = i18n("Can't create a temporary file");
0302         return false;
0303     }
0304 
0305     m_externalTerminal.reset(new QProcess(this));
0306 
0307     if (appName == QLatin1String("konsole")) {
0308         m_externalTerminal->start(appName, QStringList{
0309             QStringLiteral("-e"),
0310             QStringLiteral("sh"),
0311             QStringLiteral("-c"),
0312             QLatin1String("tty>") + file.fileName() + QLatin1String(";exec<&-;exec>&-;while :;do sleep 3600;done")});
0313     } else if (appName == QLatin1String("xfce4-terminal")) {
0314         m_externalTerminal->start(appName, QStringList{
0315             QStringLiteral("-e"),
0316             QLatin1String("sh -c \"tty>") + file.fileName() + QLatin1String(";\"\"<&\\-\"\">&\\-;\"\"while :;\"\"do sleep 3600;\"\"done\"")});
0317     } else {
0318         m_externalTerminal->start(appName, QStringList{
0319             QStringLiteral("-e"),
0320             QLatin1String("sh -c \"tty>") + file.fileName() + QLatin1String(";exec<&-;exec>&-;while :;do sleep 3600;done\"")});
0321     }
0322 
0323     if (!m_externalTerminal->waitForStarted(500)) {
0324         m_lastError = QLatin1String("Can't run terminal: ") + appName;
0325         m_externalTerminal->terminate();
0326         return false;
0327     }
0328 
0329     for (int i = 0; i < 800; i++) {
0330         if (!file.bytesAvailable()) {
0331             if (m_externalTerminal->state() == QProcess::NotRunning && m_externalTerminal->exitCode()) {
0332                 break;
0333             }
0334             QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
0335             usleep(8000);
0336         } else {
0337             qCDebug(DEBUGGERCOMMON) << "Received terminal output(tty)";
0338             break;
0339         }
0340     }
0341 
0342     usleep(1000);
0343     ttySlave = QString::fromUtf8(file.readAll().trimmed());
0344 
0345     file.close();
0346 
0347     if (ttySlave.isEmpty()) {
0348         m_lastError = i18n("Can't receive %1 tty/pty. Check that %1 is actually a terminal and that it accepts these arguments: -e sh -c \"tty> %2 ;exec<&-;exec>&-;while :;do sleep 3600;done\"", appName, file.fileName());
0349     }
0350 #endif
0351     return true;
0352 }
0353 // **************************************************************************
0354 
0355 #include "moc_stty.cpp"