• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

KDEsu

process.cpp

Go to the documentation of this file.
00001 /* vi: ts=8 sts=4 sw=4
00002  *
00003  * This file is part of the KDE project, module kdesu.
00004  * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org>
00005  *
00006  * This file contains code from TEShell.C of the KDE konsole.
00007  * Copyright (c) 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
00008  *
00009  * This is free software; you can use this library under the GNU Library
00010  * General Public License, version 2. See the file "COPYING.LIB" for the
00011  * exact licensing terms.
00012  *
00013  * process.cpp: Functionality to build a front end to password asking
00014  *  terminal programs.
00015  */
00016 
00017 #include "process.h"
00018 #include "kcookie.h"
00019 
00020 #include <config.h>
00021 
00022 #include <stdio.h>
00023 #include <stdlib.h>
00024 #include <unistd.h>
00025 #include <fcntl.h>
00026 #include <signal.h>
00027 #include <errno.h>
00028 #include <string.h>
00029 #include <termios.h>
00030 
00031 #include <sys/types.h>
00032 #include <sys/wait.h>
00033 #include <sys/stat.h>
00034 #include <sys/time.h>
00035 #include <sys/resource.h>
00036 
00037 #ifdef HAVE_SYS_SELECT_H
00038 #include <sys/select.h>                // Needed on some systems.
00039 #endif
00040 
00041 #include <QtCore/QBool>
00042 #include <QtCore/QFile>
00043 
00044 #include <ksharedconfig.h>
00045 #include <kconfiggroup.h>
00046 #include <kdebug.h>
00047 #include <kstandarddirs.h>
00048 #include <kde_file.h>
00049 
00050 extern int kdesuDebugArea();
00051 
00052 namespace KDESu {
00053 
00054 using namespace KDESuPrivate;
00055 
00056 /*
00057 ** Wait for @p ms miliseconds
00058 ** @param fd file descriptor
00059 ** @param ms time to wait in miliseconds
00060 ** @return
00061 */
00062 int PtyProcess::waitMS(int fd,int ms)
00063 {
00064     struct timeval tv;
00065     tv.tv_sec = 0;
00066     tv.tv_usec = 1000*ms;
00067 
00068     fd_set fds;
00069     FD_ZERO(&fds);
00070     FD_SET(fd,&fds);
00071     return select(fd+1, &fds, 0L, 0L, &tv);
00072 }
00073 
00074 // XXX this function is nonsense:
00075 // - for our child, we could use waitpid().
00076 // - the configurability at this place it *complete* braindamage
00077 /*
00078 ** Basic check for the existence of @p pid.
00079 ** Returns true iff @p pid is an extant process.
00080 */
00081 bool PtyProcess::checkPid(pid_t pid)
00082 {
00083     KSharedConfig::Ptr config = KGlobal::config();
00084     KConfigGroup cg(config, "super-user-command");
00085     QString superUserCommand = cg.readEntry("super-user-command", "sudo");
00086     //sudo does not accept signals from user so we except it
00087     if (superUserCommand == "sudo") {
00088         return true;
00089     } else {
00090         return kill(pid, 0) == 0;
00091     }
00092 }
00093 
00094 /*
00095 ** Check process exit status for process @p pid.
00096 ** On error (no child, no exit), return Error (-1).
00097 ** If child @p pid has exited, return its exit status,
00098 ** (which may be zero).
00099 ** If child @p has not exited, return NotExited (-2).
00100 */
00101 
00102 int PtyProcess::checkPidExited(pid_t pid)
00103 {
00104     int state, ret;
00105     ret = waitpid(pid, &state, WNOHANG);
00106 
00107     if (ret < 0)
00108     {
00109         kError(kdesuDebugArea()) << k_lineinfo << "waitpid():" << perror;
00110         return Error;
00111     }
00112     if (ret == pid)
00113     {
00114         if (WIFEXITED(state))
00115             return WEXITSTATUS(state);
00116         return Killed;
00117     }
00118 
00119     return NotExited;
00120 }
00121 
00122 
00123 class PtyProcess::PtyProcessPrivate
00124 {
00125 public:
00126     PtyProcessPrivate() : m_pPTY(0L) {}
00127     ~PtyProcessPrivate()
00128     {
00129         delete m_pPTY;
00130     }
00131     QList<QByteArray> env;
00132     KPty *m_pPTY;
00133     QByteArray m_Inbuf;
00134 };
00135 
00136 
00137 PtyProcess::PtyProcess()
00138     :d(new PtyProcessPrivate)
00139 {
00140     m_bTerminal = false;
00141     m_bErase = false;
00142 }
00143 
00144 
00145 int PtyProcess::init()
00146 {
00147     delete d->m_pPTY;
00148     d->m_pPTY = new KPty();
00149     if (!d->m_pPTY->open())
00150     {
00151         kError(kdesuDebugArea()) << k_lineinfo << "Failed to open PTY.";
00152         return -1;
00153     }
00154     d->m_Inbuf.resize(0);
00155     return 0;
00156 }
00157 
00158 
00159 PtyProcess::~PtyProcess()
00160 {
00161     delete d;
00162 }
00163 
00165 void PtyProcess::setEnvironment( const QList<QByteArray> &env )
00166 {
00167     d->env = env;
00168 }
00169 
00170 int PtyProcess::fd() const
00171 {
00172     return d->m_pPTY ? d->m_pPTY->masterFd() : -1;
00173 }
00174 
00175 int PtyProcess::pid() const
00176 {
00177     return m_Pid;
00178 }
00179 
00181 QList<QByteArray> PtyProcess::environment() const
00182 {
00183     return d->env;
00184 }
00185 
00186 
00187 QByteArray PtyProcess::readAll(bool block)
00188 {
00189     QByteArray ret;
00190     if (!d->m_Inbuf.isEmpty())
00191     {
00192         // if there is still something in the buffer, we need not block.
00193         // we should still try to read any further output, from the fd, though.
00194         block = false;
00195         ret = d->m_Inbuf;
00196         d->m_Inbuf.resize(0);
00197     }
00198 
00199     int flags = fcntl(fd(), F_GETFL);
00200     if (flags < 0)
00201     {
00202         kError(kdesuDebugArea()) << k_lineinfo << "fcntl(F_GETFL):" << perror;
00203         return ret;
00204     }
00205     int oflags = flags;
00206     if (block)
00207         flags &= ~O_NONBLOCK;
00208     else
00209         flags |= O_NONBLOCK;
00210 
00211     if ((flags != oflags) && (fcntl(fd(), F_SETFL, flags) < 0))
00212     {
00213        // We get an error here when the child process has closed
00214        // the file descriptor already.
00215        return ret;
00216     }
00217 
00218     while (1)
00219     {
00220         ret.reserve(ret.size() + 0x8000);
00221         int nbytes = read(fd(), ret.data() + ret.size(), 0x8000);
00222         if (nbytes == -1)
00223         {
00224             if (errno == EINTR)
00225                 continue;
00226             else break;
00227         }
00228         if (nbytes == 0)
00229             break;        // nothing available / eof
00230 
00231         ret.resize(ret.size() + nbytes);
00232         break;
00233     }
00234 
00235     return ret;
00236 }
00237 
00238 
00239 QByteArray PtyProcess::readLine(bool block)
00240 {
00241     d->m_Inbuf = readAll(block);
00242 
00243     int pos;
00244     QByteArray ret;
00245     if (!d->m_Inbuf.isEmpty())
00246     {
00247         pos = d->m_Inbuf.indexOf('\n');
00248         if (pos == -1)
00249         {
00250             // NOTE: this means we return something even if there in no full line!
00251             ret = d->m_Inbuf;
00252             d->m_Inbuf.resize(0);
00253         } else
00254         {
00255             ret = d->m_Inbuf.left(pos);
00256             d->m_Inbuf.remove(0, pos+1);
00257         }
00258     }
00259 
00260     return ret;
00261 }
00262 
00263 
00264 void PtyProcess::writeLine(const QByteArray &line, bool addnl)
00265 {
00266     if (!line.isEmpty())
00267         write(fd(), line, line.length());
00268     if (addnl)
00269         write(fd(), "\n", 1);
00270 }
00271 
00272 
00273 void PtyProcess::unreadLine(const QByteArray &line, bool addnl)
00274 {
00275     QByteArray tmp = line;
00276     if (addnl)
00277         tmp += '\n';
00278     if (!tmp.isEmpty())
00279         d->m_Inbuf.prepend(tmp);
00280 }
00281 
00282 void PtyProcess::setExitString(const QByteArray &exit)
00283 {
00284     m_Exit = exit;
00285 }
00286 
00287 /*
00288  * Fork and execute the command. This returns in the parent.
00289  */
00290 
00291 int PtyProcess::exec(const QByteArray &command, const QList<QByteArray> &args)
00292 {
00293     kDebug(kdesuDebugArea()) << k_lineinfo << "Running" << command;
00294     int i;
00295 
00296     if (init() < 0)
00297         return -1;
00298 
00299     if ((m_Pid = fork()) == -1)
00300     {
00301         kError(kdesuDebugArea()) << k_lineinfo << "fork():" << perror;
00302         return -1;
00303     }
00304 
00305     // Parent
00306     if (m_Pid)
00307     {
00308         d->m_pPTY->closeSlave();
00309         return 0;
00310     }
00311 
00312     // Child
00313     if (setupTTY() < 0)
00314         _exit(1);
00315 
00316     for (i = 0; i < d->env.count(); ++i)
00317     {
00318         putenv(const_cast<char *>(d->env.at(i).constData()));
00319     }
00320     unsetenv("KDE_FULL_SESSION");
00321     // for : Qt: Session management error
00322     unsetenv("SESSION_MANAGER");
00323     // QMutex::lock , deadlocks without that.
00324     // <thiago> you cannot connect to the user's session bus from another UID
00325     unsetenv("DBUS_SESSION_BUS_ADDRESS");
00326 
00327     // set temporarily LC_ALL to C, for su (to be able to parse "Password:")
00328     const QByteArray old_lc_all = qgetenv( "LC_ALL" );
00329     if( !old_lc_all.isEmpty() )
00330         qputenv( "KDESU_LC_ALL", old_lc_all );
00331     else
00332         unsetenv( "KDESU_LC_ALL" );
00333     qputenv("LC_ALL", "C");
00334 
00335     // From now on, terminal output goes through the tty.
00336 
00337     QByteArray path;
00338     if (command.contains('/'))
00339         path = command;
00340     else
00341     {
00342         QString file = KStandardDirs::findExe(command);
00343         if (file.isEmpty())
00344         {
00345             kError(kdesuDebugArea()) << k_lineinfo << command << "not found.";
00346             _exit(1);
00347         }
00348         path = QFile::encodeName(file);
00349     }
00350 
00351     const char **argp = (const char **)malloc((args.count()+2)*sizeof(char *));
00352 
00353     i = 0;
00354     argp[i++] = path;
00355     for (QList<QByteArray>::ConstIterator it=args.begin(); it!=args.end(); ++it, ++i)
00356         argp[i] = *it;
00357 
00358     argp[i] = NULL;
00359 
00360     execv(path, const_cast<char **>(argp));
00361     kError(kdesuDebugArea()) << k_lineinfo << "execv(" << path << "):" << perror;
00362     _exit(1);
00363     return -1; // Shut up compiler. Never reached.
00364 }
00365 
00366 
00367 /*
00368  * Wait until the terminal is set into no echo mode. At least one su
00369  * (RH6 w/ Linux-PAM patches) sets noecho mode AFTER writing the Password:
00370  * prompt, using TCSAFLUSH. This flushes the terminal I/O queues, possibly
00371  * taking the password  with it. So we wait until no echo mode is set
00372  * before writing the password.
00373  * Note that this is done on the slave fd. While Linux allows tcgetattr() on
00374  * the master side, Solaris doesn't.
00375  */
00376 
00377 int PtyProcess::WaitSlave()
00378 {
00379     kDebug(kdesuDebugArea()) << k_lineinfo << "Child pid" << m_Pid;
00380 
00381     struct termios tio;
00382     while (1)
00383     {
00384         if (!checkPid(m_Pid))
00385         {
00386             kError(kdesuDebugArea()) << "process has exited while waiting for password.";
00387             return -1;
00388         }
00389         if (!d->m_pPTY->tcGetAttr(&tio))
00390         {
00391             kError(kdesuDebugArea()) << k_lineinfo << "tcgetattr():" << perror;
00392             return -1;
00393         }
00394         if (tio.c_lflag & ECHO)
00395         {
00396             kDebug(kdesuDebugArea()) << k_lineinfo << "Echo mode still on.";
00397             usleep(10000);
00398             continue;
00399         }
00400         break;
00401     }
00402     return 0;
00403 }
00404 
00405 
00406 int PtyProcess::enableLocalEcho(bool enable)
00407 {
00408     return d->m_pPTY->setEcho(enable) ? 0 : -1;
00409 }
00410 
00411 
00412 void PtyProcess::setTerminal(bool terminal)
00413 {
00414     m_bTerminal = terminal;
00415 }
00416 
00417 void PtyProcess::setErase(bool erase)
00418 {
00419     m_bErase = erase;
00420 }
00421 
00422 /*
00423  * Copy output to stdout until the child process exits, or a line of output
00424  * matches `m_Exit'.
00425  * We have to use waitpid() to test for exit. Merely waiting for EOF on the
00426  * pty does not work, because the target process may have children still
00427  * attached to the terminal.
00428  */
00429 
00430 int PtyProcess::waitForChild()
00431 {
00432     fd_set fds;
00433     FD_ZERO(&fds);
00434     QByteArray remainder;
00435 
00436     while (1)
00437     {
00438         FD_SET(fd(), &fds);
00439 
00440         // specify timeout to make sure select() does not block, even if the
00441         // process is dead / non-responsive. It does not matter if we abort too
00442         // early. In that case 0 is returned, and we'll try again in the next
00443         // iteration. (As long as we don't consitently time out in each iteration)
00444         timeval timeout;
00445         timeout.tv_sec = 0;
00446         timeout.tv_usec = 100000;
00447         int ret = select(fd()+1, &fds, 0L, 0L, &timeout);
00448         if (ret == -1)
00449         {
00450             if (errno != EINTR)
00451             {
00452                 kError(kdesuDebugArea()) << k_lineinfo << "select():" << perror;
00453                 return -1;
00454             }
00455             ret = 0;
00456         }
00457 
00458         if (ret)
00459         {
00460             forever {
00461                 QByteArray output = readAll(false);
00462                 if (output.isEmpty())
00463                     break;
00464                 if (m_bTerminal)
00465                 {
00466                     fwrite(output.constData(), output.size(), 1, stdout);
00467                     fflush(stdout);
00468                 }
00469                 if (!m_Exit.isEmpty())
00470                 {
00471                     // match exit string only at line starts
00472                     remainder += output;
00473                     while (remainder.length() >= m_Exit.length()) {
00474                         if (remainder.startsWith(m_Exit)) {
00475                             kill(m_Pid, SIGTERM);
00476                             remainder.remove(0, m_Exit.length());
00477                         }
00478                         int off = remainder.indexOf('\n');
00479                         if (off < 0)
00480                             break;
00481                         remainder.remove(0, off + 1);
00482                     }
00483                 }
00484             }
00485         }
00486 
00487         ret = checkPidExited(m_Pid);
00488         if (ret == Error)
00489         {
00490             if (errno == ECHILD) return 0;
00491             else return 1;
00492         }
00493         else if (ret == Killed)
00494         {
00495             return 0;
00496         }
00497         else if (ret == NotExited)
00498         {
00499             // keep checking
00500         }
00501         else
00502         {
00503             return ret;
00504         }
00505     }
00506 }
00507 
00508 /*
00509  * SetupTTY: Creates a new session. The filedescriptor "fd" should be
00510  * connected to the tty. It is closed after the tty is reopened to make it
00511  * our controlling terminal. This way the tty is always opened at least once
00512  * so we'll never get EIO when reading from it.
00513  */
00514 
00515 int PtyProcess::setupTTY()
00516 {
00517     // Reset signal handlers
00518     for (int sig = 1; sig < NSIG; sig++)
00519         KDE_signal(sig, SIG_DFL);
00520     KDE_signal(SIGHUP, SIG_IGN);
00521 
00522     d->m_pPTY->setCTty();
00523 
00524     // Connect stdin, stdout and stderr
00525     int slave = d->m_pPTY->slaveFd();
00526     dup2(slave, 0); dup2(slave, 1); dup2(slave, 2);
00527 
00528     // Close all file handles
00529     // XXX this caused problems in KProcess - not sure why anymore. -- ???
00530     // Because it will close the start notification pipe. -- ossi
00531     struct rlimit rlp;
00532     getrlimit(RLIMIT_NOFILE, &rlp);
00533     for (int i = 3; i < (int)rlp.rlim_cur; i++)
00534         close(i);
00535 
00536     // Disable OPOST processing. Otherwise, '\n' are (on Linux at least)
00537     // translated to '\r\n'.
00538     struct ::termios tio;
00539     if (tcgetattr(0, &tio) < 0)
00540     {
00541         kError(kdesuDebugArea()) << k_lineinfo << "tcgetattr():" << perror;
00542         return -1;
00543     }
00544     tio.c_oflag &= ~OPOST;
00545     if (tcsetattr(0, TCSANOW, &tio) < 0)
00546     {
00547         kError(kdesuDebugArea()) << k_lineinfo << "tcsetattr():" << perror;
00548         return -1;
00549     }
00550 
00551     return 0;
00552 }
00553 
00554 void PtyProcess::virtual_hook( int, void* )
00555 { /*BASE::virtual_hook( id, data );*/ }
00556 
00557 }

KDEsu

Skip menu "KDEsu"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal