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