From 21334dac009de9e49914300134052446d45af59a Mon Sep 17 00:00:00 2001 From: Michael Spencer Date: Fri, 5 Feb 2016 18:03:59 -0600 Subject: [PATCH] Add support for getting the name of the foreground process --- AUTHORS | 1 + lib.pri | 3 +- lib/ProcessInfo.cpp | 1174 +++++++++++++++++++++++++++++++++++++++++++ lib/ProcessInfo.h | 460 +++++++++++++++++ lib/Session.cpp | 37 +- lib/Session.h | 11 +- src/ksession.cpp | 5 + src/ksession.h | 6 + 8 files changed, 1694 insertions(+), 3 deletions(-) create mode 100644 lib/ProcessInfo.cpp create mode 100644 lib/ProcessInfo.h diff --git a/AUTHORS b/AUTHORS index 0540a6a..13a6c76 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,5 +11,6 @@ Daniel O'Neill Francisco Ballina Georg Rudoy <0xd34df00d@gmail.com> Jerome Leclanche +Michael Spencer Petr Vanek @kulti diff --git a/lib.pri b/lib.pri index cbff518..c38f08b 100644 --- a/lib.pri +++ b/lib.pri @@ -18,6 +18,7 @@ HEADERS += $$PWD/lib/BlockArray.h \ $$PWD/lib/kptyprocess.h \ $$PWD/lib/LineFont.h \ $$PWD/lib/Pty.h \ + $$PWD/lib/ProcessInfo.h \ $$PWD/lib/Screen.h \ $$PWD/lib/ScreenWindow.h \ #$$PWD/lib/SearchBar.h \ @@ -41,6 +42,7 @@ SOURCES += $$PWD/lib/BlockArray.cpp \ $$PWD/lib/kpty.cpp \ $$PWD/lib/kptydevice.cpp \ $$PWD/lib/kptyprocess.cpp \ + $$PWD/lib/ProcessInfo.cpp \ $$PWD/lib/Pty.cpp \ #$$PWD/lib/qtermwidget.cpp \ $$PWD/lib/Screen.cpp \ @@ -54,4 +56,3 @@ SOURCES += $$PWD/lib/BlockArray.cpp \ $$PWD/lib/Vt102Emulation.cpp #FORMS = $$PWD/lib/SearchBar.ui - diff --git a/lib/ProcessInfo.cpp b/lib/ProcessInfo.cpp new file mode 100644 index 0000000..f06a3c9 --- /dev/null +++ b/lib/ProcessInfo.cpp @@ -0,0 +1,1174 @@ +/* + Copyright 2007-2008 by Robert Knight + + 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., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "ProcessInfo.h" + +// Unix +#include +#include +#include +#include +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include + +// KDE +// #include +// #include +#include + +#if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) || defined(Q_OS_MAC) +#include +#endif + +#if defined(Q_OS_MAC) +#include +#endif + +#if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) +#include +#include +#include +# if defined(Q_OS_FREEBSD) +# include +# endif +#endif + +using namespace Konsole; + +ProcessInfo::ProcessInfo(int aPid , bool enableEnvironmentRead) + : _fields(ARGUMENTS | ENVIRONMENT) // arguments and environments + // are currently always valid, + // they just return an empty + // vector / map respectively + // if no arguments + // or environment bindings + // have been explicitly set + , _enableEnvironmentRead(enableEnvironmentRead) + , _pid(aPid) + , _parentPid(0) + , _foregroundPid(0) + , _userId(0) + , _lastError(NoError) + , _userName(QString()) + , _userHomeDir(QString()) +{ +} + +ProcessInfo::Error ProcessInfo::error() const +{ + return _lastError; +} +void ProcessInfo::setError(Error error) +{ + _lastError = error; +} + +void ProcessInfo::update() +{ + readProcessInfo(_pid, _enableEnvironmentRead); +} + +QString ProcessInfo::validCurrentDir() const +{ + bool ok = false; + + // read current dir, if an error occurs try the parent as the next + // best option + int currentPid = parentPid(&ok); + QString dir = currentDir(&ok); + while (!ok && currentPid != 0) { + ProcessInfo* current = ProcessInfo::newInstance(currentPid); + current->update(); + currentPid = current->parentPid(&ok); + dir = current->currentDir(&ok); + delete current; + } + + return dir; +} + +QString ProcessInfo::format(const QString& input) const +{ + bool ok = false; + + QString output(input); + + // search for and replace known marker + output.replace("%u", userName()); + output.replace("%h", localHost()); + output.replace("%n", name(&ok)); + + QString dir = validCurrentDir(); + if (output.contains("%D")) { + QString homeDir = userHomeDir(); + QString tempDir = dir; + // Change User's Home Dir w/ ~ only at the beginning + if (tempDir.startsWith(homeDir)) { + tempDir.remove(0, homeDir.length()); + tempDir.prepend('~'); + } + output.replace("%D", tempDir); + } + output.replace("%d", formatShortDir(dir)); + + return output; +} + +QSet ProcessInfo::_commonDirNames; + +QSet ProcessInfo::commonDirNames() +{ + // static bool forTheFirstTime = true; + // + // if (forTheFirstTime) { + // const KSharedConfigPtr& config = KSharedConfig::openConfig(); + // const KConfigGroup& configGroup = config->group("ProcessInfo"); + // _commonDirNames = QSet::fromList(configGroup.readEntry("CommonDirNames", QStringList())); + // + // forTheFirstTime = false; + // } + + return _commonDirNames; +} + +QString ProcessInfo::formatShortDir(const QString& input) const +{ + QString result; + + const QStringList& parts = input.split(QDir::separator()); + + QSet dirNamesToShorten = commonDirNames(); + + QListIterator iter(parts); + iter.toBack(); + + // go backwards through the list of the path's parts + // adding abbreviations of common directory names + // and stopping when we reach a dir name which is not + // in the commonDirNames set + while (iter.hasPrevious()) { + const QString& part = iter.previous(); + + if (dirNamesToShorten.contains(part)) { + result.prepend(QString(QDir::separator()) + part[0]); + } else { + result.prepend(part); + break; + } + } + + return result; +} + +QVector ProcessInfo::arguments(bool* ok) const +{ + *ok = _fields.testFlag(ARGUMENTS); + + return _arguments; +} + +QMap ProcessInfo::environment(bool* ok) const +{ + *ok = _fields.testFlag(ENVIRONMENT); + + return _environment; +} + +bool ProcessInfo::isValid() const +{ + return _fields.testFlag(PROCESS_ID); +} + +int ProcessInfo::pid(bool* ok) const +{ + *ok = _fields.testFlag(PROCESS_ID); + + return _pid; +} + +int ProcessInfo::parentPid(bool* ok) const +{ + *ok = _fields.testFlag(PARENT_PID); + + return _parentPid; +} + +int ProcessInfo::foregroundPid(bool* ok) const +{ + *ok = _fields.testFlag(FOREGROUND_PID); + + return _foregroundPid; +} + +QString ProcessInfo::name(bool* ok) const +{ + *ok = _fields.testFlag(NAME); + + return _name; +} + +int ProcessInfo::userId(bool* ok) const +{ + *ok = _fields.testFlag(UID); + + return _userId; +} + +QString ProcessInfo::userName() const +{ + return _userName; +} + +QString ProcessInfo::userHomeDir() const +{ + return _userHomeDir; +} + +QString ProcessInfo::localHost() +{ + return QHostInfo::localHostName(); +} + +void ProcessInfo::setPid(int aPid) +{ + _pid = aPid; + _fields |= PROCESS_ID; +} + +void ProcessInfo::setUserId(int uid) +{ + _userId = uid; + _fields |= UID; +} + +void ProcessInfo::setUserName(const QString& name) +{ + _userName = name; + setUserHomeDir(); +} + +void ProcessInfo::setUserHomeDir() +{ + _userHomeDir = QDir::homePath(); +} + +void ProcessInfo::setParentPid(int aPid) +{ + _parentPid = aPid; + _fields |= PARENT_PID; +} +void ProcessInfo::setForegroundPid(int aPid) +{ + _foregroundPid = aPid; + _fields |= FOREGROUND_PID; +} + +QString ProcessInfo::currentDir(bool* ok) const +{ + if (ok) + *ok = _fields & CURRENT_DIR; + + return _currentDir; +} +void ProcessInfo::setCurrentDir(const QString& dir) +{ + _fields |= CURRENT_DIR; + _currentDir = dir; +} + +void ProcessInfo::setName(const QString& name) +{ + _name = name; + _fields |= NAME; +} +void ProcessInfo::addArgument(const QString& argument) +{ + _arguments << argument; +} + +void ProcessInfo::clearArguments() +{ + _arguments.clear(); +} + +void ProcessInfo::addEnvironmentBinding(const QString& name , const QString& value) +{ + _environment.insert(name, value); +} + +void ProcessInfo::setFileError(QFile::FileError error) +{ + switch (error) { + case QFile::PermissionsError: + setError(ProcessInfo::PermissionsError); + break; + case QFile::NoError: + setError(ProcessInfo::NoError); + break; + default: + setError(ProcessInfo::UnknownError); + } +} + +// +// ProcessInfo::newInstance() is way at the bottom so it can see all of the +// implementations of the UnixProcessInfo abstract class. +// + +NullProcessInfo::NullProcessInfo(int aPid, bool enableEnvironmentRead) + : ProcessInfo(aPid, enableEnvironmentRead) +{ +} + +bool NullProcessInfo::readProcessInfo(int /*pid*/ , bool /*enableEnvironmentRead*/) +{ + return false; +} + +void NullProcessInfo::readUserName() +{ +} + +#if !defined(Q_OS_WIN) +UnixProcessInfo::UnixProcessInfo(int aPid, bool enableEnvironmentRead) + : ProcessInfo(aPid, enableEnvironmentRead) +{ +} + +bool UnixProcessInfo::readProcessInfo(int aPid , bool enableEnvironmentRead) +{ + // prevent _arguments from growing longer and longer each time this + // method is called. + clearArguments(); + + bool ok = readProcInfo(aPid); + if (ok) { + ok |= readArguments(aPid); + ok |= readCurrentDir(aPid); + if (enableEnvironmentRead) { + ok |= readEnvironment(aPid); + } + } + return ok; +} + +void UnixProcessInfo::readUserName() +{ + bool ok = false; + const int uid = userId(&ok); + if (!ok) return; + + struct passwd passwdStruct; + struct passwd* getpwResult; + char* getpwBuffer; + long getpwBufferSize; + int getpwStatus; + + getpwBufferSize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (getpwBufferSize == -1) + getpwBufferSize = 16384; + + getpwBuffer = new char[getpwBufferSize]; + if (getpwBuffer == NULL) + return; + getpwStatus = getpwuid_r(uid, &passwdStruct, getpwBuffer, getpwBufferSize, &getpwResult); + if ((getpwStatus == 0) && (getpwResult != NULL)) { + setUserName(QString(passwdStruct.pw_name)); + } else { + setUserName(QString()); + qWarning() << "getpwuid_r returned error : " << getpwStatus; + } + delete [] getpwBuffer; +} +#endif + +#if defined(Q_OS_LINUX) +class LinuxProcessInfo : public UnixProcessInfo +{ +public: + LinuxProcessInfo(int aPid, bool env) : + UnixProcessInfo(aPid, env) { + } + +private: + virtual bool readProcInfo(int aPid) { + // indicies of various fields within the process status file which + // contain various information about the process + const int PARENT_PID_FIELD = 3; + const int PROCESS_NAME_FIELD = 1; + const int GROUP_PROCESS_FIELD = 7; + + QString parentPidString; + QString processNameString; + QString foregroundPidString; + QString uidLine; + QString uidString; + QStringList uidStrings; + + // For user id read process status file ( /proc//status ) + // Can not use getuid() due to it does not work for 'su' + QFile statusInfo(QString("/proc/%1/status").arg(aPid)); + if (statusInfo.open(QIODevice::ReadOnly)) { + QTextStream stream(&statusInfo); + QString statusLine; + do { + statusLine = stream.readLine(); + if (statusLine.startsWith(QLatin1String("Uid:"))) + uidLine = statusLine; + } while (!statusLine.isNull() && uidLine.isNull()); + + uidStrings << uidLine.split('\t', QString::SkipEmptyParts); + // Must be 5 entries: 'Uid: %d %d %d %d' and + // uid string must be less than 5 chars (uint) + if (uidStrings.size() == 5) + uidString = uidStrings[1]; + if (uidString.size() > 5) + uidString.clear(); + + bool ok = false; + const int uid = uidString.toInt(&ok); + if (ok) + setUserId(uid); + readUserName(); + } else { + setFileError(statusInfo.error()); + return false; + } + + // read process status file ( /proc//cmdline + // the expected format is a list of strings delimited by null characters, + // and ending in a double null character pair. + + QFile argumentsFile(QString("/proc/%1/cmdline").arg(aPid)); + if (argumentsFile.open(QIODevice::ReadOnly)) { + QTextStream stream(&argumentsFile); + const QString& data = stream.readAll(); + + const QStringList& argList = data.split(QChar('\0')); + + foreach(const QString & entry , argList) { + if (!entry.isEmpty()) + addArgument(entry); + } + } else { + setFileError(argumentsFile.error()); + } + + return true; + } + + virtual bool readCurrentDir(int aPid) { + char path_buffer[MAXPATHLEN + 1]; + path_buffer[MAXPATHLEN] = 0; + QByteArray procCwd = QFile::encodeName(QString("/proc/%1/cwd").arg(aPid)); + const int length = readlink(procCwd.constData(), path_buffer, MAXPATHLEN); + if (length == -1) { + setError(UnknownError); + return false; + } + + path_buffer[length] = '\0'; + QString path = QFile::decodeName(path_buffer); + + setCurrentDir(path); + return true; + } + + virtual bool readEnvironment(int aPid) { + // read environment bindings file found at /proc//environ + // the expected format is a list of KEY=VALUE strings delimited by null + // characters and ending in a double null character pair. + + QFile environmentFile(QString("/proc/%1/environ").arg(aPid)); + if (environmentFile.open(QIODevice::ReadOnly)) { + QTextStream stream(&environmentFile); + const QString& data = stream.readAll(); + + const QStringList& bindingList = data.split(QChar('\0')); + + foreach(const QString & entry , bindingList) { + QString name; + QString value; + + const int splitPos = entry.indexOf('='); + + if (splitPos != -1) { + name = entry.mid(0, splitPos); + value = entry.mid(splitPos + 1, -1); + + addEnvironmentBinding(name, value); + } + } + } else { + setFileError(environmentFile.error()); + } + + return true; + } +}; + +#elif defined(Q_OS_FREEBSD) +class FreeBSDProcessInfo : public UnixProcessInfo +{ +public: + FreeBSDProcessInfo(int aPid, bool readEnvironment) : + UnixProcessInfo(aPid, readEnvironment) { + } + +private: + virtual bool readProcInfo(int aPid) { + int managementInfoBase[4]; + size_t mibLength; + struct kinfo_proc* kInfoProc; + + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_PID; + managementInfoBase[3] = aPid; + + if (sysctl(managementInfoBase, 4, NULL, &mibLength, NULL, 0) == -1) + return false; + + kInfoProc = new struct kinfo_proc [mibLength]; + + if (sysctl(managementInfoBase, 4, kInfoProc, &mibLength, NULL, 0) == -1) { + delete [] kInfoProc; + return false; + } + +#if defined(HAVE_OS_DRAGONFLYBSD) + setName(kInfoProc->kp_comm); + setPid(kInfoProc->kp_pid); + setParentPid(kInfoProc->kp_ppid); + setForegroundPid(kInfoProc->kp_pgid); + setUserId(kInfoProc->kp_uid); +#else + setName(kInfoProc->ki_comm); + setPid(kInfoProc->ki_pid); + setParentPid(kInfoProc->ki_ppid); + setForegroundPid(kInfoProc->ki_pgid); + setUserId(kInfoProc->ki_uid); +#endif + + readUserName(); + + delete [] kInfoProc; + return true; + } + + virtual bool readArguments(int aPid) { + char args[ARG_MAX]; + int managementInfoBase[4]; + size_t len; + + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_PID; + managementInfoBase[3] = aPid; + + len = sizeof(args); + if (sysctl(managementInfoBase, 4, args, &len, NULL, 0) == -1) + return false; + + const QStringList& argumentList = QString(args).split(QChar('\0')); + + for (QStringList::const_iterator it = argumentList.begin(); it != argumentList.end(); ++it) { + addArgument(*it); + } + + return true; + } + + virtual bool readEnvironment(int aPid) { + Q_UNUSED(aPid); + // Not supported in FreeBSD? + return false; + } + + virtual bool readCurrentDir(int aPid) { +#if defined(HAVE_OS_DRAGONFLYBSD) + char buf[PATH_MAX]; + int managementInfoBase[4]; + size_t len; + + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_CWD; + managementInfoBase[3] = aPid; + + len = sizeof(buf); + if (sysctl(managementInfoBase, 4, buf, &len, NULL, 0) == -1) + return false; + + setCurrentDir(buf); + + return true; +#else + int numrecords; + struct kinfo_file* info = 0; + + info = kinfo_getfile(aPid, &numrecords); + + if (!info) + return false; + + for (int i = 0; i < numrecords; ++i) { + if (info[i].kf_fd == KF_FD_TYPE_CWD) { + setCurrentDir(info[i].kf_path); + + free(info); + return true; + } + } + + free(info); + return false; +#endif + } +}; + +#elif defined(Q_OS_OPENBSD) +class OpenBSDProcessInfo : public UnixProcessInfo +{ +public: + OpenBSDProcessInfo(int aPid, bool readEnvironment) : + UnixProcessInfo(aPid, readEnvironment) { + } + +private: + virtual bool readProcInfo(int aPid) { + int managementInfoBase[6]; + size_t mibLength; + struct kinfo_proc* kInfoProc; + + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_PID; + managementInfoBase[3] = aPid; + managementInfoBase[4] = sizeof(struct kinfo_proc); + managementInfoBase[5] = 1; + + if (::sysctl(managementInfoBase, 6, NULL, &mibLength, NULL, 0) == -1) { + qWarning() << "first sysctl() call failed with code" << errno; + return false; + } + + kInfoProc = (struct kinfo_proc*)malloc(mibLength); + + if (::sysctl(managementInfoBase, 6, kInfoProc, &mibLength, NULL, 0) == -1) { + qWarning() << "second sysctl() call failed with code" << errno; + free(kInfoProc); + return false; + } + + setName(kInfoProc->p_comm); + setPid(kInfoProc->p_pid); + setParentPid(kInfoProc->p_ppid); + setForegroundPid(kInfoProc->p_tpgid); + setUserId(kInfoProc->p_uid); + setUserName(kInfoProc->p_login); + + free(kInfoProc); + return true; + } + + char** readProcArgs(int aPid, int whatMib) { + void* buf = NULL; + void* nbuf; + size_t len = 4096; + int rc = -1; + int managementInfoBase[4]; + + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC_ARGS; + managementInfoBase[2] = aPid; + managementInfoBase[3] = whatMib; + + do { + len *= 2; + nbuf = realloc(buf, len); + if (nbuf == NULL) { + break; + } + + buf = nbuf; + rc = ::sysctl(managementInfoBase, 4, buf, &len, NULL, 0); + qWarning() << "sysctl() call failed with code" << errno; + } while (rc == -1 && errno == ENOMEM); + + if (nbuf == NULL || rc == -1) { + free(buf); + return NULL; + } + + return (char**)buf; + } + + virtual bool readArguments(int aPid) { + char** argv; + + argv = readProcArgs(aPid, KERN_PROC_ARGV); + if (argv == NULL) { + return false; + } + + for (char** p = argv; *p != NULL; p++) { + addArgument(QString(*p)); + } + free(argv); + return true; + } + + virtual bool readEnvironment(int aPid) { + char** envp; + char* eqsign; + + envp = readProcArgs(aPid, KERN_PROC_ENV); + if (envp == NULL) { + return false; + } + + for (char **p = envp; *p != NULL; p++) { + eqsign = strchr(*p, '='); + if (eqsign == NULL || eqsign[1] == '\0') { + continue; + } + *eqsign = '\0'; + addEnvironmentBinding(QString((const char *)p), + QString((const char *)eqsign + 1)); + } + free(envp); + return true; + } + + virtual bool readCurrentDir(int aPid) { + char buf[PATH_MAX]; + int managementInfoBase[3]; + size_t len; + + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC_CWD; + managementInfoBase[2] = aPid; + + len = sizeof(buf); + if (::sysctl(managementInfoBase, 3, buf, &len, NULL, 0) == -1) { + qWarning() << "sysctl() call failed with code" << errno; + return false; + } + + setCurrentDir(buf); + return true; + } +}; + +#elif defined(Q_OS_MAC) +class MacProcessInfo : public UnixProcessInfo +{ +public: + MacProcessInfo(int aPid, bool env) : + UnixProcessInfo(aPid, env) { + } + +private: + virtual bool readProcInfo(int aPid) { + int managementInfoBase[4]; + size_t mibLength; + struct kinfo_proc* kInfoProc; +/* + KDE_struct_stat statInfo; + + // Find the tty device of 'pid' (Example: /dev/ttys001) + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_PID; + managementInfoBase[3] = aPid; + + if (sysctl(managementInfoBase, 4, NULL, &mibLength, NULL, 0) == -1) { + return false; + } else { + kInfoProc = new struct kinfo_proc [mibLength]; + if (sysctl(managementInfoBase, 4, kInfoProc, &mibLength, NULL, 0) == -1) { + delete [] kInfoProc; + return false; + } else { + const QString deviceNumber = QString(devname(((&kInfoProc->kp_eproc)->e_tdev), S_IFCHR)); + const QString fullDeviceName = QString("/dev/") + deviceNumber.rightJustified(3, '0'); + delete [] kInfoProc; + + const QByteArray deviceName = fullDeviceName.toLatin1(); + const char* ttyName = deviceName.data(); + + if (KDE::stat(ttyName, &statInfo) != 0) + return false; + + // Find all processes attached to ttyName + managementInfoBase[0] = CTL_KERN; + managementInfoBase[1] = KERN_PROC; + managementInfoBase[2] = KERN_PROC_TTY; + managementInfoBase[3] = statInfo.st_rdev; + + mibLength = 0; + if (sysctl(managementInfoBase, sizeof(managementInfoBase) / sizeof(int), NULL, &mibLength, NULL, 0) == -1) + return false; + + kInfoProc = new struct kinfo_proc [mibLength]; + if (sysctl(managementInfoBase, sizeof(managementInfoBase) / sizeof(int), kInfoProc, &mibLength, NULL, 0) == -1) + return false; + + // The foreground program is the first one + setName(QString(kInfoProc->kp_proc.p_comm)); + + delete [] kInfoProc; + } + setPid(aPid); + } + return true; +*/ + return false; + } + + virtual bool readArguments(int aPid) { + Q_UNUSED(aPid); + return false; + } + virtual bool readCurrentDir(int aPid) { + struct proc_vnodepathinfo vpi; + const int nb = proc_pidinfo(aPid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi)); + if (nb == sizeof(vpi)) { + setCurrentDir(QString(vpi.pvi_cdir.vip_path)); + return true; + } + return false; + } + virtual bool readEnvironment(int aPid) { + Q_UNUSED(aPid); + return false; + } +}; + +#elif defined(Q_OS_SOLARIS) +// The procfs structure definition requires off_t to be +// 32 bits, which only applies if FILE_OFFSET_BITS=32. +// Futz around here to get it to compile regardless, +// although some of the structure sizes might be wrong. +// Fortunately, the structures we actually use don't use +// off_t, and we're safe. +#if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS==64) +#undef _FILE_OFFSET_BITS +#endif +#include + +class SolarisProcessInfo : public UnixProcessInfo +{ +public: + SolarisProcessInfo(int aPid, bool readEnvironment) + : UnixProcessInfo(aPid, readEnvironment) { + } +private: + virtual bool readProcInfo(int aPid) { + QFile psinfo(QString("/proc/%1/psinfo").arg(aPid)); + if (psinfo.open(QIODevice::ReadOnly)) { + struct psinfo info; + if (psinfo.read((char *)&info, sizeof(info)) != sizeof(info)) { + return false; + } + + setParentPid(info.pr_ppid); + setForegroundPid(info.pr_pgid); + setName(info.pr_fname); + setPid(aPid); + + // Bogus, because we're treating the arguments as one single string + info.pr_psargs[PRARGSZ - 1] = 0; + addArgument(info.pr_psargs); + } + return true; + } + + virtual bool readArguments(int /*pid*/) { + // Handled in readProcInfo() + return false; + } + + virtual bool readEnvironment(int /*pid*/) { + // Not supported in Solaris + return false; + } + + // FIXME: This will have the same issues as BKO 251351; the Linux + // version uses readlink. + virtual bool readCurrentDir(int aPid) { + QFileInfo info(QString("/proc/%1/path/cwd").arg(aPid)); + const bool readable = info.isReadable(); + + if (readable && info.isSymLink()) { + setCurrentDir(info.symLinkTarget()); + return true; + } else { + if (!readable) + setError(PermissionsError); + else + setError(UnknownError); + + return false; + } + } +}; +#endif + +SSHProcessInfo::SSHProcessInfo(const ProcessInfo& process) + : _process(process) +{ + bool ok = false; + + // check that this is a SSH process + const QString& name = _process.name(&ok); + + if (!ok || name != "ssh") { + if (!ok) + qWarning() << "Could not read process info"; + else + qWarning() << "Process is not a SSH process"; + + return; + } + + // read arguments + const QVector& args = _process.arguments(&ok); + + // SSH options + // these are taken from the SSH manual ( accessed via 'man ssh' ) + + // options which take no arguments + static const QString noArgumentOptions("1246AaCfgKkMNnqsTtVvXxYy"); + // options which take one argument + static const QString singleArgumentOptions("bcDeFIiLlmOopRSWw"); + + if (ok) { + // find the username, host and command arguments + // + // the username/host is assumed to be the first argument + // which is not an option + // ( ie. does not start with a dash '-' character ) + // or an argument to a previous option. + // + // the command, if specified, is assumed to be the argument following + // the username and host + // + // note that we skip the argument at index 0 because that is the + // program name ( expected to be 'ssh' in this case ) + for (int i = 1 ; i < args.count() ; i++) { + // If this one is an option ... + // Most options together with its argument will be skipped. + if (args[i].startsWith('-')) { + const QChar optionChar = (args[i].length() > 1) ? args[i][1] : '\0'; + // for example: -p2222 or -p 2222 ? + const bool optionArgumentCombined = args[i].length() > 2; + + if (noArgumentOptions.contains(optionChar)) { + continue; + } else if (singleArgumentOptions.contains(optionChar)) { + QString argument; + if (optionArgumentCombined) { + argument = args[i].mid(2); + } else { + // Verify correct # arguments are given + if ((i + 1) < args.count()) { + argument = args[i + 1]; + } + i++; + } + + // support using `-l user` to specify username. + if (optionChar == 'l') + _user = argument; + // support using `-p port` to specify port. + else if (optionChar == 'p') + _port = argument; + + continue; + } + } + + // check whether the host has been found yet + // if not, this must be the username/host argument + if (_host.isEmpty()) { + // check to see if only a hostname is specified, or whether + // both a username and host are specified ( in which case they + // are separated by an '@' character: username@host ) + + const int separatorPosition = args[i].indexOf('@'); + if (separatorPosition != -1) { + // username and host specified + _user = args[i].left(separatorPosition); + _host = args[i].mid(separatorPosition + 1); + } else { + // just the host specified + _host = args[i]; + } + } else { + // host has already been found, this must be the command argument + _command = args[i]; + } + } + } else { + qWarning() << "Could not read arguments"; + + return; + } +} + +QString SSHProcessInfo::userName() const +{ + return _user; +} +QString SSHProcessInfo::host() const +{ + return _host; +} +QString SSHProcessInfo::port() const +{ + return _port; +} +QString SSHProcessInfo::command() const +{ + return _command; +} +QString SSHProcessInfo::format(const QString& input) const +{ + QString output(input); + + // test whether host is an ip address + // in which case 'short host' and 'full host' + // markers in the input string are replaced with + // the full address + bool isIpAddress = false; + + struct in_addr address; + if (inet_aton(_host.toLocal8Bit().constData(), &address) != 0) + isIpAddress = true; + else + isIpAddress = false; + + // search for and replace known markers + output.replace("%u", _user); + + if (isIpAddress) + output.replace("%h", _host); + else + output.replace("%h", _host.left(_host.indexOf('.'))); + + output.replace("%H", _host); + output.replace("%c", _command); + + return output; +} + +ProcessInfo* ProcessInfo::newInstance(int aPid, bool enableEnvironmentRead) +{ +#if defined(Q_OS_LINUX) + return new LinuxProcessInfo(aPid, enableEnvironmentRead); +#elif defined(Q_OS_SOLARIS) + return new SolarisProcessInfo(aPid, enableEnvironmentRead); +#elif defined(Q_OS_MAC) + return new MacProcessInfo(aPid, enableEnvironmentRead); +#elif defined(Q_OS_FREEBSD) + return new FreeBSDProcessInfo(aPid, enableEnvironmentRead); +#elif defined(Q_OS_OPENBSD) + return new OpenBSDProcessInfo(aPid, enableEnvironmentRead); +#else + return new NullProcessInfo(aPid, enableEnvironmentRead); +#endif +} diff --git a/lib/ProcessInfo.h b/lib/ProcessInfo.h new file mode 100644 index 0000000..a164354 --- /dev/null +++ b/lib/ProcessInfo.h @@ -0,0 +1,460 @@ +/* + Copyright 2007-2008 by Robert Knight + + 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., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef PROCESSINFO_H +#define PROCESSINFO_H + +// Qt +#include +#include +#include +#include + +namespace Konsole +{ +/** + * Takes a snapshot of the state of a process and provides access to + * information such as the process name, parent process, + * the foreground process in the controlling terminal, + * the arguments with which the process was started and the + * environment. + * + * To create a new snapshot, construct a new ProcessInfo instance, + * using ProcessInfo::newInstance(), + * passing the process identifier of the process you are interested in. + * + * After creating a new instance, call the update() method to take a + * snapshot of the current state of the process. + * + * Before calling any additional methods, check that the process state + * was read successfully using the isValid() method. + * + * Each accessor method which provides information about the process state ( such as pid(), + * currentDir(), name() ) takes a pointer to a boolean as an argument. If the information + * requested was read successfully then the boolean is set to true, otherwise it is set + * to false, in which case the return value from the function should be ignored. + * If this boolean is set to false, it may indicate an error reading the process information, + * or it may indicate that the information is not available on the current platform. + * + * eg. + * + * @code + * ProcessInfo* info = ProcessInfo::newInstance(pid); + * info->update(); + * + * if ( info->isValid() ) + * { + * bool ok; + * + * QString name = info->name(&ok); + * if ( ok ) qDebug() << "process name - " << name; + * int parentPid = info->parentPid(&ok); + * if ( ok ) qDebug() << "parent process - " << parentPid; + * int foregroundPid = info->foregroundPid(&ok); + * if ( ok ) qDebug() << "foreground process - " << foregroundPid; + * } + * @endcode + */ +class ProcessInfo +{ +public: + /** + * Constructs a new instance of a suitable ProcessInfo sub-class for + * the current platform which provides information about a given process. + * + * @param pid The pid of the process to examine + * @param readEnvironment Specifies whether environment bindings should + * be read. If this is false, then environment() calls will + * always fail. This is an optimization to avoid the overhead + * of reading the (potentially large) environment data when it + * is not required. + */ + static ProcessInfo* newInstance(int pid, bool readEnvironment = false); + + virtual ~ProcessInfo() {} + + /** + * Updates the information about the process. This must + * be called before attempting to use any of the accessor methods. + */ + void update(); + + /** Returns true if the process state was read successfully. */ + bool isValid() const; + /** + * Returns the process id. + * + * @param ok Set to true if the process id was read successfully or false otherwise + */ + int pid(bool* ok) const; + /** + * Returns the id of the parent process id was read successfully or false otherwise + * + * @param ok Set to true if the parent process id + */ + int parentPid(bool* ok) const; + + /** + * Returns the id of the current foreground process + * + * NOTE: Using the foregroundProcessGroup() method of the Pty + * instance associated with the terminal of interest is preferred + * over using this method. + * + * @param ok Set to true if the foreground process id was read successfully or false otherwise + */ + int foregroundPid(bool* ok) const; + + /* Returns the user id of the process */ + int userId(bool* ok) const; + + /** Returns the user's name of the process */ + QString userName() const; + + /** Returns the user's home directory of the process */ + QString userHomeDir() const; + + /** Returns the local host */ + static QString localHost(); + + /** Returns the name of the current process */ + QString name(bool* ok) const; + + /** + * Returns the command-line arguments which the process + * was started with. + * + * The first argument is the name used to launch the process. + * + * @param ok Set to true if the arguments were read successfully or false otherwise. + */ + QVector arguments(bool* ok) const; + /** + * Returns the environment bindings which the process + * was started with. + * In the returned map, the key is the name of the environment variable, + * and the value is the corresponding value. + * + * @param ok Set to true if the environment bindings were read successfully or false otherwise + */ + QMap environment(bool* ok) const; + + /** + * Returns the current working directory of the process + * + * @param ok Set to true if the current working directory was read successfully or false otherwise + */ + QString currentDir(bool* ok) const; + + /** + * Returns the current working directory of the process (or its parent) + */ + QString validCurrentDir() const; + + /** Forces the user home directory to be calculated */ + void setUserHomeDir(); + + /** + * Parses an input string, looking for markers beginning with a '%' + * character and returns a string with the markers replaced + * with information from this process description. + *
+ * The markers recognized are: + *
    + *
  • %u - Name of the user which owns the process.
  • + *
  • %n - Replaced with the name of the process.
  • + *
  • %d - Replaced with the last part of the path name of the + * process' current working directory. + * + * (eg. if the current directory is '/home/bob' then + * 'bob' would be returned) + *
  • + *
  • %D - Replaced with the current working directory of the process.
  • + *
+ */ + QString format(const QString& text) const; + + /** + * This enum describes the errors which can occur when trying to read + * a process's information. + */ + enum Error { + /** No error occurred. */ + NoError, + /** The nature of the error is unknown. */ + UnknownError, + /** Konsole does not have permission to obtain the process information. */ + PermissionsError + }; + + /** + * Returns the last error which occurred. + */ + Error error() const; + + enum Field { + PROCESS_ID = 1, + PARENT_PID = 2, + FOREGROUND_PID = 4, + ARGUMENTS = 8, + ENVIRONMENT = 16, + NAME = 32, + CURRENT_DIR = 64, + UID = 128 + }; + Q_DECLARE_FLAGS(Fields, Field) + +protected: + /** + * Constructs a new process instance. You should not call the constructor + * of ProcessInfo or its subclasses directly. Instead use the + * static ProcessInfo::newInstance() method which will return + * a suitable ProcessInfo instance for the current platform. + */ + explicit ProcessInfo(int pid , bool readEnvironment = false); + + /** + * This is called on construction to read the process state + * Subclasses should reimplement this function to provide + * platform-specific process state reading functionality. + * + * When called, readProcessInfo() should attempt to read all + * of the necessary state information. If the attempt is successful, + * it should set the process id using setPid(), and update + * the other relevant information using setParentPid(), setName(), + * setArguments() etc. + * + * Calls to isValid() will return true only if the process id + * has been set using setPid() + * + * @param pid The process id of the process to read + * @param readEnvironment Specifies whether the environment bindings + * for the process should be read + */ + virtual bool readProcessInfo(int pid , bool readEnvironment) = 0; + + /* Read the user name */ + virtual void readUserName(void) = 0; + + /** Sets the process id associated with this ProcessInfo instance */ + void setPid(int pid); + /** Sets the parent process id as returned by parentPid() */ + void setParentPid(int pid); + /** Sets the foreground process id as returned by foregroundPid() */ + void setForegroundPid(int pid); + /** Sets the user id associated with this ProcessInfo instance */ + void setUserId(int uid); + /** Sets the user name of the process as set by readUserName() */ + void setUserName(const QString& name); + /** Sets the name of the process as returned by name() */ + void setName(const QString& name); + /** Sets the current working directory for the process */ + void setCurrentDir(const QString& dir); + + /** Sets the error */ + void setError(Error error); + + /** Convenience method. Sets the error based on a QFile error code. */ + void setFileError(QFile::FileError error); + + /** + * Adds a commandline argument for the process, as returned + * by arguments() + */ + void addArgument(const QString& argument); + + /** + * clear the commandline arguments for the process, as returned + * by arguments() + */ + void clearArguments(); + + /** + * Adds an environment binding for the process, as returned by + * environment() + * + * @param name The name of the environment variable, eg. "PATH" + * @param value The value of the environment variable, eg. "/bin" + */ + void addEnvironmentBinding(const QString& name , const QString& value); + +private: + // takes a full directory path and returns a + // shortened version suitable for display in + // space-constrained UI elements (eg. tabs) + QString formatShortDir(const QString& dirPath) const; + + Fields _fields; + + bool _enableEnvironmentRead; // specifies whether to read the environment + // bindings when update() is called + int _pid; + int _parentPid; + int _foregroundPid; + int _userId; + + Error _lastError; + + QString _name; + QString _userName; + QString _userHomeDir; + QString _currentDir; + + QVector _arguments; + QMap _environment; + + static QSet commonDirNames(); + static QSet _commonDirNames; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessInfo::Fields) + +/** + * Implementation of ProcessInfo which does nothing. + * Used on platforms where a suitable ProcessInfo subclass is not + * available. + * + * isValid() will always return false for instances of NullProcessInfo + */ +class NullProcessInfo : public ProcessInfo +{ +public: + /** + * Constructs a new NullProcessInfo instance. + * See ProcessInfo::newInstance() + */ + explicit NullProcessInfo(int pid, bool readEnvironment = false); +protected: + virtual bool readProcessInfo(int pid, bool readEnvironment); + virtual void readUserName(void); +}; + +#if !defined(Q_OS_WIN) +/** + * Implementation of ProcessInfo for Unix platforms which uses + * the /proc filesystem + */ +class UnixProcessInfo : public ProcessInfo +{ +public: + /** + * Constructs a new instance of UnixProcessInfo. + * See ProcessInfo::newInstance() + */ + explicit UnixProcessInfo(int pid, bool readEnvironment = false); + +protected: + /** + * Implementation of ProcessInfo::readProcessInfo(); calls the + * four private methods below in turn. + */ + virtual bool readProcessInfo(int pid , bool readEnvironment); + + virtual void readUserName(void); + +private: + /** + * Read the standard process information -- PID, parent PID, foreground PID. + * @param pid process ID to use + * @return true on success + */ + virtual bool readProcInfo(int pid) = 0; + + /** + * Read the environment of the process. Sets _environment. + * @param pid process ID to use + * @return true on success + */ + virtual bool readEnvironment(int pid) = 0; + + /** + * Determine what arguments were passed to the process. Sets _arguments. + * @param pid process ID to use + * @return true on success + */ + virtual bool readArguments(int pid) = 0; + + /** + * Determine the current directory of the process. + * @param pid process ID to use + * @return true on success + */ + virtual bool readCurrentDir(int pid) = 0; +}; +#endif + +/** + * Lightweight class which provides additional information about SSH processes. + */ +class SSHProcessInfo +{ +public: + /** + * Constructs a new SSHProcessInfo instance which provides additional + * information about the specified SSH process. + * + * @param process A ProcessInfo instance for a SSH process. + */ + explicit SSHProcessInfo(const ProcessInfo& process); + + /** + * Returns the user name which the user initially logged into on + * the remote computer. + */ + QString userName() const; + + /** + * Returns the host which the user has connected to. + */ + QString host() const; + + /** + * Returns the port on host which the user has connected to. + */ + QString port() const; + + /** + * Returns the command which the user specified to execute on the + * remote computer when starting the SSH process. + */ + QString command() const; + + /** + * Operates in the same way as ProcessInfo::format(), except + * that the set of markers understood is different: + * + * %u - Replaced with user name which the user initially logged + * into on the remote computer. + * %h - Replaced with the first part of the host name which + * is connected to. + * %H - Replaced with the full host name of the computer which + * is connected to. + * %c - Replaced with the command which the user specified + * to execute when starting the SSH process. + */ + QString format(const QString& input) const; + +private: + const ProcessInfo& _process; + QString _user; + QString _host; + QString _port; + QString _command; +}; +} +#endif //PROCESSINFO_H diff --git a/lib/Session.cpp b/lib/Session.cpp index c295b60..b0d966f 100644 --- a/lib/Session.cpp +++ b/lib/Session.cpp @@ -52,7 +52,7 @@ using namespace Konsole; int Session::lastSessionId = 0; -Session::Session(QObject* parent) : +Session::Session(QObject* parent) : QObject(parent), _shellProcess(0) , _emulation(0) @@ -920,6 +920,41 @@ int Session::foregroundProcessId() const { return _shellProcess->foregroundProcessGroup(); } + +QString Session::foregroundProcessName() +{ + QString name; + + if (updateForegroundProcessInfo()) { + bool ok = false; + name = _foregroundProcessInfo->name(&ok); + if (!ok) + name.clear(); + } + + return name; +} + +bool Session::updateForegroundProcessInfo() +{ + Q_ASSERT(_shellProcess); + + const int foregroundPid = _shellProcess->foregroundProcessGroup(); + if (foregroundPid != _foregroundPid) { + delete _foregroundProcessInfo; + _foregroundProcessInfo = ProcessInfo::newInstance(foregroundPid); + _foregroundPid = foregroundPid; + } + + if (_foregroundProcessInfo) { + _foregroundProcessInfo->update(); + return _foregroundProcessInfo->isValid(); + } else { + return false; + } +} + + int Session::processId() const { return _shellProcess->pid(); diff --git a/lib/Session.h b/lib/Session.h index b3a389f..f59f40d 100644 --- a/lib/Session.h +++ b/lib/Session.h @@ -30,6 +30,7 @@ #include #include "History.h" +#include "ProcessInfo.h" class KProcess; @@ -324,6 +325,11 @@ public: */ int foregroundProcessId() const; + /** + * Returns the name of the terminal's foreground process. + */ + QString foregroundProcessName(); + /** Returns the terminal session's window size in lines and columns. */ QSize size(); /** @@ -488,6 +494,7 @@ private slots: private: void updateTerminalSize(); + bool updateForegroundProcessInfo(); WId windowId() const; int _uniqueIdentifier; @@ -541,8 +548,10 @@ private: bool _hasDarkBackground; - static int lastSessionId; + ProcessInfo *_foregroundProcessInfo; + int _foregroundPid; + static int lastSessionId; }; /** diff --git a/src/ksession.cpp b/src/ksession.cpp index f27207a..d5befd7 100644 --- a/src/ksession.cpp +++ b/src/ksession.cpp @@ -302,3 +302,8 @@ bool KSession::hasActiveProcess() const { return m_session->processId() != m_session->foregroundProcessId(); } + +QString KSession::foregroundProcessName() +{ + return m_session->foregroundProcessName(); +} diff --git a/src/ksession.h b/src/ksession.h index 47e5603..ef8aed5 100644 --- a/src/ksession.h +++ b/src/ksession.h @@ -41,6 +41,7 @@ class KSession : public QObject Q_PROPERTY(QStringList shellProgramArgs WRITE setArgs) Q_PROPERTY(QString history READ getHistory) Q_PROPERTY(bool hasActiveProcess READ hasActiveProcess) + Q_PROPERTY(QString foregroundProcessName READ foregroundProcessName) public: KSession(QObject *parent = 0); @@ -99,6 +100,11 @@ public: */ bool hasActiveProcess() const; + /** + * Returns the name of the terminal's foreground process. + */ + QString foregroundProcessName(); + signals: void started(); void finished();