Files
2025-12-10 14:38:26 -08:00

297 lines
10 KiB
C++

// *****************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under *
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
// *****************************************************************************
#include "sys_info.h"
#include "crc.h"
#include "file_access.h"
#include "sys_version.h"
#include "symlink_target.h"
#include "file_io.h"
#include <ifaddrs.h>
#include <net/if.h> //IFF_LOOPBACK
#include <netpacket/packet.h> //sockaddr_ll
#include "process_exec.h"
#include <unistd.h> //getuid()
#include <pwd.h> //getpwuid_r()
using namespace zen;
Zstring zen::getLoginUser() //throw FileError
{
auto tryGetNonRootUser = [](const char* varName) -> std::optional<Zstring>
{
if (const std::optional<Zstring> username = getEnvironmentVar(varName))
if (!username->empty() && *username != "root")
return *username;
return {};
};
if (const uid_t userIdNo = ::getuid(); //never fails
userIdNo != 0) //nofail; non-root
{
//ugh, the world's stupidest API:
std::vector<char> buf(std::max<long>(10000, ::sysconf(_SC_GETPW_R_SIZE_MAX))); //::sysconf may return long(-1) or even a too small size!! WTF!
passwd buf2 = {};
passwd* pwEntry = nullptr;
if (const int rv = ::getpwuid_r(userIdNo, //uid_t uid
&buf2, //struct passwd* pwd
buf.data(), //char* buf
buf.size(), //size_t buflen
&pwEntry); //struct passwd** result
rv != 0 || !pwEntry)
{
//"If an error occurs, errno is set appropriately" => why the fuck, then, also return errno as return value!?
errno = rv != 0 ? rv : ENOENT;
THROW_LAST_FILE_ERROR(_("Cannot get process information."), "getpwuid_r(" + numberTo<std::string>(userIdNo) + ')');
}
return pwEntry->pw_name;
}
//else: root(0) => consider as request for elevation, NOT impersonation!
//getlogin() is smarter than simply evaluating $LOGNAME! even in contexts without
//$LOGNAME, e.g. "sudo su" on Ubuntu, it returns the correct non-root user!
if (const char* loginUser = ::getlogin()) //https://linux.die.net/man/3/getlogin
if (strLength(loginUser) > 0 && !equalString(loginUser, "root"))
return loginUser;
//BUT: getlogin() can fail with ENOENT on Linux Mint: https://freefilesync.org/forum/viewtopic.php?t=8181
//getting a little desperate: variables used by installer.sh
if (const std::optional<Zstring> username = tryGetNonRootUser("USER")) return *username;
if (const std::optional<Zstring> username = tryGetNonRootUser("SUDO_USER")) return *username;
if (const std::optional<Zstring> username = tryGetNonRootUser("LOGNAME")) return *username;
//apparently the current user really IS root: https://freefilesync.org/forum/viewtopic.php?t=8405
assert(getuid() == 0);
return "root";
}
Zstring zen::getUserDescription() //throw FileError
{
const Zstring username = getLoginUser(); //throw FileError
const Zstring computerName = []() -> Zstring //throw FileError
{
std::vector<char> buf(10000);
if (::gethostname(buf.data(), buf.size()) != 0)
THROW_LAST_FILE_ERROR(_("Cannot get process information."), "gethostname");
Zstring hostName = buf.data();
if (endsWithAsciiNoCase(hostName, ".local")) //strip fluff (macOS) => apparently not added on Linux?
hostName = beforeLast(hostName, '.', IfNotFoundReturn::none);
return hostName;
}();
if (contains(getUpperCase(computerName), getUpperCase(username)))
return username; //no need for text duplication! e.g. "Zenju (Zenju-PC)"
return username + Zstr(" (") + computerName + Zstr(')'); //e.g. "Admin (Zenju-PC)"
}
namespace
{
}
ComputerModel zen::getComputerModel() //throw FileError
{
ComputerModel cm;
try
{
auto tryGetInfo = [](const Zstring& filePath)
{
try
{
const std::string stream = getFileContent(filePath, nullptr /*notifyUnbufferedIO*/); //throw FileError
return utfTo<std::wstring>(stream);
}
catch (FileError&)
{
if (!itemExists(filePath)) //throw FileError
return std::wstring();
throw;
}
};
cm.model = tryGetInfo("/sys/devices/virtual/dmi/id/product_name"); //throw FileError
cm.vendor = tryGetInfo("/sys/devices/virtual/dmi/id/sys_vendor"); //
//clean up:
cm.model = beforeFirst(cm.model, L'\u00ff', IfNotFoundReturn::all); //fix broken BIOS entries:
cm.vendor = beforeFirst(cm.vendor, L'\u00ff', IfNotFoundReturn::all); //0xff can be considered 0
replace(cm.model, L'_', L' '); //e.g. "CBX3___", "SYSTEM_MANUFACTURER", or just "_"
replace(cm.vendor, L'_', L' '); //e.g. "DELL__", "Exertis_CapTech", or just "_"
trim(cm.model);
trim(cm.vendor);
for (const char* dummyModel :
{
"Please change product name",
"System Product Name",
"To Be Filled By O.E.M.",
"Default string",
"$(DEFAULT STRING)",
"<null string>",
"Product Name",
"Undefined",
"INVALID",
"Unknow",
"empty",
"O.E.M.",
"O.E.M",
"OEM",
"NA",
".",
})
if (equalAsciiNoCase(cm.model, dummyModel))
{
cm.model.clear();
break;
}
for (const char* dummyVendor :
{
"OEM Manufacturer",
"System manufacturer",
"System Manufacter",
"To Be Filled By O.E.M.",
"Default string",
"$(DEFAULT STRING)",
"Undefined",
"Unknow",
"empty",
"O.E.M.",
"O.E.M",
"OEM",
"NA",
".",
})
if (equalAsciiNoCase(cm.vendor, dummyVendor))
{
cm.vendor.clear();
break;
}
return cm;
}
catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); }
}
std::wstring zen::getOsDescription() //throw FileError
{
try
{
const OsVersionDetail verDetail = getOsVersionDetail(); //throw SysError
return trimCpy(verDetail.osName + L" (" + verDetail.osVersionRaw) + L')'; //e.g. "CentOS (7.8.2003)"
}
catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); }
}
Zstring zen::getProcessPath() //throw FileError
{
try
{
return getSymlinkRawContent_impl("/proc/self/exe").targetPath; //throw SysError
//path does not contain symlinks => no need for ::realpath()
}
catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); }
}
Zstring zen::getUserHome() //throw FileError
{
if (::getuid() != 0) //nofail; non-root
/* https://linux.die.net/man/3/getpwuid: An application that wants to determine its user's home directory
should inspect the value of HOME (rather than the value getpwuid(getuid())->pw_dir) since this allows
the user to modify their notion of "the home directory" during a login session. */
if (const std::optional<Zstring> homeDirPath = getEnvironmentVar("HOME"))
return *homeDirPath;
//root(0) => consider as request for elevation, NOT impersonation!
//=> "HOME=/root" :(
const Zstring loginUser = getLoginUser(); //throw FileError
//ugh, the world's stupidest API:
std::vector<char> buf(std::max<long>(10000, ::sysconf(_SC_GETPW_R_SIZE_MAX))); //::sysconf may return long(-1) or even a too small size!! WTF!
passwd buf2 = {};
passwd* pwEntry = nullptr;
if (const int rv = ::getpwnam_r(loginUser.c_str(), //const char *name
&buf2, //struct passwd* pwd
buf.data(), //char* buf
buf.size(), //size_t buflen
&pwEntry); //struct passwd** result
rv != 0 || !pwEntry)
{
//"If an error occurs, errno is set appropriately" => why the fuck, then also return errno as return value!?
errno = rv != 0 ? rv : ENOENT;
THROW_LAST_FILE_ERROR(_("Cannot get process information."), "getpwnam_r(" + utfTo<std::string>(loginUser) + ')');
}
return pwEntry->pw_dir; //home directory
}
Zstring zen::getUserDataPath() //throw FileError
{
if (::getuid() != 0) //nofail; non-root
if (const std::optional<Zstring> xdgCfgPath = getEnvironmentVar("XDG_CONFIG_HOME");
xdgCfgPath&& !xdgCfgPath->empty())
return *xdgCfgPath;
//root(0) => consider as request for elevation, NOT impersonation
return appendPath(getUserHome(), ".config"); //throw FileError
}
Zstring zen::getUserDownloadsPath() //throw FileError
{
try
{
if (::getuid() != 0) //nofail; non-root
if (const auto& [exitCode, output] = consoleExecute("xdg-user-dir DOWNLOAD", std::nullopt /*timeoutMs*/); //throw SysError
exitCode == 0)
{
const Zstring& downloadsPath = trimCpy(output);
ASSERT_SYSERROR(!downloadsPath.empty());
return downloadsPath;
}
//root(0) => consider as request for elevation, NOT impersonation
//fallback: probably correct 99.9% of the time anyway...
return appendPath(getUserHome(), "Downloads"); //throw FileError
}
catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); }
}
bool zen::runningElevated() //throw FileError
{
if (::geteuid() != 0) //nofail; non-root
return false;
return getLoginUser() != "root"; //throw FileError
//consider "root login" like "UAC disabled" on Windows
}