• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • 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 
00051 namespace KDESu {
00052 
00053 using namespace KDESuPrivate;
00054 
00055 /*
00056 ** Wait for @p ms miliseconds
00057 ** @param fd file descriptor
00058 ** @param ms time to wait in miliseconds
00059 ** @return
00060 */
00061 int PtyProcess::waitMS(int fd,int ms)
00062 {
00063     struct timeval tv;
00064     tv.tv_sec = 0;
00065     tv.tv_usec = 1000*ms;
00066 
00067     fd_set fds;
00068     FD_ZERO(&fds);
00069     FD_SET(fd,&fds);
00070     return select(fd+1, &fds, 0L, 0L, &tv);
00071 }
00072 
00073 // XXX this function is nonsense:
00074 // - for our child, we could use waitpid().
00075 // - the configurability at this place it *complete* braindamage
00076 /*
00077 ** Basic check for the existence of @p pid.
00078 ** Returns true iff @p pid is an extant process.
00079 */
00080 bool PtyProcess::checkPid(pid_t pid)
00081 {
00082     KSharedConfig::Ptr config = KGlobal::config();
00083     KConfigGroup cg(config, "super-user-command");
00084     QString superUserCommand = cg.readEntry("super-user-command", "sudo");
00085     //sudo does not accept signals from user so we except it
00086     if (superUserCommand == "sudo") {
00087         return true;
00088     } else {
00089         return kill(pid, 0) == 0;
00090     }
00091 }
00092 
00093 /*
00094 ** Check process exit status for process @p pid.
00095 ** On error (no child, no exit), return Error (-1).
00096 ** If child @p pid has exited, return its exit status,
00097 ** (which may be zero).
00098 ** If child @p has not exited, return NotExited (-2).
00099 */
00100 
00101 int PtyProcess::checkPidExited(pid_t pid)
00102 {
00103     int state, ret;
00104     ret = waitpid(pid, &state, WNOHANG);
00105 
00106     if (ret < 0)
00107     {
00108         kError(900) << k_lineinfo << "waitpid(): " << perror << "\n";
00109         return Error;
00110     }
00111     if (ret == pid)
00112     {
00113         if (WIFEXITED(state))
00114             return WEXITSTATUS(state);
00115         return Killed;
00116     }
00117 
00118     return NotExited;
00119 }
00120 
00121 
00122 class PtyProcess::PtyProcessPrivate
00123 {
00124 public:
00125     PtyProcessPrivate() : m_pPTY(0L) {}
00126     ~PtyProcessPrivate()
00127     {
00128         delete m_pPTY;
00129     }
00130     QList<QByteArray> env;
00131     KPty *m_pPTY;
00132     QByteArray m_Inbuf;
00133 };
00134 
00135 
00136 PtyProcess::PtyProcess()
00137     :d(new PtyProcessPrivate)
00138 {
00139     m_bTerminal = false;
00140     m_bErase = false;
00141 }
00142 
00143 
00144 int PtyProcess::init()
00145 {
00146     delete d->m_pPTY;
00147     d->m_pPTY = new KPty();
00148     if (!d->m_pPTY->open())
00149     {
00150         kError(900) << k_lineinfo << "Failed to open PTY.\n";
00151         return -1;
00152     }
00153     d->m_Inbuf.resize(0);
00154     return 0;
00155 }
00156 
00157 
00158 PtyProcess::~PtyProcess()
00159 {
00160     delete d;
00161 }
00162 
00164 void PtyProcess::setEnvironment( const QList<QByteArray> &env )
00165 {
00166     d->env = env;
00167 }
00168 
00169 int PtyProcess::fd() const
00170 {
00171     return d->m_pPTY ? d->m_pPTY->masterFd() : -1;
00172 }
00173 
00174 int PtyProcess::pid() const
00175 {
00176     return m_Pid;
00177 }
00178 
00180 QList<QByteArray> PtyProcess::environment() const
00181 {
00182     return d->env;
00183 }
00184 
00185 
00186 QByteArray PtyProcess::readAll(bool block)
00187 {
00188     QByteArray ret;
00189     if (!d->m_Inbuf.isEmpty())
00190     {
00191         // if there is still something in the buffer, we need not block.
00192         // we should still try to read any further output, from the fd, though.
00193         block = false;
00194         ret = d->m_Inbuf;
00195         d->m_Inbuf.resize(0);
00196     }
00197 
00198     int flags = fcntl(fd(), F_GETFL);
00199     if (flags < 0)
00200     {
00201         kError(900) << k_lineinfo << "fcntl(F_GETFL): " << perror << "\n";
00202         return ret;
00203     }
00204     int oflags = flags;
00205     if (block)
00206         flags &= ~O_NONBLOCK;
00207     else
00208         flags |= O_NONBLOCK;
00209 
00210     if ((flags != oflags) && (fcntl(fd(), F_SETFL, flags) < 0))
00211     {
00212        // We get an error here when the child process has closed
00213        // the file descriptor already.
00214        return ret;
00215     }
00216 
00217     int nbytes;
00218     char buf[256];
00219     while (1)
00220     {
00221         nbytes = read(fd(), buf, 255);
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         buf[nbytes] = '\000';
00232         ret += buf;
00233         break;
00234     }
00235 
00236     return ret;
00237 }
00238 
00239 
00240 QByteArray PtyProcess::readLine(bool block)
00241 {
00242     d->m_Inbuf = readAll(block);
00243 
00244     int pos;
00245     QByteArray ret;
00246     if (!d->m_Inbuf.isEmpty())
00247     {
00248         pos = d->m_Inbuf.indexOf('\n');
00249         if (pos == -1)
00250         {
00251             // NOTE: this means we return something even if there in no full line!
00252             ret = d->m_Inbuf;
00253             d->m_Inbuf.resize(0);
00254         } else
00255         {
00256             ret = d->m_Inbuf.left(pos);
00257             d->m_Inbuf = d->m_Inbuf.mid(pos+1);
00258         }
00259     }
00260 
00261     return ret;
00262 }
00263 
00264 
00265 void PtyProcess::writeLine(const QByteArray &line, bool addnl)
00266 {
00267     if (!line.isEmpty())
00268         write(fd(), line, line.length());
00269     if (addnl)
00270         write(fd(), "\n", 1);
00271 }
00272 
00273 
00274 void PtyProcess::unreadLine(const QByteArray &line, bool addnl)
00275 {
00276     QByteArray tmp = line;
00277     if (addnl)
00278         tmp += '\n';
00279     if (!tmp.isEmpty())
00280         d->m_Inbuf.prepend(tmp);
00281 }
00282 
00283 void PtyProcess::setExitString(const QByteArray &exit)
00284 {
00285     m_Exit = exit;
00286 }
00287 
00288 /*
00289  * Fork and execute the command. This returns in the parent.
00290  */
00291 
00292 int PtyProcess::exec(const QByteArray &command, const QList<QByteArray> &args)
00293 {
00294     kDebug(900) << k_lineinfo << "Running `" << command << "'\n";
00295     int i;
00296 
00297     if (init() < 0)
00298         return -1;
00299 
00300     if ((m_Pid = fork()) == -1)
00301     {
00302         kError(900) << k_lineinfo << "fork(): " << perror << "\n";
00303         return -1;
00304     }
00305 
00306     // Parent
00307     if (m_Pid)
00308     {
00309         d->m_pPTY->closeSlave();
00310         return 0;
00311     }
00312 
00313     // Child
00314     if (setupTTY() < 0)
00315         _exit(1);
00316 
00317     for (i = 0; i < d->env.count(); ++i)
00318     {
00319         putenv(const_cast<char *>(d->env.at(i).constData()));
00320     }
00321     unsetenv("KDE_FULL_SESSION");
00322     // for : Qt: Session management error
00323     unsetenv("SESSION_MANAGER");
00324     // QMutex::lock , deadlocks without that.
00325     // <thiago> you cannot connect to the user's session bus from another UID
00326     unsetenv("DBUS_SESSION_BUS_ADDRESS");
00327 
00328     // set temporarily LC_ALL to C, for su (to be able to parse "Password:")
00329     const QByteArray old_lc_all = qgetenv( "LC_ALL" );
00330     if( !old_lc_all.isEmpty() )
00331         qputenv( "KDESU_LC_ALL", old_lc_all );
00332     else
00333         unsetenv( "KDESU_LC_ALL" );
00334     qputenv("LC_ALL", "C");
00335 
00336     // From now on, terminal output goes through the tty.
00337 
00338     QByteArray path;
00339     if (command.contains('/'))
00340         path = command;
00341     else
00342     {
00343         QString file = KStandardDirs::findExe(command);
00344         if (file.isEmpty())
00345         {
00346             kError(900) << k_lineinfo << command << " not found\n";
00347             _exit(1);
00348         }
00349         path = QFile::encodeName(file);
00350     }
00351 
00352     const char **argp = (const char **)malloc((args.count()+2)*sizeof(char *));
00353 
00354     i = 0;
00355     argp[i++] = path;
00356     for (QList<QByteArray>::ConstIterator it=args.begin(); it!=args.end(); ++it, ++i)
00357         argp[i] = *it;
00358 
00359     argp[i] = NULL;
00360 
00361     execv(path, const_cast<char **>(argp));
00362     kError(900) << k_lineinfo << "execv(\"" << path << "\"): " << perror << "\n";
00363     _exit(1);
00364     return -1; // Shut up compiler. Never reached.
00365 }
00366 
00367 
00368 /*
00369  * Wait until the terminal is set into no echo mode. At least one su
00370  * (RH6 w/ Linux-PAM patches) sets noecho mode AFTER writing the Password:
00371  * prompt, using TCSAFLUSH. This flushes the terminal I/O queues, possibly
00372  * taking the password  with it. So we wait until no echo mode is set
00373  * before writing the password.
00374  * Note that this is done on the slave fd. While Linux allows tcgetattr() on
00375  * the master side, Solaris doesn't.
00376  */
00377 
00378 int PtyProcess::WaitSlave()
00379 {
00380     kDebug(900) << k_lineinfo << "Child pid " << m_Pid;
00381 
00382     struct termios tio;
00383     while (1)
00384     {
00385         if (!checkPid(m_Pid))
00386         {
00387             return -1;
00388         }
00389         if (!d->m_pPTY->tcGetAttr(&tio))
00390         {
00391             kError(900) << k_lineinfo << "tcgetattr(): " << perror << "\n";
00392             return -1;
00393         }
00394         if (tio.c_lflag & ECHO)
00395         {
00396             kDebug(900) << k_lineinfo << "Echo mode still on.\n";
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 exists, 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 
00435     while (1)
00436     {
00437         FD_SET(fd(), &fds);
00438 
00439         // specify timeout to make sure select() does not block, even if the
00440         // process is dead / non-responsive. It does not matter if we abort too
00441         // early. In that case 0 is returned, and we'll try again in the next
00442         // iteration. (As long as we don't consitently time out in each iteration)
00443         timeval timeout;
00444         timeout.tv_sec = 0;
00445         timeout.tv_usec = 100000;
00446         int ret = select(fd()+1, &fds, 0L, 0L, &timeout);
00447         if (ret == -1)
00448         {
00449             if (errno != EINTR)
00450             {
00451                 kError(900) << k_lineinfo << "select(): " << perror << "\n";
00452                 return -1;
00453             }
00454             ret = 0;
00455         }
00456 
00457         if (ret)
00458         {
00459             QByteArray output = readAll(false);
00460             bool lineStart = true;
00461             while (!output.isNull())
00462             {
00463                 if (!m_Exit.isEmpty())
00464                 {
00465                     // match exit string only at line starts
00466                     int pos = output.indexOf(m_Exit);
00467                     if ((pos >= 0) && ((pos == 0 && lineStart) || (output.at (pos - 1) == '\n')))
00468                     {
00469                         kill(m_Pid, SIGTERM);
00470                     }
00471                 }
00472                 if (m_bTerminal)
00473                 {
00474                     fputs(output, stdout);
00475                     fflush(stdout);
00476                 }
00477                 lineStart = output.endsWith( '\n' );
00478                 output = readAll(false);
00479             }
00480         }
00481 
00482         ret = checkPidExited(m_Pid);
00483         if (ret == Error)
00484         {
00485             if (errno == ECHILD) return 0;
00486             else return 1;
00487         }
00488         else if (ret == Killed)
00489         {
00490             return 0;
00491         }
00492         else if (ret == NotExited)
00493         {
00494             // keep checking
00495         }
00496         else
00497         {
00498             return ret;
00499         }
00500     }
00501 }
00502 
00503 /*
00504  * SetupTTY: Creates a new session. The filedescriptor "fd" should be
00505  * connected to the tty. It is closed after the tty is reopened to make it
00506  * our controlling terminal. This way the tty is always opened at least once
00507  * so we'll never get EIO when reading from it.
00508  */
00509 
00510 int PtyProcess::setupTTY()
00511 {
00512     // Reset signal handlers
00513     for (int sig = 1; sig < NSIG; sig++)
00514         KDE_signal(sig, SIG_DFL);
00515     KDE_signal(SIGHUP, SIG_IGN);
00516 
00517     d->m_pPTY->setCTty();
00518 
00519     // Connect stdin, stdout and stderr
00520     int slave = d->m_pPTY->slaveFd();
00521     dup2(slave, 0); dup2(slave, 1); dup2(slave, 2);
00522 
00523     // Close all file handles
00524     // XXX this caused problems in KProcess - not sure why anymore. -- ???
00525     // Because it will close the start notification pipe. -- ossi
00526     struct rlimit rlp;
00527     getrlimit(RLIMIT_NOFILE, &rlp);
00528     for (int i = 3; i < (int)rlp.rlim_cur; i++)
00529         close(i);
00530 
00531     // Disable OPOST processing. Otherwise, '\n' are (on Linux at least)
00532     // translated to '\r\n'.
00533     struct ::termios tio;
00534     if (tcgetattr(0, &tio) < 0)
00535     {
00536         kError(900) << k_lineinfo << "tcgetattr(): " << perror << "\n";
00537         return -1;
00538     }
00539     tio.c_oflag &= ~OPOST;
00540     if (tcsetattr(0, TCSANOW, &tio) < 0)
00541     {
00542         kError(900) << k_lineinfo << "tcsetattr(): " << perror << "\n";
00543         return -1;
00544     }
00545 
00546     return 0;
00547 }
00548 
00549 void PtyProcess::virtual_hook( int, void* )
00550 { /*BASE::virtual_hook( id, data );*/ }
00551 
00552 }

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
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.4
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