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