FS.cpp
1 /* 2 * Copyright (c) 2013-2025, The PurpleI2P Project 3 * 4 * This file is part of Purple i2pd project and licensed under BSD3 5 * 6 * See full license text in LICENSE file at top of project tree 7 */ 8 9 #include <algorithm> 10 11 #if defined(MAC_OSX) 12 #if !STD_FILESYSTEM 13 #include <boost/system/system_error.hpp> 14 #endif 15 #include <TargetConditionals.h> 16 #endif 17 18 #if defined(__HAIKU__) 19 #include <FindDirectory.h> 20 #endif 21 22 #ifdef _WIN32 23 #include <shlobj.h> 24 #include <windows.h> 25 #include <codecvt> 26 #endif 27 28 #include "Base.h" 29 #include "FS.h" 30 #include "Log.h" 31 #include "Garlic.h" 32 33 #if STD_FILESYSTEM 34 #include <filesystem> 35 namespace fs_lib = std::filesystem; 36 #else 37 #include <boost/filesystem.hpp> 38 namespace fs_lib = boost::filesystem; 39 #endif 40 41 namespace i2p { 42 namespace fs { 43 std::string appName = "i2pd"; 44 std::string dataDir = ""; 45 std::string certsDir = ""; 46 #ifdef _WIN32 47 std::string dirSep = "\\"; 48 #else 49 std::string dirSep = "/"; 50 #endif 51 52 const std::string & GetAppName () { 53 return appName; 54 } 55 56 void SetAppName (const std::string& name) { 57 appName = name; 58 } 59 60 const std::string & GetDataDir () { 61 return dataDir; 62 } 63 64 const std::string & GetCertsDir () { 65 return certsDir; 66 } 67 68 const std::string GetUTF8DataDir () { 69 #ifdef _WIN32 70 int size = MultiByteToWideChar(CP_ACP, 0, 71 dataDir.c_str(), dataDir.size(), nullptr, 0); 72 std::wstring utf16Str(size, L'\0'); 73 MultiByteToWideChar(CP_ACP, 0, 74 dataDir.c_str(), dataDir.size(), &utf16Str[0], size); 75 int utf8Size = WideCharToMultiByte(CP_UTF8, 0, 76 utf16Str.c_str(), utf16Str.size(), nullptr, 0, nullptr, nullptr); 77 std::string utf8Str(utf8Size, '\0'); 78 WideCharToMultiByte(CP_UTF8, 0, 79 utf16Str.c_str(), utf16Str.size(), &utf8Str[0], utf8Size, nullptr, nullptr); 80 return utf8Str; 81 #else 82 return dataDir; // linux, osx, android uses UTF-8 by default 83 #endif 84 } 85 86 void DetectDataDir(const std::string & cmdline_param, bool isService) { 87 // with 'datadir' option 88 if (cmdline_param != "") { 89 dataDir = cmdline_param; 90 return; 91 } 92 93 #if !defined(MAC_OSX) && !defined(ANDROID) 94 // with 'service' option 95 if (isService) { 96 #ifdef _WIN32 97 wchar_t commonAppData[MAX_PATH]; 98 if(SHGetFolderPathW(NULL, CSIDL_COMMON_APPDATA, NULL, 0, commonAppData) != S_OK) 99 { 100 #ifdef WIN32_APP 101 MessageBox(NULL, TEXT("Unable to get common AppData path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); 102 #else 103 fprintf(stderr, "Error: Unable to get common AppData path!"); 104 #endif 105 exit(1); 106 } 107 else 108 { 109 #if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM) 110 dataDir = fs_lib::path(commonAppData).string() + "\\" + appName; 111 #else 112 dataDir = fs_lib::wpath(commonAppData).string() + "\\" + appName; 113 #endif 114 } 115 #else 116 dataDir = "/var/lib/" + appName; 117 #endif 118 return; 119 } 120 #endif 121 122 // detect directory as usual 123 #ifdef _WIN32 124 wchar_t localAppData[MAX_PATH]; 125 126 // check executable directory first 127 if(!GetModuleFileNameW(NULL, localAppData, MAX_PATH)) 128 { 129 #ifdef WIN32_APP 130 MessageBox(NULL, TEXT("Unable to get application path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); 131 #else 132 fprintf(stderr, "Error: Unable to get application path!"); 133 #endif 134 exit(1); 135 } 136 else 137 { 138 #if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM) 139 auto execPath = fs_lib::path(localAppData).parent_path(); 140 #else 141 auto execPath = fs_lib::wpath(localAppData).parent_path(); 142 #endif 143 144 // if config file exists in .exe's folder use it 145 if(fs_lib::exists(execPath/"i2pd.conf")) // TODO: magic string 146 { 147 dataDir = execPath.string (); 148 } else // otherwise %appdata% 149 { 150 if(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, localAppData) != S_OK) 151 { 152 #ifdef WIN32_APP 153 MessageBox(NULL, TEXT("Unable to get AppData path!"), TEXT("I2Pd: error"), MB_ICONERROR | MB_OK); 154 #else 155 fprintf(stderr, "Error: Unable to get AppData path!"); 156 #endif 157 exit(1); 158 } 159 else 160 { 161 #if ((BOOST_VERSION >= 108500) || STD_FILESYSTEM) 162 dataDir = fs_lib::path(localAppData).string() + "\\" + appName; 163 #else 164 dataDir = fs_lib::wpath(localAppData).string() + "\\" + appName; 165 #endif 166 } 167 } 168 } 169 return; 170 #elif defined(MAC_OSX) 171 char *home = getenv("HOME"); 172 dataDir = (home != NULL && strlen(home) > 0) ? home : ""; 173 dataDir += "/Library/Application Support/" + appName; 174 return; 175 #elif defined(__HAIKU__) 176 char home[PATH_MAX]; // /boot/home/config/settings 177 if (find_directory(B_USER_SETTINGS_DIRECTORY, -1, false, home, PATH_MAX) == B_OK) 178 dataDir = std::string(home) + "/" + appName; 179 else 180 dataDir = "/tmp/" + appName; 181 return; 182 #else /* other unix */ 183 #if defined(ANDROID) 184 const char * ext = getenv("EXTERNAL_STORAGE"); 185 if (!ext) ext = "/sdcard"; 186 if (fs_lib::exists(ext)) 187 { 188 dataDir = std::string (ext) + "/" + appName; 189 return; 190 } 191 #endif // ANDROID 192 // use /home/user/.i2pd or /tmp/i2pd 193 char *home = getenv("HOME"); 194 if (home != NULL && strlen(home) > 0) { 195 dataDir = std::string(home) + "/." + appName; 196 } else { 197 dataDir = "/tmp/" + appName; 198 } 199 return; 200 #endif 201 } 202 203 void SetCertsDir(const std::string & cmdline_certsdir) { 204 if (cmdline_certsdir != "") 205 { 206 if (cmdline_certsdir[cmdline_certsdir.length()-1] == '/') 207 certsDir = cmdline_certsdir.substr(0, cmdline_certsdir.size()-1); // strip trailing slash 208 else 209 certsDir = cmdline_certsdir; 210 } 211 else 212 { 213 certsDir = i2p::fs::DataDirPath("certificates"); 214 } 215 return; 216 } 217 218 bool Init() { 219 if (!fs_lib::exists(dataDir)) 220 fs_lib::create_directory(dataDir); 221 222 std::string destinations = DataDirPath("destinations"); 223 if (!fs_lib::exists(destinations)) 224 fs_lib::create_directory(destinations); 225 226 std::string tags = DataDirPath("tags"); 227 if (!fs_lib::exists(tags)) 228 fs_lib::create_directory(tags); 229 else 230 i2p::garlic::CleanUpTagsFiles (); 231 232 return true; 233 } 234 235 bool ReadDir(const std::string & path, std::vector<std::string> & files) { 236 if (!fs_lib::exists(path)) 237 return false; 238 fs_lib::directory_iterator it(path); 239 fs_lib::directory_iterator end; 240 241 for ( ; it != end; it++) { 242 if (!fs_lib::is_regular_file(it->status())) 243 continue; 244 files.push_back(it->path().string()); 245 } 246 247 return true; 248 } 249 250 bool Exists(const std::string & path) { 251 return fs_lib::exists(path); 252 } 253 254 uint32_t GetLastUpdateTime (const std::string & path) 255 { 256 if (!fs_lib::exists(path)) 257 return 0; 258 #if STD_FILESYSTEM 259 std::error_code ec; 260 auto t = std::filesystem::last_write_time (path, ec); 261 if (ec) return 0; 262 /*#if __cplusplus >= 202002L // C++ 20 or higher 263 const auto sctp = std::chrono::clock_cast<std::chrono::system_clock>(t); 264 #else */ // TODO: wait until implemented 265 const auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>( 266 t - decltype(t)::clock::now() + std::chrono::system_clock::now()); 267 /*#endif */ 268 return std::chrono::system_clock::to_time_t(sctp); 269 #else 270 boost::system::error_code ec; 271 auto t = boost::filesystem::last_write_time (path, ec); 272 return ec ? 0 : t; 273 #endif 274 } 275 276 bool Remove(const std::string & path) { 277 if (!fs_lib::exists(path)) 278 return false; 279 return fs_lib::remove(path); 280 } 281 282 bool CreateDirectory (const std::string& path) 283 { 284 if (fs_lib::exists(path) && fs_lib::is_directory (fs_lib::status (path))) 285 return true; 286 return fs_lib::create_directory(path); 287 } 288 289 void HashedStorage::SetPlace(const std::string &path) { 290 root = path + i2p::fs::dirSep + name; 291 } 292 293 bool HashedStorage::Init(const char * chars, size_t count) { 294 if (!fs_lib::exists(root)) { 295 fs_lib::create_directories(root); 296 } 297 298 for (size_t i = 0; i < count; i++) { 299 auto p = root + i2p::fs::dirSep + prefix1 + chars[i]; 300 if (fs_lib::exists(p)) 301 continue; 302 #if TARGET_OS_SIMULATOR 303 // ios simulator fs says it is case sensitive, but it is not 304 boost::system::error_code ec; 305 if (fs_lib::create_directory(p, ec)) 306 continue; 307 switch (ec.value()) { 308 case boost::system::errc::file_exists: 309 case boost::system::errc::success: 310 continue; 311 default: 312 throw boost::system::system_error( ec, __func__ ); 313 } 314 #else 315 if (fs_lib::create_directory(p)) 316 continue; /* ^ throws exception on failure */ 317 #endif 318 return false; 319 } 320 return true; 321 } 322 323 std::string HashedStorage::Path(const std::string & ident) const { 324 std::string safe_ident = ident; 325 std::replace(safe_ident.begin(), safe_ident.end(), '/', '-'); 326 std::replace(safe_ident.begin(), safe_ident.end(), '\\', '-'); 327 328 std::stringstream t(""); 329 t << this->root << i2p::fs::dirSep; 330 t << prefix1 << safe_ident[0] << i2p::fs::dirSep; 331 t << prefix2 << safe_ident << "." << suffix; 332 333 return t.str(); 334 } 335 336 void HashedStorage::Remove(const std::string & ident) { 337 std::string path = Path(ident); 338 if (!fs_lib::exists(path)) 339 return; 340 fs_lib::remove(path); 341 } 342 343 void HashedStorage::Traverse(std::vector<std::string> & files) { 344 Iterate([&files] (const std::string & fname) { 345 files.push_back(fname); 346 }); 347 } 348 349 void HashedStorage::Iterate(FilenameVisitor v) 350 { 351 fs_lib::path p(root); 352 fs_lib::recursive_directory_iterator it(p); 353 fs_lib::recursive_directory_iterator end; 354 355 for ( ; it != end; it++) { 356 if (!fs_lib::is_regular_file( it->status() )) 357 continue; 358 const std::string & t = it->path().string(); 359 v(t); 360 } 361 } 362 } // fs 363 } // i2p