1 /*
2 * LibrePCB - Professional EDA for everyone!
3 * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4 * https://librepcb.org/
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /*******************************************************************************
21 * Includes
22 ******************************************************************************/
23 #include "systeminfo.h"
24
25 #include "fileio/filepath.h"
26
27 #include <QHostInfo>
28 #include <QtCore>
29
30 #if defined(Q_OS_OSX) // macOS
31 #include <sys/types.h>
32 #include <system_error>
33
34 #include <cerrno>
35 #include <libproc.h>
36 #include <signal.h>
37 #elif defined(Q_OS_UNIX) // UNIX/Linux
38 #include <sys/types.h>
39 #include <system_error>
40
41 #include <cerrno>
42 #include <pwd.h>
43 #include <signal.h>
44 #include <unistd.h>
45 #if defined(Q_OS_SOLARIS)
46 #include <libproc.h>
47 #endif
48 #if defined(Q_OS_OPENBSD)
49 #include <sys/sysctl.h>
50 #endif
51 #elif defined(Q_OS_WIN32) || defined(Q_OS_WIN64) // Windows
52 #ifdef WINVER
53 #undef WINVER
54 #endif
55 #ifdef _WIN32_WINNT
56 #undef _WIN32_WINNT
57 #endif
58 #define WINVER 0x0600
59 #define _WIN32_WINNT 0x0600
60 #include <windows.h>
61 #else
62 #error "Unknown operating system!"
63 #endif
64
65 /*******************************************************************************
66 * Namespace
67 ******************************************************************************/
68 namespace librepcb {
69
70 /*******************************************************************************
71 * Static Variables
72 ******************************************************************************/
73
74 QString SystemInfo::sUsername;
75 QString SystemInfo::sFullUsername;
76 QString SystemInfo::sHostname;
77
78 /*******************************************************************************
79 * Static Methods
80 ******************************************************************************/
81
getUsername()82 const QString& SystemInfo::getUsername() noexcept {
83 if (sUsername.isNull()) {
84 // this line should work for most UNIX, Linux, Mac and Windows systems
85 sUsername =
86 QString(qgetenv("USERNAME")).remove('\n').remove('\r').trimmed();
87
88 // if the environment variable "USERNAME" is not set, we will try "USER"
89 if (sUsername.isEmpty()) {
90 sUsername = QString(qgetenv("USER")).remove('\n').remove('\r').trimmed();
91 }
92
93 if (sUsername.isEmpty()) {
94 qWarning() << "Could not determine the system's username!";
95 }
96 }
97
98 return sUsername;
99 }
100
getFullUsername()101 const QString& SystemInfo::getFullUsername() noexcept {
102 if (sFullUsername.isNull()) {
103 #if defined(Q_OS_OSX) // macOS
104 QString command(
105 "finger `whoami` | awk -F: '{ print $3 }' | head -n1 | sed 's/^ //'");
106 QProcess process;
107 process.start("sh", QStringList() << "-c" << command);
108 process.waitForFinished(500);
109 sFullUsername = QString(process.readAllStandardOutput())
110 .remove('\n')
111 .remove('\r')
112 .trimmed();
113 #elif defined(Q_OS_UNIX) // UNIX/Linux
114 passwd* userinfo = getpwuid(getuid());
115 if (userinfo == NULL) {
116 qWarning() << "Could not fetch user info via getpwuid!";
117 } else {
118 QString gecosString = QString::fromLocal8Bit(userinfo->pw_gecos);
119 sFullUsername =
120 gecosString.section(',', 0, 0).remove('\n').remove('\r').trimmed();
121 }
122 #elif defined(Q_OS_WIN32) || defined(Q_OS_WIN64) // Windows
123 QString command("net user %USERNAME%");
124 QProcess process;
125 process.start("cmd", QStringList() << "/c" << command);
126 process.waitForFinished(500);
127 QStringList lines = QString(process.readAllStandardOutput()).split('\n');
128 foreach (const QString& line, lines) {
129 if (line.contains("Full Name")) {
130 sFullUsername = QString(line)
131 .remove("Full Name")
132 .remove('\n')
133 .remove('\r')
134 .trimmed();
135 break;
136 }
137 }
138 #else
139 #error "Unknown operating system!"
140 #endif
141
142 if (sFullUsername.isEmpty()) {
143 qWarning()
144 << "The system's full username is empty or could not be determined!";
145 sFullUsername = getUsername(); // fall back to username
146 }
147 }
148
149 return sFullUsername;
150 }
151
getHostname()152 const QString& SystemInfo::getHostname() noexcept {
153 if (sHostname.isNull()) {
154 sHostname = QHostInfo::localHostName().remove('\n').remove('\r').trimmed();
155 }
156
157 if (sHostname.isEmpty()) {
158 qWarning() << "Could not determine the system's hostname!";
159 }
160
161 return sHostname;
162 }
163
isProcessRunning(qint64 pid)164 bool SystemInfo::isProcessRunning(qint64 pid) {
165 #if defined(Q_OS_UNIX) // Mac OS X / Linux / UNIX
166 // From:
167 // http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qlockfile_unix.cpp
168 errno = 0;
169 int ret = ::kill(pid, 0);
170 if (ret == 0) {
171 return true;
172 } else if ((ret == -1) &&
173 (errno == static_cast<int>(std::errc::no_such_process))) {
174 return false;
175 } else {
176 qDebug() << "errno:" << errno;
177 throw RuntimeError(
178 __FILE__, __LINE__,
179 tr("Could not determine if another process is running."));
180 }
181 #elif defined(Q_OS_WIN32) || defined(Q_OS_WIN64) // Windows
182 // From:
183 // http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qlockfile_win.cpp
184 HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
185 if (handle) {
186 DWORD exitCode = 0;
187 BOOL success = ::GetExitCodeProcess(handle, &exitCode);
188 ::CloseHandle(handle);
189 if ((success) && (exitCode == STILL_ACTIVE)) {
190 return true;
191 } else if (success) {
192 return false;
193 } else {
194 qDebug() << "GetLastError():" << GetLastError();
195 throw RuntimeError(
196 __FILE__, __LINE__,
197 tr("Could not determine if another process is running."));
198 }
199 } else if (GetLastError() == ERROR_INVALID_PARAMETER) {
200 return false;
201 } else {
202 qDebug() << "GetLastError():" << GetLastError();
203 throw RuntimeError(
204 __FILE__, __LINE__,
205 tr("Could not determine if another process is running."));
206 }
207 #else
208 #error "Unknown operating system!"
209 #endif
210 }
211
getProcessNameByPid(qint64 pid)212 QString SystemInfo::getProcessNameByPid(qint64 pid) {
213 QString processName;
214 #if defined(Q_OS_OSX) // macOS
215 // From:
216 // http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qlockfile_unix.cpp
217 errno = 0;
218 char name[1024] = {0};
219 int retval = proc_name(pid, name, sizeof(name) / sizeof(char));
220 if (retval > 0) {
221 processName = QFile::decodeName(name);
222 } else if ((retval == 0) &&
223 (errno == static_cast<int>(std::errc::no_such_process))) {
224 return QString(); // process not running
225 } else {
226 throw RuntimeError(__FILE__, __LINE__,
227 tr("proc_name() failed with error %1.").arg(errno));
228 }
229 #elif defined(Q_OS_FREEBSD)
230 char exePath[64];
231 char buf[PATH_MAX + 1];
232 sprintf(exePath, "/proc/%lld/file", pid);
233 size_t len = (size_t)readlink(exePath, buf, sizeof(buf));
234 if (len >= sizeof(buf)) {
235 return QString(); // process not running
236 }
237 buf[len] = 0;
238 processName = QFileInfo(QFile::decodeName(buf)).fileName();
239 #elif defined(Q_OS_OPENBSD)
240 // https://man.openbsd.org/sysctl.2
241 // NOTE: This will return only the first 16 bytes of the process name. If
242 // someone finds a way to get the full process name, feel free to improve it.
243 kinfo_proc proc;
244 size_t procSize = sizeof(proc);
245 int mib[6] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, static_cast<int>(pid),
246 sizeof(proc), 1};
247 int retval = sysctl(mib, 6, &proc, &procSize, NULL, 0);
248 if (retval != 0) {
249 throw RuntimeError(__FILE__, __LINE__,
250 tr("sysctl() failed with retval=%1 and errno=%2.")
251 .arg(retval)
252 .arg(errno));
253 }
254 if (procSize < sizeof(proc)) {
255 return QString(); // process not running
256 }
257 processName = QString::fromLocal8Bit(proc.p_comm, sizeof(proc.p_comm))
258 .section('\0', 0, 0);
259 #elif defined(Q_OS_LINUX)
260
261 // From:
262 // http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qlockfile_unix.cpp
263 if (!FilePath("/proc/version").isExistingFile()) {
264 throw RuntimeError(__FILE__, __LINE__,
265 tr("Could not find the file \"/proc/version\"."));
266 }
267 char exePath[64];
268 char buf[PATH_MAX + 1];
269 sprintf(exePath, "/proc/%lld/exe", pid);
270 size_t len = (size_t)readlink(exePath, buf, sizeof(buf));
271 if (len >= sizeof(buf)) {
272 return QString(); // process not running
273 }
274 buf[len] = 0;
275 processName = QFileInfo(QFile::decodeName(buf)).fileName();
276 // If the executable does no longer exist, the string " (deleted)" is added to
277 // the end of the symlink, so we need to remove that to get the naked process
278 // name.
279 if (processName.endsWith(" (deleted)"))
280 processName.chop(strlen(" (deleted)"));
281 #elif defined(Q_OS_WIN32) || defined(Q_OS_WIN64) // Windows
282 // Originally from:
283 // http://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/io/qlockfile_win.cpp
284 // But then saw this article:
285 // https://blogs.msdn.microsoft.com/oldnewthing/20150716-00/?p=45131/ And
286 // therefore switched from GetModuleFileNameExW() to
287 // QueryFullProcessImageNameW()
288 HANDLE hProcess = OpenProcess(
289 PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, FALSE, DWORD(pid));
290 if ((!hProcess) && (GetLastError() == ERROR_INVALID_PARAMETER)) {
291 return QString(); // process not running
292 } else if (!hProcess) {
293 throw RuntimeError(
294 __FILE__, __LINE__,
295 tr("OpenProcess() failed with error %1.").arg(GetLastError()));
296 }
297 wchar_t buf[MAX_PATH];
298 DWORD length = MAX_PATH;
299 BOOL success = QueryFullProcessImageNameW(hProcess, 0, buf, &length);
300 CloseHandle(hProcess);
301 if ((!success) || (!length)) {
302 throw RuntimeError(__FILE__, __LINE__,
303 tr("QueryFullProcessImageNameW() failed with error %1.")
304 .arg(GetLastError()));
305 }
306 processName = QString::fromWCharArray(buf, length);
307 int i = processName.lastIndexOf(QLatin1Char('\\'));
308 if (i >= 0) processName.remove(0, i + 1);
309 i = processName.lastIndexOf(QLatin1Char('.'));
310 if (i >= 0) processName.truncate(i);
311 #elif defined(Q_OS_SOLARIS)
312 // https://illumos.org/man/3proc/
313 // NOTE: This will only return the first PRFNSZ (16) bytes of the process
314 // name. If someone finds a way to get the full process name, feel free to
315 // improve this.
316 psinfo_t psinfo;
317 if (proc_get_psinfo(pid, &psinfo) != 0) {
318 return QString(); // process not running
319 }
320 processName = QString::fromLocal8Bit(psinfo.pr_fname);
321 #else
322 #error "Unknown operating system!"
323 #endif
324
325 // check if the process name is not empty
326 if (processName.isEmpty()) {
327 throw RuntimeError(
328 __FILE__, __LINE__,
329 tr("Could not determine the process name of another process."));
330 }
331
332 return processName;
333 }
334
335 /*******************************************************************************
336 * End of File
337 ******************************************************************************/
338
339 } // namespace librepcb
340