• Skip to content
  • Skip to link menu
Trinity API Reference
  • Trinity API Reference
  • tdesu
 

tdesu

process.cpp

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

tdesu

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

tdesu

Skip menu "tdesu"
  • arts
  • dcop
  • dnssd
  • interfaces
  •   kspeech
  •     interface
  •     library
  •   tdetexteditor
  • kate
  • kded
  • kdoctools
  • kimgio
  • kjs
  • libtdemid
  • libtdescreensaver
  • tdeabc
  • tdecmshell
  • tdecore
  • tdefx
  • tdehtml
  • tdeinit
  • tdeio
  •   bookmarks
  •   httpfilter
  •   kpasswdserver
  •   kssl
  •   tdefile
  •   tdeio
  •   tdeioexec
  • tdeioslave
  •   http
  • tdemdi
  •   tdemdi
  • tdenewstuff
  • tdeparts
  • tdeprint
  • tderandr
  • tderesources
  • tdespell2
  • tdesu
  • tdeui
  • tdeunittest
  • tdeutils
  • tdewallet
Generated for tdesu by doxygen 1.6.3
This website is maintained by Timothy Pearson.