1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include <base/hash_ctxt.h>
4#include <base/log.h>
5#include <base/math.h>
6#include <base/system.h>
7
8#include <engine/client/updater.h>
9#include <engine/shared/linereader.h>
10#include <engine/storage.h>
11
12#include <unordered_set>
13
14#ifdef CONF_PLATFORM_HAIKU
15#include <cstdlib>
16#endif
17
18#include <zlib.h>
19
20class CStorage : public IStorage
21{
22 char m_aaStoragePaths[MAX_PATHS][IO_MAX_PATH_LENGTH];
23 int m_NumPaths = 0;
24 char m_aUserdir[IO_MAX_PATH_LENGTH] = "";
25 char m_aDatadir[IO_MAX_PATH_LENGTH] = "";
26 char m_aCurrentdir[IO_MAX_PATH_LENGTH] = "";
27 char m_aBinarydir[IO_MAX_PATH_LENGTH] = "";
28
29public:
30 bool Init(EInitializationType InitializationType, int NumArgs, const char **ppArguments)
31 {
32 dbg_assert(NumArgs > 0, "Expected at least one argument");
33 const char *pExecutablePath = ppArguments[0];
34
35 FindUserDirectory();
36 FindDataDirectory(pArgv0: pExecutablePath);
37 FindCurrentDirectory();
38 FindBinaryDirectory(pArgv0: pExecutablePath);
39
40 if(!LoadPathsFromFile(pArgv0: pExecutablePath))
41 {
42 return false;
43 }
44
45 if(!m_NumPaths)
46 {
47 if(!AddDefaultPaths())
48 {
49 return false;
50 }
51 }
52
53 if(InitializationType == EInitializationType::BASIC)
54 {
55 return true;
56 }
57
58 if(m_aaStoragePaths[TYPE_SAVE][0] != '\0')
59 {
60 if(fs_makedir_rec_for(path: m_aaStoragePaths[TYPE_SAVE]) != 0 ||
61 fs_makedir(path: m_aaStoragePaths[TYPE_SAVE]) != 0)
62 {
63 log_error("storage", "failed to create the user directory");
64 return false;
65 }
66 }
67
68 bool Success = true;
69 if(InitializationType == EInitializationType::CLIENT)
70 {
71 static constexpr const char *CLIENT_DIRS[] = {
72 "assets",
73 "assets/emoticons",
74 "assets/entities",
75 "assets/extras",
76 "assets/game",
77 "assets/hud",
78 "assets/particles",
79 "audio",
80 "communityicons",
81 "downloadedmaps",
82 "downloadedskins",
83 "mapres",
84 "maps",
85 "maps/auto",
86 "screenshots",
87 "screenshots/auto",
88 "screenshots/auto/stats",
89 "skins",
90 "skins7",
91 "themes",
92#if defined(CONF_VIDEORECORDER)
93 "videos"
94#endif
95 };
96
97 for(const char *pDir : CLIENT_DIRS)
98 Success &= CreateFolder(pFoldername: pDir, Type: TYPE_SAVE);
99 }
100
101 static constexpr const char *COMMON_DIRS[] = {
102 "dumps",
103 "demos",
104 "demos/auto",
105 "demos/auto/race",
106 "demos/auto/server",
107 "demos/replays",
108 "editor",
109 "ghosts",
110 "teehistorian"};
111
112 for(const char *pDir : COMMON_DIRS)
113 Success &= CreateFolder(pFoldername: pDir, Type: TYPE_SAVE);
114
115 if(!Success)
116 {
117 log_error("storage", "failed to create default folders in the user directory");
118 }
119
120 return Success;
121 }
122
123 bool LoadPathsFromFile(const char *pArgv0)
124 {
125 // check current directory
126 IOHANDLE File = io_open(filename: "storage.cfg", flags: IOFLAG_READ);
127 if(!File)
128 {
129 // check usable path in argv[0]
130 unsigned int Pos = ~0U;
131 for(unsigned i = 0; pArgv0[i]; i++)
132 if(pArgv0[i] == '/' || pArgv0[i] == '\\')
133 Pos = i;
134 if(Pos < IO_MAX_PATH_LENGTH)
135 {
136 char aBuffer[IO_MAX_PATH_LENGTH];
137 str_copy(dst: aBuffer, src: pArgv0, dst_size: Pos + 1);
138 str_append(dst&: aBuffer, src: "/storage.cfg");
139 File = io_open(filename: aBuffer, flags: IOFLAG_READ);
140 }
141 }
142
143 CLineReader LineReader;
144 if(!LineReader.OpenFile(File))
145 {
146 log_error("storage", "couldn't open storage.cfg");
147 return true;
148 }
149 while(const char *pLine = LineReader.Get())
150 {
151 const char *pLineWithoutPrefix = str_startswith(str: pLine, prefix: "add_path ");
152 if(pLineWithoutPrefix)
153 {
154 if(!AddPath(pPath: pLineWithoutPrefix) && !m_NumPaths)
155 {
156 log_error("storage", "failed to add path for the user directory");
157 return false;
158 }
159 }
160 }
161
162 if(!m_NumPaths)
163 {
164 log_error("storage", "no usable paths found in storage.cfg");
165 }
166 return true;
167 }
168
169 bool AddDefaultPaths()
170 {
171 log_info("storage", "using standard paths");
172 if(!AddPath(pPath: "$USERDIR"))
173 {
174 log_error("storage", "failed to add default path for the user directory");
175 return false;
176 }
177 AddPath(pPath: "$DATADIR");
178 AddPath(pPath: "$CURRENTDIR");
179 return true;
180 }
181
182 bool AddPath(const char *pPath)
183 {
184 if(!pPath[0])
185 {
186 log_error("storage", "cannot add empty path");
187 return false;
188 }
189 if(m_NumPaths >= MAX_PATHS)
190 {
191 log_error("storage", "cannot add path '%s', the maximum number of paths is %d", pPath, MAX_PATHS);
192 return false;
193 }
194
195 if(!str_comp(a: pPath, b: "$USERDIR"))
196 {
197 if(m_aUserdir[0])
198 {
199 str_copy(dst&: m_aaStoragePaths[m_NumPaths++], src: m_aUserdir);
200 log_info("storage", "added path '$USERDIR' ('%s')", m_aUserdir);
201 return true;
202 }
203 else
204 {
205 log_error("storage", "cannot add path '$USERDIR' because it could not be determined");
206 return false;
207 }
208 }
209 else if(!str_comp(a: pPath, b: "$DATADIR"))
210 {
211 if(m_aDatadir[0])
212 {
213 str_copy(dst&: m_aaStoragePaths[m_NumPaths++], src: m_aDatadir);
214 log_info("storage", "added path '$DATADIR' ('%s')", m_aDatadir);
215 return true;
216 }
217 else
218 {
219 log_error("storage", "cannot add path '$DATADIR' because it could not be determined");
220 return false;
221 }
222 }
223 else if(!str_comp(a: pPath, b: "$CURRENTDIR"))
224 {
225 m_aaStoragePaths[m_NumPaths++][0] = '\0';
226 log_info("storage", "added path '$CURRENTDIR' ('%s')", m_aCurrentdir);
227 return true;
228 }
229 else if(str_utf8_check(str: pPath))
230 {
231 if(fs_is_dir(path: pPath))
232 {
233 str_copy(dst&: m_aaStoragePaths[m_NumPaths++], src: pPath);
234 log_info("storage", "added path '%s'", pPath);
235 return true;
236 }
237 else
238 {
239 log_error("storage", "cannot add path '%s', which is not a directory", pPath);
240 return false;
241 }
242 }
243 else
244 {
245 log_error("storage", "cannot add path containing invalid UTF-8");
246 return false;
247 }
248 }
249
250 void FindUserDirectory()
251 {
252#if defined(CONF_PLATFORM_ANDROID)
253 // See InitAndroid in android_main.cpp for details about Android storage handling.
254 // The current working directory is set to the app specific external storage location
255 // on Android. The user data is stored within a folder "user" in the external storage.
256 str_copy(m_aUserdir, "user");
257#else
258 char aFallbackUserdir[IO_MAX_PATH_LENGTH];
259 if(fs_storage_path(appname: "DDNet", path: m_aUserdir, max: sizeof(m_aUserdir)))
260 {
261 log_error("storage", "could not determine user directory");
262 }
263 if(fs_storage_path(appname: "Teeworlds", path: aFallbackUserdir, max: sizeof(aFallbackUserdir)))
264 {
265 log_error("storage", "could not determine fallback user directory");
266 }
267
268 if((m_aUserdir[0] == '\0' || !fs_is_dir(path: m_aUserdir)) && aFallbackUserdir[0] != '\0' && fs_is_dir(path: aFallbackUserdir))
269 {
270 str_copy(dst&: m_aUserdir, src: aFallbackUserdir);
271 }
272#endif
273 }
274
275 void FindDataDirectory(const char *pArgv0)
276 {
277 // 1) use data-dir in PWD if present
278 if(fs_is_dir(path: "data/mapres"))
279 {
280 str_copy(dst&: m_aDatadir, src: "data");
281 return;
282 }
283
284#if defined(DATA_DIR)
285 // 2) use compiled-in data-dir if present
286 if(fs_is_dir(DATA_DIR "/mapres"))
287 {
288 str_copy(m_aDatadir, DATA_DIR, sizeof(m_aDatadir));
289 return;
290 }
291#endif
292
293 // 3) check for usable path in argv[0]
294 {
295#ifdef CONF_PLATFORM_HAIKU
296 pArgv0 = realpath(pArgv0, NULL);
297#endif
298 unsigned int Pos = ~0U;
299 for(unsigned i = 0; pArgv0[i]; i++)
300 if(pArgv0[i] == '/' || pArgv0[i] == '\\')
301 Pos = i;
302
303 if(Pos < IO_MAX_PATH_LENGTH)
304 {
305 char aBuf[IO_MAX_PATH_LENGTH];
306 char aDir[IO_MAX_PATH_LENGTH];
307 str_copy(dst: aDir, src: pArgv0, dst_size: Pos + 1);
308 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s/data/mapres", aDir);
309 if(fs_is_dir(path: aBuf))
310 {
311 str_format(buffer: m_aDatadir, buffer_size: sizeof(m_aDatadir), format: "%s/data", aDir);
312 return;
313 }
314 }
315 }
316#ifdef CONF_PLATFORM_HAIKU
317 free((void *)pArgv0);
318#endif
319
320#if defined(CONF_FAMILY_UNIX)
321 // 4) check for all default locations
322 {
323 const char *apDirs[] = {
324 "/usr/share/ddnet",
325 "/usr/share/games/ddnet",
326 "/usr/local/share/ddnet",
327 "/usr/local/share/games/ddnet",
328 "/usr/pkg/share/ddnet",
329 "/usr/pkg/share/games/ddnet",
330 "/opt/ddnet"};
331
332 for(const char *pDir : apDirs)
333 {
334 char aBuf[IO_MAX_PATH_LENGTH];
335 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s/data/mapres", pDir);
336 if(fs_is_dir(path: aBuf))
337 {
338 str_format(buffer: m_aDatadir, buffer_size: sizeof(m_aDatadir), format: "%s/data", pDir);
339 return;
340 }
341 }
342 }
343#endif
344
345 log_warn("storage", "no data directory found");
346 }
347
348 bool FindCurrentDirectory()
349 {
350 if(!fs_getcwd(buffer: m_aCurrentdir, buffer_size: sizeof(m_aCurrentdir)))
351 {
352 log_error("storage", "could not determine current directory");
353 return false;
354 }
355 return true;
356 }
357
358 void FindBinaryDirectory(const char *pArgv0)
359 {
360#if defined(BINARY_DIR)
361 str_copy(m_aBinarydir, BINARY_DIR, sizeof(m_aBinarydir));
362 return;
363#endif
364
365 // check for usable path in argv[0]
366 {
367 unsigned int Pos = ~0U;
368 for(unsigned i = 0; pArgv0[i]; i++)
369 if(pArgv0[i] == '/' || pArgv0[i] == '\\')
370 Pos = i;
371
372 if(Pos < IO_MAX_PATH_LENGTH)
373 {
374 char aBuf[IO_MAX_PATH_LENGTH];
375 str_copy(dst: m_aBinarydir, src: pArgv0, dst_size: Pos + 1);
376 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s/" PLAT_SERVER_EXEC, m_aBinarydir);
377 if(fs_is_file(path: aBuf))
378 {
379 return;
380 }
381#if defined(CONF_PLATFORM_MACOS)
382 str_append(m_aBinarydir, "/../../../DDNet-Server.app/Contents/MacOS");
383 str_format(aBuf, sizeof(aBuf), "%s/" PLAT_SERVER_EXEC, m_aBinarydir);
384 if(fs_is_file(aBuf))
385 {
386 return;
387 }
388#endif
389 }
390 }
391
392 // no binary directory found, use $PATH on Posix, $PWD on Windows
393 m_aBinarydir[0] = '\0';
394 }
395
396 int NumPaths() const override
397 {
398 return m_NumPaths;
399 }
400
401 struct SListDirectoryInfoUniqueCallbackData
402 {
403 FS_LISTDIR_CALLBACK_FILEINFO m_pfnDelegate;
404 void *m_pDelegateUser;
405 std::unordered_set<std::string> m_Seen;
406 };
407
408 static int ListDirectoryInfoUniqueCallback(const CFsFileInfo *pInfo, int IsDir, int Type, void *pUser)
409 {
410 SListDirectoryInfoUniqueCallbackData *pData = static_cast<SListDirectoryInfoUniqueCallbackData *>(pUser);
411 auto [_, InsertionTookPlace] = pData->m_Seen.emplace(args: pInfo->m_pName);
412 if(InsertionTookPlace)
413 return pData->m_pfnDelegate(pInfo, IsDir, Type, pData->m_pDelegateUser);
414 return 0;
415 }
416
417 void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_CALLBACK_FILEINFO pfnCallback, void *pUser) override
418 {
419 char aBuffer[IO_MAX_PATH_LENGTH];
420 if(Type == TYPE_ALL)
421 {
422 SListDirectoryInfoUniqueCallbackData Data;
423 Data.m_pfnDelegate = pfnCallback;
424 Data.m_pDelegateUser = pUser;
425 // list all available directories
426 for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
427 fs_listdir_fileinfo(dir: GetPath(Type: i, pDir: pPath, pBuffer: aBuffer, BufferSize: sizeof(aBuffer)), cb: ListDirectoryInfoUniqueCallback, type: i, user: &Data);
428 }
429 else if(Type >= TYPE_SAVE && Type < m_NumPaths)
430 {
431 // list wanted directory
432 fs_listdir_fileinfo(dir: GetPath(Type, pDir: pPath, pBuffer: aBuffer, BufferSize: sizeof(aBuffer)), cb: pfnCallback, type: Type, user: pUser);
433 }
434 else
435 {
436 dbg_assert_failed("Type invalid");
437 }
438 }
439
440 struct SListDirectoryUniqueCallbackData
441 {
442 FS_LISTDIR_CALLBACK m_pfnDelegate;
443 void *m_pDelegateUser;
444 std::unordered_set<std::string> m_Seen;
445 };
446
447 static int ListDirectoryUniqueCallback(const char *pName, int IsDir, int Type, void *pUser)
448 {
449 SListDirectoryUniqueCallbackData *pData = static_cast<SListDirectoryUniqueCallbackData *>(pUser);
450 auto [_, InsertionTookPlace] = pData->m_Seen.emplace(args&: pName);
451 if(InsertionTookPlace)
452 return pData->m_pfnDelegate(pName, IsDir, Type, pData->m_pDelegateUser);
453 return 0;
454 }
455
456 void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) override
457 {
458 char aBuffer[IO_MAX_PATH_LENGTH];
459 if(Type == TYPE_ALL)
460 {
461 SListDirectoryUniqueCallbackData Data;
462 Data.m_pfnDelegate = pfnCallback;
463 Data.m_pDelegateUser = pUser;
464 // list all available directories
465 for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
466 fs_listdir(dir: GetPath(Type: i, pDir: pPath, pBuffer: aBuffer, BufferSize: sizeof(aBuffer)), cb: ListDirectoryUniqueCallback, type: i, user: &Data);
467 }
468 else if(Type >= TYPE_SAVE && Type < m_NumPaths)
469 {
470 // list wanted directory
471 fs_listdir(dir: GetPath(Type, pDir: pPath, pBuffer: aBuffer, BufferSize: sizeof(aBuffer)), cb: pfnCallback, type: Type, user: pUser);
472 }
473 else
474 {
475 dbg_assert_failed("Type invalid");
476 }
477 }
478
479 const char *GetPath(int Type, const char *pDir, char *pBuffer, unsigned BufferSize) const
480 {
481 if(Type == TYPE_ABSOLUTE)
482 {
483 str_copy(dst: pBuffer, src: pDir, dst_size: BufferSize);
484 }
485 else
486 {
487 str_format(buffer: pBuffer, buffer_size: BufferSize, format: "%s%s%s", m_aaStoragePaths[Type], !m_aaStoragePaths[Type][0] ? "" : "/", pDir);
488 }
489 return pBuffer;
490 }
491
492 void TranslateType(int &Type, const char *pPath) const
493 {
494 if(Type == TYPE_SAVE_OR_ABSOLUTE)
495 Type = fs_is_relative_path(path: pPath) ? TYPE_SAVE : TYPE_ABSOLUTE;
496 else if(Type == TYPE_ALL_OR_ABSOLUTE)
497 Type = fs_is_relative_path(path: pPath) ? TYPE_ALL : TYPE_ABSOLUTE;
498 }
499
500 IOHANDLE OpenFile(const char *pFilename, int Flags, int Type, char *pBuffer = nullptr, int BufferSize = 0) override
501 {
502 TranslateType(Type, pPath: pFilename);
503
504 dbg_assert((Flags & IOFLAG_WRITE) == 0 || Type == TYPE_SAVE || Type == TYPE_ABSOLUTE, "IOFLAG_WRITE only usable with TYPE_SAVE and TYPE_ABSOLUTE");
505
506 char aBuffer[IO_MAX_PATH_LENGTH];
507 if(!pBuffer)
508 {
509 pBuffer = aBuffer;
510 BufferSize = sizeof(aBuffer);
511 }
512 pBuffer[0] = '\0';
513
514 if(Type == TYPE_ABSOLUTE)
515 {
516 return io_open(filename: GetPath(Type: TYPE_ABSOLUTE, pDir: pFilename, pBuffer, BufferSize), flags: Flags);
517 }
518
519 if(str_startswith(str: pFilename, prefix: "mapres/../skins/"))
520 {
521 pFilename = pFilename + str_length(str: "mapres/../");
522 }
523 if(pFilename[0] == '/' || pFilename[0] == '\\' || str_find(haystack: pFilename, needle: "../") != nullptr || str_find(haystack: pFilename, needle: "..\\") != nullptr
524#ifdef CONF_FAMILY_WINDOWS
525 || (pFilename[0] && pFilename[1] == ':')
526#endif
527 )
528 {
529 // don't escape base directory
530 return nullptr;
531 }
532 else if(Type == TYPE_ALL)
533 {
534 // check all available directories
535 for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
536 {
537 IOHANDLE Handle = io_open(filename: GetPath(Type: i, pDir: pFilename, pBuffer, BufferSize), flags: Flags);
538 if(Handle)
539 {
540 return Handle;
541 }
542 }
543 return nullptr;
544 }
545 else if(Type >= TYPE_SAVE && Type < m_NumPaths)
546 {
547 // check wanted directory
548 return io_open(filename: GetPath(Type, pDir: pFilename, pBuffer, BufferSize), flags: Flags);
549 }
550 else
551 {
552 dbg_assert_failed("Type invalid");
553 }
554 }
555
556 template<typename F>
557 bool GenericExists(const char *pFilename, int Type, F &&CheckFunction) const
558 {
559 TranslateType(Type, pPath: pFilename);
560
561 char aBuffer[IO_MAX_PATH_LENGTH];
562 if(Type == TYPE_ALL)
563 {
564 // check all available directories
565 for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
566 {
567 if(CheckFunction(GetPath(Type: i, pDir: pFilename, pBuffer: aBuffer, BufferSize: sizeof(aBuffer))))
568 return true;
569 }
570 return false;
571 }
572 else if(Type == TYPE_ABSOLUTE || (Type >= TYPE_SAVE && Type < m_NumPaths))
573 {
574 // check wanted directory
575 return CheckFunction(GetPath(Type, pDir: pFilename, pBuffer: aBuffer, BufferSize: sizeof(aBuffer)));
576 }
577 else
578 {
579 dbg_assert_failed("Type invalid");
580 }
581 }
582
583 bool FileExists(const char *pFilename, int Type) override
584 {
585 return GenericExists(pFilename, Type, CheckFunction&: fs_is_file);
586 }
587
588 bool FolderExists(const char *pFilename, int Type) override
589 {
590 return GenericExists(pFilename, Type, CheckFunction&: fs_is_dir);
591 }
592
593 bool ReadFile(const char *pFilename, int Type, void **ppResult, unsigned *pResultLen) override
594 {
595 IOHANDLE File = OpenFile(pFilename, Flags: IOFLAG_READ, Type);
596 if(!File)
597 {
598 *ppResult = nullptr;
599 *pResultLen = 0;
600 return false;
601 }
602 const bool ReadSuccess = io_read_all(io: File, result: ppResult, result_len: pResultLen);
603 io_close(io: File);
604 if(!ReadSuccess)
605 {
606 *ppResult = nullptr;
607 *pResultLen = 0;
608 return false;
609 }
610 return true;
611 }
612
613 char *ReadFileStr(const char *pFilename, int Type) override
614 {
615 IOHANDLE File = OpenFile(pFilename, Flags: IOFLAG_READ, Type);
616 if(!File)
617 return nullptr;
618 char *pResult = io_read_all_str(io: File);
619 io_close(io: File);
620 return pResult;
621 }
622
623 bool RetrieveTimes(const char *pFilename, int Type, time_t *pCreated, time_t *pModified) override
624 {
625 dbg_assert(Type == TYPE_ABSOLUTE || (Type >= TYPE_SAVE && Type < m_NumPaths), "Type invalid");
626
627 char aBuffer[IO_MAX_PATH_LENGTH];
628 return fs_file_time(name: GetPath(Type, pDir: pFilename, pBuffer: aBuffer, BufferSize: sizeof(aBuffer)), created: pCreated, modified: pModified) == 0;
629 }
630
631 bool CalculateHashes(const char *pFilename, int Type, SHA256_DIGEST *pSha256, unsigned *pCrc) override
632 {
633 dbg_assert(pSha256 != nullptr || pCrc != nullptr, "At least one output argument required");
634
635 IOHANDLE File = OpenFile(pFilename, Flags: IOFLAG_READ, Type);
636 if(!File)
637 return false;
638
639 SHA256_CTX Sha256Ctxt;
640 if(pSha256 != nullptr)
641 sha256_init(ctxt: &Sha256Ctxt);
642 if(pCrc != nullptr)
643 *pCrc = 0;
644 unsigned char aBuffer[64 * 1024];
645 while(true)
646 {
647 unsigned Bytes = io_read(io: File, buffer: aBuffer, size: sizeof(aBuffer));
648 if(Bytes == 0)
649 break;
650 if(pSha256 != nullptr)
651 sha256_update(ctxt: &Sha256Ctxt, data: aBuffer, data_len: Bytes);
652 if(pCrc != nullptr)
653 *pCrc = crc32(crc: *pCrc, buf: aBuffer, len: Bytes);
654 }
655 if(pSha256 != nullptr)
656 *pSha256 = sha256_finish(ctxt: &Sha256Ctxt);
657
658 io_close(io: File);
659 return true;
660 }
661
662 struct CFindCBData
663 {
664 CStorage *m_pStorage;
665 const char *m_pFilename;
666 const char *m_pPath;
667 char *m_pBuffer;
668 int m_BufferSize;
669 };
670
671 static int FindFileCallback(const char *pName, int IsDir, int Type, void *pUser)
672 {
673 CFindCBData Data = *static_cast<CFindCBData *>(pUser);
674 if(IsDir)
675 {
676 if(pName[0] == '.')
677 return 0;
678
679 // search within the folder
680 char aBuf[IO_MAX_PATH_LENGTH];
681 char aPath[IO_MAX_PATH_LENGTH];
682 str_format(buffer: aPath, buffer_size: sizeof(aPath), format: "%s/%s", Data.m_pPath, pName);
683 Data.m_pPath = aPath;
684 fs_listdir(dir: Data.m_pStorage->GetPath(Type, pDir: aPath, pBuffer: aBuf, BufferSize: sizeof(aBuf)), cb: FindFileCallback, type: Type, user: &Data);
685 if(Data.m_pBuffer[0])
686 return 1;
687 }
688 else if(!str_comp(a: pName, b: Data.m_pFilename))
689 {
690 // found the file = end
691 str_format(buffer: Data.m_pBuffer, buffer_size: Data.m_BufferSize, format: "%s/%s", Data.m_pPath, Data.m_pFilename);
692 return 1;
693 }
694
695 return 0;
696 }
697
698 bool FindFile(const char *pFilename, const char *pPath, int Type, char *pBuffer, int BufferSize) override
699 {
700 dbg_assert(BufferSize >= 1, "BufferSize invalid");
701
702 pBuffer[0] = 0;
703
704 CFindCBData Data;
705 Data.m_pStorage = this;
706 Data.m_pFilename = pFilename;
707 Data.m_pPath = pPath;
708 Data.m_pBuffer = pBuffer;
709 Data.m_BufferSize = BufferSize;
710
711 char aBuf[IO_MAX_PATH_LENGTH];
712 if(Type == TYPE_ALL)
713 {
714 // search within all available directories
715 for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
716 {
717 fs_listdir(dir: GetPath(Type: i, pDir: pPath, pBuffer: aBuf, BufferSize: sizeof(aBuf)), cb: FindFileCallback, type: i, user: &Data);
718 if(pBuffer[0])
719 return true;
720 }
721 }
722 else if(Type >= TYPE_SAVE && Type < m_NumPaths)
723 {
724 // search within wanted directory
725 fs_listdir(dir: GetPath(Type, pDir: pPath, pBuffer: aBuf, BufferSize: sizeof(aBuf)), cb: FindFileCallback, type: Type, user: &Data);
726 }
727 else
728 {
729 dbg_assert_failed("Type invalid");
730 }
731
732 return pBuffer[0] != 0;
733 }
734
735 struct SFindFilesCallbackData
736 {
737 CStorage *m_pStorage;
738 const char *m_pFilename;
739 const char *m_pPath;
740 std::set<std::string> *m_pEntries;
741 };
742
743 static int FindFilesCallback(const char *pName, int IsDir, int Type, void *pUser)
744 {
745 SFindFilesCallbackData Data = *static_cast<SFindFilesCallbackData *>(pUser);
746 if(IsDir)
747 {
748 if(pName[0] == '.')
749 return 0;
750
751 // search within the folder
752 char aBuf[IO_MAX_PATH_LENGTH];
753 char aPath[IO_MAX_PATH_LENGTH];
754 str_format(buffer: aPath, buffer_size: sizeof(aPath), format: "%s/%s", Data.m_pPath, pName);
755 Data.m_pPath = aPath;
756 fs_listdir(dir: Data.m_pStorage->GetPath(Type, pDir: aPath, pBuffer: aBuf, BufferSize: sizeof(aBuf)), cb: FindFilesCallback, type: Type, user: &Data);
757 }
758 else if(!str_comp(a: pName, b: Data.m_pFilename))
759 {
760 char aBuffer[IO_MAX_PATH_LENGTH];
761 str_format(buffer: aBuffer, buffer_size: sizeof(aBuffer), format: "%s/%s", Data.m_pPath, Data.m_pFilename);
762 Data.m_pEntries->emplace(args&: aBuffer);
763 }
764
765 return 0;
766 }
767
768 size_t FindFiles(const char *pFilename, const char *pPath, int Type, std::set<std::string> *pEntries) override
769 {
770 SFindFilesCallbackData Data;
771 Data.m_pStorage = this;
772 Data.m_pFilename = pFilename;
773 Data.m_pPath = pPath;
774 Data.m_pEntries = pEntries;
775
776 char aBuf[IO_MAX_PATH_LENGTH];
777 if(Type == TYPE_ALL)
778 {
779 // search within all available directories
780 for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
781 {
782 fs_listdir(dir: GetPath(Type: i, pDir: pPath, pBuffer: aBuf, BufferSize: sizeof(aBuf)), cb: FindFilesCallback, type: i, user: &Data);
783 }
784 }
785 else if(Type >= TYPE_SAVE && Type < m_NumPaths)
786 {
787 // search within wanted directory
788 fs_listdir(dir: GetPath(Type, pDir: pPath, pBuffer: aBuf, BufferSize: sizeof(aBuf)), cb: FindFilesCallback, type: Type, user: &Data);
789 }
790 else
791 {
792 dbg_assert_failed("Type invalid");
793 }
794
795 return pEntries->size();
796 }
797
798 bool RemoveFile(const char *pFilename, int Type) override
799 {
800 dbg_assert(Type == TYPE_ABSOLUTE || (Type >= TYPE_SAVE && Type < m_NumPaths), "Type invalid");
801
802 char aBuffer[IO_MAX_PATH_LENGTH];
803 GetPath(Type, pDir: pFilename, pBuffer: aBuffer, BufferSize: sizeof(aBuffer));
804
805 return fs_remove(filename: aBuffer) == 0;
806 }
807
808 bool RemoveFolder(const char *pFilename, int Type) override
809 {
810 dbg_assert(Type == TYPE_ABSOLUTE || (Type >= TYPE_SAVE && Type < m_NumPaths), "Type invalid");
811
812 char aBuffer[IO_MAX_PATH_LENGTH];
813 GetPath(Type, pDir: pFilename, pBuffer: aBuffer, BufferSize: sizeof(aBuffer));
814
815 return fs_removedir(path: aBuffer) == 0;
816 }
817
818 bool RemoveBinaryFile(const char *pFilename) override
819 {
820 char aBuffer[IO_MAX_PATH_LENGTH];
821 GetBinaryPath(pFilename, pBuffer: aBuffer, BufferSize: sizeof(aBuffer));
822
823 return fs_remove(filename: aBuffer) == 0;
824 }
825
826 bool RenameFile(const char *pOldFilename, const char *pNewFilename, int Type) override
827 {
828 dbg_assert(Type >= TYPE_SAVE && Type < m_NumPaths, "Type invalid");
829
830 char aOldBuffer[IO_MAX_PATH_LENGTH];
831 char aNewBuffer[IO_MAX_PATH_LENGTH];
832 GetPath(Type, pDir: pOldFilename, pBuffer: aOldBuffer, BufferSize: sizeof(aOldBuffer));
833 GetPath(Type, pDir: pNewFilename, pBuffer: aNewBuffer, BufferSize: sizeof(aNewBuffer));
834
835 return fs_rename(oldname: aOldBuffer, newname: aNewBuffer) == 0;
836 }
837
838 bool RenameBinaryFile(const char *pOldFilename, const char *pNewFilename) override
839 {
840 char aOldBuffer[IO_MAX_PATH_LENGTH];
841 char aNewBuffer[IO_MAX_PATH_LENGTH];
842 GetBinaryPath(pFilename: pOldFilename, pBuffer: aOldBuffer, BufferSize: sizeof(aOldBuffer));
843 GetBinaryPath(pFilename: pNewFilename, pBuffer: aNewBuffer, BufferSize: sizeof(aNewBuffer));
844
845 if(fs_makedir_rec_for(path: aNewBuffer) < 0)
846 {
847 log_error("storage", "failed to create folders for: %s", aNewBuffer);
848 return false;
849 }
850
851 return fs_rename(oldname: aOldBuffer, newname: aNewBuffer) == 0;
852 }
853
854 bool CreateFolder(const char *pFoldername, int Type) override
855 {
856 dbg_assert(Type >= TYPE_SAVE && Type < m_NumPaths, "Type invalid");
857
858 char aBuffer[IO_MAX_PATH_LENGTH];
859 GetPath(Type, pDir: pFoldername, pBuffer: aBuffer, BufferSize: sizeof(aBuffer));
860
861 return fs_makedir(path: aBuffer) == 0;
862 }
863
864 void GetCompletePath(int Type, const char *pDir, char *pBuffer, unsigned BufferSize) override
865 {
866 TranslateType(Type, pPath: pDir);
867 dbg_assert(Type >= TYPE_SAVE && Type < m_NumPaths, "Type invalid");
868 GetPath(Type, pDir, pBuffer, BufferSize);
869 }
870
871 const char *GetBinaryPath(const char *pFilename, char *pBuffer, unsigned BufferSize) override
872 {
873 str_format(buffer: pBuffer, buffer_size: BufferSize, format: "%s%s%s", m_aBinarydir, !m_aBinarydir[0] ? "" : "/", pFilename);
874 return pBuffer;
875 }
876
877 const char *GetBinaryPathAbsolute(const char *pFilename, char *pBuffer, unsigned BufferSize) override
878 {
879 char aBinaryPath[IO_MAX_PATH_LENGTH];
880 GetBinaryPath(pFilename, pBuffer: aBinaryPath, BufferSize: sizeof(aBinaryPath));
881 if(fs_is_relative_path(path: aBinaryPath))
882 {
883 if(fs_getcwd(buffer: pBuffer, buffer_size: BufferSize))
884 {
885 str_append(dst: pBuffer, src: "/", dst_size: BufferSize);
886 str_append(dst: pBuffer, src: aBinaryPath, dst_size: BufferSize);
887 }
888 }
889 else
890 str_copy(dst: pBuffer, src: aBinaryPath, dst_size: BufferSize);
891 return pBuffer;
892 }
893
894 static IStorage *Create(EInitializationType InitializationType, int NumArgs, const char **ppArguments)
895 {
896 CStorage *pStorage = new CStorage();
897 if(!pStorage->Init(InitializationType, NumArgs, ppArguments))
898 {
899 delete pStorage;
900 return nullptr;
901 }
902 return pStorage;
903 }
904};
905
906void IStorage::StripPathAndExtension(const char *pFilename, char *pBuffer, int BufferSize)
907{
908 const char *pFilenameEnd = pFilename + str_length(str: pFilename);
909 const char *pExtractedName = pFilename;
910 const char *pEnd = pFilenameEnd;
911 for(const char *pIter = pFilename; *pIter; pIter++)
912 {
913 if(*pIter == '/' || *pIter == '\\')
914 {
915 pExtractedName = pIter + 1;
916 pEnd = pFilenameEnd;
917 }
918 else if(*pIter == '.')
919 {
920 pEnd = pIter;
921 }
922 }
923
924 int Length = minimum(a: BufferSize, b: (int)(pEnd - pExtractedName + 1));
925 str_copy(dst: pBuffer, src: pExtractedName, dst_size: Length);
926}
927
928const char *IStorage::FormatTmpPath(char *aBuf, unsigned BufSize, const char *pPath)
929{
930 str_format(buffer: aBuf, buffer_size: BufSize, format: "%s.%d.tmp", pPath, pid());
931 return aBuf;
932}
933
934IStorage *CreateStorage(IStorage::EInitializationType InitializationType, int NumArgs, const char **ppArguments)
935{
936 return CStorage::Create(InitializationType, NumArgs, ppArguments);
937}
938
939std::unique_ptr<IStorage> CreateLocalStorage()
940{
941 std::unique_ptr<CStorage> pStorage = std::make_unique<CStorage>();
942 if(!pStorage->FindCurrentDirectory() ||
943 !pStorage->AddPath(pPath: "$CURRENTDIR"))
944 {
945 return std::unique_ptr<IStorage>(nullptr);
946 }
947 return pStorage;
948}
949
950std::unique_ptr<IStorage> CreateTempStorage(const char *pDirectory, int NumArgs, const char **ppArguments)
951{
952 dbg_assert(NumArgs > 0, "Expected at least one argument");
953 std::unique_ptr<CStorage> pStorage = std::make_unique<CStorage>();
954 pStorage->FindDataDirectory(pArgv0: ppArguments[0]);
955 if(!pStorage->FindCurrentDirectory() ||
956 !pStorage->AddPath(pPath: pDirectory) ||
957 !pStorage->AddPath(pPath: "$DATADIR") ||
958 !pStorage->AddPath(pPath: "$CURRENTDIR"))
959 {
960 return std::unique_ptr<IStorage>(nullptr);
961 }
962 return pStorage;
963}
964