/ libi2pd / FS.cpp
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