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

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.7
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