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 }
KDE 4.6 API Reference