/*
� � � � � � � �             This file is part of the Opie Project
 � � � � � �                Copyright (C) 2002-2004 Holger Freyther <zecke@handhelds.org>
                            and The Opie Team <opie-devel@handhelds.org>
             =.             Based on KProcess (C) 1997 Christian Czezatke (e9025461@student.tuwien.ac.at)
           .=l.
 � � � � �.>+-=
_;:, � � .> � �:=|.         This program is free software; you can
.> <`_, � > �. � <=          redistribute it and/or  modify it under
:`=1 )Y*s>-.-- � :           the terms of the GNU Library General Public
.="- .-=="i, � � .._         License as published by the Free Software
- . � .-<_> � � .<>         Foundation; either version 2 of the License,
 � �._= =} � � � :          or (at your option) any later version.
 � .%`+i> � � � _;_.
 � .i_,=:_. � � �-<s.       This program 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., 59 Temple Place - Suite 330,
                            Boston, MA 02111-1307, USA.
*/

#include "oprocctrl.h"

/* OPIE */
#include <opie2/oprocess.h>

/* QT */

#include <qapplication.h>
#include <qdir.h>
#include <qmap.h>
#include <qsocketnotifier.h>
#include <qtextstream.h>

/* STD */
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#ifdef HAVE_INITGROUPS
#include <grp.h>
#endif

class OProcessPrivate
{
public:
    OProcessPrivate() : useShell( false )
    { }

    bool useShell;
    QMap<QString, QString> env;
    QString wd;
    QCString shell;
};


OProcess::OProcess( QObject *parent, const char *name )
        : QObject( parent, name )
{
    init ( );
}

OProcess::OProcess( const QString &arg0, QObject *parent, const char *name )
        : QObject( parent, name )
{
    init ( );
    *this << arg0;
}

OProcess::OProcess( const QStringList &args, QObject *parent, const char *name )
        : QObject( parent, name )
{
    init ( );
    *this << args;
}

void OProcess::init ( )
{
    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;
    d = 0;

    if ( 0 == OProcessController::theOProcessController )
    {
        ( void ) new OProcessController();
        CHECK_PTR( OProcessController::theOProcessController );
    }

    OProcessController::theOProcessController->addOProcess( this );
    out[ 0 ] = out[ 1 ] = -1;
    in[ 0 ] = in[ 1 ] = -1;
    err[ 0 ] = err[ 1 ] = -1;
}

void OProcess::setEnvironment( const QString &name, const QString &value )
{
    if ( !d )
        d = new OProcessPrivate;
    d->env.insert( name, value );
}

void OProcess::setWorkingDirectory( const QString &dir )
{
    if ( !d )
        d = new OProcessPrivate;
    d->wd = dir;
}

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

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

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

OProcess::~OProcess()
{
    // destroying the OProcess instance sends a SIGKILL to the
    // child process (if it is running) after removing it from the
    // list of valid processes (if the process is not started as
    // "DontCare")

    OProcessController::theOProcessController->removeOProcess( this );
    // this must happen before we kill the child
    // TODO: block the signal while removing the current process from the process list

    if ( runs && ( run_mode != DontCare ) )
        kill( SIGKILL );

    // Clean up open fd's and socket notifiers.
    closeStdin();
    closeStdout();
    closeStderr();

    // TODO: restore SIGCHLD and SIGPIPE handler if this is the last OProcess
    delete d;
}

void OProcess::detach()
{
    OProcessController::theOProcessController->removeOProcess( this );

    runs = false;
    pid_ = 0;

    // Clean up open fd's and socket notifiers.
    closeStdin();
    closeStdout();
    closeStderr();
}

bool OProcess::setExecutable( const QString& proc )
{
    if ( runs )
        return false;

    if ( proc.isEmpty() )
        return false;

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

    return true;
}

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

OProcess &OProcess::operator<<( const QCString& arg )
{
    return operator<< ( arg.data() );
}

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

OProcess &OProcess::operator<<( const QString& arg )
{
    arguments.append( QFile::encodeName( arg ) );
    return *this;
}

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

bool OProcess::start( RunMode runmode, Communication comm )
{
    uint i;
    uint n = arguments.count();
    char **arglist;

    if ( runs || ( 0 == n ) )
    {
        return false;  // cannot start a process that is already running
        // or if no executable has been assigned
    }
    run_mode = runmode;
    status = 0;

    QCString shellCmd;
    if ( d && d->useShell )
    {
        if ( d->shell.isEmpty() )
        {
            qWarning( "Could not find a valid shell" );
            return false;
        }

        arglist = static_cast<char **>( malloc( ( 4 ) * sizeof( char * ) ) );
        for ( i = 0; i < n; i++ )
        {
            shellCmd += arguments[ i ];
            shellCmd += " "; // CC: to separate the arguments
        }

        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 ( i = 0; i < n; i++ )
            arglist[ i ] = arguments[ i ].data();
        arglist[ n ] = 0;
    }

    if ( !setupCommunication( comm ) )
        qWarning( "Could not setup Communication!" );

    // We do this in the parent because if we do it in the child process
    // gdb gets confused when the application runs from gdb.
    uid_t uid = getuid();
    gid_t gid = getgid();
#ifdef HAVE_INITGROUPS

    struct passwd *pw = getpwuid( uid );
#endif

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

    runs = true;

    QApplication::flushX();

    // WABA: Note that we use fork() and not vfork() because
    // vfork() has unclear semantics and is not standardized.
    pid_ = fork();

    if ( 0 == pid_ )
    {
        if ( fd[ 0 ] )
            close( fd[ 0 ] );
        if ( !runPrivileged() )
        {
            setgid( gid );
#if defined( HAVE_INITGROUPS)

            if ( pw )
                initgroups( pw->pw_name, pw->pw_gid );
#endif

            setuid( uid );
        }
        // The child process
        if ( !commSetupDoneC() )
            qWarning( "Could not finish comm setup in child!" );

        setupEnvironment();

        // Matthias
        if ( run_mode == DontCare )
            setpgid( 0, 0 );
        // restore default SIGPIPE handler (Harri)
        struct sigaction act;
        sigemptyset( &( act.sa_mask ) );
        sigaddset( &( act.sa_mask ), SIGPIPE );
        act.sa_handler = SIG_DFL;
        act.sa_flags = 0;
        sigaction( SIGPIPE, &act, 0L );

        // We set the close on exec flag.
        // Closing of fd[1] indicates that the execvp succeeded!
        if ( fd[ 1 ] )
            fcntl( fd[ 1 ], F_SETFD, FD_CLOEXEC );
        execvp( arglist[ 0 ], arglist );
        char resultByte = 1;
        if ( fd[ 1 ] )
            write( fd[ 1 ], &resultByte, 1 );
        _exit( -1 );
    }
    else if ( -1 == pid_ )
    {
        // forking failed

        runs = false;
        free( arglist );
        return false;
    }
    else
    {
        if ( fd[ 1 ] )
            close( fd[ 1 ] );
        // the parent continues here

        // Discard any data for stdin that might still be there
        input_data = 0;

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

        if ( !commSetupDoneP() )   // finish communication socket setup for the parent
            qWarning( "Could not finish comm setup in parent!" );

        if ( run_mode == Block )
        {
            commClose();

            // The SIGCHLD handler of the process controller will catch
            // the exit and set the status
            while ( runs )
            {
                OProcessController::theOProcessController->
                slotDoHousekeeping( 0 );
            }
            runs = FALSE;
            emit processExited( this );
        }
    }
    free( arglist );
    return true;
}



bool OProcess::kill( int signo )
{
    bool rv = false;

    if ( 0 != pid_ )
        rv = ( -1 != ::kill( pid_, signo ) );
    // probably store errno somewhere...
    return rv;
}

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

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

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

int OProcess::exitStatus() const
{
    int _status = status;
    return WEXITSTATUS( ( _status ) );
}

bool OProcess::writeStdin( const char *buffer, int buflen )
{
    bool rv;

    // if there is still data pending, writing new data
    // to stdout is not allowed (since it could also confuse
    // kprocess...
    if ( 0 != input_data )
        return false;

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

void OProcess::flushStdin ( )
{
    if ( !input_data || ( input_sent == input_total ) )
        return ;

    int d1, d2;

    do
    {
        d1 = input_total - input_sent;
        slotSendData ( 0 );
        d2 = input_total - input_sent;
    }
    while ( d2 <= d1 );
}

void OProcess::suspend()
{
    if ( ( communication & Stdout ) && outnot )
        outnot->setEnabled( false );
}

void OProcess::resume()
{
    if ( ( communication & Stdout ) && outnot )
        outnot->setEnabled( true );
}

bool OProcess::closeStdin()
{
    bool rv;

    if ( communication & Stdin )
    {
        communication = ( Communication ) ( communication & ~Stdin );
        delete innot;
        innot = 0;
        close( in[ 1 ] );
        rv = true;
    }
    else
        rv = false;
    return rv;
}

bool OProcess::closeStdout()
{
    bool rv;

    if ( communication & Stdout )
    {
        communication = ( Communication ) ( communication & ~Stdout );
        delete outnot;
        outnot = 0;
        close( out[ 0 ] );
        rv = true;
    }
    else
        rv = false;
    return rv;
}

bool OProcess::closeStderr()
{
    bool rv;

    if ( communication & Stderr )
    {
        communication = static_cast<Communication>( communication & ~Stderr );
        delete errnot;
        errnot = 0;
        close( err[ 0 ] );
        rv = true;
    }
    else
        rv = false;
    return rv;
}

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

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

void OProcess::slotSendData( int )
{
    if ( input_sent == input_total )
    {
        innot->setEnabled( false );
        input_data = 0;
        emit wroteStdin( this );
    }
    else
        input_sent += ::write( in[ 1 ], input_data + input_sent, input_total - input_sent );
}

void OProcess::processHasExited( int state )
{
    if ( runs )
    {
        runs = false;
        status = state;

        commClose(); // cleanup communication sockets

        // also emit a signal if the process was run Blocking
        if ( DontCare != run_mode )
        {
            emit processExited( this );
        }
    }
}

int OProcess::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[ 1024 ];
        int len;

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

        if ( 0 < len )
        {
            emit receivedStdout( this, buffer, len );
        }
        return len;
    }
}

int OProcess::childError( int fdno )
{
    char buffer[ 1024 ];
    int len;

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

    if ( 0 < len )
        emit receivedStderr( this, buffer, len );
    return len;
}

int OProcess::setupCommunication( Communication comm )
{
    int ok;

    communication = comm;

    ok = 1;
    if ( comm & Stdin )
        ok &= socketpair( AF_UNIX, SOCK_STREAM, 0, in ) >= 0;

    if ( comm & Stdout )
        ok &= socketpair( AF_UNIX, SOCK_STREAM, 0, out ) >= 0;

    if ( comm & Stderr )
        ok &= socketpair( AF_UNIX, SOCK_STREAM, 0, err ) >= 0;

    return ok;
}

int OProcess::commSetupDoneP()
{
    int ok = 1;

    if ( communication != NoCommunication )
    {
        if ( communication & Stdin )
            close( in[ 0 ] );
        if ( communication & Stdout )
            close( out[ 1 ] );
        if ( communication & Stderr )
            close( err[ 1 ] );

        // Don't create socket notifiers and set the sockets non-blocking if
        // blocking is requested.
        if ( run_mode == Block )
            return ok;

        if ( communication & Stdin )
        {
            //        ok &= (-1 != fcntl(in[1], F_SETFL, O_NONBLOCK));
            innot = new QSocketNotifier( in[ 1 ], QSocketNotifier::Write, this );
            CHECK_PTR( innot );
            innot->setEnabled( false ); // will be enabled when data has to be sent
            QObject::connect( innot, SIGNAL( activated(int) ),
                              this, SLOT( slotSendData(int) ) );
        }

        if ( communication & Stdout )
        {
            //        ok &= (-1 != fcntl(out[0], F_SETFL, O_NONBLOCK));
            outnot = new QSocketNotifier( out[ 0 ], QSocketNotifier::Read, this );
            CHECK_PTR( outnot );
            QObject::connect( outnot, SIGNAL( activated(int) ),
                              this, SLOT( slotChildOutput(int) ) );
            if ( communication & NoRead )
                suspend();
        }

        if ( communication & Stderr )
        {
            //        ok &= (-1 != fcntl(err[0], F_SETFL, O_NONBLOCK));
            errnot = new QSocketNotifier( err[ 0 ], QSocketNotifier::Read, this );
            CHECK_PTR( errnot );
            QObject::connect( errnot, SIGNAL( activated(int) ),
                              this, SLOT( slotChildError(int) ) );
        }
    }
    return ok;
}

int OProcess::commSetupDoneC()
{
    int ok = 1;
    struct linger so;
    memset( &so, 0, sizeof( so ) );

    if ( communication & Stdin )
        close( in[ 1 ] );
    if ( communication & Stdout )
        close( out[ 0 ] );
    if ( communication & Stderr )
        close( err[ 0 ] );

    if ( communication & Stdin )
        ok &= dup2( in[ 0 ], STDIN_FILENO ) != -1;
    else
    {
        int null_fd = open( "/dev/null", O_RDONLY );
        ok &= dup2( null_fd, STDIN_FILENO ) != -1;
        close( null_fd );
    }
    if ( communication & Stdout )
    {
        ok &= dup2( out[ 1 ], STDOUT_FILENO ) != -1;
        ok &= !setsockopt( out[ 1 ], SOL_SOCKET, SO_LINGER, ( char* ) & so, sizeof( so ) );
    }
    else
    {
        int null_fd = open( "/dev/null", O_WRONLY );
        ok &= dup2( null_fd, STDOUT_FILENO ) != -1;
        close( null_fd );
    }
    if ( communication & Stderr )
    {
        ok &= dup2( err[ 1 ], STDERR_FILENO ) != -1;
        ok &= !setsockopt( err[ 1 ], SOL_SOCKET, SO_LINGER, reinterpret_cast<char *>( &so ), sizeof( so ) );
    }
    else
    {
        int null_fd = open( "/dev/null", O_WRONLY );
        ok &= dup2( null_fd, STDERR_FILENO ) != -1;
        close( null_fd );
    }
    return ok;
}

void OProcess::commClose()
{
    if ( NoCommunication != communication )
    {
        bool b_in = ( communication & Stdin );
        bool b_out = ( communication & Stdout );
        bool b_err = ( communication & Stderr );
        if ( b_in )
            delete innot;

        if ( b_out || b_err )
        {
            // 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.

            // Once one or other of the channels has reached EOF (or given an error) go back
            // to the usual mechanism.

            int fds_ready = 1;
            fd_set rfds;

            int max_fd = 0;
            if ( b_out )
            {
                fcntl( out[ 0 ], F_SETFL, O_NONBLOCK );
                if ( out[ 0 ] > max_fd )
                    max_fd = out[ 0 ];
                delete outnot;
                outnot = 0;
            }
            if ( b_err )
            {
                fcntl( err[ 0 ], F_SETFL, O_NONBLOCK );
                if ( err[ 0 ] > max_fd )
                    max_fd = err[ 0 ];
                delete errnot;
                errnot = 0;
            }


            while ( b_out || b_err )
            {
                // * If the process is still running we block until we
                // receive data. (p_timeout = 0, no timeout)
                // * If the process has already exited, we only check
                // the available data, we don't wait for more.
                // (p_timeout = &timeout, timeout immediately)
                struct timeval timeout;
                timeout.tv_sec = 0;
                timeout.tv_usec = 0;
                struct timeval *p_timeout = runs ? 0 : &timeout;

                FD_ZERO( &rfds );
                if ( b_out )
                    FD_SET( out[ 0 ], &rfds );

                if ( b_err )
                    FD_SET( err[ 0 ], &rfds );

                fds_ready = select( max_fd + 1, &rfds, 0, 0, p_timeout );
                if ( fds_ready <= 0 )
                    break;

                if ( b_out && FD_ISSET( out[ 0 ], &rfds ) )
                {
                    int ret = 1;
                    while ( ret > 0 )
                        ret = childOutput( out[ 0 ] );
                    if ( ( ret == -1 && errno != EAGAIN ) || ret == 0 )
                        b_out = false;
                }

                if ( b_err && FD_ISSET( err[ 0 ], &rfds ) )
                {
                    int ret = 1;
                    while ( ret > 0 )
                        ret = childError( err[ 0 ] );
                    if ( ( ret == -1 && errno != EAGAIN ) || ret == 0 )
                        b_err = false;
                }
            }
        }

        if ( b_in )
        {
            communication = ( Communication ) ( communication & ~Stdin );
            close( in[ 1 ] );
        }
        if ( b_out )
        {
            communication = ( Communication ) ( communication & ~Stdout );
            close( out[ 0 ] );
        }
        if ( b_err )
        {
            communication = ( Communication ) ( communication & ~Stderr );
            close( err[ 0 ] );
        }
    }
}

void OProcess::setUseShell( bool useShell, const char *shell )
{
    if ( !d )
        d = new OProcessPrivate;
    d->useShell = useShell;
    d->shell = shell;
    if ( d->shell.isEmpty() )
        d->shell = searchShell();
}

QString OProcess::quote( const QString &arg )
{
    QString res = arg;
    res.replace( QRegExp( QString::fromLatin1( "\'" ) ),
                 QString::fromLatin1( "'\"'\"'" ) );
    res.prepend( '\'' );
    res.append( '\'' );
    return res;
}

QCString OProcess::searchShell()
{
    QCString tmpShell = QCString( getenv( "SHELL" ) ).stripWhiteSpace();
    if ( !isExecutable( tmpShell ) )
    {
        tmpShell = "/bin/sh";
    }

    return tmpShell;
}

bool OProcess::isExecutable( const QCString &filename )
{
    struct stat fileinfo;

    if ( filename.isEmpty() )
        return false;

    // CC: we've got a valid filename, now let's see whether we can execute that file

    if ( -1 == stat( filename.data(), &fileinfo ) )
        return false;
    // CC: return false if the file does not exist

    // CC: anyway, we cannot execute directories, block/character devices, fifos or sockets
    if ( ( S_ISDIR( fileinfo.st_mode ) ) ||
            ( S_ISCHR( fileinfo.st_mode ) ) ||
            ( S_ISBLK( fileinfo.st_mode ) ) ||
#ifdef S_ISSOCK
            // CC: SYSVR4 systems don't have that macro
            ( S_ISSOCK( fileinfo.st_mode ) ) ||
#endif
            ( S_ISFIFO( fileinfo.st_mode ) ) ||
            ( S_ISDIR( fileinfo.st_mode ) ) )
    {
        return false;
    }

    // CC: now check for permission to execute the file
    if ( access( filename.data(), X_OK ) != 0 )
        return false;

    // CC: we've passed all the tests...
    return true;
}

int OProcess::processPID( const QString& process )
{
    QString line;
    QDir d = QDir( "/proc" );
    QStringList dirs = d.entryList( QDir::Dirs );
    QStringList::Iterator it;
    for ( it = dirs.begin(); it != dirs.end(); ++it )
    {
        //qDebug( "next entry: %s", (const char*) *it );
        QFile file( "/proc/"+*it+"/cmdline" );
        file.open( IO_ReadOnly );
        if ( !file.isOpen() ) continue;
        QTextStream t( &file );
        line = t.readLine();
        //qDebug( "cmdline = %s", (const char*) line );
        if ( line.contains( process ) ) break; //FIXME: That may find also other process, if the name is not long enough ;)
    }
    if ( line.contains( process ) )
    {
        //qDebug( "found process id #%d", (*it).toInt() );
        return (*it).toInt();
    }
    else
    {
        //qDebug( "process '%s' not found", (const char*) process );
        return 0;
    }
}