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