aboutsummaryrefslogtreecommitdiff
path: root/src/settings.h
blob: e01e86475b83c5d9a4dfe769ed09dc58dcef751c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#pragma once

#include "irrlichttypes_bloated.h"
#include "util/string.h"
#include <string>
#include <list>
#include <set>
#include <mutex>

class Settings;
struct NoiseParams;

// Global objects
extern Settings *g_settings;
extern std::string g_settings_path;

// Type for a settings changed callback function
typedef void (*SettingsChangedCallback)(const std::string &name, void *data);

typedef std::vector<
	std::pair<
		SettingsChangedCallback,
		void *
	>
> SettingsCallbackList;

typedef std::unordered_map<std::string, SettingsCallbackList> SettingsCallbackMap;

enum ValueType {
	VALUETYPE_STRING,
	VALUETYPE_FLAG // Doesn't take any arguments
};

enum SettingsParseEvent {
	SPE_NONE,
	SPE_INVALID,
	SPE_COMMENT,
	SPE_KVPAIR,
	SPE_END,
	SPE_GROUP,
	SPE_MULTILINE,
};

struct ValueSpec {
	ValueSpec(ValueType a_type, const char *a_help=NULL)
	{
		type = a_type;
		help = a_help;
	}

	ValueType type;
	const char *help;
};

struct SettingsEntry {
	SettingsEntry() = default;

	SettingsEntry(const std::string &value_) :
		value(value_)
	{}

	SettingsEntry(Settings *group_) :
		group(group_),
		is_group(true)
	{}

	std::string value = "";
	Settings *group = nullptr;
	bool is_group = false;
};

typedef std::unordered_map<std::string, SettingsEntry> SettingEntries;

class Settings {
public:
	Settings() = default;

	~Settings();

	Settings & operator += (const Settings &other);
	Settings & operator = (const Settings &other);

	/***********************
	 * Reading and writing *
	 ***********************/

	// Read configuration file.  Returns success.
	bool readConfigFile(const char *filename);
	//Updates configuration file.  Returns success.
	bool updateConfigFile(const char *filename);
	// NOTE: Types of allowed_options are ignored.  Returns success.
	bool parseCommandLine(int argc, char *argv[],
			std::map<std::string, ValueSpec> &allowed_options);
	bool parseConfigLines(std::istream &is, const std::string &end = "");
	void writeLines(std::ostream &os, u32 tab_depth=0) const;

	SettingsParseEvent parseConfigObject(const std::string &line,
		const std::string &end, std::string &name, std::string &value);
	bool updateConfigObject(std::istream &is, std::ostream &os,
		const std::string &end, u32 tab_depth=0);

	static bool checkNameValid(const std::string &name);
	static bool checkValueValid(const std::string &value);
	static std::string getMultiline(std::istream &is, size_t *num_lines=NULL);
	static void printEntry(std::ostream &os, const std::string &name,
		const SettingsEntry &entry, u32 tab_depth=0);

	/***********
	 * Getters *
	 ***********/

	const SettingsEntry &getEntry(const std::string &name) const;
	Settings *getGroup(const std::string &name) const;
	const std::string &get(const std::string &name) const;
	bool getBool(const std::string &name) const;
	u16 getU16(const std::string &name) const;
	s16 getS16(const std::string &name) const;
	u32 getU32(const std::string &name) const;
	s32 getS32(const std::string &name) const;
	u64 getU64(const std::string &name) const;
	float getFloat(const std::string &name) const;
	v2f getV2F(const std::string &name) const;
	v3f getV3F(const std::string &name) const;
	u32 getFlagStr(const std::string &name, const FlagDesc *flagdesc,
			u32 *flagmask) const;
	// N.B. if getStruct() is used to read a non-POD aggregate type,
	// the behavior is undefined.
	bool getStruct(const std::string &name, const std::string &format,
			void *out, size_t olen) const;
	bool getNoiseParams(const std::string &name, NoiseParams &np) const;
	bool getNoiseParamsFromValue(const std::string &name, NoiseParams &np) const;
	bool getNoiseParamsFromGroup(const std::string &name, NoiseParams &np) const;

	// return all keys used
	std::vector<std::string> getNames() const;
	bool exists(const std::string &name) const;


	/***************************************
	 * Getters that don't throw exceptions *
	 ***************************************/

	bool getEntryNoEx(const std::string &name, SettingsEntry &val) const;
	bool getGroupNoEx(const std::string &name, Settings *&val) const;
	bool getNoEx(const std::string &name, std::string &val) const;
	bool getFlag(const std::string &name) const;
	bool getU16NoEx(const std::string &name, u16 &val) const;
	bool getS16NoEx(const std::string &name, s16 &val) const;
	bool getS32NoEx(const std::string &name, s32 &val) const;
	bool getU64NoEx(const std::string &name, u64 &val) const;
	bool getFloatNoEx(const std::string &name, float &val) const;
	bool getV2FNoEx(const std::string &name, v2f &val) const;
	bool getV3FNoEx(const std::string &name, v3f &val) const;
	// N.B. getFlagStrNoEx() does not set val, but merely modifies it.  Thus,
	// val must be initialized before using getFlagStrNoEx().  The intention of
	// this is to simplify modifying a flags field from a default value.
	bool getFlagStrNoEx(const std::string &name, u32 &val, FlagDesc *flagdesc) const;


	/***********
	 * Setters *
	 ***********/

	// N.B. Groups not allocated with new must be set to NULL in the settings
	// tree before object destruction.
	bool setEntry(const std::string &name, const void *entry,
		bool set_group, bool set_default);
	bool set(const std::string &name, const std::string &value);
	bool setDefault(const std::string &name, const std::string &value);
	bool setGroup(const std::string &name, Settings *group);
	bool setGroupDefault(const std::string &name, Settings *group);
	bool setBool(const std::string &name, bool value);
	bool setS16(const std::string &name, s16 value);
	bool setU16(const std::string &name, u16 value);
	bool setS32(const std::string &name, s32 value);
	bool setU64(const std::string &name, u64 value);
	bool setFloat(const std::string &name, float value);
	bool setV2F(const std::string &name, v2f value);
	bool setV3F(const std::string &name, v3f value);
	bool setFlagStr(const std::string &name, u32 flags,
		const FlagDesc *flagdesc, u32 flagmask);
	bool setNoiseParams(const std::string &name, const NoiseParams &np,
		bool set_default=false);
	// N.B. if setStruct() is used to write a non-POD aggregate type,
	// the behavior is undefined.
	bool setStruct(const std::string &name, const std::string &format, void *value);

	// remove a setting
	bool remove(const std::string &name);
	void clear();
	void clearDefaults();
	void updateValue(const Settings &other, const std::string &name);
	void update(const Settings &other);

	void registerChangedCallback(const std::string &name,
		SettingsChangedCallback cbf, void *userdata = NULL);
	void deregisterChangedCallback(const std::string &name,
		SettingsChangedCallback cbf, void *userdata = NULL);

private:
	void updateNoLock(const Settings &other);
	void clearNoLock();
	void clearDefaultsNoLock();

	void doCallbacks(const std::string &name) const;

	SettingEntries m_settings;
	SettingEntries m_defaults;

	SettingsCallbackMap m_callbacks;

	mutable std::mutex m_callback_mutex;

	// All methods that access m_settings/m_defaults directly should lock this.
	mutable std::mutex m_mutex;

};
kwc">std::endl; if (!RemoveDirectory(path.c_str())) { errorstream << "Failed to recursively delete directory " << path << std::endl; return false; } return true; } bool DeleteSingleFileOrEmptyDirectory(const std::string &path) { DWORD attr = GetFileAttributes(path.c_str()); bool is_directory = (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)); if(!is_directory) { bool did = DeleteFile(path.c_str()); return did; } else { bool did = RemoveDirectory(path.c_str()); return did; } } std::string TempPath() { DWORD bufsize = GetTempPath(0, NULL); if(bufsize == 0){ errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl; return ""; } std::vector<char> buf(bufsize); DWORD len = GetTempPath(bufsize, &buf[0]); if(len == 0 || len > bufsize){ errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl; return ""; } return std::string(buf.begin(), buf.begin() + len); } #else // POSIX #include <sys/types.h> #include <dirent.h> #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> std::vector<DirListNode> GetDirListing(const std::string &pathstring) { std::vector<DirListNode> listing; DIR *dp; struct dirent *dirp; if((dp = opendir(pathstring.c_str())) == NULL) { //infostream<<"Error("<<errno<<") opening "<<pathstring<<std::endl; return listing; } while ((dirp = readdir(dp)) != NULL) { // NOTE: // Be very sure to not include '..' in the results, it will // result in an epic failure when deleting stuff. if(strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0) continue; DirListNode node; node.name = dirp->d_name; int isdir = -1; // -1 means unknown /* POSIX doesn't define d_type member of struct dirent and certain filesystems on glibc/Linux will only return DT_UNKNOWN for the d_type member. Also we don't know whether symlinks are directories or not. */ #ifdef _DIRENT_HAVE_D_TYPE if(dirp->d_type != DT_UNKNOWN && dirp->d_type != DT_LNK) isdir = (dirp->d_type == DT_DIR); #endif /* _DIRENT_HAVE_D_TYPE */ /* Was d_type DT_UNKNOWN, DT_LNK or nonexistent? If so, try stat(). */ if(isdir == -1) { struct stat statbuf{}; if (stat((pathstring + "/" + node.name).c_str(), &statbuf)) continue; isdir = ((statbuf.st_mode & S_IFDIR) == S_IFDIR); } node.dir = isdir; listing.push_back(node); } closedir(dp); return listing; } bool CreateDir(const std::string &path) { int r = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); if (r == 0) { return true; } // If already exists, return true if (errno == EEXIST) return true; return false; } bool PathExists(const std::string &path) { struct stat st{}; return (stat(path.c_str(),&st) == 0); } bool IsPathAbsolute(const std::string &path) { return path[0] == '/'; } bool IsDir(const std::string &path) { struct stat statbuf{}; if(stat(path.c_str(), &statbuf)) return false; // Actually error; but certainly not a directory return ((statbuf.st_mode & S_IFDIR) == S_IFDIR); } bool IsDirDelimiter(char c) { return c == '/'; } bool RecursiveDelete(const std::string &path) { /* Execute the 'rm' command directly, by fork() and execve() */ infostream<<"Removing \""<<path<<"\""<<std::endl; pid_t child_pid = fork(); if(child_pid == 0) { // Child const char *argv[4] = { #ifdef __ANDROID__ "/system/bin/rm", #else "/bin/rm", #endif "-rf", path.c_str(), NULL }; verbosestream<<"Executing '"<<argv[0]<<"' '"<<argv[1]<<"' '" <<argv[2]<<"'"<<std::endl; execv(argv[0], const_cast<char**>(argv)); // Execv shouldn't return. Failed. _exit(1); } else { // Parent int child_status; pid_t tpid; do{ tpid = wait(&child_status); }while(tpid != child_pid); return (child_status == 0); } } bool DeleteSingleFileOrEmptyDirectory(const std::string &path) { if (IsDir(path)) { bool did = (rmdir(path.c_str()) == 0); if (!did) errorstream << "rmdir errno: " << errno << ": " << strerror(errno) << std::endl; return did; } bool did = (unlink(path.c_str()) == 0); if (!did) errorstream << "unlink errno: " << errno << ": " << strerror(errno) << std::endl; return did; } std::string TempPath() { /* Should the environment variables TMPDIR, TMP and TEMP and the macro P_tmpdir (if defined by stdio.h) be checked before falling back on /tmp? Probably not, because this function is intended to be compatible with lua's os.tmpname which under the default configuration hardcodes mkstemp("/tmp/lua_XXXXXX"). */ #ifdef __ANDROID__ return porting::path_cache; #else return DIR_DELIM "tmp"; #endif } #endif void GetRecursiveDirs(std::vector<std::string> &dirs, const std::string &dir) { static const std::set<char> chars_to_ignore = { '_', '.' }; if (dir.empty() || !IsDir(dir)) return; dirs.push_back(dir); fs::GetRecursiveSubPaths(dir, dirs, false, chars_to_ignore); } std::vector<std::string> GetRecursiveDirs(const std::string &dir) { std::vector<std::string> result; GetRecursiveDirs(result, dir); return result; } void GetRecursiveSubPaths(const std::string &path, std::vector<std::string> &dst, bool list_files, const std::set<char> &ignore) { std::vector<DirListNode> content = GetDirListing(path); for (const auto &n : content) { std::string fullpath = path + DIR_DELIM + n.name; if (ignore.count(n.name[0])) continue; if (list_files || n.dir) dst.push_back(fullpath); if (n.dir) GetRecursiveSubPaths(fullpath, dst, list_files, ignore); } } bool RecursiveDeleteContent(const std::string &path) { infostream<<"Removing content of \""<<path<<"\""<<std::endl; std::vector<DirListNode> list = GetDirListing(path); for (const DirListNode &dln : list) { if(trim(dln.name) == "." || trim(dln.name) == "..") continue; std::string childpath = path + DIR_DELIM + dln.name; bool r = RecursiveDelete(childpath); if(!r) { errorstream << "Removing \"" << childpath << "\" failed" << std::endl; return false; } } return true; } bool CreateAllDirs(const std::string &path) { std::vector<std::string> tocreate; std::string basepath = path; while(!PathExists(basepath)) { tocreate.push_back(basepath); basepath = RemoveLastPathComponent(basepath); if(basepath.empty()) break; } for(int i=tocreate.size()-1;i>=0;i--) if(!CreateDir(tocreate[i])) return false; return true; } bool CopyFileContents(const std::string &source, const std::string &target) { FILE *sourcefile = fopen(source.c_str(), "rb"); if(sourcefile == NULL){ errorstream<<source<<": can't open for reading: " <<strerror(errno)<<std::endl; return false; } FILE *targetfile = fopen(target.c_str(), "wb"); if(targetfile == NULL){ errorstream<<target<<": can't open for writing: " <<strerror(errno)<<std::endl; fclose(sourcefile); return false; } size_t total = 0; bool retval = true; bool done = false; char readbuffer[BUFSIZ]; while(!done){ size_t readbytes = fread(readbuffer, 1, sizeof(readbuffer), sourcefile); total += readbytes; if(ferror(sourcefile)){ errorstream<<source<<": IO error: " <<strerror(errno)<<std::endl; retval = false; done = true; } if(readbytes > 0){ fwrite(readbuffer, 1, readbytes, targetfile); } if(feof(sourcefile) || ferror(sourcefile)){ // flush destination file to catch write errors // (e.g. disk full) fflush(targetfile); done = true; } if(ferror(targetfile)){ errorstream<<target<<": IO error: " <<strerror(errno)<<std::endl; retval = false; done = true; } } infostream<<"copied "<<total<<" bytes from " <<source<<" to "<<target<<std::endl; fclose(sourcefile); fclose(targetfile); return retval; } bool CopyDir(const std::string &source, const std::string &target) { if(PathExists(source)){ if(!PathExists(target)){ fs::CreateAllDirs(target); } bool retval = true; std::vector<DirListNode> content = fs::GetDirListing(source); for (const auto &dln : content) { std::string sourcechild = source + DIR_DELIM + dln.name; std::string targetchild = target + DIR_DELIM + dln.name; if(dln.dir){ if(!fs::CopyDir(sourcechild, targetchild)){ retval = false; } } else { if(!fs::CopyFileContents(sourcechild, targetchild)){ retval = false; } } } return retval; } return false; } bool PathStartsWith(const std::string &path, const std::string &prefix) { size_t pathsize = path.size(); size_t pathpos = 0; size_t prefixsize = prefix.size(); size_t prefixpos = 0; for(;;){ bool delim1 = pathpos == pathsize || IsDirDelimiter(path[pathpos]); bool delim2 = prefixpos == prefixsize || IsDirDelimiter(prefix[prefixpos]); if(delim1 != delim2) return false; if(delim1){ while(pathpos < pathsize && IsDirDelimiter(path[pathpos])) ++pathpos; while(prefixpos < prefixsize && IsDirDelimiter(prefix[prefixpos])) ++prefixpos; if(prefixpos == prefixsize) return true; if(pathpos == pathsize) return false; } else{ size_t len = 0; do{ char pathchar = path[pathpos+len]; char prefixchar = prefix[prefixpos+len]; if(FILESYS_CASE_INSENSITIVE){ pathchar = tolower(pathchar); prefixchar = tolower(prefixchar); } if(pathchar != prefixchar) return false; ++len; } while(pathpos+len < pathsize && !IsDirDelimiter(path[pathpos+len]) && prefixpos+len < prefixsize && !IsDirDelimiter( prefix[prefixpos+len])); pathpos += len; prefixpos += len; } } } std::string RemoveLastPathComponent(const std::string &path, std::string *removed, int count) { if(removed) *removed = ""; size_t remaining = path.size(); for(int i = 0; i < count; ++i){ // strip a dir delimiter while(remaining != 0 && IsDirDelimiter(path[remaining-1])) remaining--; // strip a path component size_t component_end = remaining; while(remaining != 0 && !IsDirDelimiter(path[remaining-1])) remaining--; size_t component_start = remaining; // strip a dir delimiter while(remaining != 0 && IsDirDelimiter(path[remaining-1])) remaining--; if(removed){ std::string component = path.substr(component_start, component_end - component_start); if(i) *removed = component + DIR_DELIM + *removed; else *removed = component; } } return path.substr(0, remaining); } std::string RemoveRelativePathComponents(std::string path) { size_t pos = path.size(); size_t dotdot_count = 0; while (pos != 0) { size_t component_with_delim_end = pos; // skip a dir delimiter while (pos != 0 && IsDirDelimiter(path[pos-1])) pos--; // strip a path component size_t component_end = pos; while (pos != 0 && !IsDirDelimiter(path[pos-1])) pos--; size_t component_start = pos; std::string component = path.substr(component_start, component_end - component_start); bool remove_this_component = false; if (component == ".") { remove_this_component = true; } else if (component == "..") { remove_this_component = true; dotdot_count += 1; } else if (dotdot_count != 0) { remove_this_component = true; dotdot_count -= 1; } if (remove_this_component) { while (pos != 0 && IsDirDelimiter(path[pos-1])) pos--; if (component_start == 0) { // We need to remove the delemiter too path = path.substr(component_with_delim_end, std::string::npos); } else { path = path.substr(0, pos) + DIR_DELIM + path.substr(component_with_delim_end, std::string::npos); } if (pos > 0) pos++; } } if (dotdot_count > 0) return ""; // remove trailing dir delimiters pos = path.size(); while (pos != 0 && IsDirDelimiter(path[pos-1])) pos--; return path.substr(0, pos); } std::string AbsolutePath(const std::string &path) { #ifdef _WIN32 char *abs_path = _fullpath(NULL, path.c_str(), MAX_PATH); #else char *abs_path = realpath(path.c_str(), NULL); #endif if (!abs_path) return ""; std::string abs_path_str(abs_path); free(abs_path); return abs_path_str; } const char *GetFilenameFromPath(const char *path) { const char *filename = strrchr(path, DIR_DELIM_CHAR); // Consistent with IsDirDelimiter this function handles '/' too if (DIR_DELIM_CHAR != '/') { const char *tmp = strrchr(path, '/'); if (tmp && tmp > filename) filename = tmp; } return filename ? filename + 1 : path; } bool safeWriteToFile(const std::string &path, const std::string &content) { std::string tmp_file = path + ".~mt"; // Write to a tmp file std::ofstream os(tmp_file.c_str(), std::ios::binary); if (!os.good()) return false; os << content; os.flush(); os.close(); if (os.fail()) { // Remove the temporary file because writing it failed and it's useless. remove(tmp_file.c_str()); return false; } bool rename_success = false; // Move the finished temporary file over the real file #ifdef _WIN32 // When creating the file, it can cause Windows Search indexer, virus scanners and other apps // to query the file. This can make the move file call below fail. // We retry up to 5 times, with a 1ms sleep between, before we consider the whole operation failed int number_attempts = 0; while (number_attempts < 5) { rename_success = MoveFileEx(tmp_file.c_str(), path.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH); if (rename_success) break; sleep_ms(1); ++number_attempts; } #else // On POSIX compliant systems rename() is specified to be able to swap the // file in place of the destination file, making this a truly error-proof // transaction. rename_success = rename(tmp_file.c_str(), path.c_str()) == 0; #endif if (!rename_success) { warningstream << "Failed to write to file: " << path.c_str() << std::endl; // Remove the temporary file because moving it over the target file // failed. remove(tmp_file.c_str()); return false; } return true; } bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string &destination) { if (!fs->addFileArchive(filename, false, false, io::EFAT_ZIP)) { return false; } sanity_check(fs->getFileArchiveCount() > 0); /**********************************************************************/ /* WARNING this is not threadsafe!! */ /**********************************************************************/ io::IFileArchive* opened_zip = fs->getFileArchive(fs->getFileArchiveCount() - 1); const io::IFileList* files_in_zip = opened_zip->getFileList(); unsigned int number_of_files = files_in_zip->getFileCount(); for (unsigned int i=0; i < number_of_files; i++) { std::string fullpath = destination; fullpath += DIR_DELIM; fullpath += files_in_zip->getFullFileName(i).c_str(); std::string fullpath_dir = fs::RemoveLastPathComponent(fullpath); if (!files_in_zip->isDirectory(i)) { if (!fs::PathExists(fullpath_dir) && !fs::CreateAllDirs(fullpath_dir)) { fs->removeFileArchive(fs->getFileArchiveCount()-1); return false; } io::IReadFile* toread = opened_zip->createAndOpenFile(i); FILE *targetfile = fopen(fullpath.c_str(),"wb"); if (targetfile == NULL) { fs->removeFileArchive(fs->getFileArchiveCount()-1); return false; } char read_buffer[1024]; long total_read = 0; while (total_read < toread->getSize()) { unsigned int bytes_read = toread->read(read_buffer,sizeof(read_buffer)); if ((bytes_read == 0 ) || (fwrite(read_buffer, 1, bytes_read, targetfile) != bytes_read)) { fclose(targetfile); fs->removeFileArchive(fs->getFileArchiveCount() - 1); return false; } total_read += bytes_read; } fclose(targetfile); } } fs->removeFileArchive(fs->getFileArchiveCount() - 1); return true; } bool ReadFile(const std::string &path, std::string &out) { std::ifstream is(path, std::ios::binary | std::ios::ate); if (!is.good()) { return false; } auto size = is.tellg(); out.resize(size); is.seekg(0); is.read(&out[0], size); return true; } bool Rename(const std::string &from, const std::string &to) { return rename(from.c_str(), to.c_str()) == 0; } } // namespace fs