Panda3D
Loading...
Searching...
No Matches
filename.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file filename.cxx
10 * @author drose
11 * @date 1999-01-18
12 */
13
14#include "filename.h"
15#include "filename_assist.h"
16#include "dSearchPath.h"
18#include "vector_string.h"
19#include "atomicAdjust.h"
20
21#include <stdio.h> // For rename() and tempnam()
22#include <time.h> // for clock() and time()
23#include <sys/stat.h>
24#include <algorithm>
25
26#ifdef PHAVE_UTIME_H
27#include <utime.h>
28
29// We assume we have these too.
30#include <errno.h>
31#include <fcntl.h>
32#endif
33
34#ifdef PHAVE_GLOB_H
35 #include <glob.h>
36 #ifndef GLOB_NOMATCH
37 #define GLOB_NOMATCH -3
38 #endif
39#endif
40
41#ifdef PHAVE_DIRENT_H
42#include <dirent.h>
43#endif
44
45// It's true that dtoolbase.h includes this already, but we include this again
46// in case we are building this file within ppremake.
47#ifdef PHAVE_UNISTD_H
48#include <unistd.h>
49#endif
50
51#if defined(__ANDROID__) && !defined(PHAVE_LOCKF)
52// Needed for flock.
53#include <sys/file.h>
54#endif
55
56using std::cerr;
57using std::ios;
58using std::string;
59using std::wstring;
60
61TextEncoder::Encoding Filename::_filesystem_encoding = TextEncoder::E_utf8;
62
63TVOLATILE AtomicAdjust::Pointer Filename::_home_directory;
64TVOLATILE AtomicAdjust::Pointer Filename::_temp_directory;
65TVOLATILE AtomicAdjust::Pointer Filename::_user_appdata_directory;
66TVOLATILE AtomicAdjust::Pointer Filename::_common_appdata_directory;
67TypeHandle Filename::_type_handle;
68
69#ifdef ANDROID
70string Filename::_internal_data_dir;
71#endif
72
73#ifdef WIN32
74/* begin Win32-specific code */
75
76#ifdef WIN32_VC
77#include <direct.h>
78#include <windows.h>
79#include <shlobj.h>
80#include <io.h>
81#endif
82
83// The MSVC 6.0 Win32 SDK lacks the following definitions, so we define them
84// here for compatibility.
85#ifndef FILE_ATTRIBUTE_DEVICE
86#define FILE_ATTRIBUTE_DEVICE 0x00000040
87#endif
88
89// We might have been linked with the Cygwin dll. This is ideal if it is
90// available, because it allows Panda to access all the Cygwin mount
91// definitions if they are in use. If the Cygwin dll is not available, we
92// fall back to our own convention for converting pathnames.
93#ifdef HAVE_CYGWIN
94extern "C" void cygwin_conv_to_win32_path(const char *path, char *win32);
95extern "C" void cygwin_conv_to_posix_path(const char *path, char *posix);
96#endif
97
98/*
99 * Windows uses the convention \\hostname\path\to\file to represent a pathname
100 * to a file on another share. This redefines a pathname to be something more
101 * complicated than a sequence of directory names separated by slashes. The
102 * Unix convention to represent the same thing is, like everything else, to
103 * graft the reference to the remote hostname into the one global filesystem,
104 * with something like hostshostnamepathtofile. We observe the Unix
105 * convention for internal names used in Panda; this makes operations like
106 * Filename::get_dirname() simpler and more internally consistent.
107 */
108
109/*
110 * This string hard-defines the prefix that we use internally to indicate that
111 * the next directory component name should be treated as a hostname. It
112 * might be nice to use a ConfigVariable for this, except that we haven't
113 * defined ConfigVariable by this point (and indeed we can't, since we need to
114 * have a Filename class already created in order to read the first config
115 * file). Windows purists might be tempted to define this to a double slash
116 * so that internal Panda filenames more closely resemble their Windows
117 * counterparts. That might actually work, but it will cause problems with
118 * Filename::standardize().
119 */
120
121// We use const char * instead of string to avoid static-init ordering issues.
122static const char *hosts_prefix = "/hosts/";
123static size_t hosts_prefix_length = 7;
124
125static string
126front_to_back_slash(const string &str) {
127 string result = str;
128 string::iterator si;
129 for (si = result.begin(); si != result.end(); ++si) {
130 if ((*si) == '/') {
131 (*si) = '\\';
132 }
133 }
134
135 return result;
136}
137
138static string
139back_to_front_slash(const string &str) {
140 string result = str;
141 string::iterator si;
142 for (si = result.begin(); si != result.end(); ++si) {
143 if ((*si) == '\\') {
144 (*si) = '/';
145 }
146 }
147
148 return result;
149}
150
151static const string &
152get_panda_root() {
153 static string *panda_root = nullptr;
154
155 if (panda_root == nullptr) {
156 panda_root = new string;
157 const char *envvar = getenv("PANDA_ROOT");
158 if (envvar != nullptr) {
159 (*panda_root) = front_to_back_slash(envvar);
160 }
161
162 // Ensure the string ends in a backslash. If PANDA_ROOT is empty or
163 // undefined, this function must return a single backslash--not an empty
164 // string--since this prefix is used to replace a leading slash in
165 // Filename::to_os_specific().
166 if ((*panda_root).empty() || (*panda_root)[(*panda_root).length() - 1] != '\\') {
167 (*panda_root) += '\\';
168 }
169 }
170
171 return (*panda_root);
172}
173
174static string
175convert_pathname(const string &unix_style_pathname) {
176 if (unix_style_pathname.empty()) {
177 return string();
178 }
179
180 // To convert from a Unix-style pathname to a Windows-style pathname, we
181 // need to change all forward slashes to backslashes. We might need to add
182 // a prefix as well, since Windows pathnames typically begin with a drive
183 // letter.
184
185 // By convention, if the top directory name consists of just one letter, we
186 // treat that as a drive letter and map the rest of the filename
187 // accordingly. On the other hand, if the top directory name consists of
188 // more than one letter, we assume this is a file within some predefined
189 // tree whose root is given by the environment variable "PANDA_ROOT", or if
190 // that is not defined, "CYGWIN_ROOT" (for backward compatibility).
191 string windows_pathname;
192
193 if (unix_style_pathname[0] != '/') {
194 // It doesn't even start from the root, so we don't have to do anything
195 // fancy--relative pathnames are the same in Windows as in Unix, except
196 // for the direction of the slashes.
197 windows_pathname = front_to_back_slash(unix_style_pathname);
198
199 } else if (unix_style_pathname.length() >= 2 &&
200 isalpha(unix_style_pathname[1]) &&
201 (unix_style_pathname.length() == 2 || unix_style_pathname[2] == '/')) {
202 // This pathname begins with a slash and a single letter. That must be
203 // the drive letter.
204
205 string remainder = unix_style_pathname.substr(2);
206 if (remainder.empty()) {
207 // There's a difference between "C:" and "C:".
208 remainder = "/";
209 }
210 remainder = front_to_back_slash(remainder);
211
212 // We have to cast the result of toupper() to (char) to help some
213 // compilers (e.g. Cygwin's gcc 2.95.3) happy; so that they do not
214 // confuse this string constructor with one that takes two iterators.
215 windows_pathname =
216 string(1, (char)toupper(unix_style_pathname[1])) + ":" + remainder;
217
218 } else if (unix_style_pathname.length() > hosts_prefix_length &&
219 unix_style_pathname.substr(0, hosts_prefix_length) == hosts_prefix) {
220 // A filename like hostsfooby gets turned into \\fooby.
221 windows_pathname = "\\\\" + front_to_back_slash(unix_style_pathname.substr(hosts_prefix_length));
222
223 } else {
224 // It starts with a slash, but the first part is not a single letter.
225
226#ifdef HAVE_CYGWIN
227 // Use Cygwin to convert it if possible.
228 char result[4096] = "";
229 cygwin_conv_to_win32_path(unix_style_pathname.c_str(), result);
230 windows_pathname = result;
231
232#else // HAVE_CYGWIN
233 // Without Cygwin, just prefix $PANDA_ROOT.
234 windows_pathname = get_panda_root();
235 windows_pathname += front_to_back_slash(unix_style_pathname.substr(1));
236
237#endif // HAVE_CYGWIN
238 }
239
240 return windows_pathname;
241}
242
243static string
244convert_dso_pathname(const string &unix_style_pathname) {
245 // If the extension is .so, change it to .dll.
246 size_t dot = unix_style_pathname.rfind('.');
247 if (dot == string::npos ||
248 unix_style_pathname.find('/', dot) != string::npos) {
249 // No filename extension.
250 return convert_pathname(unix_style_pathname);
251 }
252 if (unix_style_pathname.substr(dot) != ".so") {
253 // Some other extension.
254 return convert_pathname(unix_style_pathname);
255 }
256
257 string dll_basename = unix_style_pathname.substr(0, dot);
258
259#ifdef _DEBUG
260 // If we're building a debug version, all the dso files we link in must be
261 // named file_d.dll. This does prohibit us from linking in external dso
262 // files, generated outside of the Panda build system, that don't follow
263 // this _d convention. Maybe we need a separate
264 // convert_system_dso_pathname() function.
265
266 // We can't simply check to see if the file exists, because this might not
267 // be a full path to the dso filename--it might be somewhere on the
268 // LD_LIBRARY_PATH, or on PATH, or any of a number of nutty places.
269
270 return convert_pathname(dll_basename + "_d.dll");
271#else
272 return convert_pathname(dll_basename + ".dll");
273#endif
274}
275
276static string
277convert_executable_pathname(const string &unix_style_pathname) {
278 // If the extension is not .exe, append .exe.
279 size_t dot = unix_style_pathname.rfind('.');
280 if (dot == string::npos ||
281 unix_style_pathname.find('/', dot) != string::npos) {
282 // No filename extension.
283 return convert_pathname(unix_style_pathname + ".exe");
284 }
285 if (unix_style_pathname.substr(dot) != ".exe") {
286 // Some other extension.
287 return convert_pathname(unix_style_pathname + ".exe");
288 }
289
290 return convert_pathname(unix_style_pathname);
291}
292#endif //WIN32
293
294/**
295 * This constructor composes the filename out of a directory part and a
296 * basename part. It will insert an intervening '/' if necessary.
297 */
298Filename::
299Filename(const Filename &dirname, const Filename &basename) {
300 if (dirname.empty()) {
301 (*this) = basename;
302 } else {
303 _flags = basename._flags;
304 string dirpath = dirname.get_fullpath();
305 if (dirpath[dirpath.length() - 1] == '/') {
306 (*this) = dirpath + basename.get_fullpath();
307 } else {
308 (*this) = dirpath + "/" + basename.get_fullpath();
309 }
310 }
311}
312
313/**
314 * This named constructor returns a Panda-style filename (that is, using
315 * forward slashes, and no drive letter) based on the supplied filename string
316 * that describes a filename in the local system conventions (for instance, on
317 * Windows, it may use backslashes or begin with a drive letter and a colon).
318 *
319 * Use this function to create a Filename from an externally-given filename
320 * string. Use to_os_specific() again later to reconvert it back to the local
321 * operating system's conventions.
322 *
323 * This function will do the right thing even if the filename is partially
324 * local conventions and partially Panda conventions; e.g. some backslashes
325 * and some forward slashes.
326 */
327Filename Filename::
328from_os_specific(const string &os_specific, Filename::Type type) {
329#ifdef WIN32
330 string result = back_to_front_slash(os_specific);
331 const string &panda_root = get_panda_root();
332
333 // If the initial prefix is the same as panda_root, remove it.
334 if (!panda_root.empty() && panda_root != string("\\") &&
335 panda_root.length() < result.length()) {
336 bool matches = true;
337 size_t p;
338 for (p = 0; p < panda_root.length() && matches; ++p) {
339 char c = tolower(panda_root[p]);
340 if (c == '\\') {
341 c = '/';
342 }
343 matches = (c == tolower(result[p]));
344 }
345
346 if (matches) {
347 // The initial prefix matches! Replace the initial bit with a leading
348 // slash.
349 result = result.substr(panda_root.length());
350 assert(!result.empty());
351 if (result[0] != '/') {
352 result = '/' + result;
353 }
354 Filename filename(result);
355 filename.set_type(type);
356 return filename;
357 }
358 }
359
360 // All right, the initial prefix was not under panda_root. But maybe it
361 // begins with a drive letter.
362 if (result.size() >= 3 && isalpha(result[0]) &&
363 result[1] == ':' && result[2] == '/') {
364 result[1] = tolower(result[0]);
365 result[0] = '/';
366
367 // If there's *just* a slash following the drive letter, go ahead and trim
368 // it.
369 if (result.size() == 3) {
370 result = result.substr(0, 2);
371 }
372
373 } else if (result.substr(0, 2) == "//") {
374 // If the initial prefix is a double slash, convert it to hosts.
375 result = hosts_prefix + result.substr(2);
376 }
377
378 Filename filename(result);
379 filename.set_type(type);
380 return filename;
381#else // WIN32
382 // Generic Unix-style filenames--no conversion necessary.
383 Filename filename(os_specific);
384 filename.set_type(type);
385 return filename;
386#endif // WIN32
387}
388
389/**
390 * The wide-string variant of from_os_specific(). Returns a new Filename,
391 * converted from an os-specific wide-character string.
392 */
393Filename Filename::
394from_os_specific_w(const wstring &os_specific, Filename::Type type) {
395 TextEncoder encoder;
397 encoder.set_wtext(os_specific);
398 return from_os_specific(encoder.get_text(), type);
399}
400
401/**
402 * Returns the same thing as from_os_specific(), but embedded environment
403 * variable references (e.g. "$DMODELS/foo.txt") are expanded out. It also
404 * automatically elevates the file to its true case if needed.
405 */
406Filename Filename::
407expand_from(const string &os_specific, Filename::Type type) {
409 type);
410 file.make_true_case();
411 return file;
412}
413
414/**
415 * Generates a temporary filename within the indicated directory, using the
416 * indicated prefix. If the directory is empty, a system-defined directory is
417 * chosen instead.
418 *
419 * The generated filename did not exist when the Filename checked, but since
420 * it does not specifically create the file, it is possible that another
421 * process could simultaneously create a file by the same name.
422 */
423Filename Filename::
424temporary(const string &dirname, const string &prefix, const string &suffix,
425 Type type) {
426 Filename fdirname = dirname;
427#if defined(_WIN32) || defined(ANDROID)
428 // The Windows tempnam() function doesn't do a good job of choosing a
429 // temporary directory. Choose one ourselves.
430 if (fdirname.empty()) {
431 fdirname = Filename::get_temp_directory();
432 }
433#endif
434
435 if (fdirname.empty()) {
436 // If we are not given a dirname, use the system tempnam() function to
437 // create a system-defined temporary filename.
438 char *name = tempnam(nullptr, prefix.c_str());
440 free(name);
441 result.set_type(type);
442 return result;
443 }
444
445 // If we *are* given a dirname, then use our own algorithm to make up a
446 // filename within that dirname. We do that because the system tempnam()
447 // (for instance, under Windows) may ignore the dirname.
448
449 Filename result;
450 do {
451 // We take the time of day and multiply it by the process time. This will
452 // give us a very large number, of which we take the bottom 24 bits and
453 // generate a 6-character hex code.
454 int hash = (clock() * time(nullptr)) & 0xffffff;
455 char hex_code[10];
456#ifdef _WIN32
457 sprintf_s(hex_code, 10, "%06x", hash);
458#else
459 snprintf(hex_code, 10, "%06x", hash);
460#endif
461 result = Filename(fdirname, Filename(prefix + hex_code + suffix));
462 result.set_type(type);
463 } while (result.exists());
464
465 return result;
466}
467
468/**
469 * Returns a path to the user's home directory, if such a thing makes sense in
470 * the current OS, or to the nearest equivalent. This may or may not be
471 * directly writable by the application.
472 */
473const Filename &Filename::
474get_home_directory() {
475 if (AtomicAdjust::get_ptr(_home_directory) == nullptr) {
476 Filename home_directory;
477
478 // In all environments, check $HOME first.
479 char *home = getenv("HOME");
480 if (home != nullptr) {
481 Filename dirname = from_os_specific(home);
482 if (dirname.is_directory()) {
483 if (dirname.make_canonical()) {
484 home_directory = dirname;
485 }
486 }
487 }
488
489 if (home_directory.empty()) {
490#ifdef WIN32
491 wchar_t buffer[MAX_PATH];
492
493 // On Windows, fall back to the "My Documents" folder.
494 if (SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_PERSONAL, true)) {
495 Filename dirname = from_os_specific_w(buffer);
496 if (dirname.is_directory()) {
497 if (dirname.make_canonical()) {
498 home_directory = dirname;
499 }
500 }
501 }
502
503#elif defined(ANDROID)
504 // Temporary hack.
505 home_directory = "/data/data/org.panda3d.sdk";
506
507#elif defined(IS_OSX)
508 home_directory = get_osx_home_directory();
509
510#elif defined(ANDROID)
511 home_directory = _internal_data_dir;
512
513#else
514 // Posix case: check etcpasswd?
515
516#endif // WIN32
517 }
518
519 if (home_directory.empty()) {
520 // Fallback case.
521 home_directory = ExecutionEnvironment::get_cwd();
522 }
523
524 Filename *newdir = new Filename(home_directory);
525 if (AtomicAdjust::compare_and_exchange_ptr(_home_directory, nullptr, newdir) != nullptr) {
526 // Didn't store it. Must have been stored by someone else.
527 assert(_home_directory != nullptr);
528 delete newdir;
529 }
530 }
531
532 return (*(Filename *)_home_directory);
533}
534
535/**
536 * Returns a path to a system-defined temporary directory.
537 */
538const Filename &Filename::
539get_temp_directory() {
540 if (AtomicAdjust::get_ptr(_temp_directory) == nullptr) {
541 Filename temp_directory;
542
543#ifdef WIN32
544 static const size_t buffer_size = 4096;
545 wchar_t buffer[buffer_size];
546 if (GetTempPathW(buffer_size, buffer) != 0) {
547 Filename dirname = from_os_specific_w(buffer);
548 if (dirname.is_directory()) {
549 if (dirname.make_canonical()) {
550 temp_directory = dirname;
551 }
552 }
553 }
554
555#elif defined(IS_OSX)
556 temp_directory = get_osx_temp_directory();
557
558#elif defined(ANDROID)
559 temp_directory.set_dirname(_internal_data_dir);
560 temp_directory.set_basename("cache");
561
562#else
563 // Posix case.
564 temp_directory = "/tmp";
565#endif // WIN32
566
567 if (temp_directory.empty()) {
568 // Fallback case.
569 temp_directory = ExecutionEnvironment::get_cwd();
570 }
571
572 Filename *newdir = new Filename(temp_directory);
573 if (AtomicAdjust::compare_and_exchange_ptr(_temp_directory, nullptr, newdir) != nullptr) {
574 // Didn't store it. Must have been stored by someone else.
575 assert(_temp_directory != nullptr);
576 delete newdir;
577 }
578 }
579
580 return (*(Filename *)_temp_directory);
581}
582
583/**
584 * Returns a path to a system-defined directory appropriate for creating a
585 * subdirectory for storing application-specific data, specific to the current
586 * user.
587 */
588const Filename &Filename::
589get_user_appdata_directory() {
590 if (AtomicAdjust::get_ptr(_user_appdata_directory) == nullptr) {
591 Filename user_appdata_directory;
592
593#ifdef WIN32
594 wchar_t buffer[MAX_PATH];
595
596 if (SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_LOCAL_APPDATA, true)) {
597 Filename dirname = from_os_specific_w(buffer);
598 if (dirname.is_directory()) {
599 if (dirname.make_canonical()) {
600 user_appdata_directory = dirname;
601 }
602 }
603 }
604
605#elif defined(IS_OSX)
606 user_appdata_directory = get_osx_user_appdata_directory();
607
608#elif defined(ANDROID)
609 user_appdata_directory.set_dirname(_internal_data_dir);
610 user_appdata_directory.set_basename("files");
611
612#else
613 // Posix case. We follow the XDG base directory spec.
614 struct stat st;
615 const char *datadir = getenv("XDG_DATA_HOME");
616 if (datadir != nullptr && stat(datadir, &st) == 0 && S_ISDIR(st.st_mode)) {
617 user_appdata_directory = datadir;
618 } else {
619 user_appdata_directory = Filename(get_home_directory(), ".local/share");
620 }
621
622#endif // WIN32
623
624 if (user_appdata_directory.empty()) {
625 // Fallback case.
626 user_appdata_directory = ExecutionEnvironment::get_cwd();
627 }
628
629 Filename *newdir = new Filename(user_appdata_directory);
630 if (AtomicAdjust::compare_and_exchange_ptr(_user_appdata_directory, nullptr, newdir) != nullptr) {
631 // Didn't store it. Must have been stored by someone else.
632 assert(_user_appdata_directory != nullptr);
633 delete newdir;
634 }
635 }
636
637 return (*(Filename *)_user_appdata_directory);
638}
639
640/**
641 * Returns a path to a system-defined directory appropriate for creating a
642 * subdirectory for storing application-specific data, common to all users.
643 */
644const Filename &Filename::
645get_common_appdata_directory() {
646 if (AtomicAdjust::get_ptr(_common_appdata_directory) == nullptr) {
647 Filename common_appdata_directory;
648
649#ifdef WIN32
650 wchar_t buffer[MAX_PATH];
651
652 if (SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_COMMON_APPDATA, true)) {
653 Filename dirname = from_os_specific_w(buffer);
654 if (dirname.is_directory()) {
655 if (dirname.make_canonical()) {
656 common_appdata_directory = dirname;
657 }
658 }
659 }
660
661#elif defined(IS_OSX)
662 common_appdata_directory = get_osx_common_appdata_directory();
663
664#elif defined(ANDROID)
665 common_appdata_directory.set_dirname(_internal_data_dir);
666 common_appdata_directory.set_basename("files");
667
668#elif defined(__FreeBSD__)
669 common_appdata_directory = "/usr/local/share";
670#else
671 common_appdata_directory = "/usr/share";
672#endif // WIN32
673
674 if (common_appdata_directory.empty()) {
675 // Fallback case.
676 common_appdata_directory = ExecutionEnvironment::get_cwd();
677 }
678
679 Filename *newdir = new Filename(common_appdata_directory);
680 if (AtomicAdjust::compare_and_exchange_ptr(_common_appdata_directory, nullptr, newdir) != nullptr) {
681 // Didn't store it. Must have been stored by someone else.
682 assert(_common_appdata_directory != nullptr);
683 delete newdir;
684 }
685 }
686
687 return (*(Filename *)_common_appdata_directory);
688}
689
690/**
691 * Replaces the entire filename: directory, basename, extension. This can
692 * also be achieved with the assignment operator.
693 */
694void Filename::
695set_fullpath(const string &s) {
696 (*this) = s;
697}
698
699/**
700 * Replaces the directory part of the filename. This is everything in the
701 * filename up to, but not including the rightmost slash.
702 */
703void Filename::
704set_dirname(const string &s) {
705 if (s.empty()) {
706 // Remove the directory prefix altogether.
707 _filename.replace(0, _basename_start, "");
708
709 int length_change = - ((int)_basename_start);
710
711 _dirname_end = 0;
712 _basename_start += length_change;
713 _basename_end += length_change;
714 _extension_start += length_change;
715
716 } else {
717 // Replace the existing directory prefix, or insert a new one.
718
719 // We build the string ss to include the terminal slash.
720 string ss;
721 if (s[s.length()-1] == '/') {
722 ss = s;
723 } else {
724 ss = s+'/';
725 }
726
727 int length_change = (int)ss.length() - (int)_basename_start;
728
729 _filename.replace(0, _basename_start, ss);
730
731 _dirname_end = ss.length() - 1;
732
733 // An exception: if the dirname string was the single slash, the dirname
734 // includes that slash.
735 if (ss.length() == 1) {
736 _dirname_end = 1;
737 }
738
739 _basename_start += length_change;
740
741 if (_basename_end != string::npos) {
742 _basename_end += length_change;
743 _extension_start += length_change;
744 }
745 }
746 locate_hash();
747}
748
749/**
750 * Replaces the basename part of the filename. This is everything in the
751 * filename after the rightmost slash, including any extensions.
752 */
753void Filename::
754set_basename(const string &s) {
755 _filename.replace(_basename_start, string::npos, s);
756 locate_extension();
757 locate_hash();
758}
759
760
761/**
762 * Replaces the full filename--directory and basename parts--except for the
763 * extension.
764 */
765void Filename::
766set_fullpath_wo_extension(const string &s) {
767 int length_change = (int)s.length() - (int)_basename_end;
768
769 _filename.replace(0, _basename_end, s);
770
771 if (_basename_end != string::npos) {
772 _basename_end += length_change;
773 _extension_start += length_change;
774 }
775 locate_hash();
776}
777
778
779/**
780 * Replaces the basename part of the filename, without the file extension.
781 */
782void Filename::
783set_basename_wo_extension(const string &s) {
784 int length_change = (int)s.length() - (int)(_basename_end - _basename_start);
785
786 if (_basename_end == string::npos) {
787 _filename.replace(_basename_start, string::npos, s);
788
789 } else {
790 _filename.replace(_basename_start, _basename_end - _basename_start, s);
791
792 _basename_end += length_change;
793 _extension_start += length_change;
794 }
795 locate_hash();
796}
797
798
799/**
800 * Replaces the file extension. This is everything after the rightmost dot,
801 * if there is one, or the empty string if there is not.
802 */
803void Filename::
804set_extension(const string &s) {
805 if (s.empty()) {
806 // Remove the extension altogether.
807 if (_basename_end != string::npos) {
808 _filename.replace(_basename_end, string::npos, "");
809 _basename_end = string::npos;
810 _extension_start = string::npos;
811 }
812
813 } else if (_basename_end == string::npos) {
814 // Insert an extension where there was none before.
815 _basename_end = _filename.length();
816 _extension_start = _filename.length() + 1;
817 _filename += '.' + s;
818
819 } else {
820 // Replace an existing extension.
821 _filename.replace(_extension_start, string::npos, s);
822 }
823 locate_hash();
824}
825
826/**
827 * If the pattern flag is set for this Filename and the filename string
828 * actually includes a sequence of hash marks, then this returns a new
829 * Filename with the sequence of hash marks replaced by the indicated index
830 * number.
831 *
832 * If the pattern flag is not set for this Filename or it does not contain a
833 * sequence of hash marks, this quietly returns the original filename.
834 */
835Filename Filename::
836get_filename_index(int index) const {
837 Filename file(*this);
838
839 if (_hash_end != _hash_start) {
840 std::ostringstream strm;
841 strm << _filename.substr(0, _hash_start)
842 << std::setw((int)(_hash_end - _hash_start)) << std::setfill('0') << index
843 << _filename.substr(_hash_end);
844 file.set_fullpath(strm.str());
845 }
846 file.set_pattern(false);
847
848 return file;
849}
850
851/**
852 * Replaces the part of the filename from the beginning of the hash sequence
853 * to the end of the filename.
854 */
855void Filename::
856set_hash_to_end(const string &s) {
857 _filename.replace(_hash_start, string::npos, s);
858
859 locate_basename();
860 locate_extension();
861 locate_hash();
862}
863
864/**
865 * Extracts out the individual directory components of the path into a series
866 * of strings. get_basename() will be the last component stored in the
867 * vector. Note that no distinction is made by this method between a leading
868 * slash and no leading slash, but you can call is_local() to differentiate
869 * the two cases.
870 */
871void Filename::
872extract_components(vector_string &components) const {
873 components.clear();
874
875 size_t p = 0;
876 if (!_filename.empty() && _filename[0] == '/') {
877 // Skip the leading slash.
878 p = 1;
879 }
880 while (p < _filename.length()) {
881 size_t q = _filename.find('/', p);
882 if (q == string::npos) {
883 components.push_back(_filename.substr(p));
884 return;
885 }
886 components.push_back(_filename.substr(p, q - p));
887 p = q + 1;
888 }
889
890 // A trailing slash means we have an empty get_basename().
891 components.push_back(string());
892}
893
894/**
895 * Converts the filename to standard form by replacing consecutive slashes
896 * with a single slash, removing a trailing slash if present, and backing up
897 * over .. sequences within the filename where possible.
898 */
899void Filename::
900standardize() {
901 assert(!_filename.empty());
902 if (_filename == ".") {
903 // Don't change a single dot; this refers to the current directory.
904 return;
905 }
906
907 vector_string components;
908
909 // Pull off the components of the filename one at a time.
910 bool global = (_filename[0] == '/');
911
912 size_t p = 0;
913 while (p < _filename.length() && _filename[p] == '/') {
914 p++;
915 }
916 while (p < _filename.length()) {
917 size_t slash = _filename.find('/', p);
918 string component = _filename.substr(p, slash - p);
919 if (component == "." && p != 0) {
920 // Ignore ..
921 } else if (component == ".." && !components.empty() &&
922 !(components.back() == "..")) {
923 if (components.back() == ".") {
924 // To "back up" over a leading . means simply to remove the leading .
925 components.pop_back();
926 components.push_back(component);
927 } else {
928 // Back up normally.
929 components.pop_back();
930 }
931 } else {
932 components.push_back(component);
933 }
934
935 p = slash;
936 while (p < _filename.length() && _filename[p] == '/') {
937 p++;
938 }
939 }
940
941 // Now reassemble the filename.
942 string result;
943 if (global) {
944 result = "/";
945 }
946 if (!components.empty()) {
947 result += components[0];
948 for (int i = 1; i < (int)components.size(); i++) {
949 result += "/" + components[i];
950 }
951 }
952
953 (*this) = result;
954}
955
956/**
957 * Converts the filename to a fully-qualified pathname from the root (if it is
958 * a relative pathname), and then standardizes it (see standardize()).
959 *
960 * This is sometimes a little problematic, since it may convert the file to
961 * its 'true' absolute pathname, which could be an ugly NFS-named file,
962 * irrespective of symbolic links (e.g.
963 * /.automount/dimbo/root/usr2/fit/people/drose instead of /fit/people/drose);
964 * besides being ugly, filenames like this may not be consistent across
965 * multiple different platforms.
966 */
967void Filename::
968make_absolute() {
969 if (is_local()) {
971 } else {
972 standardize();
973 }
974}
975
976/**
977 * Converts the filename to a fully-qualified filename from the root (if it is
978 * a relative filename), and then standardizes it (see standardize()). This
979 * flavor accepts a specific starting directory that the filename is known to
980 * be relative to.
981 */
982void Filename::
983make_absolute(const Filename &start_directory) {
984 if (is_local()) {
985 Filename new_filename(start_directory, _filename);
986 new_filename._flags = _flags;
987 (*this) = new_filename;
988 }
989
990 standardize();
991}
992
993/**
994 * Converts this filename to a canonical name by replacing the directory part
995 * with the fully-qualified directory part. This is done by changing to that
996 * directory and calling getcwd().
997 *
998 * This has the effect of (a) converting relative paths to absolute paths (but
999 * see make_absolute() if this is the only effect you want), and (b) always
1000 * resolving a given directory name to the same string, even if different
1001 * symbolic links are traversed, and (c) changing nice symbolic-link paths
1002 * like fit/people/drose to ugly NFS automounter names like
1003 * hosts/dimbo/usr2/fit/people/drose. This can be troubling, but sometimes
1004 * this is exactly what you want, particularly if you're about to call
1005 * make_relative_to() between two filenames.
1006 *
1007 * The return value is true if successful, or false on failure (usually
1008 * because the directory name does not exist or cannot be chdir'ed into).
1009 */
1010bool Filename::
1011make_canonical() {
1012 if (empty()) {
1013 // An empty filename is a special case. This doesn't name anything.
1014 return false;
1015 }
1016
1017 if (get_fullpath() == "/") {
1018 // The root directory is a special case.
1019 return true;
1020 }
1021
1022#ifndef WIN32
1023 // Use realpath in order to resolve symlinks properly
1024 char newpath [PATH_MAX + 1];
1025 if (realpath(c_str(), newpath) != nullptr) {
1026 Filename newpath_fn(newpath);
1027 newpath_fn._flags = _flags;
1028 (*this) = newpath_fn;
1029 }
1030#endif
1031
1033 if (!r_make_canonical(cwd)) {
1034 return false;
1035 }
1036
1037 return make_true_case();
1038}
1039
1040/**
1041 * On a case-insensitive operating system (e.g. Windows), this method looks
1042 * up the file in the file system and resets the Filename to represent the
1043 * actual case of the file as it exists on the disk. The return value is true
1044 * if the file exists and the conversion can be made, or false if there is
1045 * some error.
1046 *
1047 * On a case-sensitive operating system, this method does nothing and always
1048 * returns true.
1049 *
1050 * An empty filename is considered to exist in this case.
1051 */
1052bool Filename::
1053make_true_case() {
1054 assert(!get_pattern());
1055
1056 if (empty()) {
1057 return true;
1058 }
1059
1060#ifdef WIN32
1061 wstring os_specific = to_os_specific_w();
1062
1063 // First, we have to convert it to its short name, then back to its long
1064 // name--that seems to be the trick to force Windows to throw away the case
1065 // we give it and get the actual file case.
1066
1067 wchar_t short_name[MAX_PATH + 1];
1068 DWORD l = GetShortPathNameW(os_specific.c_str(), short_name, MAX_PATH + 1);
1069 if (l == 0) {
1070 // Couldn't query the path name for some reason. Probably the file didn't
1071 // exist.
1072 return false;
1073 }
1074 // According to the Windows docs, l will return a value greater than the
1075 // specified length if the short_name length wasn't enough--but also
1076 // according to the Windows docs, MAX_PATH will always be enough.
1077 assert(l < MAX_PATH + 1);
1078
1079 wchar_t long_name[MAX_PATH + 1];
1080 l = GetLongPathNameW(short_name, long_name, MAX_PATH + 1);
1081 if (l == 0) {
1082 // Couldn't query the path name for some reason. Probably the file didn't
1083 // exist.
1084 return false;
1085 }
1086 assert(l < MAX_PATH + 1);
1087
1088 Filename true_case = Filename::from_os_specific_w(long_name);
1089
1090 // Now sanity-check the true-case filename. If it's not the same as the
1091 // source file, except for case, reject it.
1092 wstring orig_filename = get_fullpath_w();
1093 wstring new_filename = true_case.get_fullpath_w();
1094 bool match = (orig_filename.length() == new_filename.length());
1095 for (size_t i = 0; i < orig_filename.length() && match; ++i) {
1096 match = (TextEncoder::unicode_tolower(orig_filename[i]) == TextEncoder::unicode_tolower(new_filename[i]));
1097 }
1098 if (!match) {
1099 // Something went wrong. Keep the original filename, assume it was the
1100 // correct case after all. We return true because the filename is good.
1101 return true;
1102 }
1103
1104 true_case._flags = _flags;
1105 (*this) = true_case;
1106 return true;
1107
1108#else // WIN32
1109 return true;
1110#endif // WIN32
1111}
1112
1113/**
1114 * Converts the filename from our generic Unix-like convention (forward
1115 * slashes starting with the root at '/') to the corresponding filename in the
1116 * local operating system (slashes in the appropriate direction, starting with
1117 * the root at C:\, for instance). Returns the string representing the
1118 * converted filename, but does not change the Filename itself.
1119 *
1120 * See also from_os_specific().
1121 */
1122string Filename::
1123to_os_specific() const {
1124 assert(!get_pattern());
1125
1126 if (empty()) {
1127 return string();
1128 }
1129 Filename standard(*this);
1130 standard.standardize();
1131
1132#ifdef IS_OSX
1133 if (get_type() == T_dso) {
1134 std::string workname = standard.get_fullpath();
1135 size_t dot = workname.rfind('.');
1136 if (dot != string::npos) {
1137 if (workname.substr(dot) == ".so") {
1138 string dyLibBase = workname.substr(0, dot)+".dylib";
1139 return dyLibBase;
1140 }
1141 }
1142 }
1143#endif
1144
1145#ifdef WIN32
1146 switch (get_type()) {
1147 case T_dso:
1148 return convert_dso_pathname(standard.get_fullpath());
1149 case T_executable:
1150 return convert_executable_pathname(standard.get_fullpath());
1151 default:
1152 return convert_pathname(standard.get_fullpath());
1153 }
1154#else // WIN32
1155 return standard.c_str();
1156#endif // WIN32
1157}
1158
1159/**
1160 * The wide-string variant on to_os_specific().
1161 */
1162wstring Filename::
1163to_os_specific_w() const {
1164 TextEncoder encoder;
1166 encoder.set_text(to_os_specific());
1167 return encoder.get_wtext();
1168}
1169
1170/**
1171 * This is similar to to_os_specific(), but it is designed to generate a
1172 * filename that can be understood on as many platforms as possible. Since
1173 * Windows can usually understand a forward-slash-delimited filename, this
1174 * means it does the same thing as to_os_specific(), but it uses forward
1175 * slashes instead of backslashes.
1176 *
1177 * This method has a pretty limited use; it should generally be used for
1178 * writing file references to a file that might be read on any operating
1179 * system.
1180 */
1181string Filename::
1182to_os_generic() const {
1183 assert(!get_pattern());
1184
1185#ifdef WIN32
1186 return back_to_front_slash(to_os_specific());
1187#else // WIN32
1188 return to_os_specific();
1189#endif // WIN32
1190}
1191
1192/**
1193 * This works like to_os_generic(), but it returns the "short name" version of
1194 * the filename, if it exists, or the original filename otherwise.
1195 *
1196 * On Windows platforms, this returns the 8.3 filename version of the given
1197 * filename, if the file exists, and the same thing as to_os_specific()
1198 * otherwise. On non-Windows platforms, this always returns the same thing as
1199 * to_os_specific().
1200 */
1201string Filename::
1202to_os_short_name() const {
1203 assert(!get_pattern());
1204
1205#ifdef WIN32
1206 wstring os_specific = to_os_specific_w();
1207
1208 wchar_t short_name[MAX_PATH + 1];
1209 DWORD l = GetShortPathNameW(os_specific.c_str(), short_name, MAX_PATH + 1);
1210 if (l == 0) {
1211 // Couldn't query the path name for some reason. Probably the file didn't
1212 // exist.
1213 return to_os_specific();
1214 }
1215 // According to the Windows docs, l will return a value greater than the
1216 // specified length if the short_name length wasn't enough--but also
1217 // according to the Windows docs, MAX_PATH will always be enough.
1218 assert(l < MAX_PATH + 1);
1219
1220 TextEncoder encoder;
1222 encoder.set_wtext(short_name);
1223 return encoder.get_text();
1224
1225#else // WIN32
1226 return to_os_specific();
1227#endif // WIN32
1228}
1229
1230/**
1231 * This is the opposite of to_os_short_name(): it returns the "long name" of
1232 * the filename, if the filename exists. On non-Windows platforms, this
1233 * returns the same thing as to_os_specific().
1234 */
1235string Filename::
1236to_os_long_name() const {
1237 assert(!get_pattern());
1238
1239#ifdef WIN32
1240 wstring os_specific = to_os_specific_w();
1241
1242 wchar_t long_name[MAX_PATH + 1];
1243 DWORD l = GetLongPathNameW(os_specific.c_str(), long_name, MAX_PATH + 1);
1244 if (l == 0) {
1245 // Couldn't query the path name for some reason. Probably the file didn't
1246 // exist.
1247 return to_os_specific();
1248 }
1249 assert(l < MAX_PATH + 1);
1250
1251 TextEncoder encoder;
1253 encoder.set_wtext(long_name);
1254 return encoder.get_text();
1255
1256#else // WIN32
1257 return to_os_specific();
1258#endif // WIN32
1259}
1260
1261/**
1262 * Returns true if the filename exists on the physical disk, false otherwise.
1263 * If the type is indicated to be executable, this also tests that the file has
1264 * execute permission.
1265 *
1266 * @see VirtualFileSystem::exists() for checking whether the filename exists in
1267 * the virtual file system.
1268 */
1269bool Filename::
1270exists() const {
1271#ifdef WIN32_VC
1272 wstring os_specific = get_filename_index(0).to_os_specific_w();
1273
1274 bool exists = false;
1275
1276 DWORD results = GetFileAttributesW(os_specific.c_str());
1277 if (results != -1) {
1278 exists = true;
1279 }
1280
1281#else // WIN32_VC
1282 string os_specific = get_filename_index(0).to_os_specific();
1283
1284 struct stat this_buf;
1285 bool exists = false;
1286
1287 if (stat(os_specific.c_str(), &this_buf) == 0) {
1288 exists = true;
1289 }
1290#endif
1291
1292 return exists;
1293}
1294
1295/**
1296 * Returns true if the filename exists on the physical disk and is the name of
1297 * a regular file (i.e. not a directory or device), false otherwise.
1298 *
1299 * @see VirtualFileSystem::is_regular_file() for checking whether the filename
1300 * exists and is a regular file in the virtual file system.
1301 */
1302bool Filename::
1303is_regular_file() const {
1304#ifdef WIN32_VC
1305 wstring os_specific = get_filename_index(0).to_os_specific_w();
1306
1307 bool isreg = false;
1308
1309 DWORD results = GetFileAttributesW(os_specific.c_str());
1310 if (results != -1) {
1311 isreg = ((results & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE)) == 0);
1312 }
1313
1314#else // WIN32_VC
1315 string os_specific = get_filename_index(0).to_os_specific();
1316
1317 struct stat this_buf;
1318 bool isreg = false;
1319
1320 if (stat(os_specific.c_str(), &this_buf) == 0) {
1321 isreg = S_ISREG(this_buf.st_mode);
1322 }
1323#endif
1324
1325 return isreg;
1326}
1327
1328/**
1329 * Returns true if the filename exists on the physical disk and is either a
1330 * directory or a regular file that can be written to, or false otherwise.
1331 */
1332bool Filename::
1333is_writable() const {
1334 bool writable = false;
1335
1336#ifdef WIN32_VC
1337 wstring os_specific = get_filename_index(0).to_os_specific_w();
1338
1339 DWORD results = GetFileAttributesW(os_specific.c_str());
1340 if (results != -1) {
1341 if ((results & FILE_ATTRIBUTE_DIRECTORY) != 0) {
1342 // Assume directories are writable.
1343 writable = true;
1344 } else if ((results & FILE_ATTRIBUTE_READONLY) == 0) {
1345 // Not read-only means writable.
1346 writable = true;
1347 }
1348 }
1349#else // WIN32_VC
1350 string os_specific = get_filename_index(0).to_os_specific();
1351
1352 if (access(os_specific.c_str(), W_OK) == 0) {
1353 writable = true;
1354 }
1355#endif
1356
1357 return writable;
1358}
1359
1360/**
1361 * Returns true if the filename exists on the physical disk and is a directory
1362 * name, false otherwise.
1363 *
1364 * @see VirtualFileSystem::is_directory() for checking whether the filename
1365 * exists as a directory in the virtual file system.
1366 */
1367bool Filename::
1368is_directory() const {
1369#ifdef WIN32_VC
1370 wstring os_specific = get_filename_index(0).to_os_specific_w();
1371
1372 bool isdir = false;
1373
1374 DWORD results = GetFileAttributesW(os_specific.c_str());
1375 if (results != -1) {
1376 isdir = (results & FILE_ATTRIBUTE_DIRECTORY) != 0;
1377 }
1378#else // WIN32_VC
1379 string os_specific = get_filename_index(0).to_os_specific();
1380
1381 struct stat this_buf;
1382 bool isdir = false;
1383
1384 if (stat(os_specific.c_str(), &this_buf) == 0) {
1385 isdir = S_ISDIR(this_buf.st_mode);
1386 }
1387#endif
1388
1389 return isdir;
1390}
1391
1392/**
1393 * Returns true if the filename exists and is executable
1394 */
1395bool Filename::
1396is_executable() const {
1397#ifdef WIN32_VC
1398 // no access() in windows, but to our advantage executables can only end in
1399 // .exe or .com
1400 string extension = get_extension();
1401 if (extension == "exe" || extension == "com") {
1402 return exists();
1403 }
1404
1405#else /* WIN32_VC */
1406 string os_specific = get_filename_index(0).to_os_specific();
1407 if (access(os_specific.c_str(), X_OK) == 0) {
1408 return true;
1409 }
1410#endif /* WIN32_VC */
1411
1412 return false;
1413}
1414
1415/**
1416 * Returns a number less than zero if the file named by this object is older
1417 * than the given file, zero if they have the same timestamp, or greater than
1418 * zero if this one is newer.
1419 *
1420 * If this_missing_is_old is true, it indicates that a missing file will be
1421 * treated as if it were older than any other file; otherwise, a missing file
1422 * will be treated as if it were newer than any other file. Similarly for
1423 * other_missing_is_old.
1424 */
1425int Filename::
1426compare_timestamps(const Filename &other,
1427 bool this_missing_is_old,
1428 bool other_missing_is_old) const {
1429#ifdef WIN32_VC
1430 wstring os_specific = get_filename_index(0).to_os_specific_w();
1431 wstring other_os_specific = other.get_filename_index(0).to_os_specific_w();
1432
1433 struct _stat this_buf;
1434 bool this_exists = false;
1435
1436 if (_wstat(os_specific.c_str(), &this_buf) == 0) {
1437 this_exists = true;
1438 }
1439
1440 struct _stat other_buf;
1441 bool other_exists = false;
1442
1443 if (_wstat(other_os_specific.c_str(), &other_buf) == 0) {
1444 other_exists = true;
1445 }
1446#else // WIN32_VC
1447 string os_specific = get_filename_index(0).to_os_specific();
1448 string other_os_specific = other.get_filename_index(0).to_os_specific();
1449
1450 struct stat this_buf;
1451 bool this_exists = false;
1452
1453 if (stat(os_specific.c_str(), &this_buf) == 0) {
1454 this_exists = true;
1455 }
1456
1457 struct stat other_buf;
1458 bool other_exists = false;
1459
1460 if (stat(other_os_specific.c_str(), &other_buf) == 0) {
1461 other_exists = true;
1462 }
1463#endif
1464
1465 if (this_exists && other_exists) {
1466 // Both files exist, return the honest time comparison.
1467 return (int)this_buf.st_mtime - (int)other_buf.st_mtime;
1468
1469 } else if (!this_exists && !other_exists) {
1470 // Neither file exists.
1471 if (this_missing_is_old == other_missing_is_old) {
1472 // Both files are either "very old" or "very new".
1473 return 0;
1474 }
1475 if (this_missing_is_old) {
1476 // This file is "very old", the other is "very new".
1477 return -1;
1478 } else {
1479 // This file is "very new", the other is "very old".
1480 return 1;
1481 }
1482
1483 } else if (!this_exists) {
1484 // This file doesn't, the other one does.
1485 return this_missing_is_old ? -1 : 1;
1486
1487 }
1488 // !other_exists
1489 assert(!other_exists);
1490
1491 // This file exists, the other one doesn't.
1492 return other_missing_is_old ? 1 : -1;
1493}
1494
1495/**
1496 * Returns a time_t value that represents the time the file was last modified,
1497 * to within whatever precision the operating system records this information
1498 * (on a Windows95 system, for instance, this may only be accurate to within 2
1499 * seconds).
1500 *
1501 * If the timestamp cannot be determined, either because it is not supported
1502 * by the operating system or because there is some error (such as file not
1503 * found), returns 0.
1504 */
1505time_t Filename::
1506get_timestamp() const {
1507#ifdef WIN32_VC
1508 wstring os_specific = get_filename_index(0).to_os_specific_w();
1509
1510 struct _stat this_buf;
1511
1512 if (_wstat(os_specific.c_str(), &this_buf) == 0) {
1513 return this_buf.st_mtime;
1514 }
1515#else // WIN32_VC
1516 string os_specific = get_filename_index(0).to_os_specific();
1517
1518 struct stat this_buf;
1519
1520 if (stat(os_specific.c_str(), &this_buf) == 0) {
1521 return this_buf.st_mtime;
1522 }
1523#endif
1524
1525 return 0;
1526}
1527
1528/**
1529 * Returns a time_t value that represents the time the file was last accessed,
1530 * if this information is available. See also get_timestamp(), which returns
1531 * the last modification time.
1532 */
1533time_t Filename::
1534get_access_timestamp() const {
1535#ifdef WIN32_VC
1536 wstring os_specific = get_filename_index(0).to_os_specific_w();
1537
1538 struct _stat this_buf;
1539
1540 if (_wstat(os_specific.c_str(), &this_buf) == 0) {
1541 return this_buf.st_atime;
1542 }
1543#else // WIN32_VC
1544 string os_specific = get_filename_index(0).to_os_specific();
1545
1546 struct stat this_buf;
1547
1548 if (stat(os_specific.c_str(), &this_buf) == 0) {
1549 return this_buf.st_atime;
1550 }
1551#endif
1552
1553 return 0;
1554}
1555
1556/**
1557 * Returns the size of the file in bytes, or 0 if there is an error.
1558 */
1559std::streamsize Filename::
1560get_file_size() const {
1561#ifdef WIN32_VC
1562 wstring os_specific = get_filename_index(0).to_os_specific_w();
1563
1564 struct _stat64 this_buf;
1565
1566 // _wstat() only returns the lower 32 bits of the file size (!). We have to
1567 // call _wstati64() if we actually want the full 64-bit file size.
1568 if (_wstati64(os_specific.c_str(), &this_buf) == 0) {
1569 return this_buf.st_size;
1570 }
1571#else // WIN32_VC
1572 string os_specific = get_filename_index(0).to_os_specific();
1573
1574 struct stat this_buf;
1575
1576 if (stat(os_specific.c_str(), &this_buf) == 0) {
1577 return this_buf.st_size;
1578 }
1579#endif
1580
1581 return 0;
1582}
1583
1584/**
1585 * Searches the given search path for the filename. If it is found, updates
1586 * the filename to the full pathname found and returns true; otherwise,
1587 * returns false.
1588 */
1589bool Filename::
1590resolve_filename(const DSearchPath &searchpath,
1591 const string &default_extension) {
1592 Filename found;
1593
1594 if (is_local()) {
1595 found = searchpath.find_file(*this);
1596
1597 if (found.empty()) {
1598 // We didn't find it with the given extension; can we try the default
1599 // extension?
1600 if (get_extension().empty() && !default_extension.empty()) {
1601 Filename try_ext = *this;
1602 try_ext.set_extension(default_extension);
1603 found = searchpath.find_file(try_ext);
1604 }
1605 }
1606 } else {
1607 if (exists()) {
1608 // The full pathname exists. Return true.
1609 return true;
1610 } else {
1611 // The full pathname doesn't exist with the given extension; does it
1612 // exist with the default extension?
1613 if (get_extension().empty() && !default_extension.empty()) {
1614 Filename try_ext = *this;
1615 try_ext.set_extension(default_extension);
1616 if (try_ext.exists()) {
1617 found = try_ext;
1618 }
1619 }
1620 }
1621 }
1622
1623 if (!found.empty()) {
1624 (*this) = found;
1625 return true;
1626 }
1627
1628 return false;
1629}
1630
1631/**
1632 * Adjusts this filename, which must be a fully-specified pathname beginning
1633 * with a slash, to make it a relative filename, relative to the fully-
1634 * specified directory indicated (which must also begin with, and may or may
1635 * not end with, a slash--a terminating slash is ignored).
1636 *
1637 * This only performs a string comparsion, so it may be wise to call
1638 * make_canonical() on both filenames before calling make_relative_to().
1639 *
1640 * If allow_backups is false, the filename will only be adjusted to be made
1641 * relative if it is already somewhere within or below the indicated
1642 * directory. If allow_backups is true, it will be adjusted in all cases,
1643 * even if this requires putting a series of .. characters before the filename
1644 * --unless it would have to back all the way up to the root.
1645 *
1646 * Returns true if the file was adjusted, false if it was not.
1647 */
1648bool Filename::
1649make_relative_to(Filename directory, bool allow_backups) {
1650 if (_filename.empty() || directory.empty() ||
1651 _filename[0] != '/' || directory[0] != '/') {
1652 return false;
1653 }
1654
1655 standardize();
1656 directory.standardize();
1657
1658 if (directory == "/") {
1659 // Don't be silly.
1660 return false;
1661 }
1662
1663 string rel_to_file = directory.get_fullpath() + "/.";
1664
1665 size_t common = get_common_prefix(rel_to_file);
1666 if (common < 2) {
1667 // Oh, never mind.
1668 return false;
1669 }
1670
1671 string result;
1672 int slashes = count_slashes(rel_to_file.substr(common));
1673 if (slashes > 0 && !allow_backups) {
1674 // Too bad; the file's not under the indicated directory.
1675 return false;
1676 }
1677
1678 for (int i = 0; i < slashes; i++) {
1679 result += "../";
1680 }
1681 result += _filename.substr(common);
1682 (*this) = result;
1683
1684 return true;
1685}
1686
1687/**
1688 * Performs the reverse of the resolve_filename() operation: assuming that the
1689 * current filename is fully-specified pathname (i.e. beginning with '/'),
1690 * look on the indicated search path for a directory under which the file can
1691 * be found. When found, adjust the Filename to be relative to the indicated
1692 * directory name.
1693 *
1694 * Returns the index of the directory on the searchpath at which the file was
1695 * found, or -1 if it was not found.
1696 */
1697int Filename::
1698find_on_searchpath(const DSearchPath &searchpath) {
1699 if (_filename.empty() || _filename[0] != '/') {
1700 return -1;
1701 }
1702
1703 size_t num_directories = searchpath.get_num_directories();
1704 for (size_t i = 0; i < num_directories; ++i) {
1705 Filename directory = searchpath.get_directory(i);
1706 directory.make_absolute();
1707 if (make_relative_to(directory, false)) {
1708 return (int)i;
1709 }
1710 }
1711
1712 return -1;
1713}
1714
1715/**
1716 * Attempts to open the named filename as if it were a directory and looks for
1717 * the non-hidden files within the directory. Fills the given vector up with
1718 * the sorted list of filenames that are local to this directory.
1719 *
1720 * It is the user's responsibility to ensure that the contents vector is empty
1721 * before making this call; otherwise, the new files will be appended to it.
1722 *
1723 * Returns true on success, false if the directory could not be read for some
1724 * reason.
1725 */
1726bool Filename::
1727scan_directory(vector_string &contents) const {
1728 assert(!get_pattern());
1729
1730#if defined(WIN32_VC)
1731 // Use Windows' FindFirstFile() FindNextFile() to walk through the list of
1732 // files in a directory.
1733 size_t orig_size = contents.size();
1734
1735 wstring match;
1736 if (empty()) {
1737 match = L"*.*";
1738 } else {
1739 match = to_os_specific_w() + L"\\*.*";
1740 }
1741 WIN32_FIND_DATAW find_data;
1742
1743 HANDLE handle = FindFirstFileW(match.c_str(), &find_data);
1744 if (handle == INVALID_HANDLE_VALUE) {
1745 if (GetLastError() == ERROR_NO_MORE_FILES) {
1746 // No matching files is not an error.
1747 return true;
1748 }
1749 return false;
1750 }
1751
1752 TextEncoder encoder;
1754 do {
1755 thread_consider_yield();
1756 wstring filename = find_data.cFileName;
1757 if (filename != L"." && filename != L"..") {
1758 encoder.set_wtext(filename);
1759 contents.push_back(encoder.get_text());
1760 }
1761 } while (FindNextFileW(handle, &find_data));
1762
1763 bool scan_ok = (GetLastError() == ERROR_NO_MORE_FILES);
1764 FindClose(handle);
1765
1766 sort(contents.begin() + orig_size, contents.end());
1767 return scan_ok;
1768
1769#elif defined(PHAVE_DIRENT_H)
1770 // Use Posix's opendir() readdir() to walk through the list of files in a
1771 // directory.
1772 size_t orig_size = contents.size();
1773
1774 string dirname;
1775 if (empty()) {
1776 dirname = ".";
1777 } else {
1778 dirname = _filename;
1779 }
1780 DIR *root = opendir(dirname.c_str());
1781 if (root == nullptr) {
1782 if (errno != ENOTDIR) {
1783 perror(dirname.c_str());
1784 }
1785 return false;
1786 }
1787
1788 struct dirent *d;
1789 d = readdir(root);
1790 while (d != nullptr) {
1791 thread_consider_yield();
1792 if (d->d_name[0] != '.') {
1793 contents.push_back(d->d_name);
1794 }
1795 d = readdir(root);
1796 }
1797
1798 // It turns out to be a mistake to check the value of errno after calling
1799 // readdir(), since it might have been set to non-zero during some internal
1800 // operation of readdir(), even though there wasn't really a problem with
1801 // scanning the directory itself.
1802 /*
1803 if (errno != 0 && errno != ENOENT && errno != ENOTDIR) {
1804 cerr << "Error occurred while scanning directory " << dirname << "\n";
1805 perror(dirname.c_str());
1806 closedir(root);
1807 return false;
1808 }
1809 */
1810 closedir(root);
1811
1812 sort(contents.begin() + orig_size, contents.end());
1813 return true;
1814
1815#elif defined(PHAVE_GLOB_H)
1816 // It's hard to imagine a system that provides glob.h but does not provide
1817 // openddir() .. readdir(), but this code is leftover from a time when there
1818 // was an undetected bug in the above readdir() loop, and it works, so we
1819 // might as well keep it around for now.
1820 string dirname;
1821 if (empty()) {
1822 dirname = "*";
1823 } else if (_filename[_filename.length() - 1] == '/') {
1824 dirname = _filename + "*";
1825 } else {
1826 dirname = _filename + "/*"; /* comment to fix emacs syntax hilight */
1827 }
1828
1829 glob_t globbuf;
1830
1831 int r = glob(dirname.c_str(), GLOB_ERR, nullptr, &globbuf);
1832
1833 if (r != 0) {
1834 // Some error processing the match string. If our version of glob.h
1835 // defines GLOB_NOMATCH, then we can differentiate an empty return result
1836 // from some other kind of error.
1837#ifdef GLOB_NOMATCH
1838 if (r != GLOB_NOMATCH) {
1839 perror(dirname.c_str());
1840 return false;
1841 }
1842#endif
1843
1844 // Otherwise, all errors mean the same thing: no matches, but otherwise no
1845 // problem.
1846 return true;
1847 }
1848
1849 size_t offset = dirname.size() - 1;
1850
1851 for (int i = 0; globbuf.gl_pathv[i] != nullptr; i++) {
1852 contents.push_back(globbuf.gl_pathv[i] + offset);
1853 }
1854 globfree(&globbuf);
1855
1856 return true;
1857
1858#else
1859 // Don't know how to scan directories!
1860 return false;
1861#endif
1862}
1863
1864/**
1865 * Opens the indicated ifstream for reading the file, if possible. Returns
1866 * true if successful, false otherwise. This requires the setting of the
1867 * set_text()/set_binary() flags to open the file appropriately as indicated;
1868 * it is an error to call open_read() without first calling one of set_text()
1869 * or set_binary().
1870 */
1871bool Filename::
1872open_read(std::ifstream &stream) const {
1873 assert(!get_pattern());
1874 assert(is_binary_or_text());
1875
1876 ios_openmode open_mode = ios::in;
1877
1878#ifdef HAVE_IOS_BINARY
1879 // For some reason, some systems (like Irix) don't define ios::binary.
1880 if (!is_text()) {
1881 open_mode |= ios::binary;
1882 }
1883#endif
1884
1885 stream.clear();
1886#ifdef WIN32_VC
1887 wstring os_specific = to_os_specific_w();
1888 stream.open(os_specific.c_str(), open_mode);
1889#else
1890 string os_specific = to_os_specific();
1891 stream.open(os_specific.c_str(), open_mode);
1892#endif // WIN32_VC
1893
1894 return (!stream.fail());
1895}
1896
1897/**
1898 * Opens the indicated ifstream for writing the file, if possible. Returns
1899 * true if successful, false otherwise. This requires the setting of the
1900 * set_text()/set_binary() flags to open the file appropriately as indicated;
1901 * it is an error to call open_read() without first calling one of set_text()
1902 * or set_binary().
1903 *
1904 * If truncate is true, the file is truncated to zero length upon opening it,
1905 * if it already exists. Otherwise, the file is kept at its original length.
1906 */
1907bool Filename::
1908open_write(std::ofstream &stream, bool truncate) const {
1909 assert(!get_pattern());
1910 assert(is_binary_or_text());
1911
1912 ios_openmode open_mode = ios::out;
1913
1914 if (truncate) {
1915 open_mode |= ios::trunc;
1916
1917 } else {
1918 // Some systems insist on having ios::in set to prevent the file from
1919 // being truncated when we open it. Makes ios::trunc kind of pointless,
1920 // doesn't it? On the other hand, setting ios::in also seems to imply
1921 // ios::nocreate (!), so we should only set this if the file already
1922 // exists.
1923 if (exists()) {
1924 open_mode |= ios::in;
1925 }
1926 }
1927
1928#ifdef HAVE_IOS_BINARY
1929 // For some reason, some systems (like Irix) don't define ios::binary.
1930 if (!is_text()) {
1931 open_mode |= ios::binary;
1932 }
1933#endif
1934
1935 stream.clear();
1936#ifdef WIN32_VC
1937 wstring os_specific = to_os_specific_w();
1938#else
1939 string os_specific = to_os_specific();
1940#endif // WIN32_VC
1941 stream.open(os_specific.c_str(), open_mode);
1942
1943 return (!stream.fail());
1944}
1945
1946/**
1947 * Opens the indicated ofstream for writing the file, if possible. Returns
1948 * true if successful, false otherwise. This requires the setting of the
1949 * set_text()/set_binary() flags to open the file appropriately as indicated;
1950 * it is an error to call open_read() without first calling one of set_text()
1951 * or set_binary().
1952 */
1953bool Filename::
1954open_append(std::ofstream &stream) const {
1955 assert(!get_pattern());
1956 assert(is_binary_or_text());
1957
1958 ios_openmode open_mode = ios::app;
1959
1960#ifdef HAVE_IOS_BINARY
1961 // For some reason, some systems (like Irix) don't define ios::binary.
1962 if (!is_text()) {
1963 open_mode |= ios::binary;
1964 }
1965#endif
1966
1967 stream.clear();
1968#ifdef WIN32_VC
1969 wstring os_specific = to_os_specific_w();
1970#else
1971 string os_specific = to_os_specific();
1972#endif // WIN32_VC
1973 stream.open(os_specific.c_str(), open_mode);
1974
1975 return (!stream.fail());
1976}
1977
1978/**
1979 * Opens the indicated fstream for read/write access to the file, if possible.
1980 * Returns true if successful, false otherwise. This requires the setting of
1981 * the set_text()/set_binary() flags to open the file appropriately as
1982 * indicated; it is an error to call open_read_write() without first calling
1983 * one of set_text() or set_binary().
1984 */
1985bool Filename::
1986open_read_write(std::fstream &stream, bool truncate) const {
1987 assert(!get_pattern());
1988 assert(is_binary_or_text());
1989
1990 ios_openmode open_mode = ios::out | ios::in;
1991
1992 if (truncate) {
1993 open_mode |= ios::trunc;
1994 }
1995
1996 // Since ios::in also seems to imply ios::nocreate (!), we must guarantee
1997 // the file already exists before we try to open it.
1998 if (!exists()) {
1999 touch();
2000 }
2001
2002#ifdef HAVE_IOS_BINARY
2003 // For some reason, some systems (like Irix) don't define ios::binary.
2004 if (!is_text()) {
2005 open_mode |= ios::binary;
2006 }
2007#endif
2008
2009 stream.clear();
2010#ifdef WIN32_VC
2011 wstring os_specific = to_os_specific_w();
2012#else
2013 string os_specific = to_os_specific();
2014#endif // WIN32_VC
2015 stream.open(os_specific.c_str(), open_mode);
2016
2017 return (!stream.fail());
2018}
2019
2020/**
2021 * Opens the indicated ifstream for reading and writing the file, if possible;
2022 * writes are appended to the end of the file. Returns true if successful,
2023 * false otherwise. This requires the setting of the set_text()/set_binary()
2024 * flags to open the file appropriately as indicated; it is an error to call
2025 * open_read() without first calling one of set_text() or set_binary().
2026 */
2027bool Filename::
2028open_read_append(std::fstream &stream) const {
2029 assert(!get_pattern());
2030 assert(is_binary_or_text());
2031
2032 ios_openmode open_mode = ios::app | ios::in;
2033
2034#ifdef HAVE_IOS_BINARY
2035 // For some reason, some systems (like Irix) don't define ios::binary.
2036 if (!is_text()) {
2037 open_mode |= ios::binary;
2038 }
2039#endif
2040
2041 stream.clear();
2042#ifdef WIN32_VC
2043 wstring os_specific = to_os_specific_w();
2044#else
2045 string os_specific = to_os_specific();
2046#endif // WIN32_VC
2047 stream.open(os_specific.c_str(), open_mode);
2048
2049 return (!stream.fail());
2050}
2051
2052#ifdef USE_PANDAFILESTREAM
2053/**
2054 * Opens the indicated pifstream for reading the file, if possible. Returns
2055 * true if successful, false otherwise. This requires the setting of the
2056 * set_text()/set_binary() flags to open the file appropriately as indicated;
2057 * it is an error to call open_read() without first calling one of set_text()
2058 * or set_binary().
2059 */
2060bool Filename::
2061open_read(pifstream &stream) const {
2062 assert(!get_pattern());
2063 assert(is_binary_or_text());
2064
2065 ios_openmode open_mode = ios::in;
2066
2067#ifdef HAVE_IOS_BINARY
2068 // For some reason, some systems (like Irix) don't define ios::binary.
2069 if (!is_text()) {
2070 open_mode |= ios::binary;
2071 }
2072#endif
2073
2074 string os_specific = to_os_specific();
2075 stream.clear();
2076 stream.open(os_specific.c_str(), open_mode);
2077 return (!stream.fail());
2078}
2079#endif // USE_PANDAFILESTREAM
2080
2081#ifdef USE_PANDAFILESTREAM
2082/**
2083 * Opens the indicated pifstream for writing the file, if possible. Returns
2084 * true if successful, false otherwise. This requires the setting of the
2085 * set_text()/set_binary() flags to open the file appropriately as indicated;
2086 * it is an error to call open_read() without first calling one of set_text()
2087 * or set_binary().
2088 *
2089 * If truncate is true, the file is truncated to zero length upon opening it,
2090 * if it already exists. Otherwise, the file is kept at its original length.
2091 */
2092bool Filename::
2093open_write(pofstream &stream, bool truncate) const {
2094 assert(!get_pattern());
2095 assert(is_binary_or_text());
2096
2097 ios_openmode open_mode = ios::out;
2098
2099 if (truncate) {
2100 open_mode |= ios::trunc;
2101
2102 } else {
2103 // Some systems insist on having ios::in set to prevent the file from
2104 // being truncated when we open it. Makes ios::trunc kind of pointless,
2105 // doesn't it? On the other hand, setting ios::in also seems to imply
2106 // ios::nocreate (!), so we should only set this if the file already
2107 // exists.
2108 if (exists()) {
2109 open_mode |= ios::in;
2110 }
2111 }
2112
2113#ifdef HAVE_IOS_BINARY
2114 // For some reason, some systems (like Irix) don't define ios::binary.
2115 if (!is_text()) {
2116 open_mode |= ios::binary;
2117 }
2118#endif
2119
2120 stream.clear();
2121 string os_specific = to_os_specific();
2122 stream.open(os_specific.c_str(), open_mode);
2123
2124 return (!stream.fail());
2125}
2126#endif // USE_PANDAFILESTREAM
2127
2128#ifdef USE_PANDAFILESTREAM
2129/**
2130 * Opens the indicated pifstream for writing the file, if possible. Returns
2131 * true if successful, false otherwise. This requires the setting of the
2132 * set_text()/set_binary() flags to open the file appropriately as indicated;
2133 * it is an error to call open_read() without first calling one of set_text()
2134 * or set_binary().
2135 */
2136bool Filename::
2137open_append(pofstream &stream) const {
2138 assert(!get_pattern());
2139 assert(is_binary_or_text());
2140
2141 ios_openmode open_mode = ios::app;
2142
2143#ifdef HAVE_IOS_BINARY
2144 // For some reason, some systems (like Irix) don't define ios::binary.
2145 if (!is_text()) {
2146 open_mode |= ios::binary;
2147 }
2148#endif
2149
2150 stream.clear();
2151 string os_specific = to_os_specific();
2152 stream.open(os_specific.c_str(), open_mode);
2153
2154 return (!stream.fail());
2155}
2156#endif // USE_PANDAFILESTREAM
2157
2158#ifdef USE_PANDAFILESTREAM
2159/**
2160 * Opens the indicated fstream for read/write access to the file, if possible.
2161 * Returns true if successful, false otherwise. This requires the setting of
2162 * the set_text()/set_binary() flags to open the file appropriately as
2163 * indicated; it is an error to call open_read_write() without first calling
2164 * one of set_text() or set_binary().
2165 */
2166bool Filename::
2167open_read_write(pfstream &stream, bool truncate) const {
2168 assert(!get_pattern());
2169 assert(is_binary_or_text());
2170
2171 ios_openmode open_mode = ios::out | ios::in;
2172
2173 if (truncate) {
2174 open_mode |= ios::trunc;
2175 }
2176
2177 // Since ios::in also seems to imply ios::nocreate (!), we must guarantee
2178 // the file already exists before we try to open it.
2179 if (!exists()) {
2180 touch();
2181 }
2182
2183#ifdef HAVE_IOS_BINARY
2184 // For some reason, some systems (like Irix) don't define ios::binary.
2185 if (!is_text()) {
2186 open_mode |= ios::binary;
2187 }
2188#endif
2189
2190 stream.clear();
2191 string os_specific = to_os_specific();
2192 stream.open(os_specific.c_str(), open_mode);
2193
2194 return (!stream.fail());
2195}
2196#endif // USE_PANDAFILESTREAM
2197
2198#ifdef USE_PANDAFILESTREAM
2199/**
2200 * Opens the indicated pfstream for reading and writing the file, if possible;
2201 * writes are appended to the end of the file. Returns true if successful,
2202 * false otherwise. This requires the setting of the set_text()/set_binary()
2203 * flags to open the file appropriately as indicated; it is an error to call
2204 * open_read() without first calling one of set_text() or set_binary().
2205 */
2206bool Filename::
2207open_read_append(pfstream &stream) const {
2208 assert(!get_pattern());
2209 assert(is_binary_or_text());
2210
2211 ios_openmode open_mode = ios::app | ios::in;
2212
2213#ifdef HAVE_IOS_BINARY
2214 // For some reason, some systems (like Irix) don't define ios::binary.
2215 if (!is_text()) {
2216 open_mode |= ios::binary;
2217 }
2218#endif
2219
2220 stream.clear();
2221 string os_specific = to_os_specific();
2222 stream.open(os_specific.c_str(), open_mode);
2223
2224 return (!stream.fail());
2225}
2226#endif // USE_PANDAFILESTREAM
2227
2228/**
2229 * Updates the modification time of the file to the current time. If the file
2230 * does not already exist, it will be created. Returns true if successful,
2231 * false if there is an error.
2232 */
2233bool Filename::
2234touch() const {
2235 assert(!get_pattern());
2236#ifdef WIN32_VC
2237 // In Windows, we have to use the Windows API to do this reliably.
2238
2239 // First, guarantee the file exists (and also get its handle).
2240 wstring os_specific = to_os_specific_w();
2241 HANDLE fhandle;
2242 fhandle = CreateFileW(os_specific.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE,
2243 nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
2244 if (fhandle == INVALID_HANDLE_VALUE) {
2245 return false;
2246 }
2247
2248 // Now update the file time and date.
2249 SYSTEMTIME sysnow;
2250 FILETIME ftnow;
2251 GetSystemTime(&sysnow);
2252 if (!SystemTimeToFileTime(&sysnow, &ftnow)) {
2253 CloseHandle(fhandle);
2254 return false;
2255 }
2256
2257 if (!SetFileTime(fhandle, nullptr, nullptr, &ftnow)) {
2258 CloseHandle(fhandle);
2259 return false;
2260 }
2261
2262 CloseHandle(fhandle);
2263 return true;
2264
2265#elif defined(PHAVE_UTIME_H)
2266 // Most Unix systems can do this explicitly.
2267
2268 string os_specific = to_os_specific();
2269#ifdef HAVE_CYGWIN
2270 // In the Cygwin case, it seems we need to be sure to use the Cygwin-style
2271 // name; some broken utime() implementation. That's almost the same thing
2272 // as the original Panda-style name, but not exactly, so we first convert
2273 // the Panda name to a Windows name, then convert it back to Cygwin, to
2274 // ensure we get it exactly right by Cygwin rules.
2275 {
2276 char result[4096] = "";
2277 cygwin_conv_to_posix_path(os_specific.c_str(), result);
2278 os_specific = result;
2279 }
2280#endif // HAVE_CYGWIN
2281 int result = utime(os_specific.c_str(), nullptr);
2282 if (result < 0) {
2283 if (errno == ENOENT) {
2284 // So the file doesn't already exist; create it.
2285 int fd = creat(os_specific.c_str(), 0666);
2286 if (fd < 0) {
2287 perror(os_specific.c_str());
2288 return false;
2289 }
2290 close(fd);
2291 return true;
2292 }
2293 perror(os_specific.c_str());
2294 return false;
2295 }
2296 return true;
2297#else // WIN32, PHAVE_UTIME_H
2298 // Other systems may not have an explicit control over the modification
2299 // time. For these systems, we'll just temporarily open the file in append
2300 // mode, then close it again (it gets closed when the pfstream goes out of
2301 // scope).
2302 pfstream file;
2303 return open_append(file);
2304#endif // WIN32, PHAVE_UTIME_H
2305}
2306
2307/**
2308 * Changes directory to the specified location. Returns true if successful,
2309 * false if failure.
2310 */
2311bool Filename::
2312chdir() const {
2313#ifdef WIN32_VC
2314 wstring os_specific = to_os_specific_w();
2315 return (_wchdir(os_specific.c_str()) >= 0);
2316#else
2317 string os_specific = to_os_specific();
2318 return (::chdir(os_specific.c_str()) >= 0);
2319#endif // WIN32_VC
2320}
2321
2322/**
2323 * Permanently deletes the file associated with the filename, if possible.
2324 * Returns true if successful, false if failure (for instance, because the
2325 * file did not exist, or because permissions were inadequate).
2326 */
2327bool Filename::
2328unlink() const {
2329 assert(!get_pattern());
2330#ifdef WIN32_VC
2331 // Windows can't delete a file if it's read-only. Weird.
2332 wstring os_specific = to_os_specific_w();
2333 _wchmod(os_specific.c_str(), 0644);
2334 return (_wunlink(os_specific.c_str()) == 0);
2335#else
2336 string os_specific = to_os_specific();
2337 return (::unlink(os_specific.c_str()) == 0);
2338#endif // WIN32_VC
2339}
2340
2341
2342/**
2343 * Renames the file to the indicated new filename. If the new filename is in
2344 * a different directory, this will perform a move. Returns true if
2345 * successful, false on failure.
2346 */
2347bool Filename::
2348rename_to(const Filename &other) const {
2349 assert(!get_pattern());
2350
2351 if (*this == other) {
2352 // Trivial success.
2353 return true;
2354 }
2355
2356#ifdef WIN32_VC
2357 wstring os_specific = to_os_specific_w();
2358 wstring other_os_specific = other.to_os_specific_w();
2359
2360 if (_wrename(os_specific.c_str(),
2361 other_os_specific.c_str()) == 0) {
2362 // Successfully renamed.
2363 return true;
2364 }
2365
2366 // The above might fail if we have tried to move a file to a different
2367 // filesystem. In this case, copy the file into the same directory first,
2368 // and then rename it.
2369 string dirname = other.get_dirname();
2370 if (dirname.empty()) {
2371 dirname = ".";
2372 }
2373 Filename temp = Filename::temporary(dirname, "");
2374 temp.set_binary();
2375 if (!Filename::binary_filename(*this).copy_to(temp)) {
2376 return false;
2377 }
2378
2379 wstring temp_os_specific = temp.to_os_specific_w();
2380 if (_wrename(temp_os_specific.c_str(),
2381 other_os_specific.c_str()) == 0) {
2382 // Successfully renamed.
2383 unlink();
2384 return true;
2385 }
2386
2387 // Try unlinking the target first.
2388 other.unlink();
2389 if (_wrename(temp_os_specific.c_str(),
2390 other_os_specific.c_str()) == 0) {
2391 // Successfully renamed.
2392 unlink();
2393 return true;
2394 }
2395#else // WIN32_VC
2396 string os_specific = to_os_specific();
2397 string other_os_specific = other.to_os_specific();
2398
2399 if (rename(os_specific.c_str(),
2400 other_os_specific.c_str()) == 0) {
2401 // Successfully renamed.
2402 return true;
2403 }
2404
2405 // The above might fail if we have tried to move a file to a different
2406 // filesystem. In this case, copy the file into the same directory first,
2407 // and then rename it.
2408 string dirname = other.get_dirname();
2409 if (dirname.empty()) {
2410 dirname = ".";
2411 }
2412 Filename temp = Filename::temporary(dirname, "");
2413 temp.set_binary();
2414 if (!Filename::binary_filename(*this).copy_to(temp)) {
2415 return false;
2416 }
2417
2418 string temp_os_specific = temp.to_os_specific();
2419 if (rename(temp_os_specific.c_str(),
2420 other_os_specific.c_str()) == 0) {
2421 // Successfully renamed.
2422 unlink();
2423 return true;
2424 }
2425
2426 // Try unlinking the target first.
2427 other.unlink();
2428 if (rename(temp_os_specific.c_str(),
2429 other_os_specific.c_str()) == 0) {
2430 // Successfully renamed.
2431 unlink();
2432 return true;
2433 }
2434#endif // WIN32_VC
2435
2436 // Failed.
2437 temp.unlink();
2438 return false;
2439}
2440
2441/**
2442 * Copies the file to the indicated new filename, by reading the contents and
2443 * writing it to the new file. Returns true if successful, false on failure.
2444 * The copy is always binary, regardless of the filename settings.
2445 */
2446bool Filename::
2447copy_to(const Filename &other) const {
2448 Filename this_filename = Filename::binary_filename(*this);
2449 pifstream in;
2450 if (!this_filename.open_read(in)) {
2451 return false;
2452 }
2453
2454 Filename other_filename = Filename::binary_filename(other);
2455 pofstream out;
2456 if (!other_filename.open_write(out)) {
2457 return false;
2458 }
2459
2460 static const size_t buffer_size = 4096;
2461 char buffer[buffer_size];
2462
2463 in.read(buffer, buffer_size);
2464 size_t count = in.gcount();
2465 while (count != 0) {
2466 out.write(buffer, count);
2467 if (out.fail()) {
2468 other.unlink();
2469 return false;
2470 }
2471 in.read(buffer, buffer_size);
2472 count = in.gcount();
2473 }
2474
2475 if (!in.eof()) {
2476 other.unlink();
2477 return false;
2478 }
2479
2480 return true;
2481}
2482
2483/**
2484 * Creates all the directories in the path to the file specified in the
2485 * filename, except for the basename itself. This assumes that the Filename
2486 * contains the name of a file, not a directory name; it ensures that the
2487 * directory containing the file exists.
2488 *
2489 * However, if the filename ends in a slash, it assumes the Filename
2490 * represents the name of a directory, and creates all the paths.
2491 */
2492bool Filename::
2493make_dir() const {
2494 assert(!get_pattern());
2495 if (empty()) {
2496 return false;
2497 }
2498 Filename path;
2499 if (_filename[_filename.length() - 1] == '/') {
2500 // The Filename ends in a slash; it represents a directory.
2501 path = (*this);
2502
2503 } else {
2504 // The Filename does not end in a slash; it represents a file.
2505 path = get_dirname();
2506 }
2507
2508 if (path.empty()) {
2509 return false;
2510 }
2511 string dirname = path.get_fullpath();
2512
2513 // First, make sure everything up to the last path is known. We don't care
2514 // too much if any of these fail; maybe they failed because the directory
2515 // was already there.
2516 size_t slash = dirname.find('/');
2517 while (slash != string::npos) {
2518 Filename component(dirname.substr(0, slash));
2519#ifdef WIN32_VC
2520 wstring os_specific = component.to_os_specific_w();
2521 _wmkdir(os_specific.c_str());
2522#else
2523 string os_specific = component.to_os_specific();
2524 ::mkdir(os_specific.c_str(), 0777);
2525#endif // WIN32_VC
2526 slash = dirname.find('/', slash + 1);
2527 }
2528
2529 // Now make the last one, and check the return value.
2530 Filename component(dirname);
2531#ifdef WIN32_VC
2532 wstring os_specific = component.to_os_specific_w();
2533 int result = _wmkdir(os_specific.c_str());
2534#else
2535 string os_specific = component.to_os_specific();
2536 int result = ::mkdir(os_specific.c_str(), 0777);
2537#endif // WIN32_VC
2538
2539 return (result == 0);
2540}
2541
2542/**
2543 * Creates the directory named by this filename. Unlike make_dir(), this
2544 * assumes that the Filename contains the directory name itself. Also, parent
2545 * directories are not automatically created; this function fails if any
2546 * parent directory is missing.
2547 */
2548bool Filename::
2549mkdir() const {
2550#ifdef WIN32_VC
2551 wstring os_specific = to_os_specific_w();
2552 int result = _wmkdir(os_specific.c_str());
2553#else
2554 string os_specific = to_os_specific();
2555 int result = ::mkdir(os_specific.c_str(), 0777);
2556#endif // WIN32_VC
2557
2558 return (result == 0);
2559}
2560
2561/**
2562 * The inverse of mkdir(): this removes the directory named by this Filename,
2563 * if it is in fact a directory.
2564 */
2565bool Filename::
2566rmdir() const {
2567#ifdef WIN32_VC
2568 wstring os_specific = to_os_specific_w();
2569
2570 int result = _wrmdir(os_specific.c_str());
2571 if (result != 0) {
2572 // Windows may require the directory to be writable before we can remove
2573 // it.
2574 _wchmod(os_specific.c_str(), 0777);
2575 result = _wrmdir(os_specific.c_str());
2576 }
2577
2578#else // WIN32_VC
2579 string os_specific = to_os_specific();
2580 int result = ::rmdir(os_specific.c_str());
2581#endif // WIN32_VC
2582
2583 return (result == 0);
2584}
2585
2586/**
2587 * Returns a hash code that attempts to be mostly unique for different
2588 * Filenames.
2589 */
2590int Filename::
2591get_hash() const {
2592 static const int primes[] = {
2593 2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
2594 31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
2595 73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
2596 127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
2597 179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
2598 233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
2599 283, 293, 307, 311, 313, 317, 331, 337, 347, 349,
2600 353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
2601 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
2602 467, 479, 487, 491, 499, 503, 509, 521, 523, 541,
2603 547, 557, 563, 569, 571, 577, 587, 593, 599, 601,
2604 607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
2605 661, 673, 677, 683, 691, 701, 709, 719, 727, 733,
2606 739, 743, 751, 757, 761, 769, 773, 787, 797, 809,
2607 811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
2608 877, 881, 883, 887, 907, 911, 919, 929, 937, 941,
2609 947, 953, 967, 971, 977, 983, 991, 997
2610 };
2611 static const size_t num_primes = sizeof(primes) / sizeof(int);
2612
2613 int hash = 0;
2614 for (size_t i = 0; i < _filename.size(); ++i) {
2615 hash += (int)_filename[i] * primes[i % num_primes];
2616 }
2617
2618 return hash;
2619}
2620
2621
2622/**
2623 * Uses native file-locking mechanisms to atomically replace the contents of a
2624 * (small) file with the specified contents, assuming it hasn't changed since
2625 * the last time the file was read.
2626 *
2627 * This is designed to be similar to AtomicAdjust::compare_and_exchange().
2628 * The method writes new_contents to the file, completely replacing the
2629 * original contents; but only if the original contents exactly matched
2630 * old_contents. If the file was modified, returns true. If, however, the
2631 * original contents of the file did not exactly match old_contents, then the
2632 * file is not modified, and false is returned. In either case, orig_contents
2633 * is filled with the original contents of the file.
2634 *
2635 * If the file does not exist, it is implicitly created, and its original
2636 * contents are empty.
2637 *
2638 * If an I/O error occurs on write, some of the file may or may not have been
2639 * written, and false is returned.
2640 *
2641 * Expressed in pseudo-code, the logic is:
2642 *
2643 * orig_contents = file.read(); if (orig_contents == old_contents) {
2644 * file.write(new_contents); return true; } return false;
2645 *
2646 * The operation is guaranteed to be atomic only if the only operations that
2647 * read and write to this file are atomic_compare_and_exchange_contents() and
2648 * atomic_read_contents().
2649 */
2650bool Filename::
2651atomic_compare_and_exchange_contents(string &orig_contents,
2652 const string &old_contents,
2653 const string &new_contents) const {
2654#ifdef WIN32_VC
2655 wstring os_specific = to_os_specific_w();
2656 HANDLE hfile = CreateFileW(os_specific.c_str(), GENERIC_READ | GENERIC_WRITE,
2657 0, nullptr, OPEN_ALWAYS,
2658 FILE_ATTRIBUTE_NORMAL, nullptr);
2659 while (hfile == INVALID_HANDLE_VALUE) {
2660 DWORD error = GetLastError();
2661 if (error == ERROR_SHARING_VIOLATION) {
2662 // If the file is locked by another process, yield and try again.
2663 Sleep(0);
2664 hfile = CreateFileW(os_specific.c_str(), GENERIC_READ | GENERIC_WRITE,
2665 0, nullptr, OPEN_ALWAYS,
2666 FILE_ATTRIBUTE_NORMAL, nullptr);
2667 } else {
2668 cerr << "Couldn't open file: " << os_specific
2669 << ", error " << error << "\n";
2670 return false;
2671 }
2672 }
2673
2674 if (hfile == INVALID_HANDLE_VALUE) {
2675 cerr << "Couldn't open file: " << os_specific
2676 << ", error " << GetLastError() << "\n";
2677 return false;
2678 }
2679
2680 static const size_t buf_size = 512;
2681 char buf[buf_size];
2682
2683 orig_contents = string();
2684
2685 DWORD bytes_read;
2686 if (!ReadFile(hfile, buf, buf_size, &bytes_read, nullptr)) {
2687 cerr << "Error reading file: " << os_specific
2688 << ", error " << GetLastError() << "\n";
2689 CloseHandle(hfile);
2690 return false;
2691 }
2692 while (bytes_read > 0) {
2693 orig_contents += string(buf, bytes_read);
2694
2695 if (!ReadFile(hfile, buf, buf_size, &bytes_read, nullptr)) {
2696 cerr << "Error reading file: " << os_specific
2697 << ", error " << GetLastError() << "\n";
2698 CloseHandle(hfile);
2699 return false;
2700 }
2701 }
2702
2703 bool match = false;
2704 if (orig_contents == old_contents) {
2705 match = true;
2706 SetFilePointer(hfile, 0, 0, FILE_BEGIN);
2707 DWORD bytes_written;
2708 if (!WriteFile(hfile, new_contents.data(), new_contents.size(),
2709 &bytes_written, nullptr)) {
2710 cerr << "Error writing file: " << os_specific
2711 << ", error " << GetLastError() << "\n";
2712 CloseHandle(hfile);
2713 return false;
2714 }
2715 }
2716
2717 CloseHandle(hfile);
2718 return match;
2719
2720#else // WIN32_VC
2721 string os_specific = to_os_specific();
2722 int fd = open(os_specific.c_str(), O_RDWR | O_CREAT, 0666);
2723 if (fd < 0) {
2724 perror(os_specific.c_str());
2725 return false;
2726 }
2727
2728 static const size_t buf_size = 512;
2729 char buf[buf_size];
2730
2731 orig_contents = string();
2732
2733#ifdef PHAVE_LOCKF
2734 if (lockf(fd, F_LOCK, 0) != 0) {
2735#else
2736 if (flock(fd, LOCK_EX) != 0) {
2737#endif
2738 perror(os_specific.c_str());
2739 close(fd);
2740 return false;
2741 }
2742
2743 ssize_t bytes_read = read(fd, buf, buf_size);
2744 while (bytes_read > 0) {
2745 orig_contents += string(buf, bytes_read);
2746 bytes_read = read(fd, buf, buf_size);
2747 }
2748
2749 if (bytes_read < 0) {
2750 perror(os_specific.c_str());
2751 close(fd);
2752 return false;
2753 }
2754
2755 bool match = false;
2756 if (orig_contents == old_contents) {
2757 match = true;
2758 lseek(fd, 0, SEEK_SET);
2759 ssize_t bytes_written = write(fd, new_contents.data(), new_contents.size());
2760 if (bytes_written < 0) {
2761 perror(os_specific.c_str());
2762 close(fd);
2763 return false;
2764 }
2765 }
2766
2767 if (close(fd) < 0) {
2768 perror(os_specific.c_str());
2769 return false;
2770 }
2771
2772 return match;
2773#endif // WIN32_VC
2774}
2775
2776/**
2777 * Uses native file-locking mechanisms to atomically read the contents of a
2778 * (small) file. This is the only way to read a file protected by
2779 * atomic_compare_and_exchange_contents(), and be confident that the read
2780 * operation is actually atomic with respect to that method.
2781 *
2782 * If the file does not exist, it is implicitly created, and its contents are
2783 * empty.
2784 *
2785 * If the file is read successfully, fills its contents in the indicated
2786 * string, and returns true. If the file cannot be read, returns false.
2787 */
2788bool Filename::
2789atomic_read_contents(string &contents) const {
2790#ifdef WIN32_VC
2791 wstring os_specific = to_os_specific_w();
2792 HANDLE hfile = CreateFileW(os_specific.c_str(), GENERIC_READ,
2793 FILE_SHARE_READ, nullptr, OPEN_ALWAYS,
2794 FILE_ATTRIBUTE_NORMAL, nullptr);
2795 while (hfile == INVALID_HANDLE_VALUE) {
2796 DWORD error = GetLastError();
2797 if (error == ERROR_SHARING_VIOLATION) {
2798 // If the file is locked by another process, yield and try again.
2799 Sleep(0);
2800 hfile = CreateFileW(os_specific.c_str(), GENERIC_READ,
2801 FILE_SHARE_READ, nullptr, OPEN_ALWAYS,
2802 FILE_ATTRIBUTE_NORMAL, nullptr);
2803 } else {
2804 cerr << "Couldn't open file: " << os_specific
2805 << ", error " << error << "\n";
2806 return false;
2807 }
2808 }
2809
2810 static const size_t buf_size = 512;
2811 char buf[buf_size];
2812
2813 contents = string();
2814
2815 DWORD bytes_read;
2816 if (!ReadFile(hfile, buf, buf_size, &bytes_read, nullptr)) {
2817 cerr << "Error reading file: " << os_specific
2818 << ", error " << GetLastError() << "\n";
2819 CloseHandle(hfile);
2820 return false;
2821 }
2822 while (bytes_read > 0) {
2823 contents += string(buf, bytes_read);
2824
2825 if (!ReadFile(hfile, buf, buf_size, &bytes_read, nullptr)) {
2826 cerr << "Error reading file: " << os_specific
2827 << ", error " << GetLastError() << "\n";
2828 CloseHandle(hfile);
2829 return false;
2830 }
2831 }
2832
2833 CloseHandle(hfile);
2834 return true;
2835
2836#else // WIN32_VC
2837 string os_specific = to_os_specific();
2838 int fd = open(os_specific.c_str(), O_RDWR | O_CREAT, 0666);
2839 if (fd < 0) {
2840 perror(os_specific.c_str());
2841 return false;
2842 }
2843
2844 static const size_t buf_size = 512;
2845 char buf[buf_size];
2846
2847 contents = string();
2848
2849#ifdef PHAVE_LOCKF
2850 if (lockf(fd, F_LOCK, 0) != 0) {
2851#else
2852 if (flock(fd, LOCK_EX) != 0) {
2853#endif
2854 perror(os_specific.c_str());
2855 close(fd);
2856 return false;
2857 }
2858
2859 ssize_t bytes_read = read(fd, buf, buf_size);
2860 while (bytes_read > 0) {
2861 contents += string(buf, bytes_read);
2862 bytes_read = read(fd, buf, buf_size);
2863 }
2864
2865 if (bytes_read < 0) {
2866 perror(os_specific.c_str());
2867 close(fd);
2868 return false;
2869 }
2870
2871 close(fd);
2872 return true;
2873#endif // WIN32_VC
2874}
2875
2876/**
2877 * After the string has been reassigned, search for the slash marking the
2878 * beginning of the basename, and set _dirname_end and _basename_start
2879 * correctly.
2880 */
2881void Filename::
2882locate_basename() {
2883 // Scan for the last slash, which marks the end of the directory part.
2884 if (_filename.empty()) {
2885 _dirname_end = 0;
2886 _basename_start = 0;
2887
2888 } else {
2889
2890 string::size_type slash = _filename.rfind('/');
2891 if (slash != string::npos) {
2892 _basename_start = slash + 1;
2893 _dirname_end = _basename_start;
2894
2895 // One exception: in case there are multiple slashes in a row, we want
2896 // to treat them as a single slash. The directory therefore actually
2897 // ends at the first of these; back up a bit.
2898 while (_dirname_end > 0 && _filename[_dirname_end-1] == '/') {
2899 _dirname_end--;
2900 }
2901
2902 // Another exception: if the dirname was nothing but slashes, it was the
2903 // root directory, or itself. In this case the dirname does include
2904 // the terminal slash (of course).
2905 if (_dirname_end == 0) {
2906 _dirname_end = 1;
2907 }
2908
2909 } else {
2910 _dirname_end = 0;
2911 _basename_start = 0;
2912 }
2913 }
2914
2915 // Now:
2916
2917 // _dirname_end is the last slash character, or 0 if there are no slash
2918 // characters.
2919
2920 // _basename_start is the character after the last slash character, or 0 if
2921 // there are no slash characters.
2922}
2923
2924
2925/**
2926 * Once the end of the directory prefix has been found, and _dirname_end and
2927 * _basename_start are set correctly, search for the dot marking the beginning
2928 * of the extension, and set _basename_end and _extension_start correctly.
2929 */
2930void Filename::
2931locate_extension() {
2932 // Now scan for the last dot after that slash.
2933 if (_filename.empty()) {
2934 _basename_end = string::npos;
2935 _extension_start = string::npos;
2936
2937 } else {
2938 string::size_type dot = _filename.length() - 1;
2939
2940 while (dot+1 > _basename_start && _filename[dot] != '.') {
2941 --dot;
2942 }
2943
2944 if (dot+1 > _basename_start) {
2945 _basename_end = dot;
2946 _extension_start = dot + 1;
2947 } else {
2948 _basename_end = string::npos;
2949 _extension_start = string::npos;
2950 }
2951 }
2952
2953 // Now:
2954
2955 // _basename_end is the last dot, or npos if there is no dot.
2956
2957 // _extension_start is the character after the last dot, or npos if there is
2958 // no dot.
2959}
2960
2961/**
2962 * Identifies the part of the filename that contains the sequence of hash
2963 * marks, if any.
2964 */
2965void Filename::
2966locate_hash() {
2967 if (!get_pattern()) {
2968 // If it's not a pattern-type filename, these are always set to the end of
2969 // the string.
2970 _hash_end = string::npos;
2971 _hash_start = string::npos;
2972
2973 } else {
2974 // If it is a pattern-type filename, we must search for the hash marks,
2975 // which could be anywhere (but are usually toward the end).
2976 _hash_end = _filename.rfind('#');
2977 if (_hash_end == string::npos) {
2978 _hash_end = string::npos;
2979 _hash_start = string::npos;
2980
2981 } else {
2982 _hash_start = _hash_end;
2983 ++_hash_end;
2984 while (_hash_start > 0 && _filename[_hash_start - 1] == '#') {
2985 --_hash_start;
2986 }
2987 }
2988 }
2989}
2990
2991
2992/**
2993 * Returns the length of the longest common initial substring of this string
2994 * and the other one that ends in a slash. This is the lowest directory
2995 * common to both filenames.
2996 */
2997size_t Filename::
2998get_common_prefix(const string &other) const {
2999 size_t len = 0;
3000
3001 // First, get the length of the common initial substring.
3002 while (len < length() && len < other.length() &&
3003 _filename[len] == other[len]) {
3004 len++;
3005 }
3006
3007 // Now insist that it ends in a slash.
3008 while (len > 0 && _filename[len-1] != '/') {
3009 len--;
3010 }
3011
3012 return len;
3013}
3014
3015/**
3016 * Returns the number of non-consecutive slashes in the indicated string, not
3017 * counting a terminal slash.
3018 */
3019int Filename::
3020count_slashes(const string &str) {
3021 int count = 0;
3022 string::const_iterator si;
3023 si = str.begin();
3024
3025 while (si != str.end()) {
3026 if (*si == '/') {
3027 count++;
3028
3029 // Skip consecutive slashes.
3030 ++si;
3031 while (*si == '/') {
3032 ++si;
3033 }
3034 if (si == str.end()) {
3035 // Oops, that was a terminal slash. Don't count it.
3036 count--;
3037 }
3038
3039 } else {
3040 ++si;
3041 }
3042 }
3043
3044 return count;
3045}
3046
3047
3048/**
3049 * The recursive implementation of make_canonical().
3050 */
3051bool Filename::
3052r_make_canonical(const Filename &cwd) {
3053 if (get_fullpath() == "/") {
3054 // If we reached the root, the whole path doesn't exist. Report failure.
3055 return false;
3056 }
3057
3058#ifdef WIN32_VC
3059 // First, try to cd to the filename directly.
3060 wstring os_specific = to_os_specific_w();
3061 if (_wchdir(os_specific.c_str()) >= 0) {
3062 // That worked, save the full path string.
3064
3065 // And restore the current working directory.
3066 wstring osdir = cwd.to_os_specific_w();
3067 if (_wchdir(osdir.c_str()) < 0) {
3068 cerr << "Error! Cannot change back to " << osdir << "\n";
3069 }
3070 return true;
3071 }
3072#else // WIN32_VC
3073 // First, try to cd to the filename directly.
3074 string os_specific = to_os_specific();
3075 if (::chdir(os_specific.c_str()) >= 0) {
3076 // That worked, save the full path string.
3078
3079 // And restore the current working directory.
3080 string osdir = cwd.to_os_specific();
3081 if (::chdir(osdir.c_str()) < 0) {
3082 cerr << "Error! Cannot change back to " << osdir << "\n";
3083 }
3084 return true;
3085 }
3086#endif // WIN32_VC
3087
3088 // That didn't work; maybe it's not a directory. Recursively go to the
3089 // directory above.
3090
3091 Filename dir(get_dirname());
3092
3093 if (dir.empty()) {
3094 // No dirname means the file is in this directory.
3095 set_dirname(cwd);
3096 return true;
3097 }
3098
3099 if (!dir.r_make_canonical(cwd)) {
3100 return false;
3101 }
3102 set_dirname(dir);
3103 return true;
3104}
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static Pointer compare_and_exchange_ptr(Pointer &mem, Pointer old_value, Pointer new_value)
Atomic compare and exchange.
static Pointer get_ptr(const Pointer &var)
Atomically retrieves the snapshot value of the indicated variable.
This class stores a list of directories that can be searched, in order, to locate a particular file.
Definition dSearchPath.h:28
Filename find_file(const Filename &filename) const
Searches all the directories in the search list for the indicated file, in order.
get_num_directories
Returns the number of directories on the search list.
Definition dSearchPath.h:76
get_directory
Returns the nth directory on the search list.
Definition dSearchPath.h:76
static std::string expand_string(const std::string &str)
Reads the string, looking for environment variable names marked by a $.
get_cwd
Returns the name of the current working directory.
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
void set_basename(const std::string &s)
Replaces the basename part of the filename.
Definition filename.cxx:754
bool open_append(std::ofstream &stream) const
Opens the indicated ofstream for writing the file, if possible.
void set_type(Type type)
Sets the type of the file represented by the filename.
Definition filename.I:468
Filename()
Creates an empty Filename.
Definition filename.I:85
bool copy_to(const Filename &other) const
Copies the file to the indicated new filename, by reading the contents and writing it to the new file...
Filename get_filename_index(int index) const
If the pattern flag is set for this Filename and the filename string actually includes a sequence of ...
Definition filename.cxx:836
bool is_text() const
Returns true if the Filename has been indicated to represent a text file via a previous call to set_t...
Definition filename.I:456
std::string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
bool open_read(std::ifstream &stream) const
Opens the indicated ifstream for reading the file, if possible.
static const Filename & get_home_directory()
Returns a path to the user's home directory, if such a thing makes sense in the current OS,...
Definition filename.cxx:474
bool rmdir() const
The inverse of mkdir(): this removes the directory named by this Filename, if it is in fact a directo...
static Filename from_os_specific_w(const std::wstring &os_specific, Type type=T_general)
The wide-string variant of from_os_specific().
Definition filename.cxx:394
bool unlink() const
Permanently deletes the file associated with the filename, if possible.
void standardize()
Converts the filename to standard form by replacing consecutive slashes with a single slash,...
Definition filename.cxx:900
void set_dirname(const std::string &s)
Replaces the directory part of the filename.
Definition filename.cxx:704
void set_binary()
Indicates that the filename represents a binary file.
Definition filename.I:414
bool is_binary_or_text() const
Returns true either is_binary() or is_text() is true; that is, that the filename has been specified a...
Definition filename.I:445
static TextEncoder::Encoding get_filesystem_encoding()
Specifies the default encoding to be used for all subsequent Filenames objects.
Definition filename.I:639
static const Filename & get_temp_directory()
Returns a path to a system-defined temporary directory.
Definition filename.cxx:539
bool chdir() const
Changes directory to the specified location.
bool touch() const
Updates the modification time of the file to the current time.
std::wstring to_os_specific_w() const
The wide-string variant on to_os_specific().
bool get_pattern() const
Returns the flag indicating whether this is a filename pattern.
Definition filename.I:519
bool make_true_case()
On a case-insensitive operating system (e.g.
std::string get_fullpath() const
Returns the entire filename: directory, basename, extension.
Definition filename.I:338
bool make_canonical()
Converts this filename to a canonical name by replacing the directory part with the fully-qualified d...
static Filename temporary(const std::string &dirname, const std::string &prefix, const std::string &suffix=std::string(), Type type=T_general)
Generates a temporary filename within the indicated directory, using the indicated prefix.
Definition filename.cxx:424
static Filename from_os_specific(const std::string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes,...
Definition filename.cxx:328
void set_extension(const std::string &s)
Replaces the file extension.
Definition filename.cxx:804
bool is_directory() const
Returns true if the filename exists on the physical disk and is a directory name, false otherwise.
std::wstring get_fullpath_w() const
Returns the entire filename as a wide-character string.
Definition filename.I:346
bool open_write(std::ofstream &stream, bool truncate=true) const
Opens the indicated ifstream for writing the file, if possible.
bool make_relative_to(Filename directory, bool allow_backups=true)
Adjusts this filename, which must be a fully-specified pathname beginning with a slash,...
std::string get_extension() const
Returns the file extension.
Definition filename.I:400
void set_fullpath(const std::string &s)
Replaces the entire filename: directory, basename, extension.
Definition filename.cxx:695
bool mkdir() const
Creates the directory named by this filename.
bool is_local() const
Returns true if the filename is local, e.g.
Definition filename.I:549
std::string get_dirname() const
Returns the directory part of the filename.
Definition filename.I:358
void make_absolute()
Converts the filename to a fully-qualified pathname from the root (if it is a relative pathname),...
Definition filename.cxx:968
Type get_type() const
Returns the type of the file represented by the filename, as previously set by set_type().
Definition filename.I:485
void set_pattern(bool pattern)
Sets the flag indicating whether this is a filename pattern.
Definition filename.I:503
bool exists() const
Returns true if the filename exists on the physical disk, false otherwise.
This class can be used to convert text between multiple representations, e.g.
Definition textEncoder.h:33
set_text
Changes the text that is stored in the encoder.
static int unicode_tolower(char32_t character)
Returns the uppercase equivalent of the given Unicode character.
void set_encoding(Encoding encoding)
Specifies how the string set via set_text() is to be interpreted.
Definition textEncoder.I:48
get_text
Returns the current text, as encoded via the current encoding system.
const std::wstring & get_wtext() const
Returns the text associated with the TextEncoder, as a wide-character string.
void set_wtext(const std::wstring &wtext)
Changes the text that is stored in the encoder.
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.