1 /*
2  *  The ManaPlus Client
3  *  Copyright (C) 2013-2019  The ManaPlus Developers
4  *  Copyright (C) 2019-2021  Andrei Karas
5  *
6  *  This file is part of The ManaPlus Client.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include "fs/virtfs/fsdir.h"
23 
24 #include "fs/files.h"
25 #include "fs/mkdir.h"
26 #include "fs/paths.h"
27 
28 #include "fs/virtfs/direntry.h"
29 #include "fs/virtfs/file.h"
30 #include "fs/virtfs/fsdirrwops.h"
31 #include "fs/virtfs/fsfuncs.h"
32 #include "fs/virtfs/list.h"
33 
34 #include "utils/cast.h"
35 #include "utils/checkutils.h"
36 #include "utils/foreach.h"
37 #include "utils/stdmove.h"
38 #include "utils/stringutils.h"
39 
40 #include <dirent.h>
41 #include <unistd.h>
42 
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 
46 #include "debug.h"
47 
48 extern const char *dirSeparator;
49 
50 namespace VirtFs
51 {
52 
53 namespace
54 {
55     std::string mWriteDir;
56     std::string mBaseDir;
57     std::string mUserDir;
58     bool mPermitLinks = false;
59     FsFuncs funcs;
60 }  // namespace
61 
62 namespace FsDir
63 {
openInternal(FsEntry * restrict const entry,const std::string & filename,const FILEMTYPE mode)64     File *openInternal(FsEntry *restrict const entry,
65                        const std::string &filename,
66                        const FILEMTYPE mode)
67     {
68         const std::string path = static_cast<DirEntry*>(entry)->rootSubDir +
69             filename;
70         if (Files::existsLocal(path) == false)
71             return nullptr;
72         FILEHTYPE fd = FILEOPEN(path.c_str(),
73             mode);
74         if (fd == FILEHDEFAULT)
75         {
76             reportAlways("VirtFs::open file open error: %s",
77                 filename.c_str())
78             return nullptr;
79         }
80         return new File(&funcs, fd);
81     }
82 
openRead(FsEntry * restrict const entry,std::string filename)83     File *openRead(FsEntry *restrict const entry,
84                    std::string filename)
85     {
86         return openInternal(entry, filename, FILEOPEN_FLAG_READ);
87     }
88 
openWrite(FsEntry * restrict const entry,const std::string & filename)89     File *openWrite(FsEntry *restrict const entry,
90                     const std::string &filename)
91     {
92         return openInternal(entry, filename, FILEOPEN_FLAG_WRITE);
93     }
94 
openAppend(FsEntry * restrict const entry,const std::string & filename)95     File *openAppend(FsEntry *restrict const entry,
96                      const std::string &filename)
97     {
98         return openInternal(entry, filename, FILEOPEN_FLAG_APPEND);
99     }
100 
deinit()101     void deinit()
102     {
103     }
104 
105 #if defined(__native_client__)
init(const std::string & restrict name A_UNUSED)106     void init(const std::string &restrict name A_UNUSED)
107     {
108         mBaseDir = "/";
109 #elif defined(ANDROID)
110     void init(const std::string &restrict name A_UNUSED)
111     {
112         mBaseDir = getRealPath(".");
113 #else  // defined(__native_client__)
114 
115     void init(const std::string &restrict name)
116     {
117         mBaseDir = getRealPath(getFileDir(name));
118 #endif  // defined(__native_client__)
119 
120         prepareFsPath(mBaseDir);
121         mUserDir = getHomePath();
122         prepareFsPath(mUserDir);
123         initFuncs(&funcs);
124     }
125 
126     void initFuncs(FsFuncs *restrict const ptr)
127     {
128         ptr->close = &FsDir::close;
129         ptr->read = &FsDir::read;
130         ptr->write = &FsDir::write;
131         ptr->fileLength = &FsDir::fileLength;
132         ptr->tell = &FsDir::tell;
133         ptr->seek = &FsDir::seek;
134         ptr->eof = &FsDir::eof;
135         ptr->exists = &FsDir::exists;
136         ptr->getRealDir = &FsDir::getRealDir;
137         ptr->enumerate = &FsDir::enumerate;
138         ptr->isDirectory = &FsDir::isDirectory;
139         ptr->openRead = &FsDir::openRead;
140         ptr->openWrite = &FsDir::openWrite;
141         ptr->openAppend = &FsDir::openAppend;
142         ptr->loadFile = &FsDir::loadFile;
143         ptr->getFiles = &FsDir::getFiles;
144         ptr->getFilesWithDir = &FsDir::getFilesWithDir;
145         ptr->getDirs = &FsDir::getDirs;
146         ptr->rwops_seek = &FsDir::rwops_seek;
147         ptr->rwops_read = &FsDir::rwops_read;
148         ptr->rwops_write = &FsDir::rwops_write;
149         ptr->rwops_close = &FsDir::rwops_close;
150 #ifdef USE_SDL2
151         ptr->rwops_size = &FsDir::rwops_size;
152 #endif  // USE_SDL2
153     }
154 
155     FsFuncs *getFuncs()
156     {
157         return &funcs;
158     }
159 
160     const char *getBaseDir()
161     {
162         return mBaseDir.c_str();
163     }
164 
165     const char *getUserDir()
166     {
167         return mUserDir.c_str();
168     }
169 
170     bool getRealDir(FsEntry *restrict const entry,
171                     std::string filename,
172                     std::string dirName A_UNUSED,
173                     std::string &realDir)
174     {
175         const DirEntry *const dirEntry = static_cast<const DirEntry*>(entry);
176         if (Files::existsLocal(dirEntry->rootSubDir + filename))
177         {
178             realDir = dirEntry->userDir;
179             return true;
180         }
181         return false;
182     }
183 
184     bool exists(FsEntry *restrict const entry,
185                 std::string fileName,
186                 std::string dirName A_UNUSED)
187     {
188         return Files::existsLocal(static_cast<DirEntry*>(entry)->rootSubDir +
189             fileName);
190     }
191 
192     void enumerate(FsEntry *restrict const entry,
193                    std::string dirName,
194                    StringVect &names)
195     {
196         const std::string path = static_cast<DirEntry*>(entry)->rootSubDir +
197             dirName;
198         const dirent *next_file = nullptr;
199         DIR *const dir = opendir(path.c_str());
200         if (dir != nullptr)
201         {
202             while ((next_file = readdir(dir)) != nullptr)
203             {
204                 const std::string file = next_file->d_name;
205                 if (file == "." || file == "..")
206                     continue;
207 #ifndef WIN32
208                 if (mPermitLinks == false)
209                 {
210                     struct stat statbuf;
211                     if (lstat(path.c_str(), &statbuf) == 0 &&
212                         S_ISLNK(statbuf.st_mode) != 0)
213                     {
214                         continue;
215                     }
216                 }
217 #endif  // WIN32
218 
219                 bool found(false);
220                 FOR_EACH (StringVectCIter, itn, names)
221                 {
222                     if (*itn == file)
223                     {
224                         found = true;
225                         break;
226                     }
227                 }
228                 if (found == false)
229                     names.push_back(file);
230             }
231             closedir(dir);
232         }
233     }
234 
235     bool isDirectory(FsEntry *restrict const entry,
236                      std::string dirName,
237                      bool &isDirFlag)
238     {
239         std::string path = static_cast<DirEntry*>(entry)->rootSubDir + dirName;
240 
241         struct stat statbuf;
242         if (stat(path.c_str(), &statbuf) == 0)
243         {
244             isDirFlag = (S_ISDIR(statbuf.st_mode) != 0);
245             return true;
246         }
247         return false;
248     }
249 
250     bool isSymbolicLink(std::string name)
251     {
252         prepareFsPath(name);
253         if (checkPath(name) == false)
254         {
255             reportAlways("FsDir::isSymbolicLink invalid path: %s",
256                 name.c_str())
257             return false;
258         }
259 #ifndef WIN32
260         if (mPermitLinks == false)
261             return false;
262 
263         struct stat statbuf;
264         return lstat(name.c_str(), &statbuf) == 0 &&
265             S_ISLNK(statbuf.st_mode) != 0;
266 #else
267         return false;
268 #endif  // WIN32
269     }
270 
271     void freeList(List *restrict const handle)
272     {
273         delete handle;
274     }
275 
276     bool setWriteDir(std::string newDir)
277     {
278         prepareFsPath(newDir);
279         mWriteDir = STD_MOVE(newDir);
280         if (findLast(mWriteDir, std::string(dirSeparator)) == false)
281             mWriteDir += dirSeparator;
282         return true;
283     }
284 
285     bool mkdir(std::string dirname)
286     {
287         prepareFsPath(dirname);
288         if (mWriteDir.empty())
289         {
290             reportAlways("FsDir::mkdir write dir is empty")
291             return false;
292         }
293         return mkdir_r((mWriteDir + dirname).c_str()) != -1;
294     }
295 
296     bool remove(std::string filename)
297     {
298         prepareFsPath(filename);
299         if (mWriteDir.empty())
300         {
301             reportAlways("FsDir::remove write dir is empty")
302             return false;
303         }
304         return ::remove((mWriteDir + filename).c_str()) != 0;
305     }
306 
307     void permitLinks(const bool val)
308     {
309         mPermitLinks = val;
310     }
311 
312     int close(File *restrict const file)
313     {
314         if (file == nullptr)
315             return 0;
316         delete file;
317         return 1;
318     }
319 
320     int64_t read(File *restrict const file,
321                  void *restrict const buffer,
322                  const uint32_t objSize,
323                  const uint32_t objCount)
324     {
325         if (file == nullptr)
326             return 0;
327         FILEHTYPE fd = file->mFd;
328         if (fd == FILEHDEFAULT)
329         {
330             reportAlways("FsDir::read file not opened.")
331             return 0;
332         }
333 #ifdef USE_FILE_FOPEN
334         return fread(buffer, objSize, objCount, fd);
335 #else  // USE_FILE_FOPEN
336         int max = objSize * objCount;
337         int cnt = ::read(fd, buffer, max);
338         if (cnt <= 0)
339             return cnt;
340         return cnt / objSize;
341 #endif  // USE_FILE_FOPEN
342     }
343 
344     int64_t write(File *restrict const file,
345                   const void *restrict const buffer,
346                   const uint32_t objSize,
347                   const uint32_t objCount)
348     {
349         if (file == nullptr)
350             return 0;
351         FILEHTYPE fd = file->mFd;
352         if (fd == FILEHDEFAULT)
353         {
354             reportAlways("FsDir::write file not opened.")
355             return 0;
356         }
357 #ifdef USE_FILE_FOPEN
358         return fwrite(buffer, objSize, objCount, fd);
359 #else  // USE_FILE_FOPEN
360         int max = objSize * objCount;
361         int cnt = ::write(fd, buffer, max);
362         if (cnt <= 0)
363             return cnt;
364         return cnt / objSize;
365 #endif  // USE_FILE_FOPEN
366     }
367 
368     int64_t fileLength(File *restrict const file)
369     {
370         if (file == nullptr)
371             return -1;
372         FILEHTYPE fd = file->mFd;
373         if (fd == FILEHDEFAULT)
374         {
375             reportAlways("FsDir::fileLength file not opened.")
376             return 0;
377         }
378 #ifdef USE_FILE_FOPEN
379         const long pos = ftell(fd);
380         if (pos < 0)
381         {
382             reportAlways("FsDir::fileLength ftell error.")
383             return -1;
384         }
385         fseek(fd, 0, SEEK_END);
386         const long sz = ftell(fd);
387         fseek(fd, pos, SEEK_SET);
388         return sz;
389 #else  // USE_FILE_FOPEN
390         struct stat statbuf;
391         if (fstat(fd, &statbuf) == -1)
392         {
393             reportAlways("FsDir::fileLength error.")
394             return -1;
395         }
396         return static_cast<int64_t>(statbuf.st_size);
397 #endif  // USE_FILE_FOPEN
398     }
399 
400     int64_t tell(File *restrict const file)
401     {
402         if (file == nullptr)
403             return -1;
404 
405         FILEHTYPE fd = file->mFd;
406         if (fd == FILEHDEFAULT)
407         {
408             reportAlways("FsDir::tell file not opened.")
409             return 0;
410         }
411 #ifdef USE_FILE_FOPEN
412         return ftell(fd);
413 #else  // USE_FILE_FOPEN
414         return lseek(fd, 0, SEEK_CUR);
415 #endif  // USE_FILE_FOPEN
416     }
417 
418     int seek(File *restrict const file,
419              const uint64_t pos)
420     {
421         if (file == nullptr)
422             return 0;
423 
424         FILEHTYPE fd = file->mFd;
425         if (fd == FILEHDEFAULT)
426         {
427             reportAlways("FsDir::seek file not opened.")
428             return 0;
429         }
430         const int64_t res = FILESEEK(fd, pos, SEEK_SET);
431         if (res == -1)
432             return 0;
433         return 1;
434     }
435 
436     int eof(File *restrict const file)
437     {
438         if (file == nullptr)
439             return -1;
440 
441         FILEHTYPE fd = file->mFd;
442         if (fd == FILEHDEFAULT)
443         {
444             reportAlways("FsDir::eof file not opened.")
445             return 0;
446         }
447 #ifdef USE_FILE_FOPEN
448         const int flag = feof(fd);
449         if (flag != 0)
450             return 1;
451         const int64_t pos = ftell(fd);
452         const int64_t len = fileLength(file);
453 #else  // USE_FILE_FOPEN
454         const int64_t pos = lseek(fd, 0, SEEK_CUR);
455         struct stat statbuf;
456         if (fstat(fd, &statbuf) == -1)
457         {
458             reportAlways("FsDir::fileLength error.")
459             return -1;
460         }
461         const int64_t len = static_cast<int64_t>(statbuf.st_size);
462 #endif  // USE_FILE_FOPEN
463         return static_cast<int>(pos < 0 || len < 0 || pos >= len);
464     }
465 
466     const char *loadFile(FsEntry *restrict const entry,
467                          std::string filename,
468                          int &restrict fileSize)
469     {
470         const DirEntry *const dirEntry = static_cast<DirEntry*>(entry);
471         const std::string path = dirEntry->rootSubDir + filename;
472         if (Files::existsLocal(path) == false)
473             return nullptr;
474         FILEHTYPE fd = FILEOPEN(path.c_str(),
475             FILEOPEN_FLAG_READ);
476         if (fd == FILEHDEFAULT)
477         {
478             reportAlways("VirtFs::loadFile file open error: %s",
479                 filename.c_str())
480             return nullptr;
481         }
482 
483         logger->log("Loaded %s/%s",
484             dirEntry->userDir.c_str(),
485             filename.c_str());
486 
487 #ifdef USE_FILE_FOPEN
488         fseek(fd, 0, SEEK_END);
489         const long sz = ftell(fd);
490         if (sz < 0)
491         {
492             reportAlways("FsDir::fileLength ftell error.")
493             if (fd != FILEHDEFAULT)
494                 FILECLOSE(fd);
495             return nullptr;
496         }
497         fseek(fd, 0, SEEK_SET);
498         fileSize = static_cast<int>(sz);
499 #else  // USE_FILE_FOPEN
500         struct stat statbuf;
501         if (fstat(fd, &statbuf) == -1)
502         {
503             reportAlways("FsDir::fileLength error.")
504             if (fd != FILEHDEFAULT)
505                 FILECLOSE(fd);
506             return nullptr;
507         }
508         fileSize = static_cast<int>(statbuf.st_size);
509 #endif  // USE_FILE_FOPEN
510 
511         // Allocate memory and load the file
512         char *restrict const buffer = new char[CAST_SIZE(fileSize)];
513         if (fileSize > 0)
514             buffer[fileSize - 1] = 0;
515 
516 #ifdef USE_FILE_FOPEN
517         const int cnt = CAST_S32(fread(buffer, 1, fileSize, fd));
518 #else  // USE_FILE_FOPEN
519         const int cnt = ::read(fd, buffer, fileSize);
520 #endif  // USE_FILE_FOPEN
521 
522         if (cnt <= 0)
523         {
524             delete [] buffer;
525             if (fd != FILEHDEFAULT)
526                 FILECLOSE(fd);
527             return nullptr;
528         }
529 
530         if (fd != FILEHDEFAULT)
531             FILECLOSE(fd);
532 
533         return buffer;
534     }
535 
536     void getFiles(FsEntry *restrict const entry,
537                   std::string dirName,
538                   StringVect &names)
539     {
540         const std::string path = static_cast<DirEntry*>(entry)->rootSubDir +
541             dirName;
542         const dirent *next_file = nullptr;
543         DIR *const dir = opendir(path.c_str());
544         if (dir != nullptr)
545         {
546             while ((next_file = readdir(dir)) != nullptr)
547             {
548                 struct stat statbuf;
549                 const std::string file = next_file->d_name;
550                 if (file == "." || file == "..")
551                     continue;
552 #ifndef WIN32
553                 if (mPermitLinks == false)
554                 {
555                     if (lstat(path.c_str(), &statbuf) == 0 &&
556                         S_ISLNK(statbuf.st_mode) != 0)
557                     {
558                         continue;
559                     }
560                 }
561 #endif  // WIN32
562 
563                 const std::string filePath = pathJoin(path, file);
564                 if (stat(filePath.c_str(), &statbuf) == 0)
565                 {
566                     if (S_ISDIR(statbuf.st_mode) != 0)
567                         continue;
568                 }
569 
570                 bool found(false);
571                 FOR_EACH (StringVectCIter, itn, names)
572                 {
573                     if (*itn == file)
574                     {
575                         found = true;
576                         break;
577                     }
578                 }
579                 if (found == false)
580                     names.push_back(file);
581             }
582             closedir(dir);
583         }
584     }
585 
586     void getFilesWithDir(FsEntry *restrict const entry,
587                          const std::string &dirName,
588                          StringVect &names)
589     {
590         const std::string path = static_cast<DirEntry*>(entry)->rootSubDir +
591             dirName;
592         const dirent *next_file = nullptr;
593         DIR *const dir = opendir(path.c_str());
594         if (dir != nullptr)
595         {
596             while ((next_file = readdir(dir)) != nullptr)
597             {
598                 struct stat statbuf;
599                 const std::string file = next_file->d_name;
600                 if (file == "." || file == "..")
601                     continue;
602 #ifndef WIN32
603                 if (mPermitLinks == false)
604                 {
605                     if (lstat(path.c_str(), &statbuf) == 0 &&
606                         S_ISLNK(statbuf.st_mode) != 0)
607                     {
608                         continue;
609                     }
610                 }
611 #endif  // WIN32
612 
613                 const std::string filePath = pathJoin(path, file);
614                 if (stat(filePath.c_str(), &statbuf) == 0)
615                 {
616                     if (S_ISDIR(statbuf.st_mode) != 0)
617                         continue;
618                 }
619 
620                 bool found(false);
621                 FOR_EACH (StringVectCIter, itn, names)
622                 {
623                     if (*itn == file)
624                     {
625                         found = true;
626                         break;
627                     }
628                 }
629                 if (found == false)
630                     names.push_back(pathJoin(dirName, file));
631             }
632             closedir(dir);
633         }
634     }
635 
636     void getDirs(FsEntry *restrict const entry,
637                  std::string dirName,
638                  StringVect &names)
639     {
640         const std::string path = static_cast<DirEntry*>(entry)->rootSubDir +
641             dirName;
642         const dirent *next_file = nullptr;
643         DIR *const dir = opendir(path.c_str());
644         if (dir != nullptr)
645         {
646             while ((next_file = readdir(dir)) != nullptr)
647             {
648                 struct stat statbuf;
649                 const std::string file = next_file->d_name;
650                 if (file == "." || file == "..")
651                     continue;
652 #ifndef WIN32
653                 if (mPermitLinks == false)
654                 {
655                     if (lstat(path.c_str(), &statbuf) == 0 &&
656                         S_ISLNK(statbuf.st_mode) != 0)
657                     {
658                         continue;
659                     }
660                 }
661 #endif  // WIN32
662 
663                 const std::string filePath = pathJoin(path, file);
664                 if (stat(filePath.c_str(), &statbuf) == 0)
665                 {
666                     if (S_ISDIR(statbuf.st_mode) == 0)
667                         continue;
668                 }
669 
670                 bool found(false);
671                 FOR_EACH (StringVectCIter, itn, names)
672                 {
673                     if (*itn == file)
674                     {
675                         found = true;
676                         break;
677                     }
678                 }
679                 if (found == false)
680                     names.push_back(file);
681             }
682             closedir(dir);
683         }
684     }
685 }  // namespace FsDir
686 
687 }  // namespace VirtFs
688