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 if(fs_executable_path(buffer: m_aBinarydir, buffer_size: sizeof(m_aBinarydir)) == 0)
366 {
367 fs_parent_dir(path: m_aBinarydir);
368 return;
369 }
370
371 // check for usable path in argv[0]
372 {
373 unsigned int Pos = ~0U;
374 for(unsigned i = 0; pArgv0[i]; i++)
375 if(pArgv0[i] == '/' || pArgv0[i] == '\\')
376 Pos = i;
377
378 if(Pos < IO_MAX_PATH_LENGTH)
379 {
380 char aBuf[IO_MAX_PATH_LENGTH];
381 str_copy(dst: m_aBinarydir, src: pArgv0, dst_size: Pos + 1);
382 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s/" PLAT_SERVER_EXEC, m_aBinarydir);
383 if(fs_is_file(path: aBuf))
384 {
385 return;
386 }
387 // Also look for client binary. (see https://github.com/ddnet/ddnet/issues/11418)
388 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s/" PLAT_CLIENT_EXEC, m_aBinarydir);
389 if(fs_is_file(path: aBuf))
390 {
391 return;
392 }
393 }
394 }
395
396 // no binary directory found, use $PATH on Posix, $PWD on Windows
397 m_aBinarydir[0] = '\0';
398 }
399
400 int NumPaths() const override
401 {
402 return m_NumPaths;
403 }
404
405 struct SListDirectoryInfoUniqueCallbackData
406 {
407 FS_LISTDIR_CALLBACK_FILEINFO m_pfnDelegate;
408 void *m_pDelegateUser;
409 std::unordered_set<std::string> m_Seen;
410 };
411
412 static int ListDirectoryInfoUniqueCallback(const CFsFileInfo *pInfo, int IsDir, int Type, void *pUser)
413 {
414 SListDirectoryInfoUniqueCallbackData *pData = static_cast<SListDirectoryInfoUniqueCallbackData *>(pUser);
415 auto [_, InsertionTookPlace] = pData->m_Seen.emplace(args: pInfo->m_pName);
416 if(InsertionTookPlace)
417 return pData->m_pfnDelegate(pInfo, IsDir, Type, pData->m_pDelegateUser);
418 return 0;
419 }
420
421 void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_CALLBACK_FILEINFO pfnCallback, void *pUser) override
422 {
423 char aBuffer[IO_MAX_PATH_LENGTH];
424 if(Type == TYPE_ALL)
425 {
426 SListDirectoryInfoUniqueCallbackData Data;
427 Data.m_pfnDelegate = pfnCallback;
428 Data.m_pDelegateUser = pUser;
429 // list all available directories
430 for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
431 fs_listdir_fileinfo(dir: GetPath(Type: i, pDir: pPath, pBuffer: aBuffer, BufferSize: sizeof(aBuffer)), cb: ListDirectoryInfoUniqueCallback, type: i, user: &Data);
432 }
433 else if(Type >= TYPE_SAVE && Type < m_NumPaths)
434 {
435 // list wanted directory
436 fs_listdir_fileinfo(dir: GetPath(Type, pDir: pPath, pBuffer: aBuffer, BufferSize: sizeof(aBuffer)), cb: pfnCallback, type: Type, user: pUser);
437 }
438 else
439 {
440 dbg_assert_failed("Type invalid");
441 }
442 }
443
444 struct SListDirectoryUniqueCallbackData
445 {
446 FS_LISTDIR_CALLBACK m_pfnDelegate;
447 void *m_pDelegateUser;
448 std::unordered_set<std::string> m_Seen;
449 };
450
451 static int ListDirectoryUniqueCallback(const char *pName, int IsDir, int Type, void *pUser)
452 {
453 SListDirectoryUniqueCallbackData *pData = static_cast<SListDirectoryUniqueCallbackData *>(pUser);
454 auto [_, InsertionTookPlace] = pData->m_Seen.emplace(args&: pName);
455 if(InsertionTookPlace)
456 return pData->m_pfnDelegate(pName, IsDir, Type, pData->m_pDelegateUser);
457 return 0;
458 }
459
460 void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) override
461 {
462 char aBuffer[IO_MAX_PATH_LENGTH];
463 if(Type == TYPE_ALL)
464 {
465 SListDirectoryUniqueCallbackData Data;
466 Data.m_pfnDelegate = pfnCallback;
467 Data.m_pDelegateUser = pUser;
468 // list all available directories
469 for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
470 fs_listdir(dir: GetPath(Type: i, pDir: pPath, pBuffer: aBuffer, BufferSize: sizeof(aBuffer)), cb: ListDirectoryUniqueCallback, type: i, user: &Data);
471 }
472 else if(Type >= TYPE_SAVE && Type < m_NumPaths)
473 {
474 // list wanted directory
475 fs_listdir(dir: GetPath(Type, pDir: pPath, pBuffer: aBuffer, BufferSize: sizeof(aBuffer)), cb: pfnCallback, type: Type, user: pUser);
476 }
477 else
478 {
479 dbg_assert_failed("Type invalid");
480 }
481 }
482
483 const char *GetPath(int Type, const char *pDir, char *pBuffer, unsigned BufferSize) const
484 {
485 if(Type == TYPE_ABSOLUTE)
486 {
487 str_copy(dst: pBuffer, src: pDir, dst_size: BufferSize);
488 }
489 else
490 {
491 str_format(buffer: pBuffer, buffer_size: BufferSize, format: "%s%s%s", m_aaStoragePaths[Type], !m_aaStoragePaths[Type][0] ? "" : "/", pDir);
492 }
493 return pBuffer;
494 }
495
496 void TranslateType(int &Type, const char *pPath) const
497 {
498 if(Type == TYPE_SAVE_OR_ABSOLUTE)
499 Type = fs_is_relative_path(path: pPath) ? TYPE_SAVE : TYPE_ABSOLUTE;
500 else if(Type == TYPE_ALL_OR_ABSOLUTE)
501 Type = fs_is_relative_path(path: pPath) ? TYPE_ALL : TYPE_ABSOLUTE;
502 }
503
504 IOHANDLE OpenFile(const char *pFilename, int Flags, int Type, char *pBuffer = nullptr, int BufferSize = 0) override
505 {
506 TranslateType(Type, pPath: pFilename);
507
508 dbg_assert((Flags & IOFLAG_WRITE) == 0 || Type == TYPE_SAVE || Type == TYPE_ABSOLUTE, "IOFLAG_WRITE only usable with TYPE_SAVE and TYPE_ABSOLUTE");
509
510 char aBuffer[IO_MAX_PATH_LENGTH];
511 if(!pBuffer)
512 {
513 pBuffer = aBuffer;
514 BufferSize = sizeof(aBuffer);
515 }
516 pBuffer[0] = '\0';
517
518 if(Type == TYPE_ABSOLUTE)
519 {
520 return io_open(filename: GetPath(Type: TYPE_ABSOLUTE, pDir: pFilename, pBuffer, BufferSize), flags: Flags);
521 }
522
523 if(str_startswith(str: pFilename, prefix: "mapres/../skins/"))
524 {
525 pFilename = pFilename + str_length(str: "mapres/../");
526 }
527 if(pFilename[0] == '/' || pFilename[0] == '\\' || str_find(haystack: pFilename, needle: "../") != nullptr || str_find(haystack: pFilename, needle: "..\\") != nullptr
528#ifdef CONF_FAMILY_WINDOWS
529 || (pFilename[0] && pFilename[1] == ':')
530#endif
531 )
532 {
533 // don't escape base directory
534 return nullptr;
535 }
536 else if(Type == TYPE_ALL)
537 {
538 // check all available directories
539 for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
540 {
541 IOHANDLE Handle = io_open(filename: GetPath(Type: i, pDir: pFilename, pBuffer, BufferSize), flags: Flags);
542 if(Handle)
543 {
544 return Handle;
545 }
546 }
547 return nullptr;
548 }
549 else if(Type >= TYPE_SAVE && Type < m_NumPaths)
550 {
551 // check wanted directory
552 return io_open(filename: GetPath(Type, pDir: pFilename, pBuffer, BufferSize), flags: Flags);
553 }
554 else
555 {
556 dbg_assert_failed("Type invalid");
557 }
558 }
559
560 template<typename F>
561 bool GenericExists(const char *pFilename, int Type, F &&CheckFunction) const
562 {
563 TranslateType(Type, pPath: pFilename);
564
565 char aBuffer[IO_MAX_PATH_LENGTH];
566 if(Type == TYPE_ALL)
567 {
568 // check all available directories
569 for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
570 {
571 if(CheckFunction(GetPath(Type: i, pDir: pFilename, pBuffer: aBuffer, BufferSize: sizeof(aBuffer))))
572 return true;
573 }
574 return false;
575 }
576 else if(Type == TYPE_ABSOLUTE || (Type >= TYPE_SAVE && Type < m_NumPaths))
577 {
578 // check wanted directory
579 return CheckFunction(GetPath(Type, pDir: pFilename, pBuffer: aBuffer, BufferSize: sizeof(aBuffer)));
580 }
581 else
582 {
583 dbg_assert_failed("Type invalid");
584 }
585 }
586
587 bool FileExists(const char *pFilename, int Type) override
588 {
589 return GenericExists(pFilename, Type, CheckFunction&: fs_is_file);
590 }
591
592 bool FolderExists(const char *pFilename, int Type) override
593 {
594 return GenericExists(pFilename, Type, CheckFunction&: fs_is_dir);
595 }
596
597 bool ReadFile(const char *pFilename, int Type, void **ppResult, unsigned *pResultLen) override
598 {
599 IOHANDLE File = OpenFile(pFilename, Flags: IOFLAG_READ, Type);
600 if(!File)
601 {
602 *ppResult = nullptr;
603 *pResultLen = 0;
604 return false;
605 }
606 const bool ReadSuccess = io_read_all(io: File, result: ppResult, result_len: pResultLen);
607 io_close(io: File);
608 if(!ReadSuccess)
609 {
610 *ppResult = nullptr;
611 *pResultLen = 0;
612 return false;
613 }
614 return true;
615 }
616
617 char *ReadFileStr(const char *pFilename, int Type) override
618 {
619 IOHANDLE File = OpenFile(pFilename, Flags: IOFLAG_READ, Type);
620 if(!File)
621 return nullptr;
622 char *pResult = io_read_all_str(io: File);
623 io_close(io: File);
624 return pResult;
625 }
626
627 bool RetrieveTimes(const char *pFilename, int Type, time_t *pCreated, time_t *pModified) override
628 {
629 dbg_assert(Type == TYPE_ABSOLUTE || (Type >= TYPE_SAVE && Type < m_NumPaths), "Type invalid");
630
631 char aBuffer[IO_MAX_PATH_LENGTH];
632 return fs_file_time(name: GetPath(Type, pDir: pFilename, pBuffer: aBuffer, BufferSize: sizeof(aBuffer)), created: pCreated, modified: pModified) == 0;
633 }
634
635 bool CalculateHashes(const char *pFilename, int Type, SHA256_DIGEST *pSha256, unsigned *pCrc) override
636 {
637 dbg_assert(pSha256 != nullptr || pCrc != nullptr, "At least one output argument required");
638
639 IOHANDLE File = OpenFile(pFilename, Flags: IOFLAG_READ, Type);
640 if(!File)
641 return false;
642
643 SHA256_CTX Sha256Ctxt;
644 if(pSha256 != nullptr)
645 sha256_init(ctxt: &Sha256Ctxt);
646 if(pCrc != nullptr)
647 *pCrc = 0;
648 unsigned char aBuffer[64 * 1024];
649 while(true)
650 {
651 unsigned Bytes = io_read(io: File, buffer: aBuffer, size: sizeof(aBuffer));
652 if(Bytes == 0)
653 break;
654 if(pSha256 != nullptr)
655 sha256_update(ctxt: &Sha256Ctxt, data: aBuffer, data_len: Bytes);
656 if(pCrc != nullptr)
657 *pCrc = crc32(crc: *pCrc, buf: aBuffer, len: Bytes);
658 }
659 if(pSha256 != nullptr)
660 *pSha256 = sha256_finish(ctxt: &Sha256Ctxt);
661
662 io_close(io: File);
663 return true;
664 }
665
666 struct CFindCBData
667 {
668 CStorage *m_pStorage;
669 const char *m_pFilename;
670 const char *m_pPath;
671 char *m_pBuffer;
672 int m_BufferSize;
673 };
674
675 static int FindFileCallback(const char *pName, int IsDir, int Type, void *pUser)
676 {
677 CFindCBData Data = *static_cast<CFindCBData *>(pUser);
678 if(IsDir)
679 {
680 if(pName[0] == '.')
681 return 0;
682
683 // search within the folder
684 char aBuf[IO_MAX_PATH_LENGTH];
685 char aPath[IO_MAX_PATH_LENGTH];
686 str_format(buffer: aPath, buffer_size: sizeof(aPath), format: "%s/%s", Data.m_pPath, pName);
687 Data.m_pPath = aPath;
688 fs_listdir(dir: Data.m_pStorage->GetPath(Type, pDir: aPath, pBuffer: aBuf, BufferSize: sizeof(aBuf)), cb: FindFileCallback, type: Type, user: &Data);
689 if(Data.m_pBuffer[0])
690 return 1;
691 }
692 else if(!str_comp(a: pName, b: Data.m_pFilename))
693 {
694 // found the file = end
695 str_format(buffer: Data.m_pBuffer, buffer_size: Data.m_BufferSize, format: "%s/%s", Data.m_pPath, Data.m_pFilename);
696 return 1;
697 }
698
699 return 0;
700 }
701
702 bool FindFile(const char *pFilename, const char *pPath, int Type, char *pBuffer, int BufferSize) override
703 {
704 dbg_assert(BufferSize >= 1, "BufferSize invalid");
705
706 pBuffer[0] = 0;
707
708 CFindCBData Data;
709 Data.m_pStorage = this;
710 Data.m_pFilename = pFilename;
711 Data.m_pPath = pPath;
712 Data.m_pBuffer = pBuffer;
713 Data.m_BufferSize = BufferSize;
714
715 char aBuf[IO_MAX_PATH_LENGTH];
716 if(Type == TYPE_ALL)
717 {
718 // search within all available directories
719 for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
720 {
721 fs_listdir(dir: GetPath(Type: i, pDir: pPath, pBuffer: aBuf, BufferSize: sizeof(aBuf)), cb: FindFileCallback, type: i, user: &Data);
722 if(pBuffer[0])
723 return true;
724 }
725 }
726 else if(Type >= TYPE_SAVE && Type < m_NumPaths)
727 {
728 // search within wanted directory
729 fs_listdir(dir: GetPath(Type, pDir: pPath, pBuffer: aBuf, BufferSize: sizeof(aBuf)), cb: FindFileCallback, type: Type, user: &Data);
730 }
731 else
732 {
733 dbg_assert_failed("Type invalid");
734 }
735
736 return pBuffer[0] != 0;
737 }
738
739 struct SFindFilesCallbackData
740 {
741 CStorage *m_pStorage;
742 const char *m_pFilename;
743 const char *m_pPath;
744 std::set<std::string> *m_pEntries;
745 };
746
747 static int FindFilesCallback(const char *pName, int IsDir, int Type, void *pUser)
748 {
749 SFindFilesCallbackData Data = *static_cast<SFindFilesCallbackData *>(pUser);
750 if(IsDir)
751 {
752 if(pName[0] == '.')
753 return 0;
754
755 // search within the folder
756 char aBuf[IO_MAX_PATH_LENGTH];
757 char aPath[IO_MAX_PATH_LENGTH];
758 str_format(buffer: aPath, buffer_size: sizeof(aPath), format: "%s/%s", Data.m_pPath, pName);
759 Data.m_pPath = aPath;
760 fs_listdir(dir: Data.m_pStorage->GetPath(Type, pDir: aPath, pBuffer: aBuf, BufferSize: sizeof(aBuf)), cb: FindFilesCallback, type: Type, user: &Data);
761 }
762 else if(!str_comp(a: pName, b: Data.m_pFilename))
763 {
764 char aBuffer[IO_MAX_PATH_LENGTH];
765 str_format(buffer: aBuffer, buffer_size: sizeof(aBuffer), format: "%s/%s", Data.m_pPath, Data.m_pFilename);
766 Data.m_pEntries->emplace(args&: aBuffer);
767 }
768
769 return 0;
770 }
771
772 size_t FindFiles(const char *pFilename, const char *pPath, int Type, std::set<std::string> *pEntries) override
773 {
774 SFindFilesCallbackData Data;
775 Data.m_pStorage = this;
776 Data.m_pFilename = pFilename;
777 Data.m_pPath = pPath;
778 Data.m_pEntries = pEntries;
779
780 char aBuf[IO_MAX_PATH_LENGTH];
781 if(Type == TYPE_ALL)
782 {
783 // search within all available directories
784 for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
785 {
786 fs_listdir(dir: GetPath(Type: i, pDir: pPath, pBuffer: aBuf, BufferSize: sizeof(aBuf)), cb: FindFilesCallback, type: i, user: &Data);
787 }
788 }
789 else if(Type >= TYPE_SAVE && Type < m_NumPaths)
790 {
791 // search within wanted directory
792 fs_listdir(dir: GetPath(Type, pDir: pPath, pBuffer: aBuf, BufferSize: sizeof(aBuf)), cb: FindFilesCallback, type: Type, user: &Data);
793 }
794 else
795 {
796 dbg_assert_failed("Type invalid");
797 }
798
799 return pEntries->size();
800 }
801
802 bool RemoveFile(const char *pFilename, int Type) override
803 {
804 dbg_assert(Type == TYPE_ABSOLUTE || (Type >= TYPE_SAVE && Type < m_NumPaths), "Type invalid");
805
806 char aBuffer[IO_MAX_PATH_LENGTH];
807 GetPath(Type, pDir: pFilename, pBuffer: aBuffer, BufferSize: sizeof(aBuffer));
808
809 return fs_remove(filename: aBuffer) == 0;
810 }
811
812 bool RemoveFolder(const char *pFilename, int Type) override
813 {
814 dbg_assert(Type == TYPE_ABSOLUTE || (Type >= TYPE_SAVE && Type < m_NumPaths), "Type invalid");
815
816 char aBuffer[IO_MAX_PATH_LENGTH];
817 GetPath(Type, pDir: pFilename, pBuffer: aBuffer, BufferSize: sizeof(aBuffer));
818
819 return fs_removedir(path: aBuffer) == 0;
820 }
821
822 bool RemoveBinaryFile(const char *pFilename) override
823 {
824 char aBuffer[IO_MAX_PATH_LENGTH];
825 GetBinaryPath(pFilename, pBuffer: aBuffer, BufferSize: sizeof(aBuffer));
826
827 return fs_remove(filename: aBuffer) == 0;
828 }
829
830 bool RenameFile(const char *pOldFilename, const char *pNewFilename, int Type) override
831 {
832 dbg_assert(Type >= TYPE_SAVE && Type < m_NumPaths, "Type invalid");
833
834 char aOldBuffer[IO_MAX_PATH_LENGTH];
835 char aNewBuffer[IO_MAX_PATH_LENGTH];
836 GetPath(Type, pDir: pOldFilename, pBuffer: aOldBuffer, BufferSize: sizeof(aOldBuffer));
837 GetPath(Type, pDir: pNewFilename, pBuffer: aNewBuffer, BufferSize: sizeof(aNewBuffer));
838
839 return fs_rename(oldname: aOldBuffer, newname: aNewBuffer) == 0;
840 }
841
842 bool RenameBinaryFile(const char *pOldFilename, const char *pNewFilename) override
843 {
844 char aOldBuffer[IO_MAX_PATH_LENGTH];
845 char aNewBuffer[IO_MAX_PATH_LENGTH];
846 GetBinaryPath(pFilename: pOldFilename, pBuffer: aOldBuffer, BufferSize: sizeof(aOldBuffer));
847 GetBinaryPath(pFilename: pNewFilename, pBuffer: aNewBuffer, BufferSize: sizeof(aNewBuffer));
848
849 if(fs_makedir_rec_for(path: aNewBuffer) < 0)
850 {
851 log_error("storage", "failed to create folders for: %s", aNewBuffer);
852 return false;
853 }
854
855 return fs_rename(oldname: aOldBuffer, newname: aNewBuffer) == 0;
856 }
857
858 bool CreateFolder(const char *pFoldername, int Type) override
859 {
860 dbg_assert(Type >= TYPE_SAVE && Type < m_NumPaths, "Type invalid");
861
862 char aBuffer[IO_MAX_PATH_LENGTH];
863 GetPath(Type, pDir: pFoldername, pBuffer: aBuffer, BufferSize: sizeof(aBuffer));
864
865 return fs_makedir(path: aBuffer) == 0;
866 }
867
868 void GetCompletePath(int Type, const char *pDir, char *pBuffer, unsigned BufferSize) override
869 {
870 TranslateType(Type, pPath: pDir);
871 dbg_assert(Type >= TYPE_SAVE && Type < m_NumPaths, "Type invalid");
872 GetPath(Type, pDir, pBuffer, BufferSize);
873 }
874
875 const char *GetBinaryPath(const char *pFilename, char *pBuffer, unsigned BufferSize) override
876 {
877 str_format(buffer: pBuffer, buffer_size: BufferSize, format: "%s%s%s", m_aBinarydir, !m_aBinarydir[0] ? "" : "/", pFilename);
878 return pBuffer;
879 }
880
881 const char *GetBinaryPathAbsolute(const char *pFilename, char *pBuffer, unsigned BufferSize) override
882 {
883 char aBinaryPath[IO_MAX_PATH_LENGTH];
884 GetBinaryPath(pFilename, pBuffer: aBinaryPath, BufferSize: sizeof(aBinaryPath));
885 if(fs_is_relative_path(path: aBinaryPath))
886 {
887 if(fs_getcwd(buffer: pBuffer, buffer_size: BufferSize))
888 {
889 str_append(dst: pBuffer, src: "/", dst_size: BufferSize);
890 str_append(dst: pBuffer, src: aBinaryPath, dst_size: BufferSize);
891 }
892 }
893 else
894 str_copy(dst: pBuffer, src: aBinaryPath, dst_size: BufferSize);
895 return pBuffer;
896 }
897
898 static IStorage *Create(EInitializationType InitializationType, int NumArgs, const char **ppArguments)
899 {
900 CStorage *pStorage = new CStorage();
901 if(!pStorage->Init(InitializationType, NumArgs, ppArguments))
902 {
903 delete pStorage;
904 return nullptr;
905 }
906 return pStorage;
907 }
908};
909
910void IStorage::StripPathAndExtension(const char *pFilename, char *pBuffer, int BufferSize)
911{
912 const char *pFilenameEnd = pFilename + str_length(str: pFilename);
913 const char *pExtractedName = pFilename;
914 const char *pEnd = pFilenameEnd;
915 for(const char *pIter = pFilename; *pIter; pIter++)
916 {
917 if(*pIter == '/' || *pIter == '\\')
918 {
919 pExtractedName = pIter + 1;
920 pEnd = pFilenameEnd;
921 }
922 else if(*pIter == '.')
923 {
924 pEnd = pIter;
925 }
926 }
927
928 int Length = minimum(a: BufferSize, b: (int)(pEnd - pExtractedName + 1));
929 str_copy(dst: pBuffer, src: pExtractedName, dst_size: Length);
930}
931
932const char *IStorage::FormatTmpPath(char *aBuf, unsigned BufSize, const char *pPath)
933{
934 str_format(buffer: aBuf, buffer_size: BufSize, format: "%s.%d.tmp", pPath, pid());
935 return aBuf;
936}
937
938IStorage *CreateStorage(IStorage::EInitializationType InitializationType, int NumArgs, const char **ppArguments)
939{
940 return CStorage::Create(InitializationType, NumArgs, ppArguments);
941}
942
943std::unique_ptr<IStorage> CreateLocalStorage()
944{
945 std::unique_ptr<CStorage> pStorage = std::make_unique<CStorage>();
946 if(!pStorage->FindCurrentDirectory() ||
947 !pStorage->AddPath(pPath: "$CURRENTDIR"))
948 {
949 return std::unique_ptr<IStorage>(nullptr);
950 }
951 return pStorage;
952}
953
954std::unique_ptr<IStorage> CreateTempStorage(const char *pDirectory, int NumArgs, const char **ppArguments)
955{
956 dbg_assert(NumArgs > 0, "Expected at least one argument");
957 std::unique_ptr<CStorage> pStorage = std::make_unique<CStorage>();
958 pStorage->FindDataDirectory(pArgv0: ppArguments[0]);
959 if(!pStorage->FindCurrentDirectory() ||
960 !pStorage->AddPath(pPath: pDirectory) ||
961 !pStorage->AddPath(pPath: "$DATADIR") ||
962 !pStorage->AddPath(pPath: "$CURRENTDIR"))
963 {
964 return std::unique_ptr<IStorage>(nullptr);
965 }
966 return pStorage;
967}
968