• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

Konsole

ProcessInfo.cpp

Go to the documentation of this file.
00001 /*
00002     Copyright 2007-2008 by Robert Knight <robertknight@gmail.countm>
00003 
00004     This program is free software; you can redistribute it and/or modify
00005     it under the terms of the GNU General Public License as published by
00006     the Free Software Foundation; either version 2 of the License, or
00007     (at your option) any later version.
00008 
00009     This program is distributed in the hope that it will be useful,
00010     but WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012     GNU General Public License for more details.
00013 
00014     You should have received a copy of the GNU General Public License
00015     along with this program; if not, write to the Free Software
00016     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017     02110-1301  USA.
00018 */
00019 
00020 // Own
00021 #include "ProcessInfo.h"
00022 
00023 // Unix
00024 #include <sys/socket.h>
00025 #include <netinet/in.h>
00026 #include <arpa/inet.h>
00027 
00028 // Qt
00029 #include <KDebug>
00030 #include <QtCore/QDir>
00031 #include <QtCore/QFileInfo>
00032 #include <QtCore/QRegExp>
00033 #include <QtCore/QTextStream>
00034 #include <QtCore/QStringList>
00035 
00036 // KDE
00037 #include <KConfigGroup>
00038 #include <KGlobal>
00039 #include <KSharedConfig>
00040 
00041 using namespace Konsole;
00042 
00043 ProcessInfo::ProcessInfo(int pid , bool enableEnvironmentRead)
00044     : _fields( ARGUMENTS | ENVIRONMENT ) // arguments and environments
00045                                          // are currently always valid,
00046                                          // they just return an empty
00047                                          // vector / map respectively
00048                                          // if no arguments
00049                                          // or environment bindings
00050                                          // have been explicitly set
00051     , _enableEnvironmentRead(enableEnvironmentRead)
00052     , _pid(pid)
00053     , _parentPid(0)
00054     , _foregroundPid(0)
00055     , _lastError(NoError)
00056 {
00057 }
00058 
00059 ProcessInfo::Error ProcessInfo::error() const { return _lastError; }
00060 void ProcessInfo::setError(Error error) { _lastError = error; }
00061 
00062 void ProcessInfo::update() 
00063 {
00064     readProcessInfo(_pid,_enableEnvironmentRead);
00065 }
00066 
00067 QString ProcessInfo::format(const QString& input) const
00068 {
00069    bool ok = false;
00070 
00071    QString output(input);
00072 
00073    // search for and replace known marker
00074    output.replace("%u","NOT IMPLEMENTED YET");
00075    output.replace("%n",name(&ok));
00076    output.replace("%c",formatCommand(name(&ok),arguments(&ok),ShortCommandFormat));
00077    output.replace("%C",formatCommand(name(&ok),arguments(&ok),LongCommandFormat));
00078    
00079    // read current dir, if an error occurs try the parent as the next
00080    // best option
00081    int currentPid = parentPid(&ok);
00082    QString dir = currentDir(&ok);
00083    while ( !ok && currentPid != 0 )
00084    {
00085        ProcessInfo* current = ProcessInfo::newInstance(currentPid);
00086        current->update();
00087        currentPid = current->parentPid(&ok); 
00088        dir = current->currentDir(&ok);
00089        delete current;
00090    }
00091         
00092    output.replace("%D",dir);
00093    output.replace("%d",formatShortDir(dir));
00094    
00095    // remove any remaining %[LETTER] sequences
00096    // output.replace(QRegExp("%\\w"), QString());
00097 
00098    return output;
00099 }
00100 
00101 QString ProcessInfo::formatCommand(const QString& name, 
00102                                    const QVector<QString>& arguments,
00103                                    CommandFormat format) const
00104 {
00105     // TODO Implement me
00106     return QStringList(QList<QString>::fromVector(arguments)).join(" ");
00107 }
00108 
00109 QSet<QString> ProcessInfo::_commonDirNames;
00110 
00111 QSet<QString> ProcessInfo::commonDirNames() 
00112 {
00113     if ( _commonDirNames.isEmpty() )
00114     {
00115         KSharedConfigPtr config = KGlobal::config();
00116         KConfigGroup configGroup = config->group("ProcessInfo");
00117 
00118         QStringList defaults = QStringList() 
00119                              << "src" << "build" << "debug" << "release" 
00120                              << "bin" << "lib"   << "libs"  << "tmp" 
00121                              << "doc" << "docs"  << "data"  << "share"
00122                              << "examples" << "icons" << "pics" << "plugins" 
00123                              << "tests" << "media" << "l10n" << "include" 
00124                              << "includes" << "locale" << "ui";
00125 
00126         _commonDirNames = QSet<QString>::fromList(configGroup.readEntry("CommonDirNames",defaults));
00127 
00128     }
00129 
00130     return _commonDirNames;
00131 }
00132 
00133 QString ProcessInfo::formatShortDir(const QString& input) const
00134 {
00135     QString result;
00136 
00137     QStringList parts = input.split( QDir::separator() );
00138 
00139     // temporarily hard-coded
00140     QSet<QString> dirNamesToShorten = commonDirNames();
00141 
00142     QListIterator<QString> iter(parts);
00143     iter.toBack();
00144 
00145     // go backwards through the list of the path's parts
00146     // adding abbreviations of common directory names
00147     // and stopping when we reach a dir name which is not
00148     // in the commonDirNames set
00149     while ( iter.hasPrevious() )
00150     {
00151         QString part = iter.previous();
00152 
00153         if ( dirNamesToShorten.contains(part) )
00154         {
00155             result.prepend(QDir::separator() + part[0]);
00156         }
00157         else
00158         {
00159             result.prepend(part);
00160             break;
00161         }
00162     }
00163 
00164     return result;
00165 }
00166 
00167 QVector<QString> ProcessInfo::arguments(bool* ok) const
00168 {
00169     *ok = _fields & ARGUMENTS;
00170 
00171     return _arguments;
00172 }
00173 
00174 QMap<QString,QString> ProcessInfo::environment(bool* ok) const
00175 {
00176     *ok = _fields & ENVIRONMENT;
00177 
00178     return _environment;
00179 }
00180 
00181 bool ProcessInfo::isValid() const
00182 {
00183     return _fields & PROCESS_ID;
00184 }
00185 
00186 int ProcessInfo::pid(bool* ok) const
00187 {
00188     *ok = _fields & PROCESS_ID;
00189 
00190     return _pid;
00191 }
00192 
00193 int ProcessInfo::parentPid(bool* ok) const
00194 {
00195     *ok = _fields & PARENT_PID;
00196 
00197     return _parentPid;
00198 }
00199 
00200 int ProcessInfo::foregroundPid(bool* ok) const
00201 {
00202     *ok = _fields & FOREGROUND_PID;
00203 
00204     return _foregroundPid;
00205 }
00206 
00207 QString ProcessInfo::name(bool* ok) const
00208 {
00209     *ok = _fields & NAME;
00210 
00211     return _name;
00212 }
00213 
00214 void ProcessInfo::setPid(int pid)
00215 {
00216     _pid = pid;
00217     _fields |= PROCESS_ID;
00218 }
00219 
00220 void ProcessInfo::setParentPid(int pid)
00221 {
00222     _parentPid = pid;
00223     _fields |= PARENT_PID;
00224 }
00225 void ProcessInfo::setForegroundPid(int pid)
00226 {
00227     _foregroundPid = pid;
00228     _fields |= FOREGROUND_PID;
00229 }
00230 
00231 QString ProcessInfo::currentDir(bool* ok) const
00232 {
00233     if (ok)
00234         *ok = _fields & CURRENT_DIR;
00235 
00236     return _currentDir;
00237 }
00238 void ProcessInfo::setCurrentDir(const QString& dir)
00239 {
00240     _fields |= CURRENT_DIR;
00241     _currentDir = dir;
00242 }
00243 
00244 void ProcessInfo::setName(const QString& name)
00245 {
00246     _name = name;
00247     _fields |= NAME;
00248 }
00249 void ProcessInfo::addArgument(const QString& argument)
00250 {
00251     _arguments << argument;    
00252 }
00253 
00254 void ProcessInfo::addEnvironmentBinding(const QString& name , const QString& value)
00255 {
00256     _environment.insert(name,value);
00257 }
00258 
00259 void ProcessInfo::setFileError( QFile::FileError error )
00260 {
00261     switch ( error )
00262     {
00263         case PermissionsError:
00264             setError( PermissionsError );
00265             break;
00266         case NoError:
00267             setError( NoError );
00268             break;
00269         default:
00270             setError( UnknownError );
00271     }
00272 }
00273 
00274 //
00275 // ProcessInfo::newInstance() is way at the bottom so it can see all of the
00276 // implementations of the UnixProcessInfo abstract class.
00277 //
00278 
00279 NullProcessInfo::NullProcessInfo(int pid,bool enableEnvironmentRead)
00280     : ProcessInfo(pid,enableEnvironmentRead)
00281 {
00282 }
00283 
00284 bool NullProcessInfo::readProcessInfo(int /*pid*/ , bool /*enableEnvironmentRead*/)
00285 {
00286     return false;
00287 }
00288 
00289 UnixProcessInfo::UnixProcessInfo(int pid,bool enableEnvironmentRead)
00290     : ProcessInfo(pid,enableEnvironmentRead)
00291 {
00292 }
00293 
00294 bool UnixProcessInfo::readProcessInfo(int pid , bool enableEnvironmentRead)
00295 {
00296     bool ok = readProcInfo(pid);
00297     if (ok)
00298     {
00299         ok |= readArguments(pid);
00300         ok |= readCurrentDir(pid);
00301         if ( enableEnvironmentRead )
00302         {
00303             ok |= readEnvironment(pid);
00304         }
00305     }
00306     return ok;
00307 }
00308 
00309 
00310 class LinuxProcessInfo : public UnixProcessInfo
00311 {
00312 public:
00313     LinuxProcessInfo(int pid, bool env) :
00314         UnixProcessInfo(pid,env)
00315     {
00316     }
00317 
00318 private:
00319     virtual bool readProcInfo(int pid)
00320     {
00321         // indicies of various fields within the process status file which
00322         // contain various information about the process
00323         const int PARENT_PID_FIELD = 3;
00324         const int PROCESS_NAME_FIELD = 1;
00325         const int GROUP_PROCESS_FIELD = 7;
00326 
00327         QString parentPidString;
00328         QString processNameString;
00329         QString foregroundPidString;
00330 
00331         // read process status file ( /proc/<pid/stat )
00332         //
00333         // the expected file format is a list of fields separated by spaces, using
00334         // parenthesies to escape fields such as the process name which may itself contain
00335         // spaces:
00336         //
00337         // FIELD FIELD (FIELD WITH SPACES) FIELD FIELD
00338         //
00339         QFile processInfo( QString("/proc/%1/stat").arg(pid) );
00340         if ( processInfo.open(QIODevice::ReadOnly) )
00341         {
00342             QTextStream stream(&processInfo);
00343             QString data = stream.readAll();
00344 
00345             int stack = 0;
00346             int field = 0;
00347             int pos = 0;
00348 
00349             while (pos < data.count())
00350             {
00351                 QChar c = data[pos];
00352 
00353                 if ( c == '(' )
00354                     stack++;
00355                 else if ( c == ')' )
00356                     stack--;
00357                 else if ( stack == 0 && c == ' ' )
00358                     field++;
00359                 else
00360                 {
00361                     switch ( field )
00362                     {
00363                         case PARENT_PID_FIELD:
00364                             parentPidString.append(c);
00365                             break;
00366                         case PROCESS_NAME_FIELD:
00367                             processNameString.append(c);
00368                             break;
00369                         case GROUP_PROCESS_FIELD:
00370                             foregroundPidString.append(c);
00371                             break;
00372                     }
00373                 }
00374 
00375                 pos++;
00376             }
00377         }
00378         else
00379         {
00380             setFileError( processInfo.error() );
00381             return false;
00382         }
00383 
00384         // check that data was read successfully
00385         bool ok = false;
00386         int foregroundPid = foregroundPidString.toInt(&ok);
00387         if (ok)
00388             setForegroundPid(foregroundPid);
00389 
00390         int parentPid = parentPidString.toInt(&ok);
00391         if (ok)
00392             setParentPid(parentPid);
00393 
00394         if (!processNameString.isEmpty())
00395             setName(processNameString);
00396 
00397         // update object state
00398         setPid(pid);
00399 
00400         return ok;
00401     }
00402 
00403     virtual bool readArguments(int pid)
00404     {
00405         // read command-line arguments file found at /proc/<pid>/cmdline
00406         // the expected format is a list of strings delimited by null characters,
00407         // and ending in a double null character pair.
00408 
00409         QFile argumentsFile( QString("/proc/%1/cmdline").arg(pid) );
00410         if ( argumentsFile.open(QIODevice::ReadOnly) )
00411         {
00412             QTextStream stream(&argumentsFile);
00413             QString data = stream.readAll();
00414 
00415             QStringList argList = data.split( QChar('\0') );
00416 
00417             foreach ( const QString &entry , argList )
00418             {
00419                 if (!entry.isEmpty())
00420                     addArgument(entry);
00421             }
00422         }
00423         else
00424         {
00425             setFileError( argumentsFile.error() );
00426         }
00427 
00428         return true;
00429     }
00430 
00431     virtual bool readCurrentDir(int pid)
00432     {
00433         QFileInfo info( QString("/proc/%1/cwd").arg(pid) );
00434 
00435         const bool readable = info.isReadable();
00436 
00437         if ( readable && info.isSymLink() )
00438         {
00439             setCurrentDir( info.symLinkTarget() );
00440             return true;
00441         }
00442         else
00443         {
00444             if ( !readable )
00445                 setError( PermissionsError );
00446             else
00447                 setError( UnknownError );
00448 
00449             return false;
00450         }
00451     }
00452 
00453     virtual bool readEnvironment(int pid)
00454     {
00455         // read environment bindings file found at /proc/<pid>/environ
00456         // the expected format is a list of KEY=VALUE strings delimited by null
00457         // characters and ending in a double null character pair.
00458 
00459         QFile environmentFile( QString("/proc/%1/environ").arg(pid) );
00460         if ( environmentFile.open(QIODevice::ReadOnly) )
00461         {
00462             QTextStream stream(&environmentFile);
00463             QString data = stream.readAll();
00464 
00465             QStringList bindingList = data.split( QChar('\0') );
00466 
00467             foreach( const QString &entry , bindingList )
00468             {
00469                 QString name;
00470                 QString value;
00471 
00472                 int splitPos = entry.indexOf('=');
00473 
00474                 if ( splitPos != -1 )
00475                 {
00476                     name = entry.mid(0,splitPos);
00477                     value = entry.mid(splitPos+1,-1);
00478 
00479                     addEnvironmentBinding(name,value);
00480                 }
00481             }
00482         }
00483         else
00484         {
00485             setFileError( environmentFile.error() );
00486         }
00487 
00488         return true;
00489     }
00490 } ;
00491 
00492 #ifdef Q_OS_SOLARIS
00493     // The procfs structure definition requires off_t to be
00494     // 32 bits, which only applies if FILE_OFFSET_BITS=32.
00495     // Futz around here to get it to compile regardless,
00496     // although some of the structure sizes might be wrong.
00497     // Fortunately, the structures we actually use don't use
00498     // off_t, and we're safe.
00499     #if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS==64)
00500         #undef _FILE_OFFSET_BITS
00501     #endif
00502     #include <procfs.h>
00503 #else
00504     // On non-Solaris platforms, define a fake psinfo structure
00505     // so that the SolarisProcessInfo class can be compiled
00506     //
00507     // That avoids the trap where you change the API and
00508     // don't notice it in #ifdeffed platform-specific parts
00509     // of the code.
00510     struct psinfo {
00511         int pr_ppid;
00512         int pr_pgid;
00513         char* pr_fname;
00514         char* pr_psargs;
00515     } ;
00516     static const int PRARGSZ=1;
00517 #endif
00518 
00519 class SolarisProcessInfo : public UnixProcessInfo
00520 {
00521 public:
00522     SolarisProcessInfo(int pid, bool readEnvironment) 
00523     : UnixProcessInfo(pid,readEnvironment)
00524     {
00525     }
00526 private:
00527     virtual bool readProcInfo(int pid)
00528     {
00529         QFile psinfo( QString("/proc/%1/psinfo").arg(pid) );
00530         if ( psinfo.open( QIODevice::ReadOnly ) )
00531         {
00532             struct psinfo info;
00533             if (psinfo.read((char *)&info,sizeof(info)) != sizeof(info))
00534             {
00535                 return false;
00536             }
00537 
00538             setParentPid(info.pr_ppid);
00539             setForegroundPid(info.pr_pgid);
00540             setName(info.pr_fname);
00541             setPid(pid);
00542 
00543             // Bogus, because we're treating the arguments as one single string
00544             info.pr_psargs[PRARGSZ-1]=0;
00545             addArgument(info.pr_psargs);
00546         }
00547         return true;
00548     }
00549 
00550     virtual bool readArguments(int /*pid*/)
00551     {
00552         // Handled in readProcInfo()
00553         return true;
00554     }
00555 
00556     virtual bool readEnvironment(int /*pid*/)
00557     {
00558         // Not supported in Solaris
00559         return true;
00560     }
00561 
00562     virtual bool readCurrentDir(int pid)
00563     {
00564         QFileInfo info( QString("/proc/%1/path/cwd").arg(pid) );
00565         const bool readable = info.isReadable();
00566 
00567         if ( readable && info.isSymLink() )
00568         {
00569             setCurrentDir( info.symLinkTarget() );
00570             return true;
00571         }
00572         else
00573         {
00574             if ( !readable )
00575                 setError( PermissionsError );
00576             else
00577                 setError( UnknownError );
00578 
00579             return false;
00580         }
00581     }
00582 } ;
00583 
00584 SSHProcessInfo::SSHProcessInfo(const ProcessInfo& process)
00585  : _process(process)
00586 {
00587     bool ok = false;
00588 
00589     // check that this is a SSH process
00590     const QString& name = _process.name(&ok);
00591 
00592     if ( !ok || name != "ssh" )
00593     {
00594         if ( !ok )
00595             kDebug() << "Could not read process info";
00596         else
00597             kDebug() << "Process is not a SSH process";
00598 
00599         return;
00600     }
00601 
00602     // read arguments
00603     const QVector<QString>& args = _process.arguments(&ok); 
00604 
00605     // SSH options
00606     // these are taken from the SSH manual ( accessed via 'man ssh' )
00607     
00608     // options which take no arguments
00609     static const QString noOptionsArguments("1246AaCfgkMNnqsTtVvXxY"); 
00610     // options which take one argument
00611     static const QString singleOptionArguments("bcDeFiLlmOopRSw");
00612 
00613     if ( ok )
00614     {
00615          // find the username, host and command arguments
00616          //
00617          // the username/host is assumed to be the first argument 
00618          // which is not an option
00619          // ( ie. does not start with a dash '-' character )
00620          // or an argument to a previous option.
00621          //
00622          // the command, if specified, is assumed to be the argument following
00623          // the username and host
00624          //
00625          // note that we skip the argument at index 0 because that is the
00626          // program name ( expected to be 'ssh' in this case )
00627          for ( int i = 1 ; i < args.count() ; i++ )
00628          {
00629             // if this argument is an option then skip it, plus any 
00630             // following arguments which refer to this option
00631             if ( args[i].startsWith('-') )
00632             {
00633                 QChar argChar = ( args[i].length() > 1 ) ? args[i][1] : '\0';
00634 
00635                 if ( noOptionsArguments.contains(argChar) )
00636                     continue;
00637                 else if ( singleOptionArguments.contains(argChar) )
00638                 {
00639                     i++;
00640                     continue;
00641                 }
00642             }
00643 
00644             // check whether the host has been found yet
00645             // if not, this must be the username/host argument 
00646             if ( _host.isEmpty() )
00647             {
00648                 // check to see if only a hostname is specified, or whether
00649                 // both a username and host are specified ( in which case they
00650                 // are separated by an '@' character:  username@host )
00651             
00652                 int separatorPosition = args[i].indexOf('@');
00653                 if ( separatorPosition != -1 )
00654                 {
00655                     // username and host specified
00656                     _user = args[i].left(separatorPosition);
00657                     _host = args[i].mid(separatorPosition+1);
00658                 }
00659                 else
00660                 {
00661                     // just the host specified
00662                     _host = args[i];
00663                 }
00664             }
00665             else
00666             {
00667                 // host has already been found, this must be the command argument
00668                 _command = args[i];
00669             }
00670 
00671          }
00672     }
00673     else
00674     {
00675         kDebug() << "Could not read arguments";
00676         
00677         return;
00678     }
00679 }
00680 
00681 QString SSHProcessInfo::userName() const
00682 {
00683     return _user;
00684 }
00685 QString SSHProcessInfo::host() const
00686 {
00687     return _host;
00688 }
00689 QString SSHProcessInfo::command() const
00690 {
00691     return _command;
00692 }
00693 QString SSHProcessInfo::format(const QString& input) const
00694 {
00695     QString output(input);
00696    
00697     // test whether host is an ip address
00698     // in which case 'short host' and 'full host'
00699     // markers in the input string are replaced with
00700     // the full address
00701     bool isIpAddress = false;
00702    
00703     struct in_addr address;
00704     if ( inet_aton(_host.toLocal8Bit().constData(),&address) != 0 )
00705         isIpAddress = true;
00706     else
00707         isIpAddress = false;
00708 
00709     // search for and replace known markers
00710     output.replace("%u",_user);
00711 
00712     if ( isIpAddress )
00713         output.replace("%h",_host);
00714     else
00715         output.replace("%h",_host.left(_host.indexOf('.')));
00716     
00717     output.replace("%H",_host);
00718     output.replace("%c",_command);
00719 
00720     return output;
00721 }
00722 
00723 ProcessInfo* ProcessInfo::newInstance(int pid,bool enableEnvironmentRead)
00724 {
00725 #ifdef Q_OS_LINUX
00726     return new LinuxProcessInfo(pid,enableEnvironmentRead);
00727 #elif defined(Q_OS_SOLARIS)
00728     return new SolarisProcessInfo(pid,enableEnvironmentRead);
00729 #else
00730     return new NullProcessInfo(pid,enableEnvironmentRead);
00731 #endif
00732 }
00733 

Konsole

Skip menu "Konsole"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

API Reference

Skip menu "API Reference"
  • Konsole
  • Libraries
  •   libkonq
Generated for API Reference by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal