/*

   $Id$

   This file is part of the KDE libraries
   Copyright (C) 1997 Christian Czezatke (e9025461@student.tuwien.ac.at)

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/


#include "kprocess.h"
#include "kprocctrl.h"
#include "kpty.h"

#include <config.h>

#ifdef __sgi
#define __svr4__
#endif

#ifdef __osf__
#define _OSF_SOURCE
#include <float.h>
#endif

#ifdef _AIX
#define _ALL_SOURCE
#endif

#ifdef Q_OS_UNIX
#include <sys/socket.h>
#include <sys/ioctl.h>
#endif

#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/wait.h>

#ifdef HAVE_SYS_STROPTS_H
#include <sys/stropts.h>	// Defines I_PUSH
#define _NEW_TTY_CTRL
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <time.h>
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>

#include <tqfile.h>
#include <tqsocketnotifier.h>
#include <tqapplication.h>

#include <kdebug.h>
#include <kstandarddirs.h>
#include <kuser.h>


//////////////////
// private data //
//////////////////

class TDEProcessPrivate {
public:
   TDEProcessPrivate() : 
     usePty(TDEProcess::NoCommunication),
     addUtmp(false), useShell(false),
#ifdef Q_OS_UNIX
     pty(0),
#endif
     priority(0)
   {
   }

   TDEProcess::Communication usePty;
   bool addUtmp : 1;
   bool useShell : 1;

#ifdef Q_OS_UNIX
   KPty *pty;
#endif

   int priority;

   TQMap<TQString,TQString> env;
   TQString wd;
   TQCString shell;
   TQCString executable;
};

/////////////////////////////
// public member functions //
/////////////////////////////

TDEProcess::TDEProcess( TQObject* parent, const char *name )
  : TQObject( parent, name ),
    run_mode(NotifyOnExit),
    runs(false),
    pid_(0),
    status(0),
    keepPrivs(false),
    innot(0),
    outnot(0),
    errnot(0),
    communication(NoCommunication),
    input_data(0),
    input_sent(0),
    input_total(0)
{
  TDEProcessController::ref();
  TDEProcessController::theTDEProcessController->addTDEProcess(this);

  d = new TDEProcessPrivate;

  out[0] = out[1] = -1;
  in[0] = in[1] = -1;
  err[0] = err[1] = -1;
}

TDEProcess::TDEProcess()
  : TQObject(),
    run_mode(NotifyOnExit),
    runs(false),
    pid_(0),
    status(0),
    keepPrivs(false),
    innot(0),
    outnot(0),
    errnot(0),
    communication(NoCommunication),
    input_data(0),
    input_sent(0),
    input_total(0)
{
  TDEProcessController::ref();
  TDEProcessController::theTDEProcessController->addTDEProcess(this);

  d = new TDEProcessPrivate;

  out[0] = out[1] = -1;
  in[0] = in[1] = -1;
  err[0] = err[1] = -1;
}

void
TDEProcess::setEnvironment(const TQString &name, const TQString &value)
{
   d->env.insert(name, value);
}

void
TDEProcess::setWorkingDirectory(const TQString &dir)
{
   d->wd = dir;   
} 

void 
TDEProcess::setupEnvironment()
{
   TQMap<TQString,TQString>::Iterator it;
   for(it = d->env.begin(); it != d->env.end(); ++it)
   {
      setenv(TQFile::encodeName(it.key()).data(),
             TQFile::encodeName(it.data()).data(), 1);
   }
   if (!d->wd.isEmpty())
   {
      chdir(TQFile::encodeName(d->wd).data());
   }
}

void
TDEProcess::setRunPrivileged(bool keepPrivileges)
{
   keepPrivs = keepPrivileges;
}

bool
TDEProcess::runPrivileged() const
{
   return keepPrivs;
}

bool
TDEProcess::setPriority(int prio)
{
#ifdef Q_OS_UNIX
    if (runs) {
        if (setpriority(PRIO_PROCESS, pid_, prio))
            return false;
    } else {
        if (prio > 19 || prio < (geteuid() ? getpriority(PRIO_PROCESS, 0) : -20))
            return false;
    }
#endif
    d->priority = prio;
    return true;
}

TDEProcess::~TDEProcess()
{
  if (run_mode != DontCare)
    kill(SIGKILL);
  detach();

#ifdef Q_OS_UNIX
  delete d->pty;
#endif
  delete d;

  TDEProcessController::theTDEProcessController->removeTDEProcess(this);
  TDEProcessController::deref();
}

void TDEProcess::detach()
{
  if (runs) {
    TDEProcessController::theTDEProcessController->addProcess(pid_);
    runs = false;
    pid_ = 0; // close without draining
    commClose(); // Clean up open fd's and socket notifiers.
  }
}

void TDEProcess::setBinaryExecutable(const char *filename)
{
   d->executable = filename;
}

bool TDEProcess::setExecutable(const TQString& proc)
{
  if (runs) return false;

  if (proc.isEmpty())  return false;

  if (!arguments.isEmpty())
     arguments.remove(arguments.begin());
  arguments.prepend(TQFile::encodeName(proc));

  return true;
}

TDEProcess &TDEProcess::operator<<(const TQStringList& args)
{
  TQStringList::ConstIterator it = args.begin();
  for ( ; it != args.end() ; ++it )
      arguments.append(TQFile::encodeName(*it));
  return *this;
}

TDEProcess &TDEProcess::operator<<(const TQCString& arg)
{
  return operator<< (arg.data());
}

TDEProcess &TDEProcess::operator<<(const char* arg)
{
  arguments.append(arg);
  return *this;
}

TDEProcess &TDEProcess::operator<<(const TQString& arg)
{
  arguments.append(TQFile::encodeName(arg));
  return *this;
}

void TDEProcess::clearArguments()
{
  arguments.clear();
}

bool TDEProcess::start(RunMode runmode, Communication comm)
{
  if (runs) {
    kdDebug(175) << "Attempted to start an already running process" << endl;
    return false;
  }

  uint n = arguments.count();
  if (n == 0) {
    kdDebug(175) << "Attempted to start a process without arguments" << endl;
    return false;
  }
#ifdef Q_OS_UNIX
  char **arglist;
  TQCString shellCmd;
  if (d->useShell)
  {
      if (d->shell.isEmpty()) {
        kdDebug(175) << "Invalid shell specified" << endl;
        return false;
      }

      for (uint i = 0; i < n; i++) {
          shellCmd += arguments[i];
          shellCmd += " "; // CC: to separate the arguments
      }

      arglist = static_cast<char **>(malloc( 4 * sizeof(char *)));
      arglist[0] = d->shell.data();
      arglist[1] = (char *) "-c";
      arglist[2] = shellCmd.data();
      arglist[3] = 0;
  }
  else
  {
      arglist = static_cast<char **>(malloc( (n + 1) * sizeof(char *)));
      for (uint i = 0; i < n; i++)
         arglist[i] = arguments[i].data();
      arglist[n] = 0;
  }

  run_mode = runmode;

  if (!setupCommunication(comm))
  {
      kdDebug(175) << "Could not setup Communication!" << endl;
      free(arglist);
      return false;
  }

  // We do this in the parent because if we do it in the child process
  // gdb gets confused when the application runs from gdb.
#ifdef HAVE_INITGROUPS
  struct passwd *pw = geteuid() ? 0 : getpwuid(getuid());
#endif

  int fd[2];
  if (pipe(fd))
     fd[0] = fd[1] = -1; // Pipe failed.. continue

  // we don't use vfork() because
  // - it has unclear semantics and is not standardized
  // - we do way too much magic in the child
  pid_ = fork();
  if (pid_ == 0) {
        // The child process

        close(fd[0]);
        // Closing of fd[1] indicates that the execvp() succeeded!
        fcntl(fd[1], F_SETFD, FD_CLOEXEC);

        if (!commSetupDoneC())
          kdDebug(175) << "Could not finish comm setup in child!" << endl;

        // reset all signal handlers
        struct sigaction act;
        sigemptyset(&act.sa_mask);
        act.sa_handler = SIG_DFL;
        act.sa_flags = 0;
        for (int sig = 1; sig < NSIG; sig++)
          sigaction(sig, &act, 0L);

        if (d->priority)
            setpriority(PRIO_PROCESS, 0, d->priority);

        if (!runPrivileged())
        {
           setgid(getgid());
#ifdef HAVE_INITGROUPS
           if (pw)
              initgroups(pw->pw_name, pw->pw_gid);
#endif
	   if (geteuid() != getuid())
	       setuid(getuid());
	   if (geteuid() != getuid())
	       _exit(1);
        }

        setupEnvironment();

        if (runmode == DontCare || runmode == OwnGroup)
          setsid();

        const char *executable = arglist[0];
        if (!d->executable.isEmpty())
           executable = d->executable.data();
        execvp(executable, arglist);

        char resultByte = 1;
        write(fd[1], &resultByte, 1);
        _exit(-1);
  } else if (pid_ == -1) {
        // forking failed

        // commAbort();
        pid_ = 0;
        free(arglist);
        return false;
  }
  // the parent continues here
  free(arglist);

  if (!commSetupDoneP())
    kdDebug(175) << "Could not finish comm setup in parent!" << endl;

  // Check whether client could be started.
  close(fd[1]);
  for(;;)
  {
     char resultByte;
     int n = ::read(fd[0], &resultByte, 1);
     if (n == 1)
     {
         // exec() failed
         close(fd[0]);
         waitpid(pid_, 0, 0);
         pid_ = 0;
         commClose();
         return false;
     }
     if (n == -1)
     {
        if (errno == EINTR)
           continue; // Ignore
     }
     break; // success
  }
  close(fd[0]);

  runs = true;
  switch (runmode)
  {
  case Block:
    for (;;)
    {
      commClose(); // drain only, unless obsolete reimplementation
      if (!runs)
      {
        // commClose detected data on the process exit notifification pipe
        TDEProcessController::theTDEProcessController->unscheduleCheck();
        if (waitpid(pid_, &status, WNOHANG) != 0) // error finishes, too
        {
          commClose(); // this time for real (runs is false)
          TDEProcessController::theTDEProcessController->rescheduleCheck();
          break;
        }
        runs = true; // for next commClose() iteration
      }
      else
      {
        // commClose is an obsolete reimplementation and waited until
        // all output channels were closed (or it was interrupted).
        // there is a chance that it never gets here ...
        waitpid(pid_, &status, 0);
        runs = false;
        break;
      }
    }
    // why do we do this? i think this signal should be emitted _only_
    // after the process has successfully run _asynchronously_ --ossi
    emit processExited(this);
    break;
  default: // NotifyOnExit & OwnGroup
    input_data = 0; // Discard any data for stdin that might still be there
    break;
  }
  return true;
#else
  //TODO
  return false;
#endif
}



bool TDEProcess::kill(int signo)
{
#ifdef Q_OS_UNIX
  if (runs && pid_ > 0 && !::kill(run_mode == OwnGroup ? -pid_ : pid_, signo))
    return true;
#endif
  return false;
}



bool TDEProcess::isRunning() const
{
  return runs;
}



pid_t TDEProcess::pid() const
{
  return pid_;
}

#ifndef timersub
# define timersub(a, b, result) \
  do { \
    (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
    (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
    if ((result)->tv_usec < 0) { \
      --(result)->tv_sec; \
      (result)->tv_usec += 1000000; \
    } \
  } while (0)
#endif

bool TDEProcess::wait(int timeout)
{
  if (!runs)
    return true;

#ifndef __linux__
  struct timeval etv;
#endif
  struct timeval tv, *tvp;
  if (timeout < 0)
    tvp = 0;
  else
  {
#ifndef __linux__
    gettimeofday(&etv, 0);
    etv.tv_sec += timeout;
#else
    tv.tv_sec = timeout;
    tv.tv_usec = 0;
#endif
    tvp = &tv;
  }

#ifdef Q_OS_UNIX
  int fd = TDEProcessController::theTDEProcessController->notifierFd();
  for(;;)
  {
    fd_set fds;
    FD_ZERO( &fds );
    FD_SET( fd, &fds );

#ifndef __linux__
    if (tvp)
    {
      gettimeofday(&tv, 0);
      timersub(&etv, &tv, &tv);
      if (tv.tv_sec < 0)
        tv.tv_sec = tv.tv_usec = 0;
    }
#endif

    switch( select( fd+1, &fds, 0, 0, tvp ) )
    {
    case -1:
      if( errno == EINTR )
        break;
      // fall through; should happen if tvp->tv_sec < 0
    case 0:
      TDEProcessController::theTDEProcessController->rescheduleCheck();
      return false;
    default:
      TDEProcessController::theTDEProcessController->unscheduleCheck();
      if (waitpid(pid_, &status, WNOHANG) != 0) // error finishes, too
      {
        processHasExited(status);
        TDEProcessController::theTDEProcessController->rescheduleCheck();
        return true;
      }
    }
  }
#endif //Q_OS_UNIX
  return false;
}



bool TDEProcess::normalExit() const
{
  return (pid_ != 0) && !runs && WIFEXITED(status);
}


bool TDEProcess::signalled() const
{
  return (pid_ != 0) && !runs && WIFSIGNALED(status);
}


bool TDEProcess::coreDumped() const
{
#ifdef WCOREDUMP
  return signalled() && WCOREDUMP(status);
#else
  return false;
#endif
}


int TDEProcess::exitStatus() const
{
  return WEXITSTATUS(status);
}


int TDEProcess::exitSignal() const
{
  return WTERMSIG(status);
}


bool TDEProcess::writeStdin(const char *buffer, int buflen)
{
  // if there is still data pending, writing new data
  // to stdout is not allowed (since it could also confuse
  // kprocess ...)
  if (input_data != 0)
    return false;

  if (communication & Stdin) {
    input_data = buffer;
    input_sent = 0;
    input_total = buflen;
    innot->setEnabled(true);
    if (input_total)
       slotSendData(0);
    return true;
  } else
    return false;
}

void TDEProcess::suspend()
{
  if (outnot)
     outnot->setEnabled(false);
}

void TDEProcess::resume()
{
  if (outnot)
     outnot->setEnabled(true);
}

bool TDEProcess::closeStdin()
{
  if (communication & Stdin) {
    communication = (Communication) (communication & ~Stdin);
    delete innot;
    innot = 0;
    if (!(d->usePty & Stdin))
      close(in[1]);
    in[1] = -1;
    return true;
  } else
    return false;
}

bool TDEProcess::closeStdout()
{
  if (communication & Stdout) {
    communication = (Communication) (communication & ~Stdout);
    delete outnot;
    outnot = 0;
    if (!(d->usePty & Stdout))
      close(out[0]);
    out[0] = -1;
    return true;
  } else
    return false;
}

bool TDEProcess::closeStderr()
{
  if (communication & Stderr) {
    communication = (Communication) (communication & ~Stderr);
    delete errnot;
    errnot = 0;
    if (!(d->usePty & Stderr))
      close(err[0]);
    err[0] = -1;
    return true;
  } else
    return false;
}

bool TDEProcess::closePty()
{
#ifdef Q_OS_UNIX
  if (d->pty && d->pty->masterFd() >= 0) {
    if (d->addUtmp)
      d->pty->logout();
    d->pty->close();
    return true;
  } else
    return false;
#else
    return false;
#endif
}

void TDEProcess::closeAll()
{
  closeStdin();
  closeStdout();
  closeStderr();
  closePty();
}

/////////////////////////////
// protected slots         //
/////////////////////////////



void TDEProcess::slotChildOutput(int fdno)
{
  if (!childOutput(fdno))
     closeStdout();
}


void TDEProcess::slotChildError(int fdno)
{
  if (!childError(fdno))
     closeStderr();
}


void TDEProcess::slotSendData(int)
{
  if (input_sent == input_total) {
    innot->setEnabled(false);
    input_data = 0;
    emit wroteStdin(this);
  } else {
    int result = ::write(in[1], input_data+input_sent, input_total-input_sent);
    if (result >= 0)
    {
       input_sent += result;
    }
    else if ((errno != EAGAIN) && (errno != EINTR))
    {
       kdDebug(175) << "Error writing to stdin of child process" << endl;
       closeStdin();
    }
  }
}

void TDEProcess::setUseShell(bool useShell, const char *shell)
{
  d->useShell = useShell;
  if (shell && *shell)
    d->shell = shell;
  else
// #ifdef NON_FREE // ... as they ship non-POSIX /bin/sh
#if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__GNU__) && !defined(__DragonFly__)
  // Solaris POSIX ...
  if (!access( "/usr/xpg4/bin/sh", X_OK ))
    d->shell = "/usr/xpg4/bin/sh";
  else
  // ... which links here anyway
  if (!access( "/bin/ksh", X_OK ))
    d->shell = "/bin/ksh";
  else
  // dunno, maybe superfluous?
  if (!access( "/usr/ucb/sh", X_OK ))
    d->shell = "/usr/ucb/sh";
  else
#endif
    d->shell = "/bin/sh";
}

#ifdef Q_OS_UNIX
void TDEProcess::setUsePty(Communication usePty, bool addUtmp)
{
  d->usePty = usePty;
  d->addUtmp = addUtmp;
  if (usePty) {
    if (!d->pty)
      d->pty = new KPty;
  } else {
    delete d->pty;
    d->pty = 0;
  }
}

KPty *TDEProcess::pty() const
{
  return d->pty;
}
#endif //Q_OS_UNIX

TQString TDEProcess::quote(const TQString &arg)
{
    TQChar q('\'');
    return TQString(arg).replace(q, "'\\''").prepend(q).append(q);
}


//////////////////////////////
// private member functions //
//////////////////////////////


void TDEProcess::processHasExited(int state)
{
    // only successfully run NotifyOnExit processes ever get here

    status = state;
    runs = false; // do this before commClose, so it knows we're dead

    commClose(); // cleanup communication sockets

    if (run_mode != DontCare)
      emit processExited(this);
}



int TDEProcess::childOutput(int fdno)
{
  if (communication & NoRead) {
     int len = -1;
     emit receivedStdout(fdno, len);
     errno = 0; // Make sure errno doesn't read "EAGAIN"
     return len;
  }
  else
  {
     char buffer[1025];
     int len;

     len = ::read(fdno, buffer, 1024);
     
     if (len > 0) {
        buffer[len] = 0; // Just in case.
        emit receivedStdout(this, buffer, len);
     }
     return len;
  }
}

int TDEProcess::childError(int fdno)
{
  char buffer[1025];
  int len;

  len = ::read(fdno, buffer, 1024);

  if (len > 0) {
     buffer[len] = 0; // Just in case.
     emit receivedStderr(this, buffer, len);
  }
  return len;
}


int TDEProcess::setupCommunication(Communication comm)
{
#ifdef Q_OS_UNIX
  // PTY stuff //
  if (d->usePty)
  {
    // cannot communicate on both stderr and stdout if they are both on the pty
    if (!(~(comm & d->usePty) & (Stdout | Stderr))) {
       kdWarning(175) << "Invalid usePty/communication combination (" << d->usePty << "/" << comm << ")" << endl;
       return 0;
    }
    if (!d->pty->open())
       return 0;

    int rcomm = comm & d->usePty;
    int mfd = d->pty->masterFd();
    if (rcomm & Stdin)
      in[1] = mfd;
    if (rcomm & Stdout)
      out[0] = mfd;
    if (rcomm & Stderr)
      err[0] = mfd;
  }

  communication = comm;

  comm = (Communication) (comm & ~d->usePty);
  if (comm & Stdin) {
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, in))
      goto fail0;
    fcntl(in[0], F_SETFD, FD_CLOEXEC);
    fcntl(in[1], F_SETFD, FD_CLOEXEC);
  }
  if (comm & Stdout) {
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, out))
      goto fail1;
    fcntl(out[0], F_SETFD, FD_CLOEXEC);
    fcntl(out[1], F_SETFD, FD_CLOEXEC);
  }
  if (comm & Stderr) {
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, err))
      goto fail2;
    fcntl(err[0], F_SETFD, FD_CLOEXEC);
    fcntl(err[1], F_SETFD, FD_CLOEXEC);
  }
  return 1; // Ok
 fail2:
  if (comm & Stdout)
  {
    close(out[0]);
    close(out[1]);
    out[0] = out[1] = -1;
  }
 fail1:
  if (comm & Stdin)
  {
    close(in[0]);
    close(in[1]);
    in[0] = in[1] = -1;
  }
 fail0:
  communication = NoCommunication;
#endif //Q_OS_UNIX
  return 0; // Error
}



int TDEProcess::commSetupDoneP()
{
  int rcomm = communication & ~d->usePty;
  if (rcomm & Stdin)
    close(in[0]);
  if (rcomm & Stdout)
    close(out[1]);
  if (rcomm & Stderr)
    close(err[1]);
  in[0] = out[1] = err[1] = -1;

  // Don't create socket notifiers if no interactive comm is to be expected
  if (run_mode != NotifyOnExit && run_mode != OwnGroup)
    return 1;

  if (communication & Stdin) {
    fcntl(in[1], F_SETFL, O_NONBLOCK | fcntl(in[1], F_GETFL));
    innot =  new TQSocketNotifier(in[1], TQSocketNotifier::Write, this);
    TQ_CHECK_PTR(innot);
    innot->setEnabled(false); // will be enabled when data has to be sent
    TQObject::connect(innot, TQT_SIGNAL(activated(int)),
                     this, TQT_SLOT(slotSendData(int)));
  }

  if (communication & Stdout) {
    outnot = new TQSocketNotifier(out[0], TQSocketNotifier::Read, this);
    TQ_CHECK_PTR(outnot);
    TQObject::connect(outnot, TQT_SIGNAL(activated(int)),
                     this, TQT_SLOT(slotChildOutput(int)));
    if (communication & NoRead)
        suspend();
  }

  if (communication & Stderr) {
    errnot = new TQSocketNotifier(err[0], TQSocketNotifier::Read, this );
    TQ_CHECK_PTR(errnot);
    TQObject::connect(errnot, TQT_SIGNAL(activated(int)),
                     this, TQT_SLOT(slotChildError(int)));
  }

  return 1;
}



int TDEProcess::commSetupDoneC()
{
  int ok = 1;
#ifdef Q_OS_UNIX

  if (d->usePty & Stdin) {
    if (dup2(d->pty->slaveFd(), STDIN_FILENO) < 0) ok = 0;
  } else if (communication & Stdin) {
    if (dup2(in[0], STDIN_FILENO) < 0) ok = 0;
  } else {
    int null_fd = open( "/dev/null", O_RDONLY );
    if (dup2( null_fd, STDIN_FILENO ) < 0) ok = 0;
    close( null_fd );
  }
  struct linger so;
  memset(&so, 0, sizeof(so));
  if (d->usePty & Stdout) {
    if (dup2(d->pty->slaveFd(), STDOUT_FILENO) < 0) ok = 0;
  } else if (communication & Stdout) {
    if (dup2(out[1], STDOUT_FILENO) < 0 ||
        setsockopt(out[1], SOL_SOCKET, SO_LINGER, (char *)&so, sizeof(so)))
      ok = 0;
    if (communication & MergedStderr) {
      if (dup2(out[1], STDERR_FILENO) < 0)
        ok = 0;
    }
  }
  if (d->usePty & Stderr) {
    if (dup2(d->pty->slaveFd(), STDERR_FILENO) < 0) ok = 0;
  } else if (communication & Stderr) {
    if (dup2(err[1], STDERR_FILENO) < 0 ||
        setsockopt(err[1], SOL_SOCKET, SO_LINGER, (char *)&so, sizeof(so)))
      ok = 0;
  }

  // don't even think about closing all open fds here or anywhere else

  // PTY stuff //
  if (d->usePty) {
    d->pty->setCTty();
    if (d->addUtmp)
      d->pty->login(KUser(KUser::UseRealUserID).loginName().local8Bit().data(), getenv("DISPLAY"));
  }
#endif //Q_OS_UNIX

  return ok;
}



void TDEProcess::commClose()
{
  closeStdin();

#ifdef Q_OS_UNIX
  if (pid_) { // detached, failed, and killed processes have no output. basta. :)
    // If both channels are being read we need to make sure that one socket
    // buffer doesn't fill up whilst we are waiting for data on the other
    // (causing a deadlock). Hence we need to use select.

    int notfd = TDEProcessController::theTDEProcessController->notifierFd();

    while ((communication & (Stdout | Stderr)) || runs) {
      fd_set rfds;
      FD_ZERO(&rfds);
      struct timeval timeout, *p_timeout;

      int max_fd = 0;
      if (communication & Stdout) {
        FD_SET(out[0], &rfds);
        max_fd = out[0];
      }
      if (communication & Stderr) {
        FD_SET(err[0], &rfds);
        if (err[0] > max_fd)
          max_fd = err[0];
      }
      if (runs) {
        FD_SET(notfd, &rfds);
        if (notfd > max_fd)
          max_fd = notfd;
        // If the process is still running we block until we
        // receive data or the process exits.
        p_timeout = 0; // no timeout
      } else {
        // If the process has already exited, we only check
        // the available data, we don't wait for more.
        timeout.tv_sec = timeout.tv_usec = 0; // timeout immediately
        p_timeout = &timeout;
      }

      int fds_ready = select(max_fd+1, &rfds, 0, 0, p_timeout);
      if (fds_ready < 0) {
        if (errno == EINTR)
          continue;
        break;
      } else if (!fds_ready)
        break;

      if ((communication & Stdout) && FD_ISSET(out[0], &rfds))
        slotChildOutput(out[0]);

      if ((communication & Stderr) && FD_ISSET(err[0], &rfds))
        slotChildError(err[0]);

      if (runs && FD_ISSET(notfd, &rfds)) {
        runs = false; // hack: signal potential exit
        return; // don't close anything, we will be called again
      }
    }
  }
#endif //Q_OS_UNIX

  closeStdout();
  closeStderr();

  closePty();
}


void TDEProcess::virtual_hook( int, void* )
{ /*BASE::virtual_hook( id, data );*/ }


///////////////////////////
// CC: Class KShellProcess
///////////////////////////

KShellProcess::KShellProcess(const char *shellname):
  TDEProcess()
{
  setUseShell( true, shellname ? shellname : getenv("SHELL") );
}

KShellProcess::~KShellProcess() {
}

TQString KShellProcess::quote(const TQString &arg)
{
    return TDEProcess::quote(arg);
}

bool KShellProcess::start(RunMode runmode, Communication comm)
{
  return TDEProcess::start(runmode, comm);
}

void KShellProcess::virtual_hook( int id, void* data )
{ TDEProcess::virtual_hook( id, data ); }

#include "kprocess.moc"
