1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* Linux conversion by Günther Brammer, 2005 */
18
19/* Lots of file helpers */
20
21#include <Standard.h>
22#include <StdFile.h>
23#include <StdBuf.h>
24
25#include <stdio.h>
26#ifdef _WIN32
27#include <direct.h>
28#endif
29#ifndef _WIN32
30#include <unistd.h>
31#endif
32#include <errno.h>
33#include <stdlib.h>
34#include <ctype.h>
35#include <fcntl.h>
36#include <sys/types.h>
37#include <sys/stat.h>
38
39/* Path & Filename */
40
41// Return pointer to position after last backslash.
42
43char *GetFilename(char *szPath)
44{
45 if (!szPath) return nullptr;
46 char *pPos, *pFilename = szPath;
47 for (pPos = szPath; *pPos; pPos++) if (*pPos == DirectorySeparator || *pPos == '/') pFilename = pPos + 1;
48 return pFilename;
49}
50
51const char *GetFilename(const char *szPath)
52{
53 return GetFilename(szPath: const_cast<char *>(szPath));
54}
55
56const char *GetFilenameOnly(const char *strFilename)
57{
58 // Get filename to static buffer
59 static char strBuffer[_MAX_PATH + 1];
60 SCopy(szSource: GetFilename(szPath: strFilename), sTarget: strBuffer);
61 // Truncate extension
62 RemoveExtension(szFileName: strBuffer);
63 // Return buffer
64 return strBuffer;
65}
66
67const char *GetC4Filename(const char *szPath)
68{
69 // returns path to file starting at first .c4*-directory.
70 if (!szPath) return nullptr;
71 const char *pPos, *pFilename = szPath;
72 for (pPos = szPath; *pPos; pPos++)
73 {
74 if (*pPos == DirectorySeparator || *pPos == '/')
75 {
76 if (pPos >= szPath + 4 && SEqual2NoCase(szStr1: pPos - 4, szStr2: ".c4")) return pFilename;
77 pFilename = pPos + 1;
78 }
79 }
80 return pFilename;
81}
82
83int GetTrailingNumber(const char *strString)
84{
85 // Default
86 int iNumber = 0;
87 // Start from end
88 const char *cpPos = strString + SLen(sptr: strString);
89 // Walk back while number
90 while ((cpPos > strString) && Inside(ival: *(cpPos - 1), lbound: '0', rbound: '9')) cpPos--;
91 // Scan number
92 sscanf(s: cpPos, format: "%d", &iNumber);
93 // Return result
94 return iNumber;
95}
96
97// Return pointer to last file extension.
98
99char *GetExtension(char *szFilename)
100{
101 int pos, end;
102 for (end = 0; szFilename[end]; end++);
103 pos = end;
104 while ((pos > 0) && (szFilename[pos - 1] != '.') && (szFilename[pos - 1] != DirectorySeparator)) --pos;
105 if ((pos > 0) && szFilename[pos - 1] == '.') return szFilename + pos;
106 return szFilename + end;
107}
108
109const char *GetExtension(const char *szFilename)
110{
111 return GetExtension(szFilename: const_cast<char *>(szFilename));
112}
113
114void RealPath(const char *szFilename, char *pFullFilename)
115{
116#ifdef _WIN32
117 _fullpath(pFullFilename, szFilename, _MAX_PATH);
118#else
119 char *pSuffix = nullptr;
120 char szCopy[_MAX_PATH + 1];
121 for (;;)
122 {
123 // Try to convert to full filename. Note this might fail if the given file doesn't exist
124 if (realpath(name: szFilename, resolved: pFullFilename))
125 break;
126 // ... which is undesired behaviour here. Try to reduce the filename until it works.
127 if (!pSuffix)
128 {
129 SCopy(szSource: szFilename, sTarget: szCopy, _MAX_PATH);
130 szFilename = szCopy;
131 pSuffix = szCopy + SLen(sptr: szCopy);
132 }
133 else
134 *pSuffix = '/';
135 while (pSuffix > szCopy)
136 if (*--pSuffix == '/')
137 break;
138 if (pSuffix <= szCopy)
139 {
140 // Give up: Just copy whatever we got
141 SCopy(szSource: szFilename, sTarget: pFullFilename, _MAX_PATH);
142 return;
143 }
144 *pSuffix = 0;
145 }
146 // Append suffix
147 if (pSuffix)
148 {
149 *pSuffix = '/';
150 SAppend(szSource: pSuffix, szTarget: pFullFilename, _MAX_PATH);
151 }
152#endif
153}
154
155// Copy (extended) parent path (without backslash) to target buffer.
156
157bool GetParentPath(const char *szFilename, char *szBuffer)
158{
159 // Prepare filename
160 SCopy(szSource: szFilename, sTarget: szBuffer, _MAX_PATH);
161 // Extend relative single filenames
162#ifdef _WIN32
163 if (!SCharCount(DirectorySeparator, szFilename)) _fullpath(szBuffer, szFilename, _MAX_PATH);
164#else
165 if (!SCharCount(DirectorySeparator, szInStr: szFilename)) RealPath(szFilename, pFullFilename: szBuffer);
166#endif
167 // Truncate path
168 return TruncatePath(szPath: szBuffer);
169}
170
171bool GetParentPath(const char *szFilename, StdStrBuf *outBuf)
172{
173 char buf[_MAX_PATH + 1]; *buf = '\0';
174 if (!GetParentPath(szFilename, szBuffer: buf)) return false;
175 outBuf->Copy(pnData: buf);
176 return true;
177}
178
179bool GetRelativePath(const char *strPath, const char *strRelativeTo, char *strBuffer, int iBufferSize)
180{
181 // Specified path is relative to base path
182 // Copy relative section
183 const char *szCpy;
184 SCopy(szSource: szCpy = GetRelativePathS(strPath, strRelativeTo), sTarget: strBuffer, iMaxL: iBufferSize);
185 // return whether it was made relative
186 return szCpy != strPath;
187}
188
189const char *GetRelativePathS(const char *strPath, const char *strRelativeTo)
190{
191 // Specified path is relative to base path
192#ifdef _WIN32
193 if (SEqual2NoCase(strPath, strRelativeTo))
194#else
195 if (SEqual2(szStr1: strPath, szStr2: strRelativeTo))
196#endif
197 {
198 // return relative section
199 return strPath + SLen(sptr: strRelativeTo) + ((strPath[SLen(sptr: strRelativeTo)] == DirectorySeparator) ? +1 : 0);
200 }
201 // Not relative: return full path
202 return strPath;
203}
204
205bool IsGlobalPath(const char *szPath)
206{
207#ifdef _WIN32
208 // C:\...
209 if (*szPath && szPath[1] == ':') return true;
210#endif
211 // /usr/bin, \Temp\, ...
212 if (*szPath == DirectorySeparator) return true;
213 return false;
214}
215
216// Truncate string before last backslash.
217
218bool TruncatePath(char *szPath)
219{
220 if (!szPath) return false;
221 int iBSPos;
222 iBSPos = SCharLastPos(DirectorySeparator, szInStr: szPath);
223#ifndef _WIN32
224 int iBSPos2;
225 iBSPos2 = SCharLastPos(cTarget: '\\', szInStr: szPath);
226 if (iBSPos2 > iBSPos) fprintf(stderr, format: "Warning: TruncatePath with a \\ (%s)\n", szPath);
227#endif
228 if (iBSPos < 0) return false;
229 szPath[iBSPos] = 0;
230 return true;
231}
232
233// Append terminating backslash if not present.
234
235void AppendBackslash(char *szFilename)
236{
237 const auto i = SLen(sptr: szFilename);
238 if (i > 0) if ((szFilename[i - 1] == DirectorySeparator)) return;
239 SAppendChar(DirectorySeparator, szStr: szFilename);
240}
241
242// Remove terminating backslash if present.
243
244void TruncateBackslash(char *szFilename)
245{
246 const auto i = SLen(sptr: szFilename);
247 if (i > 0) if ((szFilename[i - 1] == DirectorySeparator)) szFilename[i - 1] = 0;
248}
249
250// Append extension if no extension.
251
252void DefaultExtension(char *szFilename, const char *szExtension)
253{
254 if (!(*GetExtension(szFilename)))
255 {
256 SAppend(szSource: ".", szTarget: szFilename); SAppend(szSource: szExtension, szTarget: szFilename);
257 }
258}
259
260// Append or overwrite extension.
261
262void EnforceExtension(char *szFilename, const char *szExtension)
263{
264 char *ext = GetExtension(szFilename);
265 if (ext[0]) { SCopy(szSource: szExtension, sTarget: ext); }
266 else { SAppend(szSource: ".", szTarget: szFilename); SAppend(szSource: szExtension, szTarget: szFilename); }
267}
268
269void EnforceExtension(StdStrBuf *sFilename, const char *szExtension)
270{
271 assert(sFilename);
272 const char *ext = GetExtension(szFilename: sFilename->getData());
273 if (ext[0]) { sFilename->ReplaceEnd(iPos: ext - sFilename->getData(), szNewEnd: szExtension); }
274 else { sFilename->AppendChar(cChar: '.'); sFilename->Append(pnData: szExtension); }
275}
276
277// remove extension
278
279void RemoveExtension(char *szFilename)
280{
281 char *ext = GetExtension(szFilename);
282 if (ext[0]) ext[-1] = 0;
283}
284
285void RemoveExtension(StdStrBuf *psFileName)
286{
287 if (psFileName && *psFileName)
288 {
289 RemoveExtension(szFilename: psFileName->getMData());
290 psFileName->SetLength(strlen(s: psFileName->getData()));
291 }
292}
293
294// Enforce indexed extension until item does not exist.
295
296void MakeTempFilename(char *szFilename)
297{
298 DefaultExtension(szFilename, szExtension: "tmp");
299 char *fn_ext = GetExtension(szFilename);
300 int cnum = -1;
301 do
302 {
303 cnum++;
304 FormatWithNull(buf: std::span<char, 4>{fn_ext, 4}, fmt: "{:03}", args&: cnum);
305 } while (FileExists(szFileName: szFilename) && (cnum < 999));
306}
307
308void MakeTempFilename(StdStrBuf *sFilename)
309{
310 assert(sFilename);
311 if (!sFilename->getLength()) sFilename->Copy(pnData: "temp.tmp");
312 EnforceExtension(sFilename, szExtension: "tmp");
313 char *fn_ext = GetExtension(szFilename: sFilename->getMData());
314 int cnum = -1;
315 do
316 {
317 cnum++;
318 FormatWithNull(buf: std::span<char, 4>{fn_ext, 4}, fmt: "{:03}", args&: cnum);
319 } while (FileExists(szFileName: sFilename->getData()) && (cnum < 999));
320}
321
322bool WildcardListMatch(const char *szWildcardList, const char *szString)
323{
324 // safety
325 if (!szString || !szWildcardList) return false;
326 // match any item in list
327 StdStrBuf sWildcard, sWildcardList(szWildcardList, false);
328 int32_t i = 0;
329 while (sWildcardList.GetSection(idx: i++, psOutSection: &sWildcard, cSeparator: '|'))
330 {
331 if (WildcardMatch(szFName1: sWildcard.getData(), szFName2: szString)) return true;
332 }
333 // none matched
334 return false;
335}
336
337bool WildcardMatch(const char *szWildcard, const char *szString)
338{
339 // safety
340 if (!szString || !szWildcard) return false;
341 // match char-wise
342 const char *pWild = szWildcard, *pPos = szString;
343 const char *pLWild = nullptr, *pLPos = nullptr; // backtracking
344 while (*pWild || pLWild)
345 // string wildcard?
346 if (*pWild == '*')
347 {
348 pLWild = ++pWild; pLPos = pPos;
349 }
350 // nothing left to match?
351 else if (!*pPos)
352 break;
353 // equal or one-character-wildcard? proceed
354 else if (*pWild == '?' || tolower(c: *pWild) == tolower(c: *pPos))
355 {
356 pWild++; pPos++;
357 }
358 // backtrack possible?
359 else if (pLPos)
360 {
361 pWild = pLWild; pPos = ++pLPos;
362 }
363 // match failed
364 else
365 return false;
366 // match complete if both strings are fully matched
367 return !*pWild && !*pPos;
368}
369
370// !"§%&/=?+*#:;<>\.
371#define SStripChars "!\"\xa7%&/=?+*#:;<>\\."
372// create a valid file name from some title
373void MakeFilenameFromTitle(char *szTitle)
374{
375 // copy all chars but those to be stripped
376 char *szFilename = szTitle, *szTitle2 = szTitle;
377 while (*szTitle2)
378 {
379 bool fStrip;
380 if (IsWhiteSpace(cChar: *szTitle2))
381 fStrip = (szFilename == szTitle);
382 else
383 fStrip = (SCharPos(cTarget: *szTitle2, SStripChars) >= 0);
384 if (!fStrip) *szFilename++ = *szTitle2;
385 ++szTitle2;
386 }
387 // truncate spaces from end
388 while (IsWhiteSpace(cChar: *--szFilename)) if (szFilename == szTitle) { --szFilename; break; }
389 // terminate
390 *++szFilename = 0;
391 // no name? (only invalid chars)
392 if (!*szTitle) SCopy(szSource: "unnamed", sTarget: szTitle, iMaxL: 50);
393 // done
394}
395
396/* Files */
397
398bool FileExists(const char *szFilename)
399{
400 return (!access(name: szFilename, F_OK));
401}
402
403size_t FileSize(const char *szFilename)
404{
405 struct stat stStats;
406 if (stat(file: szFilename, buf: &stStats)) return 0;
407 return stStats.st_size;
408}
409
410// operates on a filedescriptor from open or fileno
411size_t FileSize(int fdes)
412{
413 struct stat stStats;
414 if (fstat(fd: fdes, buf: &stStats)) return 0;
415 return stStats.st_size;
416}
417
418time_t FileTime(const char *szFilename)
419{
420 struct stat stStats;
421 if (stat(file: szFilename, buf: &stStats) != 0) return 0;
422 return static_cast<int>(stStats.st_mtime);
423}
424
425bool EraseFile(const char *szFilename)
426{
427#ifdef _WIN32
428 SetFileAttributesA(szFilename, FILE_ATTRIBUTE_NORMAL);
429#endif
430 // either unlink or remove could be used. Well, stick to ANSI C where possible.
431 if (remove(filename: szFilename))
432 {
433 if (errno == ENOENT)
434 {
435 // Hah, here the wrapper actually makes sense:
436 // The engine only cares about the file not being there after this call.
437 return true;
438 }
439 return false;
440 }
441 return true;
442}
443
444#ifdef _WIN32
445#define CopyFileTo CopyFileA
446#else
447bool CopyFileTo(const char *szSource, const char *szTarget, bool FailIfExists)
448{
449 int fds = open(file: szSource, O_RDONLY);
450 if (!fds) return false;
451 struct stat info; fstat(fd: fds, buf: &info);
452 int fdt = open(file: szTarget, O_WRONLY | O_CREAT | (FailIfExists ? O_EXCL : O_TRUNC), info.st_mode);
453 if (!fdt)
454 {
455 close(fd: fds);
456 return false;
457 }
458 char buffer[1024]; ssize_t l;
459 while ((l = read(fd: fds, buf: buffer, nbytes: sizeof(buffer))) > 0) write(fd: fdt, buf: buffer, n: l);
460 close(fd: fds);
461 close(fd: fdt);
462 // On error, return false
463 return l != -1;
464}
465#endif
466
467bool RenameFile(const char *szFilename, const char *szNewFilename)
468{
469 if (rename(old: szFilename, new: szNewFilename) < 0)
470 {
471 if (CopyFileTo(szSource: szFilename, szTarget: szNewFilename, FailIfExists: false))
472 {
473 return EraseFile(szFilename);
474 }
475 return false;
476 }
477 return true;
478}
479
480bool MakeOriginalFilename(char *szFilename)
481{
482 // safety
483 if (!szFilename) return false;
484#ifdef _WIN32
485 // root-directory?
486 if (Inside<size_t>(SLen(szFilename), 2, 3)) if (szFilename[1] == ':')
487 {
488 szFilename[2] = '\\'; szFilename[3] = 0;
489 if (GetDriveTypeA(szFilename) == DRIVE_NO_ROOT_DIR) return false;
490 return true;
491 }
492 struct _finddata_t fdt; intptr_t shnd;
493 if ((shnd = _findfirst((char *)szFilename, &fdt)) < 0) return false;
494 _findclose(shnd);
495 SCopy(GetFilename(fdt.name), GetFilename(szFilename), _MAX_FNAME);
496#else
497 if (SCharPos(cTarget: '*', szInStr: szFilename) != -1)
498 {
499 fputs(s: "Warning: MakeOriginalFilename with \"", stderr);
500 fputs(s: szFilename, stderr);
501 fputs(s: "\"!\n", stderr);
502 }
503#endif
504 return true;
505}
506
507/* Directories */
508
509const char *GetWorkingDirectory()
510{
511 static char buf[_MAX_PATH + 1];
512 return getcwd(buf: buf, _MAX_PATH);
513}
514
515bool SetWorkingDirectory(const char *path)
516{
517#ifdef _WIN32
518 return SetCurrentDirectoryA(path) != 0;
519#else
520 return (chdir(path: path) == 0);
521#endif
522}
523
524#ifndef _WIN32
525// MakeDirectory: true on success
526bool MakeDirectory(const char *pathname, void *)
527{
528 // mkdir: false on success
529 return !mkdir(path: pathname, S_IREAD | S_IWRITE | S_IEXEC);
530}
531#endif
532
533bool DirectoryExists(const char *szFilename)
534{
535 // Ignore trailing slash or backslash
536 char bufFilename[_MAX_PATH + 1];
537 if (szFilename && szFilename[0])
538 if ((szFilename[SLen(sptr: szFilename) - 1] == '\\') || (szFilename[SLen(sptr: szFilename) - 1] == '/'))
539 {
540 SCopy(szSource: szFilename, sTarget: bufFilename, _MAX_PATH);
541 bufFilename[SLen(sptr: bufFilename) - 1] = 0;
542 szFilename = bufFilename;
543 }
544 // Check file attributes
545#ifdef _WIN32
546 struct _finddata_t fdt; intptr_t shnd;
547 if ((shnd = _findfirst(szFilename, &fdt)) < 0) return false;
548 _findclose(shnd);
549 if (fdt.attrib & _A_SUBDIR) return true;
550#else
551 struct stat stStats;
552 if (stat(file: szFilename, buf: &stStats) != 0) return 0;
553 return (S_ISDIR(stStats.st_mode));
554#endif
555 return false;
556}
557
558bool CopyDirectory(const char *szSource, const char *szTarget, bool fResetAttributes)
559{
560 // Source check
561 if (!szSource || !szTarget) return false;
562 if (!DirectoryExists(szFilename: szSource)) return false;
563 // Do not process system navigation directories
564 if (SEqual(szStr1: GetFilename(szPath: szSource), szStr2: ".")
565 || SEqual(szStr1: GetFilename(szPath: szSource), szStr2: ".."))
566 return true;
567 // Overwrite target
568 if (!EraseItem(szItemName: szTarget)) return false;
569 // Create target directory
570 bool status = true;
571#ifdef _WIN32
572 if (_mkdir(szTarget) != 0) return false;
573 // Copy contents to target directory
574 char contents[_MAX_PATH + 1];
575 SCopy(szSource, contents); AppendBackslash(contents);
576 SAppend("*", contents);
577 _finddata_t fdt; intptr_t hfdt;
578 if ((hfdt = _findfirst(contents, &fdt)) > -1)
579 {
580 do
581 {
582 char itemsource[_MAX_PATH + 1], itemtarget[_MAX_PATH + 1];
583 SCopy(szSource, itemsource); AppendBackslash(itemsource); SAppend(fdt.name, itemsource);
584 SCopy(szTarget, itemtarget); AppendBackslash(itemtarget); SAppend(fdt.name, itemtarget);
585 if (!CopyItem(itemsource, itemtarget, fResetAttributes)) status = false;
586 } while (_findnext(hfdt, &fdt) == 0);
587 _findclose(hfdt);
588 }
589#else
590 if (mkdir(path: szTarget, mode: 0777) != 0) return false;
591 DIR *d = opendir(name: szSource);
592 dirent *ent;
593 char itemsource[_MAX_PATH + 1], itemtarget[_MAX_PATH + 1];
594 while ((ent = readdir(dirp: d)))
595 {
596 SCopy(szSource, sTarget: itemsource); AppendBackslash(szFilename: itemsource); SAppend(szSource: ent->d_name, szTarget: itemsource);
597 SCopy(szSource: szTarget, sTarget: itemtarget); AppendBackslash(szFilename: itemtarget); SAppend(szSource: ent->d_name, szTarget: itemtarget);
598 if (!CopyItem(szSource: itemsource, szTarget: itemtarget, fResetAttributes)) status = false;
599 }
600 closedir(dirp: d);
601#endif
602 return status;
603}
604
605bool EraseDirectory(const char *szDirName)
606{
607 // Do not process system navigation directories
608 if (SEqual(szStr1: GetFilename(szPath: szDirName), szStr2: ".")
609 || SEqual(szStr1: GetFilename(szPath: szDirName), szStr2: ".."))
610 return true;
611 char path[_MAX_PATH + 1];
612#ifdef _WIN32
613 // Get path to directory contents
614 SCopy(szDirName, path); SAppend("\\*.*", path);
615 // Erase subdirectories and files
616 ForEachFile(path, &EraseItem);
617#else
618 DIR *d = opendir(name: szDirName);
619 dirent *ent;
620 while ((ent = readdir(dirp: d)))
621 {
622 SCopy(szSource: szDirName, sTarget: path); AppendBackslash(szFilename: path); SAppend(szSource: ent->d_name, szTarget: path);
623 if (!EraseItem(szItemName: path)) return false;
624 }
625 closedir(dirp: d);
626#endif
627 // Check working directory
628 if (SEqual(szStr1: szDirName, szStr2: GetWorkingDirectory()))
629 {
630 // Will work only if szDirName is full path and correct case!
631 SCopy(szSource: GetWorkingDirectory(), sTarget: path);
632 int lbacks = SCharLastPos(DirectorySeparator, szInStr: path);
633 if (lbacks > -1)
634 {
635 path[lbacks] = 0; SetWorkingDirectory(path);
636 }
637 }
638 // Remove directory
639#ifdef _WIN32
640 return !!RemoveDirectoryA(szDirName);
641#else
642 return (rmdir(path: szDirName) == 0 || errno == ENOENT);
643#endif
644}
645
646/* Items */
647
648bool RenameItem(const char *szItemName, const char *szNewItemName)
649{
650 // FIXME: What if the directory would have to be copied?
651 return RenameFile(szFilename: szItemName, szNewFilename: szNewItemName);
652}
653
654bool EraseItem(const char *szItemName)
655{
656 if (!EraseFile(szFilename: szItemName)) return EraseDirectory(szDirName: szItemName);
657 else return true;
658}
659
660bool CreateItem(const char *szItemname)
661{
662 // Overwrite any old item
663 EraseItem(szItemName: szItemname);
664 // Create dummy item
665 FILE *fhnd;
666 if (!(fhnd = fopen(filename: szItemname, modes: "wb"))) return false;
667 fclose(stream: fhnd);
668 // Success
669 return true;
670}
671
672bool CopyItem(const char *szSource, const char *szTarget, bool fResetAttributes)
673{
674 // Check for identical source and target
675 if (ItemIdentical(szFilename1: szSource, szFilename2: szTarget)) return true;
676 // Copy directory
677 if (DirectoryExists(szFilename: szSource))
678 return CopyDirectory(szSource, szTarget, fResetAttributes);
679 // Copy file
680 if (!CopyFileTo(szSource, szTarget, FailIfExists: false)) return false;
681 // Reset any attributes if desired
682#ifdef _WIN32
683 if (fResetAttributes) if (!SetFileAttributesA(szTarget, FILE_ATTRIBUTE_NORMAL)) return false;
684#else
685 if (fResetAttributes) if (chmod(file: szTarget, S_IRWXU)) return false;
686#endif
687 return true;
688}
689
690bool MoveItem(const char *szSource, const char *szTarget)
691{
692 if (ItemIdentical(szFilename1: szSource, szFilename2: szTarget)) return true;
693 return RenameFile(szFilename: szSource, szNewFilename: szTarget);
694}
695
696bool ItemIdentical(const char *szFilename1, const char *szFilename2)
697{
698 char szFullFile1[_MAX_PATH + 1], szFullFile2[_MAX_PATH + 1];
699 RealPath(szFilename: szFilename1, pFullFilename: szFullFile1); RealPath(szFilename: szFilename2, pFullFilename: szFullFile2);
700#ifdef _WIN32
701 if (SEqualNoCase(szFullFile1, szFullFile2)) return true;
702#else
703 if (SEqual(szStr1: szFullFile1, szStr2: szFullFile2)) return true;
704#endif
705 return false;
706}
707
708// Multi File Processing
709
710#ifdef _WIN32
711
712DirectoryIterator::DirectoryIterator() : fdthnd(0) { *filename = 0; }
713
714void DirectoryIterator::Reset()
715{
716 if (fdthnd)
717 {
718 _findclose(fdthnd);
719 fdthnd = 0;
720 }
721 filename[0] = 0;
722}
723
724void DirectoryIterator::Reset(const char *dirname)
725{
726 if (fdthnd)
727 {
728 _findclose(fdthnd);
729 }
730 if (!dirname[0]) dirname = ".";
731 SCopy(dirname, filename);
732 AppendBackslash(filename);
733 SAppend("*", filename, _MAX_PATH);
734 if ((fdthnd = _findfirst(filename, &fdt)) < 0)
735 {
736 filename[0] = 0;
737 }
738 else
739 {
740 if (fdt.name[0] == '.' && (fdt.name[1] == '.' || fdt.name[1] == 0))
741 {
742 operator++(); return;
743 }
744 SCopy(fdt.name, GetFilename(filename));
745 }
746}
747
748DirectoryIterator::DirectoryIterator(const char *dirname)
749{
750 if (!dirname[0]) dirname = ".";
751 SCopy(dirname, filename);
752 AppendBackslash(filename);
753 SAppend("*", filename, _MAX_PATH);
754 if ((fdthnd = _findfirst(filename, &fdt)) < 0)
755 {
756 filename[0] = 0;
757 }
758 else
759 {
760 if (fdt.name[0] == '.' && (fdt.name[1] == '.' || fdt.name[1] == 0))
761 {
762 operator++(); return;
763 }
764 SCopy(fdt.name, GetFilename(filename));
765 }
766}
767
768DirectoryIterator &DirectoryIterator::operator++()
769{
770 if (_findnext(fdthnd, &fdt) == 0)
771 {
772 if (fdt.name[0] == '.' && (fdt.name[1] == '.' || fdt.name[1] == 0))
773 return operator++();
774 SCopy(fdt.name, GetFilename(filename));
775 }
776 else
777 {
778 filename[0] = 0;
779 }
780 return *this;
781}
782
783DirectoryIterator::~DirectoryIterator()
784{
785 if (fdthnd) _findclose(fdthnd);
786}
787
788#else
789
790DirectoryIterator::DirectoryIterator() : d{} { filename[0] = 0; }
791
792void DirectoryIterator::Reset()
793{
794 if (d)
795 {
796 closedir(dirp: d);
797 d = nullptr;
798 }
799 filename[0] = 0;
800}
801
802void DirectoryIterator::Reset(const char *dirname)
803{
804 if (d)
805 {
806 closedir(dirp: d);
807 }
808 if (!dirname[0]) dirname = ".";
809 SCopy(szSource: dirname, sTarget: filename);
810 AppendBackslash(szFilename: filename);
811 d = opendir(name: dirname);
812 this->operator++();
813}
814
815DirectoryIterator::DirectoryIterator(const char *dirname)
816{
817 if (!dirname[0]) dirname = ".";
818 SCopy(szSource: dirname, sTarget: filename);
819 AppendBackslash(szFilename: filename);
820 d = opendir(name: dirname);
821 this->operator++();
822}
823
824DirectoryIterator &DirectoryIterator::operator++()
825{
826 if (d && (ent = readdir(dirp: d)))
827 {
828 if (ent->d_name[0] == '.' && (ent->d_name[1] == '.' || ent->d_name[1] == 0))
829 return operator++();
830 SCopy(szSource: ent->d_name, sTarget: GetFilename(szPath: filename));
831 }
832 else
833 {
834 filename[0] = 0;
835 }
836 return *this;
837}
838
839DirectoryIterator::~DirectoryIterator()
840{
841 if (d) closedir(dirp: d);
842}
843
844#endif
845
846const char *DirectoryIterator::operator*() const { return filename[0] ? filename : nullptr; }
847void DirectoryIterator::operator++(int) { operator++(); }
848
849int ForEachFile(const char *szDirName, bool(*fnCallback)(const char *))
850{
851 if (!szDirName || !fnCallback)
852 return 0;
853 char szFilename[_MAX_PATH + 1];
854 SCopy(szSource: szDirName, sTarget: szFilename);
855 bool fHasWildcard = (SCharPos(cTarget: '*', szInStr: szFilename) >= 0);
856 if (!fHasWildcard) // parameter without wildcard: Append "/*.*" or "\*.*"
857 AppendBackslash(szFilename);
858 int iFileCount = 0;
859#ifdef _WIN32
860 struct _finddata_t fdt; intptr_t fdthnd;
861 if (!fHasWildcard) // parameter without wildcard: Append "/*.*" or "\*.*"
862 SAppend("*", szFilename, _MAX_PATH);
863 if ((fdthnd = _findfirst((char *)szFilename, &fdt)) < 0)
864 return 0;
865 do
866 {
867 if (SEqual(fdt.name, ".") || SEqual(fdt.name, "..")) continue;
868 SCopy(fdt.name, GetFilename(szFilename));
869 if ((*fnCallback)(szFilename))
870 iFileCount++;
871 } while (_findnext(fdthnd, &fdt) == 0);
872 _findclose(fdthnd);
873#else
874 if (fHasWildcard) fprintf(stderr, format: "Warning: ForEachFile with * (%s)\n", szDirName);
875 DIR *d = opendir(name: szDirName);
876 dirent *ent;
877 while ((ent = readdir(dirp: d)))
878 {
879 SCopy(szSource: ent->d_name, sTarget: GetFilename(szPath: szFilename));
880 if ((*fnCallback)(szFilename))
881 iFileCount++;
882 }
883 closedir(dirp: d);
884#endif
885 return iFileCount;
886}
887
888// Text Files
889
890bool ReadFileLine(FILE *fhnd, char *tobuf, int maxlen)
891{
892 int cread;
893 char inc;
894 if (!fhnd || !tobuf) return 0;
895 for (cread = 0; cread < maxlen; cread++)
896 {
897 inc = fgetc(stream: fhnd);
898 if (inc == 0x0D) // Binary text file line feed
899 {
900 fgetc(stream: fhnd); break;
901 }
902 if (inc == 0x0A) break; // Text file line feed
903 if (!inc || (inc == EOF)) break; // End of file
904 *tobuf = inc; tobuf++;
905 }
906 *tobuf = 0;
907 if (inc == EOF) return 0;
908 return 1;
909}
910
911void AdvanceFileLine(FILE *fhnd)
912{
913 int cread, loops = 0;
914 if (!fhnd) return;
915 do
916 {
917 cread = fgetc(stream: fhnd);
918 if (cread == EOF) { rewind(stream: fhnd); loops++; }
919 } while ((cread != 0x0A) && (loops < 2));
920}
921