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