ProcessInfo.cpp 31.5 KB
Newer Older
1
/*
2
    Copyright 2007-2008 by Robert Knight <robertknight@gmail.countm>
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

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

20
// Config
21
#include "config-konsole.h"
22

23 24 25
// Own
#include "ProcessInfo.h"

26 27 28 29
// Unix
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
30 31
#include <unistd.h>
#include <pwd.h>
32
#include <sys/param.h>
33
#include <cerrno>
34

35
// Qt
36 37 38 39 40
#include <QDir>
#include <QFileInfo>
#include <QTextStream>
#include <QStringList>
#include <QHostInfo>
41

42 43 44
// KDE
#include <KConfigGroup>
#include <KSharedConfig>
45
#include <KUser>
46

47
#if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) || defined(Q_OS_MACOS)
48
#include <sys/sysctl.h>
49 50
#endif

51
#if defined(Q_OS_MACOS)
52
#include <libproc.h>
53
#include <qplatformdefs.h>
54 55
#endif

56
#if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
57 58
#include <sys/types.h>
#include <sys/user.h>
59
#include <sys/syslimits.h>
60 61
#   if defined(Q_OS_FREEBSD)
#   include <libutil.h>
62 63
#   include <sys/param.h>
#   include <sys/queue.h>
64
#   endif
65 66
#endif

67
using namespace Konsole;
68

69
ProcessInfo::ProcessInfo(int pid) :
70
    _fields(ARGUMENTS)     // arguments
Kurt Hindenburg's avatar
Kurt Hindenburg committed
71 72 73 74 75
    // are currently always valid,
    // they just return an empty
    // vector / map respectively
    // if no arguments
    // have been explicitly set
76
    ,
77
    _pid(pid),
78 79 80 81 82 83 84 85 86 87
    _parentPid(0),
    _foregroundPid(0),
    _userId(0),
    _lastError(NoError),
    _name(QString()),
    _userName(QString()),
    _userHomeDir(QString()),
    _currentDir(QString()),
    _userNameRequired(true),
    _arguments(QVector<QString>())
88 89 90
{
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
91 92 93 94
ProcessInfo::Error ProcessInfo::error() const
{
    return _lastError;
}
95

Kurt Hindenburg's avatar
Kurt Hindenburg committed
96 97 98 99
void ProcessInfo::setError(Error error)
{
    _lastError = error;
}
100

Kurt Hindenburg's avatar
Kurt Hindenburg committed
101
void ProcessInfo::update()
102
{
103
    readCurrentDir(_pid);
104 105
}

106
QString ProcessInfo::validCurrentDir() const
107
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
108 109 110 111 112 113 114
    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) {
115
        ProcessInfo *current = ProcessInfo::newInstance(currentPid);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
116 117 118 119 120 121 122
        current->update();
        currentPid = current->parentPid(&ok);
        dir = current->currentDir(&ok);
        delete current;
    }

    return dir;
123 124
}

125 126
QSet<QString> ProcessInfo::_commonDirNames;

Kurt Hindenburg's avatar
Kurt Hindenburg committed
127
QSet<QString> ProcessInfo::commonDirNames()
128
{
129
    static bool forTheFirstTime = true;
130

131
    if (forTheFirstTime) {
132 133
        const KSharedConfigPtr &config = KSharedConfig::openConfig();
        const KConfigGroup &configGroup = config->group("ProcessInfo");
134
        _commonDirNames = QSet<QString>::fromList(configGroup.readEntry("CommonDirNames", QStringList()));
135

136
        forTheFirstTime = false;
137 138 139 140 141
    }

    return _commonDirNames;
}

142
QString ProcessInfo::formatShortDir(const QString &input) const
143
{
144 145 146 147
    if(input == QStringLiteral("/")) {
        return QStringLiteral("/");
    }

148 149
    QString result;

150
    const QStringList &parts = input.split(QDir::separator());
151

152
    QSet<QString> dirNamesToShorten = commonDirNames();
153 154 155 156 157 158 159 160

    QListIterator<QString> 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
Kurt Hindenburg's avatar
Kurt Hindenburg committed
161
    while (iter.hasPrevious()) {
162
        const QString &part = iter.previous();
163

Kurt Hindenburg's avatar
Kurt Hindenburg committed
164
        if (dirNamesToShorten.contains(part)) {
165
            result.prepend(QDir::separator() + part[0]);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
166
        } else {
167 168 169 170 171 172 173 174
            result.prepend(part);
            break;
        }
    }

    return result;
}

175
QVector<QString> ProcessInfo::arguments(bool *ok) const
176
{
177
    *ok = _fields.testFlag(ARGUMENTS);
178 179 180 181 182 183

    return _arguments;
}

bool ProcessInfo::isValid() const
{
184
    return _fields.testFlag(PROCESS_ID);
185 186
}

187
int ProcessInfo::pid(bool *ok) const
188
{
189
    *ok = _fields.testFlag(PROCESS_ID);
190 191 192 193

    return _pid;
}

194
int ProcessInfo::parentPid(bool *ok) const
195
{
196
    *ok = _fields.testFlag(PARENT_PID);
197 198 199 200

    return _parentPid;
}

201
int ProcessInfo::foregroundPid(bool *ok) const
202
{
203
    *ok = _fields.testFlag(FOREGROUND_PID);
204 205 206 207

    return _foregroundPid;
}

208
QString ProcessInfo::name(bool *ok) const
209
{
210
    *ok = _fields.testFlag(NAME);
211 212 213 214

    return _name;
}

215
int ProcessInfo::userId(bool *ok) const
216
{
217
    *ok = _fields.testFlag(UID);
218 219 220 221 222 223 224 225 226

    return _userId;
}

QString ProcessInfo::userName() const
{
    return _userName;
}

227 228 229 230 231
QString ProcessInfo::userHomeDir() const
{
    return _userHomeDir;
}

232 233 234 235 236
QString ProcessInfo::localHost()
{
    return QHostInfo::localHostName();
}

237
void ProcessInfo::setPid(int pid)
238
{
239
    _pid = pid;
240 241 242
    _fields |= PROCESS_ID;
}

243 244 245 246 247 248
void ProcessInfo::setUserId(int uid)
{
    _userId = uid;
    _fields |= UID;
}

249
void ProcessInfo::setUserName(const QString &name)
250 251
{
    _userName = name;
252
    setUserHomeDir();
253 254
}

255 256
void ProcessInfo::setUserHomeDir()
{
257 258
    const QString &usersName = userName();
    if (!usersName.isEmpty()) {
259
        _userHomeDir = KUser(usersName).homeDir();
260
    } else {
261
        _userHomeDir = QDir::homePath();
262
    }
263
}
264

265
void ProcessInfo::setParentPid(int pid)
266
{
267
    _parentPid = pid;
268 269
    _fields |= PARENT_PID;
}
270

271
void ProcessInfo::setForegroundPid(int pid)
272
{
273
    _foregroundPid = pid;
274 275
    _fields |= FOREGROUND_PID;
}
276

277 278 279 280 281 282 283 284 285 286
void ProcessInfo::setUserNameRequired(bool need)
{
    _userNameRequired = need;
}

bool ProcessInfo::userNameRequired() const
{
    return _userNameRequired;
}

287
QString ProcessInfo::currentDir(bool *ok) const
288
{
289
    if (ok != nullptr) {
290
        *ok = (_fields & CURRENT_DIR) != 0;
291
    }
292 293 294

    return _currentDir;
}
295 296

void ProcessInfo::setCurrentDir(const QString &dir)
297 298 299 300 301
{
    _fields |= CURRENT_DIR;
    _currentDir = dir;
}

302
void ProcessInfo::setName(const QString &name)
303 304 305 306
{
    _name = name;
    _fields |= NAME;
}
307 308

void ProcessInfo::addArgument(const QString &argument)
309
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
310
    _arguments << argument;
311 312
}

313 314 315 316 317
void ProcessInfo::clearArguments()
{
    _arguments.clear();
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
318
void ProcessInfo::setFileError(QFile::FileError error)
319
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
320
    switch (error) {
321
    case QFile::PermissionsError:
322
        setError(ProcessInfo::PermissionsError);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
323
        break;
324
    case QFile::NoError:
325
        setError(ProcessInfo::NoError);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
326 327
        break;
    default:
328
        setError(ProcessInfo::UnknownError);
329 330 331
    }
}

332 333 334 335
//
// ProcessInfo::newInstance() is way at the bottom so it can see all of the
// implementations of the UnixProcessInfo abstract class.
//
336

337
NullProcessInfo::NullProcessInfo(int pid) :
338
    ProcessInfo(pid)
339 340 341
{
}

342
void NullProcessInfo::readProcessInfo(int /*pid*/)
343 344 345
{
}

346 347 348 349 350
bool NullProcessInfo::readCurrentDir(int /*pid*/)
{
    return false;
}

351 352 353 354
void NullProcessInfo::readUserName()
{
}

355
#if !defined(Q_OS_WIN)
356
UnixProcessInfo::UnixProcessInfo(int pid) :
357
    ProcessInfo(pid)
358
{
359
    setUserNameRequired(true);
360
}
361

362
void UnixProcessInfo::readProcessInfo(int pid)
363
{
364 365 366 367
    // prevent _arguments from growing longer and longer each time this
    // method is called.
    clearArguments();

368 369 370
    if (readProcInfo(pid)) {
        readArguments(pid);
        readCurrentDir(pid);
371 372 373 374 375 376 377 378

        bool ok = false;
        const QString &processNameString = name(&ok);

        if (ok && processNameString == QLatin1String("sudo")) {
            //Append process name along with sudo
            const QVector<QString> &args = arguments(&ok);

379
            if (ok && args.size() > 1) {
380
                setName(processNameString + QStringLiteral(" ") + args[1]);
381
            }
382
        }
383
    }
384 385
}

386 387 388
void UnixProcessInfo::readUserName()
{
    bool ok = false;
Jekyll Wu's avatar
Jekyll Wu committed
389
    const int uid = userId(&ok);
390 391 392
    if (!ok) {
        return;
    }
393

394
    struct passwd passwdStruct;
395 396
    struct passwd *getpwResult;
    char *getpwBuffer;
397 398 399 400
    long getpwBufferSize;
    int getpwStatus;

    getpwBufferSize = sysconf(_SC_GETPW_R_SIZE_MAX);
401
    if (getpwBufferSize == -1) {
402
        getpwBufferSize = 16384;
403
    }
404 405

    getpwBuffer = new char[getpwBufferSize];
Kurt Hindenburg's avatar
Kurt Hindenburg committed
406
    if (getpwBuffer == nullptr) {
407
        return;
408
    }
409
    getpwStatus = getpwuid_r(uid, &passwdStruct, getpwBuffer, getpwBufferSize, &getpwResult);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
410
    if ((getpwStatus == 0) && (getpwResult != nullptr)) {
411
        setUserName(QLatin1String(passwdStruct.pw_name));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
412
    } else {
413
        setUserName(QString());
Laurent Montel's avatar
Laurent Montel committed
414
        qWarning() << "getpwuid_r returned error : " << getpwStatus;
415
    }
416
    delete [] getpwBuffer;
417
}
418

419
#endif
420

421
#if defined(Q_OS_LINUX)
422 423 424
class LinuxProcessInfo : public UnixProcessInfo
{
public:
425 426
    LinuxProcessInfo(int pid) :
        UnixProcessInfo(pid)
427
    {
428
    }
429

430
protected:
431
    bool readCurrentDir(int pid) Q_DECL_OVERRIDE
432
    {
433 434
        char path_buffer[MAXPATHLEN + 1];
        path_buffer[MAXPATHLEN] = 0;
435
        QByteArray procCwd = QFile::encodeName(QStringLiteral("/proc/%1/cwd").arg(pid));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
436
        const auto length = static_cast<int>(readlink(procCwd.constData(), path_buffer, MAXPATHLEN));
437 438 439 440 441 442 443 444 445 446 447 448
        if (length == -1) {
            setError(UnknownError);
            return false;
        }

        path_buffer[length] = '\0';
        QString path = QFile::decodeName(path_buffer);

        setCurrentDir(path);
        return true;
    }

449
private:
450
    bool readProcInfo(int pid) Q_DECL_OVERRIDE
451
    {
452 453 454 455 456 457 458 459 460
        // 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;
461 462 463
        QString uidLine;
        QString uidString;
        QStringList uidStrings;
464

465 466
        // For user id read process status file ( /proc/<pid>/status )
        //  Can not use getuid() due to it does not work for 'su'
467
        QFile statusInfo(QStringLiteral("/proc/%1/status").arg(pid));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
468
        if (statusInfo.open(QIODevice::ReadOnly)) {
469
            QTextStream stream(&statusInfo);
470 471
            QString statusLine;
            do {
472
                statusLine = stream.readLine();
473
                if (statusLine.startsWith(QLatin1String("Uid:"))) {
474
                    uidLine = statusLine;
475
                }
476 477
            } while (!statusLine.isNull() && uidLine.isNull());

478
            uidStrings << uidLine.split(QLatin1Char('\t'), QString::SkipEmptyParts);
479 480
            // Must be 5 entries: 'Uid: %d %d %d %d' and
            // uid string must be less than 5 chars (uint)
481
            if (uidStrings.size() == 5) {
482
                uidString = uidStrings[1];
483 484
            }
            if (uidString.size() > 5) {
485
                uidString.clear();
486
            }
487

488
            bool ok = false;
Jekyll Wu's avatar
Jekyll Wu committed
489
            const int uid = uidString.toInt(&ok);
490
            if (ok) {
491
                setUserId(uid);
492
            }
493 494 495
            // This will cause constant opening of /etc/passwd
            if (userNameRequired()) {
                readUserName();
496
                setUserNameRequired(false);
497
            }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
498 499
        } else {
            setFileError(statusInfo.error());
500 501 502
            return false;
        }

503 504 505
        // read process status file ( /proc/<pid/stat )
        //
        // the expected file format is a list of fields separated by spaces, using
506
        // parentheses to escape fields such as the process name which may itself contain
507 508 509 510
        // spaces:
        //
        // FIELD FIELD (FIELD WITH SPACES) FIELD FIELD
        //
511
        QFile processInfo(QStringLiteral("/proc/%1/stat").arg(pid));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
512
        if (processInfo.open(QIODevice::ReadOnly)) {
513
            QTextStream stream(&processInfo);
514
            const QString &data = stream.readAll();
515

516 517 518 519
            int stack = 0;
            int field = 0;
            int pos = 0;

Kurt Hindenburg's avatar
Kurt Hindenburg committed
520
            while (pos < data.count()) {
521 522
                QChar c = data[pos];

523
                if (c == QLatin1Char('(')) {
524
                    stack++;
525
                } else if (c == QLatin1Char(')')) {
526
                    stack--;
527
                } else if (stack == 0 && c == QLatin1Char(' ')) {
528
                    field++;
529
                } else {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
530 531 532 533 534 535 536 537 538 539
                    switch (field) {
                    case PARENT_PID_FIELD:
                        parentPidString.append(c);
                        break;
                    case PROCESS_NAME_FIELD:
                        processNameString.append(c);
                        break;
                    case GROUP_PROCESS_FIELD:
                        foregroundPidString.append(c);
                        break;
540 541
                    }
                }
542

543 544
                pos++;
            }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
545 546
        } else {
            setFileError(processInfo.error());
547 548
            return false;
        }
549

550 551
        // check that data was read successfully
        bool ok = false;
Jekyll Wu's avatar
Jekyll Wu committed
552
        const int foregroundPid = foregroundPidString.toInt(&ok);
553
        if (ok) {
554
            setForegroundPid(foregroundPid);
555
        }
556

Jekyll Wu's avatar
Jekyll Wu committed
557
        const int parentPid = parentPidString.toInt(&ok);
558
        if (ok) {
559
            setParentPid(parentPid);
560
        }
561

562
        if (!processNameString.isEmpty()) {
563
            setName(processNameString);
564
        }
565 566

        // update object state
567
        setPid(pid);
568 569 570 571

        return ok;
    }

572
    bool readArguments(int pid) Q_DECL_OVERRIDE
573
    {
574 575 576 577
        // read command-line arguments file found at /proc/<pid>/cmdline
        // the expected format is a list of strings delimited by null characters,
        // and ending in a double null character pair.

578
        QFile argumentsFile(QStringLiteral("/proc/%1/cmdline").arg(pid));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
579
        if (argumentsFile.open(QIODevice::ReadOnly)) {
580
            QTextStream stream(&argumentsFile);
581
            const QString &data = stream.readAll();
582

583
            const QStringList &argList = data.split(QLatin1Char('\0'));
584

585 586
            foreach (const QString &entry, argList) {
                if (!entry.isEmpty()) {
587
                    addArgument(entry);
588
                }
589
            }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
590 591
        } else {
            setFileError(argumentsFile.error());
592 593
        }

594 595
        return true;
    }
596
};
597

598
#elif defined(Q_OS_FREEBSD)
599 600 601
class FreeBSDProcessInfo : public UnixProcessInfo
{
public:
602 603
    FreeBSDProcessInfo(int pid) :
        UnixProcessInfo(pid)
604
    {
605 606
    }

607
protected:
608
    bool readCurrentDir(int pid) Q_DECL_OVERRIDE
609
    {
610 611 612 613 614 615 616 617
#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;
618
        managementInfoBase[3] = pid;
619 620

        len = sizeof(buf);
621
        if (sysctl(managementInfoBase, 4, buf, &len, NULL, 0) == -1) {
622
            return false;
623
        }
624

625
        setCurrentDir(QString::fromUtf8(buf));
626 627 628 629

        return true;
#else
        int numrecords;
630
        struct kinfo_file *info = nullptr;
631

632
        info = kinfo_getfile(pid, &numrecords);
633

634
        if (!info) {
635
            return false;
636
        }
637 638 639

        for (int i = 0; i < numrecords; ++i) {
            if (info[i].kf_fd == KF_FD_TYPE_CWD) {
640
                setCurrentDir(QString::fromUtf8(info[i].kf_path));
641 642 643 644 645 646 647 648 649 650 651

                free(info);
                return true;
            }
        }

        free(info);
        return false;
#endif
    }

652
private:
653
    bool readProcInfo(int pid) Q_DECL_OVERRIDE
654
    {
655 656
        int managementInfoBase[4];
        size_t mibLength;
657
        struct kinfo_proc *kInfoProc;
658 659 660 661

        managementInfoBase[0] = CTL_KERN;
        managementInfoBase[1] = KERN_PROC;
        managementInfoBase[2] = KERN_PROC_PID;
662
        managementInfoBase[3] = pid;
663

664
        if (sysctl(managementInfoBase, 4, NULL, &mibLength, NULL, 0) == -1) {
665
            return false;
666
        }
667 668 669

        kInfoProc = new struct kinfo_proc [mibLength];

Kurt Hindenburg's avatar
Kurt Hindenburg committed
670
        if (sysctl(managementInfoBase, 4, kInfoProc, &mibLength, NULL, 0) == -1) {
671 672 673 674
            delete [] kInfoProc;
            return false;
        }

675
#if defined(HAVE_OS_DRAGONFLYBSD)
676
        setName(QString::fromUtf8(kInfoProc->kp_comm));
677 678 679 680 681
        setPid(kInfoProc->kp_pid);
        setParentPid(kInfoProc->kp_ppid);
        setForegroundPid(kInfoProc->kp_pgid);
        setUserId(kInfoProc->kp_uid);
#else
682
        setName(QString::fromUtf8(kInfoProc->ki_comm));
683 684 685 686
        setPid(kInfoProc->ki_pid);
        setParentPid(kInfoProc->ki_ppid);
        setForegroundPid(kInfoProc->ki_pgid);
        setUserId(kInfoProc->ki_uid);
687
#endif
688 689 690 691 692 693 694

        readUserName();

        delete [] kInfoProc;
        return true;
    }

695
    bool readArguments(int pid) Q_DECL_OVERRIDE
696
    {
697 698 699 700 701 702
        char args[ARG_MAX];
        int managementInfoBase[4];
        size_t len;

        managementInfoBase[0] = CTL_KERN;
        managementInfoBase[1] = KERN_PROC;
703
        managementInfoBase[2] = KERN_PROC_ARGS;
704
        managementInfoBase[3] = pid;
705 706

        len = sizeof(args);
707
        if (sysctl(managementInfoBase, 4, args, &len, NULL, 0) == -1) {
708
            return false;
709
        }
710

711
        // len holds the length of the string
712 713 714
        QString qargs = QString::fromLocal8Bit(args, len);
        foreach (const QString &value, qargs.split(QLatin1Char('\u0000'))) {
            if (!value.isEmpty()) {
715 716
                addArgument(value);
            }
717 718 719
        }

        return true;
720
    }
721
};
722

723 724 725 726
#elif defined(Q_OS_OPENBSD)
class OpenBSDProcessInfo : public UnixProcessInfo
{
public:
727 728
    OpenBSDProcessInfo(int pid) :
        UnixProcessInfo(pid)
729
    {
730 731 732
    }

protected:
733
    bool readCurrentDir(int pid) Q_DECL_OVERRIDE
734 735 736 737
    {
        char buf[PATH_MAX];
        int managementInfoBase[3];
        size_t len;
738 739

        managementInfoBase[0] = CTL_KERN;
740
        managementInfoBase[1] = KERN_PROC_CWD;
741
        managementInfoBase[2] = pid;
742 743

        len = sizeof(buf);
744 745
        if (::sysctl(managementInfoBase, 3, buf, &len, NULL, 0) == -1) {
            qWarning() << "sysctl() call failed with code" << errno;
746
            return false;
747
        }
748

749
        setCurrentDir(QString::fromUtf8(buf));
750
        return true;
751 752 753
    }

private:
754
    bool readProcInfo(int pid) Q_DECL_OVERRIDE
755 756 757 758
    {
        int managementInfoBase[6];
        size_t mibLength;
        struct kinfo_proc *kInfoProc;
759 760 761 762

        managementInfoBase[0] = CTL_KERN;
        managementInfoBase[1] = KERN_PROC;
        managementInfoBase[2] = KERN_PROC_PID;
763
        managementInfoBase[3] = pid;
764 765 766 767
        managementInfoBase[4] = sizeof(struct kinfo_proc);
        managementInfoBase[5] = 1;

        if (::sysctl(managementInfoBase, 6, NULL, &mibLength, NULL, 0) == -1) {
Laurent Montel's avatar
Laurent Montel committed
768
            qWarning() << "first sysctl() call failed with code" << errno;
769 770 771
            return false;
        }

772
        kInfoProc = (struct kinfo_proc *)malloc(mibLength);
773 774

        if (::sysctl(managementInfoBase, 6, kInfoProc, &mibLength, NULL, 0) == -1) {
Laurent Montel's avatar
Laurent Montel committed
775
            qWarning() << "second sysctl() call failed with code" << errno;
776 777 778 779 780 781 782 783 784 785 786 787 788 789 790
            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;
    }

791
    char **readProcArgs(int pid, int whatMib)
792 793 794 795 796 797
    {
        void *buf = NULL;
        void *nbuf;
        size_t len = 4096;
        int rc = -1;
        int managementInfoBase[4];
798 799 800

        managementInfoBase[0] = CTL_KERN;
        managementInfoBase[1] = KERN_PROC_ARGS;
801
        managementInfoBase[2] = pid;
802 803 804 805 806 807 808 809 810 811 812
        managementInfoBase[3] = whatMib;

        do {
            len *= 2;
            nbuf = realloc(buf, len);
            if (nbuf == NULL) {
                break;
            }

            buf = nbuf;
            rc = ::sysctl(managementInfoBase, 4, buf, &len, NULL, 0);
Laurent Montel's avatar
Laurent Montel committed
813
            qWarning() << "sysctl() call failed with code" << errno;
814 815 816 817 818 819 820
        } while (rc == -1 && errno == ENOMEM);

        if (nbuf == NULL || rc == -1) {
            free(buf);
            return NULL;
        }

821
        return (char **)buf;
822 823
    }

824
    bool readArguments(int pid) Q_DECL_OVERRIDE
825 826
    {
        char **argv;
827

828
        argv = readProcArgs(pid, KERN_PROC_ARGV);
829 830 831 832
        if (argv == NULL) {
            return false;
        }

833
        for (char **p = argv; *p != NULL; p++) {
834 835 836 837 838 839 840
            addArgument(QString(*p));
        }
        free(argv);
        return true;
    }
};

841
#elif defined(Q_OS_MACOS)
842 843 844
class MacProcessInfo : public UnixProcessInfo
{
public:
845 846
    MacProcessInfo(int pid) :
        UnixProcessInfo(pid)
847
    {
848 849
    }

850
protected:
851
    bool readCurrentDir(int pid) Q_DECL_OVERRIDE
852
    {
853
        struct proc_vnodepathinfo vpi;
854
        const int nb = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
855
        if (nb == sizeof(vpi)) {
856
            setCurrentDir(QString::fromUtf8(vpi.pvi_cdir.vip_path));
857 858 859 860 861
            return true;
        }
        return false;
    }

862
private:
863
    bool readProcInfo(int pid) Q_DECL_OVERRIDE
864
    {
865 866
        int managementInfoBase[4];
        size_t mibLength;
867
        struct kinfo_proc *kInfoProc;
868
        QT_STATBUF statInfo;
869 870 871 872 873

        // Find the tty device of 'pid' (Example: /dev/ttys001)
        managementInfoBase[0] = CTL_KERN;
        managementInfoBase[1] = KERN_PROC;
        managementInfoBase[2] = KERN_PROC_PID;
874
        managementInfoBase[3] = pid;
875

876
        if (sysctl(managementInfoBase, 4, nullptr, &mibLength, nullptr, 0) == -1) {
877
            return false;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
878
        } else {
879
            kInfoProc = new struct kinfo_proc [mibLength];
880
            if (sysctl(managementInfoBase, 4, kInfoProc, &mibLength, nullptr, 0) == -1) {
881 882
                delete [] kInfoProc;
                return false;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
883
            } else {
884
                const QString deviceNumber = QString::fromUtf8(devname(((&kInfoProc->kp_eproc)->e_tdev), S_IFCHR));
885 886
                const QString fullDeviceName = QStringLiteral("/dev/")
                                               + deviceNumber.rightJustified(3, QLatin1Char('0'));
887 888
                delete [] kInfoProc;

Jekyll Wu's avatar
Jekyll Wu committed
889
                const QByteArray deviceName = fullDeviceName.toLatin1();
890
                const char *ttyName = deviceName.data();
891

892
                if (QT_STAT(ttyName, &statInfo) != 0) {
893
                    return false;
894
                }
895 896 897 898 899 900 901 902

                // 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;
903 904
                if (sysctl(managementInfoBase, sizeof(managementInfoBase) / sizeof(int), nullptr,
                           &mibLength, nullptr, 0) == -1) {
905
                    return false;
906
                }
907 908

                kInfoProc = new struct kinfo_proc [mibLength];
909
                if (sysctl(managementInfoBase, sizeof(managementInfoBase) / sizeof(int), kInfoProc,
910
                           &mibLength, nullptr, 0) == -1) {
911
                    return false;
912
                }
913 914

                // The foreground program is the first one
915
                setName(QString::fromUtf8(kInfoProc->kp_proc.p_comm));
916 917 918

                delete [] kInfoProc;
            }
919
            setPid(pid);
920 921 922 923
        }
        return true;
    }

924
    bool readArguments(int pid) Q_DECL_OVERRIDE
925
    {
926
        Q_UNUSED(pid);
927 928
        return false;
    }
929
};
930

931
#elif defined(Q_OS_SOLARIS)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
932 933 934 935 936 937
// 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.
938
#if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS == 64)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
939 940 941
#undef _FILE_OFFSET_BITS
#endif
#include <procfs.h>
942 943 944 945

class SolarisProcessInfo : public UnixProcessInfo
{
public:
946 947
    SolarisProcessInfo(int pid) :
        UnixProcessInfo(pid)
948
    {
949
    }
950 951 952 953

protected:
    // FIXME: This will have the same issues as BKO 251351; the Linux
    // version uses readlink.
954
    bool readCurrentDir(int pid) Q_DECL_OVERRIDE
955
    {
956
        QFileInfo info(QString("/proc/%1/path/cwd").arg(pid));
957 958 959 960 961 962
        const bool readable = info.isReadable();

        if (readable && info.isSymLink()) {
            setCurrentDir(info.symLinkTarget());
            return true;
        } else {
963
            if (!readable) {
964
                setError(PermissionsError);
965
            } else {
966
                setError(UnknownError);
967
            }
968 969 970 971 972

            return false;
        }
    }

973
private:
974
    bool readProcInfo(int pid) Q_DECL_OVERRIDE
975
    {
976
        QFile psinfo(QString("/proc/%1/psinfo").arg(pid));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
977
        if (psinfo.open(QIODevice::ReadOnly)) {
978
            struct psinfo info;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
979
            if (psinfo.read((char *)&info, sizeof(info)) != sizeof(info)) {
980 981
                return false;
            }
982

983 984 985
            setParentPid(info.pr_ppid);
            setForegroundPid(info.pr_pgid);
            setName(info.pr_fname);
986
            setPid(pid);
987 988

            // Bogus, because we're treating the arguments as one single string
Kurt Hindenburg's avatar
Kurt Hindenburg committed
989
            info.pr_psargs[PRARGSZ - 1] = 0;
990 991 992 993 994
            addArgument(info.pr_psargs);
        }
        return true;
    }

995
    bool readArguments(int /*pid*/) Q_DECL_OVERRIDE
996
    {
997
        // Handled in readProcInfo()
998
        return false;
999
    }
1000
};
1001
#endif
1002

1003 1004 1005 1006 1007 1008
SSHProcessInfo::SSHProcessInfo(const ProcessInfo &process) :
    _process(process),
    _user(QString()),
    _host(QString()),
    _port(QString()),
    _command(QString())
1009
{
1010 1011 1012
    bool ok = false;

    // check that this is a SSH process
1013
    const QString &name = _process.name(&ok);
1014

1015
    if (!ok || name != QLatin1String("ssh")) {
1016
        if (!ok) {
Laurent Montel's avatar
Laurent Montel committed
1017
            qWarning() << "Could not read process info";
1018
        } else {
Laurent Montel's avatar
Laurent Montel committed
1019
            qWarning() << "Process is not a SSH process";
1020
        }
1021 1022 1023 1024 1025

        return;
    }

    // read arguments
1026
    const QVector<QString> &args = _process.arguments(&ok);
1027

1028 1029
    // SSH options
    // these are taken from the SSH manual ( accessed via 'man ssh' )
1030

1031
    // options which take no arguments
1032
    static const QString noArgumentOptions(QStringLiteral("1246AaCfgKkMNnqsTtVvXxYy"));
1033
    // options which take one argument
1034
    static const QString singleArgumentOptions(QStringLiteral("bcDeFIiLlmOopRSWw"));
1035

1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
    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 )
1049
        for (int i = 1; i < args.count(); i++) {
1050 1051
            // If this one is an option ...
            // Most options together with its argument will be skipped.
1052 1053
            if (args[i].startsWith(QLatin1Char('-'))) {
                const QChar optionChar = (args[i].length() > 1) ? args[i][1] : QLatin1Char('\0');
1054
                // for example: -p2222 or -p 2222 ?
1055
                const bool optionArgumentCombined = args[i].length() > 2;
1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069

                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++;
                    }
1070

1071
                    // support using `-l user` to specify username.
1072
                    if (optionChar == QLatin1Char('l')) {
1073
                        _user = argument;
1074
                    }
1075
                    // support using `-p port` to specify port.
1076
                    else if (optionChar == QLatin1Char('p')) {
1077
                        _port = argument;
1078
                    }
1079

1080
                    continue;
1081
                }
1082
            }
1083

1084 1085 1086 1087 1088 1089 1090
            // 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 )

1091
                const int separatorPosition = args[i].indexOf(QLatin1Char('@'));
1092 1093 1094 1095 1096 1097 1098 1099 1100
                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 {
1101 1102 1103 1104 1105 1106 1107 1108 1109
                // host has already been found, this must be part of the
                // command arguments.
                // Note this is not 100% correct.  If any of the above
                // noArgumentOptions or singleArgumentOptions are found, this
                // will not be correct (example ssh server top -i 50)
                // Suggest putting ssh command in quotes
                if (_command.isEmpty()) {
                    _command = args[i];
                } else {