add more spacing
[personal-kdebase.git] / apps / konsole / src / ProcessInfo.cpp
blob27bc2fd98f90e2784852b7ba19a1182de1010e46
1 /*
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
17 02110-1301 USA.
20 // Own
21 #include "ProcessInfo.h"
23 // Unix
24 #include <sys/socket.h>
25 #include <netinet/in.h>
26 #include <arpa/inet.h>
28 // Qt
29 #include <KDebug>
30 #include <QtCore/QDir>
31 #include <QtCore/QFileInfo>
32 #include <QtCore/QRegExp>
33 #include <QtCore/QTextStream>
34 #include <QtCore/QStringList>
36 // KDE
37 #include <KConfigGroup>
38 #include <KGlobal>
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
48 // if no arguments
49 // or environment bindings
50 // have been explicitly set
51 , _enableEnvironmentRead(enableEnvironmentRead)
52 , _pid(pid)
53 , _parentPid(0)
54 , _foregroundPid(0)
55 , _lastError(NoError)
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
69 bool ok = false;
71 // read current dir, if an error occurs try the parent as the next
72 // best option
73 int currentPid = parentPid(&ok);
74 QString dir = currentDir(&ok);
75 while ( !ok && currentPid != 0 )
77 ProcessInfo* current = ProcessInfo::newInstance(currentPid);
78 current->update();
79 currentPid = current->parentPid(&ok);
80 dir = current->currentDir(&ok);
81 delete current;
84 return dir;
87 QString ProcessInfo::format(const QString& input) const
89 bool ok = false;
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());
106 return output;
109 QString ProcessInfo::formatCommand(const QString& name,
110 const QVector<QString>& arguments,
111 CommandFormat format) const
113 // TODO Implement me
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
143 QString result;
145 QStringList parts = input.split( QDir::separator() );
147 // temporarily hard-coded
148 QSet<QString> dirNamesToShorten = commonDirNames();
150 QListIterator<QString> iter(parts);
151 iter.toBack();
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]);
165 else
167 result.prepend(part);
168 break;
172 return result;
175 QVector<QString> ProcessInfo::arguments(bool* ok) const
177 *ok = _fields & ARGUMENTS;
179 return _arguments;
182 QMap<QString,QString> ProcessInfo::environment(bool* ok) const
184 *ok = _fields & ENVIRONMENT;
186 return _environment;
189 bool ProcessInfo::isValid() const
191 return _fields & PROCESS_ID;
194 int ProcessInfo::pid(bool* ok) const
196 *ok = _fields & PROCESS_ID;
198 return _pid;
201 int ProcessInfo::parentPid(bool* ok) const
203 *ok = _fields & PARENT_PID;
205 return _parentPid;
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;
219 return _name;
222 void ProcessInfo::setPid(int pid)
224 _pid = pid;
225 _fields |= PROCESS_ID;
228 void ProcessInfo::setParentPid(int pid)
230 _parentPid = 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
241 if (ok)
242 *ok = _fields & CURRENT_DIR;
244 return _currentDir;
246 void ProcessInfo::setCurrentDir(const QString& dir)
248 _fields |= CURRENT_DIR;
249 _currentDir = dir;
252 void ProcessInfo::setName(const QString& name)
254 _name = name;
255 _fields |= 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 )
269 switch ( error )
271 case PermissionsError:
272 setError( PermissionsError );
273 break;
274 case NoError:
275 setError( NoError );
276 break;
277 default:
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*/)
294 return false;
297 UnixProcessInfo::UnixProcessInfo(int pid,bool enableEnvironmentRead)
298 : ProcessInfo(pid,enableEnvironmentRead)
302 bool UnixProcessInfo::readProcessInfo(int pid , bool enableEnvironmentRead)
304 bool ok = readProcInfo(pid);
305 if (ok)
307 ok |= readArguments(pid);
308 ok |= readCurrentDir(pid);
309 if ( enableEnvironmentRead )
311 ok |= readEnvironment(pid);
314 return ok;
318 class LinuxProcessInfo : public UnixProcessInfo
320 public:
321 LinuxProcessInfo(int pid, bool env) :
322 UnixProcessInfo(pid,env)
326 private:
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
343 // spaces:
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();
353 int stack = 0;
354 int field = 0;
355 int pos = 0;
357 while (pos < data.count())
359 QChar c = data[pos];
361 if ( c == '(' )
362 stack++;
363 else if ( c == ')' )
364 stack--;
365 else if ( stack == 0 && c == ' ' )
366 field++;
367 else
369 switch ( field )
371 case PARENT_PID_FIELD:
372 parentPidString.append(c);
373 break;
374 case PROCESS_NAME_FIELD:
375 processNameString.append(c);
376 break;
377 case GROUP_PROCESS_FIELD:
378 foregroundPidString.append(c);
379 break;
383 pos++;
386 else
388 setFileError( processInfo.error() );
389 return false;
392 // check that data was read successfully
393 bool ok = false;
394 int foregroundPid = foregroundPidString.toInt(&ok);
395 if (ok)
396 setForegroundPid(foregroundPid);
398 int parentPid = parentPidString.toInt(&ok);
399 if (ok)
400 setParentPid(parentPid);
402 if (!processNameString.isEmpty())
403 setName(processNameString);
405 // update object state
406 setPid(pid);
408 return ok;
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())
428 addArgument(entry);
431 else
433 setFileError( argumentsFile.error() );
436 return true;
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() );
448 return true;
450 else
452 if ( !readable )
453 setError( PermissionsError );
454 else
455 setError( UnknownError );
457 return false;
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 )
477 QString name;
478 QString value;
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);
491 else
493 setFileError( environmentFile.error() );
496 return true;
500 #ifdef Q_OS_SOLARIS
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
509 #endif
510 #include <procfs.h>
511 #else
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
517 // of the code.
518 struct psinfo {
519 int pr_ppid;
520 int pr_pgid;
521 char* pr_fname;
522 char* pr_psargs;
524 static const int PRARGSZ=1;
525 #endif
527 class SolarisProcessInfo : public UnixProcessInfo
529 public:
530 SolarisProcessInfo(int pid, bool readEnvironment)
531 : UnixProcessInfo(pid,readEnvironment)
534 private:
535 virtual bool readProcInfo(int pid)
537 QFile psinfo( QString("/proc/%1/psinfo").arg(pid) );
538 if ( psinfo.open( QIODevice::ReadOnly ) )
540 struct psinfo info;
541 if (psinfo.read((char *)&info,sizeof(info)) != sizeof(info))
543 return false;
546 setParentPid(info.pr_ppid);
547 setForegroundPid(info.pr_pgid);
548 setName(info.pr_fname);
549 setPid(pid);
551 // Bogus, because we're treating the arguments as one single string
552 info.pr_psargs[PRARGSZ-1]=0;
553 addArgument(info.pr_psargs);
555 return true;
558 virtual bool readArguments(int /*pid*/)
560 // Handled in readProcInfo()
561 return true;
564 virtual bool readEnvironment(int /*pid*/)
566 // Not supported in Solaris
567 return true;
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() );
578 return true;
580 else
582 if ( !readable )
583 setError( PermissionsError );
584 else
585 setError( UnknownError );
587 return false;
592 SSHProcessInfo::SSHProcessInfo(const ProcessInfo& process)
593 : _process(process)
595 bool ok = false;
597 // check that this is a SSH process
598 const QString& name = _process.name(&ok);
600 if ( !ok || name != "ssh" )
602 if ( !ok )
603 kDebug() << "Could not read process info";
604 else
605 kDebug() << "Process is not a SSH process";
607 return;
610 // read arguments
611 const QVector<QString>& args = _process.arguments(&ok);
613 // SSH options
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");
621 if ( ok )
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) )
644 continue;
645 else if ( singleOptionArguments.contains(argChar) )
647 i++;
648 continue;
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);
667 else
669 // just the host specified
670 _host = args[i];
673 else
675 // host has already been found, this must be the command argument
676 _command = args[i];
681 else
683 kDebug() << "Could not read arguments";
685 return;
689 QString SSHProcessInfo::userName() const
691 return _user;
693 QString SSHProcessInfo::host() const
695 return _host;
697 QString SSHProcessInfo::command() const
699 return _command;
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
708 // the full address
709 bool isIpAddress = false;
711 struct in_addr address;
712 if ( inet_aton(_host.toLocal8Bit().constData(),&address) != 0 )
713 isIpAddress = true;
714 else
715 isIpAddress = false;
717 // search for and replace known markers
718 output.replace("%u",_user);
720 if ( isIpAddress )
721 output.replace("%h",_host);
722 else
723 output.replace("%h",_host.left(_host.indexOf('.')));
725 output.replace("%H",_host);
726 output.replace("%c",_command);
728 return output;
731 ProcessInfo* ProcessInfo::newInstance(int pid,bool enableEnvironmentRead)
733 #ifdef Q_OS_LINUX
734 return new LinuxProcessInfo(pid,enableEnvironmentRead);
735 #elif defined(Q_OS_SOLARIS)
736 return new SolarisProcessInfo(pid,enableEnvironmentRead);
737 #else
738 return new NullProcessInfo(pid,enableEnvironmentRead);
739 #endif
743 Local Variables:
744 mode: c++
745 c-file-style: "stroustrup"
746 indent-tabs-mode: nil
747 tab-width: 4
748 End: