1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 #if defined(_MSC_VER) && !defined(S_ISDIR)
4 #	define S_ISDIR(m) (((m) & 0170000) == 0040000)
5 #endif
6 
7 #include "FileSystemAbstraction.h"
8 
9 #include "FileQueryFlags.h"
10 
11 #include "System/Util.h"
12 #include "System/Log/ILog.h"
13 #include "System/Exceptions.h"
14 
15 #include <cassert>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #include <errno.h>
19 #include <string.h>
20 #include <boost/regex.hpp>
21 #include <boost/filesystem.hpp>
22 
23 #ifndef _WIN32
24 	#include <dirent.h>
25 	#include <sstream>
26 	#include <unistd.h>
27 	#include <time.h>
28 #else
29 	#include <windows.h>
30 	#include <io.h>
31 	#include <direct.h>
32 	#include <fstream>
33 	// Win-API redifines these, which breaks things
34 	#if defined(CreateDirectory)
35 		#undef CreateDirectory
36 	#endif
37 	#if defined(DeleteFile)
38 		#undef DeleteFile
39 	#endif
40 #endif
41 
42 
43 
RemoveLocalPathPrefix(const std::string & path)44 std::string FileSystemAbstraction::RemoveLocalPathPrefix(const std::string& path)
45 {
46 	std::string p(path);
47 
48 	if ((p.length() >= 2) && (p[0] == '.') && IsPathSeparator(p[1])) {
49 	    p.erase(0, 2);
50 	}
51 
52 	return p;
53 }
54 
IsFSRoot(const std::string & p)55 bool FileSystemAbstraction::IsFSRoot(const std::string& p)
56 {
57 
58 #ifdef WIN32
59 	// examples: "C:\", "C:/", "C:", "c:", "D:"
60 	bool isFsRoot = (p.length() >= 2 && p[1] == ':' &&
61 			((p[0] >= 'a' && p[0] <= 'z') || (p[0] >= 'A' && p[0] <= 'Z')) &&
62 			(p.length() == 2 || (p.length() == 3 && IsPathSeparator(p[2]))));
63 #else
64 	// examples: "/"
65 	bool isFsRoot = (p.length() == 1 && IsNativePathSeparator(p[0]));
66 #endif
67 
68 	return isFsRoot;
69 }
70 
IsPathSeparator(char aChar)71 bool FileSystemAbstraction::IsPathSeparator(char aChar) {
72 	return ((aChar == cPS_WIN32) || (aChar == cPS_POSIX));
73 }
74 
IsNativePathSeparator(char aChar)75 bool FileSystemAbstraction::IsNativePathSeparator(char aChar) {
76 	return (aChar == cPS);
77 }
78 
HasPathSepAtEnd(const std::string & path)79 bool FileSystemAbstraction::HasPathSepAtEnd(const std::string& path) {
80 
81 	bool pathSepAtEnd = false;
82 
83 	if (!path.empty()) {
84 		pathSepAtEnd = IsNativePathSeparator(path.at(path.size() - 1));
85 	}
86 
87 	return pathSepAtEnd;
88 }
89 
EnsurePathSepAtEnd(std::string & path)90 void FileSystemAbstraction::EnsurePathSepAtEnd(std::string& path) {
91 
92 	if (path.empty()) {
93 		path += "." sPS;
94 	} else if (!HasPathSepAtEnd(path)) {
95 		path += cPS;
96 	}
97 }
EnsurePathSepAtEnd(const std::string & path)98 std::string FileSystemAbstraction::EnsurePathSepAtEnd(const std::string& path) {
99 
100 	std::string pathCopy(path);
101 	EnsurePathSepAtEnd(pathCopy);
102 	return pathCopy;
103 }
104 
EnsureNoPathSepAtEnd(std::string & path)105 void FileSystemAbstraction::EnsureNoPathSepAtEnd(std::string& path) {
106 
107 	if (HasPathSepAtEnd(path)) {
108 		path.resize(path.size() - 1);
109 	}
110 }
EnsureNoPathSepAtEnd(const std::string & path)111 std::string FileSystemAbstraction::EnsureNoPathSepAtEnd(const std::string& path) {
112 
113 	std::string pathCopy(path);
114 	EnsureNoPathSepAtEnd(pathCopy);
115 	return pathCopy;
116 }
117 
StripTrailingSlashes(const std::string & path)118 std::string FileSystemAbstraction::StripTrailingSlashes(const std::string& path)
119 {
120 	size_t len = path.length();
121 
122 	while (len > 0) {
123 		if (IsPathSeparator(path.at(len - 1))) {
124 			--len;
125 		} else {
126 			break;
127 		}
128 	}
129 
130 	return path.substr(0, len);
131 }
132 
GetParent(const std::string & path)133 std::string FileSystemAbstraction::GetParent(const std::string& path)
134 {
135 	//TODO uncomment and test if it breaks other code
136 	/*try {
137 		const boost::filesystem::path p(path);
138 		return p.parent_path.string();
139 	} catch (const boost::filesystem::filesystem_error& ex) {
140 		return "";
141 	}*/
142 
143 	std::string parent = path;
144 	EnsureNoPathSepAtEnd(parent);
145 
146 	static const char* PATH_SEP_REGEX = sPS_POSIX sPS_WIN32;
147 	const std::string::size_type slashPos = parent.find_last_of(PATH_SEP_REGEX);
148 	if (slashPos == std::string::npos) {
149 		parent = "";
150 	} else {
151 		parent.resize(slashPos + 1);
152 	}
153 
154 	return parent;
155 }
156 
GetFileSize(const std::string & file)157 size_t FileSystemAbstraction::GetFileSize(const std::string& file)
158 {
159 	size_t fileSize = -1;
160 
161 	struct stat info;
162 	if ((stat(file.c_str(), &info) == 0) && (!S_ISDIR(info.st_mode))) {
163 		fileSize = info.st_size;
164 	}
165 
166 	return fileSize;
167 }
168 
IsReadableFile(const std::string & file)169 bool FileSystemAbstraction::IsReadableFile(const std::string& file)
170 {
171 	// Exclude directories!
172 	if (!FileExists(file)) {
173 		return false;
174 	}
175 
176 #ifdef WIN32
177 	return (_access(StripTrailingSlashes(file).c_str(), 4) == 0);
178 #else
179 	return (access(file.c_str(), R_OK | F_OK) == 0);
180 #endif
181 }
182 
GetFileModificationDate(const std::string & file)183 std::string FileSystemAbstraction::GetFileModificationDate(const std::string& file)
184 {
185 	try {
186 		const boost::filesystem::path f(file);
187 		const std::time_t t = boost::filesystem::last_write_time(f);
188 		struct tm* clock = std::gmtime(&t);
189 
190 		const size_t cTime_size = 20;
191 		char cTime[cTime_size];
192 		SNPRINTF(cTime, cTime_size, "%d%02d%02d%02d%02d%02d", 1900+clock->tm_year, clock->tm_mon, clock->tm_mday, clock->tm_hour, clock->tm_min, clock->tm_sec);
193 		return cTime;
194 	} catch (const boost::filesystem::filesystem_error& ex) {
195 		LOG_L(L_WARNING, "Failed fetching last modification time from file: %s", file.c_str());
196 		LOG_L(L_WARNING, "Error is: \"%s\"", ex.what());
197 		return "";
198 	}
199 }
200 
201 
GetNativePathSeparator()202 char FileSystemAbstraction::GetNativePathSeparator()
203 {
204 	#ifndef _WIN32
205 	return '/';
206 	#else
207 	return '\\';
208 	#endif
209 }
210 
IsAbsolutePath(const std::string & path)211 bool FileSystemAbstraction::IsAbsolutePath(const std::string& path)
212 {
213 	//TODO uncomment this and test if there are conflicts in the code when this returns true but other custom code doesn't (e.g. with IsFSRoot)
214 	//const boost::filesystem::path f(file);
215 	//return f.is_absolute();
216 
217 #ifdef WIN32
218 	return ((path.length() > 1) && (path[1] == ':'));
219 #else
220 	return ((path.length() > 0) && (path[0] == '/'));
221 #endif
222 }
223 
224 
225 /**
226  * @brief creates a rwxr-xr-x dir in the writedir
227  *
228  * Returns true if the postcondition of this function is that dir exists in
229  * the write directory.
230  *
231  * Note that this function does not check access to the dir, ie. if you've
232  * created it manually with 0000 permissions then this function may return
233  * true, subsequent operation on files inside the directory may still fail.
234  *
235  * As a rule of thumb, set identical permissions on identical items in the
236  * data directory, ie. all subdirectories the same perms, all files the same
237  * perms.
238  */
MkDir(const std::string & dir)239 bool FileSystemAbstraction::MkDir(const std::string& dir)
240 {
241 	// First check if directory exists. We'll return success if it does.
242 	if (DirExists(dir)) {
243 		return true;
244 	}
245 
246 
247 	// If it doesn't exist we try to mkdir it and return success if that succeeds.
248 #ifndef _WIN32
249 	bool dirCreated = (::mkdir(dir.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0);
250 #else
251 	bool dirCreated = (::_mkdir(StripTrailingSlashes(dir).c_str()) == 0);
252 #endif
253 
254 	if (!dirCreated) {
255 		LOG_L(L_WARNING, "Could not create directory %s: %s", dir.c_str(), strerror(errno));
256 	}
257 
258 	return dirCreated;
259 }
260 
DeleteFile(const std::string & file)261 bool FileSystemAbstraction::DeleteFile(const std::string& file)
262 {
263 	try {
264 		const boost::filesystem::path f(file);
265 		// same as posix remove(), but on windows that func doesn't allow deletion of dirs
266 		// so it's easier to use boost from the start
267 		return (boost::filesystem::remove_all(f) >= 1);
268 	} catch (const boost::filesystem::filesystem_error& ex) {
269 		LOG_L(L_WARNING, "Could not delete file %s: %s", file.c_str(), ex.what());
270 		return false;
271 	}
272 }
273 
FileExists(const std::string & file)274 bool FileSystemAbstraction::FileExists(const std::string& file)
275 {
276 	struct stat info;
277 	const int ret = stat(file.c_str(), &info);
278 	bool fileExists = ((ret == 0 && !S_ISDIR(info.st_mode)));
279 	return fileExists;
280 }
281 
DirExists(const std::string & dir)282 bool FileSystemAbstraction::DirExists(const std::string& dir)
283 {
284 	try {
285 		const boost::filesystem::path p(dir);
286 		return boost::filesystem::exists(p) && boost::filesystem::is_directory(p);
287 	} catch (const boost::filesystem::filesystem_error& ex) {
288 		return false;
289 	}
290 }
291 
292 
DirIsWritable(const std::string & dir)293 bool FileSystemAbstraction::DirIsWritable(const std::string& dir)
294 {
295 #ifdef _WIN32
296 	// this exists because _access does not do the right thing
297 	// see http://msdn.microsoft.com/en-us/library/1w06ktdy(VS.71).aspx
298 	// for now, try to create a temporary file in a directory and open it
299 	// to rule out the possibility of it being created in the virtual store
300 	// TODO perhaps use SECURITY_DESCRIPTOR winapi calls here
301 
302 	std::string testfile = dir + "\\__$testfile42$.test";
303 	std::ofstream os(testfile.c_str());
304 	if (os.fail()) {
305 		return false;
306 	}
307 	const char* testdata = "THIS IS A TEST";
308 	os << testdata;
309 	os.close();
310 
311 	// this part should only be needed when there is no manifest embedded
312 	std::ifstream is(testfile.c_str());
313 	if (is.fail()) {
314 		return false; // the file most likely exists in the virtual store
315 	}
316 	std::string input;
317 	getline(is, input);
318 	if (input != testdata) {
319 		unlink(testfile.c_str());
320 		return false;
321 	}
322 	is.close();
323 	unlink(testfile.c_str());
324 	return true;
325 #else
326 	return (access(dir.c_str(), W_OK) == 0);
327 #endif
328 }
329 
330 
ComparePaths(const std::string & path1,const std::string & path2)331 bool FileSystemAbstraction::ComparePaths(const std::string& path1, const std::string& path2)
332 {
333 	try {
334 		const boost::filesystem::path p1(path1);
335 		const boost::filesystem::path p2(path2);
336 		return boost::filesystem::equivalent(p1, p2);
337 	} catch (const boost::filesystem::filesystem_error& ex) {
338 		return false;
339 	}
340 }
341 
342 
GetCwd()343 std::string FileSystemAbstraction::GetCwd()
344 {
345 	std::string cwd = "";
346 
347 #ifndef _WIN32
348 	#define GETCWD getcwd
349 #else
350 	#define GETCWD _getcwd
351 #endif
352 
353 	const size_t path_maxSize = 1024;
354 	char path[path_maxSize];
355 	if (GETCWD(path, path_maxSize) != NULL) {
356 		cwd = path;
357 	}
358 
359 	return cwd;
360 }
361 
ChDir(const std::string & dir)362 void FileSystemAbstraction::ChDir(const std::string& dir)
363 {
364 #ifndef _WIN32
365 	const int err = chdir(dir.c_str());
366 #else
367 	const int err = _chdir(StripTrailingSlashes(dir).c_str());
368 #endif
369 
370 	if (err) {
371 		throw content_error("Could not chdir into " + dir);
372 	}
373 }
374 
FindFiles(std::vector<std::string> & matches,const std::string & datadir,const std::string & dir,const boost::regex & regexPattern,int flags)375 static void FindFiles(std::vector<std::string>& matches, const std::string& datadir, const std::string& dir, const boost::regex& regexPattern, int flags)
376 {
377 #ifdef _WIN32
378 	WIN32_FIND_DATA wfd;
379 	HANDLE hFind = FindFirstFile((datadir + dir + "\\*").c_str(), &wfd);
380 
381 	if (hFind != INVALID_HANDLE_VALUE) {
382 		do {
383 			if(strcmp(wfd.cFileName,".") && strcmp(wfd.cFileName ,"..")) {
384 				if(!(wfd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)) {
385 					if ((flags & FileQueryFlags::ONLY_DIRS) == 0) {
386 						if (boost::regex_match(wfd.cFileName, regexPattern)) {
387 							matches.push_back(dir + wfd.cFileName);
388 						}
389 					}
390 				} else {
391 					if (flags & FileQueryFlags::INCLUDE_DIRS) {
392 						if (boost::regex_match(wfd.cFileName, regexPattern)) {
393 							matches.push_back(dir + wfd.cFileName + "\\");
394 						}
395 					}
396 					if (flags & FileQueryFlags::RECURSE) {
397 						FindFiles(matches, datadir, dir + wfd.cFileName + "\\", regexPattern, flags);
398 					}
399 				}
400 			}
401 		} while (FindNextFile(hFind, &wfd));
402 		FindClose(hFind);
403 	}
404 #else
405 	DIR* dp;
406 	struct dirent* ep;
407 
408 	if (!(dp = opendir((datadir + dir).c_str()))) {
409 		return;
410 	}
411 
412 	while ((ep = readdir(dp))) {
413 		// exclude hidden files
414 		if (ep->d_name[0] != '.') {
415 			// is it a file? (we just treat sockets / pipes / fifos / character&block devices as files...)
416 			// (need to stat because d_type is DT_UNKNOWN on linux :-/)
417 			struct stat info;
418 			if (stat((datadir + dir + ep->d_name).c_str(), &info) == 0) {
419 				if (!S_ISDIR(info.st_mode)) {
420 					if ((flags & FileQueryFlags::ONLY_DIRS) == 0) {
421 						if (boost::regex_match(ep->d_name, regexPattern)) {
422 							matches.push_back(dir + ep->d_name);
423 						}
424 					}
425 				} else {
426 					// or a directory?
427 					if (flags & FileQueryFlags::INCLUDE_DIRS) {
428 						if (boost::regex_match(ep->d_name, regexPattern)) {
429 							matches.push_back(dir + ep->d_name + "/");
430 						}
431 					}
432 					if (flags & FileQueryFlags::RECURSE) {
433 						FindFiles(matches, datadir, dir + ep->d_name + "/", regexPattern, flags);
434 					}
435 				}
436 			}
437 		}
438 	}
439 	closedir(dp);
440 #endif
441 }
442 
FindFiles(std::vector<std::string> & matches,const std::string & dataDir,const std::string & dir,const std::string & regex,int flags)443 void FileSystemAbstraction::FindFiles(std::vector<std::string>& matches, const std::string& dataDir, const std::string& dir, const std::string& regex, int flags)
444 {
445 	const boost::regex regexPattern(regex);
446 	::FindFiles(matches, dataDir, dir, regexPattern, flags);
447 }
448 
449