path_util.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <algorithm>
  4. #include <unordered_map>
  5. #include "common/fs/fs.h"
  6. #include "common/fs/fs_paths.h"
  7. #include "common/fs/path_util.h"
  8. #include "common/logging/log.h"
  9. #ifdef _WIN32
  10. #include <shlobj.h> // Used in GetExeDirectory()
  11. #else
  12. #include <cstdlib> // Used in Get(Home/Data)Directory()
  13. #include <pwd.h> // Used in GetHomeDirectory()
  14. #include <sys/types.h> // Used in GetHomeDirectory()
  15. #include <unistd.h> // Used in GetDataDirectory()
  16. #endif
  17. #ifdef __APPLE__
  18. #include <sys/param.h> // Used in GetBundleDirectory()
  19. // CFURL contains __attribute__ directives that gcc does not know how to parse, so we need to just
  20. // ignore them if we're not using clang. The macro is only used to prevent linking against
  21. // functions that don't exist on older versions of macOS, and the worst case scenario is a linker
  22. // error, so this is perfectly safe, just inconvenient.
  23. #ifndef __clang__
  24. #define availability(...)
  25. #endif
  26. #include <CoreFoundation/CFBundle.h> // Used in GetBundleDirectory()
  27. #include <CoreFoundation/CFString.h> // Used in GetBundleDirectory()
  28. #include <CoreFoundation/CFURL.h> // Used in GetBundleDirectory()
  29. #ifdef availability
  30. #undef availability
  31. #endif
  32. #endif
  33. #ifndef MAX_PATH
  34. #ifdef _WIN32
  35. // This is the maximum number of UTF-16 code units permissible in Windows file paths
  36. #define MAX_PATH 260
  37. #else
  38. // This is the maximum number of UTF-8 code units permissible in all other OSes' file paths
  39. #define MAX_PATH 1024
  40. #endif
  41. #endif
  42. namespace Common::FS {
  43. namespace fs = std::filesystem;
  44. /**
  45. * The PathManagerImpl is a singleton allowing to manage the mapping of
  46. * YuzuPath enums to real filesystem paths.
  47. * This class provides 2 functions: GetYuzuPathImpl and SetYuzuPathImpl.
  48. * These are used by GetYuzuPath and SetYuzuPath respectively to get or modify
  49. * the path mapped by the YuzuPath enum.
  50. */
  51. class PathManagerImpl {
  52. public:
  53. static PathManagerImpl& GetInstance() {
  54. static PathManagerImpl path_manager_impl;
  55. return path_manager_impl;
  56. }
  57. PathManagerImpl(const PathManagerImpl&) = delete;
  58. PathManagerImpl& operator=(const PathManagerImpl&) = delete;
  59. PathManagerImpl(PathManagerImpl&&) = delete;
  60. PathManagerImpl& operator=(PathManagerImpl&&) = delete;
  61. [[nodiscard]] const fs::path& GetYuzuPathImpl(YuzuPath yuzu_path) {
  62. return yuzu_paths.at(yuzu_path);
  63. }
  64. void SetYuzuPathImpl(YuzuPath yuzu_path, const fs::path& new_path) {
  65. yuzu_paths.insert_or_assign(yuzu_path, new_path);
  66. }
  67. private:
  68. PathManagerImpl() {
  69. fs::path yuzu_path;
  70. fs::path yuzu_path_cache;
  71. fs::path yuzu_path_config;
  72. #ifdef _WIN32
  73. yuzu_path = GetExeDirectory() / PORTABLE_DIR;
  74. if (!IsDir(yuzu_path)) {
  75. yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR;
  76. }
  77. yuzu_path_cache = yuzu_path / CACHE_DIR;
  78. yuzu_path_config = yuzu_path / CONFIG_DIR;
  79. #else
  80. yuzu_path = GetCurrentDir() / PORTABLE_DIR;
  81. if (Exists(yuzu_path) && IsDir(yuzu_path)) {
  82. yuzu_path_cache = yuzu_path / CACHE_DIR;
  83. yuzu_path_config = yuzu_path / CONFIG_DIR;
  84. } else {
  85. yuzu_path = GetDataDirectory("XDG_DATA_HOME") / YUZU_DIR;
  86. yuzu_path_cache = GetDataDirectory("XDG_CACHE_HOME") / YUZU_DIR;
  87. yuzu_path_config = GetDataDirectory("XDG_CONFIG_HOME") / YUZU_DIR;
  88. }
  89. #endif
  90. GenerateYuzuPath(YuzuPath::YuzuDir, yuzu_path);
  91. GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache);
  92. GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config);
  93. GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR);
  94. GenerateYuzuPath(YuzuPath::KeysDir, yuzu_path / KEYS_DIR);
  95. GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
  96. GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR);
  97. GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR);
  98. GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
  99. GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
  100. GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
  101. GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
  102. }
  103. ~PathManagerImpl() = default;
  104. void GenerateYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) {
  105. void(FS::CreateDir(new_path));
  106. SetYuzuPathImpl(yuzu_path, new_path);
  107. }
  108. std::unordered_map<YuzuPath, fs::path> yuzu_paths;
  109. };
  110. bool ValidatePath(const fs::path& path) {
  111. if (path.empty()) {
  112. LOG_ERROR(Common_Filesystem, "Input path is empty, path={}", PathToUTF8String(path));
  113. return false;
  114. }
  115. #ifdef _WIN32
  116. if (path.u16string().size() >= MAX_PATH) {
  117. LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path));
  118. return false;
  119. }
  120. #else
  121. if (path.u8string().size() >= MAX_PATH) {
  122. LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path));
  123. return false;
  124. }
  125. #endif
  126. return true;
  127. }
  128. fs::path ConcatPath(const fs::path& first, const fs::path& second) {
  129. const bool second_has_dir_sep = IsDirSeparator(second.u8string().front());
  130. if (!second_has_dir_sep) {
  131. return (first / second).lexically_normal();
  132. }
  133. fs::path concat_path = first;
  134. concat_path += second;
  135. return concat_path.lexically_normal();
  136. }
  137. fs::path ConcatPathSafe(const fs::path& base, const fs::path& offset) {
  138. const auto concatenated_path = ConcatPath(base, offset);
  139. if (!IsPathSandboxed(base, concatenated_path)) {
  140. return base;
  141. }
  142. return concatenated_path;
  143. }
  144. bool IsPathSandboxed(const fs::path& base, const fs::path& path) {
  145. const auto base_string = RemoveTrailingSeparators(base.lexically_normal()).u8string();
  146. const auto path_string = RemoveTrailingSeparators(path.lexically_normal()).u8string();
  147. if (path_string.size() < base_string.size()) {
  148. return false;
  149. }
  150. return base_string.compare(0, base_string.size(), path_string, 0, base_string.size()) == 0;
  151. }
  152. bool IsDirSeparator(char character) {
  153. return character == '/' || character == '\\';
  154. }
  155. bool IsDirSeparator(char8_t character) {
  156. return character == u8'/' || character == u8'\\';
  157. }
  158. fs::path RemoveTrailingSeparators(const fs::path& path) {
  159. if (path.empty()) {
  160. return path;
  161. }
  162. auto string_path = path.u8string();
  163. while (IsDirSeparator(string_path.back())) {
  164. string_path.pop_back();
  165. }
  166. return fs::path{string_path};
  167. }
  168. const fs::path& GetYuzuPath(YuzuPath yuzu_path) {
  169. return PathManagerImpl::GetInstance().GetYuzuPathImpl(yuzu_path);
  170. }
  171. std::string GetYuzuPathString(YuzuPath yuzu_path) {
  172. return PathToUTF8String(GetYuzuPath(yuzu_path));
  173. }
  174. void SetYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) {
  175. if (!FS::IsDir(new_path)) {
  176. LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} is not a directory",
  177. PathToUTF8String(new_path));
  178. return;
  179. }
  180. PathManagerImpl::GetInstance().SetYuzuPathImpl(yuzu_path, new_path);
  181. }
  182. #ifdef _WIN32
  183. fs::path GetExeDirectory() {
  184. wchar_t exe_path[MAX_PATH];
  185. if (GetModuleFileNameW(nullptr, exe_path, MAX_PATH) == 0) {
  186. LOG_ERROR(Common_Filesystem,
  187. "Failed to get the path to the executable of the current process");
  188. }
  189. return fs::path{exe_path}.parent_path();
  190. }
  191. fs::path GetAppDataRoamingDirectory() {
  192. PWSTR appdata_roaming_path = nullptr;
  193. SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &appdata_roaming_path);
  194. auto fs_appdata_roaming_path = fs::path{appdata_roaming_path};
  195. CoTaskMemFree(appdata_roaming_path);
  196. if (fs_appdata_roaming_path.empty()) {
  197. LOG_ERROR(Common_Filesystem, "Failed to get the path to the %APPDATA% directory");
  198. }
  199. return fs_appdata_roaming_path;
  200. }
  201. #else
  202. fs::path GetHomeDirectory() {
  203. const char* home_env_var = getenv("HOME");
  204. if (home_env_var) {
  205. return fs::path{home_env_var};
  206. }
  207. LOG_INFO(Common_Filesystem,
  208. "$HOME is not defined in the environment variables, "
  209. "attempting to query passwd to get the home path of the current user");
  210. const auto* pw = getpwuid(getuid());
  211. if (!pw) {
  212. LOG_ERROR(Common_Filesystem, "Failed to get the home path of the current user");
  213. return {};
  214. }
  215. return fs::path{pw->pw_dir};
  216. }
  217. fs::path GetDataDirectory(const std::string& env_name) {
  218. const char* data_env_var = getenv(env_name.c_str());
  219. if (data_env_var) {
  220. return fs::path{data_env_var};
  221. }
  222. if (env_name == "XDG_DATA_HOME") {
  223. return GetHomeDirectory() / ".local/share";
  224. } else if (env_name == "XDG_CACHE_HOME") {
  225. return GetHomeDirectory() / ".cache";
  226. } else if (env_name == "XDG_CONFIG_HOME") {
  227. return GetHomeDirectory() / ".config";
  228. }
  229. return {};
  230. }
  231. #endif
  232. #ifdef __APPLE__
  233. fs::path GetBundleDirectory() {
  234. char app_bundle_path[MAXPATHLEN];
  235. // Get the main bundle for the app
  236. CFURLRef bundle_ref = CFBundleCopyBundleURL(CFBundleGetMainBundle());
  237. CFStringRef bundle_path = CFURLCopyFileSystemPath(bundle_ref, kCFURLPOSIXPathStyle);
  238. CFStringGetFileSystemRepresentation(bundle_path, app_bundle_path, sizeof(app_bundle_path));
  239. CFRelease(bundle_ref);
  240. CFRelease(bundle_path);
  241. return fs::path{app_bundle_path};
  242. }
  243. #endif
  244. // vvvvvvvvvv Deprecated vvvvvvvvvv //
  245. std::string_view RemoveTrailingSlash(std::string_view path) {
  246. if (path.empty()) {
  247. return path;
  248. }
  249. if (path.back() == '\\' || path.back() == '/') {
  250. path.remove_suffix(1);
  251. return path;
  252. }
  253. return path;
  254. }
  255. std::vector<std::string> SplitPathComponents(std::string_view filename) {
  256. std::string copy(filename);
  257. std::replace(copy.begin(), copy.end(), '\\', '/');
  258. std::vector<std::string> out;
  259. std::stringstream stream(copy);
  260. std::string item;
  261. while (std::getline(stream, item, '/')) {
  262. out.push_back(std::move(item));
  263. }
  264. return out;
  265. }
  266. std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) {
  267. std::string path(path_);
  268. char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\';
  269. char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/';
  270. if (directory_separator == DirectorySeparator::PlatformDefault) {
  271. #ifdef _WIN32
  272. type1 = '/';
  273. type2 = '\\';
  274. #endif
  275. }
  276. std::replace(path.begin(), path.end(), type1, type2);
  277. auto start = path.begin();
  278. #ifdef _WIN32
  279. // allow network paths which start with a double backslash (e.g. \\server\share)
  280. if (start != path.end())
  281. ++start;
  282. #endif
  283. path.erase(std::unique(start, path.end(),
  284. [type2](char c1, char c2) { return c1 == type2 && c2 == type2; }),
  285. path.end());
  286. return std::string(RemoveTrailingSlash(path));
  287. }
  288. std::string_view GetParentPath(std::string_view path) {
  289. const auto name_bck_index = path.rfind('\\');
  290. const auto name_fwd_index = path.rfind('/');
  291. std::size_t name_index;
  292. if (name_bck_index == std::string_view::npos || name_fwd_index == std::string_view::npos) {
  293. name_index = std::min(name_bck_index, name_fwd_index);
  294. } else {
  295. name_index = std::max(name_bck_index, name_fwd_index);
  296. }
  297. return path.substr(0, name_index);
  298. }
  299. std::string_view GetPathWithoutTop(std::string_view path) {
  300. if (path.empty()) {
  301. return path;
  302. }
  303. while (path[0] == '\\' || path[0] == '/') {
  304. path.remove_prefix(1);
  305. if (path.empty()) {
  306. return path;
  307. }
  308. }
  309. const auto name_bck_index = path.find('\\');
  310. const auto name_fwd_index = path.find('/');
  311. return path.substr(std::min(name_bck_index, name_fwd_index) + 1);
  312. }
  313. std::string_view GetFilename(std::string_view path) {
  314. const auto name_index = path.find_last_of("\\/");
  315. if (name_index == std::string_view::npos) {
  316. return {};
  317. }
  318. return path.substr(name_index + 1);
  319. }
  320. std::string_view GetExtensionFromFilename(std::string_view name) {
  321. const std::size_t index = name.rfind('.');
  322. if (index == std::string_view::npos) {
  323. return {};
  324. }
  325. return name.substr(index + 1);
  326. }
  327. } // namespace Common::FS