Panda3D
virtualFileSystem.cxx
1 // Filename: virtualFileSystem.cxx
2 // Created by: drose (03Aug02)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "virtualFileSystem.h"
16 #include "virtualFileSimple.h"
17 #include "virtualFileComposite.h"
18 #include "virtualFileMount.h"
19 #include "virtualFileMountMultifile.h"
20 #include "virtualFileMountRamdisk.h"
21 #include "virtualFileMountSystem.h"
22 #include "streamWrapper.h"
23 #include "dSearchPath.h"
24 #include "dcast.h"
25 #include "config_express.h"
26 #include "executionEnvironment.h"
27 #include "pset.h"
28 
29 VirtualFileSystem *VirtualFileSystem::_global_ptr = NULL;
30 
31 
32 ////////////////////////////////////////////////////////////////////
33 // Function: VirtualFileSystem::Constructor
34 // Access: Published
35 // Description:
36 ////////////////////////////////////////////////////////////////////
37 VirtualFileSystem::
38 VirtualFileSystem() :
39  vfs_case_sensitive
40 ("vfs-case-sensitive",
41 #ifdef NDEBUG
42  false, // The default for a production build is not case-sensitive;
43  // this avoids runtime overhead to verify case sensitivity.
44 #else
45  true,
46 #endif
47  PRC_DESC("Set this true to make the VirtualFileSystem present the native "
48  "OS-provided filesystem as if it were a case-sensitive file "
49  "system, even if it is not (e.g. on Windows). This variable "
50  "has no effect if the native filesystem is already case-sensitive, "
51  "and it has no effect on mounted multifile systems, which are "
52  "always case-sensitive.")),
53  vfs_implicit_pz
54  ("vfs-implicit-pz", true,
55  PRC_DESC("When this is true, the VirtualFileSystem will pretend a named "
56  "file exists even if it doesn't, as long as a filename with the "
57  "same name and the additional extension .pz does exist. In this "
58  "case, the VirtualFileSystem will implicitly open the .pz file "
59  "and decompress it on-the-fly.")),
60  vfs_implicit_mf
61  ("vfs-implicit-mf", false,
62  PRC_DESC("When this is true, the VirtualFileSystem will automatically "
63  "mount multifiles on-the-fly when they are used as directories. "
64  "For instance, opening the file /c/files/foo.mf/dirname/mytex.jpg "
65  "will implicitly retrieve a file named 'dirname/mytex.jpg' "
66  "within the multifile /c/files/foo.mf, even if the multifile "
67  "has not already been mounted. This makes all of your multifiles "
68  "act like directories."))
69 {
70  _cwd = "/";
71  _mount_seq = 0;
72 }
73 
74 ////////////////////////////////////////////////////////////////////
75 // Function: VirtualFileSystem::Destructor
76 // Access: Published
77 // Description:
78 ////////////////////////////////////////////////////////////////////
79 VirtualFileSystem::
80 ~VirtualFileSystem() {
81  unmount_all();
82 }
83 
84 ////////////////////////////////////////////////////////////////////
85 // Function: VirtualFileSystem::mount
86 // Access: Published
87 // Description: Mounts the indicated Multifile at the given mount
88 // point.
89 ////////////////////////////////////////////////////////////////////
91 mount(Multifile *multifile, const Filename &mount_point, int flags) {
92  PT(VirtualFileMountMultifile) new_mount =
93  new VirtualFileMountMultifile(multifile);
94  return mount(new_mount, mount_point, flags);
95 }
96 
97 ////////////////////////////////////////////////////////////////////
98 // Function: VirtualFileSystem::mount
99 // Access: Published
100 // Description: Mounts the indicated system file or directory at the
101 // given mount point. If the named file is a directory,
102 // mounts the directory. If the named file is a
103 // Multifile, mounts it as a Multifile. Returns true on
104 // success, false on failure.
105 //
106 // A given system directory may be mounted to multiple
107 // different mount point, and the same mount point may
108 // share multiple system directories. In the case of
109 // ambiguities (that is, two different files with
110 // exactly the same full pathname), the most-recently
111 // mounted system wins.
112 //
113 // The filename specified as the first parameter must
114 // refer to a real, physical filename on disk; it cannot
115 // be a virtual file already appearing within the vfs
116 // filespace. However, it is possible to mount such a
117 // file; see mount_loop() for this.
118 ////
119 // Note that a mounted VirtualFileSystem directory is
120 // fully case-sensitive, unlike the native Windows file
121 // system, so you must refer to files within the virtual
122 // file system with exactly the right case.
123 ////////////////////////////////////////////////////////////////////
125 mount(const Filename &physical_filename, const Filename &mount_point,
126  int flags, const string &password) {
127  if (!physical_filename.exists()) {
128  express_cat->warning()
129  << "Attempt to mount " << physical_filename << ", not found.\n";
130  return false;
131  }
132 
133  if (physical_filename.is_directory()) {
134  PT(VirtualFileMountSystem) new_mount =
135  new VirtualFileMountSystem(physical_filename);
136  return mount(new_mount, mount_point, flags);
137  } else {
138  // It's not a directory; it must be a Multifile.
139  PT(Multifile) multifile = new Multifile;
140  multifile->set_encryption_password(password);
141 
142  // For now these are always opened read only. Maybe later we'll
143  // support read-write on Multifiles.
144  flags |= MF_read_only;
145  if (!multifile->open_read(physical_filename)) {
146  return false;
147  }
148 
149  return mount(multifile, mount_point, flags);
150  }
151 }
152 
153 ////////////////////////////////////////////////////////////////////
154 // Function: VirtualFileSystem::mount_loop
155 // Access: Published
156 // Description: This is similar to mount(), but it receives the name
157 // of a Multifile that already appears within the
158 // virtual file system. It can be used to mount a
159 // Multifile that is itself hosted within a
160 // virtually-mounted Multifile.
161 //
162 // This interface can also be used to mount physical
163 // files (that appear within the virtual filespace), but
164 // it cannot be used to mount directories. Use mount()
165 // if you need to mount a directory.
166 //
167 // Note that there is additional overhead, in the form
168 // of additional buffer copies of the data, for
169 // recursively mounting a multifile like this.
170 ////////////////////////////////////////////////////////////////////
172 mount_loop(const Filename &virtual_filename, const Filename &mount_point,
173  int flags, const string &password) {
174  PT(VirtualFile) file = get_file(virtual_filename, false);
175  if (file == NULL) {
176  express_cat->warning()
177  << "Attempt to mount " << virtual_filename << ", not found.\n";
178  return false;
179  }
180 
181  if (file->is_directory()) {
182  PT(VirtualFileMountSystem) new_mount =
183  new VirtualFileMountSystem(virtual_filename);
184  return mount(new_mount, mount_point, flags);
185 
186  } else {
187  // It's not a directory; it must be a Multifile.
188  PT(Multifile) multifile = new Multifile;
189  multifile->set_encryption_password(password);
190 
191  // For now these are always opened read only. Maybe later we'll
192  // support read-write on Multifiles.
193  flags |= MF_read_only;
194  if (!multifile->open_read(virtual_filename)) {
195  return false;
196  }
197 
198  return mount(multifile, mount_point, flags);
199  }
200 }
201 
202 ////////////////////////////////////////////////////////////////////
203 // Function: VirtualFileSystem::mount
204 // Access: Published
205 // Description: Adds the given VirtualFileMount object to the mount
206 // list. This is a lower-level function that the other
207 // flavors of mount(); it requires you to create a
208 // VirtualFileMount object specifically.
209 ////////////////////////////////////////////////////////////////////
211 mount(VirtualFileMount *mount, const Filename &mount_point, int flags) {
212  if (express_cat->is_debug()) {
213  express_cat->debug()
214  << "mount " << *mount << " under " << mount_point << "\n";
215  }
216 
217  _lock.acquire();
218  bool result = do_mount(mount, mount_point, flags);
219  _lock.release();
220  return result;
221 }
222 
223 ////////////////////////////////////////////////////////////////////
224 // Function: VirtualFileSystem::unmount
225 // Access: Published
226 // Description: Unmounts all appearances of the indicated Multifile
227 // from the file system. Returns the number of
228 // appearances unmounted.
229 ////////////////////////////////////////////////////////////////////
231 unmount(Multifile *multifile) {
232  _lock.acquire();
233  Mounts::iterator ri, wi;
234  wi = ri = _mounts.begin();
235  while (ri != _mounts.end()) {
236  VirtualFileMount *mount = (*ri);
237  (*wi) = mount;
238 
239  if (mount->is_exact_type(VirtualFileMountMultifile::get_class_type())) {
240  VirtualFileMountMultifile *mmount =
241  DCAST(VirtualFileMountMultifile, mount);
242  if (mmount->get_multifile() == multifile) {
243  // Remove this one. Don't increment wi.
244  if (express_cat->is_debug()) {
245  express_cat->debug()
246  << "unmount " << *mount << " from " << mount->get_mount_point() << "\n";
247  }
248  mount->_file_system = NULL;
249 
250  } else {
251  // Don't remove this one.
252  ++wi;
253  }
254  } else {
255  // Don't remove this one.
256  ++wi;
257  }
258  ++ri;
259  }
260 
261  int num_removed = _mounts.end() - wi;
262  _mounts.erase(wi, _mounts.end());
263  ++_mount_seq;
264  _lock.release();
265  return num_removed;
266 }
267 
268 ////////////////////////////////////////////////////////////////////
269 // Function: VirtualFileSystem::unmount
270 // Access: Published
271 // Description: Unmounts all appearances of the indicated directory
272 // name or multifile name from the file system. Returns
273 // the number of appearances unmounted.
274 ////////////////////////////////////////////////////////////////////
276 unmount(const Filename &physical_filename) {
277  _lock.acquire();
278  Mounts::iterator ri, wi;
279  wi = ri = _mounts.begin();
280  while (ri != _mounts.end()) {
281  VirtualFileMount *mount = (*ri);
282  (*wi) = mount;
283 
284  if (mount->is_exact_type(VirtualFileMountSystem::get_class_type())) {
285  VirtualFileMountSystem *smount =
286  DCAST(VirtualFileMountSystem, mount);
287  if (smount->get_physical_filename() == physical_filename) {
288  // Remove this one. Don't increment wi.
289  if (express_cat->is_debug()) {
290  express_cat->debug()
291  << "unmount " << *mount << " from " << mount->get_mount_point() << "\n";
292  }
293  mount->_file_system = NULL;
294 
295  } else {
296  // Don't remove this one.
297  ++wi;
298  }
299 
300  } else if (mount->is_exact_type(VirtualFileMountMultifile::get_class_type())) {
301  VirtualFileMountMultifile *mmount =
302  DCAST(VirtualFileMountMultifile, mount);
303  if (mmount->get_multifile()->get_multifile_name() == physical_filename) {
304  // Remove this one. Don't increment wi.
305  if (express_cat->is_debug()) {
306  express_cat->debug()
307  << "unmount " << *mount << " from " << mount->get_mount_point() << "\n";
308  }
309  mount->_file_system = NULL;
310 
311  } else {
312  // Don't remove this one.
313  ++wi;
314  }
315 
316  } else {
317  // Don't remove this one.
318  ++wi;
319  }
320  ++ri;
321  }
322 
323  int num_removed = _mounts.end() - wi;
324  _mounts.erase(wi, _mounts.end());
325  ++_mount_seq;
326  _lock.release();
327  return num_removed;
328 }
329 
330 ////////////////////////////////////////////////////////////////////
331 // Function: VirtualFileSystem::unmount
332 // Access: Published
333 // Description: Unmounts the indicated VirtualFileMount object
334 // from the file system. Returns the number of
335 // appearances unmounted.
336 ////////////////////////////////////////////////////////////////////
339  _lock.acquire();
340  Mounts::iterator ri, wi;
341  wi = ri = _mounts.begin();
342  while (ri != _mounts.end()) {
343  (*wi) = (*ri);
344  if ((*ri) == mount) {
345  // Remove this one. Don't increment wi.
346  if (express_cat->is_debug()) {
347  express_cat->debug()
348  << "unmount " << *mount << " from " << mount->get_mount_point() << "\n";
349  }
350  (*ri)->_file_system = NULL;
351 
352  } else {
353  // Don't remove this one.
354  ++wi;
355  }
356  ++ri;
357  }
358 
359  int num_removed = _mounts.end() - wi;
360  _mounts.erase(wi, _mounts.end());
361  ++_mount_seq;
362  _lock.release();
363  return num_removed;
364 }
365 
366 ////////////////////////////////////////////////////////////////////
367 // Function: VirtualFileSystem::unmount_point
368 // Access: Published
369 // Description: Unmounts all systems attached to the given mount
370 // point from the file system. Returns the number of
371 // appearances unmounted.
372 ////////////////////////////////////////////////////////////////////
374 unmount_point(const Filename &mount_point) {
375  _lock.acquire();
376  Filename nmp = normalize_mount_point(mount_point);
377  Mounts::iterator ri, wi;
378  wi = ri = _mounts.begin();
379  while (ri != _mounts.end()) {
380  VirtualFileMount *mount = (*ri);
381  (*wi) = mount;
382 
383  if (mount->get_mount_point() == nmp) {
384  // Remove this one. Don't increment wi.
385  if (express_cat->is_debug()) {
386  express_cat->debug()
387  << "unmount " << *mount << " from " << mount->get_mount_point() << "\n";
388  }
389  mount->_file_system = NULL;
390 
391  } else {
392  // Don't remove this one.
393  ++wi;
394  }
395  ++ri;
396  }
397 
398  int num_removed = _mounts.end() - wi;
399  _mounts.erase(wi, _mounts.end());
400  ++_mount_seq;
401  _lock.release();
402  return num_removed;
403 }
404 
405 ////////////////////////////////////////////////////////////////////
406 // Function: VirtualFileSystem::unmount_all
407 // Access: Published
408 // Description: Unmounts all files from the file system. Returns the
409 // number of systems unmounted.
410 ////////////////////////////////////////////////////////////////////
413  _lock.acquire();
414  Mounts::iterator ri;
415  for (ri = _mounts.begin(); ri != _mounts.end(); ++ri) {
416  VirtualFileMount *mount = (*ri);
417  if (express_cat->is_debug()) {
418  express_cat->debug()
419  << "unmount " << *mount << " from " << mount->get_mount_point() << "\n";
420  }
421  mount->_file_system = NULL;
422  }
423 
424  int num_removed = _mounts.size();
425  _mounts.clear();
426  ++_mount_seq;
427  _lock.release();
428  return num_removed;
429 }
430 
431 ////////////////////////////////////////////////////////////////////
432 // Function: VirtualFileSystem::get_num_mounts
433 // Access: Published
434 // Description: Returns the number of individual mounts in the
435 // system.
436 ////////////////////////////////////////////////////////////////////
438 get_num_mounts() const {
439  ((VirtualFileSystem *)this)->_lock.acquire();
440  int result = _mounts.size();
441  ((VirtualFileSystem *)this)->_lock.release();
442  return result;
443 }
444 
445 ////////////////////////////////////////////////////////////////////
446 // Function: VirtualFileSystem::get_mount
447 // Access: Published
448 // Description: Returns the nth mount in the system.
449 ////////////////////////////////////////////////////////////////////
451 get_mount(int n) const {
452  ((VirtualFileSystem *)this)->_lock.acquire();
453  nassertd(n >= 0 && n < (int)_mounts.size()) {
454  ((VirtualFileSystem *)this)->_lock.release();
455  return NULL;
456  }
457  PT(VirtualFileMount) result = _mounts[n];
458  ((VirtualFileSystem *)this)->_lock.release();
459  return result;
460 }
461 
462 ////////////////////////////////////////////////////////////////////
463 // Function: VirtualFileSystem::chdir
464 // Access: Published
465 // Description: Changes the current directory. This is used to
466 // resolve relative pathnames in get_file() and/or
467 // find_file(). Returns true if successful, false
468 // otherwise.
469 ////////////////////////////////////////////////////////////////////
471 chdir(const Filename &new_directory) {
472  _lock.acquire();
473  if (new_directory == "/") {
474  // We can always return to the root.
475  _cwd = new_directory;
476  _lock.release();
477  return true;
478  }
479 
480  PT(VirtualFile) file = do_get_file(new_directory, OF_status_only);
481  if (file != (VirtualFile *)NULL && file->is_directory()) {
482  _cwd = file->get_filename();
483  _lock.release();
484  return true;
485  }
486  _lock.release();
487  return false;
488 }
489 
490 ////////////////////////////////////////////////////////////////////
491 // Function: VirtualFileSystem::get_cwd
492 // Access: Published
493 // Description: Returns the current directory name. See chdir().
494 ////////////////////////////////////////////////////////////////////
496 get_cwd() const {
497  ((VirtualFileSystem *)this)->_lock.acquire();
498  Filename result = _cwd;
499  ((VirtualFileSystem *)this)->_lock.release();
500  return result;
501 }
502 
503 ////////////////////////////////////////////////////////////////////
504 // Function: VirtualFileSystem::make_directory
505 // Access: Published
506 // Description: Attempts to create a directory within the file
507 // system. Returns true on success, false on failure
508 // (for instance, because the parent directory does not
509 // exist, or is read-only). If the directory already
510 // existed prior to this call, returns true.
511 ////////////////////////////////////////////////////////////////////
513 make_directory(const Filename &filename) {
514  _lock.acquire();
515  PT(VirtualFile) result = do_get_file(filename, OF_make_directory);
516  _lock.release();
517  nassertr_always(result != NULL, false);
518  return result->is_directory();
519 }
520 
521 ////////////////////////////////////////////////////////////////////
522 // Function: VirtualFileSystem::make_directory_full
523 // Access: Published
524 // Description: Attempts to create a directory within the file
525 // system. Will also create any intervening directories
526 // needed. Returns true on success, false on failure.
527 ////////////////////////////////////////////////////////////////////
529 make_directory_full(const Filename &filename) {
530  _lock.acquire();
531 
532  // First, make sure everything up to the last path is known. We
533  // don't care too much if any of these fail; maybe they failed
534  // because the directory was already there.
535  string dirname = filename;
536  size_t slash = dirname.find('/', 1);
537  while (slash != string::npos) {
538  Filename component(dirname.substr(0, slash));
539  do_get_file(component, OF_make_directory);
540  slash = dirname.find('/', slash + 1);
541  }
542 
543  // Now make the last one, and check the return value.
544  PT(VirtualFile) result = do_get_file(filename, OF_make_directory);
545  _lock.release();
546  nassertr_always(result != NULL, false);
547  return result->is_directory();
548 }
549 
550 ////////////////////////////////////////////////////////////////////
551 // Function: VirtualFileSystem::get_file
552 // Access: Published
553 // Description: Looks up the file by the indicated name in the file
554 // system. Returns a VirtualFile pointer representing
555 // the file if it is found, or NULL if it is not.
556 //
557 // If status_only is true, the file will be checked for
558 // existence and length and so on, but the returned
559 // file's contents cannot be read. This is an
560 // optimization which is especially important for
561 // certain mount types, for instance HTTP, for which
562 // opening a file to determine its status is
563 // substantially less expensive than opening it to read
564 // its contents.
565 ////////////////////////////////////////////////////////////////////
567 get_file(const Filename &filename, bool status_only) const {
568  int open_flags = status_only ? OF_status_only : 0;
569  ((VirtualFileSystem *)this)->_lock.acquire();
570  PT(VirtualFile) result = do_get_file(filename, open_flags);
571  ((VirtualFileSystem *)this)->_lock.release();
572  return result;
573 }
574 
575 ////////////////////////////////////////////////////////////////////
576 // Function: VirtualFileSystem::create_file
577 // Access: Published
578 // Description: Attempts to create a file by the indicated name in
579 // the filesystem, if possible, and returns it. If a
580 // file by this name already exists, returns the same
581 // thing as get_file(). If the filename is located
582 // within a read-only directory, or the directory
583 // doesn't exist, returns NULL.
584 ////////////////////////////////////////////////////////////////////
586 create_file(const Filename &filename) {
587  ((VirtualFileSystem *)this)->_lock.acquire();
588  PT(VirtualFile) result = do_get_file(filename, OF_create_file);
589  ((VirtualFileSystem *)this)->_lock.release();
590  return result;
591 }
592 
593 ////////////////////////////////////////////////////////////////////
594 // Function: VirtualFileSystem::find_file
595 // Access: Published
596 // Description: Uses the indicated search path to find the file
597 // within the file system. Returns the first occurrence
598 // of the file found, or NULL if the file cannot be
599 // found.
600 ////////////////////////////////////////////////////////////////////
602 find_file(const Filename &filename, const DSearchPath &searchpath,
603  bool status_only) const {
604  if (!filename.is_local()) {
605  return get_file(filename, status_only);
606  }
607 
608  int num_directories = searchpath.get_num_directories();
609  for (int i = 0; i < num_directories; ++i) {
610  Filename match(searchpath.get_directory(i), filename);
611  if (searchpath.get_directory(i) == "." &&
612  filename.is_fully_qualified()) {
613  // A special case for the "." directory: to avoid prefixing an
614  // endless stream of ./ in front of files, if the filename
615  // already has a ./ prefixed (i.e. is_fully_qualified() is
616  // true), we don't prefix another one.
617  match = filename;
618  }
619  PT(VirtualFile) found_file = get_file(match, status_only);
620  if (found_file != (VirtualFile *)NULL) {
621  return found_file;
622  }
623  }
624 
625  return NULL;
626 }
627 
628 ////////////////////////////////////////////////////////////////////
629 // Function: VirtualFileSystem::delete_file
630 // Access: Public
631 // Description: Attempts to delete the indicated file or directory.
632 // This can remove a single file or an empty directory.
633 // It will not remove a nonempty directory. Returns
634 // true on success, false on failure.
635 ////////////////////////////////////////////////////////////////////
637 delete_file(const Filename &filename) {
638  PT(VirtualFile) file = get_file(filename, true);
639  if (file == (VirtualFile *)NULL) {
640  return false;
641  }
642 
643  return file->delete_file();
644 }
645 
646 ////////////////////////////////////////////////////////////////////
647 // Function: VirtualFileSystem::rename_file
648 // Access: Public
649 // Description: Attempts to move or rename the indicated file or
650 // directory. If the original file is an ordinary file,
651 // it will quietly replace any already-existing file in
652 // the new filename (but not a directory). If the
653 // original file is a directory, the new filename must
654 // not already exist.
655 //
656 // If the file is a directory, the new filename must be
657 // within the same mount point. If the file is an
658 // ordinary file, the new filename may be anywhere; but
659 // if it is not within the same mount point then the
660 // rename operation is automatically performed as a
661 // two-step copy-and-delete operation.
662 ////////////////////////////////////////////////////////////////////
664 rename_file(const Filename &orig_filename, const Filename &new_filename) {
665  _lock.acquire();
666  PT(VirtualFile) orig_file = do_get_file(orig_filename, OF_status_only);
667  if (orig_file == (VirtualFile *)NULL) {
668  _lock.release();
669  return false;
670  }
671 
672  PT(VirtualFile) new_file = do_get_file(new_filename, OF_status_only | OF_allow_nonexist);
673  if (new_file == (VirtualFile *)NULL) {
674  _lock.release();
675  return false;
676  }
677 
678  _lock.release();
679 
680  return orig_file->rename_file(new_file);
681 }
682 
683 ////////////////////////////////////////////////////////////////////
684 // Function: VirtualFileSystem::copy_file
685 // Access: Public
686 // Description: Attempts to copy the contents of the indicated file
687 // to the indicated file. Returns true on success,
688 // false on failure.
689 ////////////////////////////////////////////////////////////////////
691 copy_file(const Filename &orig_filename, const Filename &new_filename) {
692  PT(VirtualFile) orig_file = get_file(orig_filename, true);
693  if (orig_file == (VirtualFile *)NULL) {
694  return false;
695  }
696 
697  PT(VirtualFile) new_file = create_file(new_filename);
698  if (new_file == (VirtualFile *)NULL) {
699  return false;
700  }
701 
702  return orig_file->copy_file(new_file);
703 }
704 
705 ////////////////////////////////////////////////////////////////////
706 // Function: VirtualFileSystem::resolve_filename
707 // Access: Public
708 // Description: Searches the given search path for the filename. If
709 // it is found, updates the filename to the full
710 // pathname found and returns true; otherwise, returns
711 // false.
712 ////////////////////////////////////////////////////////////////////
715  const DSearchPath &searchpath,
716  const string &default_extension) const {
717  PT(VirtualFile) found;
718 
719  if (filename.is_local()) {
720  found = find_file(filename, searchpath, true);
721 
722  if (found.is_null()) {
723  // We didn't find it with the given extension; can we try the
724  // default extension?
725  if (filename.get_extension().empty() && !default_extension.empty()) {
726  Filename try_ext = filename;
727  try_ext.set_extension(default_extension);
728  found = find_file(try_ext, searchpath, true);
729  }
730  }
731  } else {
732  if (exists(filename)) {
733  // The full pathname exists. Return true.
734  return true;
735  } else {
736  // The full pathname doesn't exist with the given extension;
737  // does it exist with the default extension?
738  if (filename.get_extension().empty() && !default_extension.empty()) {
739  Filename try_ext = filename;
740  try_ext.set_extension(default_extension);
741  found = get_file(try_ext, true);
742  }
743  }
744  }
745 
746  if (!found.is_null()) {
747  filename = found->get_original_filename();
748  return true;
749  }
750 
751  return false;
752 }
753 
754 ////////////////////////////////////////////////////////////////////
755 // Function: VirtualFileSystem::find_all_files
756 // Access: Public
757 // Description: Searches all the directories in the search list for
758 // the indicated file, in order. Fills up the results
759 // list with *all* of the matching filenames found, if
760 // any. Returns the number of matches found.
761 //
762 // It is the responsibility of the the caller to clear
763 // the results list first; otherwise, the newly-found
764 // files will be appended to the list.
765 ////////////////////////////////////////////////////////////////////
767 find_all_files(const Filename &filename, const DSearchPath &searchpath,
768  DSearchPath::Results &results) const {
769  int num_added = 0;
770 
771  if (filename.is_local()) {
772  int num_directories = searchpath.get_num_directories();
773  for (int i = 0; i < num_directories; ++i) {
774  Filename match(searchpath.get_directory(i), filename);
775  if (exists(match)) {
776  if (searchpath.get_directory(i) == "." &&
777  filename.is_fully_qualified()) {
778  // A special case for the "." directory: to avoid prefixing
779  // an endless stream of ./ in front of files, if the
780  // filename already has a ./ prefixed
781  // (i.e. is_fully_fully_qualified() is true), we don't
782  // prefix another one.
783  results.add_file(filename);
784  } else {
785  results.add_file(match);
786  }
787  ++num_added;
788  }
789  }
790  }
791 
792  return num_added;
793 }
794 
795 ////////////////////////////////////////////////////////////////////
796 // Function: VirtualFileSystem::write
797 // Access: Published
798 // Description: Print debugging information.
799 // (e.g. from Python or gdb prompt).
800 ////////////////////////////////////////////////////////////////////
802 write(ostream &out) const {
803  ((VirtualFileSystem *)this)->_lock.acquire();
804  Mounts::const_iterator mi;
805  for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
806  VirtualFileMount *mount = (*mi);
807  mount->write(out);
808  }
809  ((VirtualFileSystem *)this)->_lock.release();
810 }
811 
812 
813 ////////////////////////////////////////////////////////////////////
814 // Function: VirtualFileSystem::get_global_ptr
815 // Access: Published, Static
816 // Description: Returns the default global VirtualFileSystem. You
817 // may create your own personal VirtualFileSystem
818 // objects and use them for whatever you like, but Panda
819 // will attempt to load models and stuff from this
820 // default object.
821 //
822 // Initially, the global VirtualFileSystem is set up to
823 // mount the OS filesystem to root; i.e. it is
824 // equivalent to the OS filesystem. This may be
825 // subsequently adjusted by the user.
826 ////////////////////////////////////////////////////////////////////
829  if (_global_ptr == (VirtualFileSystem *)NULL) {
830  // Make sure this is initialized.
831  init_libexpress();
832 
833  _global_ptr = new VirtualFileSystem;
834 
835  // Set up the default mounts. First, there is always the root
836  // mount.
837  _global_ptr->mount("/", "/", 0);
838 
839  // And our initial cwd comes from the environment.
840  _global_ptr->chdir(ExecutionEnvironment::get_cwd());
841 
842  // Then, we add whatever mounts are listed in the Configrc file.
843  ConfigVariableList mounts
844  ("vfs-mount",
845  PRC_DESC("vfs-mount system-filename mount-point [options]"));
846 
847  int num_unique_values = mounts.get_num_unique_values();
848  for (int i = 0; i < num_unique_values; i++) {
849  string mount_desc = mounts.get_unique_value(i);
850 
851  // The vfs-mount syntax is:
852 
853  // vfs-mount system-filename mount-point [options]
854 
855  // The last two spaces mark the beginning of the mount point,
856  // and of the options, respectively. There might be multiple
857  // spaces in the system filename, which are part of the
858  // filename.
859 
860  // The last space marks the beginning of the mount point.
861  // Spaces before that are part of the system filename.
862  size_t space = mount_desc.rfind(' ');
863  if (space == string::npos) {
864  express_cat.warning()
865  << "No space in vfs-mount descriptor: " << mount_desc << "\n";
866 
867  } else {
868  string mount_point = mount_desc.substr(space + 1);
869  while (space > 0 && isspace(mount_desc[space - 1])) {
870  space--;
871  }
872  mount_desc = mount_desc.substr(0, space);
873  string options;
874 
875  space = mount_desc.rfind(' ');
876  if (space != string::npos) {
877  // If there's another space, we have the optional options field.
878  options = mount_point;
879  mount_point = mount_desc.substr(space + 1);
880  while (space > 0 && isspace(mount_desc[space - 1])) {
881  --space;
882  }
883  mount_desc = mount_desc.substr(0, space);
884  }
885 
886  mount_desc = ExecutionEnvironment::expand_string(mount_desc);
887  Filename physical_filename = Filename::from_os_specific(mount_desc);
888 
889  int flags;
890  string password;
891  parse_options(options, flags, password);
892  _global_ptr->mount(physical_filename, mount_point, flags, password);
893  }
894  }
895 
896  ConfigVariableString vfs_mount_ramdisk
897  ("vfs-mount-ramdisk", "",
898  PRC_DESC("vfs-mount-ramdisk mount-point [options]"));
899  if (!vfs_mount_ramdisk.empty()) {
900  string mount_point = vfs_mount_ramdisk;
901  string options;
902 
903  size_t space = mount_point.rfind(' ');
904  if (space != string::npos) {
905  // If there's a space, we have the optional options field.
906  options = mount_point.substr(space + 1);
907  while (space > 0 && isspace(mount_point[space - 1])) {
908  --space;
909  }
910  mount_point = mount_point.substr(0, space);
911  }
912 
913  int flags;
914  string password;
915  parse_options(options, flags, password);
916 
917  PT(VirtualFileMount) ramdisk = new VirtualFileMountRamdisk;
918  _global_ptr->mount(ramdisk, mount_point, flags);
919  }
920  }
921 
922  return _global_ptr;
923 }
924 
925 ////////////////////////////////////////////////////////////////////
926 // Function: VirtualFileSystem::open_read_file
927 // Access: Published
928 // Description: Convenience function; returns a newly allocated
929 // istream if the file exists and can be read, or NULL
930 // otherwise. Does not return an invalid istream.
931 //
932 // If auto_unwrap is true, an explicitly-named .pz file
933 // is automatically decompressed and the decompressed
934 // contents are returned. This is different than
935 // vfs-implicit-pz, which will automatically decompress
936 // a file if the extension .pz is *not* given.
937 ////////////////////////////////////////////////////////////////////
938 istream *VirtualFileSystem::
939 open_read_file(const Filename &filename, bool auto_unwrap) const {
940  PT(VirtualFile) file = get_file(filename, false);
941  if (file == (VirtualFile *)NULL) {
942  return NULL;
943  }
944  istream *str = file->open_read_file(auto_unwrap);
945  if (str != (istream *)NULL && str->fail()) {
946  close_read_file(str);
947  str = (istream *)NULL;
948  }
949  return str;
950 }
951 
952 ////////////////////////////////////////////////////////////////////
953 // Function: VirtualFileSystem::close_read_file
954 // Access: Published, Static
955 // Description: Closes a file opened by a previous call to
956 // open_read_file(). This really just deletes the
957 // istream pointer, but it is recommended to use this
958 // interface instead of deleting it explicitly, to help
959 // work around compiler issues.
960 ////////////////////////////////////////////////////////////////////
962 close_read_file(istream *stream) {
963  if (stream != (istream *)NULL) {
964  // For some reason--compiler bug in gcc 3.2?--explicitly deleting
965  // the stream pointer does not call the appropriate global delete
966  // function; instead apparently calling the system delete
967  // function. So we call the delete function by hand instead.
968 #if (!defined(WIN32_VC) && !defined(WIN64_VC)) && !defined(USE_MEMORY_NOWRAPPERS) && defined(REDEFINE_GLOBAL_OPERATOR_NEW)
969  stream->~istream();
970  (*global_operator_delete)(stream);
971 #else
972  delete stream;
973 #endif
974  }
975 }
976 
977 ////////////////////////////////////////////////////////////////////
978 // Function: VirtualFileSystem::open_write_file
979 // Access: Published
980 // Description: Convenience function; returns a newly allocated
981 // ostream if the file exists and can be written, or
982 // NULL otherwise. Does not return an invalid ostream.
983 //
984 // If auto_wrap is true, an explicitly-named .pz file is
985 // automatically compressed while writing. If truncate
986 // is true, the file is truncated to zero length before
987 // writing.
988 ////////////////////////////////////////////////////////////////////
989 ostream *VirtualFileSystem::
990 open_write_file(const Filename &filename, bool auto_wrap, bool truncate) {
991  PT(VirtualFile) file = create_file(filename);
992  if (file == (VirtualFile *)NULL) {
993  return NULL;
994  }
995  ostream *str = file->open_write_file(auto_wrap, truncate);
996  if (str != (ostream *)NULL && str->fail()) {
997  close_write_file(str);
998  str = (ostream *)NULL;
999  }
1000  return str;
1001 }
1002 
1003 ////////////////////////////////////////////////////////////////////
1004 // Function: VirtualFileSystem::open_append_file
1005 // Access: Published
1006 // Description: Works like open_write_file(), but the file is opened
1007 // in append mode. Like open_write_file, the returned
1008 // pointer should eventually be passed to
1009 // close_write_file().
1010 ////////////////////////////////////////////////////////////////////
1011 ostream *VirtualFileSystem::
1012 open_append_file(const Filename &filename) {
1013  PT(VirtualFile) file = create_file(filename);
1014  if (file == (VirtualFile *)NULL) {
1015  return NULL;
1016  }
1017  ostream *str = file->open_append_file();
1018  if (str != (ostream *)NULL && str->fail()) {
1019  close_write_file(str);
1020  str = (ostream *)NULL;
1021  }
1022  return str;
1023 }
1024 
1025 ////////////////////////////////////////////////////////////////////
1026 // Function: VirtualFileSystem::close_write_file
1027 // Access: Published, Static
1028 // Description: Closes a file opened by a previous call to
1029 // open_write_file(). This really just deletes the
1030 // ostream pointer, but it is recommended to use this
1031 // interface instead of deleting it explicitly, to help
1032 // work around compiler issues.
1033 ////////////////////////////////////////////////////////////////////
1035 close_write_file(ostream *stream) {
1036  if (stream != (ostream *)NULL) {
1037 #if (!defined(WIN32_VC) && !defined(WIN64_VC)) && !defined(USE_MEMORY_NOWRAPPERS) && defined(REDEFINE_GLOBAL_OPERATOR_NEW)
1038  stream->~ostream();
1039  (*global_operator_delete)(stream);
1040 #else
1041  delete stream;
1042 #endif
1043  }
1044 }
1045 
1046 ////////////////////////////////////////////////////////////////////
1047 // Function: VirtualFileSystem::open_read_write_file
1048 // Access: Published
1049 // Description: Convenience function; returns a newly allocated
1050 // iostream if the file exists and can be written, or
1051 // NULL otherwise. Does not return an invalid iostream.
1052 ////////////////////////////////////////////////////////////////////
1053 iostream *VirtualFileSystem::
1054 open_read_write_file(const Filename &filename, bool truncate) {
1055  PT(VirtualFile) file = create_file(filename);
1056  if (file == (VirtualFile *)NULL) {
1057  return NULL;
1058  }
1059  iostream *str = file->open_read_write_file(truncate);
1060  if (str != (iostream *)NULL && str->fail()) {
1061  close_read_write_file(str);
1062  str = (iostream *)NULL;
1063  }
1064  return str;
1065 }
1066 
1067 ////////////////////////////////////////////////////////////////////
1068 // Function: VirtualFileSystem::open_read_append_file
1069 // Access: Published
1070 // Description: Works like open_read_write_file(), but the file is opened
1071 // in append mode. Like open_read_write_file, the returned
1072 // pointer should eventually be passed to
1073 // close_read_write_file().
1074 ////////////////////////////////////////////////////////////////////
1075 iostream *VirtualFileSystem::
1077  PT(VirtualFile) file = create_file(filename);
1078  if (file == (VirtualFile *)NULL) {
1079  return NULL;
1080  }
1081  iostream *str = file->open_read_append_file();
1082  if (str != (iostream *)NULL && str->fail()) {
1083  close_read_write_file(str);
1084  str = (iostream *)NULL;
1085  }
1086  return str;
1087 }
1088 
1089 ////////////////////////////////////////////////////////////////////
1090 // Function: VirtualFileSystem::close_read_write_file
1091 // Access: Published, Static
1092 // Description: Closes a file opened by a previous call to
1093 // open_read_write_file(). This really just deletes the
1094 // iostream pointer, but it is recommended to use this
1095 // interface instead of deleting it explicitly, to help
1096 // work around compiler issues.
1097 ////////////////////////////////////////////////////////////////////
1099 close_read_write_file(iostream *stream) {
1100  if (stream != (iostream *)NULL) {
1101 #if (!defined(WIN32_VC) && !defined(WIN64_VC)) && !defined(USE_MEMORY_NOWRAPPERS) && defined(REDEFINE_GLOBAL_OPERATOR_NEW)
1102  stream->~iostream();
1103  (*global_operator_delete)(stream);
1104 #else
1105  delete stream;
1106 #endif
1107  }
1108 }
1109 
1110 ////////////////////////////////////////////////////////////////////
1111 // Function: VirtualFileSystem::atomic_compare_and_exchange_contents
1112 // Access: Public
1113 // Description: See Filename::atomic_compare_and_exchange_contents().
1114 ////////////////////////////////////////////////////////////////////
1116 atomic_compare_and_exchange_contents(const Filename &filename, string &orig_contents,
1117  const string &old_contents,
1118  const string &new_contents) {
1119  PT(VirtualFile) file = create_file(filename);
1120  if (file == NULL) {
1121  return false;
1122  }
1123 
1124  return file->atomic_compare_and_exchange_contents(orig_contents, old_contents, new_contents);
1125 }
1126 
1127 ////////////////////////////////////////////////////////////////////
1128 // Function: VirtualFileSystem::atomic_read_contents
1129 // Access: Public
1130 // Description: See Filename::atomic_read_contents().
1131 ////////////////////////////////////////////////////////////////////
1133 atomic_read_contents(const Filename &filename, string &contents) const {
1134  PT(VirtualFile) file = get_file(filename, false);
1135  if (file == NULL) {
1136  return false;
1137  }
1138 
1139  return file->atomic_read_contents(contents);
1140 }
1141 
1142 ////////////////////////////////////////////////////////////////////
1143 // Function: VirtualFileSystem::scan_mount_points
1144 // Access: Public
1145 // Description: Adds to names a list of all the mount points in use
1146 // that are one directory below path, if any. That is,
1147 // these are the external files or directories mounted
1148 // directly to the indicated path.
1149 //
1150 // The names vector is filled with a set of basenames,
1151 // the basename part of the mount point.
1152 ////////////////////////////////////////////////////////////////////
1154 scan_mount_points(vector_string &names, const Filename &path) const {
1155  nassertv(!path.empty() && !path.is_local());
1156  string prefix = path.get_fullpath().substr(1);
1157  Mounts::const_iterator mi;
1158  for (mi = _mounts.begin(); mi != _mounts.end(); ++mi) {
1159  VirtualFileMount *mount = (*mi);
1160 
1161  string mount_point = mount->get_mount_point();
1162  if (prefix.empty()) {
1163  // The indicated path is the root. Is the mount point on the
1164  // root?
1165  if (mount_point.find('/') == string::npos) {
1166  // No embedded slashes, so the mount point is only one
1167  // directory below the root.
1168  names.push_back(mount_point);
1169  }
1170  } else {
1171  if (mount_point.substr(0, prefix.length()) == prefix &&
1172  mount_point.length() > prefix.length() &&
1173  mount_point[prefix.length()] == '/') {
1174  // This mount point is below the indicated path. Is it only one
1175  // directory below?
1176  string basename = mount_point.substr(prefix.length());
1177  if (basename.find('/') == string::npos) {
1178  // No embedded slashes, so it's only one directory below.
1179  names.push_back(basename);
1180  }
1181  }
1182  }
1183  }
1184 }
1185 
1186 
1187 ////////////////////////////////////////////////////////////////////
1188 // Function: VirtualFileSystem::parse_options
1189 // Access: Public, Static
1190 // Description: Parses all of the option flags in the options list on
1191 // the vfs-mount Config.prc line.
1192 ////////////////////////////////////////////////////////////////////
1194 parse_options(const string &options, int &flags, string &password) {
1195  flags = 0;
1196  password = string();
1197 
1198  // Split the options up by commas.
1199  size_t p = 0;
1200  size_t q = options.find(',', p);
1201  while (q != string::npos) {
1202  parse_option(options.substr(p, q - p),
1203  flags, password);
1204  p = q + 1;
1205  q = options.find(',', p);
1206  }
1207  parse_option(options.substr(p), flags, password);
1208 }
1209 
1210 ////////////////////////////////////////////////////////////////////
1211 // Function: VirtualFileSystem::parse_option
1212 // Access: Public, Static
1213 // Description: Parses one of the option flags in the options list on
1214 // the vfs-mount Config.prc line.
1215 ////////////////////////////////////////////////////////////////////
1217 parse_option(const string &option, int &flags, string &password) {
1218  if (option == "0" || option.empty()) {
1219  // 0 is the null option.
1220  } else if (option == "ro") {
1221  flags |= MF_read_only;
1222  } else if (option.substr(0, 3) == "pw:") {
1223  password = option.substr(3);
1224  } else {
1225  express_cat.warning()
1226  << "Invalid option on vfs-mount: \"" << option << "\"\n";
1227  }
1228 }
1229 
1230 ////////////////////////////////////////////////////////////////////
1231 // Function: VirtualFileSystem::normalize_mount_point
1232 // Access: Private
1233 // Description: Converts the mount point string supplied by the user
1234 // to standard form (relative to the current directory,
1235 // with no double slashes, and not terminating with a
1236 // slash). The initial slash is removed.
1237 //
1238 // Assumes the lock is already held.
1239 ////////////////////////////////////////////////////////////////////
1240 Filename VirtualFileSystem::
1241 normalize_mount_point(const Filename &mount_point) const {
1242  Filename nmp = mount_point;
1243  if (nmp.is_local()) {
1244  nmp = Filename(_cwd, mount_point);
1245  }
1246  nmp.standardize();
1247  nassertr(!nmp.empty() && nmp[0] == '/', nmp);
1248  return nmp.get_fullpath().substr(1);
1249 }
1250 
1251 ////////////////////////////////////////////////////////////////////
1252 // Function: VirtualFileSystem::do_mount
1253 // Access: Private
1254 // Description: The private implementation of mount(). Assumes the
1255 // lock is already held.
1256 ////////////////////////////////////////////////////////////////////
1257 bool VirtualFileSystem::
1258 do_mount(VirtualFileMount *mount, const Filename &mount_point, int flags) {
1259  nassertr(mount->_file_system == NULL, false);
1260  mount->_file_system = this;
1261  mount->_mount_point = normalize_mount_point(mount_point);
1262  mount->_mount_flags = flags;
1263  _mounts.push_back(mount);
1264  ++_mount_seq;
1265  return true;
1266 }
1267 
1268 ////////////////////////////////////////////////////////////////////
1269 // Function: VirtualFileSystem::do_get_file
1270 // Access: Private
1271 // Description: The private implementation of get_file(),
1272 // create_file(), and make_directory(). Assumes the
1273 // lock is already held.
1274 ////////////////////////////////////////////////////////////////////
1275 PT(VirtualFile) VirtualFileSystem::
1276 do_get_file(const Filename &filename, int open_flags) const {
1277  if (filename.empty()) {
1278  return NULL;
1279  }
1280  Filename pathname(filename);
1281  if (pathname.is_local()) {
1282  pathname = Filename(_cwd, filename);
1283  if (filename.is_text()) {
1284  pathname.set_text();
1285  }
1286  }
1287  pathname.standardize();
1288  Filename strpath = pathname.get_filename_index(0).get_fullpath().substr(1);
1289  strpath.set_type(filename.get_type());
1290  // Also transparently look for a regular file suffixed .pz.
1291  Filename strpath_pz = strpath + ".pz";
1292 
1293  // Now scan all the mount points, from the back (since later mounts
1294  // override more recent ones), until a match is found.
1295  PT(VirtualFile) found_file = NULL;
1296  VirtualFileComposite *composite_file = NULL;
1297 
1298  // We use an index instead of an iterator, since the vector might
1299  // change if implicit mounts are added during this loop.
1300  unsigned int start_seq = _mount_seq;
1301 
1302  size_t i = _mounts.size();
1303  while (i > 0) {
1304  --i;
1305  VirtualFileMount *mount = _mounts[i];
1306  Filename mount_point = mount->get_mount_point();
1307  if (strpath == mount_point) {
1308  // Here's an exact match on the mount point. This filename is
1309  // the root directory of this mount object.
1310  if (consider_match(found_file, composite_file, mount, "", pathname,
1311  false, open_flags)) {
1312  return found_file;
1313  }
1314  } else if (mount_point.empty()) {
1315  // This is the root mount point; all files are in here.
1316  if (consider_match(found_file, composite_file, mount, strpath,
1317  pathname, false, open_flags)) {
1318  return found_file;
1319  }
1320 #ifdef HAVE_ZLIB
1321  if (vfs_implicit_pz) {
1322  if (consider_match(found_file, composite_file, mount, strpath_pz,
1323  pathname, true, open_flags)) {
1324  return found_file;
1325  }
1326  }
1327 #endif // HAVE_ZLIB
1328 
1329  } else if (strpath.length() > mount_point.length() &&
1330  mount_point == strpath.substr(0, mount_point.length()) &&
1331  strpath[mount_point.length()] == '/') {
1332  // This pathname falls within this mount system.
1333  Filename local_filename = strpath.substr(mount_point.length() + 1);
1334  Filename local_filename_pz = strpath_pz.substr(mount_point.length() + 1);
1335  if (consider_match(found_file, composite_file, mount, local_filename,
1336  pathname, false, open_flags)) {
1337  return found_file;
1338  }
1339 #ifdef HAVE_ZLIB
1340  if (vfs_implicit_pz) {
1341  // Bingo!
1342  if (consider_match(found_file, composite_file, mount, local_filename_pz,
1343  pathname, true, open_flags)) {
1344  return found_file;
1345  }
1346  }
1347 #endif // HAVE_ZLIB
1348  }
1349 
1350  // If we discover that a file has been implicitly mounted during
1351  // one of the above operations, start over from the beginning of
1352  // the loop.
1353  if (start_seq != _mount_seq) {
1354  start_seq = _mount_seq;
1355  i = _mounts.size();
1356  }
1357  }
1358 
1359  if (found_file == (VirtualFile *)NULL && vfs_implicit_mf) {
1360  // The file wasn't found, as-is. Does it appear to be an implicit
1361  // .mf file reference?
1362  ((VirtualFileSystem *)this)->consider_mount_mf(filename);
1363 
1364  if (start_seq != _mount_seq) {
1365  // Yes, it was, or some nested file was. Now that we've
1366  // implicitly mounted the .mf file, go back and look again.
1367  return do_get_file(filename, open_flags);
1368  }
1369  }
1370 
1371  return found_file;
1372 }
1373 
1374 ////////////////////////////////////////////////////////////////////
1375 // Function: VirtualFileSystem::consider_match
1376 // Access: Private
1377 // Description: Evaluates one possible filename match found during a
1378 // get_file() operation. There may be multiple matches
1379 // for a particular filename due to the ambiguities
1380 // introduced by allowing multiple mount points, so we
1381 // may have to keep searching even after the first match
1382 // is found.
1383 //
1384 // Returns true if the search should terminate now, or
1385 // false if it should keep iterating.
1386 ////////////////////////////////////////////////////////////////////
1387 bool VirtualFileSystem::
1388 consider_match(PT(VirtualFile) &found_file, VirtualFileComposite *&composite_file,
1389  VirtualFileMount *mount, const Filename &local_filename,
1390  const Filename &original_filename, bool implicit_pz_file,
1391  int open_flags) const {
1392  PT(VirtualFile) vfile =
1393  mount->make_virtual_file(local_filename, original_filename, false, open_flags);
1394  if (!vfile->has_file() && ((open_flags & OF_allow_nonexist) == 0)) {
1395  // Keep looking.
1396  return false;
1397  }
1398 
1399  if (found_file == (VirtualFile *)NULL) {
1400  // This was our first match. Save it.
1401  found_file = vfile;
1402  if (!found_file->is_directory() || ((open_flags & OF_make_directory) != 0)) {
1403  // If it's not a directory (or we wanted to make a directory), we're done.
1404  return true;
1405  }
1406  // It is a directory, so save it for later.
1407  if (implicit_pz_file) {
1408  // Don't look for directories named file.pz.
1409  found_file = NULL;
1410  }
1411 
1412  } else {
1413  // This was our second match. The previous match(es) must
1414  // have been directories.
1415  if (!vfile->is_directory()) {
1416  // However, this one isn't a directory. We're done.
1417  return true;
1418  }
1419 
1420  if (!implicit_pz_file) {
1421  // At least two directories matched to the same path. We
1422  // need a composite directory.
1423  if (composite_file == (VirtualFileComposite *)NULL) {
1424  composite_file =
1425  new VirtualFileComposite((VirtualFileSystem *)this, found_file->get_original_filename());
1426  composite_file->set_original_filename(original_filename);
1427  composite_file->add_component(found_file);
1428  found_file = composite_file;
1429  }
1430 
1431  composite_file->add_component(vfile);
1432  }
1433  }
1434 
1435  // Keep going, looking for more directories.
1436  return false;
1437 }
1438 
1439 ////////////////////////////////////////////////////////////////////
1440 // Function: VirtualFileSystem::consider_mount_mf
1441 // Access: Private
1442 // Description: The indicated filename was not found. Check to see
1443 // if it is using an implicit reference to a .mf file as
1444 // a directory, that hasn't already been mounted. If it
1445 // is, mount the .mf file in-place, and return true; if
1446 // it is not, or if its .mf file is already mounted
1447 // in-place, return false.
1448 //
1449 // Assumes the lock is already held.
1450 ////////////////////////////////////////////////////////////////////
1451 bool VirtualFileSystem::
1452 consider_mount_mf(const Filename &filename) {
1453  Filename dirname = filename.get_dirname();
1454  if (dirname.empty() || dirname == filename) {
1455  // Reached the top directory; no .mf file references.
1456  return false;
1457  }
1458  if (is_directory(dirname)) {
1459  // Reached a real (or already-mounted) directory; no unmounted .mf
1460  // file references.
1461  return false;
1462  }
1463  if (dirname.get_extension() == "mf") {
1464  // Hey, here's a multifile reference!
1465  dirname.set_binary();
1466  PT(VirtualFile) file = do_get_file(dirname, false);
1467  if (file == (VirtualFile *)NULL || !file->is_regular_file()) {
1468  // Oh, never mind. Not a real file.
1469  return false;
1470  }
1471 
1472  PT(Multifile) multifile = new Multifile;
1473 
1474  istream *stream = file->open_read_file(false);
1475  if (stream == (istream *)NULL) {
1476  // Couldn't read file.
1477  return false;
1478  }
1479 
1480  // Wrap a thread-safe wrapper around that stream, so multiple
1481  // threads can safely read the multifile simultaneously.
1482  IStreamWrapper *streamw = new IStreamWrapper(stream, true);
1483 
1484  if (!multifile->open_read(streamw, true)) {
1485  // Invalid multifile.
1486  return false;
1487  }
1488 
1489  multifile->set_multifile_name(dirname.get_basename());
1490  express_cat->info()
1491  << "Implicitly mounting " << dirname << "\n";
1492 
1493  PT(VirtualFileMountMultifile) new_mount =
1494  new VirtualFileMountMultifile(multifile);
1495  return do_mount(new_mount, dirname, MF_read_only);
1496  }
1497 
1498  // Recurse.
1499  return consider_mount_mf(dirname);
1500 }
Filename get_filename_index(int index) const
If the pattern flag is set for this Filename and the filename string actually includes a sequence of ...
Definition: filename.cxx:873
string get_dirname() const
Returns the directory part of the filename.
Definition: filename.I:424
bool copy_file(const Filename &orig_filename, const Filename &new_filename)
Attempts to copy the contents of the indicated file to the indicated file.
iostream * open_read_append_file(const Filename &filename)
Works like open_read_write_file(), but the file is opened in append mode.
PointerTo< VirtualFile > find_file(const Filename &filename, const DSearchPath &searchpath, bool status_only=false) const
Uses the indicated search path to find the file within the file system.
bool atomic_compare_and_exchange_contents(const Filename &filename, string &orig_contents, const string &old_contents, const string &new_contents)
See Filename::atomic_compare_and_exchange_contents().
string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:436
void set_extension(const string &s)
Replaces the file extension.
Definition: filename.cxx:837
bool rename_file(const Filename &orig_filename, const Filename &new_filename)
Attempts to move or rename the indicated file or directory.
bool is_exact_type(TypeHandle handle) const
Returns true if the current object is the indicated type exactly.
Definition: typedObject.I:74
virtual ostream * open_append_file()
Works like open_write_file(), but the file is opened in append mode.
bool resolve_filename(Filename &filename, const DSearchPath &searchpath, const string &default_extension=string()) const
Searches the given search path for the filename.
Multifile * get_multifile() const
Returns the Multifile pointer that this mount object is based on.
int find_all_files(const Filename &filename, const DSearchPath &searchpath, DSearchPath::Results &results) const
Searches all the directories in the search list for the indicated file, in order. ...
void set_encryption_password(const string &encryption_password)
Specifies the password that will be used to encrypt subfiles subsequently added to the multifile...
Definition: multifile.I:190
PointerTo< VirtualFileMount > get_mount(int n) const
Returns the nth mount in the system.
string get_fullpath() const
Returns the entire filename: directory, basename, extension.
Definition: filename.I:398
Maps an actual OS directory into the VirtualFileSystem.
A hierarchy of directories and files that appears to be one continuous file system, even though the files may originate from several different sources that may not be related to the actual OS&#39;s file system.
const Filename & get_mount_point() const
Returns the name of the directory within the virtual file system that this mount object is attached t...
istream * open_read_file(const Filename &filename, bool auto_unwrap) const
Convenience function; returns a newly allocated istream if the file exists and can be read...
void add_component(VirtualFile *file)
Adds one more component to the composite directory.
void set_type(Type type)
Sets the type of the file represented by the filename.
Definition: filename.I:564
virtual ostream * open_write_file(bool auto_wrap, bool truncate)
Opens the file for writing.
void set_binary()
Indicates that the filename represents a binary file.
Definition: filename.I:494
void set_text()
Indicates that the filename represents a text file.
Definition: filename.I:507
void set_original_filename(const Filename &filename)
Stores the original filename that was used to locate this VirtualFile.
Definition: virtualFile.I:69
static void close_read_write_file(iostream *stream)
Closes a file opened by a previous call to open_read_write_file().
static void parse_option(const string &option, int &flags, string &password)
Parses one of the option flags in the options list on the vfs-mount Config.prc line.
int get_num_mounts() const
Returns the number of individual mounts in the system.
bool make_directory_full(const Filename &filename)
Attempts to create a directory within the file system.
The abstract base class for a file or directory within the VirtualFileSystem.
Definition: virtualFile.h:37
bool is_fully_qualified() const
Returns true if the filename is fully qualified, e.g.
Definition: filename.I:682
This class is similar to ConfigVariable, but it reports its value as a list of strings.
string get_extension() const
Returns the file extension.
Definition: filename.I:477
PointerTo< VirtualFile > create_file(const Filename &filename)
Attempts to create a file by the indicated name in the filesystem, if possible, and returns it...
static void close_read_file(istream *stream)
Closes a file opened by a previous call to open_read_file().
void standardize()
Converts the filename to standard form by replacing consecutive slashes with a single slash...
Definition: filename.cxx:945
Simulates an actual directory on disk with in-memory storage.
Type get_type() const
Returns the type of the file represented by the filename, as previously set by set_type().
Definition: filename.I:583
virtual iostream * open_read_write_file(bool truncate)
Opens the file for writing.
int get_num_directories() const
Returns the number of directories on the search list.
bool atomic_read_contents(const Filename &filename, string &contents) const
See Filename::atomic_read_contents().
const Filename & get_directory(int n) const
Returns the nth directory on the search list.
static void parse_options(const string &options, int &flags, string &password)
Parses all of the option flags in the options list on the vfs-mount Config.prc line.
bool exists(const Filename &filename) const
Convenience function; returns true if the named file exists.
bool mount(Multifile *multifile, const Filename &mount_point, int flags)
Mounts the indicated Multifile at the given mount point.
bool is_directory(const Filename &filename) const
Convenience function; returns true if the named file exists and is a directory.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
This is a convenience class to specialize ConfigVariable as a string type.
This class provides a locking wrapper around an arbitrary istream pointer.
Definition: streamWrapper.h:53
bool make_directory(const Filename &filename)
Attempts to create a directory within the file system.
virtual bool is_directory() const
Returns true if this file represents a directory (and scan_directory() may be called), false otherwise.
Definition: virtualFile.cxx:41
string get_unique_value(int n) const
Returns the nth unique value of the variable.
bool chdir(const Filename &new_directory)
Changes the current directory.
ostream * open_append_file(const Filename &filename)
Works like open_write_file(), but the file is opened in append mode.
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
virtual iostream * open_read_append_file()
Works like open_read_write_file(), but the file is opened in append mode.
void scan_mount_points(vector_string &names, const Filename &path) const
Adds to names a list of all the mount points in use that are one directory below path, if any.
const Filename & get_physical_filename() const
Returns the name of the source file on the OS filesystem of the directory or file that is mounted...
int unmount_point(const Filename &mount_point)
Unmounts all systems attached to the given mount point from the file system.
PointerTo< VirtualFile > get_file(const Filename &filename, bool status_only=false) const
Looks up the file by the indicated name in the file system.
const Filename & get_original_filename() const
Returns the original filename as it was used to locate this VirtualFile.
Definition: virtualFile.I:33
int unmount(Multifile *multifile)
Unmounts all appearances of the indicated Multifile from the file system.
bool is_local() const
Returns true if the filename is local, e.g.
Definition: filename.I:664
bool is_text() const
Returns true if the Filename has been indicated to represent a text file via a previous call to set_t...
Definition: filename.I:548
virtual istream * open_read_file(bool auto_unwrap) const
Opens the file for reading.
bool is_directory() const
Returns true if the filename exists and is a directory name, false otherwise.
Definition: filename.cxx:1456
void write(ostream &out) const
Print debugging information.
ostream * open_write_file(const Filename &filename, bool auto_wrap, bool truncate)
Convenience function; returns a newly allocated ostream if the file exists and can be written...
The abstract base class for a mount definition used within a VirtualFileSystem.
iostream * open_read_write_file(const Filename &filename, bool truncate)
Convenience function; returns a newly allocated iostream if the file exists and can be written...
A file that contains a set of files.
Definition: multifile.h:34
This class stores a list of directories that can be searched, in order, to locate a particular file...
Definition: dSearchPath.h:32
int get_num_unique_values() const
Returns the number of unique values in the variable.
static Filename get_cwd()
Returns the name of the current working directory.
Filename get_cwd() const
Returns the current directory name.
int unmount_all()
Unmounts all files from the file system.
const Filename & get_multifile_name() const
Returns the filename of the Multifile, if it is available.
Definition: multifile.I:23
static string expand_string(const string &str)
Reads the string, looking for environment variable names marked by a $.
bool delete_file(const Filename &filename)
Attempts to delete the indicated file or directory.
static void close_write_file(ostream *stream)
Closes a file opened by a previous call to open_write_file().
A composite directory within the VirtualFileSystem: this maps to more than one directory on different...
Maps a Multifile&#39;s contents into the VirtualFileSystem.
virtual PointerTo< VirtualFile > make_virtual_file(const Filename &local_filename, const Filename &original_filename, bool implicit_pz_file, int open_flags)
Constructs and returns a new VirtualFile instance that corresponds to the indicated filename within t...
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1356
void add_file(const Filename &file)
Adds a new file to the result list.
Definition: dSearchPath.cxx:97
bool mount_loop(const Filename &virtual_filename, const Filename &mount_point, int flags, const string &password="")
This is similar to mount(), but it receives the name of a Multifile that already appears within the v...
static Filename from_os_specific(const string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes, and no drive letter) based on the supplied filename string that describes a filename in the local system conventions (for instance, on Windows, it may use backslashes or begin with a drive letter and a colon).
Definition: filename.cxx:332