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 | // 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 | |
870 | void IStorage::StripPathAndExtension(const char *pFilename, char *pBuffer, int BufferSize) |
871 | { |
872 | const char *pFilenameEnd = pFilename + str_length(str: pFilename); |
873 | const char * = 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 | |
892 | const 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 | |
898 | IStorage *CreateStorage(int StorageType, int NumArgs, const char **ppArguments) |
899 | { |
900 | return CStorage::Create(StorageType, NumArgs, ppArguments); |
901 | } |
902 | |
903 | IStorage *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 | } |
917 | IStorage *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 | |