// Filename: filename.cxx
// Created by:  drose (18Jan99)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////

#include "filename.h"
#include "filename_assist.h"
#include "dSearchPath.h"
#include "executionEnvironment.h"
#include "vector_string.h"
#include "atomicAdjust.h"

#include <stdio.h>  // For rename() and tempnam()
#include <time.h>   // for clock() and time()
#include <sys/stat.h>
#include <algorithm>

#ifdef PHAVE_UTIME_H
#include <utime.h>

// We assume we have these too.
#include <errno.h>
#include <fcntl.h>
#endif

#ifdef PHAVE_GLOB_H
  #include <glob.h>
  #ifndef GLOB_NOMATCH
    #define GLOB_NOMATCH -3
  #endif
#endif

#ifdef PHAVE_DIRENT_H
#include <dirent.h>
#endif

// It's true that dtoolbase.h includes this already, but we include
// this again in case we are building this file within ppremake.
#ifdef PHAVE_UNISTD_H
#include <unistd.h>
#endif

TextEncoder::Encoding Filename::_filesystem_encoding = TextEncoder::E_utf8;

TVOLATILE AtomicAdjust::Pointer Filename::_home_directory;
TVOLATILE AtomicAdjust::Pointer Filename::_temp_directory;
TVOLATILE AtomicAdjust::Pointer Filename::_user_appdata_directory;
TVOLATILE AtomicAdjust::Pointer Filename::_common_appdata_directory;
TypeHandle Filename::_type_handle;

#ifdef WIN32
/* begin Win32-specific code */

#ifdef WIN32_VC
#include <direct.h>
#include <windows.h>
#include <shlobj.h>
#include <io.h>
#endif

// The MSVC 6.0 Win32 SDK lacks the following definitions, so we define them
// here for compatibility.
#ifndef FILE_ATTRIBUTE_DEVICE
#define FILE_ATTRIBUTE_DEVICE 0x00000040
#endif

// We might have been linked with the Cygwin dll.  This is ideal if it
// is available, because it allows Panda to access all the Cygwin
// mount definitions if they are in use.  If the Cygwin dll is not
// available, we fall back to our own convention for converting
// pathnames.
#ifdef HAVE_CYGWIN
extern "C" void cygwin_conv_to_win32_path(const char *path, char *win32);
extern "C" void cygwin_conv_to_posix_path(const char *path, char *posix);
#endif

// Windows uses the convention \\hostname\path\to\file to represent a
// pathname to a file on another share.  This redefines a pathname to
// be something more complicated than a sequence of directory names
// separated by slashes.  The Unix convention to represent the same
// thing is, like everything else, to graft the reference to the
// remote hostname into the one global filesystem, with something like
// /hosts/hostname/path/to/file.  We observe the Unix convention for
// internal names used in Panda; this makes operations like
// Filename::get_dirname() simpler and more internally consistent.

// This string hard-defines the prefix that we use internally to
// indicate that the next directory component name should be treated
// as a hostname.  It might be nice to use a ConfigVariable for this,
// except that we haven't defined ConfigVariable by this point (and
// indeed we can't, since we need to have a Filename class already
// created in order to read the first config file).  Windows purists
// might be tempted to define this to a double slash so that internal
// Panda filenames more closely resemble their Windows counterparts.
// That might actually work, but it will cause problems with
// Filename::standardize().

// We use const char * instead of string to avoid static-init ordering
// issues.
static const char *hosts_prefix = "/hosts/";
static size_t hosts_prefix_length = 7;

static string
front_to_back_slash(const string &str) {
  string result = str;
  string::iterator si;
  for (si = result.begin(); si != result.end(); ++si) {
    if ((*si) == '/') {
      (*si) = '\\';
    }
  }

  return result;
}

static string
back_to_front_slash(const string &str) {
  string result = str;
  string::iterator si;
  for (si = result.begin(); si != result.end(); ++si) {
    if ((*si) == '\\') {
      (*si) = '/';
    }
  }

  return result;
}

static const string &
get_panda_root() {
  static string *panda_root = NULL;

  if (panda_root == NULL) {
    panda_root = new string;
    const char *envvar = getenv("PANDA_ROOT");
    if (envvar != (const char *)NULL) {
      (*panda_root) = front_to_back_slash(envvar);
    }

    // Ensure the string ends in a backslash.  If PANDA_ROOT is empty
    // or undefined, this function must return a single backslash--not
    // an empty string--since this prefix is used to replace a leading
    // slash in Filename::to_os_specific().
    if ((*panda_root).empty() || (*panda_root)[(*panda_root).length() - 1] != '\\') {
      (*panda_root) += '\\';
    }
  }

  return (*panda_root);
}

static string
convert_pathname(const string &unix_style_pathname) {
  if (unix_style_pathname.empty()) {
    return string();
  }

  // To convert from a Unix-style pathname to a Windows-style
  // pathname, we need to change all forward slashes to backslashes.
  // We might need to add a prefix as well, since Windows pathnames
  // typically begin with a drive letter.

  // By convention, if the top directory name consists of just one
  // letter, we treat that as a drive letter and map the rest of the
  // filename accordingly.  On the other hand, if the top directory
  // name consists of more than one letter, we assume this is a file
  // within some predefined tree whose root is given by the
  // environment variable "PANDA_ROOT", or if that is not defined,
  // "CYGWIN_ROOT" (for backward compatibility).
  string windows_pathname;

  if (unix_style_pathname[0] != '/') {
    // It doesn't even start from the root, so we don't have to do
    // anything fancy--relative pathnames are the same in Windows as
    // in Unix, except for the direction of the slashes.
    windows_pathname = front_to_back_slash(unix_style_pathname);

  } else if (unix_style_pathname.length() >= 2 &&
             isalpha(unix_style_pathname[1]) &&
             (unix_style_pathname.length() == 2 || unix_style_pathname[2] == '/')) {
    // This pathname begins with a slash and a single letter.  That
    // must be the drive letter.

    string remainder = unix_style_pathname.substr(2);
    if (remainder.empty()) {
      // There's a difference between "C:" and "C:/".
      remainder = "/";
    }
    remainder = front_to_back_slash(remainder);

    // We have to cast the result of toupper() to (char) to help some
    // compilers (e.g. Cygwin's gcc 2.95.3) happy; so that they do not
    // confuse this string constructor with one that takes two
    // iterators.
    windows_pathname =
      string(1, (char)toupper(unix_style_pathname[1])) + ":" + remainder;

  } else if (unix_style_pathname.length() > hosts_prefix_length &&
             unix_style_pathname.substr(0, hosts_prefix_length) == hosts_prefix) {
    // A filename like /hosts/fooby gets turned into \\fooby.
    windows_pathname = "\\\\" + front_to_back_slash(unix_style_pathname.substr(hosts_prefix_length));
    
  } else {
    // It starts with a slash, but the first part is not a single
    // letter.

#ifdef HAVE_CYGWIN
    // Use Cygwin to convert it if possible.
    char result[4096] = "";
    cygwin_conv_to_win32_path(unix_style_pathname.c_str(), result);
    windows_pathname = result;

#else  // HAVE_CYGWIN
    // Without Cygwin, just prefix $PANDA_ROOT.
    windows_pathname = get_panda_root();
    windows_pathname += front_to_back_slash(unix_style_pathname.substr(1));

#endif  // HAVE_CYGWIN
  }

  return windows_pathname;
}

static string
convert_dso_pathname(const string &unix_style_pathname) {
  // If the extension is .so, change it to .dll.
  size_t dot = unix_style_pathname.rfind('.');
  if (dot == string::npos ||
      unix_style_pathname.find('/', dot) != string::npos) {
    // No filename extension.
    return convert_pathname(unix_style_pathname);
  }
  if (unix_style_pathname.substr(dot) != ".so") {
    // Some other extension.
    return convert_pathname(unix_style_pathname);
  }

  string dll_basename = unix_style_pathname.substr(0, dot);

#ifdef _DEBUG
  // If we're building a debug version, all the dso files we link in
  // must be named file_d.dll.  This does prohibit us from linking in
  // external dso files, generated outside of the Panda build system,
  // that don't follow this _d convention.  Maybe we need a separate
  // convert_system_dso_pathname() function.

  // We can't simply check to see if the file exists, because this
  // might not be a full path to the dso filename--it might be
  // somewhere on the LD_LIBRARY_PATH, or on PATH, or any of a number
  // of nutty places.

  return convert_pathname(dll_basename + "_d.dll");
#else
  return convert_pathname(dll_basename + ".dll");
#endif
}

static string
convert_executable_pathname(const string &unix_style_pathname) {
  // If the extension is not .exe, append .exe.
  size_t dot = unix_style_pathname.rfind('.');
  if (dot == string::npos ||
      unix_style_pathname.find('/', dot) != string::npos) {
    // No filename extension.
    return convert_pathname(unix_style_pathname + ".exe");
  }
  if (unix_style_pathname.substr(dot) != ".exe") {
    // Some other extension.
    return convert_pathname(unix_style_pathname + ".exe");
  }

  return convert_pathname(unix_style_pathname);
}
#endif //WIN32

////////////////////////////////////////////////////////////////////
//     Function: Filename::Constructor
//       Access: Published
//  Description: This constructor composes the filename out of a
//               directory part and a basename part.  It will insert
//               an intervening '/' if necessary.
////////////////////////////////////////////////////////////////////
Filename::
Filename(const Filename &dirname, const Filename &basename) {
  if (dirname.empty()) {
    (*this) = basename;
  } else {
    _flags = basename._flags;
    string dirpath = dirname.get_fullpath();
    if (dirpath[dirpath.length() - 1] == '/') {
      (*this) = dirpath + basename.get_fullpath();
    } else {
      (*this) = dirpath + "/" + basename.get_fullpath();
    }
  }
}

#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
//     Function: Filename::__reduce__
//       Access: Published
//  Description: This special Python method is implement to provide
//               support for the pickle module.
////////////////////////////////////////////////////////////////////
PyObject *Filename::
__reduce__(PyObject *self) const {
  // We should return at least a 2-tuple, (Class, (args)): the
  // necessary class object whose constructor we should call
  // (e.g. this), and the arguments necessary to reconstruct this
  // object.
  PyObject *this_class = PyObject_Type(self);
  if (this_class == NULL) {
    return NULL;
  }

  PyObject *result = Py_BuildValue("(O(s))", this_class, c_str());
  Py_DECREF(this_class);
  return result;
}
#endif  // HAVE_PYTHON

////////////////////////////////////////////////////////////////////
//     Function: Filename::from_os_specific
//       Access: Published, Static
//  Description: 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).
//
//               Use this function to create a Filename from an
//               externally-given filename string.  Use
//               to_os_specific() again later to reconvert it back to
//               the local operating system's conventions.
//
//               This function will do the right thing even if the
//               filename is partially local conventions and partially
//               Panda conventions; e.g. some backslashes and some
//               forward slashes.
////////////////////////////////////////////////////////////////////
Filename Filename::
from_os_specific(const string &os_specific, Filename::Type type) {
#ifdef WIN32
  string result = back_to_front_slash(os_specific);
  const string &panda_root = get_panda_root();

  // If the initial prefix is the same as panda_root, remove it.
  if (!panda_root.empty() && panda_root != string("\\") && 
      panda_root.length() < result.length()) {
    bool matches = true;
    size_t p;
    for (p = 0; p < panda_root.length() && matches; ++p) {
      char c = tolower(panda_root[p]);
      if (c == '\\') {
        c = '/';
      }
      matches = (c == tolower(result[p]));
    }

    if (matches) {
      // The initial prefix matches!  Replace the initial bit with a
      // leading slash.
      result = result.substr(panda_root.length());
      assert(!result.empty());
      if (result[0] != '/') {
        result = '/' + result;
      }
      Filename filename(result);
      filename.set_type(type);
      return filename;
    }
  }

  // All right, the initial prefix was not under panda_root.  But
  // maybe it begins with a drive letter.
  if (result.size() >= 3 && isalpha(result[0]) &&
      result[1] == ':' && result[2] == '/') {
    result[1] = tolower(result[0]);
    result[0] = '/';

    // If there's *just* a slash following the drive letter, go ahead
    // and trim it.
    if (result.size() == 3) {
      result = result.substr(0, 2);
    }

  } else if (result.substr(0, 2) == "//") {
    // If the initial prefix is a double slash, convert it to /hosts/.
    result = hosts_prefix + result.substr(2);
  }

  Filename filename(result);
  filename.set_type(type);
  return filename;
#else  // WIN32
  // Generic Unix-style filenames--no conversion necessary.
  Filename filename(os_specific);
  filename.set_type(type);
  return filename;
#endif  // WIN32
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::from_os_specific_w
//       Access: Published, Static
//  Description: The wide-string variant of from_os_specific().
//               Returns a new Filename, converted from an os-specific
//               wide-character string.
////////////////////////////////////////////////////////////////////
Filename Filename::
from_os_specific_w(const wstring &os_specific, Filename::Type type) {
  TextEncoder encoder;
  encoder.set_encoding(get_filesystem_encoding());
  encoder.set_wtext(os_specific);
  return from_os_specific(encoder.get_text(), type);
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::expand_from
//       Access: Published, Static
//  Description: Returns the same thing as from_os_specific(), but
//               embedded environment variable references
//               (e.g. "$DMODELS/foo.txt") are expanded out.  It also
//               automatically elevates the file to its true case if
//               needed.
////////////////////////////////////////////////////////////////////
Filename Filename::
expand_from(const string &os_specific, Filename::Type type) {
  Filename file = from_os_specific(ExecutionEnvironment::expand_string(os_specific),
                                   type);
  file.make_true_case();
  return file;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::temporary
//       Access: Published, Static
//  Description: Generates a temporary filename within the indicated
//               directory, using the indicated prefix.  If the
//               directory is empty, a system-defined directory is
//               chosen instead.
//
//               The generated filename did not exist when the
//               Filename checked, but since it does not specifically
//               create the file, it is possible that another process
//               could simultaneously create a file by the same name.
////////////////////////////////////////////////////////////////////
Filename Filename::
temporary(const string &dirname, const string &prefix, const string &suffix,
          Type type) {
  Filename fdirname = dirname;
#ifdef WIN32
  // The Windows tempnam() function doesn't do a good job of choosing
  // a temporary directory.  Choose one ourselves.
  if (fdirname.empty()) {
    fdirname = Filename::get_temp_directory();
  }
#endif

  if (fdirname.empty()) {
    // If we are not given a dirname, use the system tempnam()
    // function to create a system-defined temporary filename.
    char *name = tempnam(NULL, prefix.c_str());
    Filename result = Filename::from_os_specific(name);
    free(name);
    result.set_type(type);
    return result;
  }

  // If we *are* given a dirname, then use our own algorithm to make
  // up a filename within that dirname.  We do that because the system
  // tempnam() (for instance, under Windows) may ignore the dirname.

  Filename result;
  do {
    // We take the time of day and multiply it by the process time.
    // This will give us a very large number, of which we take the
    // bottom 24 bits and generate a 6-character hex code.
    int hash = (clock() * time(NULL)) & 0xffffff;
    char hex_code[10];
    sprintf(hex_code, "%06x", hash);
    result = Filename(fdirname, Filename(prefix + hex_code + suffix));
    result.set_type(type);
  } while (result.exists());

  return result;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::get_home_directory
//       Access: Published
//  Description: Returns a path to the user's home directory, if such
//               a thing makes sense in the current OS, or to the
//               nearest equivalent.  This may or may not be directly
//               writable by the application.
////////////////////////////////////////////////////////////////////
const Filename &Filename::
get_home_directory() {
  if (AtomicAdjust::get_ptr(_home_directory) == NULL) {
    Filename home_directory;

    // In all environments, check $HOME first.
    char *home = getenv("HOME");
    if (home != (char *)NULL) {
      Filename dirname = from_os_specific(home);
      if (dirname.is_directory()) {
        if (dirname.make_canonical()) {
          home_directory = dirname;
        }
      }
    }

    if (home_directory.empty()) {
#ifdef WIN32
      wchar_t buffer[MAX_PATH];
      
      // On Windows, fall back to the "My Documents" folder.
      if (SHGetSpecialFolderPathW(NULL, buffer, CSIDL_PERSONAL, true)) {
        Filename dirname = from_os_specific_w(buffer);
        if (dirname.is_directory()) {
          if (dirname.make_canonical()) {
            home_directory = dirname;
          }
        }
      }

#elif defined(IS_OSX)
      home_directory = get_osx_home_directory();
      
#else
      // Posix case: check /etc/passwd?
      
#endif  // WIN32
    }
      
    if (home_directory.empty()) {
      // Fallback case.
      home_directory = ExecutionEnvironment::get_cwd();
    }

    Filename *newdir = new Filename(home_directory);
    if (AtomicAdjust::compare_and_exchange_ptr(_home_directory, NULL, newdir) != NULL) {
      // Didn't store it.  Must have been stored by someone else.
      assert(_home_directory != NULL);
      delete newdir;
    }
  }
  
  return (*(Filename *)_home_directory);
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::get_temp_directory
//       Access: Published
//  Description: Returns a path to a system-defined temporary
//               directory.
////////////////////////////////////////////////////////////////////
const Filename &Filename::
get_temp_directory() {
  if (AtomicAdjust::get_ptr(_temp_directory) == NULL) {
    Filename temp_directory;

#ifdef WIN32
    static const size_t buffer_size = 4096;
    wchar_t buffer[buffer_size];
    if (GetTempPathW(buffer_size, buffer) != 0) {
      Filename dirname = from_os_specific_w(buffer);
      if (dirname.is_directory()) {
        if (dirname.make_canonical()) {
          temp_directory = dirname;
        }
      }
    }

#elif defined(IS_OSX)
    temp_directory = get_osx_temp_directory();

#else
    // Posix case.
    temp_directory = "/tmp";
#endif  // WIN32

    if (temp_directory.empty()) {
      // Fallback case.
      temp_directory = ExecutionEnvironment::get_cwd();
    }

    Filename *newdir = new Filename(temp_directory);
    if (AtomicAdjust::compare_and_exchange_ptr(_temp_directory, NULL, newdir) != NULL) {
      // Didn't store it.  Must have been stored by someone else.
      assert(_temp_directory != NULL);
      delete newdir;
    }
  }

  return (*(Filename *)_temp_directory);
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::get_user_appdata_directory
//       Access: Published
//  Description: Returns a path to a system-defined directory
//               appropriate for creating a subdirectory for storing
//               application-specific data, specific to the current
//               user.
////////////////////////////////////////////////////////////////////
const Filename &Filename::
get_user_appdata_directory() {
  if (AtomicAdjust::get_ptr(_user_appdata_directory) == NULL) {
    Filename user_appdata_directory;

#ifdef WIN32
    wchar_t buffer[MAX_PATH];

    if (SHGetSpecialFolderPathW(NULL, buffer, CSIDL_LOCAL_APPDATA, true)) {
      Filename dirname = from_os_specific_w(buffer);
      if (dirname.is_directory()) {
        if (dirname.make_canonical()) {
          user_appdata_directory = dirname;
        }
      }
    }

#elif defined(IS_OSX)
    user_appdata_directory = get_osx_user_appdata_directory();

#else
    // Posix case.
    user_appdata_directory = get_home_directory();

#endif  // WIN32

    if (user_appdata_directory.empty()) {
      // Fallback case.
      user_appdata_directory = ExecutionEnvironment::get_cwd();
    }

    Filename *newdir = new Filename(user_appdata_directory);
    if (AtomicAdjust::compare_and_exchange_ptr(_user_appdata_directory, NULL, newdir) != NULL) {
      // Didn't store it.  Must have been stored by someone else.
      assert(_user_appdata_directory != NULL);
      delete newdir;
    }
  }

  return (*(Filename *)_user_appdata_directory);
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::get_common_appdata_directory
//       Access: Published
//  Description: Returns a path to a system-defined directory
//               appropriate for creating a subdirectory for storing
//               application-specific data, common to all users.
////////////////////////////////////////////////////////////////////
const Filename &Filename::
get_common_appdata_directory() {
  if (AtomicAdjust::get_ptr(_common_appdata_directory) == NULL) {
    Filename common_appdata_directory;

#ifdef WIN32
    wchar_t buffer[MAX_PATH];

    if (SHGetSpecialFolderPathW(NULL, buffer, CSIDL_COMMON_APPDATA, true)) {
      Filename dirname = from_os_specific_w(buffer);
      if (dirname.is_directory()) {
        if (dirname.make_canonical()) {
          common_appdata_directory = dirname;
        }
      }
    }

#elif defined(IS_OSX)
    common_appdata_directory = get_osx_common_appdata_directory();

#else
    // Posix case.
    common_appdata_directory = "/var";
#endif  // WIN32

    if (common_appdata_directory.empty()) {
      // Fallback case.
      common_appdata_directory = ExecutionEnvironment::get_cwd();
    }

    Filename *newdir = new Filename(common_appdata_directory);
    if (AtomicAdjust::compare_and_exchange_ptr(_common_appdata_directory, NULL, newdir) != NULL) {
      // Didn't store it.  Must have been stored by someone else.
      assert(_common_appdata_directory != NULL);
      delete newdir;
    }
  }

  return (*(Filename *)_common_appdata_directory);
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::set_fullpath
//       Access: Published
//  Description: Replaces the entire filename: directory, basename,
//               extension.  This can also be achieved with the
//               assignment operator.
////////////////////////////////////////////////////////////////////
void Filename::
set_fullpath(const string &s) {
  (*this) = s;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::set_dirname
//       Access: Published
//  Description: Replaces the directory part of the filename.  This is
//               everything in the filename up to, but not including
//               the rightmost slash.
////////////////////////////////////////////////////////////////////
void Filename::
set_dirname(const string &s) {
  if (s.empty()) {
    // Remove the directory prefix altogether.
    _filename.replace(0, _basename_start, "");

    int length_change = - ((int)_basename_start);

    _dirname_end = 0;
    _basename_start += length_change;
    _basename_end += length_change;
    _extension_start += length_change;

  } else {
    // Replace the existing directory prefix, or insert a new one.

    // We build the string ss to include the terminal slash.
    string ss;
    if (s[s.length()-1] == '/') {
      ss = s;
    } else {
      ss = s+'/';
    }

    int length_change = ss.length() - _basename_start;

    _filename.replace(0, _basename_start, ss);

    _dirname_end = ss.length() - 1;

    // An exception: if the dirname string was the single slash, the
    // dirname includes that slash.
    if (ss.length() == 1) {
      _dirname_end = 1;
    }

    _basename_start += length_change;

    if (_basename_end != string::npos) {
      _basename_end += length_change;
      _extension_start += length_change;
    }
  }
  locate_hash();
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::set_basename
//       Access: Published
//  Description: Replaces the basename part of the filename.  This is
//               everything in the filename after the rightmost slash,
//               including any extensions.
////////////////////////////////////////////////////////////////////
void Filename::
set_basename(const string &s) {
  _filename.replace(_basename_start, string::npos, s);
  locate_extension();
  locate_hash();
}


////////////////////////////////////////////////////////////////////
//     Function: Filename::set_fullpath_wo_extension
//       Access: Published
//  Description: Replaces the full filename--directory and basename
//               parts--except for the extension.
////////////////////////////////////////////////////////////////////
void Filename::
set_fullpath_wo_extension(const string &s) {
  int length_change = s.length() - _basename_end;

  _filename.replace(0, _basename_end, s);

  if (_basename_end != string::npos) {
    _basename_end += length_change;
    _extension_start += length_change;
  }
  locate_hash();
}


////////////////////////////////////////////////////////////////////
//     Function: Filename::set_basename_wo_extension
//       Access: Published
//  Description: Replaces the basename part of the filename, without
//               the file extension.
////////////////////////////////////////////////////////////////////
void Filename::
set_basename_wo_extension(const string &s) {
  int length_change = s.length() - (_basename_end - _basename_start);

  if (_basename_end == string::npos) {
    _filename.replace(_basename_start, string::npos, s);

  } else {
    _filename.replace(_basename_start, _basename_end - _basename_start, s);

    _basename_end += length_change;
    _extension_start += length_change;
  }
  locate_hash();
}


////////////////////////////////////////////////////////////////////
//     Function: Filename::set_extension
//       Access: Published
//  Description: Replaces the file extension.  This is everything after
//               the rightmost dot, if there is one, or the empty
//               string if there is not.
////////////////////////////////////////////////////////////////////
void Filename::
set_extension(const string &s) {
  if (s.empty()) {
    // Remove the extension altogether.
    if (_basename_end != string::npos) {
      _filename.replace(_basename_end, string::npos, "");
      _basename_end = string::npos;
      _extension_start = string::npos;
    }

  } else if (_basename_end == string::npos) {
    // Insert an extension where there was none before.
    _basename_end = _filename.length();
    _extension_start = _filename.length() + 1;
    _filename += '.' + s;

  } else {
    // Replace an existing extension.
    _filename.replace(_extension_start, string::npos, s);
  }
  locate_hash();
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::get_filename_index
//       Access: Published
//  Description: If the pattern flag is set for this Filename and the
//               filename string actually includes a sequence of hash
//               marks, then this returns a new Filename with the
//               sequence of hash marks replaced by the indicated
//               index number.
//
//               If the pattern flag is not set for this Filename or
//               it does not contain a sequence of hash marks, this
//               quietly returns the original filename.
////////////////////////////////////////////////////////////////////
Filename Filename::
get_filename_index(int index) const {
  Filename file(*this);

  if (_hash_end != _hash_start) {
    ostringstream strm;
    strm << _filename.substr(0, _hash_start) 
         << setw(_hash_end - _hash_start) << setfill('0') << index
         << _filename.substr(_hash_end);
    file.set_fullpath(strm.str());
  }
  file.set_pattern(false);

  return file;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::set_hash_to_end
//       Access: Published
//  Description: Replaces the part of the filename from the beginning
//               of the hash sequence to the end of the filename.
////////////////////////////////////////////////////////////////////
void Filename::
set_hash_to_end(const string &s) {
  _filename.replace(_hash_start, string::npos, s);

  locate_basename();
  locate_extension();
  locate_hash();
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::extract_components
//       Access: Published
//  Description: Extracts out the individual directory components of
//               the path into a series of strings.  get_basename()
//               will be the last component stored in the vector.
//               Note that no distinction is made by this method
//               between a leading slash and no leading slash, but you
//               can call is_local() to differentiate the two cases.
////////////////////////////////////////////////////////////////////
void Filename::
extract_components(vector_string &components) const {
  components.clear();

  size_t p = 0;
  if (!_filename.empty() && _filename[0] == '/') {
    // Skip the leading slash.
    p = 1;
  }
  while (p < _filename.length()) {
    size_t q = _filename.find('/', p);
    if (q == string::npos) {
      components.push_back(_filename.substr(p));
      return;
    }
    components.push_back(_filename.substr(p, q - p));
    p = q + 1;
  }

  // A trailing slash means we have an empty get_basename().
  components.push_back(string());
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::standardize
//       Access: Published
//  Description: Converts the filename to standard form by replacing
//               consecutive slashes with a single slash, removing a
//               trailing slash if present, and backing up over ../
//               sequences within the filename where possible.
////////////////////////////////////////////////////////////////////
void Filename::
standardize() {
  assert(!_filename.empty());
  if (_filename == ".") {
    // Don't change a single dot; this refers to the current directory.
    return;
  }

  vector_string components;

  // Pull off the components of the filename one at a time.
  bool global = (_filename[0] == '/');

  size_t p = 0;
  while (p < _filename.length() && _filename[p] == '/') {
    p++;
  }
  while (p < _filename.length()) {
    size_t slash = _filename.find('/', p);
    string component = _filename.substr(p, slash - p);
    if (component == "." && p != 0) {
      // Ignore /./.
    } else if (component == ".." && !components.empty() &&
               !(components.back() == "..")) {
      if (components.back() == ".") {
        // To "back up" over a leading ./ means simply to remove the
        // leading ./
        components.pop_back();
        components.push_back(component);
      } else {
        // Back up normally.
        components.pop_back();
      }
    } else {
      components.push_back(component);
    }

    p = slash;
    while (p < _filename.length() && _filename[p] == '/') {
      p++;
    }
  }

  // Now reassemble the filename.
  string result;
  if (global) {
    result = "/";
  }
  if (!components.empty()) {
    result += components[0];
    for (int i = 1; i < (int)components.size(); i++) {
      result += "/" + components[i];
    }
  }

  (*this) = result;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::make_absolute
//       Access: Published
//  Description: Converts the filename to a fully-qualified pathname
//               from the root (if it is a relative pathname), and
//               then standardizes it (see standardize()).
//
//               This is sometimes a little problematic, since it may
//               convert the file to its 'true' absolute pathname,
//               which could be an ugly NFS-named file, irrespective
//               of symbolic links
//               (e.g. /.automount/dimbo/root/usr2/fit/people/drose
//               instead of /fit/people/drose); besides being ugly,
//               filenames like this may not be consistent across
//               multiple different platforms.
////////////////////////////////////////////////////////////////////
void Filename::
make_absolute() {
  if (is_local()) {
    make_absolute(ExecutionEnvironment::get_cwd());
  } else {
    standardize();
  }
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::make_absolute
//       Access: Published
//  Description: Converts the filename to a fully-qualified filename
//               from the root (if it is a relative filename), and
//               then standardizes it (see standardize()).  This
//               flavor accepts a specific starting directory that the
//               filename is known to be relative to.
////////////////////////////////////////////////////////////////////
void Filename::
make_absolute(const Filename &start_directory) {
  if (is_local()) {
    Filename new_filename(start_directory, _filename);
    new_filename._flags = _flags;
    (*this) = new_filename;
  }

  standardize();
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::make_canonical
//       Access: Published
//  Description: Converts this filename to a canonical name by
//               replacing the directory part with the fully-qualified
//               directory part.  This is done by changing to that
//               directory and calling getcwd().
//
//               This has the effect of (a) converting relative paths
//               to absolute paths (but see make_absolute() if this is
//               the only effect you want), and (b) always resolving a
//               given directory name to the same string, even if
//               different symbolic links are traversed, and (c)
//               changing nice symbolic-link paths like
//               /fit/people/drose to ugly NFS automounter names like
//               /hosts/dimbo/usr2/fit/people/drose.  This can be
//               troubling, but sometimes this is exactly what you
//               want, particularly if you're about to call
//               make_relative_to() between two filenames.
//
//               The return value is true if successful, or false on
//               failure (usually because the directory name does not
//               exist or cannot be chdir'ed into).
////////////////////////////////////////////////////////////////////
bool Filename::
make_canonical() {
  if (empty()) {
    // An empty filename is a special case.  This doesn't name
    // anything.
    return false;
  }

  if (get_fullpath() == "/") {
    // The root directory is a special case.
    return true;
  }
  
#ifndef WIN32
  // Use realpath in order to resolve symlinks properly
  char newpath [PATH_MAX + 1];
  if (realpath(c_str(), newpath) != NULL) {
    Filename newpath_fn(newpath);
    newpath_fn._flags = _flags;
    (*this) = newpath_fn;
  }
#endif

  Filename cwd = ExecutionEnvironment::get_cwd();
  if (!r_make_canonical(cwd)) {
    return false;
  }

  return make_true_case();
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::make_true_case
//       Access: Published
//  Description: On a case-insensitive operating system
//               (e.g. Windows), this method looks up the file in the
//               file system and resets the Filename to represent the
//               actual case of the file as it exists on the disk.
//               The return value is true if the file exists and the
//               conversion can be made, or false if there is some
//               error.
//
//               On a case-sensitive operating system, this method
//               does nothing and always returns true.
//
//               An empty filename is considered to exist in this
//               case.
////////////////////////////////////////////////////////////////////
bool Filename::
make_true_case() {
  assert(!get_pattern());

  if (empty()) {
    return true;
  }

#ifdef WIN32
  wstring os_specific = to_os_specific_w();

  // First, we have to convert it to its short name, then back to its
  // long name--that seems to be the trick to force Windows to throw
  // away the case we give it and get the actual file case.
  
  wchar_t short_name[MAX_PATH + 1];
  DWORD l = GetShortPathNameW(os_specific.c_str(), short_name, MAX_PATH + 1);
  if (l == 0) {
    // Couldn't query the path name for some reason.  Probably the
    // file didn't exist.
    return false;
  }
  // According to the Windows docs, l will return a value greater than
  // the specified length if the short_name length wasn't enough--but also
  // according to the Windows docs, MAX_PATH will always be enough.
  assert(l < MAX_PATH + 1);
  
  wchar_t long_name[MAX_PATH + 1];
  l = GetLongPathNameW(short_name, long_name, MAX_PATH + 1);
  if (l == 0) {
    // Couldn't query the path name for some reason.  Probably the
    // file didn't exist.
    return false;
  }
  assert(l < MAX_PATH + 1);

  Filename true_case = Filename::from_os_specific_w(long_name);

  // Now sanity-check the true-case filename.  If it's not the same as
  // the source file, except for case, reject it.
  wstring orig_filename = get_fullpath_w();
  wstring new_filename = true_case.get_fullpath_w();
  bool match = (orig_filename.length() == new_filename.length());
  for (size_t i = 0; i < orig_filename.length() && match; ++i) {
    match = (TextEncoder::unicode_tolower(orig_filename[i]) == TextEncoder::unicode_tolower(new_filename[i]));
  }
  if (!match) {
    // Something went wrong.  Keep the original filename, assume it
    // was the correct case after all.  We return true because the
    // filename is good.
    return true;
  }

  true_case._flags = _flags;
  (*this) = true_case;
  return true;

#else  // WIN32
  return true;
#endif  // WIN32
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::to_os_specific
//       Access: Published
//  Description: Converts the filename from our generic Unix-like
//               convention (forward slashes starting with the root at
//               '/') to the corresponding filename in the local
//               operating system (slashes in the appropriate
//               direction, starting with the root at C:\, for
//               instance).  Returns the string representing the
//               converted filename, but does not change the Filename
//               itself.
//
//               See also from_os_specific().
////////////////////////////////////////////////////////////////////
string Filename::
to_os_specific() const {
  assert(!get_pattern());

  if (empty()) {
    return string();
  }
  Filename standard(*this);
  standard.standardize();

#ifdef IS_OSX 
  if (get_type() == T_dso) {
    std::string workname = standard.get_fullpath();
    size_t dot = workname.rfind('.');
    if (dot != string::npos) {
      if (workname.substr(dot) == ".so") {
        string dyLibBase = workname.substr(0, dot)+".dylib";
        return dyLibBase; 
      }
    }
  }
#endif

#ifdef WIN32
  switch (get_type()) {
  case T_dso:
    return convert_dso_pathname(standard.get_fullpath());
  case T_executable:
    return convert_executable_pathname(standard.get_fullpath());
  default:
    return convert_pathname(standard.get_fullpath());
  }
#else // WIN32
  return standard.c_str();
#endif // WIN32
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::to_os_specific_w
//       Access: Published
//  Description: The wide-string variant on to_os_specific().
////////////////////////////////////////////////////////////////////
wstring Filename::
to_os_specific_w() const {
  TextEncoder encoder;
  encoder.set_encoding(get_filesystem_encoding());
  encoder.set_text(to_os_specific());
  return encoder.get_wtext();
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::to_os_generic
//       Access: Published
//  Description: This is similar to to_os_specific(), but it is
//               designed to generate a filename that can be
//               understood on as many platforms as possible.  Since
//               Windows can usually understand a
//               forward-slash-delimited filename, this means it does
//               the same thing as to_os_specific(), but it uses
//               forward slashes instead of backslashes.
//
//               This method has a pretty limited use; it should
//               generally be used for writing file references to a
//               file that might be read on any operating system.
////////////////////////////////////////////////////////////////////
string Filename::
to_os_generic() const {
  assert(!get_pattern());

#ifdef WIN32
  return back_to_front_slash(to_os_specific());
#else // WIN32
  return to_os_specific();
#endif // WIN32
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::to_os_short_name
//       Access: Published
//  Description: This works like to_os_generic(), but it returns the
//               "short name" version of the filename, if it exists,
//               or the original filename otherwise.
//
//               On Windows platforms, this returns the 8.3 filename
//               version of the given filename, if the file exists,
//               and the same thing as to_os_specific() otherwise.  On
//               non-Windows platforms, this always returns the same
//               thing as to_os_specific().
////////////////////////////////////////////////////////////////////
string Filename::
to_os_short_name() const {
  assert(!get_pattern());

#ifdef WIN32
  wstring os_specific = to_os_specific_w();
  
  wchar_t short_name[MAX_PATH + 1];
  DWORD l = GetShortPathNameW(os_specific.c_str(), short_name, MAX_PATH + 1);
  if (l == 0) {
    // Couldn't query the path name for some reason.  Probably the
    // file didn't exist.
    return to_os_specific();
  }
  // According to the Windows docs, l will return a value greater than
  // the specified length if the short_name length wasn't enough--but also
  // according to the Windows docs, MAX_PATH will always be enough.
  assert(l < MAX_PATH + 1);

  TextEncoder encoder;
  encoder.set_encoding(get_filesystem_encoding());
  encoder.set_wtext(short_name);
  return encoder.get_text();

#else // WIN32
  return to_os_specific();
#endif // WIN32
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::to_os_long_name
//       Access: Published
//  Description: This is the opposite of to_os_short_name(): it
//               returns the "long name" of the filename, if the
//               filename exists.  On non-Windows platforms, this
//               returns the same thing as to_os_specific().
////////////////////////////////////////////////////////////////////
string Filename::
to_os_long_name() const {
  assert(!get_pattern());

#ifdef WIN32
  wstring os_specific = to_os_specific_w();
  
  wchar_t long_name[MAX_PATH + 1];
  DWORD l = GetLongPathNameW(os_specific.c_str(), long_name, MAX_PATH + 1);
  if (l == 0) {
    // Couldn't query the path name for some reason.  Probably the
    // file didn't exist.
    return to_os_specific();
  }
  assert(l < MAX_PATH + 1);

  TextEncoder encoder;
  encoder.set_encoding(get_filesystem_encoding());
  encoder.set_wtext(long_name);
  return encoder.get_text();

#else // WIN32
  return to_os_specific();
#endif // WIN32
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::exists
//       Access: Published
//  Description: Returns true if the filename exists on the disk,
//               false otherwise.  If the type is indicated to be
//               executable, this also tests that the file has execute
//               permission.
////////////////////////////////////////////////////////////////////
bool Filename::
exists() const {
#ifdef WIN32_VC
  wstring os_specific = get_filename_index(0).to_os_specific_w();

  bool exists = false;

  DWORD results = GetFileAttributesW(os_specific.c_str());
  if (results != -1) {
    exists = true;
  }

#else  // WIN32_VC
  string os_specific = get_filename_index(0).to_os_specific();

  struct stat this_buf;
  bool exists = false;

  if (stat(os_specific.c_str(), &this_buf) == 0) {
    exists = true;
  }
#endif

  return exists;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::is_regular_file
//       Access: Published
//  Description: Returns true if the filename exists and is the
//               name of a regular file (i.e. not a directory or
//               device), false otherwise.
////////////////////////////////////////////////////////////////////
bool Filename::
is_regular_file() const {
#ifdef WIN32_VC
  wstring os_specific = get_filename_index(0).to_os_specific_w();

  bool isreg = false;

  DWORD results = GetFileAttributesW(os_specific.c_str());
  if (results != -1) {
    isreg = ((results & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE)) == 0);
  }

#else  // WIN32_VC
  string os_specific = get_filename_index(0).to_os_specific();

  struct stat this_buf;
  bool isreg = false;

  if (stat(os_specific.c_str(), &this_buf) == 0) {
    isreg = S_ISREG(this_buf.st_mode);
  }
#endif

  return isreg;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::is_writable
//       Access: Published
//  Description: Returns true if the filename exists and is either a
//               directory or a regular file that can be written to,
//               or false otherwise.
////////////////////////////////////////////////////////////////////
bool Filename::
is_writable() const {
  bool writable = false;

#ifdef WIN32_VC
  wstring os_specific = get_filename_index(0).to_os_specific_w();

  DWORD results = GetFileAttributesW(os_specific.c_str());
  if (results != -1) {
    if ((results & FILE_ATTRIBUTE_DIRECTORY) != 0) {
      // Assume directories are writable.
      writable = true;
    } else if ((results & FILE_ATTRIBUTE_READONLY) == 0) {
      // Not read-only means writable.
      writable = true;
    }
  }
#else  // WIN32_VC
  string os_specific = get_filename_index(0).to_os_specific();

  if (access(os_specific.c_str(), W_OK) == 0) {
    writable = true;
  }
#endif

  return writable;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::is_directory
//       Access: Published
//  Description: Returns true if the filename exists and is a
//               directory name, false otherwise.
////////////////////////////////////////////////////////////////////
bool Filename::
is_directory() const {
#ifdef WIN32_VC
  wstring os_specific = get_filename_index(0).to_os_specific_w();

  bool isdir = false;

  DWORD results = GetFileAttributesW(os_specific.c_str());
  if (results != -1) {
    isdir = (results & FILE_ATTRIBUTE_DIRECTORY) != 0;
  }
#else  // WIN32_VC
  string os_specific = get_filename_index(0).to_os_specific();

  struct stat this_buf;
  bool isdir = false;

  if (stat(os_specific.c_str(), &this_buf) == 0) {
    isdir = S_ISDIR(this_buf.st_mode);
  }
#endif

  return isdir;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::is_executable
//       Access: Published
//  Description: Returns true if the filename exists and is
//               executable
////////////////////////////////////////////////////////////////////
bool Filename::
is_executable() const {
#ifdef WIN32_VC
  // no access() in windows, but to our advantage executables can only
  // end in .exe or .com
  string extension = get_extension();
  if (extension == "exe" || extension == "com") {
    return exists();
  }

#else /* WIN32_VC */
  string os_specific = get_filename_index(0).to_os_specific();
  if (access(os_specific.c_str(), X_OK) == 0) {
    return true;
  }
#endif /* WIN32_VC */

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::compare_timestamps
//       Access: Published
//  Description: Returns a number less than zero if the file named by
//               this object is older than the given file, zero if
//               they have the same timestamp, or greater than zero if
//               this one is newer.
//
//               If this_missing_is_old is true, it indicates that a
//               missing file will be treated as if it were older than
//               any other file; otherwise, a missing file will be
//               treated as if it were newer than any other file.
//               Similarly for other_missing_is_old.
////////////////////////////////////////////////////////////////////
int Filename::
compare_timestamps(const Filename &other,
                   bool this_missing_is_old,
                   bool other_missing_is_old) const {
#ifdef WIN32_VC
  wstring os_specific = get_filename_index(0).to_os_specific_w();
  wstring other_os_specific = other.get_filename_index(0).to_os_specific_w();

  struct _stat this_buf;
  bool this_exists = false;

  if (_wstat(os_specific.c_str(), &this_buf) == 0) {
    this_exists = true;
  }

  struct _stat other_buf;
  bool other_exists = false;

  if (_wstat(other_os_specific.c_str(), &other_buf) == 0) {
    other_exists = true;
  }
#else  // WIN32_VC
  string os_specific = get_filename_index(0).to_os_specific();
  string other_os_specific = other.get_filename_index(0).to_os_specific();

  struct stat this_buf;
  bool this_exists = false;

  if (stat(os_specific.c_str(), &this_buf) == 0) {
    this_exists = true;
  }

  struct stat other_buf;
  bool other_exists = false;

  if (stat(other_os_specific.c_str(), &other_buf) == 0) {
    other_exists = true;
  }
#endif

  if (this_exists && other_exists) {
    // Both files exist, return the honest time comparison.
    return (int)this_buf.st_mtime - (int)other_buf.st_mtime;

  } else if (!this_exists && !other_exists) {
    // Neither file exists.
    if (this_missing_is_old == other_missing_is_old) {
      // Both files are either "very old" or "very new".
      return 0;
    }
    if (this_missing_is_old) {
      // This file is "very old", the other is "very new".
      return -1;
    } else {
      // This file is "very new", the other is "very old".
      return 1;
    }

  } else if (!this_exists) {
    // This file doesn't, the other one does.
    return this_missing_is_old ? -1 : 1;

  }
  // !other_exists
  assert(!other_exists);
  
  // This file exists, the other one doesn't.
  return other_missing_is_old ? 1 : -1;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::get_timestamp
//       Access: Published
//  Description: Returns a time_t value that represents the time the
//               file was last modified, to within whatever precision
//               the operating system records this information (on a
//               Windows95 system, for instance, this may only be
//               accurate to within 2 seconds).
//
//               If the timestamp cannot be determined, either because
//               it is not supported by the operating system or
//               because there is some error (such as file not found),
//               returns 0.
////////////////////////////////////////////////////////////////////
time_t Filename::
get_timestamp() const {
#ifdef WIN32_VC
  wstring os_specific = get_filename_index(0).to_os_specific_w();

  struct _stat this_buf;

  if (_wstat(os_specific.c_str(), &this_buf) == 0) {
    return this_buf.st_mtime;
  }
#else  // WIN32_VC
  string os_specific = get_filename_index(0).to_os_specific();

  struct stat this_buf;

  if (stat(os_specific.c_str(), &this_buf) == 0) {
    return this_buf.st_mtime;
  }
#endif

  return 0;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::get_access_timestamp
//       Access: Published
//  Description: Returns a time_t value that represents the time the
//               file was last accessed, if this information is
//               available.  See also get_timestamp(), which returns
//               the last modification time.
////////////////////////////////////////////////////////////////////
time_t Filename::
get_access_timestamp() const {
#ifdef WIN32_VC
  wstring os_specific = get_filename_index(0).to_os_specific_w();

  struct _stat this_buf;

  if (_wstat(os_specific.c_str(), &this_buf) == 0) {
    return this_buf.st_atime;
  }
#else  // WIN32_VC
  string os_specific = get_filename_index(0).to_os_specific();

  struct stat this_buf;

  if (stat(os_specific.c_str(), &this_buf) == 0) {
    return this_buf.st_atime;
  }
#endif

  return 0;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::get_file_size
//       Access: Published
//  Description: Returns the size of the file in bytes, or 0 if there
//               is an error.
////////////////////////////////////////////////////////////////////
off_t Filename::
get_file_size() const {
#ifdef WIN32_VC
  wstring os_specific = get_filename_index(0).to_os_specific_w();

  struct _stat this_buf;

  if (_wstat(os_specific.c_str(), &this_buf) == 0) {
    return this_buf.st_size;
  }
#else  // WIN32_VC
  string os_specific = get_filename_index(0).to_os_specific();

  struct stat this_buf;

  if (stat(os_specific.c_str(), &this_buf) == 0) {
    return this_buf.st_size;
  }
#endif

  return 0;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::resolve_filename
//       Access: Published
//  Description: Searches the given search path for the filename.  If
//               it is found, updates the filename to the full
//               pathname found and returns true; otherwise, returns
//               false.
////////////////////////////////////////////////////////////////////
bool Filename::
resolve_filename(const DSearchPath &searchpath,
                 const string &default_extension) {
  Filename found;

  if (is_local()) {
    found = searchpath.find_file(*this);

    if (found.empty()) {
      // We didn't find it with the given extension; can we try the
      // default extension?
      if (get_extension().empty() && !default_extension.empty()) {
        Filename try_ext = *this;
        try_ext.set_extension(default_extension);
        found = searchpath.find_file(try_ext);
      }
    }
  } else {
    if (exists()) {
      // The full pathname exists.  Return true.
      return true;
    } else {
      // The full pathname doesn't exist with the given extension;
      // does it exist with the default extension?
      if (get_extension().empty() && !default_extension.empty()) {
        Filename try_ext = *this;
        try_ext.set_extension(default_extension);
        if (try_ext.exists()) {
          found = try_ext;
        }
      }
    }
  }

  if (!found.empty()) {
    (*this) = found;
    return true;
  }

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::make_relative_to
//       Access: Published
//  Description: Adjusts this filename, which must be a
//               fully-specified pathname beginning with a slash, to
//               make it a relative filename, relative to the
//               fully-specified directory indicated (which must also
//               begin with, and may or may not end with, a slash--a
//               terminating slash is ignored).
//
//               This only performs a string comparsion, so it may be
//               wise to call make_canonical() on both filenames
//               before calling make_relative_to().
//
//               If allow_backups is false, the filename will only be
//               adjusted to be made relative if it is already
//               somewhere within or below the indicated directory.
//               If allow_backups is true, it will be adjusted in all
//               cases, even if this requires putting a series of ../
//               characters before the filename--unless it would have
//               to back all the way up to the root.
//
//               Returns true if the file was adjusted, false if it
//               was not.
////////////////////////////////////////////////////////////////////
bool Filename::
make_relative_to(Filename directory, bool allow_backups) {
  if (_filename.empty() || directory.empty() ||
      _filename[0] != '/' || directory[0] != '/') {
    return false;
  }

  standardize();
  directory.standardize();

  if (directory == "/") {
    // Don't be silly.
    return false;
  }

  string rel_to_file = directory.get_fullpath() + "/.";

  size_t common = get_common_prefix(rel_to_file);
  if (common < 2) {
    // Oh, never mind.
    return false;
  }

  string result;
  int slashes = count_slashes(rel_to_file.substr(common));
  if (slashes > 0 && !allow_backups) {
    // Too bad; the file's not under the indicated directory.
    return false;
  }

  for (int i = 0; i < slashes; i++) {
    result += "../";
  }
  result += _filename.substr(common);
  (*this) = result;

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::find_on_searchpath
//       Access: Published
//  Description: Performs the reverse of the resolve_filename()
//               operation: assuming that the current filename is
//               fully-specified pathname (i.e. beginning with '/'),
//               look on the indicated search path for a directory
//               under which the file can be found.  When found,
//               adjust the Filename to be relative to the indicated
//               directory name.
//
//               Returns the index of the directory on the searchpath
//               at which the file was found, or -1 if it was not
//               found.
////////////////////////////////////////////////////////////////////
int Filename::
find_on_searchpath(const DSearchPath &searchpath) {
  if (_filename.empty() || _filename[0] != '/') {
    return -1;
  }

  int num_directories = searchpath.get_num_directories();
  for (int i = 0; i < num_directories; i++) {
    Filename directory = searchpath.get_directory(i);
    directory.make_absolute();
    if (make_relative_to(directory, false)) {
      return i;
    }
  }

  return -1;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::scan_directory
//       Access: Published
//  Description: Attempts to open the named filename as if it were a
//               directory and looks for the non-hidden files within
//               the directory.  Fills the given vector up with the
//               sorted list of filenames that are local to this
//               directory.
//
//               It is the user's responsibility to ensure that the
//               contents vector is empty before making this call;
//               otherwise, the new files will be appended to it.
//
//               Returns true on success, false if the directory could
//               not be read for some reason.
////////////////////////////////////////////////////////////////////
bool Filename::
scan_directory(vector_string &contents) const {
  assert(!get_pattern());

#if defined(WIN32_VC)
  // Use Windows' FindFirstFile() / FindNextFile() to walk through the
  // list of files in a directory.
  size_t orig_size = contents.size();

  wstring match;
  if (empty()) {
    match = L"*.*";
  } else {
    match = to_os_specific_w() + L"\\*.*";
  }
  WIN32_FIND_DATAW find_data;

  HANDLE handle = FindFirstFileW(match.c_str(), &find_data);
  if (handle == INVALID_HANDLE_VALUE) {
    if (GetLastError() == ERROR_NO_MORE_FILES) {
      // No matching files is not an error.
      return true;
    }
    return false;
  }

  TextEncoder encoder;
  encoder.set_encoding(get_filesystem_encoding());
  do {
    thread_consider_yield();
    wstring filename = find_data.cFileName;
    if (filename != L"." && filename != L"..") {
      encoder.set_wtext(filename);
      contents.push_back(encoder.get_text());
    }
  } while (FindNextFileW(handle, &find_data));

  bool scan_ok = (GetLastError() == ERROR_NO_MORE_FILES);
  FindClose(handle);

  sort(contents.begin() + orig_size, contents.end());
  return scan_ok;

#elif defined(PHAVE_DIRENT_H)
  // Use Posix's opendir() / readdir() to walk through the list of
  // files in a directory.
  size_t orig_size = contents.size();

  string dirname;
  if (empty()) {
    dirname = ".";
  } else {
    dirname = _filename;
  }
  DIR *root = opendir(dirname.c_str());
  if (root == (DIR *)NULL) {
    if (errno != ENOTDIR) {
      perror(dirname.c_str());
    }
    return false;
  }

  struct dirent *d;
  d = readdir(root);
  while (d != (struct dirent *)NULL) {
    thread_consider_yield();
    if (d->d_name[0] != '.') {
      contents.push_back(d->d_name);
    }
    d = readdir(root);
  }

  // It turns out to be a mistake to check the value of errno after
  // calling readdir(), since it might have been set to non-zero
  // during some internal operation of readdir(), even though there
  // wasn't really a problem with scanning the directory itself.
  /*
  if (errno != 0 && errno != ENOENT && errno != ENOTDIR) {
    cerr << "Error occurred while scanning directory " << dirname << "\n";
    perror(dirname.c_str());
    closedir(root);
    return false;
  }
  */
  closedir(root);

  sort(contents.begin() + orig_size, contents.end());
  return true;

#elif defined(PHAVE_GLOB_H)
  // It's hard to imagine a system that provides glob.h but does not
  // provide openddir() .. readdir(), but this code is leftover from a
  // time when there was an undetected bug in the above readdir()
  // loop, and it works, so we might as well keep it around for now.
  string dirname;
  if (empty()) {
    dirname = "*";
  } else if (_filename[_filename.length() - 1] == '/') {
    dirname = _filename + "*";
  } else {
    dirname = _filename + "/*";   /* comment to fix emacs syntax hilight */
  }

  glob_t globbuf;

  int r = glob(dirname.c_str(), GLOB_ERR, NULL, &globbuf);

  if (r != 0) {
    // Some error processing the match string.  If our version of
    // glob.h defines GLOB_NOMATCH, then we can differentiate an empty
    // return result from some other kind of error.
#ifdef GLOB_NOMATCH
    if (r != GLOB_NOMATCH) {
      perror(dirname.c_str());
      return false;
    }
#endif

    // Otherwise, all errors mean the same thing: no matches, but
    // otherwise no problem.
    return true;
  }

  size_t offset = dirname.size() - 1;

  for (int i = 0; globbuf.gl_pathv[i] != NULL; i++) {
    contents.push_back(globbuf.gl_pathv[i] + offset);
  }
  globfree(&globbuf);

  return true;
  
#else
  // Don't know how to scan directories!
  return false;
#endif
}

#ifdef HAVE_PYTHON
////////////////////////////////////////////////////////////////////
//     Function: Filename::scan_directory
//       Access: Published
//  Description: This variant on scan_directory returns a Python list
//               of strings on success, or None on failure.
////////////////////////////////////////////////////////////////////
PyObject *Filename::
scan_directory() const {
  vector_string contents;
  if (!scan_directory(contents)) {
    PyObject *result = Py_None;
    Py_INCREF(result);
    return result;
  }

  PyObject *result = PyList_New(contents.size());
  for (size_t i = 0; i < contents.size(); ++i) {
    const string &filename = contents[i];
    PyObject *str = PyString_FromStringAndSize(filename.data(), filename.size());
    PyList_SET_ITEM(result, i, str);
  }

  return result;
}
#endif  // HAVE_PYTHON

////////////////////////////////////////////////////////////////////
//     Function: Filename::open_read
//       Access: Published
//  Description: Opens the indicated ifstream for reading the file, if
//               possible.  Returns true if successful, false
//               otherwise.  This requires the setting of the
//               set_text()/set_binary() flags to open the file
//               appropriately as indicated; it is an error to call
//               open_read() without first calling one of set_text()
//               or set_binary().
////////////////////////////////////////////////////////////////////
bool Filename::
open_read(ifstream &stream) const {
  assert(!get_pattern());
  assert(is_binary_or_text());

  ios_openmode open_mode = ios::in;

#ifdef HAVE_IOS_BINARY
  // For some reason, some systems (like Irix) don't define
  // ios::binary.
  if (!is_text()) {
    open_mode |= ios::binary;
  }
#endif

  stream.clear();
#ifdef WIN32_VC
  wstring os_specific = to_os_specific_w();
  stream.open(os_specific.c_str(), open_mode);
#else
  string os_specific = to_os_specific();
  stream.open(os_specific.c_str(), open_mode);
#endif  // WIN32_VC

  return (!stream.fail());
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::open_write
//       Access: Published
//  Description: Opens the indicated ifstream for writing the file, if
//               possible.  Returns true if successful, false
//               otherwise.  This requires the setting of the
//               set_text()/set_binary() flags to open the file
//               appropriately as indicated; it is an error to call
//               open_read() without first calling one of set_text()
//               or set_binary().
//
//               If truncate is true, the file is truncated to zero
//               length upon opening it, if it already exists.
//               Otherwise, the file is kept at its original length.
////////////////////////////////////////////////////////////////////
bool Filename::
open_write(ofstream &stream, bool truncate) const {
  assert(!get_pattern());
  assert(is_binary_or_text());

  ios_openmode open_mode = ios::out;

  if (truncate) {
    open_mode |= ios::trunc;

  } else {
    // Some systems insist on having ios::in set to prevent the file
    // from being truncated when we open it.  Makes ios::trunc kind of
    // pointless, doesn't it?  On the other hand, setting ios::in also
    // seems to imply ios::nocreate (!), so we should only set this if
    // the file already exists.
    if (exists()) {
      open_mode |= ios::in;
    }
  }

#ifdef HAVE_IOS_BINARY
  // For some reason, some systems (like Irix) don't define
  // ios::binary.
  if (!is_text()) {
    open_mode |= ios::binary;
  }
#endif

  stream.clear();
#ifdef WIN32_VC
  wstring os_specific = to_os_specific_w();
  stream.open(os_specific.c_str(), open_mode);
#else
  string os_specific = to_os_specific();
#ifdef HAVE_OPEN_MASK
  stream.open(os_specific.c_str(), open_mode, 0666);
#else
  stream.open(os_specific.c_str(), open_mode);
#endif
#endif  // WIN32_VC

  return (!stream.fail());
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::open_append
//       Access: Published
//  Description: Opens the indicated ofstream for writing the file, if
//               possible.  Returns true if successful, false
//               otherwise.  This requires the setting of the
//               set_text()/set_binary() flags to open the file
//               appropriately as indicated; it is an error to call
//               open_read() without first calling one of set_text()
//               or set_binary().
////////////////////////////////////////////////////////////////////
bool Filename::
open_append(ofstream &stream) const {
  assert(!get_pattern());
  assert(is_binary_or_text());

  ios_openmode open_mode = ios::app;

#ifdef HAVE_IOS_BINARY
  // For some reason, some systems (like Irix) don't define
  // ios::binary.
  if (!is_text()) {
    open_mode |= ios::binary;
  }
#endif

  stream.clear();
#ifdef WIN32_VC
  wstring os_specific = to_os_specific_w();
  stream.open(os_specific.c_str(), open_mode);
#else
  string os_specific = to_os_specific();
#ifdef HAVE_OPEN_MASK
  stream.open(os_specific.c_str(), open_mode, 0666);
#else
  stream.open(os_specific.c_str(), open_mode);
#endif
#endif  // WIN32_VC

  return (!stream.fail());
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::open_read_write
//       Access: Published
//  Description: Opens the indicated fstream for read/write access to
//               the file, if possible.  Returns true if successful,
//               false otherwise.  This requires the setting of the
//               set_text()/set_binary() flags to open the file
//               appropriately as indicated; it is an error to call
//               open_read_write() without first calling one of
//               set_text() or set_binary().
////////////////////////////////////////////////////////////////////
bool Filename::
open_read_write(fstream &stream, bool truncate) const {
  assert(!get_pattern());
  assert(is_binary_or_text());

  ios_openmode open_mode = ios::out | ios::in;

  if (truncate) {
    open_mode |= ios::trunc;
  }

  // Since ios::in also seems to imply ios::nocreate (!), we must
  // guarantee the file already exists before we try to open it.
  if (!exists()) {
    touch();
  }

#ifdef HAVE_IOS_BINARY
  // For some reason, some systems (like Irix) don't define
  // ios::binary.
  if (!is_text()) {
    open_mode |= ios::binary;
  }
#endif

  stream.clear();
#ifdef WIN32_VC
  wstring os_specific = to_os_specific_w();
  stream.open(os_specific.c_str(), open_mode);
#else
  string os_specific = to_os_specific();
#ifdef HAVE_OPEN_MASK
  stream.open(os_specific.c_str(), open_mode, 0666);
#else
  stream.open(os_specific.c_str(), open_mode);
#endif
#endif  // WIN32_VC

  return (!stream.fail());
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::open_read_append
//       Access: Published
//  Description: Opens the indicated ifstream for reading and writing
//               the file, if possible; writes are appended to the end
//               of the file.  Returns true if successful, false
//               otherwise.  This requires the setting of the
//               set_text()/set_binary() flags to open the file
//               appropriately as indicated; it is an error to call
//               open_read() without first calling one of set_text()
//               or set_binary().
////////////////////////////////////////////////////////////////////
bool Filename::
open_read_append(fstream &stream) const {
  assert(!get_pattern());
  assert(is_binary_or_text());

  ios_openmode open_mode = ios::app | ios::in;

#ifdef HAVE_IOS_BINARY
  // For some reason, some systems (like Irix) don't define
  // ios::binary.
  if (!is_text()) {
    open_mode |= ios::binary;
  }
#endif

  stream.clear();
#ifdef WIN32_VC
  wstring os_specific = to_os_specific_w();
  stream.open(os_specific.c_str(), open_mode);
#else
  string os_specific = to_os_specific();
#ifdef HAVE_OPEN_MASK
  stream.open(os_specific.c_str(), open_mode, 0666);
#else
  stream.open(os_specific.c_str(), open_mode);
#endif
#endif  // WIN32_VC

  return (!stream.fail());
}

#ifdef USE_PANDAFILESTREAM
////////////////////////////////////////////////////////////////////
//     Function: Filename::open_read
//       Access: Published
//  Description: Opens the indicated pifstream for reading the file, if
//               possible.  Returns true if successful, false
//               otherwise.  This requires the setting of the
//               set_text()/set_binary() flags to open the file
//               appropriately as indicated; it is an error to call
//               open_read() without first calling one of set_text()
//               or set_binary().
////////////////////////////////////////////////////////////////////
bool Filename::
open_read(pifstream &stream) const {
  assert(!get_pattern());
  assert(is_binary_or_text());

  ios_openmode open_mode = ios::in;

#ifdef HAVE_IOS_BINARY
  // For some reason, some systems (like Irix) don't define
  // ios::binary.
  if (!is_text()) {
    open_mode |= ios::binary;
  }
#endif

  string os_specific = to_os_specific();
  stream.clear();
  stream.open(os_specific.c_str(), open_mode);
  return (!stream.fail());
}
#endif  // USE_PANDAFILESTREAM

#ifdef USE_PANDAFILESTREAM
////////////////////////////////////////////////////////////////////
//     Function: Filename::open_write
//       Access: Published
//  Description: Opens the indicated pifstream for writing the file, if
//               possible.  Returns true if successful, false
//               otherwise.  This requires the setting of the
//               set_text()/set_binary() flags to open the file
//               appropriately as indicated; it is an error to call
//               open_read() without first calling one of set_text()
//               or set_binary().
//
//               If truncate is true, the file is truncated to zero
//               length upon opening it, if it already exists.
//               Otherwise, the file is kept at its original length.
////////////////////////////////////////////////////////////////////
bool Filename::
open_write(pofstream &stream, bool truncate) const {
  assert(!get_pattern());
  assert(is_binary_or_text());

  ios_openmode open_mode = ios::out;

  if (truncate) {
    open_mode |= ios::trunc;

  } else {
    // Some systems insist on having ios::in set to prevent the file
    // from being truncated when we open it.  Makes ios::trunc kind of
    // pointless, doesn't it?  On the other hand, setting ios::in also
    // seems to imply ios::nocreate (!), so we should only set this if
    // the file already exists.
    if (exists()) {
      open_mode |= ios::in;
    }
  }

#ifdef HAVE_IOS_BINARY
  // For some reason, some systems (like Irix) don't define
  // ios::binary.
  if (!is_text()) {
    open_mode |= ios::binary;
  }
#endif

  stream.clear();
  string os_specific = to_os_specific();
#ifdef HAVE_OPEN_MASK
  stream.open(os_specific.c_str(), open_mode, 0666);
#else
  stream.open(os_specific.c_str(), open_mode);
#endif

  return (!stream.fail());
}
#endif  // USE_PANDAFILESTREAM

#ifdef USE_PANDAFILESTREAM
////////////////////////////////////////////////////////////////////
//     Function: Filename::open_append
//       Access: Published
//  Description: Opens the indicated pifstream for writing the file, if
//               possible.  Returns true if successful, false
//               otherwise.  This requires the setting of the
//               set_text()/set_binary() flags to open the file
//               appropriately as indicated; it is an error to call
//               open_read() without first calling one of set_text()
//               or set_binary().
////////////////////////////////////////////////////////////////////
bool Filename::
open_append(pofstream &stream) const {
  assert(!get_pattern());
  assert(is_binary_or_text());

  ios_openmode open_mode = ios::app;

#ifdef HAVE_IOS_BINARY
  // For some reason, some systems (like Irix) don't define
  // ios::binary.
  if (!is_text()) {
    open_mode |= ios::binary;
  }
#endif

  stream.clear();
  string os_specific = to_os_specific();
#ifdef HAVE_OPEN_MASK
  stream.open(os_specific.c_str(), open_mode, 0666);
#else
  stream.open(os_specific.c_str(), open_mode);
#endif

  return (!stream.fail());
}
#endif  // USE_PANDAFILESTREAM

#ifdef USE_PANDAFILESTREAM
////////////////////////////////////////////////////////////////////
//     Function: Filename::open_read_write
//       Access: Published
//  Description: Opens the indicated fstream for read/write access to
//               the file, if possible.  Returns true if successful,
//               false otherwise.  This requires the setting of the
//               set_text()/set_binary() flags to open the file
//               appropriately as indicated; it is an error to call
//               open_read_write() without first calling one of
//               set_text() or set_binary().
////////////////////////////////////////////////////////////////////
bool Filename::
open_read_write(pfstream &stream, bool truncate) const {
  assert(!get_pattern());
  assert(is_binary_or_text());

  ios_openmode open_mode = ios::out | ios::in;

  if (truncate) {
    open_mode |= ios::trunc;
  }

  // Since ios::in also seems to imply ios::nocreate (!), we must
  // guarantee the file already exists before we try to open it.
  if (!exists()) {
    touch();
  }

#ifdef HAVE_IOS_BINARY
  // For some reason, some systems (like Irix) don't define
  // ios::binary.
  if (!is_text()) {
    open_mode |= ios::binary;
  }
#endif

  stream.clear();
  string os_specific = to_os_specific();
#ifdef HAVE_OPEN_MASK
  stream.open(os_specific.c_str(), open_mode, 0666);
#else
  stream.open(os_specific.c_str(), open_mode);
#endif

  return (!stream.fail());
}
#endif  // USE_PANDAFILESTREAM

#ifdef USE_PANDAFILESTREAM
////////////////////////////////////////////////////////////////////
//     Function: Filename::open_read_append
//       Access: Published
//  Description: Opens the indicated pfstream for reading and writing
//               the file, if possible; writes are appended to the end
//               of the file.  Returns true if successful, false
//               otherwise.  This requires the setting of the
//               set_text()/set_binary() flags to open the file
//               appropriately as indicated; it is an error to call
//               open_read() without first calling one of set_text()
//               or set_binary().
////////////////////////////////////////////////////////////////////
bool Filename::
open_read_append(pfstream &stream) const {
  assert(!get_pattern());
  assert(is_binary_or_text());

  ios_openmode open_mode = ios::app | ios::in;

#ifdef HAVE_IOS_BINARY
  // For some reason, some systems (like Irix) don't define
  // ios::binary.
  if (!is_text()) {
    open_mode |= ios::binary;
  }
#endif

  stream.clear();
  string os_specific = to_os_specific();
#ifdef HAVE_OPEN_MASK
  stream.open(os_specific.c_str(), open_mode, 0666);
#else
  stream.open(os_specific.c_str(), open_mode);
#endif

  return (!stream.fail());
}
#endif  // USE_PANDAFILESTREAM

////////////////////////////////////////////////////////////////////
//     Function: Filename::touch
//       Access: Published
//  Description: Updates the modification time of the file to the
//               current time.  If the file does not already exist, it
//               will be created.  Returns true if successful, false
//               if there is an error.
////////////////////////////////////////////////////////////////////
bool Filename::
touch() const {
  assert(!get_pattern());
#ifdef WIN32_VC
  // In Windows, we have to use the Windows API to do this reliably.

  // First, guarantee the file exists (and also get its handle).
  wstring os_specific = to_os_specific_w();
  HANDLE fhandle;
  fhandle = CreateFileW(os_specific.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE,
                        NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  if (fhandle == INVALID_HANDLE_VALUE) {
    return false;
  }

  // Now update the file time and date.
  SYSTEMTIME sysnow;
  FILETIME ftnow;
  GetSystemTime(&sysnow);
  if (!SystemTimeToFileTime(&sysnow, &ftnow)) {
    CloseHandle(fhandle);
    return false;
  }
  
  if (!SetFileTime(fhandle, NULL, NULL, &ftnow)) {
    CloseHandle(fhandle);
    return false;
  }

  CloseHandle(fhandle);
  return true;

#elif defined(PHAVE_UTIME_H)
  // Most Unix systems can do this explicitly.

  string os_specific = to_os_specific();
#ifdef HAVE_CYGWIN
  // In the Cygwin case, it seems we need to be sure to use the
  // Cygwin-style name; some broken utime() implementation.  That's
  // almost the same thing as the original Panda-style name, but not
  // exactly, so we first convert the Panda name to a Windows name,
  // then convert it back to Cygwin, to ensure we get it exactly right
  // by Cygwin rules.
  {
    char result[4096] = "";
    cygwin_conv_to_posix_path(os_specific.c_str(), result);
    os_specific = result;
  }
#endif  // HAVE_CYGWIN
  int result = utime(os_specific.c_str(), NULL);
  if (result < 0) {
    if (errno == ENOENT) {
      // So the file doesn't already exist; create it.
      int fd = creat(os_specific.c_str(), 0666);
      if (fd < 0) {
        perror(os_specific.c_str());
        return false;
      }
      close(fd);
      return true;
    }
    perror(os_specific.c_str());
    return false;
  }
  return true;
#else  // WIN32, PHAVE_UTIME_H
  // Other systems may not have an explicit control over the
  // modification time.  For these systems, we'll just temporarily
  // open the file in append mode, then close it again (it gets closed
  // when the pfstream goes out of scope).
  pfstream file;
  return open_append(file);
#endif  // WIN32, PHAVE_UTIME_H
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::chdir
//       Access: Published
//  Description: Changes directory to the specified location.
//               Returns true if successful, false if failure.
////////////////////////////////////////////////////////////////////
bool Filename::
chdir() const {
#ifdef WIN32_VC
  wstring os_specific = to_os_specific_w();
  return (_wchdir(os_specific.c_str()) >= 0);
#else
  string os_specific = to_os_specific();
  return (::chdir(os_specific.c_str()) >= 0);
#endif  // WIN32_VC
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::unlink
//       Access: Published
//  Description: Permanently deletes the file associated with the
//               filename, if possible.  Returns true if successful,
//               false if failure (for instance, because the file did
//               not exist, or because permissions were inadequate).
////////////////////////////////////////////////////////////////////
bool Filename::
unlink() const {
  assert(!get_pattern());
#ifdef WIN32_VC
  // Windows can't delete a file if it's read-only.  Weird.
  wstring os_specific = to_os_specific_w();
  _wchmod(os_specific.c_str(), 0644);
  return (_wunlink(os_specific.c_str()) == 0);
#else
  string os_specific = to_os_specific();
  return (::unlink(os_specific.c_str()) == 0);
#endif  // WIN32_VC
}


////////////////////////////////////////////////////////////////////
//     Function: Filename::rename_to
//       Access: Published
//  Description: Renames the file to the indicated new filename.  If
//               the new filename is in a different directory, this
//               will perform a move.  Returns true if successful,
//               false on failure.
////////////////////////////////////////////////////////////////////
bool Filename::
rename_to(const Filename &other) const {
  assert(!get_pattern());

  if (*this == other) {
    // Trivial success.
    return true;
  }

#ifdef WIN32_VC
  wstring os_specific = to_os_specific_w();
  wstring other_os_specific = other.to_os_specific_w();

  if (_wrename(os_specific.c_str(),
               other_os_specific.c_str()) == 0) {
    // Successfully renamed.
    return true;
  }

  // The above might fail if we have tried to move a file to a
  // different filesystem.  In this case, copy the file into the same
  // directory first, and then rename it.
  string dirname = other.get_dirname();
  if (dirname.empty()) {
    dirname = ".";
  }
  Filename temp = Filename::temporary(dirname, "");
  temp.set_binary();
  if (!Filename::binary_filename(*this).copy_to(temp)) {
    return false;
  }

  wstring temp_os_specific = temp.to_os_specific_w();
  if (_wrename(temp_os_specific.c_str(),
               other_os_specific.c_str()) == 0) {
    // Successfully renamed.
    unlink();
    return true;
  }

  // Try unlinking the target first.
  other.unlink();
  if (_wrename(temp_os_specific.c_str(),
               other_os_specific.c_str()) == 0) {
    // Successfully renamed.
    unlink();
    return true;
  }
#else  // WIN32_VC
  string os_specific = to_os_specific();
  string other_os_specific = other.to_os_specific();

  if (rename(os_specific.c_str(),
             other_os_specific.c_str()) == 0) {
    // Successfully renamed.
    return true;
  }

  // The above might fail if we have tried to move a file to a
  // different filesystem.  In this case, copy the file into the same
  // directory first, and then rename it.
  string dirname = other.get_dirname();
  if (dirname.empty()) {
    dirname = ".";
  }
  Filename temp = Filename::temporary(dirname, "");
  temp.set_binary();
  if (!Filename::binary_filename(*this).copy_to(temp)) {
    return false;
  }

  string temp_os_specific = temp.to_os_specific();
  if (rename(temp_os_specific.c_str(),
             other_os_specific.c_str()) == 0) {
    // Successfully renamed.
    unlink();
    return true;
  }

  // Try unlinking the target first.
  other.unlink();
  if (rename(temp_os_specific.c_str(),
             other_os_specific.c_str()) == 0) {
    // Successfully renamed.
    unlink();
    return true;
  }
#endif  // WIN32_VC

  // Failed.
  temp.unlink();
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::copy_to
//       Access: Published
//  Description: Copies the file to the indicated new filename, by
//               reading the contents and writing it to the new file.
//               Returns true if successful, false on failure.  The
//               copy is always binary, regardless of the filename
//               settings.
////////////////////////////////////////////////////////////////////
bool Filename::
copy_to(const Filename &other) const {
  Filename this_filename = Filename::binary_filename(*this);
  pifstream in;
  if (!this_filename.open_read(in)) {
    return false;
  }

  Filename other_filename = Filename::binary_filename(other);
  pofstream out;
  if (!other_filename.open_write(out)) {
    return false;
  }
        
  static const size_t buffer_size = 4096;
  char buffer[buffer_size];
  
  in.read(buffer, buffer_size);
  size_t count = in.gcount();
  while (count != 0) {
    out.write(buffer, count);
    if (out.fail()) {
      other.unlink();
      return false;
    }
    in.read(buffer, buffer_size);
    count = in.gcount();
  }

  if (!in.eof()) {
    other.unlink();
    return false;
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::make_dir
//       Access: Published
//  Description: Creates all the directories in the path to the file
//               specified in the filename, except for the basename
//               itself.  This assumes that the Filename contains the
//               name of a file, not a directory name; it ensures that
//               the directory containing the file exists.
//
//               However, if the filename ends in a slash, it assumes
//               the Filename represents the name of a directory, and
//               creates all the paths.
////////////////////////////////////////////////////////////////////
bool Filename::
make_dir() const {
  assert(!get_pattern());
  if (empty()) {
    return false;
  }
  Filename path;
  if (_filename[_filename.length() - 1] == '/') {
    // The Filename ends in a slash; it represents a directory.
    path = (*this);

  } else {
    // The Filename does not end in a slash; it represents a file.
    path = get_dirname();
  }

  if (path.empty()) {
    return false;
  }
  string dirname = path.get_fullpath();

  // First, make sure everything up to the last path is known.  We
  // don't care too much if any of these fail; maybe they failed
  // because the directory was already there.
  size_t slash = dirname.find('/');
  while (slash != string::npos) {
    Filename component(dirname.substr(0, slash));
#ifdef WIN32_VC
    wstring os_specific = component.to_os_specific_w();
    _wmkdir(os_specific.c_str());
#else
    string os_specific = component.to_os_specific();
    ::mkdir(os_specific.c_str(), 0777);
#endif  // WIN32_VC
    slash = dirname.find('/', slash + 1);
  }

  // Now make the last one, and check the return value.
  Filename component(dirname);
#ifdef WIN32_VC
  wstring os_specific = component.to_os_specific_w();
  int result = _wmkdir(os_specific.c_str());
#else
  string os_specific = component.to_os_specific();
  int result = ::mkdir(os_specific.c_str(), 0777);
#endif  // WIN32_VC

  return (result == 0);
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::mkdir
//       Access: Published
//  Description: Creates the directory named by this filename.  Unlike
//               make_dir(), this assumes that the Filename contains
//               the directory name itself.  Also, parent directories
//               are not automatically created; this function fails if
//               any parent directory is missing.
////////////////////////////////////////////////////////////////////
bool Filename::
mkdir() const {
#ifdef WIN32_VC
  wstring os_specific = to_os_specific_w();
  int result = _wmkdir(os_specific.c_str());
#else
  string os_specific = to_os_specific();
  int result = ::mkdir(os_specific.c_str(), 0777);
#endif  // WIN32_VC

  return (result == 0);
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::rmdir
//       Access: Published
//  Description: The inverse of mkdir(): this removes the directory
//               named by this Filename, if it is in fact a directory.
////////////////////////////////////////////////////////////////////
bool Filename::
rmdir() const {
#ifdef WIN32_VC
  wstring os_specific = to_os_specific_w();

  int result = _wrmdir(os_specific.c_str());
  if (result != 0) {
    // Windows may require the directory to be writable before we can
    // remove it.
    _wchmod(os_specific.c_str(), 0777);
    result = _wrmdir(os_specific.c_str());
  }

#else  // WIN32_VC
  string os_specific = to_os_specific();
  int result = ::rmdir(os_specific.c_str());
#endif  // WIN32_VC

  return (result == 0);
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::get_hash
//       Access: Published
//  Description: Returns a hash code that attempts to be mostly unique
//               for different Filenames.
////////////////////////////////////////////////////////////////////
int Filename::
get_hash() const {
  static const int primes[] = {
      2,    3,    5,    7,   11,   13,   17,   19,   23,   29,
     31,   37,   41,   43,   47,   53,   59,   61,   67,   71,
     73,   79,   83,   89,   97,  101,  103,  107,  109,  113,
    127,  131,  137,  139,  149,  151,  157,  163,  167,  173,
    179,  181,  191,  193,  197,  199,  211,  223,  227,  229,
    233,  239,  241,  251,  257,  263,  269,  271,  277,  281,
    283,  293,  307,  311,  313,  317,  331,  337,  347,  349,
    353,  359,  367,  373,  379,  383,  389,  397,  401,  409,
    419,  421,  431,  433,  439,  443,  449,  457,  461,  463,
    467,  479,  487,  491,  499,  503,  509,  521,  523,  541,
    547,  557,  563,  569,  571,  577,  587,  593,  599,  601,
    607,  613,  617,  619,  631,  641,  643,  647,  653,  659,
    661,  673,  677,  683,  691,  701,  709,  719,  727,  733,
    739,  743,  751,  757,  761,  769,  773,  787,  797,  809,
    811,  821,  823,  827,  829,  839,  853,  857,  859,  863,
    877,  881,  883,  887,  907,  911,  919,  929,  937,  941,
    947,  953,  967,  971,  977,  983,  991,  997
  };
  static const size_t num_primes = sizeof(primes) / sizeof(int);

  int hash = 0;
  for (size_t i = 0; i < _filename.size(); ++i) {
    hash += (int)_filename[i] * primes[i % num_primes];
  }

  return hash;
}


////////////////////////////////////////////////////////////////////
//     Function: Filename::atomic_compare_and_exchange_contents
//       Access: Public
//  Description: Uses native file-locking mechanisms to atomically
//               replace the contents of a (small) file with the
//               specified contents, assuming it hasn't changed since
//               the last time the file was read.
//
//               This is designed to be similar to
//               AtomicAdjust::compare_and_exchange().  The method
//               writes new_contents to the file, completely replacing
//               the original contents; but only if the original
//               contents exactly matched old_contents.  If the file
//               was modified, returns true.  If, however, the
//               original contents of the file did not exactly match
//               old_contents, then the file is not modified, and
//               false is returned.  In either case, orig_contents is
//               filled with the original contents of the file.
//
//               If the file does not exist, it is implicitly created,
//               and its original contents are empty.
//
//               If an I/O error occurs on write, some of the file may
//               or may not have been written, and false is returned.
//
//               Expressed in pseudo-code, the logic is:
//
//                 orig_contents = file.read();
//                 if (orig_contents == old_contents) {
//                   file.write(new_contents);
//                   return true;
//                 }
//                 return false;
//
//               The operation is guaranteed to be atomic only if the
//               only operations that read and write to this file are
//               atomic_compare_and_exchange_contents() and
//               atomic_read_contents().
////////////////////////////////////////////////////////////////////
bool Filename::
atomic_compare_and_exchange_contents(string &orig_contents, 
                                     const string &old_contents, 
                                     const string &new_contents) const {
#ifdef WIN32_VC
  wstring os_specific = to_os_specific_w();
  HANDLE hfile = CreateFileW(os_specific.c_str(), GENERIC_READ | GENERIC_WRITE, 
                             0, NULL, OPEN_ALWAYS,
                             FILE_ATTRIBUTE_NORMAL, NULL);
  while (hfile == INVALID_HANDLE_VALUE) {
    DWORD error = GetLastError();
    if (error == ERROR_SHARING_VIOLATION) {
      // If the file is locked by another process, yield and try again.
      Sleep(0);
      hfile = CreateFileW(os_specific.c_str(), GENERIC_READ | GENERIC_WRITE, 
                          0, NULL, OPEN_ALWAYS,
                          FILE_ATTRIBUTE_NORMAL, NULL);
    } else {
      cerr << "Couldn't open file: " << os_specific 
           << ", error " << error << "\n";
      return false;
    }
  }

  if (hfile == INVALID_HANDLE_VALUE) {
    cerr << "Couldn't open file: " << os_specific 
         << ", error " << GetLastError() << "\n";
    return false;
  }

  static const size_t buf_size = 512;
  char buf[buf_size];

  orig_contents = string();

  DWORD bytes_read;
  if (!ReadFile(hfile, buf, buf_size, &bytes_read, NULL)) {
    cerr << "Error reading file: " << os_specific 
         << ", error " << GetLastError() << "\n";
    CloseHandle(hfile);
    return false;
  }
  while (bytes_read > 0) {
    orig_contents += string(buf, bytes_read);

    if (!ReadFile(hfile, buf, buf_size, &bytes_read, NULL)) {
      cerr << "Error reading file: " << os_specific 
           << ", error " << GetLastError() << "\n";
      CloseHandle(hfile);
      return false;
    }
  }

  bool match = false;
  if (orig_contents == old_contents) {
    match = true;
    SetFilePointer(hfile, 0, 0, FILE_BEGIN);
    DWORD bytes_written;
    if (!WriteFile(hfile, new_contents.data(), new_contents.size(),
                   &bytes_written, NULL)) {
      cerr << "Error writing file: " << os_specific 
           << ", error " << GetLastError() << "\n";
      CloseHandle(hfile);
      return false;
    }
  }

  CloseHandle(hfile);
  return match;

#else  // WIN32_VC
  string os_specific = to_os_specific();
  int fd = open(os_specific.c_str(), O_RDWR | O_CREAT, 0666);
  if (fd < 0) {
    perror(os_specific.c_str());
    return false;
  }

  static const size_t buf_size = 512;
  char buf[buf_size];

  orig_contents = string();

  if (lockf(fd, F_LOCK, 0) != 0) {
    perror(os_specific.c_str());
    close(fd);
    return false;
  }
    
  size_t bytes_read = read(fd, buf, buf_size);
  while (bytes_read > 0) {
    orig_contents += string(buf, bytes_read);
    bytes_read = read(fd, buf, buf_size);
  }

  if (bytes_read < 0) {
    perror(os_specific.c_str());
    close(fd);
    return false;
  }

  bool match = false;
  if (orig_contents == old_contents) {
    match = true;
    lseek(fd, 0, SEEK_SET);
    ssize_t bytes_written = write(fd, new_contents.data(), new_contents.size());
    if (bytes_written < 0) {
      perror(os_specific.c_str());
      close(fd);
      return false;
    }
  }

  if (close(fd) < 0) {
    perror(os_specific.c_str());
    return false;
  }
  
  return match;
#endif  // WIN32_VC
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::atomic_read_contents
//       Access: Public
//  Description: Uses native file-locking mechanisms to atomically
//               read the contents of a (small) file.  This is the
//               only way to read a file protected by
//               atomic_compare_and_exchange_contents(), and be
//               confident that the read operation is actually atomic
//               with respect to that method.
//
//               If the file does not exist, it is implicitly created,
//               and its contents are empty.
//
//               If the file is read successfully, fills its contents
//               in the indicated string, and returns true.  If the
//               file cannot be read, returns false.
////////////////////////////////////////////////////////////////////
bool Filename::
atomic_read_contents(string &contents) const {
#ifdef WIN32_VC
  wstring os_specific = to_os_specific_w();
  HANDLE hfile = CreateFileW(os_specific.c_str(), GENERIC_READ, 
                             FILE_SHARE_READ, NULL, OPEN_ALWAYS,
                             FILE_ATTRIBUTE_NORMAL, NULL);
  while (hfile == INVALID_HANDLE_VALUE) {
    DWORD error = GetLastError();
    if (error == ERROR_SHARING_VIOLATION) {
      // If the file is locked by another process, yield and try again.
      Sleep(0);
      hfile = CreateFileW(os_specific.c_str(), GENERIC_READ, 
                          FILE_SHARE_READ, NULL, OPEN_ALWAYS,
                          FILE_ATTRIBUTE_NORMAL, NULL);      
    } else {
      cerr << "Couldn't open file: " << os_specific 
           << ", error " << error << "\n";
      return false;
    }
  }

  static const size_t buf_size = 512;
  char buf[buf_size];

  contents = string();

  DWORD bytes_read;
  if (!ReadFile(hfile, buf, buf_size, &bytes_read, NULL)) {
    cerr << "Error reading file: " << os_specific 
         << ", error " << GetLastError() << "\n";
    CloseHandle(hfile);
    return false;
  }
  while (bytes_read > 0) {
    contents += string(buf, bytes_read);

    if (!ReadFile(hfile, buf, buf_size, &bytes_read, NULL)) {
      cerr << "Error reading file: " << os_specific 
           << ", error " << GetLastError() << "\n";
      CloseHandle(hfile);
      return false;
    }
  }

  CloseHandle(hfile);
  return true;

#else  // WIN32_VC
  string os_specific = to_os_specific();
  int fd = open(os_specific.c_str(), O_RDWR | O_CREAT, 0666);
  if (fd < 0) {
    perror(os_specific.c_str());
    return false;
  }

  static const size_t buf_size = 512;
  char buf[buf_size];

  contents = string();

  if (lockf(fd, F_LOCK, 0) != 0) {
    perror(os_specific.c_str());
    close(fd);
    return false;
  }
    
  size_t bytes_read = read(fd, buf, buf_size);
  while (bytes_read > 0) {
    contents += string(buf, bytes_read);
    bytes_read = read(fd, buf, buf_size);
  }

  if (bytes_read < 0) {
    perror(os_specific.c_str());
    close(fd);
    return false;
  }

  close(fd);
  return true;
#endif  // WIN32_VC
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::locate_basename
//       Access: Protected
//  Description: After the string has been reassigned, search for the
//               slash marking the beginning of the basename, and set
//               _dirname_end and _basename_start correctly.
////////////////////////////////////////////////////////////////////
void Filename::
locate_basename() {
  // Scan for the last slash, which marks the end of the directory
  // part.
  if (_filename.empty()) {
    _dirname_end = 0;
    _basename_start = 0;

  } else {

    string::size_type slash = _filename.rfind('/');
    if (slash != string::npos) {
      _basename_start = slash + 1;
      _dirname_end = _basename_start;

      // One exception: in case there are multiple slashes in a row,
      // we want to treat them as a single slash.  The directory
      // therefore actually ends at the first of these; back up a bit.
      while (_dirname_end > 0 && _filename[_dirname_end-1] == '/') {
        _dirname_end--;
      }

      // Another exception: if the dirname was nothing but slashes, it
      // was the root directory, or / itself.  In this case the dirname
      // does include the terminal slash (of course).
      if (_dirname_end == 0) {
        _dirname_end = 1;
      }

    } else {
      _dirname_end = 0;
      _basename_start = 0;
    }
  }

  // Now:

  // _dirname_end is the last slash character, or 0 if there are no
  // slash characters.

  // _basename_start is the character after the last slash character,
  // or 0 if there are no slash characters.
}


////////////////////////////////////////////////////////////////////
//     Function: Filename::locate_extension
//       Access: Protected
//  Description: Once the end of the directory prefix has been found,
//               and _dirname_end and _basename_start are set
//               correctly, search for the dot marking the beginning
//               of the extension, and set _basename_end and
//               _extension_start correctly.
////////////////////////////////////////////////////////////////////
void Filename::
locate_extension() {
  // Now scan for the last dot after that slash.
  if (_filename.empty()) {
    _basename_end = string::npos;
    _extension_start = string::npos;

  } else {
    string::size_type dot = _filename.length() - 1;

    while (dot+1 > _basename_start && _filename[dot] != '.') {
      --dot;
    }

    if (dot+1 > _basename_start) {
      _basename_end = dot;
      _extension_start = dot + 1;
    } else {
      _basename_end = string::npos;
      _extension_start = string::npos;
    }
  }

  // Now:

  // _basename_end is the last dot, or npos if there is no dot.

  // _extension_start is the character after the last dot, or npos if
  // there is no dot.
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::locate_hash
//       Access: Protected
//  Description: Identifies the part of the filename that contains the
//               sequence of hash marks, if any.
////////////////////////////////////////////////////////////////////
void Filename::
locate_hash() {
  if (!get_pattern()) {
    // If it's not a pattern-type filename, these are always set to
    // the end of the string.
    _hash_end = string::npos;
    _hash_start = string::npos;

  } else {
    // If it is a pattern-type filename, we must search for the hash
    // marks, which could be anywhere (but are usually toward the
    // end).
    _hash_end = _filename.rfind('#');
    if (_hash_end == string::npos) {
      _hash_end = string::npos;
      _hash_start = string::npos;
      
    } else {
      _hash_start = _hash_end;
      ++_hash_end;
      while (_hash_start > 0 && _filename[_hash_start - 1] == '#') {
        --_hash_start;
      }
    }
  }
}


////////////////////////////////////////////////////////////////////
//     Function: Filename::get_common_prefix
//       Access: Protected
//  Description: Returns the length of the longest common initial
//               substring of this string and the other one that ends
//               in a slash.  This is the lowest directory common to
//               both filenames.
////////////////////////////////////////////////////////////////////
size_t Filename::
get_common_prefix(const string &other) const {
  size_t len = 0;

  // First, get the length of the common initial substring.
  while (len < length() && len < other.length() &&
         _filename[len] == other[len]) {
    len++;
  }

  // Now insist that it ends in a slash.
  while (len > 0 && _filename[len-1] != '/') {
    len--;
  }

  return len;
}

////////////////////////////////////////////////////////////////////
//     Function: Filename::count_slashes
//       Access: Protected, Static
//  Description: Returns the number of non-consecutive slashes in the
//               indicated string, not counting a terminal slash.
////////////////////////////////////////////////////////////////////
int Filename::
count_slashes(const string &str) {
  int count = 0;
  string::const_iterator si;
  si = str.begin();

  while (si != str.end()) {
    if (*si == '/') {
      count++;

      // Skip consecutive slashes.
      ++si;
      while (*si == '/') {
        ++si;
      }
      if (si == str.end()) {
        // Oops, that was a terminal slash.  Don't count it.
        count--;
      }

    } else {
      ++si;
    }
  }

  return count;
}


////////////////////////////////////////////////////////////////////
//     Function: Filename::r_make_canonical
//       Access: Protected
//  Description: The recursive implementation of make_canonical().
////////////////////////////////////////////////////////////////////
bool Filename::
r_make_canonical(const Filename &cwd) {
  if (get_fullpath() == "/") {
    // If we reached the root, the whole path doesn't exist.  Report
    // failure.
    return false;
  }

#ifdef WIN32_VC
  // First, try to cd to the filename directly.
  wstring os_specific = to_os_specific_w();
  if (_wchdir(os_specific.c_str()) >= 0) {
    // That worked, save the full path string.
    (*this) = ExecutionEnvironment::get_cwd();

    // And restore the current working directory.
    wstring osdir = cwd.to_os_specific_w();
    if (_wchdir(osdir.c_str()) < 0) {
      cerr << "Error!  Cannot change back to " << osdir << "\n";
    }
    return true;
  }
#else  // WIN32_VC
  // First, try to cd to the filename directly.
  string os_specific = to_os_specific();
  if (::chdir(os_specific.c_str()) >= 0) {
    // That worked, save the full path string.
    (*this) = ExecutionEnvironment::get_cwd();

    // And restore the current working directory.
    string osdir = cwd.to_os_specific();
    if (::chdir(osdir.c_str()) < 0) {
      cerr << "Error!  Cannot change back to " << osdir << "\n";
    }
    return true;
  }
#endif  // WIN32_VC

  // That didn't work; maybe it's not a directory.  Recursively go to
  // the directory above.

  Filename dir(get_dirname());
  
  if (dir.empty()) {
    // No dirname means the file is in this directory.
    set_dirname(cwd);
    return true;
  }
  
  if (!dir.r_make_canonical(cwd)) {
    return false;
  }
  set_dirname(dir);
  return true;
}

