virtualFileSystem.cxx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. // Filename: virtualFileSystem.cxx
  2. // Created by: drose (03Aug02)
  3. //
  4. ////////////////////////////////////////////////////////////////////
  5. //
  6. // PANDA 3D SOFTWARE
  7. // Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved
  8. //
  9. // All use of this software is subject to the terms of the Panda 3d
  10. // Software license. You should have received a copy of this license
  11. // along with this source code; you will also find a current copy of
  12. // the license at http://www.panda3d.org/license.txt .
  13. //
  14. // To contact the maintainers of this program write to
  15. // [email protected] .
  16. //
  17. ////////////////////////////////////////////////////////////////////
  18. #include "virtualFileSystem.h"
  19. #include "virtualFileMount.h"
  20. #include "virtualFileMountMultifile.h"
  21. #include "virtualFileMountSystem.h"
  22. #include "dSearchPath.h"
  23. #include "dcast.h"
  24. #include "config_express.h"
  25. #include "executionEnvironment.h"
  26. #include "pset.h"
  27. VirtualFileSystem *VirtualFileSystem::_global_ptr = NULL;
  28. ////////////////////////////////////////////////////////////////////
  29. // Function: VirtualFileSystem::Constructor
  30. // Access: Published
  31. // Description:
  32. ////////////////////////////////////////////////////////////////////
  33. VirtualFileSystem::
  34. VirtualFileSystem() {
  35. _cwd = "/";
  36. }
  37. ////////////////////////////////////////////////////////////////////
  38. // Function: VirtualFileSystem::Destructor
  39. // Access: Published
  40. // Description:
  41. ////////////////////////////////////////////////////////////////////
  42. VirtualFileSystem::
  43. ~VirtualFileSystem() {
  44. unmount_all();
  45. }
  46. ////////////////////////////////////////////////////////////////////
  47. // Function: VirtualFileSystem::mount
  48. // Access: Published
  49. // Description: Mounts the indicated Multifile at the given mount
  50. // point. If flags contains MF_owns_pointer, the
  51. // Multifile will be deleted when it is eventually
  52. // unmounted.
  53. ////////////////////////////////////////////////////////////////////
  54. bool VirtualFileSystem::
  55. mount(Multifile *multifile, const string &mount_point, int flags) {
  56. VirtualFileMountMultifile *mount =
  57. new VirtualFileMountMultifile(this, multifile,
  58. normalize_mount_point(mount_point),
  59. flags);
  60. _mounts.push_back(mount);
  61. return true;
  62. }
  63. ////////////////////////////////////////////////////////////////////
  64. // Function: VirtualFileSystem::mount
  65. // Access: Published
  66. // Description: Mounts the indicated system file or directory at the
  67. // given mount point. If the named file is a directory,
  68. // mounts the directory. If the named file is a
  69. // Multifile, mounts it as a Multifile. Returns true on
  70. // success, false on failure.
  71. //
  72. // A given system directory may be mounted to multiple
  73. // different mount point, and the same mount point may
  74. // share multiple system directories. In the case of
  75. // ambiguities, the most-recently mounted system wins.
  76. ////////////////////////////////////////////////////////////////////
  77. bool VirtualFileSystem::
  78. mount(const Filename &physical_filename, const string &mount_point,
  79. int flags) {
  80. if (!physical_filename.exists()) {
  81. express_cat.warning()
  82. << "Attempt to mount " << physical_filename << ", not found.\n";
  83. return false;
  84. }
  85. if (physical_filename.is_directory()) {
  86. flags &= ~MF_owns_pointer;
  87. VirtualFileMountSystem *mount =
  88. new VirtualFileMountSystem(this, physical_filename,
  89. normalize_mount_point(mount_point),
  90. flags);
  91. _mounts.push_back(mount);
  92. return true;
  93. } else {
  94. // It's not a directory; it must be a Multifile.
  95. Multifile *multifile = new Multifile;
  96. // For now these are always opened read only. Maybe later we'll
  97. // support read-write on Multifiles.
  98. flags |= MF_read_only;
  99. if (!multifile->open_read(physical_filename)) {
  100. delete multifile;
  101. return false;
  102. }
  103. // We want to delete this pointer when we're done.
  104. flags |= MF_owns_pointer;
  105. return mount(multifile, mount_point, flags);
  106. }
  107. }
  108. ////////////////////////////////////////////////////////////////////
  109. // Function: VirtualFileSystem::unmount
  110. // Access: Published
  111. // Description: Unmounts all appearances of the indicated Multifile
  112. // from the file system. Returns the number of
  113. // appearances unmounted.
  114. ////////////////////////////////////////////////////////////////////
  115. int VirtualFileSystem::
  116. unmount(Multifile *multifile) {
  117. Mounts::iterator ri, wi;
  118. wi = ri = _mounts.begin();
  119. while (ri != _mounts.end()) {
  120. VirtualFileMount *mount = (*ri);
  121. (*wi) = mount;
  122. if (mount->is_exact_type(VirtualFileMountMultifile::get_class_type())) {
  123. VirtualFileMountMultifile *mmount =
  124. DCAST(VirtualFileMountMultifile, mount);
  125. if (mmount->get_multifile() == multifile) {
  126. // Remove this one. Don't increment wi.
  127. delete mount;
  128. } else {
  129. // Don't remove this one.
  130. ++wi;
  131. }
  132. } else {
  133. // Don't remove this one.
  134. ++wi;
  135. }
  136. ++ri;
  137. }
  138. int num_removed = _mounts.end() - wi;
  139. _mounts.erase(wi, _mounts.end());
  140. return num_removed;
  141. }
  142. ////////////////////////////////////////////////////////////////////
  143. // Function: VirtualFileSystem::unmount
  144. // Access: Published
  145. // Description: Unmounts all appearances of the indicated physical
  146. // filename (either a directory name or a Multifile
  147. // name) from the file system. Returns the number of
  148. // appearances unmounted.
  149. ////////////////////////////////////////////////////////////////////
  150. int VirtualFileSystem::
  151. unmount(const Filename &physical_filename) {
  152. Mounts::iterator ri, wi;
  153. wi = ri = _mounts.begin();
  154. while (ri != _mounts.end()) {
  155. VirtualFileMount *mount = (*ri);
  156. (*wi) = mount;
  157. if (mount->get_physical_filename() == physical_filename) {
  158. // Remove this one. Don't increment wi.
  159. delete mount;
  160. } else {
  161. // Don't remove this one.
  162. ++wi;
  163. }
  164. ++ri;
  165. }
  166. int num_removed = _mounts.end() - wi;
  167. _mounts.erase(wi, _mounts.end());
  168. return num_removed;
  169. }
  170. ////////////////////////////////////////////////////////////////////
  171. // Function: VirtualFileSystem::unmount_point
  172. // Access: Published
  173. // Description: Unmounts all systems attached to the given mount
  174. // point from the file system. Returns the number of
  175. // appearances unmounted.
  176. ////////////////////////////////////////////////////////////////////
  177. int VirtualFileSystem::
  178. unmount_point(const string &mount_point) {
  179. Filename nmp = normalize_mount_point(mount_point);
  180. Mounts::iterator ri, wi;
  181. wi = ri = _mounts.begin();
  182. while (ri != _mounts.end()) {
  183. VirtualFileMount *mount = (*ri);
  184. (*wi) = mount;
  185. if (mount->get_mount_point() == nmp) {
  186. // Remove this one. Don't increment wi.
  187. delete mount;
  188. } else {
  189. // Don't remove this one.
  190. ++wi;
  191. }
  192. ++ri;
  193. }
  194. int num_removed = _mounts.end() - wi;
  195. _mounts.erase(wi, _mounts.end());
  196. return num_removed;
  197. }
  198. ////////////////////////////////////////////////////////////////////
  199. // Function: VirtualFileSystem::unmount_all
  200. // Access: Published
  201. // Description: Unmounts all files from the file system. Returns the
  202. // number of systems unmounted.
  203. ////////////////////////////////////////////////////////////////////
  204. int VirtualFileSystem::
  205. unmount_all() {
  206. Mounts::iterator mi;
  207. for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
  208. VirtualFileMount *mount = (*mi);
  209. delete mount;
  210. }
  211. int num_removed = _mounts.size();
  212. _mounts.clear();
  213. return num_removed;
  214. }
  215. ////////////////////////////////////////////////////////////////////
  216. // Function: VirtualFileSystem::chdir
  217. // Access: Published
  218. // Description: Changes the current directory. This is used to
  219. // resolve relative pathnames in get_file() and/or
  220. // find_file(). Returns true if successful, false
  221. // otherwise.
  222. //
  223. // This accepts a string rather than a Filename simply
  224. // for programmer convenience from the Python prompt.
  225. ////////////////////////////////////////////////////////////////////
  226. bool VirtualFileSystem::
  227. chdir(const string &new_directory) {
  228. if (new_directory == "/") {
  229. // We can always return to the root.
  230. _cwd = new_directory;
  231. return true;
  232. }
  233. PT(VirtualFile) file = get_file(new_directory);
  234. if (file != (VirtualFile *)NULL && file->is_directory()) {
  235. _cwd = file->get_filename();
  236. return true;
  237. }
  238. return false;
  239. }
  240. ////////////////////////////////////////////////////////////////////
  241. // Function: VirtualFileSystem::get_cwd
  242. // Access: Published
  243. // Description: Returns the current directory name. See chdir().
  244. ////////////////////////////////////////////////////////////////////
  245. const Filename &VirtualFileSystem::
  246. get_cwd() const {
  247. return _cwd;
  248. }
  249. ////////////////////////////////////////////////////////////////////
  250. // Function: VirtualFileSystem::get_file
  251. // Access: Published
  252. // Description: Looks up the file by the indicated name in the file
  253. // system. Returns a VirtualFile pointer representing
  254. // the file if it is found, or NULL if it is not.
  255. ////////////////////////////////////////////////////////////////////
  256. PT(VirtualFile) VirtualFileSystem::
  257. get_file(const Filename &filename) const {
  258. nassertr(!filename.empty(), NULL);
  259. Filename pathname(filename);
  260. if (pathname.is_local()) {
  261. pathname = Filename(_cwd, filename);
  262. }
  263. pathname.standardize();
  264. string strpath = pathname.get_fullpath().substr(1);
  265. // Now scan all the mount points, from the back (since later mounts
  266. // override more recent ones), until a match is found.
  267. PT(VirtualFile) found_file = NULL;
  268. VirtualFileComposite *composite_file = NULL;
  269. Mounts::const_reverse_iterator rmi;
  270. for (rmi = _mounts.rbegin(); rmi != _mounts.rend(); ++rmi) {
  271. VirtualFileMount *mount = (*rmi);
  272. string mount_point = mount->get_mount_point();
  273. if (strpath == mount_point) {
  274. // Here's an exact match on the mount point. This filename is
  275. // the root directory of this mount object.
  276. if (found_match(found_file, composite_file, mount, "")) {
  277. return found_file;
  278. }
  279. } else if (mount_point.empty()) {
  280. // This is the root mount point; all files are in here.
  281. if (mount->has_file(strpath)) {
  282. // Bingo!
  283. if (found_match(found_file, composite_file, mount, strpath)) {
  284. return found_file;
  285. }
  286. }
  287. } else if (strpath.length() > mount_point.length() &&
  288. strpath.substr(0, mount_point.length()) == mount_point &&
  289. strpath[mount_point.length()] == '/') {
  290. // This pathname falls within this mount system.
  291. Filename local_filename = strpath.substr(mount_point.length() + 1);
  292. if (mount->has_file(local_filename)) {
  293. // Bingo!
  294. if (found_match(found_file, composite_file, mount, local_filename)) {
  295. return found_file;
  296. }
  297. }
  298. }
  299. }
  300. return found_file;
  301. }
  302. ////////////////////////////////////////////////////////////////////
  303. // Function: VirtualFileSystem::find_file
  304. // Access: Published
  305. // Description: Uses the indicated search path to find the file
  306. // within the file system. Returns the first occurrence
  307. // of the file found, or NULL if the file cannot be
  308. // found.
  309. ////////////////////////////////////////////////////////////////////
  310. PT(VirtualFile) VirtualFileSystem::
  311. find_file(const Filename &filename, const DSearchPath &searchpath) const {
  312. if (!filename.is_local()) {
  313. return get_file(filename);
  314. }
  315. int num_directories = searchpath.get_num_directories();
  316. for (int i = 0; i < num_directories; i++) {
  317. Filename match(searchpath.get_directory(i), filename);
  318. PT(VirtualFile) found_file = get_file(match);
  319. if (found_file != (VirtualFile *)NULL) {
  320. return found_file;
  321. }
  322. }
  323. return NULL;
  324. }
  325. ////////////////////////////////////////////////////////////////////
  326. // Function: VirtualFileSystem::resolve_filename
  327. // Access: Public
  328. // Description: Searches the given search path for the filename. If
  329. // it is found, updates the filename to the full
  330. // pathname found and returns true; otherwise, returns
  331. // false.
  332. ////////////////////////////////////////////////////////////////////
  333. bool VirtualFileSystem::
  334. resolve_filename(Filename &filename,
  335. const DSearchPath &searchpath,
  336. const string &default_extension) const {
  337. PT(VirtualFile) found;
  338. if (filename.is_local()) {
  339. found = find_file(filename.get_fullpath(), searchpath);
  340. if (found.is_null()) {
  341. // We didn't find it with the given extension; can we try the
  342. // default extension?
  343. if (filename.get_extension().empty() && !default_extension.empty()) {
  344. Filename try_ext = filename;
  345. try_ext.set_extension(default_extension);
  346. found = find_file(try_ext.get_fullpath(), searchpath);
  347. }
  348. }
  349. } else {
  350. if (exists(filename)) {
  351. // The full pathname exists. Return true.
  352. return true;
  353. } else {
  354. // The full pathname doesn't exist with the given extension;
  355. // does it exist with the default extension?
  356. if (filename.get_extension().empty() && !default_extension.empty()) {
  357. Filename try_ext = filename;
  358. try_ext.set_extension(default_extension);
  359. found = get_file(try_ext);
  360. }
  361. }
  362. }
  363. if (!found.is_null()) {
  364. filename = found->get_filename();
  365. return true;
  366. }
  367. return false;
  368. }
  369. ////////////////////////////////////////////////////////////////////
  370. // Function: VirtualFileSystem::find_all_files
  371. // Access: Public
  372. // Description: Searches all the directories in the search list for
  373. // the indicated file, in order. Fills up the results
  374. // list with *all* of the matching filenames found, if
  375. // any. Returns the number of matches found.
  376. //
  377. // It is the responsibility of the the caller to clear
  378. // the results list first; otherwise, the newly-found
  379. // files will be appended to the list.
  380. ////////////////////////////////////////////////////////////////////
  381. int VirtualFileSystem::
  382. find_all_files(const Filename &filename, const DSearchPath &searchpath,
  383. DSearchPath::Results &results) const {
  384. int num_added = 0;
  385. if (filename.is_local()) {
  386. int num_directories = searchpath.get_num_directories();
  387. for (int i = 0; i < num_directories; i++) {
  388. Filename match(searchpath.get_directory(i), filename);
  389. if (exists(match)) {
  390. results.add_file(match);
  391. num_added++;
  392. }
  393. }
  394. }
  395. return num_added;
  396. }
  397. ////////////////////////////////////////////////////////////////////
  398. // Function: VirtualFileSystem::write
  399. // Access: Published
  400. // Description:
  401. ////////////////////////////////////////////////////////////////////
  402. void VirtualFileSystem::
  403. write(ostream &out) const {
  404. Mounts::const_iterator mi;
  405. for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
  406. VirtualFileMount *mount = (*mi);
  407. mount->write(out);
  408. }
  409. }
  410. ////////////////////////////////////////////////////////////////////
  411. // Function: VirtualFileSystem::get_global_ptr
  412. // Access: Published, Static
  413. // Description: Returns the default global VirtualFileSystem. You
  414. // may create your own personal VirtualFileSystem
  415. // objects and use them for whatever you like, but Panda
  416. // will attempt to load models and stuff from this
  417. // default object.
  418. //
  419. // Initially, the global VirtualFileSystem is set up to
  420. // mount the OS filesystem to root; i.e. it is
  421. // equivalent to the OS filesystem. This may be
  422. // subsequently adjusted by the user.
  423. ////////////////////////////////////////////////////////////////////
  424. VirtualFileSystem *VirtualFileSystem::
  425. get_global_ptr() {
  426. if (_global_ptr == (VirtualFileSystem *)NULL) {
  427. _global_ptr = new VirtualFileSystem;
  428. // Set up the default mounts. First, there is always the root
  429. // mount.
  430. _global_ptr->mount("/", "/", 0);
  431. // And our initial cwd comes from the environment.
  432. _global_ptr->chdir(ExecutionEnvironment::get_cwd());
  433. // Then, we add whatever mounts are listed in the Configrc file.
  434. Config::ConfigTable::Symbol mounts;
  435. config_express.GetAll("vfs-mount", mounts);
  436. // When we use GetAll(), we might inadvertently read duplicate
  437. // lines. Filter them out with a set.
  438. pset<string> already_read;
  439. Config::ConfigTable::Symbol::iterator si;
  440. for (si = mounts.begin(); si != mounts.end(); ++si) {
  441. string mount_desc = (*si).Val();
  442. if (already_read.insert(mount_desc).second) {
  443. // The vfs-mount syntax is:
  444. // vfs-mount system-filename mount-point [options]
  445. // The last two spaces mark the beginning of the mount point,
  446. // and of the options, respectively. There might be multiple
  447. // spaces in the system filename, which are part of the
  448. // filename.
  449. // The last space marks the beginning of the mount point.
  450. // Spaces before that are part of the system filename.
  451. size_t space = mount_desc.rfind(' ');
  452. if (space == string::npos) {
  453. express_cat.warning()
  454. << "No space in vfs-mount descriptor: " << mount_desc << "\n";
  455. } else {
  456. string mount_point = mount_desc.substr(space + 1);
  457. while (space > 0 && isspace(mount_desc[space - 1])) {
  458. space--;
  459. }
  460. mount_desc = mount_desc.substr(0, space);
  461. string options;
  462. space = mount_desc.rfind(' ');
  463. if (space != string::npos) {
  464. // If there's another space, we have the optional options field.
  465. options = mount_point;
  466. mount_point = mount_desc.substr(space + 1);
  467. while (space > 0 && isspace(mount_desc[space - 1])) {
  468. space--;
  469. }
  470. mount_desc = mount_desc.substr(0, space);
  471. }
  472. mount_desc = ExecutionEnvironment::expand_string(mount_desc);
  473. Filename physical_filename = Filename::from_os_specific(mount_desc);
  474. _global_ptr->mount(physical_filename, mount_point, 0);
  475. }
  476. }
  477. }
  478. }
  479. return _global_ptr;
  480. }
  481. ////////////////////////////////////////////////////////////////////
  482. // Function: VirtualFileSystem::scan_mount_points
  483. // Access: Public
  484. // Description: Adds to names a list of all the mount points in use
  485. // that are one directory below path, if any. That is,
  486. // these are the external files or directories mounted
  487. // directly to the indicated path.
  488. //
  489. // The names vector is filled with a set of basenames,
  490. // the basename part of the mount point.
  491. ////////////////////////////////////////////////////////////////////
  492. void VirtualFileSystem::
  493. scan_mount_points(vector_string &names, const Filename &path) const {
  494. nassertv(!path.empty() && !path.is_local());
  495. string prefix = path.get_fullpath().substr(1);
  496. Mounts::const_iterator mi;
  497. for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
  498. VirtualFileMount *mount = (*mi);
  499. string mount_point = mount->get_mount_point();
  500. if (prefix.empty()) {
  501. // The indicated path is the root. Is the mount point on the
  502. // root?
  503. if (mount_point.find('/') == string::npos) {
  504. // No embedded slashes, so the mount point is only one
  505. // directory below the root.
  506. names.push_back(mount_point);
  507. }
  508. } else {
  509. if (mount_point.substr(0, prefix.length()) == prefix &&
  510. mount_point.length() > prefix.length() &&
  511. mount_point[prefix.length()] == '/') {
  512. // This mount point is below the indicated path. Is it only one
  513. // directory below?
  514. string basename = mount_point.substr(prefix.length());
  515. if (basename.find('/') == string::npos) {
  516. // No embedded slashes, so it's only one directory below.
  517. names.push_back(basename);
  518. }
  519. }
  520. }
  521. }
  522. }
  523. ////////////////////////////////////////////////////////////////////
  524. // Function: VirtualFileSystem::normalize_mount_point
  525. // Access: Private
  526. // Description: Converts the mount point string supplied by the user
  527. // to standard form (relative to the current directory,
  528. // with no double slashes, and not terminating with a
  529. // slash). The initial slash is removed.
  530. ////////////////////////////////////////////////////////////////////
  531. Filename VirtualFileSystem::
  532. normalize_mount_point(const string &mount_point) const {
  533. Filename nmp = mount_point;
  534. if (nmp.is_local()) {
  535. nmp = Filename(_cwd, mount_point);
  536. }
  537. nmp.standardize();
  538. nassertr(!nmp.empty() && nmp[0] == '/', nmp);
  539. return nmp.get_fullpath().substr(1);
  540. }
  541. ////////////////////////////////////////////////////////////////////
  542. // Function: VirtualFileSystem::found_match
  543. // Access: Private
  544. // Description: Evaluates one match found during a get_file()
  545. // operation. There may be multiple matches for a
  546. // particular filename due to the ambiguities introduced
  547. // by allowing multiple mount points, so we may have to
  548. // keep searching even after the first match is found.
  549. //
  550. // Returns true if the search should terminate now, or
  551. // false if it should keep iterating.
  552. ////////////////////////////////////////////////////////////////////
  553. bool VirtualFileSystem::
  554. found_match(PT(VirtualFile) &found_file, VirtualFileComposite *&composite_file,
  555. VirtualFileMount *mount, const string &local_filename) const {
  556. if (found_file == (VirtualFile *)NULL) {
  557. // This was our first match. Save it.
  558. found_file = new VirtualFileSimple(mount, local_filename);
  559. if (!mount->is_directory(local_filename)) {
  560. // If it's not a directory, we're done.
  561. return true;
  562. }
  563. } else {
  564. // This was our second match. The previous match(es) must
  565. // have been directories.
  566. if (!mount->is_directory(local_filename)) {
  567. // However, this one isn't a directory. We're done.
  568. return true;
  569. }
  570. // At least two directories matched to the same path. We
  571. // need a composite directory.
  572. if (composite_file == (VirtualFileComposite *)NULL) {
  573. composite_file =
  574. new VirtualFileComposite((VirtualFileSystem *)this, found_file->get_filename());
  575. composite_file->add_component(found_file);
  576. found_file = composite_file;
  577. }
  578. composite_file->add_component(new VirtualFileSimple(mount, local_filename));
  579. }
  580. // Keep going, looking for more directories.
  581. return false;
  582. }