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