process.cpp

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