2 Copyright 2007-2008 by Robert Knight <robertknight@gmail.countm>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 #include "ProcessInfo.h"
24 #include <sys/socket.h>
25 #include <netinet/in.h>
26 #include <arpa/inet.h>
30 #include <QtCore/QDir>
31 #include <QtCore/QFileInfo>
32 #include <QtCore/QRegExp>
33 #include <QtCore/QTextStream>
34 #include <QtCore/QStringList>
37 #include <KConfigGroup>
39 #include <KSharedConfig>
41 using namespace Konsole
;
43 ProcessInfo::ProcessInfo(int pid
, bool enableEnvironmentRead
)
44 : _fields( ARGUMENTS
| ENVIRONMENT
) // arguments and environments
45 // are currently always valid,
46 // they just return an empty
47 // vector / map respectively
49 // or environment bindings
50 // have been explicitly set
51 , _enableEnvironmentRead(enableEnvironmentRead
)
59 ProcessInfo::Error
ProcessInfo::error() const { return _lastError
; }
60 void ProcessInfo::setError(Error error
) { _lastError
= error
; }
62 void ProcessInfo::update()
64 readProcessInfo(_pid
,_enableEnvironmentRead
);
67 QString
ProcessInfo::validCurrentDir() const
71 // read current dir, if an error occurs try the parent as the next
73 int currentPid
= parentPid(&ok
);
74 QString dir
= currentDir(&ok
);
75 while ( !ok
&& currentPid
!= 0 )
77 ProcessInfo
* current
= ProcessInfo::newInstance(currentPid
);
79 currentPid
= current
->parentPid(&ok
);
80 dir
= current
->currentDir(&ok
);
87 QString
ProcessInfo::format(const QString
& input
) const
91 QString
output(input
);
93 // search for and replace known marker
94 output
.replace("%u","NOT IMPLEMENTED YET");
95 output
.replace("%n",name(&ok
));
96 output
.replace("%c",formatCommand(name(&ok
),arguments(&ok
),ShortCommandFormat
));
97 output
.replace("%C",formatCommand(name(&ok
),arguments(&ok
),LongCommandFormat
));
99 QString dir
= validCurrentDir();
100 output
.replace("%D",dir
);
101 output
.replace("%d",formatShortDir(dir
));
103 // remove any remaining %[LETTER] sequences
104 // output.replace(QRegExp("%\\w"), QString());
109 QString
ProcessInfo::formatCommand(const QString
& name
,
110 const QVector
<QString
>& arguments
,
111 CommandFormat format
) const
114 return QStringList(QList
<QString
>::fromVector(arguments
)).join(" ");
117 QSet
<QString
> ProcessInfo::_commonDirNames
;
119 QSet
<QString
> ProcessInfo::commonDirNames()
121 if ( _commonDirNames
.isEmpty() )
123 KSharedConfigPtr config
= KGlobal::config();
124 KConfigGroup configGroup
= config
->group("ProcessInfo");
126 QStringList defaults
= QStringList()
127 << "src" << "build" << "debug" << "release"
128 << "bin" << "lib" << "libs" << "tmp"
129 << "doc" << "docs" << "data" << "share"
130 << "examples" << "icons" << "pics" << "plugins"
131 << "tests" << "media" << "l10n" << "include"
132 << "includes" << "locale" << "ui";
134 _commonDirNames
= QSet
<QString
>::fromList(configGroup
.readEntry("CommonDirNames",defaults
));
138 return _commonDirNames
;
141 QString
ProcessInfo::formatShortDir(const QString
& input
) const
145 QStringList parts
= input
.split( QDir::separator() );
147 // temporarily hard-coded
148 QSet
<QString
> dirNamesToShorten
= commonDirNames();
150 QListIterator
<QString
> iter(parts
);
153 // go backwards through the list of the path's parts
154 // adding abbreviations of common directory names
155 // and stopping when we reach a dir name which is not
156 // in the commonDirNames set
157 while ( iter
.hasPrevious() )
159 QString part
= iter
.previous();
161 if ( dirNamesToShorten
.contains(part
) )
163 result
.prepend(QDir::separator() + part
[0]);
167 result
.prepend(part
);
175 QVector
<QString
> ProcessInfo::arguments(bool* ok
) const
177 *ok
= _fields
& ARGUMENTS
;
182 QMap
<QString
,QString
> ProcessInfo::environment(bool* ok
) const
184 *ok
= _fields
& ENVIRONMENT
;
189 bool ProcessInfo::isValid() const
191 return _fields
& PROCESS_ID
;
194 int ProcessInfo::pid(bool* ok
) const
196 *ok
= _fields
& PROCESS_ID
;
201 int ProcessInfo::parentPid(bool* ok
) const
203 *ok
= _fields
& PARENT_PID
;
208 int ProcessInfo::foregroundPid(bool* ok
) const
210 *ok
= _fields
& FOREGROUND_PID
;
212 return _foregroundPid
;
215 QString
ProcessInfo::name(bool* ok
) const
217 *ok
= _fields
& NAME
;
222 void ProcessInfo::setPid(int pid
)
225 _fields
|= PROCESS_ID
;
228 void ProcessInfo::setParentPid(int pid
)
231 _fields
|= PARENT_PID
;
233 void ProcessInfo::setForegroundPid(int pid
)
235 _foregroundPid
= pid
;
236 _fields
|= FOREGROUND_PID
;
239 QString
ProcessInfo::currentDir(bool* ok
) const
242 *ok
= _fields
& CURRENT_DIR
;
246 void ProcessInfo::setCurrentDir(const QString
& dir
)
248 _fields
|= CURRENT_DIR
;
252 void ProcessInfo::setName(const QString
& name
)
257 void ProcessInfo::addArgument(const QString
& argument
)
259 _arguments
<< argument
;
262 void ProcessInfo::addEnvironmentBinding(const QString
& name
, const QString
& value
)
264 _environment
.insert(name
,value
);
267 void ProcessInfo::setFileError( QFile::FileError error
)
271 case PermissionsError
:
272 setError( PermissionsError
);
278 setError( UnknownError
);
283 // ProcessInfo::newInstance() is way at the bottom so it can see all of the
284 // implementations of the UnixProcessInfo abstract class.
287 NullProcessInfo::NullProcessInfo(int pid
,bool enableEnvironmentRead
)
288 : ProcessInfo(pid
,enableEnvironmentRead
)
292 bool NullProcessInfo::readProcessInfo(int /*pid*/ , bool /*enableEnvironmentRead*/)
297 UnixProcessInfo::UnixProcessInfo(int pid
,bool enableEnvironmentRead
)
298 : ProcessInfo(pid
,enableEnvironmentRead
)
302 bool UnixProcessInfo::readProcessInfo(int pid
, bool enableEnvironmentRead
)
304 bool ok
= readProcInfo(pid
);
307 ok
|= readArguments(pid
);
308 ok
|= readCurrentDir(pid
);
309 if ( enableEnvironmentRead
)
311 ok
|= readEnvironment(pid
);
318 class LinuxProcessInfo
: public UnixProcessInfo
321 LinuxProcessInfo(int pid
, bool env
) :
322 UnixProcessInfo(pid
,env
)
327 virtual bool readProcInfo(int pid
)
329 // indicies of various fields within the process status file which
330 // contain various information about the process
331 const int PARENT_PID_FIELD
= 3;
332 const int PROCESS_NAME_FIELD
= 1;
333 const int GROUP_PROCESS_FIELD
= 7;
335 QString parentPidString
;
336 QString processNameString
;
337 QString foregroundPidString
;
339 // read process status file ( /proc/<pid/stat )
341 // the expected file format is a list of fields separated by spaces, using
342 // parenthesies to escape fields such as the process name which may itself contain
345 // FIELD FIELD (FIELD WITH SPACES) FIELD FIELD
347 QFile
processInfo( QString("/proc/%1/stat").arg(pid
) );
348 if ( processInfo
.open(QIODevice::ReadOnly
) )
350 QTextStream
stream(&processInfo
);
351 QString data
= stream
.readAll();
357 while (pos
< data
.count())
365 else if ( stack
== 0 && c
== ' ' )
371 case PARENT_PID_FIELD
:
372 parentPidString
.append(c
);
374 case PROCESS_NAME_FIELD
:
375 processNameString
.append(c
);
377 case GROUP_PROCESS_FIELD
:
378 foregroundPidString
.append(c
);
388 setFileError( processInfo
.error() );
392 // check that data was read successfully
394 int foregroundPid
= foregroundPidString
.toInt(&ok
);
396 setForegroundPid(foregroundPid
);
398 int parentPid
= parentPidString
.toInt(&ok
);
400 setParentPid(parentPid
);
402 if (!processNameString
.isEmpty())
403 setName(processNameString
);
405 // update object state
411 virtual bool readArguments(int pid
)
413 // read command-line arguments file found at /proc/<pid>/cmdline
414 // the expected format is a list of strings delimited by null characters,
415 // and ending in a double null character pair.
417 QFile
argumentsFile( QString("/proc/%1/cmdline").arg(pid
) );
418 if ( argumentsFile
.open(QIODevice::ReadOnly
) )
420 QTextStream
stream(&argumentsFile
);
421 QString data
= stream
.readAll();
423 QStringList argList
= data
.split( QChar('\0') );
425 foreach ( const QString
&entry
, argList
)
427 if (!entry
.isEmpty())
433 setFileError( argumentsFile
.error() );
439 virtual bool readCurrentDir(int pid
)
441 QFileInfo
info( QString("/proc/%1/cwd").arg(pid
) );
443 const bool readable
= info
.isReadable();
445 if ( readable
&& info
.isSymLink() )
447 setCurrentDir( info
.symLinkTarget() );
453 setError( PermissionsError
);
455 setError( UnknownError
);
461 virtual bool readEnvironment(int pid
)
463 // read environment bindings file found at /proc/<pid>/environ
464 // the expected format is a list of KEY=VALUE strings delimited by null
465 // characters and ending in a double null character pair.
467 QFile
environmentFile( QString("/proc/%1/environ").arg(pid
) );
468 if ( environmentFile
.open(QIODevice::ReadOnly
) )
470 QTextStream
stream(&environmentFile
);
471 QString data
= stream
.readAll();
473 QStringList bindingList
= data
.split( QChar('\0') );
475 foreach( const QString
&entry
, bindingList
)
480 int splitPos
= entry
.indexOf('=');
482 if ( splitPos
!= -1 )
484 name
= entry
.mid(0,splitPos
);
485 value
= entry
.mid(splitPos
+1,-1);
487 addEnvironmentBinding(name
,value
);
493 setFileError( environmentFile
.error() );
501 // The procfs structure definition requires off_t to be
502 // 32 bits, which only applies if FILE_OFFSET_BITS=32.
503 // Futz around here to get it to compile regardless,
504 // although some of the structure sizes might be wrong.
505 // Fortunately, the structures we actually use don't use
506 // off_t, and we're safe.
507 #if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS==64)
508 #undef _FILE_OFFSET_BITS
512 // On non-Solaris platforms, define a fake psinfo structure
513 // so that the SolarisProcessInfo class can be compiled
515 // That avoids the trap where you change the API and
516 // don't notice it in #ifdeffed platform-specific parts
524 static const int PRARGSZ
=1;
527 class SolarisProcessInfo
: public UnixProcessInfo
530 SolarisProcessInfo(int pid
, bool readEnvironment
)
531 : UnixProcessInfo(pid
,readEnvironment
)
535 virtual bool readProcInfo(int pid
)
537 QFile
psinfo( QString("/proc/%1/psinfo").arg(pid
) );
538 if ( psinfo
.open( QIODevice::ReadOnly
) )
541 if (psinfo
.read((char *)&info
,sizeof(info
)) != sizeof(info
))
546 setParentPid(info
.pr_ppid
);
547 setForegroundPid(info
.pr_pgid
);
548 setName(info
.pr_fname
);
551 // Bogus, because we're treating the arguments as one single string
552 info
.pr_psargs
[PRARGSZ
-1]=0;
553 addArgument(info
.pr_psargs
);
558 virtual bool readArguments(int /*pid*/)
560 // Handled in readProcInfo()
564 virtual bool readEnvironment(int /*pid*/)
566 // Not supported in Solaris
570 virtual bool readCurrentDir(int pid
)
572 QFileInfo
info( QString("/proc/%1/path/cwd").arg(pid
) );
573 const bool readable
= info
.isReadable();
575 if ( readable
&& info
.isSymLink() )
577 setCurrentDir( info
.symLinkTarget() );
583 setError( PermissionsError
);
585 setError( UnknownError
);
592 SSHProcessInfo::SSHProcessInfo(const ProcessInfo
& process
)
597 // check that this is a SSH process
598 const QString
& name
= _process
.name(&ok
);
600 if ( !ok
|| name
!= "ssh" )
603 kDebug() << "Could not read process info";
605 kDebug() << "Process is not a SSH process";
611 const QVector
<QString
>& args
= _process
.arguments(&ok
);
614 // these are taken from the SSH manual ( accessed via 'man ssh' )
616 // options which take no arguments
617 static const QString
noOptionsArguments("1246AaCfgkMNnqsTtVvXxY");
618 // options which take one argument
619 static const QString
singleOptionArguments("bcDeFiLlmOopRSw");
623 // find the username, host and command arguments
625 // the username/host is assumed to be the first argument
626 // which is not an option
627 // ( ie. does not start with a dash '-' character )
628 // or an argument to a previous option.
630 // the command, if specified, is assumed to be the argument following
631 // the username and host
633 // note that we skip the argument at index 0 because that is the
634 // program name ( expected to be 'ssh' in this case )
635 for ( int i
= 1 ; i
< args
.count() ; i
++ )
637 // if this argument is an option then skip it, plus any
638 // following arguments which refer to this option
639 if ( args
[i
].startsWith('-') )
641 QChar argChar
= ( args
[i
].length() > 1 ) ? args
[i
][1] : '\0';
643 if ( noOptionsArguments
.contains(argChar
) )
645 else if ( singleOptionArguments
.contains(argChar
) )
652 // check whether the host has been found yet
653 // if not, this must be the username/host argument
654 if ( _host
.isEmpty() )
656 // check to see if only a hostname is specified, or whether
657 // both a username and host are specified ( in which case they
658 // are separated by an '@' character: username@host )
660 int separatorPosition
= args
[i
].indexOf('@');
661 if ( separatorPosition
!= -1 )
663 // username and host specified
664 _user
= args
[i
].left(separatorPosition
);
665 _host
= args
[i
].mid(separatorPosition
+1);
669 // just the host specified
675 // host has already been found, this must be the command argument
683 kDebug() << "Could not read arguments";
689 QString
SSHProcessInfo::userName() const
693 QString
SSHProcessInfo::host() const
697 QString
SSHProcessInfo::command() const
701 QString
SSHProcessInfo::format(const QString
& input
) const
703 QString
output(input
);
705 // test whether host is an ip address
706 // in which case 'short host' and 'full host'
707 // markers in the input string are replaced with
709 bool isIpAddress
= false;
711 struct in_addr address
;
712 if ( inet_aton(_host
.toLocal8Bit().constData(),&address
) != 0 )
717 // search for and replace known markers
718 output
.replace("%u",_user
);
721 output
.replace("%h",_host
);
723 output
.replace("%h",_host
.left(_host
.indexOf('.')));
725 output
.replace("%H",_host
);
726 output
.replace("%c",_command
);
731 ProcessInfo
* ProcessInfo::newInstance(int pid
,bool enableEnvironmentRead
)
734 return new LinuxProcessInfo(pid
,enableEnvironmentRead
);
735 #elif defined(Q_OS_SOLARIS)
736 return new SolarisProcessInfo(pid
,enableEnvironmentRead
);
738 return new NullProcessInfo(pid
,enableEnvironmentRead
);
745 c-file-style: "stroustrup"
746 indent-tabs-mode: nil