some more win32'fication to fix non-ascii filename handling
[kdelibs.git] / kdesu / process.cpp
blob637e8b4ec358249c45b749a32b067849c6a1ff08
1 /* vi: ts=8 sts=4 sw=4
3 * This file is part of the KDE project, module kdesu.
4 * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org>
6 * This file contains code from TEShell.C of the KDE konsole.
7 * Copyright (c) 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
9 * This is free software; you can use this library under the GNU Library
10 * General Public License, version 2. See the file "COPYING.LIB" for the
11 * exact licensing terms.
13 * process.cpp: Functionality to build a front end to password asking
14 * terminal programs.
17 #include "process.h"
18 #include "kcookie.h"
20 #include <config.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26 #include <signal.h>
27 #include <errno.h>
28 #include <string.h>
29 #include <termios.h>
31 #include <sys/types.h>
32 #include <sys/wait.h>
33 #include <sys/stat.h>
34 #include <sys/time.h>
35 #include <sys/resource.h>
37 #ifdef HAVE_SYS_SELECT_H
38 #include <sys/select.h> // Needed on some systems.
39 #endif
41 #include <QtCore/QBool>
42 #include <QtCore/QFile>
44 #include <ksharedconfig.h>
45 #include <kconfiggroup.h>
46 #include <kdebug.h>
47 #include <kstandarddirs.h>
48 #include <kde_file.h>
51 namespace KDESu {
53 using namespace KDESuPrivate;
56 ** Wait for @p ms miliseconds
57 ** @param fd file descriptor
58 ** @param ms time to wait in miliseconds
59 ** @return
61 int PtyProcess::waitMS(int fd,int ms)
63 struct timeval tv;
64 tv.tv_sec = 0;
65 tv.tv_usec = 1000*ms;
67 fd_set fds;
68 FD_ZERO(&fds);
69 FD_SET(fd,&fds);
70 return select(fd+1, &fds, 0L, 0L, &tv);
73 // XXX this function is nonsense:
74 // - for our child, we could use waitpid().
75 // - the configurability at this place it *complete* braindamage
77 ** Basic check for the existence of @p pid.
78 ** Returns true iff @p pid is an extant process.
80 bool PtyProcess::checkPid(pid_t pid)
82 KSharedConfig::Ptr config = KGlobal::config();
83 KConfigGroup cg(config, "super-user-command");
84 QString superUserCommand = cg.readEntry("super-user-command", "sudo");
85 //sudo does not accept signals from user so we except it
86 if (superUserCommand == "sudo") {
87 return true;
88 } else {
89 return kill(pid, 0) == 0;
94 ** Check process exit status for process @p pid.
95 ** On error (no child, no exit), return Error (-1).
96 ** If child @p pid has exited, return its exit status,
97 ** (which may be zero).
98 ** If child @p has not exited, return NotExited (-2).
101 int PtyProcess::checkPidExited(pid_t pid)
103 int state, ret;
104 ret = waitpid(pid, &state, WNOHANG);
106 if (ret < 0)
108 kError(900) << k_lineinfo << "waitpid(): " << perror << "\n";
109 return Error;
111 if (ret == pid)
113 if (WIFEXITED(state))
114 return WEXITSTATUS(state);
115 return Killed;
118 return NotExited;
122 class PtyProcess::PtyProcessPrivate
124 public:
125 PtyProcessPrivate() : m_pPTY(0L) {}
126 ~PtyProcessPrivate()
128 delete m_pPTY;
130 QList<QByteArray> env;
131 KPty *m_pPTY;
132 QByteArray m_Inbuf;
136 PtyProcess::PtyProcess()
137 :d(new PtyProcessPrivate)
139 m_bTerminal = false;
140 m_bErase = false;
144 int PtyProcess::init()
146 delete d->m_pPTY;
147 d->m_pPTY = new KPty();
148 if (!d->m_pPTY->open())
150 kError(900) << k_lineinfo << "Failed to open PTY.\n";
151 return -1;
153 d->m_Inbuf.resize(0);
154 return 0;
158 PtyProcess::~PtyProcess()
160 delete d;
163 /** Set additional environment variables. */
164 void PtyProcess::setEnvironment( const QList<QByteArray> &env )
166 d->env = env;
169 int PtyProcess::fd() const
171 return d->m_pPTY ? d->m_pPTY->masterFd() : -1;
174 int PtyProcess::pid() const
176 return m_Pid;
179 /** Returns the additional environment variables set by setEnvironment() */
180 QList<QByteArray> PtyProcess::environment() const
182 return d->env;
186 QByteArray PtyProcess::readAll(bool block)
188 QByteArray ret;
189 if (!d->m_Inbuf.isEmpty())
191 // if there is still something in the buffer, we need not block.
192 // we should still try to read any further output, from the fd, though.
193 block = false;
194 ret = d->m_Inbuf;
195 d->m_Inbuf.resize(0);
198 int flags = fcntl(fd(), F_GETFL);
199 if (flags < 0)
201 kError(900) << k_lineinfo << "fcntl(F_GETFL): " << perror << "\n";
202 return ret;
204 int oflags = flags;
205 if (block)
206 flags &= ~O_NONBLOCK;
207 else
208 flags |= O_NONBLOCK;
210 if ((flags != oflags) && (fcntl(fd(), F_SETFL, flags) < 0))
212 // We get an error here when the child process has closed
213 // the file descriptor already.
214 return ret;
217 int nbytes;
218 char buf[256];
219 while (1)
221 nbytes = read(fd(), buf, 255);
222 if (nbytes == -1)
224 if (errno == EINTR)
225 continue;
226 else break;
228 if (nbytes == 0)
229 break; // nothing available / eof
231 buf[nbytes] = '\000';
232 ret += buf;
233 break;
236 return ret;
240 QByteArray PtyProcess::readLine(bool block)
242 d->m_Inbuf = readAll(block);
244 int pos;
245 QByteArray ret;
246 if (!d->m_Inbuf.isEmpty())
248 pos = d->m_Inbuf.indexOf('\n');
249 if (pos == -1)
251 // NOTE: this means we return something even if there in no full line!
252 ret = d->m_Inbuf;
253 d->m_Inbuf.resize(0);
254 } else
256 ret = d->m_Inbuf.left(pos);
257 d->m_Inbuf = d->m_Inbuf.mid(pos+1);
261 return ret;
265 void PtyProcess::writeLine(const QByteArray &line, bool addnl)
267 if (!line.isEmpty())
268 write(fd(), line, line.length());
269 if (addnl)
270 write(fd(), "\n", 1);
274 void PtyProcess::unreadLine(const QByteArray &line, bool addnl)
276 QByteArray tmp = line;
277 if (addnl)
278 tmp += '\n';
279 if (!tmp.isEmpty())
280 d->m_Inbuf.prepend(tmp);
283 void PtyProcess::setExitString(const QByteArray &exit)
285 m_Exit = exit;
289 * Fork and execute the command. This returns in the parent.
292 int PtyProcess::exec(const QByteArray &command, const QList<QByteArray> &args)
294 kDebug(900) << k_lineinfo << "Running `" << command << "'\n";
295 int i;
297 if (init() < 0)
298 return -1;
300 if ((m_Pid = fork()) == -1)
302 kError(900) << k_lineinfo << "fork(): " << perror << "\n";
303 return -1;
306 // Parent
307 if (m_Pid)
309 d->m_pPTY->closeSlave();
310 return 0;
313 // Child
314 if (setupTTY() < 0)
315 _exit(1);
317 for (i = 0; i < d->env.count(); ++i)
319 putenv(const_cast<char *>(d->env.at(i).constData()));
321 unsetenv("KDE_FULL_SESSION");
322 // for : Qt: Session management error
323 unsetenv("SESSION_MANAGER");
324 // QMutex::lock , deadlocks without that.
325 // <thiago> you cannot connect to the user's session bus from another UID
326 unsetenv("DBUS_SESSION_BUS_ADDRESS");
328 // set temporarily LC_ALL to C, for su (to be able to parse "Password:")
329 const QByteArray old_lc_all = qgetenv( "LC_ALL" );
330 if( !old_lc_all.isEmpty() )
331 qputenv( "KDESU_LC_ALL", old_lc_all );
332 else
333 unsetenv( "KDESU_LC_ALL" );
334 qputenv("LC_ALL", "C");
336 // From now on, terminal output goes through the tty.
338 QByteArray path;
339 if (command.contains('/'))
340 path = command;
341 else
343 QString file = KStandardDirs::findExe(command);
344 if (file.isEmpty())
346 kError(900) << k_lineinfo << command << " not found\n";
347 _exit(1);
349 path = QFile::encodeName(file);
352 const char **argp = (const char **)malloc((args.count()+2)*sizeof(char *));
354 i = 0;
355 argp[i++] = path;
356 for (QList<QByteArray>::ConstIterator it=args.begin(); it!=args.end(); ++it, ++i)
357 argp[i] = *it;
359 argp[i] = NULL;
361 execv(path, const_cast<char **>(argp));
362 kError(900) << k_lineinfo << "execv(\"" << path << "\"): " << perror << "\n";
363 _exit(1);
364 return -1; // Shut up compiler. Never reached.
369 * Wait until the terminal is set into no echo mode. At least one su
370 * (RH6 w/ Linux-PAM patches) sets noecho mode AFTER writing the Password:
371 * prompt, using TCSAFLUSH. This flushes the terminal I/O queues, possibly
372 * taking the password with it. So we wait until no echo mode is set
373 * before writing the password.
374 * Note that this is done on the slave fd. While Linux allows tcgetattr() on
375 * the master side, Solaris doesn't.
378 int PtyProcess::WaitSlave()
380 kDebug(900) << k_lineinfo << "Child pid " << m_Pid;
382 struct termios tio;
383 while (1)
385 if (!checkPid(m_Pid))
387 return -1;
389 if (!d->m_pPTY->tcGetAttr(&tio))
391 kError(900) << k_lineinfo << "tcgetattr(): " << perror << "\n";
392 return -1;
394 if (tio.c_lflag & ECHO)
396 kDebug(900) << k_lineinfo << "Echo mode still on.\n";
397 usleep(10000);
398 continue;
400 break;
402 return 0;
406 int PtyProcess::enableLocalEcho(bool enable)
408 return d->m_pPTY->setEcho(enable) ? 0 : -1;
412 void PtyProcess::setTerminal(bool terminal)
414 m_bTerminal = terminal;
417 void PtyProcess::setErase(bool erase)
419 m_bErase = erase;
423 * Copy output to stdout until the child process exists, or a line of output
424 * matches `m_Exit'.
425 * We have to use waitpid() to test for exit. Merely waiting for EOF on the
426 * pty does not work, because the target process may have children still
427 * attached to the terminal.
430 int PtyProcess::waitForChild()
432 fd_set fds;
433 FD_ZERO(&fds);
435 while (1)
437 FD_SET(fd(), &fds);
439 // specify timeout to make sure select() does not block, even if the
440 // process is dead / non-responsive. It does not matter if we abort too
441 // early. In that case 0 is returned, and we'll try again in the next
442 // iteration. (As long as we don't consitently time out in each iteration)
443 timeval timeout;
444 timeout.tv_sec = 0;
445 timeout.tv_usec = 100000;
446 int ret = select(fd()+1, &fds, 0L, 0L, &timeout);
447 if (ret == -1)
449 if (errno != EINTR)
451 kError(900) << k_lineinfo << "select(): " << perror << "\n";
452 return -1;
454 ret = 0;
457 if (ret)
459 QByteArray output = readAll(false);
460 bool lineStart = true;
461 while (!output.isNull())
463 if (!m_Exit.isEmpty())
465 // match exit string only at line starts
466 int pos = output.indexOf(m_Exit);
467 if ((pos >= 0) && ((pos == 0 && lineStart) || (output.at (pos - 1) == '\n')))
469 kill(m_Pid, SIGTERM);
472 if (m_bTerminal)
474 fputs(output, stdout);
475 fflush(stdout);
477 lineStart = output.endsWith( '\n' );
478 output = readAll(false);
482 ret = checkPidExited(m_Pid);
483 if (ret == Error)
485 if (errno == ECHILD) return 0;
486 else return 1;
488 else if (ret == Killed)
490 return 0;
492 else if (ret == NotExited)
494 // keep checking
496 else
498 return ret;
504 * SetupTTY: Creates a new session. The filedescriptor "fd" should be
505 * connected to the tty. It is closed after the tty is reopened to make it
506 * our controlling terminal. This way the tty is always opened at least once
507 * so we'll never get EIO when reading from it.
510 int PtyProcess::setupTTY()
512 // Reset signal handlers
513 for (int sig = 1; sig < NSIG; sig++)
514 KDE_signal(sig, SIG_DFL);
515 KDE_signal(SIGHUP, SIG_IGN);
517 d->m_pPTY->setCTty();
519 // Connect stdin, stdout and stderr
520 int slave = d->m_pPTY->slaveFd();
521 dup2(slave, 0); dup2(slave, 1); dup2(slave, 2);
523 // Close all file handles
524 // XXX this caused problems in KProcess - not sure why anymore. -- ???
525 // Because it will close the start notification pipe. -- ossi
526 struct rlimit rlp;
527 getrlimit(RLIMIT_NOFILE, &rlp);
528 for (int i = 3; i < (int)rlp.rlim_cur; i++)
529 close(i);
531 // Disable OPOST processing. Otherwise, '\n' are (on Linux at least)
532 // translated to '\r\n'.
533 struct ::termios tio;
534 if (tcgetattr(0, &tio) < 0)
536 kError(900) << k_lineinfo << "tcgetattr(): " << perror << "\n";
537 return -1;
539 tio.c_oflag &= ~OPOST;
540 if (tcsetattr(0, TCSANOW, &tio) < 0)
542 kError(900) << k_lineinfo << "tcsetattr(): " << perror << "\n";
543 return -1;
546 return 0;
549 void PtyProcess::virtual_hook( int, void* )
550 { /*BASE::virtual_hook( id, data );*/ }