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