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 | |
20 | class CStorage : public IStorage |
21 | { |
22 | public: |
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 | |
865 | void IStorage::StripPathAndExtension(const char *pFilename, char *pBuffer, int BufferSize) |
866 | { |
867 | const char *pFilenameEnd = pFilename + str_length(str: pFilename); |
868 | const char * = 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 | |
887 | const 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 | |
893 | IStorage *CreateStorage(int StorageType, int NumArgs, const char **ppArguments) |
894 | { |
895 | return CStorage::Create(StorageType, NumArgs, ppArguments); |
896 | } |
897 | |
898 | IStorage *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 | } |
912 | IStorage *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 | |