1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22 
23 #include "../Precompiled.h"
24 
25 #include "../Container/ArrayPtr.h"
26 #include "../Core/Context.h"
27 #include "../Core/CoreEvents.h"
28 #include "../Core/Thread.h"
29 #include "../Engine/EngineEvents.h"
30 #include "../IO/File.h"
31 #include "../IO/FileSystem.h"
32 #include "../IO/IOEvents.h"
33 #include "../IO/Log.h"
34 
35 #ifdef __ANDROID__
36 #include <SDL/SDL_rwops.h>
37 #endif
38 
39 #ifndef MINI_URHO
40 #include <SDL/SDL_filesystem.h>
41 #endif
42 
43 #include <sys/stat.h>
44 #include <cstdio>
45 
46 #ifdef _WIN32
47 #ifndef _MSC_VER
48 #define _WIN32_IE 0x501
49 #endif
50 #include <windows.h>
51 #include <shellapi.h>
52 #include <direct.h>
53 #include <shlobj.h>
54 #include <sys/types.h>
55 #include <sys/utime.h>
56 #else
57 #include <dirent.h>
58 #include <errno.h>
59 #include <unistd.h>
60 #include <utime.h>
61 #include <sys/wait.h>
62 #define MAX_PATH 256
63 #endif
64 
65 #if defined(__APPLE__)
66 #include <mach-o/dyld.h>
67 #endif
68 
69 extern "C"
70 {
71 #ifdef __ANDROID__
72 const char* SDL_Android_GetFilesDir();
73 char** SDL_Android_GetFileList(const char* path, int* count);
74 void SDL_Android_FreeFileList(char*** array, int* count);
75 #elif defined(IOS) || defined(TVOS)
76 const char* SDL_IOS_GetResourceDir();
77 const char* SDL_IOS_GetDocumentsDir();
78 #endif
79 }
80 
81 #include "../DebugNew.h"
82 
83 namespace Urho3D
84 {
85 
DoSystemCommand(const String & commandLine,bool redirectToLog,Context * context)86 int DoSystemCommand(const String& commandLine, bool redirectToLog, Context* context)
87 {
88 #ifdef TVOS
89     return -1;
90 #else
91 #if !defined(__EMSCRIPTEN__) && !defined(MINI_URHO)
92     if (!redirectToLog)
93 #endif
94         return system(commandLine.CString());
95 
96 #if !defined(__EMSCRIPTEN__) && !defined(MINI_URHO)
97     // Get a platform-agnostic temporary file name for stderr redirection
98     String stderrFilename;
99     String adjustedCommandLine(commandLine);
100     char* prefPath = SDL_GetPrefPath("urho3d", "temp");
101     if (prefPath)
102     {
103         stderrFilename = String(prefPath) + "command-stderr";
104         adjustedCommandLine += " 2>" + stderrFilename;
105         SDL_free(prefPath);
106     }
107 
108 #ifdef _MSC_VER
109     #define popen _popen
110     #define pclose _pclose
111 #endif
112 
113     // Use popen/pclose to capture the stdout and stderr of the command
114     FILE* file = popen(adjustedCommandLine.CString(), "r");
115     if (!file)
116         return -1;
117 
118     // Capture the standard output stream
119     char buffer[128];
120     while (!feof(file))
121     {
122         if (fgets(buffer, sizeof(buffer), file))
123             URHO3D_LOGRAW(String(buffer));
124     }
125     int exitCode = pclose(file);
126 
127     // Capture the standard error stream
128     if (!stderrFilename.Empty())
129     {
130         SharedPtr<File> errFile(new File(context, stderrFilename, FILE_READ));
131         while (!errFile->IsEof())
132         {
133             unsigned numRead = errFile->Read(buffer, sizeof(buffer));
134             if (numRead)
135                 Log::WriteRaw(String(buffer, numRead), true);
136         }
137     }
138 
139     return exitCode;
140 #endif
141 #endif
142 }
143 
DoSystemRun(const String & fileName,const Vector<String> & arguments)144 int DoSystemRun(const String& fileName, const Vector<String>& arguments)
145 {
146 #ifdef TVOS
147     return -1;
148 #else
149     String fixedFileName = GetNativePath(fileName);
150 
151 #ifdef _WIN32
152     // Add .exe extension if no extension defined
153     if (GetExtension(fixedFileName).Empty())
154         fixedFileName += ".exe";
155 
156     String commandLine = "\"" + fixedFileName + "\"";
157     for (unsigned i = 0; i < arguments.Size(); ++i)
158         commandLine += " " + arguments[i];
159 
160     STARTUPINFOW startupInfo;
161     PROCESS_INFORMATION processInfo;
162     memset(&startupInfo, 0, sizeof startupInfo);
163     memset(&processInfo, 0, sizeof processInfo);
164 
165     WString commandLineW(commandLine);
166     if (!CreateProcessW(NULL, (wchar_t*)commandLineW.CString(), 0, 0, 0, CREATE_NO_WINDOW, 0, 0, &startupInfo, &processInfo))
167         return -1;
168 
169     WaitForSingleObject(processInfo.hProcess, INFINITE);
170     DWORD exitCode;
171     GetExitCodeProcess(processInfo.hProcess, &exitCode);
172 
173     CloseHandle(processInfo.hProcess);
174     CloseHandle(processInfo.hThread);
175 
176     return exitCode;
177 #else
178     pid_t pid = fork();
179     if (!pid)
180     {
181         PODVector<const char*> argPtrs;
182         argPtrs.Push(fixedFileName.CString());
183         for (unsigned i = 0; i < arguments.Size(); ++i)
184             argPtrs.Push(arguments[i].CString());
185         argPtrs.Push(0);
186 
187         execvp(argPtrs[0], (char**)&argPtrs[0]);
188         return -1; // Return -1 if we could not spawn the process
189     }
190     else if (pid > 0)
191     {
192         int exitCode;
193         wait(&exitCode);
194         return exitCode;
195     }
196     else
197         return -1;
198 #endif
199 #endif
200 }
201 
202 /// Base class for async execution requests.
203 class AsyncExecRequest : public Thread
204 {
205 public:
206     /// Construct.
AsyncExecRequest(unsigned & requestID)207     AsyncExecRequest(unsigned& requestID) :
208         requestID_(requestID),
209         completed_(false)
210     {
211         // Increment ID for next request
212         ++requestID;
213         if (requestID == M_MAX_UNSIGNED)
214             requestID = 1;
215     }
216 
217     /// Return request ID.
GetRequestID() const218     unsigned GetRequestID() const { return requestID_; }
219 
220     /// Return exit code. Valid when IsCompleted() is true.
GetExitCode() const221     int GetExitCode() const { return exitCode_; }
222 
223     /// Return completion status.
IsCompleted() const224     bool IsCompleted() const { return completed_; }
225 
226 protected:
227     /// Request ID.
228     unsigned requestID_;
229     /// Exit code.
230     int exitCode_;
231     /// Completed flag.
232     volatile bool completed_;
233 };
234 
235 /// Async system command operation.
236 class AsyncSystemCommand : public AsyncExecRequest
237 {
238 public:
239     /// Construct and run.
AsyncSystemCommand(unsigned requestID,const String & commandLine)240     AsyncSystemCommand(unsigned requestID, const String& commandLine) :
241         AsyncExecRequest(requestID),
242         commandLine_(commandLine)
243     {
244         Run();
245     }
246 
247     /// The function to run in the thread.
ThreadFunction()248     virtual void ThreadFunction()
249     {
250         exitCode_ = DoSystemCommand(commandLine_, false, 0);
251         completed_ = true;
252     }
253 
254 private:
255     /// Command line.
256     String commandLine_;
257 };
258 
259 /// Async system run operation.
260 class AsyncSystemRun : public AsyncExecRequest
261 {
262 public:
263     /// Construct and run.
AsyncSystemRun(unsigned requestID,const String & fileName,const Vector<String> & arguments)264     AsyncSystemRun(unsigned requestID, const String& fileName, const Vector<String>& arguments) :
265         AsyncExecRequest(requestID),
266         fileName_(fileName),
267         arguments_(arguments)
268     {
269         Run();
270     }
271 
272     /// The function to run in the thread.
ThreadFunction()273     virtual void ThreadFunction()
274     {
275         exitCode_ = DoSystemRun(fileName_, arguments_);
276         completed_ = true;
277     }
278 
279 private:
280     /// File to run.
281     String fileName_;
282     /// Command line split in arguments.
283     const Vector<String>& arguments_;
284 };
285 
FileSystem(Context * context)286 FileSystem::FileSystem(Context* context) :
287     Object(context),
288     nextAsyncExecID_(1),
289     executeConsoleCommands_(false)
290 {
291     SubscribeToEvent(E_BEGINFRAME, URHO3D_HANDLER(FileSystem, HandleBeginFrame));
292 
293     // Subscribe to console commands
294     SetExecuteConsoleCommands(true);
295 }
296 
~FileSystem()297 FileSystem::~FileSystem()
298 {
299     // If any async exec items pending, delete them
300     if (asyncExecQueue_.Size())
301     {
302         for (List<AsyncExecRequest*>::Iterator i = asyncExecQueue_.Begin(); i != asyncExecQueue_.End(); ++i)
303             delete(*i);
304 
305         asyncExecQueue_.Clear();
306     }
307 }
308 
SetCurrentDir(const String & pathName)309 bool FileSystem::SetCurrentDir(const String& pathName)
310 {
311     if (!CheckAccess(pathName))
312     {
313         URHO3D_LOGERROR("Access denied to " + pathName);
314         return false;
315     }
316 #ifdef _WIN32
317     if (SetCurrentDirectoryW(GetWideNativePath(pathName).CString()) == FALSE)
318     {
319         URHO3D_LOGERROR("Failed to change directory to " + pathName);
320         return false;
321     }
322 #else
323     if (chdir(GetNativePath(pathName).CString()) != 0)
324     {
325         URHO3D_LOGERROR("Failed to change directory to " + pathName);
326         return false;
327     }
328 #endif
329 
330     return true;
331 }
332 
CreateDir(const String & pathName)333 bool FileSystem::CreateDir(const String& pathName)
334 {
335     if (!CheckAccess(pathName))
336     {
337         URHO3D_LOGERROR("Access denied to " + pathName);
338         return false;
339     }
340 
341     // Create each of the parents if necessary
342     String parentPath = GetParentPath(pathName);
343     if (parentPath.Length() > 1 && !DirExists(parentPath))
344     {
345         if (!CreateDir(parentPath))
346             return false;
347     }
348 
349 #ifdef _WIN32
350     bool success = (CreateDirectoryW(GetWideNativePath(RemoveTrailingSlash(pathName)).CString(), 0) == TRUE) ||
351         (GetLastError() == ERROR_ALREADY_EXISTS);
352 #else
353     bool success = mkdir(GetNativePath(RemoveTrailingSlash(pathName)).CString(), S_IRWXU) == 0 || errno == EEXIST;
354 #endif
355 
356     if (success)
357         URHO3D_LOGDEBUG("Created directory " + pathName);
358     else
359         URHO3D_LOGERROR("Failed to create directory " + pathName);
360 
361     return success;
362 }
363 
SetExecuteConsoleCommands(bool enable)364 void FileSystem::SetExecuteConsoleCommands(bool enable)
365 {
366     if (enable == executeConsoleCommands_)
367         return;
368 
369     executeConsoleCommands_ = enable;
370     if (enable)
371         SubscribeToEvent(E_CONSOLECOMMAND, URHO3D_HANDLER(FileSystem, HandleConsoleCommand));
372     else
373         UnsubscribeFromEvent(E_CONSOLECOMMAND);
374 }
375 
SystemCommand(const String & commandLine,bool redirectStdOutToLog)376 int FileSystem::SystemCommand(const String& commandLine, bool redirectStdOutToLog)
377 {
378     if (allowedPaths_.Empty())
379         return DoSystemCommand(commandLine, redirectStdOutToLog, context_);
380     else
381     {
382         URHO3D_LOGERROR("Executing an external command is not allowed");
383         return -1;
384     }
385 }
386 
SystemRun(const String & fileName,const Vector<String> & arguments)387 int FileSystem::SystemRun(const String& fileName, const Vector<String>& arguments)
388 {
389     if (allowedPaths_.Empty())
390         return DoSystemRun(fileName, arguments);
391     else
392     {
393         URHO3D_LOGERROR("Executing an external command is not allowed");
394         return -1;
395     }
396 }
397 
SystemCommandAsync(const String & commandLine)398 unsigned FileSystem::SystemCommandAsync(const String& commandLine)
399 {
400 #ifdef URHO3D_THREADING
401     if (allowedPaths_.Empty())
402     {
403         unsigned requestID = nextAsyncExecID_;
404         AsyncSystemCommand* cmd = new AsyncSystemCommand(nextAsyncExecID_, commandLine);
405         asyncExecQueue_.Push(cmd);
406         return requestID;
407     }
408     else
409     {
410         URHO3D_LOGERROR("Executing an external command is not allowed");
411         return M_MAX_UNSIGNED;
412     }
413 #else
414     URHO3D_LOGERROR("Can not execute an asynchronous command as threading is disabled");
415     return M_MAX_UNSIGNED;
416 #endif
417 }
418 
SystemRunAsync(const String & fileName,const Vector<String> & arguments)419 unsigned FileSystem::SystemRunAsync(const String& fileName, const Vector<String>& arguments)
420 {
421 #ifdef URHO3D_THREADING
422     if (allowedPaths_.Empty())
423     {
424         unsigned requestID = nextAsyncExecID_;
425         AsyncSystemRun* cmd = new AsyncSystemRun(nextAsyncExecID_, fileName, arguments);
426         asyncExecQueue_.Push(cmd);
427         return requestID;
428     }
429     else
430     {
431         URHO3D_LOGERROR("Executing an external command is not allowed");
432         return M_MAX_UNSIGNED;
433     }
434 #else
435     URHO3D_LOGERROR("Can not run asynchronously as threading is disabled");
436     return M_MAX_UNSIGNED;
437 #endif
438 }
439 
SystemOpen(const String & fileName,const String & mode)440 bool FileSystem::SystemOpen(const String& fileName, const String& mode)
441 {
442     if (allowedPaths_.Empty())
443     {
444         if (!FileExists(fileName) && !DirExists(fileName))
445         {
446             URHO3D_LOGERROR("File or directory " + fileName + " not found");
447             return false;
448         }
449 
450 #ifdef _WIN32
451         bool success = (size_t)ShellExecuteW(0, !mode.Empty() ? WString(mode).CString() : 0,
452             GetWideNativePath(fileName).CString(), 0, 0, SW_SHOW) > 32;
453 #else
454         Vector<String> arguments;
455         arguments.Push(fileName);
456         bool success = SystemRun(
457 #if defined(__APPLE__)
458             "/usr/bin/open",
459 #else
460             "/usr/bin/xdg-open",
461 #endif
462             arguments) == 0;
463 #endif
464         if (!success)
465             URHO3D_LOGERROR("Failed to open " + fileName + " externally");
466         return success;
467     }
468     else
469     {
470         URHO3D_LOGERROR("Opening a file externally is not allowed");
471         return false;
472     }
473 }
474 
Copy(const String & srcFileName,const String & destFileName)475 bool FileSystem::Copy(const String& srcFileName, const String& destFileName)
476 {
477     if (!CheckAccess(GetPath(srcFileName)))
478     {
479         URHO3D_LOGERROR("Access denied to " + srcFileName);
480         return false;
481     }
482     if (!CheckAccess(GetPath(destFileName)))
483     {
484         URHO3D_LOGERROR("Access denied to " + destFileName);
485         return false;
486     }
487 
488     SharedPtr<File> srcFile(new File(context_, srcFileName, FILE_READ));
489     if (!srcFile->IsOpen())
490         return false;
491     SharedPtr<File> destFile(new File(context_, destFileName, FILE_WRITE));
492     if (!destFile->IsOpen())
493         return false;
494 
495     unsigned fileSize = srcFile->GetSize();
496     SharedArrayPtr<unsigned char> buffer(new unsigned char[fileSize]);
497 
498     unsigned bytesRead = srcFile->Read(buffer.Get(), fileSize);
499     unsigned bytesWritten = destFile->Write(buffer.Get(), fileSize);
500     return bytesRead == fileSize && bytesWritten == fileSize;
501 }
502 
Rename(const String & srcFileName,const String & destFileName)503 bool FileSystem::Rename(const String& srcFileName, const String& destFileName)
504 {
505     if (!CheckAccess(GetPath(srcFileName)))
506     {
507         URHO3D_LOGERROR("Access denied to " + srcFileName);
508         return false;
509     }
510     if (!CheckAccess(GetPath(destFileName)))
511     {
512         URHO3D_LOGERROR("Access denied to " + destFileName);
513         return false;
514     }
515 
516 #ifdef _WIN32
517     return MoveFileW(GetWideNativePath(srcFileName).CString(), GetWideNativePath(destFileName).CString()) != 0;
518 #else
519     return rename(GetNativePath(srcFileName).CString(), GetNativePath(destFileName).CString()) == 0;
520 #endif
521 }
522 
Delete(const String & fileName)523 bool FileSystem::Delete(const String& fileName)
524 {
525     if (!CheckAccess(GetPath(fileName)))
526     {
527         URHO3D_LOGERROR("Access denied to " + fileName);
528         return false;
529     }
530 
531 #ifdef _WIN32
532     return DeleteFileW(GetWideNativePath(fileName).CString()) != 0;
533 #else
534     return remove(GetNativePath(fileName).CString()) == 0;
535 #endif
536 }
537 
GetCurrentDir() const538 String FileSystem::GetCurrentDir() const
539 {
540 #ifdef _WIN32
541     wchar_t path[MAX_PATH];
542     path[0] = 0;
543     GetCurrentDirectoryW(MAX_PATH, path);
544     return AddTrailingSlash(String(path));
545 #else
546     char path[MAX_PATH];
547     path[0] = 0;
548     getcwd(path, MAX_PATH);
549     return AddTrailingSlash(String(path));
550 #endif
551 }
552 
CheckAccess(const String & pathName) const553 bool FileSystem::CheckAccess(const String& pathName) const
554 {
555     String fixedPath = AddTrailingSlash(pathName);
556 
557     // If no allowed directories defined, succeed always
558     if (allowedPaths_.Empty())
559         return true;
560 
561     // If there is any attempt to go to a parent directory, disallow
562     if (fixedPath.Contains(".."))
563         return false;
564 
565     // Check if the path is a partial match of any of the allowed directories
566     for (HashSet<String>::ConstIterator i = allowedPaths_.Begin(); i != allowedPaths_.End(); ++i)
567     {
568         if (fixedPath.Find(*i) == 0)
569             return true;
570     }
571 
572     // Not found, so disallow
573     return false;
574 }
575 
GetLastModifiedTime(const String & fileName) const576 unsigned FileSystem::GetLastModifiedTime(const String& fileName) const
577 {
578     if (fileName.Empty() || !CheckAccess(fileName))
579         return 0;
580 
581 #ifdef _WIN32
582     struct _stat st;
583     if (!_stat(fileName.CString(), &st))
584         return (unsigned)st.st_mtime;
585     else
586         return 0;
587 #else
588     struct stat st;
589     if (!stat(fileName.CString(), &st))
590         return (unsigned)st.st_mtime;
591     else
592         return 0;
593 #endif
594 }
595 
FileExists(const String & fileName) const596 bool FileSystem::FileExists(const String& fileName) const
597 {
598     if (!CheckAccess(GetPath(fileName)))
599         return false;
600 
601 #ifdef __ANDROID__
602     if (URHO3D_IS_ASSET(fileName))
603     {
604         SDL_RWops* rwOps = SDL_RWFromFile(URHO3D_ASSET(fileName), "rb");
605         if (rwOps)
606         {
607             SDL_RWclose(rwOps);
608             return true;
609         }
610         else
611             return false;
612     }
613 #endif
614 
615     String fixedName = GetNativePath(RemoveTrailingSlash(fileName));
616 
617 #ifdef _WIN32
618     DWORD attributes = GetFileAttributesW(WString(fixedName).CString());
619     if (attributes == INVALID_FILE_ATTRIBUTES || attributes & FILE_ATTRIBUTE_DIRECTORY)
620         return false;
621 #else
622     struct stat st;
623     if (stat(fixedName.CString(), &st) || st.st_mode & S_IFDIR)
624         return false;
625 #endif
626 
627     return true;
628 }
629 
DirExists(const String & pathName) const630 bool FileSystem::DirExists(const String& pathName) const
631 {
632     if (!CheckAccess(pathName))
633         return false;
634 
635 #ifndef _WIN32
636     // Always return true for the root directory
637     if (pathName == "/")
638         return true;
639 #endif
640 
641     String fixedName = GetNativePath(RemoveTrailingSlash(pathName));
642 
643 #ifdef __ANDROID__
644     if (URHO3D_IS_ASSET(fixedName))
645     {
646         // Split the pathname into two components: the longest parent directory path and the last name component
647         String assetPath(URHO3D_ASSET((fixedName + "/")));
648         String parentPath;
649         unsigned pos = assetPath.FindLast('/', assetPath.Length() - 2);
650         if (pos != String::NPOS)
651         {
652             parentPath = assetPath.Substring(0, pos);
653             assetPath = assetPath.Substring(pos + 1);
654         }
655         assetPath.Resize(assetPath.Length() - 1);
656 
657         bool exist = false;
658         int count;
659         char** list = SDL_Android_GetFileList(parentPath.CString(), &count);
660         for (int i = 0; i < count; ++i)
661         {
662             exist = assetPath == list[i];
663             if (exist)
664                 break;
665         }
666         SDL_Android_FreeFileList(&list, &count);
667         return exist;
668     }
669 #endif
670 
671 #ifdef _WIN32
672     DWORD attributes = GetFileAttributesW(WString(fixedName).CString());
673     if (attributes == INVALID_FILE_ATTRIBUTES || !(attributes & FILE_ATTRIBUTE_DIRECTORY))
674         return false;
675 #else
676     struct stat st;
677     if (stat(fixedName.CString(), &st) || !(st.st_mode & S_IFDIR))
678         return false;
679 #endif
680 
681     return true;
682 }
683 
ScanDir(Vector<String> & result,const String & pathName,const String & filter,unsigned flags,bool recursive) const684 void FileSystem::ScanDir(Vector<String>& result, const String& pathName, const String& filter, unsigned flags, bool recursive) const
685 {
686     result.Clear();
687 
688     if (CheckAccess(pathName))
689     {
690         String initialPath = AddTrailingSlash(pathName);
691         ScanDirInternal(result, initialPath, initialPath, filter, flags, recursive);
692     }
693 }
694 
GetProgramDir() const695 String FileSystem::GetProgramDir() const
696 {
697 #if defined(__ANDROID__)
698     // This is an internal directory specifier pointing to the assets in the .apk
699     // Files from this directory will be opened using special handling
700     return APK;
701 #elif defined(IOS) || defined(TVOS)
702     return AddTrailingSlash(SDL_IOS_GetResourceDir());
703 #elif defined(_WIN32)
704     wchar_t exeName[MAX_PATH];
705     exeName[0] = 0;
706     GetModuleFileNameW(0, exeName, MAX_PATH);
707     return GetPath(String(exeName));
708 #elif defined(__APPLE__)
709     char exeName[MAX_PATH];
710     memset(exeName, 0, MAX_PATH);
711     unsigned size = MAX_PATH;
712     _NSGetExecutablePath(exeName, &size);
713     return GetPath(String(exeName));
714 #elif defined(__linux__)
715     char exeName[MAX_PATH];
716     memset(exeName, 0, MAX_PATH);
717     pid_t pid = getpid();
718     String link = "/proc/" + String(pid) + "/exe";
719     readlink(link.CString(), exeName, MAX_PATH);
720     return GetPath(String(exeName));
721 #else
722     return GetCurrentDir();
723 #endif
724 }
725 
GetUserDocumentsDir() const726 String FileSystem::GetUserDocumentsDir() const
727 {
728 #if defined(__ANDROID__)
729     return AddTrailingSlash(SDL_Android_GetFilesDir());
730 #elif defined(IOS) || defined(TVOS)
731     return AddTrailingSlash(SDL_IOS_GetDocumentsDir());
732 #elif defined(_WIN32)
733     wchar_t pathName[MAX_PATH];
734     pathName[0] = 0;
735     SHGetSpecialFolderPathW(0, pathName, CSIDL_PERSONAL, 0);
736     return AddTrailingSlash(String(pathName));
737 #else
738     char pathName[MAX_PATH];
739     pathName[0] = 0;
740     strcpy(pathName, getenv("HOME"));
741     return AddTrailingSlash(String(pathName));
742 #endif
743 }
744 
GetAppPreferencesDir(const String & org,const String & app) const745 String FileSystem::GetAppPreferencesDir(const String& org, const String& app) const
746 {
747     String dir;
748 #ifndef MINI_URHO
749     char* prefPath = SDL_GetPrefPath(org.CString(), app.CString());
750     if (prefPath)
751     {
752         dir = GetInternalPath(String(prefPath));
753         SDL_free(prefPath);
754     }
755     else
756 #endif
757         URHO3D_LOGWARNING("Could not get application preferences directory");
758 
759     return dir;
760 }
761 
RegisterPath(const String & pathName)762 void FileSystem::RegisterPath(const String& pathName)
763 {
764     if (pathName.Empty())
765         return;
766 
767     allowedPaths_.Insert(AddTrailingSlash(pathName));
768 }
769 
SetLastModifiedTime(const String & fileName,unsigned newTime)770 bool FileSystem::SetLastModifiedTime(const String& fileName, unsigned newTime)
771 {
772     if (fileName.Empty() || !CheckAccess(fileName))
773         return false;
774 
775 #ifdef _WIN32
776     struct _stat oldTime;
777     struct _utimbuf newTimes;
778     if (_stat(fileName.CString(), &oldTime) != 0)
779         return false;
780     newTimes.actime = oldTime.st_atime;
781     newTimes.modtime = newTime;
782     return _utime(fileName.CString(), &newTimes) == 0;
783 #else
784     struct stat oldTime;
785     struct utimbuf newTimes;
786     if (stat(fileName.CString(), &oldTime) != 0)
787         return false;
788     newTimes.actime = oldTime.st_atime;
789     newTimes.modtime = newTime;
790     return utime(fileName.CString(), &newTimes) == 0;
791 #endif
792 }
793 
ScanDirInternal(Vector<String> & result,String path,const String & startPath,const String & filter,unsigned flags,bool recursive) const794 void FileSystem::ScanDirInternal(Vector<String>& result, String path, const String& startPath,
795     const String& filter, unsigned flags, bool recursive) const
796 {
797     path = AddTrailingSlash(path);
798     String deltaPath;
799     if (path.Length() > startPath.Length())
800         deltaPath = path.Substring(startPath.Length());
801 
802     String filterExtension = filter.Substring(filter.Find('.'));
803     if (filterExtension.Contains('*'))
804         filterExtension.Clear();
805 
806 #ifdef __ANDROID__
807     if (URHO3D_IS_ASSET(path))
808     {
809         String assetPath(URHO3D_ASSET(path));
810         assetPath.Resize(assetPath.Length() - 1);       // AssetManager.list() does not like trailing slash
811         int count;
812         char** list = SDL_Android_GetFileList(assetPath.CString(), &count);
813         for (int i = 0; i < count; ++i)
814         {
815             String fileName(list[i]);
816             if (!(flags & SCAN_HIDDEN) && fileName.StartsWith("."))
817                 continue;
818 
819 #ifdef ASSET_DIR_INDICATOR
820             // Patch the directory name back after retrieving the directory flag
821             bool isDirectory = fileName.EndsWith(ASSET_DIR_INDICATOR);
822             if (isDirectory)
823             {
824                 fileName.Resize(fileName.Length() - sizeof(ASSET_DIR_INDICATOR) / sizeof(char) + 1);
825                 if (flags & SCAN_DIRS)
826                     result.Push(deltaPath + fileName);
827                 if (recursive)
828                     ScanDirInternal(result, path + fileName, startPath, filter, flags, recursive);
829             }
830             else if (flags & SCAN_FILES)
831 #endif
832             {
833                 if (filterExtension.Empty() || fileName.EndsWith(filterExtension))
834                     result.Push(deltaPath + fileName);
835             }
836         }
837         SDL_Android_FreeFileList(&list, &count);
838         return;
839     }
840 #endif
841 #ifdef _WIN32
842     WIN32_FIND_DATAW info;
843     HANDLE handle = FindFirstFileW(WString(path + "*").CString(), &info);
844     if (handle != INVALID_HANDLE_VALUE)
845     {
846         do
847         {
848             String fileName(info.cFileName);
849             if (!fileName.Empty())
850             {
851                 if (info.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN && !(flags & SCAN_HIDDEN))
852                     continue;
853                 if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
854                 {
855                     if (flags & SCAN_DIRS)
856                         result.Push(deltaPath + fileName);
857                     if (recursive && fileName != "." && fileName != "..")
858                         ScanDirInternal(result, path + fileName, startPath, filter, flags, recursive);
859                 }
860                 else if (flags & SCAN_FILES)
861                 {
862                     if (filterExtension.Empty() || fileName.EndsWith(filterExtension))
863                         result.Push(deltaPath + fileName);
864                 }
865             }
866         }
867         while (FindNextFileW(handle, &info));
868 
869         FindClose(handle);
870     }
871 #else
872     DIR* dir;
873     struct dirent* de;
874     struct stat st;
875     dir = opendir(GetNativePath(path).CString());
876     if (dir)
877     {
878         while ((de = readdir(dir)))
879         {
880             /// \todo Filename may be unnormalized Unicode on Mac OS X. Re-normalize as necessary
881             String fileName(de->d_name);
882             bool normalEntry = fileName != "." && fileName != "..";
883             if (normalEntry && !(flags & SCAN_HIDDEN) && fileName.StartsWith("."))
884                 continue;
885             String pathAndName = path + fileName;
886             if (!stat(pathAndName.CString(), &st))
887             {
888                 if (st.st_mode & S_IFDIR)
889                 {
890                     if (flags & SCAN_DIRS)
891                         result.Push(deltaPath + fileName);
892                     if (recursive && normalEntry)
893                         ScanDirInternal(result, path + fileName, startPath, filter, flags, recursive);
894                 }
895                 else if (flags & SCAN_FILES)
896                 {
897                     if (filterExtension.Empty() || fileName.EndsWith(filterExtension))
898                         result.Push(deltaPath + fileName);
899                 }
900             }
901         }
902         closedir(dir);
903     }
904 #endif
905 }
906 
HandleBeginFrame(StringHash eventType,VariantMap & eventData)907 void FileSystem::HandleBeginFrame(StringHash eventType, VariantMap& eventData)
908 {
909     /// Go through the execution queue and post + remove completed requests
910     for (List<AsyncExecRequest*>::Iterator i = asyncExecQueue_.Begin(); i != asyncExecQueue_.End();)
911     {
912         AsyncExecRequest* request = *i;
913         if (request->IsCompleted())
914         {
915             using namespace AsyncExecFinished;
916 
917             VariantMap& newEventData = GetEventDataMap();
918             newEventData[P_REQUESTID] = request->GetRequestID();
919             newEventData[P_EXITCODE] = request->GetExitCode();
920             SendEvent(E_ASYNCEXECFINISHED, newEventData);
921 
922             delete request;
923             i = asyncExecQueue_.Erase(i);
924         }
925         else
926             ++i;
927     }
928 }
929 
HandleConsoleCommand(StringHash eventType,VariantMap & eventData)930 void FileSystem::HandleConsoleCommand(StringHash eventType, VariantMap& eventData)
931 {
932     using namespace ConsoleCommand;
933     if (eventData[P_ID].GetString() == GetTypeName())
934         SystemCommand(eventData[P_COMMAND].GetString(), true);
935 }
936 
SplitPath(const String & fullPath,String & pathName,String & fileName,String & extension,bool lowercaseExtension)937 void SplitPath(const String& fullPath, String& pathName, String& fileName, String& extension, bool lowercaseExtension)
938 {
939     String fullPathCopy = GetInternalPath(fullPath);
940 
941     unsigned extPos = fullPathCopy.FindLast('.');
942     unsigned pathPos = fullPathCopy.FindLast('/');
943 
944     if (extPos != String::NPOS && (pathPos == String::NPOS || extPos > pathPos))
945     {
946         extension = fullPathCopy.Substring(extPos);
947         if (lowercaseExtension)
948             extension = extension.ToLower();
949         fullPathCopy = fullPathCopy.Substring(0, extPos);
950     }
951     else
952         extension.Clear();
953 
954     pathPos = fullPathCopy.FindLast('/');
955     if (pathPos != String::NPOS)
956     {
957         fileName = fullPathCopy.Substring(pathPos + 1);
958         pathName = fullPathCopy.Substring(0, pathPos + 1);
959     }
960     else
961     {
962         fileName = fullPathCopy;
963         pathName.Clear();
964     }
965 }
966 
GetPath(const String & fullPath)967 String GetPath(const String& fullPath)
968 {
969     String path, file, extension;
970     SplitPath(fullPath, path, file, extension);
971     return path;
972 }
973 
GetFileName(const String & fullPath)974 String GetFileName(const String& fullPath)
975 {
976     String path, file, extension;
977     SplitPath(fullPath, path, file, extension);
978     return file;
979 }
980 
GetExtension(const String & fullPath,bool lowercaseExtension)981 String GetExtension(const String& fullPath, bool lowercaseExtension)
982 {
983     String path, file, extension;
984     SplitPath(fullPath, path, file, extension, lowercaseExtension);
985     return extension;
986 }
987 
GetFileNameAndExtension(const String & fileName,bool lowercaseExtension)988 String GetFileNameAndExtension(const String& fileName, bool lowercaseExtension)
989 {
990     String path, file, extension;
991     SplitPath(fileName, path, file, extension, lowercaseExtension);
992     return file + extension;
993 }
994 
ReplaceExtension(const String & fullPath,const String & newExtension)995 String ReplaceExtension(const String& fullPath, const String& newExtension)
996 {
997     String path, file, extension;
998     SplitPath(fullPath, path, file, extension);
999     return path + file + newExtension;
1000 }
1001 
AddTrailingSlash(const String & pathName)1002 String AddTrailingSlash(const String& pathName)
1003 {
1004     String ret = pathName.Trimmed();
1005     ret.Replace('\\', '/');
1006     if (!ret.Empty() && ret.Back() != '/')
1007         ret += '/';
1008     return ret;
1009 }
1010 
RemoveTrailingSlash(const String & pathName)1011 String RemoveTrailingSlash(const String& pathName)
1012 {
1013     String ret = pathName.Trimmed();
1014     ret.Replace('\\', '/');
1015     if (!ret.Empty() && ret.Back() == '/')
1016         ret.Resize(ret.Length() - 1);
1017     return ret;
1018 }
1019 
GetParentPath(const String & path)1020 String GetParentPath(const String& path)
1021 {
1022     unsigned pos = RemoveTrailingSlash(path).FindLast('/');
1023     if (pos != String::NPOS)
1024         return path.Substring(0, pos + 1);
1025     else
1026         return String();
1027 }
1028 
GetInternalPath(const String & pathName)1029 String GetInternalPath(const String& pathName)
1030 {
1031     return pathName.Replaced('\\', '/');
1032 }
1033 
GetNativePath(const String & pathName)1034 String GetNativePath(const String& pathName)
1035 {
1036 #ifdef _WIN32
1037     return pathName.Replaced('/', '\\');
1038 #else
1039     return pathName;
1040 #endif
1041 }
1042 
GetWideNativePath(const String & pathName)1043 WString GetWideNativePath(const String& pathName)
1044 {
1045 #ifdef _WIN32
1046     return WString(pathName.Replaced('/', '\\'));
1047 #else
1048     return WString(pathName);
1049 #endif
1050 }
1051 
IsAbsolutePath(const String & pathName)1052 bool IsAbsolutePath(const String& pathName)
1053 {
1054     if (pathName.Empty())
1055         return false;
1056 
1057     String path = GetInternalPath(pathName);
1058 
1059     if (path[0] == '/')
1060         return true;
1061 
1062 #ifdef _WIN32
1063     if (path.Length() > 1 && IsAlpha(path[0]) && path[1] == ':')
1064         return true;
1065 #endif
1066 
1067     return false;
1068 }
1069 
1070 }
1071