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