| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461 |
- // 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 "dSearchPath.h"
- #include "executionEnvironment.h"
- #include "vector_string.h"
- #include <stdio.h> // For rename() and tempnam()
- #include <time.h> // for clock() and time()
- #include <sys/stat.h>
- #include <algorithm>
- #ifdef HAVE_UTIME_H
- #include <utime.h>
- // We assume we have these too.
- #include <errno.h>
- #include <fcntl.h>
- #endif
- #ifdef HAVE_GLOB_H
- #include <glob.h>
- #ifndef GLOB_NOMATCH
- #define GLOB_NOMATCH -3
- #endif
- #endif
- #ifdef HAVE_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 HAVE_UNISTD_H
- #include <unistd.h>
- #endif
- #ifdef WIN32
- /* begin Win32-specific code */
- #ifdef WIN32_VC
- #include <direct.h>
- #include <windows.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().
- static const string hosts_prefix = "/hosts/";
- 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;
- static bool got_panda_root = false;
- if (!got_panda_root) {
- 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 += '\\';
- }
- got_panda_root = true;
- }
- 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();
- }
- }
- }
- ////////////////////////////////////////////////////////////////////
- // 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::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.
- ////////////////////////////////////////////////////////////////////
- Filename Filename::
- expand_from(const string &os_specific, Filename::Type type) {
- return from_os_specific(ExecutionEnvironment::expand_string(os_specific),
- type);
- }
- ////////////////////////////////////////////////////////////////////
- // 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) {
- if (dirname.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(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(dirname, Filename(prefix + hex_code + suffix));
- result.set_type(type);
- } while (result.exists());
- return result;
- }
- ////////////////////////////////////////////////////////////////////
- // 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 == ".") {
- // Ignore /./.
- } else if (component == ".." && !components.empty() &&
- !(components.back() == "..")) {
- // Back up.
- 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;
- }
- Filename cwd = ExecutionEnvironment::get_cwd();
- return r_make_canonical(cwd);
- }
- ////////////////////////////////////////////////////////////////////
- // 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
- string os_specific = to_os_specific();
- // 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.
-
- char short_name[MAX_PATH + 1];
- DWORD l = GetShortPathName(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);
-
- char long_name[MAX_PATH + 1];
- l = GetLongPathName(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(long_name);
- // Now sanity-check the true-case filename. If it's not the same as
- // the source file, except for case, reject it.
- string orig_filename = get_fullpath();
- string new_filename = true_case.get_fullpath();
- bool match = (orig_filename.length() == new_filename.length());
- for (size_t i = 0; i < orig_filename.length() && match; ++i) {
- match = (tolower(orig_filename[i]) == 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;
- }
- (*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;
- #endif // WIN32
- }
- ////////////////////////////////////////////////////////////////////
- // 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
- string os_specific = to_os_specific();
-
- char short_name[MAX_PATH + 1];
- DWORD l = GetShortPathName(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 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);
- return string(short_name);
- #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
- string os_specific = to_os_specific();
-
- char long_name[MAX_PATH + 1];
- DWORD l = GetLongPathName(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 os_specific;
- }
- assert(l < MAX_PATH + 1);
- return string(long_name);
- #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 {
- string os_specific = get_filename_index(0).to_os_specific();
- #ifdef WIN32_VC
- bool exists = false;
- DWORD results = GetFileAttributes(os_specific.c_str());
- if (results != -1) {
- exists = true;
- }
- #else // WIN32_VC
- 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 {
- string os_specific = get_filename_index(0).to_os_specific();
- #ifdef WIN32_VC
- bool isreg = false;
- DWORD results = GetFileAttributes(os_specific.c_str());
- if (results != -1) {
- isreg = ((results & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE)) == 0);
- }
- #else // WIN32_VC
- 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_directory
- // Access: Published
- // Description: Returns true if the filename exists and is a
- // directory name, false otherwise.
- ////////////////////////////////////////////////////////////////////
- bool Filename::
- is_directory() const {
- string os_specific = get_filename_index(0).to_os_specific();
- #ifdef WIN32_VC
- bool isdir = false;
- DWORD results = GetFileAttributes(os_specific.c_str());
- if (results != -1) {
- isdir = (results & FILE_ATTRIBUTE_DIRECTORY) != 0;
- }
- #else // WIN32_VC
- 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 {
- string os_specific = get_filename_index(0).to_os_specific();
- string other_os_specific = other.get_filename_index(0).to_os_specific();
- #ifdef WIN32_VC
- 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;
- }
- #else // WIN32_VC
- 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 {
- string os_specific = get_filename_index(0).to_os_specific();
- #ifdef WIN32_VC
- struct _stat this_buf;
- if (_stat(os_specific.c_str(), &this_buf) == 0) {
- return this_buf.st_mtime;
- }
- #else // WIN32_VC
- 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 {
- string os_specific = get_filename_index(0).to_os_specific();
- #ifdef WIN32_VC
- struct _stat this_buf;
- if (_stat(os_specific.c_str(), &this_buf) == 0) {
- return this_buf.st_atime;
- }
- #else // WIN32_VC
- 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 {
- string os_specific = get_filename_index(0).to_os_specific();
- #ifdef WIN32_VC
- struct _stat this_buf;
- if (_stat(os_specific.c_str(), &this_buf) == 0) {
- return this_buf.st_size;
- }
- #else // WIN32_VC
- 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) {
- string 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();
- string match;
- if (empty()) {
- match = "*.*";
- } else {
- match = to_os_specific() + "\\*.*";
- }
- WIN32_FIND_DATA find_data;
- HANDLE handle = FindFirstFile(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;
- }
- do {
- string filename = find_data.cFileName;
- if (filename != "." && filename != "..") {
- contents.push_back(filename);
- }
- } while (FindNextFile(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(HAVE_DIRENT_H)
- // Use Posix's opendir() / readir() 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) {
- perror(dirname.c_str());
- return false;
- }
- struct dirent *d;
- d = readdir(root);
- while (d != (struct dirent *)NULL) {
- 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(HAVE_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
- }
- ////////////////////////////////////////////////////////////////////
- // 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_text() || is_binary());
- 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());
- }
- ////////////////////////////////////////////////////////////////////
- // 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_text() || is_binary());
- 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());
- }
- ////////////////////////////////////////////////////////////////////
- // Function: Filename::open_append
- // 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().
- ////////////////////////////////////////////////////////////////////
- bool Filename::
- open_append(ofstream &stream) const {
- assert(!get_pattern());
- assert(is_text() || is_binary());
- 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());
- }
- ////////////////////////////////////////////////////////////////////
- // 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) const {
- assert(!get_pattern());
- assert(is_text() || is_binary());
- ios_openmode open_mode = ios::out | ios::in;
- // 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());
- }
- ////////////////////////////////////////////////////////////////////
- // 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).
- string os_specific = to_os_specific();
- HANDLE fhandle;
- fhandle = CreateFile(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(HAVE_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, HAVE_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 ofstream goes out of scope).
- ofstream file;
- return open_append(file);
- #endif // WIN32, HAVE_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 {
- Filename os_specific = to_os_specific();
- return (::chdir(os_specific.c_str()) >= 0);
- }
- ////////////////////////////////////////////////////////////////////
- // 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());
- string os_specific = to_os_specific();
- return (::unlink(os_specific.c_str()) == 0);
- }
- ////////////////////////////////////////////////////////////////////
- // 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 if failure.
- ////////////////////////////////////////////////////////////////////
- bool Filename::
- rename_to(const Filename &other) const {
- assert(!get_pattern());
- string os_specific = to_os_specific();
- string other_os_specific = other.to_os_specific();
- return (rename(os_specific.c_str(),
- other_os_specific.c_str()) == 0);
- }
- ////////////////////////////////////////////////////////////////////
- // 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));
- string os_specific = component.to_os_specific();
- #ifndef WIN32_VC
- mkdir(os_specific.c_str(), 0777);
- #else
- mkdir(os_specific.c_str());
- #endif
- slash = dirname.find('/', slash + 1);
- }
- // Now make the last one, and check the return value.
- Filename component(dirname);
- string os_specific = component.to_os_specific();
- #ifndef WIN32_VC
- int result = mkdir(os_specific.c_str(), 0777);
- #else
- int result = mkdir(os_specific.c_str());
- #endif
- return (result == 0);
- }
- ////////////////////////////////////////////////////////////////////
- // 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
- string os_specific = to_os_specific();
- HANDLE hfile = CreateFile(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 = CreateFile(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();
- lockf(fd, F_LOCK, 0);
-
- 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
- string os_specific = to_os_specific();
- HANDLE hfile = CreateFile(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 = CreateFile(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_RDONLY | 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();
- lockf(fd, F_LOCK, 0);
-
- 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;
- }
- // 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 " << cwd << "\n";
- }
- return true;
- }
- // 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;
- }
|