Panda3D

virtualFileSystem.cxx

00001 // Filename: virtualFileSystem.cxx
00002 // Created by:  drose (03Aug02)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "virtualFileSystem.h"
00016 #include "virtualFileSimple.h"
00017 #include "virtualFileComposite.h"
00018 #include "virtualFileMount.h"
00019 #include "virtualFileMountMultifile.h"
00020 #include "virtualFileMountSystem.h"
00021 #include "streamWrapper.h"
00022 #include "dSearchPath.h"
00023 #include "dcast.h"
00024 #include "config_express.h"
00025 #include "executionEnvironment.h"
00026 #include "pset.h"
00027 
00028 VirtualFileSystem *VirtualFileSystem::_global_ptr = NULL;
00029 
00030 
00031 ////////////////////////////////////////////////////////////////////
00032 //     Function: VirtualFileSystem::Constructor
00033 //       Access: Published
00034 //  Description: 
00035 ////////////////////////////////////////////////////////////////////
00036 VirtualFileSystem::
00037 VirtualFileSystem() :
00038   vfs_case_sensitive
00039 ("vfs-case-sensitive", 
00040 #ifdef NDEBUG
00041  false,  // The default for a production build is not case-sensitive;
00042          // this avoids runtime overhead to verify case sensitivity.
00043 #else
00044  true,
00045 #endif
00046  PRC_DESC("Set this true to make the VirtualFileSystem present the native "
00047           "OS-provided filesystem as if it were a case-sensitive file "
00048           "system, even if it is not (e.g. on Windows).  This variable "
00049           "has no effect if the native filesystem is already case-sensitive, "
00050           "and it has no effect on mounted multifile systems, which are "
00051           "always case-sensitive.")),
00052   vfs_implicit_pz
00053   ("vfs-implicit-pz", true,
00054    PRC_DESC("When this is true, the VirtualFileSystem will pretend a named "
00055             "file exists even if it doesn't, as long as a filename with the "
00056             "same name and the additional extension .pz does exist.  In this "
00057             "case, the VirtualFileSystem will implicitly open the .pz file "
00058             "and decompress it on-the-fly.")),
00059   vfs_implicit_mf
00060   ("vfs-implicit-mf", false,
00061    PRC_DESC("When this is true, the VirtualFileSystem will automatically "
00062             "mount multifiles on-the-fly when they are used as directories.  "
00063             "For instance, opening the file /c/files/foo.mf/dirname/mytex.jpg "
00064             "will implicitly retrieve a file named 'dirname/mytex.jpg' "
00065             "within the multifile /c/files/foo.mf, even if the multifile "
00066             "has not already been mounted.  This makes all of your multifiles "
00067             "act like directories."))
00068 {
00069   _cwd = "/";
00070   _mount_seq = 0;
00071 }
00072 
00073 ////////////////////////////////////////////////////////////////////
00074 //     Function: VirtualFileSystem::Destructor
00075 //       Access: Published
00076 //  Description: 
00077 ////////////////////////////////////////////////////////////////////
00078 VirtualFileSystem::
00079 ~VirtualFileSystem() {
00080   unmount_all();
00081 }
00082 
00083 ////////////////////////////////////////////////////////////////////
00084 //     Function: VirtualFileSystem::mount
00085 //       Access: Published
00086 //  Description: Mounts the indicated Multifile at the given mount
00087 //               point.
00088 ////////////////////////////////////////////////////////////////////
00089 bool VirtualFileSystem::
00090 mount(Multifile *multifile, const Filename &mount_point, int flags) {
00091   PT(VirtualFileMountMultifile) new_mount = 
00092     new VirtualFileMountMultifile(multifile);
00093   return mount(new_mount, mount_point, flags);
00094 }
00095 
00096 ////////////////////////////////////////////////////////////////////
00097 //     Function: VirtualFileSystem::mount
00098 //       Access: Published
00099 //  Description: Mounts the indicated system file or directory at the
00100 //               given mount point.  If the named file is a directory,
00101 //               mounts the directory.  If the named file is a
00102 //               Multifile, mounts it as a Multifile.  Returns true on
00103 //               success, false on failure.
00104 //
00105 //               A given system directory may be mounted to multiple
00106 //               different mount point, and the same mount point may
00107 //               share multiple system directories.  In the case of
00108 //               ambiguities (that is, two different files with
00109 //               exactly the same full pathname), the most-recently
00110 //               mounted system wins.
00111 //
00112 //               The filename specified as the first parameter must
00113 //               refer to a real, physical filename on disk; it cannot
00114 //               be a virtual file already appearing within the vfs
00115 //               filespace.  However, it is possible to mount such a
00116 //               file; see mount_loop() for this.
00117 ////
00118 //               Note that a mounted VirtualFileSystem directory is
00119 //               fully case-sensitive, unlike the native Windows file
00120 //               system, so you must refer to files within the virtual
00121 //               file system with exactly the right case.
00122 ////////////////////////////////////////////////////////////////////
00123 bool VirtualFileSystem::
00124 mount(const Filename &physical_filename, const Filename &mount_point, 
00125       int flags, const string &password) {
00126   if (!physical_filename.exists()) {
00127     express_cat->warning()
00128       << "Attempt to mount " << physical_filename << ", not found.\n";
00129     return false;
00130   }
00131 
00132   if (physical_filename.is_directory()) {
00133     PT(VirtualFileMountSystem) new_mount =
00134       new VirtualFileMountSystem(physical_filename);
00135     return mount(new_mount, mount_point, flags);
00136   } else {
00137     // It's not a directory; it must be a Multifile.
00138     PT(Multifile) multifile = new Multifile;
00139     multifile->set_encryption_password(password);
00140 
00141     // For now these are always opened read only.  Maybe later we'll
00142     // support read-write on Multifiles.
00143     flags |= MF_read_only;
00144     if (!multifile->open_read(physical_filename)) {
00145       return false;
00146     }
00147 
00148     return mount(multifile, mount_point, flags);
00149   }
00150 }
00151 
00152 ////////////////////////////////////////////////////////////////////
00153 //     Function: VirtualFileSystem::mount_loop
00154 //       Access: Published
00155 //  Description: This is similar to mount(), but it receives the name
00156 //               of a Multifile that already appears within the
00157 //               virtual file system.  It can be used to mount a
00158 //               Multifile that is itself hosted within a
00159 //               virtually-mounted Multifile.
00160 //
00161 //               This interface can also be used to mount physical
00162 //               files (that appear within the virtual filespace), but
00163 //               it cannot be used to mount directories.  Use mount()
00164 //               if you need to mount a directory.
00165 //
00166 //               Note that there is additional overhead, in the form
00167 //               of additional buffer copies of the data, for
00168 //               recursively mounting a multifile like this.
00169 ////////////////////////////////////////////////////////////////////
00170 bool VirtualFileSystem::
00171 mount_loop(const Filename &virtual_filename, const Filename &mount_point, 
00172            int flags, const string &password) {
00173   PT(VirtualFile) file = get_file(virtual_filename, false);
00174   if (file == NULL) {
00175     express_cat->warning()
00176       << "Attempt to mount " << virtual_filename << ", not found.\n";
00177     return false;
00178   }
00179 
00180   if (file->is_directory()) {
00181     PT(VirtualFileMountSystem) new_mount =
00182       new VirtualFileMountSystem(virtual_filename);
00183     return mount(new_mount, mount_point, flags);
00184 
00185   } else {
00186     // It's not a directory; it must be a Multifile.
00187     PT(Multifile) multifile = new Multifile;
00188     multifile->set_encryption_password(password);
00189 
00190     // For now these are always opened read only.  Maybe later we'll
00191     // support read-write on Multifiles.
00192     flags |= MF_read_only;
00193     if (!multifile->open_read(virtual_filename)) {
00194       return false;
00195     }
00196 
00197     return mount(multifile, mount_point, flags);
00198   }
00199 }
00200 
00201 ////////////////////////////////////////////////////////////////////
00202 //     Function: VirtualFileSystem::mount
00203 //       Access: Published
00204 //  Description: Adds the given VirtualFileMount object to the mount
00205 //               list.  This is a lower-level function that the other
00206 //               flavors of mount(); it requires you to create a
00207 //               VirtualFileMount object specifically.
00208 ////////////////////////////////////////////////////////////////////
00209 bool VirtualFileSystem::
00210 mount(VirtualFileMount *mount, const Filename &mount_point, int flags) {
00211   if (express_cat->is_debug()) {
00212     express_cat->debug()
00213       << "mount " << *mount << " under " << mount_point << "\n";
00214   }
00215 
00216   _lock.acquire();
00217   bool result = do_mount(mount, mount_point, flags);
00218   _lock.release();
00219   return result;
00220 }
00221 
00222 ////////////////////////////////////////////////////////////////////
00223 //     Function: VirtualFileSystem::unmount
00224 //       Access: Published
00225 //  Description: Unmounts all appearances of the indicated Multifile
00226 //               from the file system.  Returns the number of
00227 //               appearances unmounted.
00228 ////////////////////////////////////////////////////////////////////
00229 int VirtualFileSystem::
00230 unmount(Multifile *multifile) {
00231   _lock.acquire();
00232   Mounts::iterator ri, wi;
00233   wi = ri = _mounts.begin();
00234   while (ri != _mounts.end()) {
00235     VirtualFileMount *mount = (*ri);
00236     (*wi) = mount;
00237 
00238     if (mount->is_exact_type(VirtualFileMountMultifile::get_class_type())) {
00239       VirtualFileMountMultifile *mmount = 
00240         DCAST(VirtualFileMountMultifile, mount);
00241       if (mmount->get_multifile() == multifile) {
00242         // Remove this one.  Don't increment wi.
00243         if (express_cat->is_debug()) {
00244           express_cat->debug()
00245             << "unmount " << *mount << " from " << mount->get_mount_point() << "\n";
00246         }
00247         mount->_file_system = NULL;
00248 
00249       } else {
00250         // Don't remove this one.
00251         ++wi;
00252       }
00253     } else {
00254       // Don't remove this one.
00255       ++wi;
00256     }
00257     ++ri;
00258   }
00259 
00260   int num_removed = _mounts.end() - wi;
00261   _mounts.erase(wi, _mounts.end());
00262   ++_mount_seq;
00263   _lock.release();
00264   return num_removed;
00265 }
00266 
00267 ////////////////////////////////////////////////////////////////////
00268 //     Function: VirtualFileSystem::unmount
00269 //       Access: Published
00270 //  Description: Unmounts all appearances of the indicated directory
00271 //               name or multifile name from the file system.  Returns
00272 //               the number of appearances unmounted.
00273 ////////////////////////////////////////////////////////////////////
00274 int VirtualFileSystem::
00275 unmount(const Filename &physical_filename) {
00276   _lock.acquire();
00277   Mounts::iterator ri, wi;
00278   wi = ri = _mounts.begin();
00279   while (ri != _mounts.end()) {
00280     VirtualFileMount *mount = (*ri);
00281     (*wi) = mount;
00282 
00283     if (mount->is_exact_type(VirtualFileMountSystem::get_class_type())) {
00284       VirtualFileMountSystem *smount = 
00285         DCAST(VirtualFileMountSystem, mount);
00286       if (smount->get_physical_filename() == physical_filename) {
00287         // Remove this one.  Don't increment wi.
00288         if (express_cat->is_debug()) {
00289           express_cat->debug()
00290             << "unmount " << *mount << " from " << mount->get_mount_point() << "\n";
00291         }
00292         mount->_file_system = NULL;
00293         
00294       } else {
00295         // Don't remove this one.
00296         ++wi;
00297       }
00298 
00299     } else if (mount->is_exact_type(VirtualFileMountMultifile::get_class_type())) {
00300       VirtualFileMountMultifile *mmount = 
00301         DCAST(VirtualFileMountMultifile, mount);
00302       if (mmount->get_multifile()->get_multifile_name() == physical_filename) {
00303         // Remove this one.  Don't increment wi.
00304         if (express_cat->is_debug()) {
00305           express_cat->debug()
00306             << "unmount " << *mount << " from " << mount->get_mount_point() << "\n";
00307         }
00308         mount->_file_system = NULL;
00309 
00310       } else {
00311         // Don't remove this one.
00312         ++wi;
00313       }
00314 
00315     } else {
00316       // Don't remove this one.
00317       ++wi;
00318     }
00319     ++ri;
00320   }
00321 
00322   int num_removed = _mounts.end() - wi;
00323   _mounts.erase(wi, _mounts.end());
00324   ++_mount_seq;
00325   _lock.release();
00326   return num_removed;
00327 }
00328 
00329 ////////////////////////////////////////////////////////////////////
00330 //     Function: VirtualFileSystem::unmount
00331 //       Access: Published
00332 //  Description: Unmounts the indicated VirtualFileMount object
00333 //               from the file system.  Returns the number of
00334 //               appearances unmounted.
00335 ////////////////////////////////////////////////////////////////////
00336 int VirtualFileSystem::
00337 unmount(VirtualFileMount *mount) {
00338   _lock.acquire();
00339   Mounts::iterator ri, wi;
00340   wi = ri = _mounts.begin();
00341   while (ri != _mounts.end()) {
00342     (*wi) = (*ri);
00343     if ((*ri) == mount) {
00344       // Remove this one.  Don't increment wi.
00345       if (express_cat->is_debug()) {
00346         express_cat->debug()
00347           << "unmount " << *mount << " from " << mount->get_mount_point() << "\n";
00348       }
00349       (*ri)->_file_system = NULL;
00350 
00351     } else {
00352       // Don't remove this one.
00353       ++wi;
00354     }
00355     ++ri;
00356   }
00357 
00358   int num_removed = _mounts.end() - wi;
00359   _mounts.erase(wi, _mounts.end());
00360   ++_mount_seq;
00361   _lock.release();
00362   return num_removed;
00363 }
00364 
00365 ////////////////////////////////////////////////////////////////////
00366 //     Function: VirtualFileSystem::unmount_point
00367 //       Access: Published
00368 //  Description: Unmounts all systems attached to the given mount
00369 //               point from the file system.  Returns the number of
00370 //               appearances unmounted.
00371 ////////////////////////////////////////////////////////////////////
00372 int VirtualFileSystem::
00373 unmount_point(const Filename &mount_point) {
00374   _lock.acquire();
00375   Filename nmp = normalize_mount_point(mount_point);
00376   Mounts::iterator ri, wi;
00377   wi = ri = _mounts.begin();
00378   while (ri != _mounts.end()) {
00379     VirtualFileMount *mount = (*ri);
00380     (*wi) = mount;
00381 
00382     if (mount->get_mount_point() == nmp) {
00383       // Remove this one.  Don't increment wi.
00384       if (express_cat->is_debug()) {
00385         express_cat->debug()
00386           << "unmount " << *mount << " from " << mount->get_mount_point() << "\n";
00387       }
00388       mount->_file_system = NULL;
00389 
00390     } else {
00391       // Don't remove this one.
00392       ++wi;
00393     }
00394     ++ri;
00395   }
00396 
00397   int num_removed = _mounts.end() - wi;
00398   _mounts.erase(wi, _mounts.end());
00399   ++_mount_seq;
00400   _lock.release();
00401   return num_removed;
00402 }
00403 
00404 ////////////////////////////////////////////////////////////////////
00405 //     Function: VirtualFileSystem::unmount_all
00406 //       Access: Published
00407 //  Description: Unmounts all files from the file system.  Returns the
00408 //               number of systems unmounted.
00409 ////////////////////////////////////////////////////////////////////
00410 int VirtualFileSystem::
00411 unmount_all() {
00412   _lock.acquire();
00413   Mounts::iterator ri;
00414   for (ri = _mounts.begin(); ri != _mounts.end(); ++ri) {
00415     VirtualFileMount *mount = (*ri);
00416     if (express_cat->is_debug()) {
00417       express_cat->debug()
00418         << "unmount " << *mount << " from " << mount->get_mount_point() << "\n";
00419     }
00420     mount->_file_system = NULL;
00421   }
00422 
00423   int num_removed = _mounts.size();
00424   _mounts.clear();
00425   ++_mount_seq;
00426   _lock.release();
00427   return num_removed;
00428 }
00429 
00430 ////////////////////////////////////////////////////////////////////
00431 //     Function: VirtualFileSystem::get_num_mounts
00432 //       Access: Published
00433 //  Description: Returns the number of individual mounts in the
00434 //               system.
00435 ////////////////////////////////////////////////////////////////////
00436 int VirtualFileSystem::
00437 get_num_mounts() const {
00438   ((VirtualFileSystem *)this)->_lock.acquire();
00439   int result = _mounts.size();
00440   ((VirtualFileSystem *)this)->_lock.release();
00441   return result;
00442 }
00443 
00444 ////////////////////////////////////////////////////////////////////
00445 //     Function: VirtualFileSystem::get_mount
00446 //       Access: Published
00447 //  Description: Returns the nth mount in the system.
00448 ////////////////////////////////////////////////////////////////////
00449 PT(VirtualFileMount) VirtualFileSystem::
00450 get_mount(int n) const {
00451   ((VirtualFileSystem *)this)->_lock.acquire();
00452   nassertd(n >= 0 && n < (int)_mounts.size()) {
00453     ((VirtualFileSystem *)this)->_lock.release();
00454     return NULL;
00455   }
00456   PT(VirtualFileMount) result = _mounts[n];
00457   ((VirtualFileSystem *)this)->_lock.release();
00458   return result;
00459 }
00460 
00461 ////////////////////////////////////////////////////////////////////
00462 //     Function: VirtualFileSystem::chdir
00463 //       Access: Published
00464 //  Description: Changes the current directory.  This is used to
00465 //               resolve relative pathnames in get_file() and/or
00466 //               find_file().  Returns true if successful, false
00467 //               otherwise.
00468 ////////////////////////////////////////////////////////////////////
00469 bool VirtualFileSystem::
00470 chdir(const Filename &new_directory) {
00471   _lock.acquire();
00472   if (new_directory == "/") {
00473     // We can always return to the root.
00474     _cwd = new_directory;
00475     _lock.release();
00476     return true;
00477   }
00478 
00479   PT(VirtualFile) file = do_get_file(new_directory, true);
00480   if (file != (VirtualFile *)NULL && file->is_directory()) {
00481     _cwd = file->get_filename();
00482     _lock.release();
00483     return true;
00484   }
00485   _lock.release();
00486   return false;
00487 }
00488 
00489 ////////////////////////////////////////////////////////////////////
00490 //     Function: VirtualFileSystem::get_cwd
00491 //       Access: Published
00492 //  Description: Returns the current directory name.  See chdir().
00493 ////////////////////////////////////////////////////////////////////
00494 Filename VirtualFileSystem::
00495 get_cwd() const {
00496   ((VirtualFileSystem *)this)->_lock.acquire();
00497   Filename result = _cwd;
00498   ((VirtualFileSystem *)this)->_lock.release();
00499   return result;
00500 }
00501 
00502 ////////////////////////////////////////////////////////////////////
00503 //     Function: VirtualFileSystem::get_file
00504 //       Access: Published
00505 //  Description: Looks up the file by the indicated name in the file
00506 //               system.  Returns a VirtualFile pointer representing
00507 //               the file if it is found, or NULL if it is not.
00508 //
00509 //               If status_only is true, the file will be checked for
00510 //               existence and length and so on, but the returned
00511 //               file's contents cannot be read.  This is an
00512 //               optimization which is especially important for
00513 //               certain mount types, for instance HTTP, for which
00514 //               opening a file to determine its status is
00515 //               substantially less expensive than opening it to read
00516 //               its contents.
00517 ////////////////////////////////////////////////////////////////////
00518 PT(VirtualFile) VirtualFileSystem::
00519 get_file(const Filename &filename, bool status_only) const {
00520   ((VirtualFileSystem *)this)->_lock.acquire();
00521   PT(VirtualFile) result = do_get_file(filename, status_only);
00522   ((VirtualFileSystem *)this)->_lock.release();
00523   return result;
00524 }
00525 
00526 ////////////////////////////////////////////////////////////////////
00527 //     Function: VirtualFileSystem::find_file
00528 //       Access: Published
00529 //  Description: Uses the indicated search path to find the file
00530 //               within the file system.  Returns the first occurrence
00531 //               of the file found, or NULL if the file cannot be
00532 //               found.
00533 ////////////////////////////////////////////////////////////////////
00534 PT(VirtualFile) VirtualFileSystem::
00535 find_file(const Filename &filename, const DSearchPath &searchpath,
00536           bool status_only) const {
00537   if (!filename.is_local()) {
00538     return get_file(filename, status_only);
00539   }
00540 
00541   int num_directories = searchpath.get_num_directories();
00542   for (int i = 0; i < num_directories; ++i) {
00543     Filename match(searchpath.get_directory(i), filename);
00544     if (searchpath.get_directory(i) == "." && 
00545         filename.is_fully_qualified()) {
00546       // A special case for the "." directory: to avoid prefixing an
00547       // endless stream of ./ in front of files, if the filename
00548       // already has a ./ prefixed (i.e. is_fully_qualified() is
00549       // true), we don't prefix another one.
00550       match = filename;
00551     }
00552     PT(VirtualFile) found_file = get_file(match, status_only);
00553     if (found_file != (VirtualFile *)NULL) {
00554       return found_file;
00555     }
00556   }
00557 
00558   return NULL;
00559 }
00560 
00561 
00562 ////////////////////////////////////////////////////////////////////
00563 //     Function: VirtualFileSystem::resolve_filename
00564 //       Access: Public
00565 //  Description: Searches the given search path for the filename.  If
00566 //               it is found, updates the filename to the full
00567 //               pathname found and returns true; otherwise, returns
00568 //               false.
00569 ////////////////////////////////////////////////////////////////////
00570 bool VirtualFileSystem::
00571 resolve_filename(Filename &filename,
00572                  const DSearchPath &searchpath,
00573                  const string &default_extension) const {
00574   PT(VirtualFile) found;
00575 
00576   if (filename.is_local()) {
00577     found = find_file(filename, searchpath, true);
00578 
00579     if (found.is_null()) {
00580       // We didn't find it with the given extension; can we try the
00581       // default extension?
00582       if (filename.get_extension().empty() && !default_extension.empty()) {
00583         Filename try_ext = filename;
00584         try_ext.set_extension(default_extension);
00585         found = find_file(try_ext, searchpath, true);
00586       }
00587     }
00588   } else {
00589     if (exists(filename)) {
00590       // The full pathname exists.  Return true.
00591       return true;
00592     } else {
00593       // The full pathname doesn't exist with the given extension;
00594       // does it exist with the default extension?
00595       if (filename.get_extension().empty() && !default_extension.empty()) {
00596         Filename try_ext = filename;
00597         try_ext.set_extension(default_extension);
00598         found = get_file(try_ext, true);
00599       }
00600     }
00601   }
00602 
00603   if (!found.is_null()) {
00604     filename = found->get_original_filename();
00605     return true;
00606   }
00607 
00608   return false;
00609 }
00610 
00611 ////////////////////////////////////////////////////////////////////
00612 //     Function: VirtualFileSystem::find_all_files
00613 //       Access: Public
00614 //  Description: Searches all the directories in the search list for
00615 //               the indicated file, in order.  Fills up the results
00616 //               list with *all* of the matching filenames found, if
00617 //               any.  Returns the number of matches found.
00618 //
00619 //               It is the responsibility of the the caller to clear
00620 //               the results list first; otherwise, the newly-found
00621 //               files will be appended to the list.
00622 ////////////////////////////////////////////////////////////////////
00623 int VirtualFileSystem::
00624 find_all_files(const Filename &filename, const DSearchPath &searchpath,
00625                DSearchPath::Results &results) const {
00626   int num_added = 0;
00627 
00628   if (filename.is_local()) {
00629     int num_directories = searchpath.get_num_directories();
00630     for (int i = 0; i < num_directories; ++i) {
00631       Filename match(searchpath.get_directory(i), filename);
00632       if (exists(match)) {
00633         if (searchpath.get_directory(i) == "." &&
00634             filename.is_fully_qualified()) {
00635           // A special case for the "." directory: to avoid prefixing
00636           // an endless stream of ./ in front of files, if the
00637           // filename already has a ./ prefixed
00638           // (i.e. is_fully_fully_qualified() is true), we don't
00639           // prefix another one.
00640           results.add_file(filename);
00641         } else {
00642           results.add_file(match);
00643         }
00644         ++num_added;
00645       }
00646     }
00647   }
00648 
00649   return num_added;
00650 }
00651 
00652 ////////////////////////////////////////////////////////////////////
00653 //     Function: VirtualFileSystem::write
00654 //       Access: Published
00655 //  Description: Print debugging information.
00656 //               (e.g. from Python or gdb prompt).
00657 ////////////////////////////////////////////////////////////////////
00658 void VirtualFileSystem::
00659 write(ostream &out) const {
00660   ((VirtualFileSystem *)this)->_lock.acquire();
00661   Mounts::const_iterator mi;
00662   for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
00663     VirtualFileMount *mount = (*mi);
00664     mount->write(out);
00665   }
00666   ((VirtualFileSystem *)this)->_lock.release();
00667 }
00668 
00669 
00670 ////////////////////////////////////////////////////////////////////
00671 //     Function: VirtualFileSystem::get_global_ptr
00672 //       Access: Published, Static
00673 //  Description: Returns the default global VirtualFileSystem.  You
00674 //               may create your own personal VirtualFileSystem
00675 //               objects and use them for whatever you like, but Panda
00676 //               will attempt to load models and stuff from this
00677 //               default object.
00678 //
00679 //               Initially, the global VirtualFileSystem is set up to
00680 //               mount the OS filesystem to root; i.e. it is
00681 //               equivalent to the OS filesystem.  This may be
00682 //               subsequently adjusted by the user.
00683 ////////////////////////////////////////////////////////////////////
00684 VirtualFileSystem *VirtualFileSystem::
00685 get_global_ptr() {
00686   if (_global_ptr == (VirtualFileSystem *)NULL) {
00687     // Make sure this is initialized.
00688     init_libexpress();
00689 
00690     _global_ptr = new VirtualFileSystem;
00691     
00692     // Set up the default mounts.  First, there is always the root
00693     // mount.
00694     _global_ptr->mount("/", "/", 0);
00695 
00696     // And our initial cwd comes from the environment.
00697     _global_ptr->chdir(ExecutionEnvironment::get_cwd());
00698 
00699     // Then, we add whatever mounts are listed in the Configrc file.
00700     ConfigVariableList mounts
00701       ("vfs-mount",
00702        PRC_DESC("vfs-mount system-filename mount-point [options]"));
00703 
00704     int num_unique_values = mounts.get_num_unique_values();
00705     for (int i = 0; i < num_unique_values; i++) {
00706       string mount_desc = mounts.get_unique_value(i);
00707 
00708       // The vfs-mount syntax is:
00709       
00710       // vfs-mount system-filename mount-point [options]
00711       
00712       // The last two spaces mark the beginning of the mount point,
00713       // and of the options, respectively.  There might be multiple
00714       // spaces in the system filename, which are part of the
00715       // filename.
00716       
00717       // The last space marks the beginning of the mount point.
00718       // Spaces before that are part of the system filename.
00719       size_t space = mount_desc.rfind(' ');
00720       if (space == string::npos) {
00721         express_cat.warning()
00722           << "No space in vfs-mount descriptor: " << mount_desc << "\n";
00723         
00724       } else {
00725         string mount_point = mount_desc.substr(space + 1);
00726         while (space > 0 && isspace(mount_desc[space - 1])) {
00727           space--;
00728         }
00729         mount_desc = mount_desc.substr(0, space);
00730         string options;
00731         
00732         space = mount_desc.rfind(' ');
00733         if (space != string::npos) {
00734           // If there's another space, we have the optional options field.
00735           options = mount_point;
00736           mount_point = mount_desc.substr(space + 1);
00737           while (space > 0 && isspace(mount_desc[space - 1])) {
00738             --space;
00739           }
00740           mount_desc = mount_desc.substr(0, space);
00741         }
00742         
00743         mount_desc = ExecutionEnvironment::expand_string(mount_desc);
00744         Filename physical_filename = Filename::from_os_specific(mount_desc);
00745         
00746         int flags = 0;
00747         string password;
00748         
00749         // Split the options up by commas.
00750         size_t p = 0;
00751         size_t q = options.find(',', p);
00752         while (q != string::npos) {
00753           parse_option(options.substr(p, q - p),
00754                        flags, password);
00755           p = q + 1;
00756           q = options.find(',', p);
00757         }
00758         parse_option(options.substr(p), flags, password);
00759         
00760         _global_ptr->mount(physical_filename, mount_point, flags, password);
00761       }
00762     }
00763   }
00764 
00765   return _global_ptr;
00766 }
00767 
00768 #ifdef HAVE_PYTHON
00769 ////////////////////////////////////////////////////////////////////
00770 //     Function: VirtualFileSystem::__py__read_file
00771 //       Access: Published
00772 //  Description: Convenience function; returns the entire contents of
00773 //               the indicated file as a string.
00774 //
00775 //               This variant on read_file() is implemented directly
00776 //               for Python, as a small optimization, to avoid the
00777 //               double-construction of a string object that would be
00778 //               otherwise required for the return value.
00779 ////////////////////////////////////////////////////////////////////
00780 PyObject *VirtualFileSystem::
00781 __py__read_file(const Filename &filename, bool auto_unwrap) const {
00782   pvector<unsigned char> pv;
00783   bool okflag = read_file(filename, pv, auto_unwrap);
00784   nassertr(okflag, NULL);
00785 
00786   if (pv.empty()) {
00787     return PyString_FromStringAndSize("", 0);
00788   } else {
00789     return PyString_FromStringAndSize((const char *)&pv[0], pv.size());
00790   }
00791 }
00792 #endif  // HAVE_PYTHON
00793 
00794 ////////////////////////////////////////////////////////////////////
00795 //     Function: VirtualFileSystem::open_read_file
00796 //       Access: Published
00797 //  Description: Convenience function; returns a newly allocated
00798 //               istream if the file exists and can be read, or NULL
00799 //               otherwise.  Does not return an invalid istream.
00800 //
00801 //               If auto_unwrap is true, an explicitly-named .pz file
00802 //               is automatically decompressed and the decompressed
00803 //               contents are returned.  This is different than
00804 //               vfs-implicit-pz, which will automatically decompress
00805 //               a file if the extension .pz is *not* given.
00806 ////////////////////////////////////////////////////////////////////
00807 istream *VirtualFileSystem::
00808 open_read_file(const Filename &filename, bool auto_unwrap) const {
00809   PT(VirtualFile) file = get_file(filename, false);
00810   if (file == (VirtualFile *)NULL) {
00811     return NULL;
00812   }
00813   istream *str = file->open_read_file(auto_unwrap);
00814   if (str != (istream *)NULL && str->fail()) {
00815     close_read_file(str);
00816     str = (istream *)NULL;
00817   }
00818   return str;
00819 }
00820 
00821 ////////////////////////////////////////////////////////////////////
00822 //     Function: VirtualFileSystem::close_read_file
00823 //       Access: Published, Static
00824 //  Description: Closes a file opened by a previous call to
00825 //               open_read_file().  This really just deletes the
00826 //               istream pointer, but it is recommended to use this
00827 //               interface instead of deleting it explicitly, to help
00828 //               work around compiler issues.
00829 ////////////////////////////////////////////////////////////////////
00830 void VirtualFileSystem::
00831 close_read_file(istream *stream) {
00832   if (stream != (istream *)NULL) {
00833     // For some reason--compiler bug in gcc 3.2?--explicitly deleting
00834     // the stream pointer does not call the appropriate global delete
00835     // function; instead apparently calling the system delete
00836     // function.  So we call the delete function by hand instead.
00837 #if (!defined(WIN32_VC) && !defined(WIN64_VC)) && !defined(USE_MEMORY_NOWRAPPERS) && defined(REDEFINE_GLOBAL_OPERATOR_NEW)
00838     stream->~istream();
00839     (*global_operator_delete)(stream);
00840 #else
00841     delete stream;
00842 #endif
00843   }
00844 }
00845 
00846 ////////////////////////////////////////////////////////////////////
00847 //     Function: VirtualFileSystem::scan_mount_points
00848 //       Access: Public
00849 //  Description: Adds to names a list of all the mount points in use
00850 //               that are one directory below path, if any.  That is,
00851 //               these are the external files or directories mounted
00852 //               directly to the indicated path.
00853 //
00854 //               The names vector is filled with a set of basenames,
00855 //               the basename part of the mount point.
00856 ////////////////////////////////////////////////////////////////////
00857 void VirtualFileSystem::
00858 scan_mount_points(vector_string &names, const Filename &path) const {
00859   nassertv(!path.empty() && !path.is_local());
00860   string prefix = path.get_fullpath().substr(1);
00861   Mounts::const_iterator mi;
00862   for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
00863     VirtualFileMount *mount = (*mi);
00864     
00865     string mount_point = mount->get_mount_point();
00866     if (prefix.empty()) {
00867       // The indicated path is the root.  Is the mount point on the
00868       // root?
00869       if (mount_point.find('/') == string::npos) {
00870         // No embedded slashes, so the mount point is only one
00871         // directory below the root.
00872         names.push_back(mount_point);
00873       }
00874     } else {
00875       if (mount_point.substr(0, prefix.length()) == prefix &&
00876           mount_point.length() > prefix.length() &&
00877           mount_point[prefix.length()] == '/') {
00878         // This mount point is below the indicated path.  Is it only one
00879         // directory below?
00880         string basename = mount_point.substr(prefix.length());
00881         if (basename.find('/') == string::npos) {
00882           // No embedded slashes, so it's only one directory below.
00883           names.push_back(basename);
00884         }
00885       }
00886     }
00887   }
00888 }
00889 
00890 ////////////////////////////////////////////////////////////////////
00891 //     Function: VirtualFileSystem::parse_option
00892 //       Access: Public, Static
00893 //  Description: Parses one of the option flags in the options list on
00894 //               the vfs-mount Config.prc line.
00895 ////////////////////////////////////////////////////////////////////
00896 void VirtualFileSystem::
00897 parse_option(const string &option, int &flags, string &password) {
00898   if (option == "0" || option.empty()) {
00899     // 0 is the null option.
00900   } else if (option == "ro") {
00901     flags |= MF_read_only;
00902   } else if (option.substr(0, 3) == "pw:") {
00903     password = option.substr(3);
00904   } else {
00905     express_cat.warning()
00906       << "Invalid option on vfs-mount: \"" << option << "\"\n";
00907   }
00908 }
00909 
00910 ////////////////////////////////////////////////////////////////////
00911 //     Function: VirtualFileSystem::normalize_mount_point
00912 //       Access: Private
00913 //  Description: Converts the mount point string supplied by the user
00914 //               to standard form (relative to the current directory,
00915 //               with no double slashes, and not terminating with a
00916 //               slash).  The initial slash is removed.
00917 //
00918 //               Assumes the lock is already held.
00919 ////////////////////////////////////////////////////////////////////
00920 Filename VirtualFileSystem::
00921 normalize_mount_point(const Filename &mount_point) const {
00922   Filename nmp = mount_point;
00923   if (nmp.is_local()) {
00924     nmp = Filename(_cwd, mount_point);
00925   }
00926   nmp.standardize();
00927   nassertr(!nmp.empty() && nmp[0] == '/', nmp);
00928   return nmp.get_fullpath().substr(1);
00929 }
00930 
00931 ////////////////////////////////////////////////////////////////////
00932 //     Function: VirtualFileSystem::do_mount
00933 //       Access: Private
00934 //  Description: The private implementation of mount().  Assumes the
00935 //               lock is already held.
00936 ////////////////////////////////////////////////////////////////////
00937 bool VirtualFileSystem::
00938 do_mount(VirtualFileMount *mount, const Filename &mount_point, int flags) {
00939   nassertr(mount->_file_system == NULL, false);
00940   mount->_file_system = this;
00941   mount->_mount_point = normalize_mount_point(mount_point);
00942   mount->_mount_flags = flags;
00943   _mounts.push_back(mount);
00944   ++_mount_seq;
00945   return true;
00946 }
00947 
00948 ////////////////////////////////////////////////////////////////////
00949 //     Function: VirtualFileSystem::do_get_file
00950 //       Access: Private
00951 //  Description: The private implementation of get_file().  Assumes
00952 //               the lock is already held.
00953 ////////////////////////////////////////////////////////////////////
00954 PT(VirtualFile) VirtualFileSystem::
00955 do_get_file(const Filename &filename, bool status_only) const {
00956   nassertr(!filename.empty(), NULL);
00957   Filename pathname(filename);
00958   if (pathname.is_local()) {
00959     pathname = Filename(_cwd, filename);
00960     if (filename.is_text()) {
00961       pathname.set_text();
00962     }
00963   }
00964   pathname.standardize();
00965   Filename strpath = pathname.get_filename_index(0).get_fullpath().substr(1);
00966   // Also transparently look for a regular file suffixed .pz.
00967   Filename strpath_pz = strpath + ".pz";
00968 
00969   // Now scan all the mount points, from the back (since later mounts
00970   // override more recent ones), until a match is found.
00971   PT(VirtualFile) found_file = NULL;
00972   VirtualFileComposite *composite_file = NULL;
00973 
00974   // We use an index instead of an iterator, since the vector might
00975   // change if implicit mounts are added during this loop.
00976   unsigned int start_seq = _mount_seq;
00977 
00978   size_t i = _mounts.size();
00979   while (i > 0) {
00980     --i;
00981     VirtualFileMount *mount = _mounts[i];
00982     Filename mount_point = mount->get_mount_point();
00983     if (strpath == mount_point) {
00984       // Here's an exact match on the mount point.  This filename is
00985       // the root directory of this mount object.
00986       if (consider_match(found_file, composite_file, mount, "", pathname,
00987                          false, status_only)) {
00988         return found_file;
00989       }
00990     } else if (mount_point.empty()) {
00991       // This is the root mount point; all files are in here.
00992       if (consider_match(found_file, composite_file, mount, strpath, 
00993                          pathname, false, status_only)) {
00994         return found_file;
00995       }
00996 #ifdef HAVE_ZLIB
00997       if (vfs_implicit_pz) {
00998         if (consider_match(found_file, composite_file, mount, strpath_pz, 
00999                            pathname, true, status_only)) {
01000           return found_file;
01001         }
01002       }
01003 #endif  // HAVE_ZLIB
01004 
01005     } else if (strpath.length() > mount_point.length() &&
01006                mount_point == strpath.substr(0, mount_point.length()) &&
01007                strpath[mount_point.length()] == '/') {
01008       // This pathname falls within this mount system.
01009       Filename local_filename = strpath.substr(mount_point.length() + 1);
01010       Filename local_filename_pz = strpath_pz.substr(mount_point.length() + 1);
01011       if (consider_match(found_file, composite_file, mount, local_filename, 
01012                          pathname, false, status_only)) {
01013         return found_file;
01014       }
01015 #ifdef HAVE_ZLIB
01016       if (vfs_implicit_pz) {
01017         // Bingo!
01018         if (consider_match(found_file, composite_file, mount, local_filename_pz,
01019                            pathname, true, status_only)) {
01020           return found_file;
01021         }
01022       }
01023 #endif  // HAVE_ZLIB
01024     }
01025 
01026     // If we discover that a file has been implicitly mounted during
01027     // one of the above operations, start over from the beginning of
01028     // the loop.
01029     if (start_seq != _mount_seq) {
01030       start_seq = _mount_seq;
01031       i = _mounts.size();
01032     }
01033   }
01034 
01035   if (found_file == (VirtualFile *)NULL && vfs_implicit_mf) {
01036     // The file wasn't found, as-is.  Does it appear to be an implicit
01037     // .mf file reference?
01038     ((VirtualFileSystem *)this)->consider_mount_mf(filename);
01039 
01040     if (start_seq != _mount_seq) {
01041       // Yes, it was, or some nested file was.  Now that we've
01042       // implicitly mounted the .mf file, go back and look again.
01043       return do_get_file(filename, status_only);
01044     }
01045   }
01046 
01047   return found_file;
01048 }
01049 
01050 ////////////////////////////////////////////////////////////////////
01051 //     Function: VirtualFileSystem::consider_match
01052 //       Access: Private
01053 //  Description: Evaluates one possible filename match found during a
01054 //               get_file() operation.  There may be multiple matches
01055 //               for a particular filename due to the ambiguities
01056 //               introduced by allowing multiple mount points, so we
01057 //               may have to keep searching even after the first match
01058 //               is found.
01059 //
01060 //               Returns true if the search should terminate now, or
01061 //               false if it should keep iterating.
01062 ////////////////////////////////////////////////////////////////////
01063 bool VirtualFileSystem::
01064 consider_match(PT(VirtualFile) &found_file, VirtualFileComposite *&composite_file,
01065                VirtualFileMount *mount, const Filename &local_filename,
01066                const Filename &original_filename, bool implicit_pz_file,
01067                bool status_only) const {
01068   PT(VirtualFile) vfile = 
01069     mount->make_virtual_file(local_filename, original_filename, false, status_only);
01070   if (!vfile->has_file()) {
01071     // Keep looking.
01072     return false;
01073   }
01074 
01075   if (found_file == (VirtualFile *)NULL) {
01076     // This was our first match.  Save it.
01077     found_file = vfile;
01078     if (!found_file->is_directory()) {
01079       // If it's not a directory, we're done.
01080       return true;
01081     }
01082     // It is a directory, so save it for later.
01083     if (implicit_pz_file) {
01084       // Don't look for directories named file.pz.
01085       found_file = NULL;
01086     }
01087 
01088   } else {
01089     // This was our second match.  The previous match(es) must
01090     // have been directories.
01091     if (!vfile->is_directory()) {
01092       // However, this one isn't a directory.  We're done.
01093       return true;
01094     }
01095 
01096     if (!implicit_pz_file) {
01097       // At least two directories matched to the same path.  We
01098       // need a composite directory.
01099       if (composite_file == (VirtualFileComposite *)NULL) {
01100         composite_file =
01101           new VirtualFileComposite((VirtualFileSystem *)this, found_file->get_original_filename());
01102         composite_file->set_original_filename(original_filename);
01103         composite_file->add_component(found_file);
01104         found_file = composite_file;
01105       }
01106 
01107       composite_file->add_component(vfile);
01108     }
01109   }
01110 
01111   // Keep going, looking for more directories.
01112   return false;
01113 }
01114 
01115 ////////////////////////////////////////////////////////////////////
01116 //     Function: VirtualFileSystem::consider_mount_mf
01117 //       Access: Private
01118 //  Description: The indicated filename was not found.  Check to see
01119 //               if it is using an implicit reference to a .mf file as
01120 //               a directory, that hasn't already been mounted.  If it
01121 //               is, mount the .mf file in-place, and return true; if
01122 //               it is not, or if its .mf file is already mounted
01123 //               in-place, return false.
01124 //
01125 //               Assumes the lock is already held.
01126 ////////////////////////////////////////////////////////////////////
01127 bool VirtualFileSystem::
01128 consider_mount_mf(const Filename &filename) {
01129   Filename dirname = filename.get_dirname();
01130   if (dirname.empty() || dirname == filename) {
01131     // Reached the top directory; no .mf file references.
01132     return false;
01133   }
01134   if (is_directory(dirname)) {
01135     // Reached a real (or already-mounted) directory; no unmounted .mf
01136     // file references.
01137     return false;
01138   }
01139   if (dirname.get_extension() == "mf") {
01140     // Hey, here's a multifile reference!
01141     dirname.set_binary();
01142     PT(VirtualFile) file = do_get_file(dirname, false);
01143     if (file == (VirtualFile *)NULL || !file->is_regular_file()) {
01144       // Oh, never mind.  Not a real file.
01145       return false;
01146     }
01147 
01148     PT(Multifile) multifile = new Multifile;
01149    
01150     istream *stream = file->open_read_file(false);
01151     if (stream == (istream *)NULL) {
01152       // Couldn't read file.
01153       return false;
01154     }
01155 
01156     // Wrap a thread-safe wrapper around that stream, so multiple
01157     // threads can safely read the multifile simultaneously.
01158     IStreamWrapper *streamw = new IStreamWrapper(stream, true);
01159 
01160     if (!multifile->open_read(streamw, true)) {
01161       // Invalid multifile.
01162       return false;
01163     }
01164 
01165     multifile->set_multifile_name(dirname.get_basename());
01166     express_cat->info()
01167       << "Implicitly mounting " << dirname << "\n";
01168 
01169     PT(VirtualFileMountMultifile) new_mount = 
01170       new VirtualFileMountMultifile(multifile);
01171     return do_mount(new_mount, dirname, MF_read_only);
01172   }
01173 
01174   // Recurse.
01175   return consider_mount_mf(dirname);
01176 }
 All Classes Functions Variables Enumerations