/***************************************************************************
    smb4kmounter.cpp  -  The core class that mounts the shares.
                             -------------------
    begin                : Die Jun 10 2003
    copyright            : (C) 2003-2008 by Alexander Reinholdt
    email                : dustpuppy@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   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     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,   *
 *   MA  02111-1307 USA                                                    *
 ***************************************************************************/

// Qt includes
#include <QApplication>
#include <QDir>
#include <QTextStream>
#include <QTextCodec>
#include <QDesktopWidget>

// KDE includes
#include <kapplication.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kdebug.h>
#include <kshell.h>

// system includes
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>

#ifdef __FreeBSD__
#include <pwd.h>
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
#include <qfileinfo.h>
#endif

// Application specific includes
#include <smb4kmounter.h>
#include <smb4kmounter_p.h>
#include <smb4kauthinfo.h>
#include <smb4ksambaoptionsinfo.h>
#include <smb4kcoremessage.h>
#include <smb4kglobal.h>
#include <smb4ksambaoptionshandler.h>
#include <smb4kshare.h>
#include <smb4ksettings.h>
#include <smb4kdefs.h>
#include <smb4khomesshareshandler.h>
#include <smb4kwalletmanager.h>

using namespace Smb4KGlobal;

typedef Smb4KMounterQueueContainer QueueContainer;


Smb4KMounter::Smb4KMounter( QObject *parent ) : QObject( parent )
{
  m_priv = new Smb4KMounterPrivate;

  m_proc = new KProcess( this );

  m_working = false;
  m_aborted = false;
  m_process_error = (QProcess::ProcessError)(-1);

  connect( m_proc,  SIGNAL( finished( int, QProcess::ExitStatus ) ),
           this,    SLOT( slotProcessFinished( int, QProcess::ExitStatus ) )  );

  connect( m_proc,  SIGNAL( error( QProcess::ProcessError ) ),
           this,    SLOT( slotProcessError( QProcess::ProcessError ) ) );

  connect( kapp,    SIGNAL( aboutToQuit() ),
           this,    SLOT( slotAboutToQuit() ) );
}


Smb4KMounter::~Smb4KMounter()
{
  abort();

  while ( !Smb4KGlobal::mountedSharesList()->isEmpty() )
  {
    delete Smb4KGlobal::mountedSharesList()->takeFirst();
  }

  delete m_priv;
}


void Smb4KMounter::init()
{
  startTimer( TIMER_INTERVAL );

  m_queue.enqueue( QueueContainer( Import ) );
  m_queue.enqueue( QueueContainer( TriggerRemounts ) );
}


void Smb4KMounter::abort()
{
  while( !m_queue.isEmpty() )
  {
    m_queue.dequeue();
  }

  if ( m_proc->state() == KProcess::Running )
  {
    if ( Smb4KSettings::alwaysUseSuperUser() )
    {
      KProcess proc;
      proc.setShellCommand( QString( "%1 smb4k_kill %2" ).arg( Smb4KSettings::sudo() ).arg( m_proc->pid() ) );
      proc.start();
    }
    else
    {
      m_proc->kill();
    }
  }

  m_aborted = true;
}


void Smb4KMounter::triggerRemounts()
{
  if ( Smb4KSettings::remountShares() )
  {
    QList<Smb4KSambaOptionsInfo *> list = Smb4KSambaOptionsHandler::self()->sharesToRemount();

    for ( int i = 0; i < list.size(); ++i )
    {
      QList<Smb4KShare *> shares = findShareByUNC( list.at( i )->unc() );

      if ( !shares.isEmpty() )
      {
        bool mount = true;

        for ( int j = 0; j < shares.size(); ++j )
        {
          if ( !shares.at( j )->isForeign() )
          {
            mount = false;

            break;
          }
          else
          {
            continue;
          }
        }

        if ( mount )
        {
          Smb4KShare share( list.at( i )->unc() );
          share.setWorkgroup( list.at( i )->workgroup() );
          share.setHostIP( list.at( i )->ip() );
          mountShare( &share );
        }
        else
        {
          // Do nothing
        }
      }
      else
      {
        Smb4KShare share( list.at( i )->unc() );
        share.setWorkgroup( list.at( i )->workgroup() );
        share.setHostIP( list.at( i )->ip() );
        mountShare( &share );
      }
    }

    m_working = false;
    emit state( MOUNTER_STOP );
  }
  else
  {
    m_working = false;
    emit state( MOUNTER_STOP );
  }
}


void Smb4KMounter::import()
{
  QList<Smb4KShare *> shares;

#ifndef __FreeBSD__

  if ( m_proc_mounts.fileName().isEmpty() )
  {
    m_proc_mounts.setFileName( "/proc/mounts" );
  }

  if ( !QFile::exists( m_proc_mounts.fileName() ) )
  {
    if ( !m_proc_error )
    {
      m_proc_error = true;
      Smb4KCoreMessage::error( ERROR_FILE_NOT_FOUND, m_proc_mounts.fileName() );
    }
    else
    {
      // No need to do anything here
    }
  }
  else
  {
    QStringList list;

    // Read /proc/mounts:
    if ( m_proc_mounts.open( QIODevice::ReadOnly | QIODevice::Text ) )
    {
      QTextStream ts( &m_proc_mounts );
      // Note: With Qt 4.3 this seems to be obsolete, but we'll
      // keep it for now.
      ts.setCodec( QTextCodec::codecForLocale() );

      QString line;

      // Since we are operating on a symlink, we need to read
      // the file this way. Using ts.atEnd() won't work, because
      // it would immediately return TRUE.
      while ( 1 )
      {
        line = ts.readLine( 0 );

        if ( !line.isNull() )
        {
          if ( line.contains( " smbfs ", Qt::CaseSensitive ) ||
               line.contains( " cifs ", Qt::CaseSensitive ) )
          {
            list.append( line );

            continue;
          }
          else
          {
            continue;
          }
        }
        else
        {
          break;
        }
      }

      m_proc_mounts.close();
    }
    else
    {
      Smb4KCoreMessage::error( ERROR_OPENING_FILE, m_proc_mounts.fileName() );

      return;
    }

    // Process the SMBFS and CIFS entries:
    if ( !list.isEmpty() )
    {
      for ( int i = 0; i < list.size(); ++i )
      {
        Smb4KShare *share = NULL;
        Smb4KShare *existing_share = NULL;

        if ( list.at( i ).contains( " smbfs ", Qt::CaseSensitive ) )
        {
          QString unc_and_path = list.at( i ).section( " smbfs ", 0, 0 ).trimmed();
          QString unc          = unc_and_path.section( " ", 0, 0 ).trimmed()
                                             .replace( "\\040", "\040" );
          QString path         = unc_and_path.section( " ", 1, 1 ).trimmed()
                                             .replace( "\\040", "\040" );

          // If the share is already in the list, copy it and do not
          // create a new entry:
          existing_share = findShareByPath( path.toLocal8Bit() );

          if ( existing_share )
          {
            share = new Smb4KShare( *existing_share );

            if ( share->fileSystem() != Smb4KShare::SMBFS )
            {
              uid_t uid = (uid_t)list.at( i ).section( "uid=", 1, 1 ).section( ",", 0, 0 )
                                             .trimmed().toInt();
              gid_t gid = (gid_t)list.at( i ).section( "gid=", 1, 1 ).section( ",", 0, 0 )
                                             .trimmed().toInt();

              share->setFileSystem( Smb4KShare::SMBFS );
              share->setUID( uid );
              share->setGID( gid );
            }
            else
            {
              if ( !share->uidIsSet() && !share->gidIsSet() )
              {
                uid_t uid = (uid_t)list.at( i ).section( "uid=", 1, 1 ).section( ",", 0, 0 )
                                               .trimmed().toInt();

                gid_t gid = (gid_t)list.at( i ).section( "gid=", 1, 1 ).section( ",", 0, 0 )
                                               .trimmed().toInt();

                share->setUID( uid );
                share->setGID( gid );
              }
              else
              {
                // Do nothing
              }
            }
          }
          else
          {
            uid_t uid = (uid_t)list.at( i ).section( "uid=", 1, 1 ).section( ",", 0, 0 )
                                           .trimmed().toInt();
            gid_t gid = (gid_t)list.at( i ).section( "gid=", 1, 1 ).section( ",", 0, 0 )
                                           .trimmed().toInt();

            share = new Smb4KShare( unc );
            share->setPath( path );
            share->setFileSystem( Smb4KShare::SMBFS );
            share->setUID( uid );
            share->setGID( gid );
            share->setIsMounted( true );
          }
        }
        else if ( list.at( i ).contains( " cifs ", Qt::CaseSensitive ) )
        {
          QString unc_and_path = list.at( i ).section( " cifs ", 0, 0 ).trimmed();
          QString unc          = unc_and_path.section( " ", 0, 0 ).trimmed()
                                             .replace( "\\040", "\040" );
          QString path         = unc_and_path.section( " ", 1, 1 ).trimmed()
                                             .replace( "\\040", "\040" );

          // If the share is already in the list, copy it and do not
          // create a new entry:
          existing_share = findShareByPath( path.toLocal8Bit() );

          if ( existing_share )
          {
            share = new Smb4KShare( *existing_share );

            if ( share->fileSystem() != Smb4KShare::CIFS )
            {
              QString login     = list.at( i ).section( "username=", 1, 1 )
                                              .section( ",", 0, 0 ).trimmed();
              QString workgroup = list.at( i ).section( "domain=", 1, 1 )
                                              .section( ",", 0, 0 ).trimmed();

              share->setFileSystem( Smb4KShare::CIFS );
              share->setCIFSLogin( login );
              share->setWorkgroup( workgroup );
            }
            else
            {
              if ( !share->cifsLoginIsSet() )
              {
                QString login     = list.at( i ).section( "username=", 1, 1 )
                                                .section( ",", 0, 0 ).trimmed();

                share->setCIFSLogin( login );
              }
              else
              {
                // Do nothing
              }
            }
          }
          else
          {
            QString login     = list.at( i ).section( "username=", 1, 1 )
                                            .section( ",", 0, 0 ).trimmed();
            QString workgroup = list.at( i ).section( "domain=", 1, 1 )
                                            .section( ",", 0, 0 ).trimmed();

            share = new Smb4KShare( unc );
            share->setPath( path );
            share->setFileSystem( Smb4KShare::CIFS );
            share->setCIFSLogin( login );
            share->setWorkgroup( workgroup );
            share->setIsMounted( true );
          }
        }
        else
        {
          continue;
        }

        if ( share )
        {
          // Check if the share is accessible and determine its disk usage.
          if ( (existing_share && !existing_share->isInaccessible()) || !existing_share )
          {
            check( share );

            // Check if the share is foreign.
            if ( !existing_share )
            {
              if ( share->fileSystem() == Smb4KShare::SMBFS )
              {
                if ( share->uid() == getuid() && share->gid() == getgid() )
                {
                  share->setForeign( false );
                }
                else
                {
                  share->setForeign( true );
                }
              }
              else if ( share->fileSystem() == Smb4KShare::CIFS )
              {
                if ( (!share->isInaccessible() &&
                     (qstrncmp( share->canonicalPath(),
                      QDir( Smb4KSettings::mountPrefix().path() )
                      .canonicalPath().toLocal8Bit(),
                      QDir( Smb4KSettings::mountPrefix().path() )
                      .canonicalPath().toLocal8Bit().length() ) == 0 ||
                      qstrncmp( share->canonicalPath(),
                      QDir::home().canonicalPath().toLocal8Bit(),
                      QDir::home().canonicalPath().toLocal8Bit().length() ) == 0)) ||
                      (share->isInaccessible() &&
                      (qstrncmp( share->path(),
                      QDir::homePath().toLocal8Bit(),
                      QDir::homePath().toLocal8Bit().length() ) == 0 ||
                      qstrncmp( share->path(),
                      Smb4KSettings::mountPrefix().path().toLocal8Bit(),
                      Smb4KSettings::mountPrefix().path().toLocal8Bit().length() ) == 0)) )
                {
                  share->setForeign( false );
                }
                else
                {
                  share->setForeign( true );
                }
              }
            }
            else
            {
              // Do nothing. (This should never happen...)
            }
          }
          else
          {
            // Do nothing
          }

          shares.append( share );

          continue;
        }
        else
        {
          continue;
        }
      }
    }
  }

#else

  struct statfs *buf;
  int count = getmntinfo( &buf, 0 );

  if ( count == 0 )
  {
    int err_code = errno;

    Smb4KCoreMessage::error( ERROR_IMPORTING_SHARES, QString::null, strerror( err_code ) );

    m_working = false;
    return;
  }

  for ( int i = 0; i < count; ++i )
  {
    if ( !strcmp( buf[i].f_fstypename, "smbfs" ) )
    {
      QByteArray path( buf[i].f_mntonname );

      Smb4KShare *existing_share = findShareByPath( path );
      Smb4KShare *share = NULL;

      if ( existing_share )
      {
        share = new Smb4KShare( *existing_share );

        if ( share->fileSystem() != Smb4KShare::SMBFS )
        {
          QFileInfo info( QString( buf[i].f_mntonname )+"/." );

          share->setFileSystem( Smb4KShare::SMBFS );
          share->setUID( info.ownerId() );
          share->setGID( info.groupId() );
        }
        else
        {
          // Do nothing
        }

      }
      else
      {
        QString share_name( buf[i].f_mntfromname );

        QFileInfo info( QString( buf[i].f_mntonname )+"/." );

        share = new Smb4KShare( share_name );
        share->setPath( path );
        share->setFileSystem ( !strcmp( buf[i].f_fstypename, "smbfs" ) ?
                               Smb4KShare::SMBFS :
                               Smb4KShare::Unknown );
        share->setUID( info.ownerId() );
        share->setGID( info.groupId() );
        share->setIsMounted( true );
      }

      // Test if share is broken
      if ( (existing_share && !existing_share->isInaccessible()) || !existing_share )
      {
        check( share );
      }
      else
      {
        // Since new_share is a copy of existing_share, we do not need to do
        // anything here.
      }

      shares.append( share );
    }
  }

  // Apparently, under FreeBSD we do not need to delete
  // the pointer (see manual page).

#endif

  // Delete all entries of Smb4KGlobal::mountedSharesList().
  while( !Smb4KGlobal::mountedSharesList()->isEmpty() )
  {
    delete Smb4KGlobal::mountedSharesList()->takeFirst();
  }

  // The new list is now the global list.
  *Smb4KGlobal::mountedSharesList() += shares;


  emit updated();

  m_working = false;
}


void Smb4KMounter::mountShare( Smb4KShare *share )
{
  if ( share )
  {

    // Copy the share to avoid problems with 'homes' shares.
    Smb4KShare internal_share = *share;

    if ( internal_share.isHomesShare() )
    {
      QWidget *parent = 0;

      if ( kapp )
      {
        if ( kapp->activeWindow() )
        {
          parent = kapp->activeWindow();
        }
        else
        {
          parent = kapp->desktop();
        }
      }
      else
      {
        // Do nothing
      }

      if ( !Smb4KHomesSharesHandler::self()->specifyUser( &internal_share, parent ) )
      {
        return;
      }
      else
      {
        // Do nothing
      }
    }
    else
    {
      // Do nothing
    }

    if ( !internal_share.name().isEmpty() )
    {
      // Before doing anything else let's check that the
      // share has not already been mounted by the user:
      QList<Smb4KShare *> list = findShareByUNC( internal_share.unc() );

      for ( int i = 0; i != list.size(); ++i )
      {
        if ( !list.at( i )->isForeign() )
        {
          Smb4KShare *mounted_share = findShareByPath( list.at( i )->path() );

          emit mounted( mounted_share );

          return;
        }
        else
        {
          continue;
        }
      }

      switch ( m_state )
      {
        case TriggerRemounts:
        {
          m_queue.enqueue( QueueContainer( Remount, internal_share ) );

          break;
        }
        default:
        {
          m_queue.enqueue( QueueContainer( Mount, internal_share ) );

          break;
        }
      }
    }
    else
    {
      // Do nothing
    }
  }
  else
  {
    // Do nothing
  }
}


void Smb4KMounter::unmountShare( Smb4KShare *share, bool force, bool noMessage )
{
  if ( share )
  {
    m_queue.enqueue( QueueContainer( Unmount, *share, force, noMessage ) );
  }
  else
  {
    // Do nothing
  }
}


void Smb4KMounter::unmountAllShares()
{
  m_queue.enqueue( QueueContainer( UnmountAll ) );
}


void Smb4KMounter::mount( const Smb4KShare &share )
{
  m_priv->setShare( share );

  // Create the mount point:
  QDir dir( Smb4KSettings::mountPrefix().path() );

  if ( !dir.exists() )
  {
    if ( !dir.mkdir( dir.path() ) )
    {
      Smb4KCoreMessage::error( ERROR_MKDIR_FAILED, dir.path() );
      m_working = false;
      emit state( MOUNTER_STOP );

      return;
    }
  }

  dir.setPath( dir.path() + QDir::separator() +
              (Smb4KSettings::forceLowerCaseSubdirs() ?
              m_priv->share()->host().toLower() :
              m_priv->share()->host()) );

  if ( !dir.exists() )
  {
    if ( !dir.mkdir( dir.path() ) )
    {
      Smb4KCoreMessage::error( ERROR_MKDIR_FAILED, dir.path() );
      m_working = false;
      emit state( MOUNTER_STOP );

      return;
    }
  }

  dir.setPath( dir.path() + QDir::separator() +
               (Smb4KSettings::forceLowerCaseSubdirs() ?
               m_priv->share()->name().toLower() :
               m_priv->share()->name()) );

  if ( !dir.exists() )
  {
    if ( !dir.mkdir( dir.path() ) )
    {
      Smb4KCoreMessage::error( ERROR_MKDIR_FAILED, dir.path() );
      m_working = false;
      emit state( MOUNTER_STOP );

      return;
    }
  }

  m_priv->share()->setPath( QDir::cleanPath( dir.path() ) );

  // Now we are prepared to mount the share:
  QString command;

  Smb4KAuthInfo authInfo( m_priv->share() );
  Smb4KWalletManager::self()->readAuthInfo( &authInfo );

#ifndef __FreeBSD__
  // Compile the mount command:
  command.append( Smb4KSettings::alwaysUseSuperUser() ?   // FIXME: Check if suid program is installed
                  Smb4KSettings::sudo()+" smb4k_mount -s -t cifs " :
                  "smb4k_mount -n -t cifs " );

  command.append( "-o " );

  command.append( Smb4KSambaOptionsHandler::self()->mountOptions( m_priv->share()->unc() ) );

  command.append( !m_priv->share()->workgroup().trimmed().isEmpty() ?
                  QString( "domain=%1," ).arg( KShell::quoteArg( m_priv->share()->workgroup() ) ) :
                  "" );

  command.append( !m_priv->share()->hostIP().trimmed().isEmpty() ?
                  QString( "ip=%1," ).arg( m_priv->share()->hostIP() ) :
                  "" );

  command.append( !authInfo.login().isEmpty() ?
                  QString( "user=%1" ).arg( QString::fromLocal8Bit( authInfo.login() ) ) :
                  "guest" );

  command.append( " -- " );

  command.append( QString( "//%1/%2 %3" ).arg( KShell::quoteArg( m_priv->share()->host() ) )
                                         .arg( KShell::quoteArg( m_priv->share()->name() ) )
                                         .arg( KShell::quoteArg( m_priv->share()->path() ) ) );

  m_priv->share()->setCIFSLogin( !authInfo.login().isEmpty() ?
                                 authInfo.login() :
                                 "guest" );

  m_priv->share()->setFileSystem( Smb4KShare::CIFS );

  m_proc->setEnv( "PASSWD", !authInfo.password().isEmpty() ? authInfo.password() : "", true );

#else

  Smb4KSambaOptionsInfo *info = Smb4KSambaOptionsHandler::self()->findItem( m_priv->share()->unc() );

  // Under FreeBSD the file system port is equal to the SMB port. So,
  // we can use it here directly.
  int port = info && info->port() != -1 ?
             info->port() :
             Smb4KSettings::remoteSMBPort();

  command.append( Smb4KSettings::alwaysUseSuperUser() ?   // FIXME: Check if suid program is installed
                  Smb4KSettings::sudo()+" smb4k_mount " :
                  "smb4k_mount " );

  command.append( Smb4KSambaOptionsHandler::self()->mountOptions( m_priv->share()->unc() ) );

  command.append( !m_priv->share()->workgroup().trimmed().isEmpty() ?
                  QString( " -W '%1'" ).arg( m_priv->share()->workgroup() ) :
                  "" );

  command.append( !m_priv->share()->hostIP().trimmed().isEmpty() ?
                  QString( " -I %1" ).arg( m_priv->share()->hostIP() ) :
                  "" );

  // User name (undocumented under FreeBSD, but working!)
  command.append( authInfo.login().isEmpty() ?
                  " -N" :
		  "" );

  command.append( " -- " );

  command.append( QString( "//%1@%2:%3/%4 %5" ).arg( !authInfo.login().isEmpty() ?
                                                     QString::fromLocal8Bit( authInfo.login() ) :
                                                     "guest" )
                                               .arg( KShell::quoteArg( m_priv->share()->host() ) )
                                               .arg( port )
                                               .arg( KShell::quoteArg( m_priv->share()->name() ) )
                                               .arg( KShell::quoteArg( m_priv->share()->path() ) ) );

#endif

  // Start the mount process:
  m_proc->setShellCommand( command );

  startProcess();
}


void Smb4KMounter::unmount( const Smb4KShare &share, bool force, bool noMessage )
{
  m_priv->setShare( share );

  // First let's see if all requirements are fullfilled:

  if ( force )
  {
    // Check that the user enabled the "Force Unmounting" ability:
    if ( !Smb4KSettings::useForceUnmount() )
    {
      Smb4KCoreMessage::error( ERROR_FEATURE_NOT_ENABLED );
      m_working = false;
      emit state( MOUNTER_STOP );

      return;
    }
  }

  // Compose the unmount command:
  if ( !QString::fromLocal8Bit( m_priv->share()->canonicalPath(), -1 ).isEmpty() )
  {
    bool execute = false;

    QString command;

    if ( !m_priv->share()->isForeign() )
    {
      if ( force )
      {
        QWidget *parent = 0;

        if ( kapp )
        {
          if ( kapp->activeWindow() )
          {
            parent = kapp->activeWindow();
          }
          else
          {
            parent = kapp->desktop();
          }
        }
        else
        {
          // Do nothing
        }

        if ( KMessageBox::questionYesNo( parent, i18n( "<qt>Do you really want to force the unmounting of this share?</qt>" ), QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), "Dont Ask Forced", KMessageBox::Notify ) == KMessageBox::Yes )
        {
#ifdef __linux__
          command.append( Smb4KSettings::sudo()+" smb4k_umount -s -l" );
#else
#ifdef __FreeBSD__
          command.append( Smb4KSettings::sudo()+" smb4k_umount" );
#else
          command.append( Smb4KSettings::sudo()+" smb4k_umount -s" );
#endif
#endif
          execute = true;
        }
        else
        {
          m_working = false;
          emit state( MOUNTER_STOP );

          return;
        }
      }
      else
      {
        if ( !Smb4KSettings::alwaysUseSuperUser() )
        {
#ifndef __FreeBSD__
          command.append( "smb4k_umount -n" );
#else
          command.append( "smb4k_umount" );
#endif
        }
        else
        {
#ifndef __FreeBSD__
          command.append( Smb4KSettings::sudo()+" smb4k_umount -s" );
#else
          command.append( Smb4KSettings::sudo()+" smb4k_umount" );
#endif
        }
      }
    }
    else
    {
      if ( Smb4KSettings::unmountForeignShares() )
      {
        if ( force )
        {
          QWidget *parent = 0;

          if ( kapp )
          {
            if ( kapp->activeWindow() )
            {
              parent = kapp->activeWindow();
            }
            else
            {
              parent = kapp->desktop();
            }
          }
          else
          {
            // Do nothing
          }

          if ( KMessageBox::questionYesNo( parent, i18n( "<qt>Do you really want to force the unmounting of this share?</qt>" ), QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), "Dont Ask Forced", KMessageBox::Notify ) == KMessageBox::Yes )
          {
#ifdef __linux__
            command.append( Smb4KSettings::sudo()+" smb4k_umount -s -l" );
#else
#ifdef __FreeBSD__
            command.append( Smb4KSettings::sudo()+" smb4k_umount" );
#else
            command.append( Smb4KSettings::sudo()+" smb4k_umount -s" );
#endif
#endif
            execute = true;
          }
          else
          {
            m_working = false;
            emit state( MOUNTER_STOP );

            return;
          }
        }
        else
        {
          if ( !Smb4KSettings::alwaysUseSuperUser() )
          {
#ifndef __FreeBSD__
            command.append( "smb4k_umount -n" );
#else
            command.append( "smb4k_umount" );
#endif
          }
          else
          {
#ifndef __FreeBSD__
            command.append( Smb4KSettings::sudo()+" smb4k_umount -s" );
#else
            command.append( Smb4KSettings::sudo()+" smb4k_umount" );
#endif
          }
        }
      }
      else
      {
        if ( !noMessage )
        {
          Smb4KCoreMessage::error( ERROR_UNMOUNTING_NOT_ALLOWED );
        }

        m_working = false;
        emit state( MOUNTER_STOP );

        return;
      }
    }

#ifndef __FreeBSD__
    switch ( m_priv->share()->fileSystem() )
    {
      case Smb4KShare::CIFS:
      {
        command.append( " -t cifs" );

        break;
      }
      case Smb4KShare::SMBFS:
      {
        command.append( " -t smbfs" );

        break;
      }
      default:
      {
        break;
      }
    }
#endif
    command.append( " "+KShell::quoteArg( m_priv->share()->path() ) );

    if ( force && !execute )
    {
      return;
    }

    emit aboutToUnmount( m_priv->share() );

    m_proc->setShellCommand( command );
    startProcess();
  }
  else
  {
    Smb4KCoreMessage::error( ERROR_MOUNTPOINT_EMPTY );
    m_working = false;
    emit state( MOUNTER_STOP );

    return;
  }
}


void Smb4KMounter::unmountAll()
{
  for ( int i = 0; i < Smb4KGlobal::mountedSharesList()->size(); ++i )
  {
    unmountShare( Smb4KGlobal::mountedSharesList()->at( i ), false, true );
  }

  m_working = false;
}


void Smb4KMounter::startProcess()
{
  m_aborted = false;

  if ( m_state != Import )
  {
    QApplication::setOverrideCursor( Qt::WaitCursor );
  }

  m_proc->setOutputChannelMode( KProcess::MergedChannels );
  m_proc->start();
}


void Smb4KMounter::endProcess( int /* exitCode */, QProcess::ExitStatus exitStatus )
{
  if ( exitStatus == QProcess::NormalExit )
  {
    // Everything work fine. We can proceed with calling
    // one of the process functions.
    switch ( m_state )
    {
      case Mount:
      case Remount:
      {
        processMount();
        break;
      }
      case Unmount:
      {
        processUnmount();
        break;
      }
      default:
      {
        break;
      }
    }
  }
  else
  {
    // Something went wrong. Throw an error if the problem was not
    // caused by using the abort() function.
    if ( !m_aborted )
    {
      if ( m_process_error != -1 )
      {
        Smb4KCoreMessage::processError( ERROR_PROCESS_ERROR, m_process_error );
      }
      else
      {
        Smb4KCoreMessage::processError( ERROR_PROCESS_EXIT, m_process_error );
      }
    }
    else
    {
      // Do nothing
    }
  }

  m_state = Idle;

  m_priv->clearData();

  QApplication::restoreOverrideCursor();
  m_proc->clearProgram();

  m_process_error = (QProcess::ProcessError)(-1);
  m_working = false;

  emit state( MOUNTER_STOP );
}


void Smb4KMounter::processMount()
{
  // The Samba programs report their errors to stdout. So,
  // get the (merged) output from stdout and check if there
  // was an error.
  QString output = QString::fromLocal8Bit( m_proc->readAllStandardOutput(), -1 ).trimmed();

  if ( !output.isEmpty() )
  {
#ifndef __FreeBSD__

    if ( output.contains( "ERRbadpw", Qt::CaseSensitive ) ||
         output.contains( "ERRnoaccess", Qt::CaseSensitive ) ||
         output.contains( "mount error 13 = Permission denied", Qt::CaseSensitive ) )
    {
      Smb4KAuthInfo authInfo( m_priv->share() );

      if ( Smb4KWalletManager::self()->showPasswordDialog( &authInfo ) )
      {
        mountShare( m_priv->share() );
      }
    }
    else if ( output.contains( "mount error 6", Qt::CaseSensitive ) &&
              m_priv->share()->name().contains( "_", Qt::CaseSensitive ) )
    {
      QString name = static_cast<QString>( m_priv->share()->name() ).replace( "_", " " );
      m_priv->share()->setName( name );

      mountShare( m_priv->share() );
    }
    else
    {
      QString name = QString( "//%1/%2" ).arg( m_priv->share()->host() )
                                         .arg( m_priv->share()->name() );
      Smb4KCoreMessage::error( ERROR_MOUNTING_SHARE, name, output );
    }

#else

    if ( output.contains( "Authentication error", Qt::CaseSensitive ) )
    {
      Smb4KAuthInfo authInfo( m_priv->share() );

      if ( Smb4KWalletManager::self()->showPasswordDialog( &authInfo ) )
      {
        mountShare( m_priv->share() );
      }
    }
    else
    {
      Smb4KAuthInfo authInfo( m_priv->share() );
      Smb4KWalletManager::self()->readAuthInfo( &authInfo );


      QString name = QString( "//%1@%2/%3" ).arg( authInfo.login().toUpper(),
                                                  m_priv->share()->host().toUpper(),
                                                  m_priv->share()->name().toUpper() );

      Smb4KCoreMessage::error( ERROR_MOUNTING_SHARE, name, output );
    }

#endif

    return;
  }

  // If no error occurred, process the mount. Since the mount process
  // should not report anything on stdout, we do not need to read it.

  // First of all reset the remount flag if this was a remount on start-up.
  switch ( m_state )
  {
    case Remount:
    {
      Smb4KSambaOptionsHandler::self()->remount( m_priv->share(), false );

      break;
    }
    default:
    {
      break;
    }
  }

  // Now add the share to the list of mounted shares.
#ifndef __FreeBSD__

  Smb4KShare *share = new Smb4KShare( *m_priv->share() );

  check( share );

  switch ( share->fileSystem() )
  {
    case Smb4KShare::Unknown:
    {
      // The file system is set to Smb4KShare::Unknown, i.e. the
      // query in the check() function failed. Thus, we go with the
      // predefined values.
      switch ( m_priv->share()->fileSystem() )
      {
        case Smb4KShare::CIFS:
        {
          share->setFileSystem( Smb4KShare::CIFS );
          share->setCIFSLogin( m_priv->share()->cifsLogin() );

          break;
        }
        case Smb4KShare::SMBFS:
        {
          share->setFileSystem( Smb4KShare::SMBFS );
          share->setUID( getuid() );
          share->setGID( getgid() );

          break;
        }
        default:
        {
          // That's unfortunate. We cannot do anything here.
          break;
        }
      }

      break;
    }
    default:
    {
      // We do not need to do anything here, because import()
      // will set the appropriate values if necessary.
      break;
    }
  }

  share->setIsMounted( true );

  Smb4KGlobal::mountedSharesList()->append( share );

  emit mounted( share );
  emit updated();

#else

  Smb4KAuthInfo authInfo( m_priv->share() );
  Smb4KWalletManager::self()->readAuthInfo( &authInfo );

  QString unc = QString( "//%1@%2/%3" ).arg( authInfo.login().toUpper(),
                                             m_priv->share()->host().toUpper(),
                                             m_priv->share()->name().toUpper() );

  Smb4KShare *share = new Smb4KShare( unc );
  share->setPath( m_priv->share()->path() );

  check( share );

  if ( share->fileSystem() != Smb4KShare::SMBFS )
  {
    // The file system is set to Smb4KShare::Unknown, i.e. the
    // query in the check() function failed. Thus, we go with the
    // only possible value.
    share->setFileSystem( Smb4KShare::SMBFS );
  }

  kDebug() << "Is it correct to set uid and gid to getuid() and getgid()?" << endl;

  share->setUID( getuid() );
  share->setGID( getgid() );
  share->setIsMounted( true );

  Smb4KGlobal::mountedSharesList()->append( share );

  emit mounted( share );
  emit updated();

#endif
}


void Smb4KMounter::processUnmount()
{
  // Get the share.
  // (We need this to make the removal from the list of mounted shares
  // work. Otherwise it would be better to just use m_priv->share()!)
  Smb4KShare *share = findShareByPath( m_priv->share()->path() );

  // The Samba programs report their errors to stdout. So,
  // get the (merged) output from stdout and check if there
  // was an error.
  QString output = QString::fromLocal8Bit( m_proc->readAllStandardOutput(), -1 ).trimmed();

  if ( !output.isEmpty() )
  {
    // If the user's computer is configured by a DHCP server, under
    // rare circumstances it might occur that sudo reports an error,
    // because it is not able to resolve the host. This error message
    // will be removed, because it does not affect the unmounting:
    if ( output.contains( "sudo: unable to resolve host", Qt::CaseSensitive ) )
    {
      size_t hostnamelen = 255;
      char *hostname = new char[hostnamelen];

      if ( gethostname( hostname, hostnamelen ) == -1 )
      {
        int error_number = errno;
        Smb4KCoreMessage::error( ERROR_GETTING_HOSTNAME, QString(), strerror( error_number ) );
      }
      else
      {
        QString str = QString( "sudo: unable to resolve host %1\n" ).arg( hostname );

        output.remove( str, Qt::CaseInsensitive /* because of the host name */);

        if ( !output.isEmpty() )
        {
          Smb4KCoreMessage::error( ERROR_UNMOUNTING_SHARE, share->unc(), output );
        }
        else
        {
          if ( qstrncmp( share->canonicalPath(),
                         QDir( Smb4KSettings::mountPrefix().path() ).canonicalPath().toLocal8Bit(),
                         QDir( Smb4KSettings::mountPrefix().path() ).canonicalPath().toLocal8Bit().length() ) == 0 )
          {
            QDir dir( share->canonicalPath() );

            if ( dir.rmdir( dir.canonicalPath() ) )
            {
              dir.cdUp();
              dir.rmdir( dir.canonicalPath() );
            }
          }

          int index = Smb4KGlobal::mountedSharesList()->indexOf( share );
          delete Smb4KGlobal::mountedSharesList()->takeAt( index );

          emit updated();
        }
      }

      delete [] hostname;
    }
    else
    {
      Smb4KCoreMessage::error( ERROR_UNMOUNTING_SHARE, share->unc(), output );
    }

    return;
  }
  else
  {
    // Do nothing
  }

  // If no error occurred, remove the unmounted share from the list and
  // clean up the mount prefix. Since the unmount process should not
  // report anything on stdout, we do not need to read it.
  if ( qstrncmp( share->canonicalPath(),
                 QDir( Smb4KSettings::mountPrefix().path() ).canonicalPath().toLocal8Bit(),
                 QDir( Smb4KSettings::mountPrefix().path() ).canonicalPath().toLocal8Bit().length() ) == 0 )
  {
    QDir dir( share->canonicalPath() );

    if ( dir.rmdir( dir.canonicalPath() ) )
    {
      dir.cdUp();
      dir.rmdir( dir.canonicalPath() );
    }
  }

  int index = Smb4KGlobal::mountedSharesList()->indexOf( share );
  delete Smb4KGlobal::mountedSharesList()->takeAt( index );

  emit updated();
}


void Smb4KMounter::prepareForShutdown()
{
  slotAboutToQuit();
}


void Smb4KMounter::check( Smb4KShare *share )
{
  if ( share )
  {
    m_priv->thread.setShare( share );
    m_priv->thread.start();
    m_priv->thread.wait( THREAD_WAITING_TIME );

    // Values are set by Smb4KMounterPrivate::Thread::run().
  }
  else
  {
    // Do nothing
  }
}


void Smb4KMounter::timerEvent( QTimerEvent * )
{
  if ( !kapp->startingUp() )
  {
    if ( !m_working && !m_queue.isEmpty() )
    {
      // Tell the mounter, that it is busy.
      m_working = true;

      QueueContainer c = m_queue.dequeue();

      m_state = c.todo();

      switch ( m_state )
      {
        case TriggerRemounts:
        {
          triggerRemounts();
          m_state = Idle;
          break;
        }
        case Import:
        {
          import();
          m_state = Idle;
          break;
        }
        case Mount:
        case Remount:
        {
          emit state( MOUNTER_MOUNT );
          mount( c.share() );
          break;
        }
        case Unmount:
        {
          emit state( MOUNTER_UNMOUNT );
          unmount( c.share(), c.forceUnmount(), c.noMessage() );
          break;
        }
        case UnmountAll:
        {
          unmountAll();
          break;
        }
        default:
        {
          break;
        }
      }
    }

    m_priv->timerTicks++;

    if ( m_priv->timerTicks * timerInterval() >= Smb4KSettings::checkInterval() /* msec */ &&
        (!m_working || m_queue.isEmpty()) )
    {
      m_queue.enqueue( QueueContainer( Import ) );
      m_priv->timerTicks = 0;
    }
  }
  else
  {
    // Do nothing and wait until the application started up.
  }
}


/////////////////////////////////////////////////////////////////////////////
// SLOT IMPLEMENTATIONS
/////////////////////////////////////////////////////////////////////////////


void Smb4KMounter::slotProcessFinished( int exitCode, QProcess::ExitStatus exitStatus )
{
  endProcess( exitCode, exitStatus );
}


void Smb4KMounter::slotProcessError( QProcess::ProcessError errorCode )
{
  m_process_error = errorCode;
}


void Smb4KMounter::slotAboutToQuit()
{
  // Abort any actions.
  abort();

  // If the user wants to remount his or her shares, save them now.
  if ( Smb4KSettings::remountShares() && !Smb4KGlobal::mountedSharesList()->isEmpty() )
  {
    for ( int i = 0; i < Smb4KGlobal::mountedSharesList()->size(); ++i )
    {
      Smb4KSambaOptionsHandler::self()->remount( Smb4KGlobal::mountedSharesList()->at( i ), !Smb4KGlobal::mountedSharesList()->at( i )->isForeign() );
    }
  }

  Smb4KSambaOptionsHandler::self()->sync();

  // Clean up the mount prefix.
  QDir dir;
  dir.cd( Smb4KSettings::mountPrefix().path() );
  QStringList dirs = dir.entryList( QDir::Dirs, QDir::NoSort );

  QList<Smb4KShare *> inaccessible_shares = findInaccessibleShares();

  for ( int i = 0; i < dirs.size(); ++i )
  {
    if ( QString::compare( dirs.at( i ), "." ) != 0 && QString::compare( dirs.at( i ), ".." ) != 0 )
    {
      bool inaccessible = false;

      for ( int j = 0; j < inaccessible_shares.size(); ++j )
      {
        if ( qstrncmp( inaccessible_shares.at( j )->path(),
                       (Smb4KSettings::mountPrefix().path()+dirs.at( i )).toLocal8Bit(),
                       (Smb4KSettings::mountPrefix().path()+dirs.at( i )).toLocal8Bit().length() ) == 0 ||
             qstrncmp( inaccessible_shares.at( j )->canonicalPath(),
                       (Smb4KSettings::mountPrefix().path()+dirs.at( i )).toLocal8Bit(),
                       (Smb4KSettings::mountPrefix().path()+dirs.at( i )).toLocal8Bit().length() ) == 0 )
        {
          inaccessible = true;

          break;
        }
        else
        {
          continue;
        }
      }

      if ( !inaccessible )
      {
        dir.cd( dirs.at( i ) );

        QStringList subdirs = dir.entryList( QDir::Dirs, QDir::NoSort );

        for ( int k = 0; k < subdirs.size(); ++k )
        {
          if ( QString::compare( subdirs.at( k ), "." ) != 0 &&
               QString::compare( subdirs.at( k ), ".." ) != 0 )
          {
            dir.rmdir( subdirs.at( k ) );
          }
        }

        dir.cdUp();
        dir.rmdir( dirs.at( i ) );
      }
    }
  }

  inaccessible_shares.clear();

  // Unmount the shares if the user chose to do so.
  if ( Smb4KSettings::unmountSharesOnExit() )
  {
    QString command;

    KProcess proc;

    for ( int i = 0; i < Smb4KGlobal::mountedSharesList()->size(); ++i )
    {
      if ( !Smb4KGlobal::mountedSharesList()->at( i )->isForeign() )
      {
        if ( Smb4KSettings::alwaysUseSuperUser() )
        {
#ifndef __FreeBSD__
          command.append( Smb4KSettings::sudo() );
          command.append( " smb4k_umount -s -t " );
          command.append( Smb4KGlobal::mountedSharesList()->at( i )->fileSystemString() );
          command.append( " " );
#else
          command.append( Smb4KSettings::sudo() );
          command.append( " smb4k_umount " );
#endif
          command.append( KShell::quoteArg( Smb4KGlobal::mountedSharesList()->at( i )->path() ) );
          command.append( " ; " );
        }
        else
        {
#ifndef __FreeBSD__
          command.append( "smb4k_umount -n -t " );
          command.append( Smb4KGlobal::mountedSharesList()->at( i )->fileSystemString() );
          command.append( " " );
#else
          command.append( "smb4k_umount " );
#endif
          command.append( KShell::quoteArg( Smb4KGlobal::mountedSharesList()->at( i )->path() ) );
          command.append( " ; " );
        }

        dir.setPath( Smb4KGlobal::mountedSharesList()->at( i )->canonicalPath() );

#ifndef __FreeBSD__
        command.append( "rmdir --ignore-fail-on-non-empty " );
        command.append( KShell::quoteArg( dir.canonicalPath() ) );
        command.append( " ; " );
        command.append( "rmdir --ignore-fail-on-non-empty " );
        dir.cdUp();
        command.append( KShell::quoteArg( dir.canonicalPath() ) );
        command.append( " ; " );
#else
        command.append( "rmdir " );
        command.append( KShell::quoteArg( dir.canonicalPath() ) );
        command.append( " ; " );
        command.append( "rmdir " );
        dir.cdUp();
        command.append( KShell::quoteArg( dir.canonicalPath() ) );
        command.append( " ; " );
#endif
      }
      else
      {
        continue;
      }
    }

    command.truncate( command.length() - 2 );

    proc.setShellCommand( command );
    proc.startDetached();
  }
}


#include "smb4kmounter.moc"
