localprocesslist.cpp 10 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
hjk's avatar
hjk committed
3
4
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8
9
10
11
12
13
14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
23
24
25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
29
30

#include "localprocesslist.h"
31

32
33
#include <utils/synchronousprocess.h>

34
#include <QLibrary>
35
36
#include <QTimer>

37
#ifdef Q_OS_UNIX
hjk's avatar
hjk committed
38
39
#include <QProcess>
#include <QDir>
40
#include <signal.h>
hjk's avatar
hjk committed
41
42
#include <errno.h>
#include <string.h>
43
#include <unistd.h>
44
#endif
45

46
47
48
49
50
51
52
#ifdef Q_OS_WIN
// Enable Win API of XP SP1 and later
#define _WIN32_WINNT 0x0502
#include <windows.h>
#include <utils/winutils.h>
#include <tlhelp32.h>
#include <psapi.h>
Orgad Shaneh's avatar
Orgad Shaneh committed
53
54
55
#ifndef PROCESS_SUSPEND_RESUME
#define PROCESS_SUSPEND_RESUME 0x0800
#endif
hjk's avatar
hjk committed
56
#endif
57

58
59
60
namespace ProjectExplorer {
namespace Internal {

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#ifdef Q_OS_WIN

// Resolve QueryFullProcessImageNameW out of kernel32.dll due
// to incomplete MinGW import libs and it not being present
// on Windows XP.
static BOOL queryFullProcessImageName(HANDLE h, DWORD flags, LPWSTR buffer, DWORD *size)
{
    // Resolve required symbols from the kernel32.dll
    typedef BOOL (WINAPI *QueryFullProcessImageNameWProtoType)
                 (HANDLE, DWORD, LPWSTR, PDWORD);
    static QueryFullProcessImageNameWProtoType queryFullProcessImageNameW = 0;
    if (!queryFullProcessImageNameW) {
        QLibrary kernel32Lib(QLatin1String("kernel32.dll"), 0);
        if (kernel32Lib.isLoaded() || kernel32Lib.load())
            queryFullProcessImageNameW = (QueryFullProcessImageNameWProtoType)kernel32Lib.resolve("QueryFullProcessImageNameW");
    }
    if (!queryFullProcessImageNameW)
        return FALSE;
    // Read out process
    return (*queryFullProcessImageNameW)(h, flags, buffer, size);
}

static QString imageName(DWORD processId)
{
    QString  rc;
    HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION , FALSE, processId);
    if (handle == INVALID_HANDLE_VALUE)
        return rc;
    WCHAR buffer[MAX_PATH];
    DWORD bufSize = MAX_PATH;
    if (queryFullProcessImageName(handle, 0, buffer, &bufSize))
        rc = QString::fromUtf16(reinterpret_cast<const ushort*>(buffer));
    CloseHandle(handle);
    return rc;
}
hjk's avatar
hjk committed
96
97
98

LocalProcessList::LocalProcessList(const IDevice::ConstPtr &device, QObject *parent)
        : DeviceProcessList(device, parent)
99
        , m_myPid(GetCurrentProcessId())
hjk's avatar
hjk committed
100
101
102
{
}

103
QList<DeviceProcess> LocalProcessList::getLocalProcesses()
hjk's avatar
hjk committed
104
105
106
107
108
109
110
{
    QList<DeviceProcess> processes;

    PROCESSENTRY32 pe;
    pe.dwSize = sizeof(PROCESSENTRY32);
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snapshot == INVALID_HANDLE_VALUE)
111
        return processes;
hjk's avatar
hjk committed
112
113
114
115

    for (bool hasNext = Process32First(snapshot, &pe); hasNext; hasNext = Process32Next(snapshot, &pe)) {
        DeviceProcess p;
        p.pid = pe.th32ProcessID;
116
117
118
119
120
        // Image has the absolute path, but can fail.
        const QString image = imageName(pe.th32ProcessID);
        p.exe = p.cmdLine = image.isEmpty() ?
            QString::fromWCharArray(pe.szExeFile) :
            image;
hjk's avatar
hjk committed
121
122
123
        processes << p;
    }
    CloseHandle(snapshot);
124
    return processes;
hjk's avatar
hjk committed
125
126
127
128
}

void LocalProcessList::doKillProcess(const DeviceProcess &process)
{
129
130
131
    const DWORD rights = PROCESS_QUERY_INFORMATION|PROCESS_SET_INFORMATION
            |PROCESS_VM_OPERATION|PROCESS_VM_WRITE|PROCESS_VM_READ
            |PROCESS_DUP_HANDLE|PROCESS_TERMINATE|PROCESS_CREATE_THREAD|PROCESS_SUSPEND_RESUME;
132
133
134
135
136
137
138
139
140
141
    m_error.clear();
    if (const HANDLE handle = OpenProcess(rights, FALSE, process.pid)) {
        if (!TerminateProcess(handle, UINT(-1))) {
          m_error = tr("Cannot terminate process %1: %2").
                    arg(process.pid).arg(Utils::winErrorMessage(GetLastError()));
        }
        CloseHandle(handle);
    } else {
        m_error = tr("Cannot open process %1: %2").
                  arg(process.pid).arg(Utils::winErrorMessage(GetLastError()));
142
    }
143
    QTimer::singleShot(0, this, SLOT(reportDelayedKillStatus()));
hjk's avatar
hjk committed
144
145
}

146
147
#endif //Q_OS_WIN

hjk's avatar
hjk committed
148
149

#ifdef Q_OS_UNIX
150
LocalProcessList::LocalProcessList(const IDevice::ConstPtr &device, QObject *parent)
151
    : DeviceProcessList(device, parent)
152
    , m_myPid(getpid())
153
{}
154

hjk's avatar
hjk committed
155
156
157
158
159
160
161
162
163
164
static bool isUnixProcessId(const QString &procname)
{
    for (int i = 0; i != procname.size(); ++i)
        if (!procname.at(i).isDigit())
            return false;
    return true;
}

// Determine UNIX processes by reading "/proc". Default to ps if
// it does not exist
165
166
167
168

static const char procDirC[] = "/proc/";

static QList<DeviceProcess> getLocalProcessesUsingProc(const QDir &procDir)
hjk's avatar
hjk committed
169
170
{
    QList<DeviceProcess> processes;
171
    const QString procDirPath = QLatin1String(procDirC);
hjk's avatar
hjk committed
172
173
174
175
176
177
    const QStringList procIds = procDir.entryList();
    foreach (const QString &procId, procIds) {
        if (!isUnixProcessId(procId))
            continue;
        DeviceProcess proc;
        proc.pid = procId.toInt();
178
        const QString root = procDirPath + procId;
179
180
181
182

        QFile exeFile(root + QLatin1String("/exe"));
        proc.exe = exeFile.symLinkTarget();

183
184
185
186
        QFile cmdLineFile(root + QLatin1String("/cmdline"));
        if (cmdLineFile.open(QIODevice::ReadOnly)) { // process may have exited
            QList<QByteArray> tokens = cmdLineFile.readAll().split('\0');
            if (!tokens.isEmpty()) {
187
188
                if (proc.exe.isEmpty())
                    proc.exe = QString::fromLocal8Bit(tokens.front());
189
190
191
192
193
194
                foreach (const QByteArray &t,  tokens) {
                    if (!proc.cmdLine.isEmpty())
                        proc.cmdLine.append(QLatin1Char(' '));
                    proc.cmdLine.append(QString::fromLocal8Bit(t));
                }
            }
hjk's avatar
hjk committed
195
        }
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210

        if (proc.exe.isEmpty()) {
            QFile statFile(root + QLatin1String("/stat"));
            if (!statFile.open(QIODevice::ReadOnly)) {
                const QStringList data = QString::fromLocal8Bit(statFile.readAll()).split(QLatin1Char(' '));
                proc.exe = data.at(1);
                proc.cmdLine = data.at(1); // PPID is element 3
                if (proc.exe.startsWith(QLatin1Char('(')) && proc.exe.endsWith(QLatin1Char(')'))) {
                    proc.exe.truncate(proc.exe.size() - 1);
                    proc.exe.remove(0, 1);
                }
            }
        }
        if (!proc.exe.isEmpty())
            processes.push_back(proc);
hjk's avatar
hjk committed
211
    }
212
    return processes;
hjk's avatar
hjk committed
213
214
}

215
// Determine UNIX processes by running ps
216
static QList<DeviceProcess> getLocalProcessesUsingPs()
hjk's avatar
hjk committed
217
{
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
#ifdef Q_OS_MAC
    static const char formatC[] = "pid state command";
#else
    static const char formatC[] = "pid,state,cmd";
#endif
    QList<DeviceProcess> processes;
    QProcess psProcess;
    QStringList args;
    args << QLatin1String("-e") << QLatin1String("-o") << QLatin1String(formatC);
    psProcess.start(QLatin1String("ps"), args);
    if (psProcess.waitForStarted()) {
        QByteArray output;
        if (Utils::SynchronousProcess::readDataFromProcess(psProcess, 30000, &output, 0, false)) {
            // Split "457 S+   /Users/foo.app"
            const QStringList lines = QString::fromLocal8Bit(output).split(QLatin1Char('\n'));
            const int lineCount = lines.size();
            const QChar blank = QLatin1Char(' ');
            for (int l = 1; l < lineCount; l++) { // Skip header
                const QString line = lines.at(l).simplified();
                const int pidSep = line.indexOf(blank);
                const int cmdSep = pidSep != -1 ? line.indexOf(blank, pidSep + 1) : -1;
                if (cmdSep > 0) {
                    DeviceProcess procData;
                    procData.pid = line.left(pidSep).toInt();
                    procData.exe = line.mid(cmdSep + 1);
                    procData.cmdLine = line.mid(cmdSep + 1);
                    processes.push_back(procData);
                }
246
247
248
            }
        }
    }
249
    return processes;
250
251
}

252
QList<DeviceProcess> LocalProcessList::getLocalProcesses()
253
{
254
255
    const QDir procDir = QDir(QLatin1String(procDirC));
    return procDir.exists() ? getLocalProcessesUsingProc(procDir) : getLocalProcessesUsingPs();
256
257
}

258
void LocalProcessList::doKillProcess(const DeviceProcess &process)
259
260
261
262
263
264
265
266
{
    if (kill(process.pid, SIGKILL) == -1)
        m_error = QString::fromLocal8Bit(strerror(errno));
    else
        m_error.clear();
    QTimer::singleShot(0, this, SLOT(reportDelayedKillStatus()));
}

hjk's avatar
hjk committed
267
#endif // QT_OS_UNIX
268

269
270
271
272
273
274
275
276
Qt::ItemFlags LocalProcessList::flags(const QModelIndex &index) const
{
    Qt::ItemFlags flags = DeviceProcessList::flags(index);
    if (index.isValid() && at(index.row()).pid == m_myPid)
        flags &= ~(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
    return flags;
}

277
278
279
280
281
282
283
284
285
286
void LocalProcessList::handleUpdate()
{
    reportProcessListUpdated(getLocalProcesses());
}

void LocalProcessList::doUpdate()
{
    QTimer::singleShot(0, this, SLOT(handleUpdate()));
}

287
288
289
290
291
292
293
294
void LocalProcessList::reportDelayedKillStatus()
{
    if (m_error.isEmpty())
        reportProcessKilled();
    else
        reportError(m_error);
}

295
} // namespace Internal
296
} // namespace ProjectExplorer