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"
17#include "executionEnvironment.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
56 using std::cerr;
57 using std::ios;
58 using std::string;
59 using 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;
67 TypeHandle Filename::_type_handle;
68
69#ifdef ANDROID
70 string 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
94 extern "C" void cygwin_conv_to_win32_path(const char *path, char *win32);
95 extern "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.
122 static const char *hosts_prefix = "/hosts/";
123 static size_t hosts_prefix_length = 7;
124
125 static 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
138 static 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
151 static 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
174 static 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
243 static 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
276 static 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;
396 encoder.set_encoding(get_filesystem_encoding());
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) {
408 Filename file = from_os_specific(ExecutionEnvironment::expand_string(os_specific),
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());
439 Filename result = Filename::from_os_specific(name);
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 */
473 const 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 */
538 const 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 */
588 const 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 */
644 const 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 */
694 void 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 */
703 void 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 */
753 void 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 */
765 void 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 */
782 void 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 */
803 void 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 */
855 void 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 */
871 void 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 */
899 void 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 */
967 void Filename::
968make_absolute() {
969 if (is_local()) {
970 make_absolute(ExecutionEnvironment::get_cwd());
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 */
982 void 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 */
1010 bool 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
1032 Filename cwd = ExecutionEnvironment::get_cwd();
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 */
1052 bool 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 */
1122 string 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;
1165 encoder.set_encoding(get_filesystem_encoding());
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 */
1181 string 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 */
1201 string 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;
1221 encoder.set_encoding(get_filesystem_encoding());
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 */
1235 string 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;
1252 encoder.set_encoding(get_filesystem_encoding());
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 */
1269 bool 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 */
1302 bool 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 */
1332 bool 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 */
1367 bool 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 */
1395 bool 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 */
1425 int 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 */
1589 bool 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 */
1648 bool 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 */
1697 int 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 */
1726 bool 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;
1753 encoder.set_encoding(get_filesystem_encoding());
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 */
1871 bool 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 */
1907 bool 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 */
1953 bool 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 */
1985 bool 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 */
2027 bool 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 */
2060 bool 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 */
2092 bool 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 */
2136 bool 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 */
2166 bool 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 */
2206 bool 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 */
2233 bool 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 if (close(fd) < 0) {
2291 perror(os_specific.c_str());
2292 }
2293 return true;
2294 }
2295 perror(os_specific.c_str());
2296 return false;
2297 }
2298 return true;
2299#else // WIN32, PHAVE_UTIME_H
2300 // Other systems may not have an explicit control over the modification
2301 // time. For these systems, we'll just temporarily open the file in append
2302 // mode, then close it again (it gets closed when the pfstream goes out of
2303 // scope).
2304 pfstream file;
2305 return open_append(file);
2306#endif // WIN32, PHAVE_UTIME_H
2307}
2308
2309/**
2310 * Changes directory to the specified location. Returns true if successful,
2311 * false if failure.
2312 */
2313 bool Filename::
2314chdir() const {
2315#ifdef WIN32_VC
2316 wstring os_specific = to_os_specific_w();
2317 return (_wchdir(os_specific.c_str()) >= 0);
2318#else
2319 string os_specific = to_os_specific();
2320 return (::chdir(os_specific.c_str()) >= 0);
2321#endif // WIN32_VC
2322}
2323
2324/**
2325 * Permanently deletes the file associated with the filename, if possible.
2326 * Returns true if successful, false if failure (for instance, because the
2327 * file did not exist, or because permissions were inadequate).
2328 */
2329 bool Filename::
2330unlink() const {
2331 assert(!get_pattern());
2332#ifdef WIN32_VC
2333 // Windows can't delete a file if it's read-only. Weird.
2334 wstring os_specific = to_os_specific_w();
2335 _wchmod(os_specific.c_str(), 0644);
2336 return (_wunlink(os_specific.c_str()) == 0);
2337#else
2338 string os_specific = to_os_specific();
2339 return (::unlink(os_specific.c_str()) == 0);
2340#endif // WIN32_VC
2341}
2342
2343
2344/**
2345 * Renames the file to the indicated new filename. If the new filename is in
2346 * a different directory, this will perform a move. Returns true if
2347 * successful, false on failure.
2348 */
2349 bool Filename::
2350rename_to(const Filename &other) const {
2351 assert(!get_pattern());
2352
2353 if (*this == other) {
2354 // Trivial success.
2355 return true;
2356 }
2357
2358#ifdef WIN32_VC
2359 wstring os_specific = to_os_specific_w();
2360 wstring other_os_specific = other.to_os_specific_w();
2361
2362 if (_wrename(os_specific.c_str(),
2363 other_os_specific.c_str()) == 0) {
2364 // Successfully renamed.
2365 return true;
2366 }
2367
2368 // The above might fail if we have tried to move a file to a different
2369 // filesystem. In this case, copy the file into the same directory first,
2370 // and then rename it.
2371 string dirname = other.get_dirname();
2372 if (dirname.empty()) {
2373 dirname = ".";
2374 }
2375 Filename temp = Filename::temporary(dirname, "");
2376 temp.set_binary();
2377 if (!Filename::binary_filename(*this).copy_to(temp)) {
2378 return false;
2379 }
2380
2381 wstring temp_os_specific = temp.to_os_specific_w();
2382 if (_wrename(temp_os_specific.c_str(),
2383 other_os_specific.c_str()) == 0) {
2384 // Successfully renamed.
2385 unlink();
2386 return true;
2387 }
2388
2389 // Try unlinking the target first.
2390 other.unlink();
2391 if (_wrename(temp_os_specific.c_str(),
2392 other_os_specific.c_str()) == 0) {
2393 // Successfully renamed.
2394 unlink();
2395 return true;
2396 }
2397#else // WIN32_VC
2398 string os_specific = to_os_specific();
2399 string other_os_specific = other.to_os_specific();
2400
2401 if (rename(os_specific.c_str(),
2402 other_os_specific.c_str()) == 0) {
2403 // Successfully renamed.
2404 return true;
2405 }
2406
2407 // The above might fail if we have tried to move a file to a different
2408 // filesystem. In this case, copy the file into the same directory first,
2409 // and then rename it.
2410 string dirname = other.get_dirname();
2411 if (dirname.empty()) {
2412 dirname = ".";
2413 }
2414 Filename temp = Filename::temporary(dirname, "");
2415 temp.set_binary();
2416 if (!Filename::binary_filename(*this).copy_to(temp)) {
2417 return false;
2418 }
2419
2420 string temp_os_specific = temp.to_os_specific();
2421 if (rename(temp_os_specific.c_str(),
2422 other_os_specific.c_str()) == 0) {
2423 // Successfully renamed.
2424 unlink();
2425 return true;
2426 }
2427
2428 // Try unlinking the target first.
2429 other.unlink();
2430 if (rename(temp_os_specific.c_str(),
2431 other_os_specific.c_str()) == 0) {
2432 // Successfully renamed.
2433 unlink();
2434 return true;
2435 }
2436#endif // WIN32_VC
2437
2438 // Failed.
2439 temp.unlink();
2440 return false;
2441}
2442
2443/**
2444 * Copies the file to the indicated new filename, by reading the contents and
2445 * writing it to the new file. Returns true if successful, false on failure.
2446 * The copy is always binary, regardless of the filename settings.
2447 */
2448 bool Filename::
2449copy_to(const Filename &other) const {
2450 Filename this_filename = Filename::binary_filename(*this);
2451 pifstream in;
2452 if (!this_filename.open_read(in)) {
2453 return false;
2454 }
2455
2456 Filename other_filename = Filename::binary_filename(other);
2457 pofstream out;
2458 if (!other_filename.open_write(out)) {
2459 return false;
2460 }
2461
2462 static const size_t buffer_size = 4096;
2463 char buffer[buffer_size];
2464
2465 in.read(buffer, buffer_size);
2466 size_t count = in.gcount();
2467 while (count != 0) {
2468 out.write(buffer, count);
2469 if (out.fail()) {
2470 other.unlink();
2471 return false;
2472 }
2473 in.read(buffer, buffer_size);
2474 count = in.gcount();
2475 }
2476
2477 if (!in.eof()) {
2478 other.unlink();
2479 return false;
2480 }
2481
2482 return true;
2483}
2484
2485/**
2486 * Creates all the directories in the path to the file specified in the
2487 * filename, except for the basename itself. This assumes that the Filename
2488 * contains the name of a file, not a directory name; it ensures that the
2489 * directory containing the file exists.
2490 *
2491 * However, if the filename ends in a slash, it assumes the Filename
2492 * represents the name of a directory, and creates all the paths.
2493 */
2494 bool Filename::
2495make_dir() const {
2496 assert(!get_pattern());
2497 if (empty()) {
2498 return false;
2499 }
2500 Filename path;
2501 if (_filename[_filename.length() - 1] == '/') {
2502 // The Filename ends in a slash; it represents a directory.
2503 path = (*this);
2504
2505 } else {
2506 // The Filename does not end in a slash; it represents a file.
2507 path = get_dirname();
2508 }
2509
2510 if (path.empty()) {
2511 return false;
2512 }
2513 string dirname = path.get_fullpath();
2514
2515 // First, make sure everything up to the last path is known. We don't care
2516 // too much if any of these fail; maybe they failed because the directory
2517 // was already there.
2518 size_t slash = dirname.find('/');
2519 while (slash != string::npos) {
2520 Filename component(dirname.substr(0, slash));
2521#ifdef WIN32_VC
2522 wstring os_specific = component.to_os_specific_w();
2523 _wmkdir(os_specific.c_str());
2524#else
2525 string os_specific = component.to_os_specific();
2526 ::mkdir(os_specific.c_str(), 0777);
2527#endif // WIN32_VC
2528 slash = dirname.find('/', slash + 1);
2529 }
2530
2531 // Now make the last one, and check the return value.
2532 Filename component(dirname);
2533#ifdef WIN32_VC
2534 wstring os_specific = component.to_os_specific_w();
2535 int result = _wmkdir(os_specific.c_str());
2536#else
2537 string os_specific = component.to_os_specific();
2538 int result = ::mkdir(os_specific.c_str(), 0777);
2539#endif // WIN32_VC
2540
2541 return (result == 0);
2542}
2543
2544/**
2545 * Creates the directory named by this filename. Unlike make_dir(), this
2546 * assumes that the Filename contains the directory name itself. Also, parent
2547 * directories are not automatically created; this function fails if any
2548 * parent directory is missing.
2549 */
2550 bool Filename::
2551mkdir() const {
2552#ifdef WIN32_VC
2553 wstring os_specific = to_os_specific_w();
2554 int result = _wmkdir(os_specific.c_str());
2555#else
2556 string os_specific = to_os_specific();
2557 int result = ::mkdir(os_specific.c_str(), 0777);
2558#endif // WIN32_VC
2559
2560 return (result == 0);
2561}
2562
2563/**
2564 * The inverse of mkdir(): this removes the directory named by this Filename,
2565 * if it is in fact a directory.
2566 */
2567 bool Filename::
2568rmdir() const {
2569#ifdef WIN32_VC
2570 wstring os_specific = to_os_specific_w();
2571
2572 int result = _wrmdir(os_specific.c_str());
2573 if (result != 0) {
2574 // Windows may require the directory to be writable before we can remove
2575 // it.
2576 _wchmod(os_specific.c_str(), 0777);
2577 result = _wrmdir(os_specific.c_str());
2578 }
2579
2580#else // WIN32_VC
2581 string os_specific = to_os_specific();
2582 int result = ::rmdir(os_specific.c_str());
2583#endif // WIN32_VC
2584
2585 return (result == 0);
2586}
2587
2588/**
2589 * Returns a hash code that attempts to be mostly unique for different
2590 * Filenames.
2591 */
2592 int Filename::
2593get_hash() const {
2594 static const int primes[] = {
2595 2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
2596 31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
2597 73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
2598 127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
2599 179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
2600 233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
2601 283, 293, 307, 311, 313, 317, 331, 337, 347, 349,
2602 353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
2603 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
2604 467, 479, 487, 491, 499, 503, 509, 521, 523, 541,
2605 547, 557, 563, 569, 571, 577, 587, 593, 599, 601,
2606 607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
2607 661, 673, 677, 683, 691, 701, 709, 719, 727, 733,
2608 739, 743, 751, 757, 761, 769, 773, 787, 797, 809,
2609 811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
2610 877, 881, 883, 887, 907, 911, 919, 929, 937, 941,
2611 947, 953, 967, 971, 977, 983, 991, 997
2612 };
2613 static const size_t num_primes = sizeof(primes) / sizeof(int);
2614
2615 int hash = 0;
2616 for (size_t i = 0; i < _filename.size(); ++i) {
2617 hash += (int)_filename[i] * primes[i % num_primes];
2618 }
2619
2620 return hash;
2621}
2622
2623
2624/**
2625 * Uses native file-locking mechanisms to atomically replace the contents of a
2626 * (small) file with the specified contents, assuming it hasn't changed since
2627 * the last time the file was read.
2628 *
2629 * This is designed to be similar to AtomicAdjust::compare_and_exchange().
2630 * The method writes new_contents to the file, completely replacing the
2631 * original contents; but only if the original contents exactly matched
2632 * old_contents. If the file was modified, returns true. If, however, the
2633 * original contents of the file did not exactly match old_contents, then the
2634 * file is not modified, and false is returned. In either case, orig_contents
2635 * is filled with the original contents of the file.
2636 *
2637 * If the file does not exist, it is implicitly created, and its original
2638 * contents are empty.
2639 *
2640 * If an I/O error occurs on write, some of the file may or may not have been
2641 * written, and false is returned.
2642 *
2643 * Expressed in pseudo-code, the logic is:
2644 *
2645 * orig_contents = file.read(); if (orig_contents == old_contents) {
2646 * file.write(new_contents); return true; } return false;
2647 *
2648 * The operation is guaranteed to be atomic only if the only operations that
2649 * read and write to this file are atomic_compare_and_exchange_contents() and
2650 * atomic_read_contents().
2651 */
2652 bool Filename::
2653atomic_compare_and_exchange_contents(string &orig_contents,
2654 const string &old_contents,
2655 const string &new_contents) const {
2656#ifdef WIN32_VC
2657 wstring os_specific = to_os_specific_w();
2658 HANDLE hfile = CreateFileW(os_specific.c_str(), GENERIC_READ | GENERIC_WRITE,
2659 0, nullptr, OPEN_ALWAYS,
2660 FILE_ATTRIBUTE_NORMAL, nullptr);
2661 while (hfile == INVALID_HANDLE_VALUE) {
2662 DWORD error = GetLastError();
2663 if (error == ERROR_SHARING_VIOLATION) {
2664 // If the file is locked by another process, yield and try again.
2665 Sleep(0);
2666 hfile = CreateFileW(os_specific.c_str(), GENERIC_READ | GENERIC_WRITE,
2667 0, nullptr, OPEN_ALWAYS,
2668 FILE_ATTRIBUTE_NORMAL, nullptr);
2669 } else {
2670 cerr << "Couldn't open file: " << os_specific
2671 << ", error " << error << "\n";
2672 return false;
2673 }
2674 }
2675
2676 if (hfile == INVALID_HANDLE_VALUE) {
2677 cerr << "Couldn't open file: " << os_specific
2678 << ", error " << GetLastError() << "\n";
2679 return false;
2680 }
2681
2682 static const size_t buf_size = 512;
2683 char buf[buf_size];
2684
2685 orig_contents = string();
2686
2687 DWORD bytes_read;
2688 if (!ReadFile(hfile, buf, buf_size, &bytes_read, nullptr)) {
2689 cerr << "Error reading file: " << os_specific
2690 << ", error " << GetLastError() << "\n";
2691 CloseHandle(hfile);
2692 return false;
2693 }
2694 while (bytes_read > 0) {
2695 orig_contents += string(buf, bytes_read);
2696
2697 if (!ReadFile(hfile, buf, buf_size, &bytes_read, nullptr)) {
2698 cerr << "Error reading file: " << os_specific
2699 << ", error " << GetLastError() << "\n";
2700 CloseHandle(hfile);
2701 return false;
2702 }
2703 }
2704
2705 bool match = false;
2706 if (orig_contents == old_contents) {
2707 match = true;
2708 SetFilePointer(hfile, 0, 0, FILE_BEGIN);
2709 DWORD bytes_written;
2710 if (!WriteFile(hfile, new_contents.data(), new_contents.size(),
2711 &bytes_written, nullptr)) {
2712 cerr << "Error writing file: " << os_specific
2713 << ", error " << GetLastError() << "\n";
2714 CloseHandle(hfile);
2715 return false;
2716 }
2717 }
2718
2719 CloseHandle(hfile);
2720 return match;
2721
2722#else // WIN32_VC
2723 string os_specific = to_os_specific();
2724 int fd = open(os_specific.c_str(), O_RDWR | O_CREAT, 0666);
2725 if (fd < 0) {
2726 perror(os_specific.c_str());
2727 return false;
2728 }
2729
2730 static const size_t buf_size = 512;
2731 char buf[buf_size];
2732
2733 orig_contents = string();
2734
2735#ifdef PHAVE_LOCKF
2736 if (lockf(fd, F_LOCK, 0) != 0) {
2737#else
2738 if (flock(fd, LOCK_EX) != 0) {
2739#endif
2740 perror(os_specific.c_str());
2741 if (close(fd) < 0) {
2742 perror(os_specific.c_str());
2743 }
2744 return false;
2745 }
2746
2747 ssize_t bytes_read = read(fd, buf, buf_size);
2748 while (bytes_read > 0) {
2749 orig_contents += string(buf, bytes_read);
2750 bytes_read = read(fd, buf, buf_size);
2751 }
2752
2753 if (bytes_read < 0) {
2754 perror(os_specific.c_str());
2755 if (close(fd) < 0) {
2756 perror(os_specific.c_str());
2757 }
2758 return false;
2759 }
2760
2761 bool match = false;
2762 if (orig_contents == old_contents) {
2763 match = true;
2764 lseek(fd, 0, SEEK_SET);
2765 ssize_t bytes_written = write(fd, new_contents.data(), new_contents.size());
2766 if (bytes_written < 0) {
2767 perror(os_specific.c_str());
2768 if (close(fd) < 0) {
2769 perror(os_specific.c_str());
2770 }
2771 return false;
2772 }
2773 }
2774
2775 if (close(fd) < 0) {
2776 perror(os_specific.c_str());
2777 return false;
2778 }
2779
2780 return match;
2781#endif // WIN32_VC
2782}
2783
2784/**
2785 * Uses native file-locking mechanisms to atomically read the contents of a
2786 * (small) file. This is the only way to read a file protected by
2787 * atomic_compare_and_exchange_contents(), and be confident that the read
2788 * operation is actually atomic with respect to that method.
2789 *
2790 * If the file does not exist, it is implicitly created, and its contents are
2791 * empty.
2792 *
2793 * If the file is read successfully, fills its contents in the indicated
2794 * string, and returns true. If the file cannot be read, returns false.
2795 */
2796 bool Filename::
2797atomic_read_contents(string &contents) const {
2798#ifdef WIN32_VC
2799 wstring os_specific = to_os_specific_w();
2800 HANDLE hfile = CreateFileW(os_specific.c_str(), GENERIC_READ,
2801 FILE_SHARE_READ, nullptr, OPEN_ALWAYS,
2802 FILE_ATTRIBUTE_NORMAL, nullptr);
2803 while (hfile == INVALID_HANDLE_VALUE) {
2804 DWORD error = GetLastError();
2805 if (error == ERROR_SHARING_VIOLATION) {
2806 // If the file is locked by another process, yield and try again.
2807 Sleep(0);
2808 hfile = CreateFileW(os_specific.c_str(), GENERIC_READ,
2809 FILE_SHARE_READ, nullptr, OPEN_ALWAYS,
2810 FILE_ATTRIBUTE_NORMAL, nullptr);
2811 } else {
2812 cerr << "Couldn't open file: " << os_specific
2813 << ", error " << error << "\n";
2814 return false;
2815 }
2816 }
2817
2818 static const size_t buf_size = 512;
2819 char buf[buf_size];
2820
2821 contents = string();
2822
2823 DWORD bytes_read;
2824 if (!ReadFile(hfile, buf, buf_size, &bytes_read, nullptr)) {
2825 cerr << "Error reading file: " << os_specific
2826 << ", error " << GetLastError() << "\n";
2827 CloseHandle(hfile);
2828 return false;
2829 }
2830 while (bytes_read > 0) {
2831 contents += string(buf, bytes_read);
2832
2833 if (!ReadFile(hfile, buf, buf_size, &bytes_read, nullptr)) {
2834 cerr << "Error reading file: " << os_specific
2835 << ", error " << GetLastError() << "\n";
2836 CloseHandle(hfile);
2837 return false;
2838 }
2839 }
2840
2841 CloseHandle(hfile);
2842 return true;
2843
2844#else // WIN32_VC
2845 string os_specific = to_os_specific();
2846 int fd = open(os_specific.c_str(), O_RDWR | O_CREAT, 0666);
2847 if (fd < 0) {
2848 perror(os_specific.c_str());
2849 return false;
2850 }
2851
2852 static const size_t buf_size = 512;
2853 char buf[buf_size];
2854
2855 contents = string();
2856
2857#ifdef PHAVE_LOCKF
2858 if (lockf(fd, F_LOCK, 0) != 0) {
2859#else
2860 if (flock(fd, LOCK_EX) != 0) {
2861#endif
2862 perror(os_specific.c_str());
2863 if (close(fd) < 0) {
2864 perror(os_specific.c_str());
2865 }
2866 return false;
2867 }
2868
2869 ssize_t bytes_read = read(fd, buf, buf_size);
2870 while (bytes_read > 0) {
2871 contents += string(buf, bytes_read);
2872 bytes_read = read(fd, buf, buf_size);
2873 }
2874
2875 if (bytes_read < 0) {
2876 perror(os_specific.c_str());
2877 if (close(fd) < 0) {
2878 perror(os_specific.c_str());
2879 }
2880 return false;
2881 }
2882
2883 if (close(fd) < 0) {
2884 perror(os_specific.c_str());
2885 }
2886 return true;
2887#endif // WIN32_VC
2888}
2889
2890/**
2891 * After the string has been reassigned, search for the slash marking the
2892 * beginning of the basename, and set _dirname_end and _basename_start
2893 * correctly.
2894 */
2895 void Filename::
2896locate_basename() {
2897 // Scan for the last slash, which marks the end of the directory part.
2898 if (_filename.empty()) {
2899 _dirname_end = 0;
2900 _basename_start = 0;
2901
2902 } else {
2903
2904 string::size_type slash = _filename.rfind('/');
2905 if (slash != string::npos) {
2906 _basename_start = slash + 1;
2907 _dirname_end = _basename_start;
2908
2909 // One exception: in case there are multiple slashes in a row, we want
2910 // to treat them as a single slash. The directory therefore actually
2911 // ends at the first of these; back up a bit.
2912 while (_dirname_end > 0 && _filename[_dirname_end-1] == '/') {
2913 _dirname_end--;
2914 }
2915
2916 // Another exception: if the dirname was nothing but slashes, it was the
2917 // root directory, or itself. In this case the dirname does include
2918 // the terminal slash (of course).
2919 if (_dirname_end == 0) {
2920 _dirname_end = 1;
2921 }
2922
2923 } else {
2924 _dirname_end = 0;
2925 _basename_start = 0;
2926 }
2927 }
2928
2929 // Now:
2930
2931 // _dirname_end is the last slash character, or 0 if there are no slash
2932 // characters.
2933
2934 // _basename_start is the character after the last slash character, or 0 if
2935 // there are no slash characters.
2936}
2937
2938
2939/**
2940 * Once the end of the directory prefix has been found, and _dirname_end and
2941 * _basename_start are set correctly, search for the dot marking the beginning
2942 * of the extension, and set _basename_end and _extension_start correctly.
2943 */
2944 void Filename::
2945locate_extension() {
2946 // Now scan for the last dot after that slash.
2947 if (_filename.empty()) {
2948 _basename_end = string::npos;
2949 _extension_start = string::npos;
2950
2951 } else {
2952 string::size_type dot = _filename.length() - 1;
2953
2954 while (dot+1 > _basename_start && _filename[dot] != '.') {
2955 --dot;
2956 }
2957
2958 if (dot+1 > _basename_start) {
2959 _basename_end = dot;
2960 _extension_start = dot + 1;
2961 } else {
2962 _basename_end = string::npos;
2963 _extension_start = string::npos;
2964 }
2965 }
2966
2967 // Now:
2968
2969 // _basename_end is the last dot, or npos if there is no dot.
2970
2971 // _extension_start is the character after the last dot, or npos if there is
2972 // no dot.
2973}
2974
2975/**
2976 * Identifies the part of the filename that contains the sequence of hash
2977 * marks, if any.
2978 */
2979 void Filename::
2980locate_hash() {
2981 if (!get_pattern()) {
2982 // If it's not a pattern-type filename, these are always set to the end of
2983 // the string.
2984 _hash_end = string::npos;
2985 _hash_start = string::npos;
2986
2987 } else {
2988 // If it is a pattern-type filename, we must search for the hash marks,
2989 // which could be anywhere (but are usually toward the end).
2990 _hash_end = _filename.rfind('#');
2991 if (_hash_end == string::npos) {
2992 _hash_end = string::npos;
2993 _hash_start = string::npos;
2994
2995 } else {
2996 _hash_start = _hash_end;
2997 ++_hash_end;
2998 while (_hash_start > 0 && _filename[_hash_start - 1] == '#') {
2999 --_hash_start;
3000 }
3001 }
3002 }
3003}
3004
3005
3006/**
3007 * Returns the length of the longest common initial substring of this string
3008 * and the other one that ends in a slash. This is the lowest directory
3009 * common to both filenames.
3010 */
3011 size_t Filename::
3012get_common_prefix(const string &other) const {
3013 size_t len = 0;
3014
3015 // First, get the length of the common initial substring.
3016 while (len < length() && len < other.length() &&
3017 _filename[len] == other[len]) {
3018 len++;
3019 }
3020
3021 // Now insist that it ends in a slash.
3022 while (len > 0 && _filename[len-1] != '/') {
3023 len--;
3024 }
3025
3026 return len;
3027}
3028
3029/**
3030 * Returns the number of non-consecutive slashes in the indicated string, not
3031 * counting a terminal slash.
3032 */
3033 int Filename::
3034count_slashes(const string &str) {
3035 int count = 0;
3036 string::const_iterator si;
3037 si = str.begin();
3038
3039 while (si != str.end()) {
3040 if (*si == '/') {
3041 count++;
3042
3043 // Skip consecutive slashes.
3044 ++si;
3045 while (*si == '/') {
3046 ++si;
3047 }
3048 if (si == str.end()) {
3049 // Oops, that was a terminal slash. Don't count it.
3050 count--;
3051 }
3052
3053 } else {
3054 ++si;
3055 }
3056 }
3057
3058 return count;
3059}
3060
3061
3062/**
3063 * The recursive implementation of make_canonical().
3064 */
3065 bool Filename::
3066r_make_canonical(const Filename &cwd) {
3067 if (get_fullpath() == "/") {
3068 // If we reached the root, the whole path doesn't exist. Report failure.
3069 return false;
3070 }
3071
3072#ifdef WIN32_VC
3073 // First, try to cd to the filename directly.
3074 wstring os_specific = to_os_specific_w();
3075 if (_wchdir(os_specific.c_str()) >= 0) {
3076 // That worked, save the full path string.
3077 (*this) = ExecutionEnvironment::get_cwd();
3078
3079 // And restore the current working directory.
3080 wstring osdir = cwd.to_os_specific_w();
3081 if (_wchdir(osdir.c_str()) < 0) {
3082 cerr << "Error! Cannot change back to " << osdir << "\n";
3083 }
3084 return true;
3085 }
3086#else // WIN32_VC
3087 // First, try to cd to the filename directly.
3088 string os_specific = to_os_specific();
3089 if (::chdir(os_specific.c_str()) >= 0) {
3090 // That worked, save the full path string.
3091 (*this) = ExecutionEnvironment::get_cwd();
3092
3093 // And restore the current working directory.
3094 string osdir = cwd.to_os_specific();
3095 if (::chdir(osdir.c_str()) < 0) {
3096 cerr << "Error! Cannot change back to " << osdir << "\n";
3097 }
3098 return true;
3099 }
3100#endif // WIN32_VC
3101
3102 // That didn't work; maybe it's not a directory. Recursively go to the
3103 // directory above.
3104
3105 Filename dir(get_dirname());
3106
3107 if (dir.empty()) {
3108 // No dirname means the file is in this directory.
3109 set_dirname(cwd);
3110 return true;
3111 }
3112
3113 if (!dir.r_make_canonical(cwd)) {
3114 return false;
3115 }
3116 set_dirname(dir);
3117 return true;
3118}
atomicAdjust.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
AtomicAdjustDummyImpl::compare_and_exchange_ptr
static Pointer compare_and_exchange_ptr(Pointer &mem, Pointer old_value, Pointer new_value)
Atomic compare and exchange.
Definition atomicAdjustDummyImpl.I:109
AtomicAdjustDummyImpl::get_ptr
static Pointer get_ptr(const Pointer &var)
Atomically retrieves the snapshot value of the indicated variable.
Definition atomicAdjustDummyImpl.I:81
DSearchPath
This class stores a list of directories that can be searched, in order, to locate a particular file.
Definition dSearchPath.h:28
DSearchPath::find_file
Filename find_file(const Filename &filename) const
Searches all the directories in the search list for the indicated file, in order.
Definition dSearchPath.cxx:244
DSearchPath::get_num_directories
get_num_directories
Returns the number of directories on the search list.
Definition dSearchPath.h:76
DSearchPath::get_directory
get_directory
Returns the nth directory on the search list.
Definition dSearchPath.h:76
ExecutionEnvironment::expand_string
static std::string expand_string(const std::string &str)
Reads the string, looking for environment variable names marked by a $.
Definition executionEnvironment.cxx:112
ExecutionEnvironment::get_cwd
get_cwd
Returns the name of the current working directory.
Definition executionEnvironment.h:61
Filename::set_basename
void set_basename(const std::string &s)
Replaces the basename part of the filename.
Definition filename.cxx:754
Filename::open_append
bool open_append(std::ofstream &stream) const
Opens the indicated ofstream for writing the file, if possible.
Definition filename.cxx:1954
Filename::set_type
void set_type(Type type)
Sets the type of the file represented by the filename.
Definition filename.I:468
Filename::Filename
Filename()
Creates an empty Filename.
Definition filename.I:85
Filename::copy_to
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...
Definition filename.cxx:2449
Filename::get_filename_index
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
Filename::is_text
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
Filename::to_os_specific
std::string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
Definition filename.cxx:1123
Filename::open_read
bool open_read(std::ifstream &stream) const
Opens the indicated ifstream for reading the file, if possible.
Definition filename.cxx:1872
Filename::get_home_directory
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
Filename::rmdir
bool rmdir() const
The inverse of mkdir(): this removes the directory named by this Filename, if it is in fact a directo...
Definition filename.cxx:2568
Filename::from_os_specific_w
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
Filename::unlink
bool unlink() const
Permanently deletes the file associated with the filename, if possible.
Definition filename.cxx:2330
Filename::standardize
void standardize()
Converts the filename to standard form by replacing consecutive slashes with a single slash,...
Definition filename.cxx:900
Filename::set_dirname
void set_dirname(const std::string &s)
Replaces the directory part of the filename.
Definition filename.cxx:704
Filename::set_binary
void set_binary()
Indicates that the filename represents a binary file.
Definition filename.I:414
Filename::is_binary_or_text
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
Filename::get_filesystem_encoding
static TextEncoder::Encoding get_filesystem_encoding()
Specifies the default encoding to be used for all subsequent Filenames objects.
Definition filename.I:639
Filename::get_temp_directory
static const Filename & get_temp_directory()
Returns a path to a system-defined temporary directory.
Definition filename.cxx:539
Filename::chdir
bool chdir() const
Changes directory to the specified location.
Definition filename.cxx:2314
Filename::touch
bool touch() const
Updates the modification time of the file to the current time.
Definition filename.cxx:2234
Filename::to_os_specific_w
std::wstring to_os_specific_w() const
The wide-string variant on to_os_specific().
Definition filename.cxx:1163
Filename::get_pattern
bool get_pattern() const
Returns the flag indicating whether this is a filename pattern.
Definition filename.I:519
Filename::make_true_case
bool make_true_case()
On a case-insensitive operating system (e.g.
Definition filename.cxx:1053
Filename::get_fullpath
std::string get_fullpath() const
Returns the entire filename: directory, basename, extension.
Definition filename.I:338
Filename::make_canonical
bool make_canonical()
Converts this filename to a canonical name by replacing the directory part with the fully-qualified d...
Definition filename.cxx:1011
Filename::temporary
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
Filename::from_os_specific
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
Filename::set_extension
void set_extension(const std::string &s)
Replaces the file extension.
Definition filename.cxx:804
Filename::is_directory
bool is_directory() const
Returns true if the filename exists on the physical disk and is a directory name, false otherwise.
Definition filename.cxx:1368
Filename::get_fullpath_w
std::wstring get_fullpath_w() const
Returns the entire filename as a wide-character string.
Definition filename.I:346
Filename::open_write
bool open_write(std::ofstream &stream, bool truncate=true) const
Opens the indicated ifstream for writing the file, if possible.
Definition filename.cxx:1908
Filename::make_relative_to
bool make_relative_to(Filename directory, bool allow_backups=true)
Adjusts this filename, which must be a fully-specified pathname beginning with a slash,...
Definition filename.cxx:1649
Filename::get_extension
std::string get_extension() const
Returns the file extension.
Definition filename.I:400
Filename::set_fullpath
void set_fullpath(const std::string &s)
Replaces the entire filename: directory, basename, extension.
Definition filename.cxx:695
Filename::mkdir
bool mkdir() const
Creates the directory named by this filename.
Definition filename.cxx:2551
Filename::is_local
bool is_local() const
Returns true if the filename is local, e.g.
Definition filename.I:549
Filename::get_dirname
std::string get_dirname() const
Returns the directory part of the filename.
Definition filename.I:358
Filename::make_absolute
void make_absolute()
Converts the filename to a fully-qualified pathname from the root (if it is a relative pathname),...
Definition filename.cxx:968
Filename::get_type
Type get_type() const
Returns the type of the file represented by the filename, as previously set by set_type().
Definition filename.I:485
Filename::set_pattern
void set_pattern(bool pattern)
Sets the flag indicating whether this is a filename pattern.
Definition filename.I:503
Filename::exists
bool exists() const
Returns true if the filename exists on the physical disk, false otherwise.
Definition filename.cxx:1270
TextEncoder
This class can be used to convert text between multiple representations, e.g.
Definition textEncoder.h:33
TextEncoder::set_text
set_text
Changes the text that is stored in the encoder.
Definition textEncoder.h:124
TextEncoder::unicode_tolower
static int unicode_tolower(char32_t character)
Returns the uppercase equivalent of the given Unicode character.
Definition textEncoder.I:385
TextEncoder::set_encoding
void set_encoding(Encoding encoding)
Specifies how the string set via set_text() is to be interpreted.
Definition textEncoder.I:48
TextEncoder::get_text
get_text
Returns the current text, as encoded via the current encoding system.
Definition textEncoder.h:124
TextEncoder::get_wtext
const std::wstring & get_wtext() const
Returns the text associated with the TextEncoder, as a wide-character string.
Definition textEncoder.I:456
TextEncoder::set_wtext
void set_wtext(const std::wstring &wtext)
Changes the text that is stored in the encoder.
Definition textEncoder.I:443
TypeHandle
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
string::const_iterator
STL iterator class.
string::iterator
STL iterator class.
dSearchPath.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
executionEnvironment.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
filename.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
filename_assist.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
vector_string.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.