Panda3D

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
61 TextEncoder::Encoding Filename::_filesystem_encoding = TextEncoder::E_utf8;
62
63 TVOLATILE AtomicAdjust::Pointer Filename::_home_directory;
64 TVOLATILE AtomicAdjust::Pointer Filename::_temp_directory;
65 TVOLATILE AtomicAdjust::Pointer Filename::_user_appdata_directory;
66 TVOLATILE 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
126 front_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
139 back_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 &
152 get_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
175 convert_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
244 convert_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
277 convert_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 */
298 Filename::
299 Filename(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 */
327 Filename Filename::
328 from_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 */
393 Filename Filename::
394 from_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 */
406 Filename Filename::
407 expand_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 */
423 Filename Filename::
424 temporary(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::
474 get_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::
539 get_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::
589 get_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::
645 get_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::
695 set_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::
704 set_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::
754 set_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::
766 set_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::
783 set_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::
804 set_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 */
835 Filename Filename::
836 get_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::
856 set_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::
872 extract_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::
900 standardize() {
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::
968 make_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::
983 make_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::
1011 make_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::
1053 make_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::
1123 to_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 */
1162 wstring Filename::
1163 to_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::
1182 to_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::
1202 to_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::
1236 to_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 disk, false otherwise. If the
1263 * type is indicated to be executable, this also tests that the file has
1264 * execute permission.
1265 */
1266 bool Filename::
1267 exists() const {
1268 #ifdef WIN32_VC
1269 wstring os_specific = get_filename_index(0).to_os_specific_w();
1270
1271 bool exists = false;
1272
1273 DWORD results = GetFileAttributesW(os_specific.c_str());
1274 if (results != -1) {
1275 exists = true;
1276 }
1277
1278 #else // WIN32_VC
1279 string os_specific = get_filename_index(0).to_os_specific();
1280
1281 struct stat this_buf;
1282 bool exists = false;
1283
1284 if (stat(os_specific.c_str(), &this_buf) == 0) {
1285 exists = true;
1286 }
1287 #endif
1288
1289 return exists;
1290 }
1291
1292 /**
1293 * Returns true if the filename exists and is the name of a regular file (i.e.
1294 * not a directory or device), false otherwise.
1295 */
1296 bool Filename::
1297 is_regular_file() const {
1298 #ifdef WIN32_VC
1299 wstring os_specific = get_filename_index(0).to_os_specific_w();
1300
1301 bool isreg = false;
1302
1303 DWORD results = GetFileAttributesW(os_specific.c_str());
1304 if (results != -1) {
1305 isreg = ((results & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE)) == 0);
1306 }
1307
1308 #else // WIN32_VC
1309 string os_specific = get_filename_index(0).to_os_specific();
1310
1311 struct stat this_buf;
1312 bool isreg = false;
1313
1314 if (stat(os_specific.c_str(), &this_buf) == 0) {
1315 isreg = S_ISREG(this_buf.st_mode);
1316 }
1317 #endif
1318
1319 return isreg;
1320 }
1321
1322 /**
1323 * Returns true if the filename exists and is either a directory or a regular
1324 * file that can be written to, or false otherwise.
1325 */
1326 bool Filename::
1327 is_writable() const {
1328 bool writable = false;
1329
1330 #ifdef WIN32_VC
1331 wstring os_specific = get_filename_index(0).to_os_specific_w();
1332
1333 DWORD results = GetFileAttributesW(os_specific.c_str());
1334 if (results != -1) {
1335 if ((results & FILE_ATTRIBUTE_DIRECTORY) != 0) {
1336 // Assume directories are writable.
1337 writable = true;
1338 } else if ((results & FILE_ATTRIBUTE_READONLY) == 0) {
1339 // Not read-only means writable.
1340 writable = true;
1341 }
1342 }
1343 #else // WIN32_VC
1344 string os_specific = get_filename_index(0).to_os_specific();
1345
1346 if (access(os_specific.c_str(), W_OK) == 0) {
1347 writable = true;
1348 }
1349 #endif
1350
1351 return writable;
1352 }
1353
1354 /**
1355 * Returns true if the filename exists and is a directory name, false
1356 * otherwise.
1357 */
1358 bool Filename::
1359 is_directory() const {
1360 #ifdef WIN32_VC
1361 wstring os_specific = get_filename_index(0).to_os_specific_w();
1362
1363 bool isdir = false;
1364
1365 DWORD results = GetFileAttributesW(os_specific.c_str());
1366 if (results != -1) {
1367 isdir = (results & FILE_ATTRIBUTE_DIRECTORY) != 0;
1368 }
1369 #else // WIN32_VC
1370 string os_specific = get_filename_index(0).to_os_specific();
1371
1372 struct stat this_buf;
1373 bool isdir = false;
1374
1375 if (stat(os_specific.c_str(), &this_buf) == 0) {
1376 isdir = S_ISDIR(this_buf.st_mode);
1377 }
1378 #endif
1379
1380 return isdir;
1381 }
1382
1383 /**
1384 * Returns true if the filename exists and is executable
1385 */
1386 bool Filename::
1387 is_executable() const {
1388 #ifdef WIN32_VC
1389 // no access() in windows, but to our advantage executables can only end in
1390 // .exe or .com
1391 string extension = get_extension();
1392 if (extension == "exe" || extension == "com") {
1393 return exists();
1394 }
1395
1396 #else /* WIN32_VC */
1397 string os_specific = get_filename_index(0).to_os_specific();
1398 if (access(os_specific.c_str(), X_OK) == 0) {
1399 return true;
1400 }
1401 #endif /* WIN32_VC */
1402
1403 return false;
1404 }
1405
1406 /**
1407 * Returns a number less than zero if the file named by this object is older
1408 * than the given file, zero if they have the same timestamp, or greater than
1409 * zero if this one is newer.
1410 *
1411 * If this_missing_is_old is true, it indicates that a missing file will be
1412 * treated as if it were older than any other file; otherwise, a missing file
1413 * will be treated as if it were newer than any other file. Similarly for
1414 * other_missing_is_old.
1415 */
1416 int Filename::
1417 compare_timestamps(const Filename &other,
1418 bool this_missing_is_old,
1419 bool other_missing_is_old) const {
1420 #ifdef WIN32_VC
1421 wstring os_specific = get_filename_index(0).to_os_specific_w();
1422 wstring other_os_specific = other.get_filename_index(0).to_os_specific_w();
1423
1424 struct _stat this_buf;
1425 bool this_exists = false;
1426
1427 if (_wstat(os_specific.c_str(), &this_buf) == 0) {
1428 this_exists = true;
1429 }
1430
1431 struct _stat other_buf;
1432 bool other_exists = false;
1433
1434 if (_wstat(other_os_specific.c_str(), &other_buf) == 0) {
1435 other_exists = true;
1436 }
1437 #else // WIN32_VC
1438 string os_specific = get_filename_index(0).to_os_specific();
1439 string other_os_specific = other.get_filename_index(0).to_os_specific();
1440
1441 struct stat this_buf;
1442 bool this_exists = false;
1443
1444 if (stat(os_specific.c_str(), &this_buf) == 0) {
1445 this_exists = true;
1446 }
1447
1448 struct stat other_buf;
1449 bool other_exists = false;
1450
1451 if (stat(other_os_specific.c_str(), &other_buf) == 0) {
1452 other_exists = true;
1453 }
1454 #endif
1455
1456 if (this_exists && other_exists) {
1457 // Both files exist, return the honest time comparison.
1458 return (int)this_buf.st_mtime - (int)other_buf.st_mtime;
1459
1460 } else if (!this_exists && !other_exists) {
1461 // Neither file exists.
1462 if (this_missing_is_old == other_missing_is_old) {
1463 // Both files are either "very old" or "very new".
1464 return 0;
1465 }
1466 if (this_missing_is_old) {
1467 // This file is "very old", the other is "very new".
1468 return -1;
1469 } else {
1470 // This file is "very new", the other is "very old".
1471 return 1;
1472 }
1473
1474 } else if (!this_exists) {
1475 // This file doesn't, the other one does.
1476 return this_missing_is_old ? -1 : 1;
1477
1478 }
1479 // !other_exists
1480 assert(!other_exists);
1481
1482 // This file exists, the other one doesn't.
1483 return other_missing_is_old ? 1 : -1;
1484 }
1485
1486 /**
1487 * Returns a time_t value that represents the time the file was last modified,
1488 * to within whatever precision the operating system records this information
1489 * (on a Windows95 system, for instance, this may only be accurate to within 2
1490 * seconds).
1491 *
1492 * If the timestamp cannot be determined, either because it is not supported
1493 * by the operating system or because there is some error (such as file not
1494 * found), returns 0.
1495 */
1496 time_t Filename::
1497 get_timestamp() const {
1498 #ifdef WIN32_VC
1499 wstring os_specific = get_filename_index(0).to_os_specific_w();
1500
1501 struct _stat this_buf;
1502
1503 if (_wstat(os_specific.c_str(), &this_buf) == 0) {
1504 return this_buf.st_mtime;
1505 }
1506 #else // WIN32_VC
1507 string os_specific = get_filename_index(0).to_os_specific();
1508
1509 struct stat this_buf;
1510
1511 if (stat(os_specific.c_str(), &this_buf) == 0) {
1512 return this_buf.st_mtime;
1513 }
1514 #endif
1515
1516 return 0;
1517 }
1518
1519 /**
1520 * Returns a time_t value that represents the time the file was last accessed,
1521 * if this information is available. See also get_timestamp(), which returns
1522 * the last modification time.
1523 */
1524 time_t Filename::
1525 get_access_timestamp() const {
1526 #ifdef WIN32_VC
1527 wstring os_specific = get_filename_index(0).to_os_specific_w();
1528
1529 struct _stat this_buf;
1530
1531 if (_wstat(os_specific.c_str(), &this_buf) == 0) {
1532 return this_buf.st_atime;
1533 }
1534 #else // WIN32_VC
1535 string os_specific = get_filename_index(0).to_os_specific();
1536
1537 struct stat this_buf;
1538
1539 if (stat(os_specific.c_str(), &this_buf) == 0) {
1540 return this_buf.st_atime;
1541 }
1542 #endif
1543
1544 return 0;
1545 }
1546
1547 /**
1548 * Returns the size of the file in bytes, or 0 if there is an error.
1549 */
1550 std::streamsize Filename::
1551 get_file_size() const {
1552 #ifdef WIN32_VC
1553 wstring os_specific = get_filename_index(0).to_os_specific_w();
1554
1555 struct _stat64 this_buf;
1556
1557 // _wstat() only returns the lower 32 bits of the file size (!). We have to
1558 // call _wstati64() if we actually want the full 64-bit file size.
1559 if (_wstati64(os_specific.c_str(), &this_buf) == 0) {
1560 return this_buf.st_size;
1561 }
1562 #else // WIN32_VC
1563 string os_specific = get_filename_index(0).to_os_specific();
1564
1565 struct stat this_buf;
1566
1567 if (stat(os_specific.c_str(), &this_buf) == 0) {
1568 return this_buf.st_size;
1569 }
1570 #endif
1571
1572 return 0;
1573 }
1574
1575 /**
1576 * Searches the given search path for the filename. If it is found, updates
1577 * the filename to the full pathname found and returns true; otherwise,
1578 * returns false.
1579 */
1580 bool Filename::
1581 resolve_filename(const DSearchPath &searchpath,
1582 const string &default_extension) {
1583 Filename found;
1584
1585 if (is_local()) {
1586 found = searchpath.find_file(*this);
1587
1588 if (found.empty()) {
1589 // We didn't find it with the given extension; can we try the default
1590 // extension?
1591 if (get_extension().empty() && !default_extension.empty()) {
1592 Filename try_ext = *this;
1593 try_ext.set_extension(default_extension);
1594 found = searchpath.find_file(try_ext);
1595 }
1596 }
1597 } else {
1598 if (exists()) {
1599 // The full pathname exists. Return true.
1600 return true;
1601 } else {
1602 // The full pathname doesn't exist with the given extension; does it
1603 // exist with the default extension?
1604 if (get_extension().empty() && !default_extension.empty()) {
1605 Filename try_ext = *this;
1606 try_ext.set_extension(default_extension);
1607 if (try_ext.exists()) {
1608 found = try_ext;
1609 }
1610 }
1611 }
1612 }
1613
1614 if (!found.empty()) {
1615 (*this) = found;
1616 return true;
1617 }
1618
1619 return false;
1620 }
1621
1622 /**
1623 * Adjusts this filename, which must be a fully-specified pathname beginning
1624 * with a slash, to make it a relative filename, relative to the fully-
1625 * specified directory indicated (which must also begin with, and may or may
1626 * not end with, a slash--a terminating slash is ignored).
1627 *
1628 * This only performs a string comparsion, so it may be wise to call
1629 * make_canonical() on both filenames before calling make_relative_to().
1630 *
1631 * If allow_backups is false, the filename will only be adjusted to be made
1632 * relative if it is already somewhere within or below the indicated
1633 * directory. If allow_backups is true, it will be adjusted in all cases,
1634 * even if this requires putting a series of .. characters before the filename
1635 * --unless it would have to back all the way up to the root.
1636 *
1637 * Returns true if the file was adjusted, false if it was not.
1638 */
1639 bool Filename::
1640 make_relative_to(Filename directory, bool allow_backups) {
1641 if (_filename.empty() || directory.empty() ||
1642 _filename[0] != '/' || directory[0] != '/') {
1643 return false;
1644 }
1645
1646 standardize();
1647 directory.standardize();
1648
1649 if (directory == "/") {
1650 // Don't be silly.
1651 return false;
1652 }
1653
1654 string rel_to_file = directory.get_fullpath() + "/.";
1655
1656 size_t common = get_common_prefix(rel_to_file);
1657 if (common < 2) {
1658 // Oh, never mind.
1659 return false;
1660 }
1661
1662 string result;
1663 int slashes = count_slashes(rel_to_file.substr(common));
1664 if (slashes > 0 && !allow_backups) {
1665 // Too bad; the file's not under the indicated directory.
1666 return false;
1667 }
1668
1669 for (int i = 0; i < slashes; i++) {
1670 result += "../";
1671 }
1672 result += _filename.substr(common);
1673 (*this) = result;
1674
1675 return true;
1676 }
1677
1678 /**
1679 * Performs the reverse of the resolve_filename() operation: assuming that the
1680 * current filename is fully-specified pathname (i.e. beginning with '/'),
1681 * look on the indicated search path for a directory under which the file can
1682 * be found. When found, adjust the Filename to be relative to the indicated
1683 * directory name.
1684 *
1685 * Returns the index of the directory on the searchpath at which the file was
1686 * found, or -1 if it was not found.
1687 */
1688 int Filename::
1689 find_on_searchpath(const DSearchPath &searchpath) {
1690 if (_filename.empty() || _filename[0] != '/') {
1691 return -1;
1692 }
1693
1694 size_t num_directories = searchpath.get_num_directories();
1695 for (size_t i = 0; i < num_directories; ++i) {
1696 Filename directory = searchpath.get_directory(i);
1697 directory.make_absolute();
1698 if (make_relative_to(directory, false)) {
1699 return (int)i;
1700 }
1701 }
1702
1703 return -1;
1704 }
1705
1706 /**
1707 * Attempts to open the named filename as if it were a directory and looks for
1708 * the non-hidden files within the directory. Fills the given vector up with
1709 * the sorted list of filenames that are local to this directory.
1710 *
1711 * It is the user's responsibility to ensure that the contents vector is empty
1712 * before making this call; otherwise, the new files will be appended to it.
1713 *
1714 * Returns true on success, false if the directory could not be read for some
1715 * reason.
1716 */
1717 bool Filename::
1718 scan_directory(vector_string &contents) const {
1719 assert(!get_pattern());
1720
1721 #if defined(WIN32_VC)
1722 // Use Windows' FindFirstFile() FindNextFile() to walk through the list of
1723 // files in a directory.
1724 size_t orig_size = contents.size();
1725
1726 wstring match;
1727 if (empty()) {
1728 match = L"*.*";
1729 } else {
1730 match = to_os_specific_w() + L"\\*.*";
1731 }
1732 WIN32_FIND_DATAW find_data;
1733
1734 HANDLE handle = FindFirstFileW(match.c_str(), &find_data);
1735 if (handle == INVALID_HANDLE_VALUE) {
1736 if (GetLastError() == ERROR_NO_MORE_FILES) {
1737 // No matching files is not an error.
1738 return true;
1739 }
1740 return false;
1741 }
1742
1743 TextEncoder encoder;
1744 encoder.set_encoding(get_filesystem_encoding());
1745 do {
1746 thread_consider_yield();
1747 wstring filename = find_data.cFileName;
1748 if (filename != L"." && filename != L"..") {
1749 encoder.set_wtext(filename);
1750 contents.push_back(encoder.get_text());
1751 }
1752 } while (FindNextFileW(handle, &find_data));
1753
1754 bool scan_ok = (GetLastError() == ERROR_NO_MORE_FILES);
1755 FindClose(handle);
1756
1757 sort(contents.begin() + orig_size, contents.end());
1758 return scan_ok;
1759
1760 #elif defined(PHAVE_DIRENT_H)
1761 // Use Posix's opendir() readdir() to walk through the list of files in a
1762 // directory.
1763 size_t orig_size = contents.size();
1764
1765 string dirname;
1766 if (empty()) {
1767 dirname = ".";
1768 } else {
1769 dirname = _filename;
1770 }
1771 DIR *root = opendir(dirname.c_str());
1772 if (root == nullptr) {
1773 if (errno != ENOTDIR) {
1774 perror(dirname.c_str());
1775 }
1776 return false;
1777 }
1778
1779 struct dirent *d;
1780 d = readdir(root);
1781 while (d != nullptr) {
1782 thread_consider_yield();
1783 if (d->d_name[0] != '.') {
1784 contents.push_back(d->d_name);
1785 }
1786 d = readdir(root);
1787 }
1788
1789 // It turns out to be a mistake to check the value of errno after calling
1790 // readdir(), since it might have been set to non-zero during some internal
1791 // operation of readdir(), even though there wasn't really a problem with
1792 // scanning the directory itself.
1793 /*
1794 if (errno != 0 && errno != ENOENT && errno != ENOTDIR) {
1795 cerr << "Error occurred while scanning directory " << dirname << "\n";
1796 perror(dirname.c_str());
1797 closedir(root);
1798 return false;
1799 }
1800 */
1801 closedir(root);
1802
1803 sort(contents.begin() + orig_size, contents.end());
1804 return true;
1805
1806 #elif defined(PHAVE_GLOB_H)
1807 // It's hard to imagine a system that provides glob.h but does not provide
1808 // openddir() .. readdir(), but this code is leftover from a time when there
1809 // was an undetected bug in the above readdir() loop, and it works, so we
1810 // might as well keep it around for now.
1811 string dirname;
1812 if (empty()) {
1813 dirname = "*";
1814 } else if (_filename[_filename.length() - 1] == '/') {
1815 dirname = _filename + "*";
1816 } else {
1817 dirname = _filename + "/*"; /* comment to fix emacs syntax hilight */
1818 }
1819
1820 glob_t globbuf;
1821
1822 int r = glob(dirname.c_str(), GLOB_ERR, nullptr, &globbuf);
1823
1824 if (r != 0) {
1825 // Some error processing the match string. If our version of glob.h
1826 // defines GLOB_NOMATCH, then we can differentiate an empty return result
1827 // from some other kind of error.
1828 #ifdef GLOB_NOMATCH
1829 if (r != GLOB_NOMATCH) {
1830 perror(dirname.c_str());
1831 return false;
1832 }
1833 #endif
1834
1835 // Otherwise, all errors mean the same thing: no matches, but otherwise no
1836 // problem.
1837 return true;
1838 }
1839
1840 size_t offset = dirname.size() - 1;
1841
1842 for (int i = 0; globbuf.gl_pathv[i] != nullptr; i++) {
1843 contents.push_back(globbuf.gl_pathv[i] + offset);
1844 }
1845 globfree(&globbuf);
1846
1847 return true;
1848
1849 #else
1850 // Don't know how to scan directories!
1851 return false;
1852 #endif
1853 }
1854
1855 /**
1856 * Opens the indicated ifstream for reading the file, if possible. Returns
1857 * true if successful, false otherwise. This requires the setting of the
1858 * set_text()/set_binary() flags to open the file appropriately as indicated;
1859 * it is an error to call open_read() without first calling one of set_text()
1860 * or set_binary().
1861 */
1862 bool Filename::
1863 open_read(std::ifstream &stream) const {
1864 assert(!get_pattern());
1865 assert(is_binary_or_text());
1866
1867 ios_openmode open_mode = ios::in;
1868
1869 #ifdef HAVE_IOS_BINARY
1870 // For some reason, some systems (like Irix) don't define ios::binary.
1871 if (!is_text()) {
1872 open_mode |= ios::binary;
1873 }
1874 #endif
1875
1876 stream.clear();
1877 #ifdef WIN32_VC
1878 wstring os_specific = to_os_specific_w();
1879 stream.open(os_specific.c_str(), open_mode);
1880 #else
1881 string os_specific = to_os_specific();
1882 stream.open(os_specific.c_str(), open_mode);
1883 #endif // WIN32_VC
1884
1885 return (!stream.fail());
1886 }
1887
1888 /**
1889 * Opens the indicated ifstream for writing the file, if possible. Returns
1890 * true if successful, false otherwise. This requires the setting of the
1891 * set_text()/set_binary() flags to open the file appropriately as indicated;
1892 * it is an error to call open_read() without first calling one of set_text()
1893 * or set_binary().
1894 *
1895 * If truncate is true, the file is truncated to zero length upon opening it,
1896 * if it already exists. Otherwise, the file is kept at its original length.
1897 */
1898 bool Filename::
1899 open_write(std::ofstream &stream, bool truncate) const {
1900 assert(!get_pattern());
1901 assert(is_binary_or_text());
1902
1903 ios_openmode open_mode = ios::out;
1904
1905 if (truncate) {
1906 open_mode |= ios::trunc;
1907
1908 } else {
1909 // Some systems insist on having ios::in set to prevent the file from
1910 // being truncated when we open it. Makes ios::trunc kind of pointless,
1911 // doesn't it? On the other hand, setting ios::in also seems to imply
1912 // ios::nocreate (!), so we should only set this if the file already
1913 // exists.
1914 if (exists()) {
1915 open_mode |= ios::in;
1916 }
1917 }
1918
1919 #ifdef HAVE_IOS_BINARY
1920 // For some reason, some systems (like Irix) don't define ios::binary.
1921 if (!is_text()) {
1922 open_mode |= ios::binary;
1923 }
1924 #endif
1925
1926 stream.clear();
1927 #ifdef WIN32_VC
1928 wstring os_specific = to_os_specific_w();
1929 #else
1930 string os_specific = to_os_specific();
1931 #endif // WIN32_VC
1932 stream.open(os_specific.c_str(), open_mode);
1933
1934 return (!stream.fail());
1935 }
1936
1937 /**
1938 * Opens the indicated ofstream for writing the file, if possible. Returns
1939 * true if successful, false otherwise. This requires the setting of the
1940 * set_text()/set_binary() flags to open the file appropriately as indicated;
1941 * it is an error to call open_read() without first calling one of set_text()
1942 * or set_binary().
1943 */
1944 bool Filename::
1945 open_append(std::ofstream &stream) const {
1946 assert(!get_pattern());
1947 assert(is_binary_or_text());
1948
1949 ios_openmode open_mode = ios::app;
1950
1951 #ifdef HAVE_IOS_BINARY
1952 // For some reason, some systems (like Irix) don't define ios::binary.
1953 if (!is_text()) {
1954 open_mode |= ios::binary;
1955 }
1956 #endif
1957
1958 stream.clear();
1959 #ifdef WIN32_VC
1960 wstring os_specific = to_os_specific_w();
1961 #else
1962 string os_specific = to_os_specific();
1963 #endif // WIN32_VC
1964 stream.open(os_specific.c_str(), open_mode);
1965
1966 return (!stream.fail());
1967 }
1968
1969 /**
1970 * Opens the indicated fstream for read/write access to the file, if possible.
1971 * Returns true if successful, false otherwise. This requires the setting of
1972 * the set_text()/set_binary() flags to open the file appropriately as
1973 * indicated; it is an error to call open_read_write() without first calling
1974 * one of set_text() or set_binary().
1975 */
1976 bool Filename::
1977 open_read_write(std::fstream &stream, bool truncate) const {
1978 assert(!get_pattern());
1979 assert(is_binary_or_text());
1980
1981 ios_openmode open_mode = ios::out | ios::in;
1982
1983 if (truncate) {
1984 open_mode |= ios::trunc;
1985 }
1986
1987 // Since ios::in also seems to imply ios::nocreate (!), we must guarantee
1988 // the file already exists before we try to open it.
1989 if (!exists()) {
1990 touch();
1991 }
1992
1993 #ifdef HAVE_IOS_BINARY
1994 // For some reason, some systems (like Irix) don't define ios::binary.
1995 if (!is_text()) {
1996 open_mode |= ios::binary;
1997 }
1998 #endif
1999
2000 stream.clear();
2001 #ifdef WIN32_VC
2002 wstring os_specific = to_os_specific_w();
2003 #else
2004 string os_specific = to_os_specific();
2005 #endif // WIN32_VC
2006 stream.open(os_specific.c_str(), open_mode);
2007
2008 return (!stream.fail());
2009 }
2010
2011 /**
2012 * Opens the indicated ifstream for reading and writing the file, if possible;
2013 * writes are appended to the end of the file. Returns true if successful,
2014 * false otherwise. This requires the setting of the set_text()/set_binary()
2015 * flags to open the file appropriately as indicated; it is an error to call
2016 * open_read() without first calling one of set_text() or set_binary().
2017 */
2018 bool Filename::
2019 open_read_append(std::fstream &stream) const {
2020 assert(!get_pattern());
2021 assert(is_binary_or_text());
2022
2023 ios_openmode open_mode = ios::app | ios::in;
2024
2025 #ifdef HAVE_IOS_BINARY
2026 // For some reason, some systems (like Irix) don't define ios::binary.
2027 if (!is_text()) {
2028 open_mode |= ios::binary;
2029 }
2030 #endif
2031
2032 stream.clear();
2033 #ifdef WIN32_VC
2034 wstring os_specific = to_os_specific_w();
2035 #else
2036 string os_specific = to_os_specific();
2037 #endif // WIN32_VC
2038 stream.open(os_specific.c_str(), open_mode);
2039
2040 return (!stream.fail());
2041 }
2042
2043 #ifdef USE_PANDAFILESTREAM
2044 /**
2045 * Opens the indicated pifstream for reading the file, if possible. Returns
2046 * true if successful, false otherwise. This requires the setting of the
2047 * set_text()/set_binary() flags to open the file appropriately as indicated;
2048 * it is an error to call open_read() without first calling one of set_text()
2049 * or set_binary().
2050 */
2051 bool Filename::
2052 open_read(pifstream &stream) const {
2053 assert(!get_pattern());
2054 assert(is_binary_or_text());
2055
2056 ios_openmode open_mode = ios::in;
2057
2058 #ifdef HAVE_IOS_BINARY
2059 // For some reason, some systems (like Irix) don't define ios::binary.
2060 if (!is_text()) {
2061 open_mode |= ios::binary;
2062 }
2063 #endif
2064
2065 string os_specific = to_os_specific();
2066 stream.clear();
2067 stream.open(os_specific.c_str(), open_mode);
2068 return (!stream.fail());
2069 }
2070 #endif // USE_PANDAFILESTREAM
2071
2072 #ifdef USE_PANDAFILESTREAM
2073 /**
2074 * Opens the indicated pifstream for writing the file, if possible. Returns
2075 * true if successful, false otherwise. This requires the setting of the
2076 * set_text()/set_binary() flags to open the file appropriately as indicated;
2077 * it is an error to call open_read() without first calling one of set_text()
2078 * or set_binary().
2079 *
2080 * If truncate is true, the file is truncated to zero length upon opening it,
2081 * if it already exists. Otherwise, the file is kept at its original length.
2082 */
2083 bool Filename::
2084 open_write(pofstream &stream, bool truncate) const {
2085 assert(!get_pattern());
2086 assert(is_binary_or_text());
2087
2088 ios_openmode open_mode = ios::out;
2089
2090 if (truncate) {
2091 open_mode |= ios::trunc;
2092
2093 } else {
2094 // Some systems insist on having ios::in set to prevent the file from
2095 // being truncated when we open it. Makes ios::trunc kind of pointless,
2096 // doesn't it? On the other hand, setting ios::in also seems to imply
2097 // ios::nocreate (!), so we should only set this if the file already
2098 // exists.
2099 if (exists()) {
2100 open_mode |= ios::in;
2101 }
2102 }
2103
2104 #ifdef HAVE_IOS_BINARY
2105 // For some reason, some systems (like Irix) don't define ios::binary.
2106 if (!is_text()) {
2107 open_mode |= ios::binary;
2108 }
2109 #endif
2110
2111 stream.clear();
2112 string os_specific = to_os_specific();
2113 stream.open(os_specific.c_str(), open_mode);
2114
2115 return (!stream.fail());
2116 }
2117 #endif // USE_PANDAFILESTREAM
2118
2119 #ifdef USE_PANDAFILESTREAM
2120 /**
2121 * Opens the indicated pifstream for writing the file, if possible. Returns
2122 * true if successful, false otherwise. This requires the setting of the
2123 * set_text()/set_binary() flags to open the file appropriately as indicated;
2124 * it is an error to call open_read() without first calling one of set_text()
2125 * or set_binary().
2126 */
2127 bool Filename::
2128 open_append(pofstream &stream) const {
2129 assert(!get_pattern());
2130 assert(is_binary_or_text());
2131
2132 ios_openmode open_mode = ios::app;
2133
2134 #ifdef HAVE_IOS_BINARY
2135 // For some reason, some systems (like Irix) don't define ios::binary.
2136 if (!is_text()) {
2137 open_mode |= ios::binary;
2138 }
2139 #endif
2140
2141 stream.clear();
2142 string os_specific = to_os_specific();
2143 stream.open(os_specific.c_str(), open_mode);
2144
2145 return (!stream.fail());
2146 }
2147 #endif // USE_PANDAFILESTREAM
2148
2149 #ifdef USE_PANDAFILESTREAM
2150 /**
2151 * Opens the indicated fstream for read/write access to the file, if possible.
2152 * Returns true if successful, false otherwise. This requires the setting of
2153 * the set_text()/set_binary() flags to open the file appropriately as
2154 * indicated; it is an error to call open_read_write() without first calling
2155 * one of set_text() or set_binary().
2156 */
2157 bool Filename::
2158 open_read_write(pfstream &stream, bool truncate) const {
2159 assert(!get_pattern());
2160 assert(is_binary_or_text());
2161
2162 ios_openmode open_mode = ios::out | ios::in;
2163
2164 if (truncate) {
2165 open_mode |= ios::trunc;
2166 }
2167
2168 // Since ios::in also seems to imply ios::nocreate (!), we must guarantee
2169 // the file already exists before we try to open it.
2170 if (!exists()) {
2171 touch();
2172 }
2173
2174 #ifdef HAVE_IOS_BINARY
2175 // For some reason, some systems (like Irix) don't define ios::binary.
2176 if (!is_text()) {
2177 open_mode |= ios::binary;
2178 }
2179 #endif
2180
2181 stream.clear();
2182 string os_specific = to_os_specific();
2183 stream.open(os_specific.c_str(), open_mode);
2184
2185 return (!stream.fail());
2186 }
2187 #endif // USE_PANDAFILESTREAM
2188
2189 #ifdef USE_PANDAFILESTREAM
2190 /**
2191 * Opens the indicated pfstream for reading and writing the file, if possible;
2192 * writes are appended to the end of the file. Returns true if successful,
2193 * false otherwise. This requires the setting of the set_text()/set_binary()
2194 * flags to open the file appropriately as indicated; it is an error to call
2195 * open_read() without first calling one of set_text() or set_binary().
2196 */
2197 bool Filename::
2198 open_read_append(pfstream &stream) const {
2199 assert(!get_pattern());
2200 assert(is_binary_or_text());
2201
2202 ios_openmode open_mode = ios::app | ios::in;
2203
2204 #ifdef HAVE_IOS_BINARY
2205 // For some reason, some systems (like Irix) don't define ios::binary.
2206 if (!is_text()) {
2207 open_mode |= ios::binary;
2208 }
2209 #endif
2210
2211 stream.clear();
2212 string os_specific = to_os_specific();
2213 stream.open(os_specific.c_str(), open_mode);
2214
2215 return (!stream.fail());
2216 }
2217 #endif // USE_PANDAFILESTREAM
2218
2219 /**
2220 * Updates the modification time of the file to the current time. If the file
2221 * does not already exist, it will be created. Returns true if successful,
2222 * false if there is an error.
2223 */
2224 bool Filename::
2225 touch() const {
2226 assert(!get_pattern());
2227 #ifdef WIN32_VC
2228 // In Windows, we have to use the Windows API to do this reliably.
2229
2230 // First, guarantee the file exists (and also get its handle).
2231 wstring os_specific = to_os_specific_w();
2232 HANDLE fhandle;
2233 fhandle = CreateFileW(os_specific.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE,
2234 nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
2235 if (fhandle == INVALID_HANDLE_VALUE) {
2236 return false;
2237 }
2238
2239 // Now update the file time and date.
2240 SYSTEMTIME sysnow;
2241 FILETIME ftnow;
2242 GetSystemTime(&sysnow);
2243 if (!SystemTimeToFileTime(&sysnow, &ftnow)) {
2244 CloseHandle(fhandle);
2245 return false;
2246 }
2247
2248 if (!SetFileTime(fhandle, nullptr, nullptr, &ftnow)) {
2249 CloseHandle(fhandle);
2250 return false;
2251 }
2252
2253 CloseHandle(fhandle);
2254 return true;
2255
2256 #elif defined(PHAVE_UTIME_H)
2257 // Most Unix systems can do this explicitly.
2258
2259 string os_specific = to_os_specific();
2260 #ifdef HAVE_CYGWIN
2261 // In the Cygwin case, it seems we need to be sure to use the Cygwin-style
2262 // name; some broken utime() implementation. That's almost the same thing
2263 // as the original Panda-style name, but not exactly, so we first convert
2264 // the Panda name to a Windows name, then convert it back to Cygwin, to
2265 // ensure we get it exactly right by Cygwin rules.
2266 {
2267 char result[4096] = "";
2268 cygwin_conv_to_posix_path(os_specific.c_str(), result);
2269 os_specific = result;
2270 }
2271 #endif // HAVE_CYGWIN
2272 int result = utime(os_specific.c_str(), nullptr);
2273 if (result < 0) {
2274 if (errno == ENOENT) {
2275 // So the file doesn't already exist; create it.
2276 int fd = creat(os_specific.c_str(), 0666);
2277 if (fd < 0) {
2278 perror(os_specific.c_str());
2279 return false;
2280 }
2281 close(fd);
2282 return true;
2283 }
2284 perror(os_specific.c_str());
2285 return false;
2286 }
2287 return true;
2288 #else // WIN32, PHAVE_UTIME_H
2289 // Other systems may not have an explicit control over the modification
2290 // time. For these systems, we'll just temporarily open the file in append
2291 // mode, then close it again (it gets closed when the pfstream goes out of
2292 // scope).
2293 pfstream file;
2294 return open_append(file);
2295 #endif // WIN32, PHAVE_UTIME_H
2296 }
2297
2298 /**
2299 * Changes directory to the specified location. Returns true if successful,
2300 * false if failure.
2301 */
2302 bool Filename::
2303 chdir() const {
2304 #ifdef WIN32_VC
2305 wstring os_specific = to_os_specific_w();
2306 return (_wchdir(os_specific.c_str()) >= 0);
2307 #else
2308 string os_specific = to_os_specific();
2309 return (::chdir(os_specific.c_str()) >= 0);
2310 #endif // WIN32_VC
2311 }
2312
2313 /**
2314 * Permanently deletes the file associated with the filename, if possible.
2315 * Returns true if successful, false if failure (for instance, because the
2316 * file did not exist, or because permissions were inadequate).
2317 */
2318 bool Filename::
2319 unlink() const {
2320 assert(!get_pattern());
2321 #ifdef WIN32_VC
2322 // Windows can't delete a file if it's read-only. Weird.
2323 wstring os_specific = to_os_specific_w();
2324 _wchmod(os_specific.c_str(), 0644);
2325 return (_wunlink(os_specific.c_str()) == 0);
2326 #else
2327 string os_specific = to_os_specific();
2328 return (::unlink(os_specific.c_str()) == 0);
2329 #endif // WIN32_VC
2330 }
2331
2332
2333 /**
2334 * Renames the file to the indicated new filename. If the new filename is in
2335 * a different directory, this will perform a move. Returns true if
2336 * successful, false on failure.
2337 */
2338 bool Filename::
2339 rename_to(const Filename &other) const {
2340 assert(!get_pattern());
2341
2342 if (*this == other) {
2343 // Trivial success.
2344 return true;
2345 }
2346
2347 #ifdef WIN32_VC
2348 wstring os_specific = to_os_specific_w();
2349 wstring other_os_specific = other.to_os_specific_w();
2350
2351 if (_wrename(os_specific.c_str(),
2352 other_os_specific.c_str()) == 0) {
2353 // Successfully renamed.
2354 return true;
2355 }
2356
2357 // The above might fail if we have tried to move a file to a different
2358 // filesystem. In this case, copy the file into the same directory first,
2359 // and then rename it.
2360 string dirname = other.get_dirname();
2361 if (dirname.empty()) {
2362 dirname = ".";
2363 }
2364 Filename temp = Filename::temporary(dirname, "");
2365 temp.set_binary();
2366 if (!Filename::binary_filename(*this).copy_to(temp)) {
2367 return false;
2368 }
2369
2370 wstring temp_os_specific = temp.to_os_specific_w();
2371 if (_wrename(temp_os_specific.c_str(),
2372 other_os_specific.c_str()) == 0) {
2373 // Successfully renamed.
2374 unlink();
2375 return true;
2376 }
2377
2378 // Try unlinking the target first.
2379 other.unlink();
2380 if (_wrename(temp_os_specific.c_str(),
2381 other_os_specific.c_str()) == 0) {
2382 // Successfully renamed.
2383 unlink();
2384 return true;
2385 }
2386 #else // WIN32_VC
2387 string os_specific = to_os_specific();
2388 string other_os_specific = other.to_os_specific();
2389
2390 if (rename(os_specific.c_str(),
2391 other_os_specific.c_str()) == 0) {
2392 // Successfully renamed.
2393 return true;
2394 }
2395
2396 // The above might fail if we have tried to move a file to a different
2397 // filesystem. In this case, copy the file into the same directory first,
2398 // and then rename it.
2399 string dirname = other.get_dirname();
2400 if (dirname.empty()) {
2401 dirname = ".";
2402 }
2403 Filename temp = Filename::temporary(dirname, "");
2404 temp.set_binary();
2405 if (!Filename::binary_filename(*this).copy_to(temp)) {
2406 return false;
2407 }
2408
2409 string temp_os_specific = temp.to_os_specific();
2410 if (rename(temp_os_specific.c_str(),
2411 other_os_specific.c_str()) == 0) {
2412 // Successfully renamed.
2413 unlink();
2414 return true;
2415 }
2416
2417 // Try unlinking the target first.
2418 other.unlink();
2419 if (rename(temp_os_specific.c_str(),
2420 other_os_specific.c_str()) == 0) {
2421 // Successfully renamed.
2422 unlink();
2423 return true;
2424 }
2425 #endif // WIN32_VC
2426
2427 // Failed.
2428 temp.unlink();
2429 return false;
2430 }
2431
2432 /**
2433 * Copies the file to the indicated new filename, by reading the contents and
2434 * writing it to the new file. Returns true if successful, false on failure.
2435 * The copy is always binary, regardless of the filename settings.
2436 */
2437 bool Filename::
2438 copy_to(const Filename &other) const {
2439 Filename this_filename = Filename::binary_filename(*this);
2440 pifstream in;
2441 if (!this_filename.open_read(in)) {
2442 return false;
2443 }
2444
2445 Filename other_filename = Filename::binary_filename(other);
2446 pofstream out;
2447 if (!other_filename.open_write(out)) {
2448 return false;
2449 }
2450
2451 static const size_t buffer_size = 4096;
2452 char buffer[buffer_size];
2453
2454 in.read(buffer, buffer_size);
2455 size_t count = in.gcount();
2456 while (count != 0) {
2457 out.write(buffer, count);
2458 if (out.fail()) {
2459 other.unlink();
2460 return false;
2461 }
2462 in.read(buffer, buffer_size);
2463 count = in.gcount();
2464 }
2465
2466 if (!in.eof()) {
2467 other.unlink();
2468 return false;
2469 }
2470
2471 return true;
2472 }
2473
2474 /**
2475 * Creates all the directories in the path to the file specified in the
2476 * filename, except for the basename itself. This assumes that the Filename
2477 * contains the name of a file, not a directory name; it ensures that the
2478 * directory containing the file exists.
2479 *
2480 * However, if the filename ends in a slash, it assumes the Filename
2481 * represents the name of a directory, and creates all the paths.
2482 */
2483 bool Filename::
2484 make_dir() const {
2485 assert(!get_pattern());
2486 if (empty()) {
2487 return false;
2488 }
2489 Filename path;
2490 if (_filename[_filename.length() - 1] == '/') {
2491 // The Filename ends in a slash; it represents a directory.
2492 path = (*this);
2493
2494 } else {
2495 // The Filename does not end in a slash; it represents a file.
2496 path = get_dirname();
2497 }
2498
2499 if (path.empty()) {
2500 return false;
2501 }
2502 string dirname = path.get_fullpath();
2503
2504 // First, make sure everything up to the last path is known. We don't care
2505 // too much if any of these fail; maybe they failed because the directory
2506 // was already there.
2507 size_t slash = dirname.find('/');
2508 while (slash != string::npos) {
2509 Filename component(dirname.substr(0, slash));
2510 #ifdef WIN32_VC
2511 wstring os_specific = component.to_os_specific_w();
2512 _wmkdir(os_specific.c_str());
2513 #else
2514 string os_specific = component.to_os_specific();
2515 ::mkdir(os_specific.c_str(), 0777);
2516 #endif // WIN32_VC
2517 slash = dirname.find('/', slash + 1);
2518 }
2519
2520 // Now make the last one, and check the return value.
2521 Filename component(dirname);
2522 #ifdef WIN32_VC
2523 wstring os_specific = component.to_os_specific_w();
2524 int result = _wmkdir(os_specific.c_str());
2525 #else
2526 string os_specific = component.to_os_specific();
2527 int result = ::mkdir(os_specific.c_str(), 0777);
2528 #endif // WIN32_VC
2529
2530 return (result == 0);
2531 }
2532
2533 /**
2534 * Creates the directory named by this filename. Unlike make_dir(), this
2535 * assumes that the Filename contains the directory name itself. Also, parent
2536 * directories are not automatically created; this function fails if any
2537 * parent directory is missing.
2538 */
2539 bool Filename::
2540 mkdir() const {
2541 #ifdef WIN32_VC
2542 wstring os_specific = to_os_specific_w();
2543 int result = _wmkdir(os_specific.c_str());
2544 #else
2545 string os_specific = to_os_specific();
2546 int result = ::mkdir(os_specific.c_str(), 0777);
2547 #endif // WIN32_VC
2548
2549 return (result == 0);
2550 }
2551
2552 /**
2553 * The inverse of mkdir(): this removes the directory named by this Filename,
2554 * if it is in fact a directory.
2555 */
2556 bool Filename::
2557 rmdir() const {
2558 #ifdef WIN32_VC
2559 wstring os_specific = to_os_specific_w();
2560
2561 int result = _wrmdir(os_specific.c_str());
2562 if (result != 0) {
2563 // Windows may require the directory to be writable before we can remove
2564 // it.
2565 _wchmod(os_specific.c_str(), 0777);
2566 result = _wrmdir(os_specific.c_str());
2567 }
2568
2569 #else // WIN32_VC
2570 string os_specific = to_os_specific();
2571 int result = ::rmdir(os_specific.c_str());
2572 #endif // WIN32_VC
2573
2574 return (result == 0);
2575 }
2576
2577 /**
2578 * Returns a hash code that attempts to be mostly unique for different
2579 * Filenames.
2580 */
2581 int Filename::
2582 get_hash() const {
2583 static const int primes[] = {
2584 2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
2585 31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
2586 73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
2587 127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
2588 179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
2589 233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
2590 283, 293, 307, 311, 313, 317, 331, 337, 347, 349,
2591 353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
2592 419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
2593 467, 479, 487, 491, 499, 503, 509, 521, 523, 541,
2594 547, 557, 563, 569, 571, 577, 587, 593, 599, 601,
2595 607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
2596 661, 673, 677, 683, 691, 701, 709, 719, 727, 733,
2597 739, 743, 751, 757, 761, 769, 773, 787, 797, 809,
2598 811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
2599 877, 881, 883, 887, 907, 911, 919, 929, 937, 941,
2600 947, 953, 967, 971, 977, 983, 991, 997
2601 };
2602 static const size_t num_primes = sizeof(primes) / sizeof(int);
2603
2604 int hash = 0;
2605 for (size_t i = 0; i < _filename.size(); ++i) {
2606 hash += (int)_filename[i] * primes[i % num_primes];
2607 }
2608
2609 return hash;
2610 }
2611
2612
2613 /**
2614 * Uses native file-locking mechanisms to atomically replace the contents of a
2615 * (small) file with the specified contents, assuming it hasn't changed since
2616 * the last time the file was read.
2617 *
2618 * This is designed to be similar to AtomicAdjust::compare_and_exchange().
2619 * The method writes new_contents to the file, completely replacing the
2620 * original contents; but only if the original contents exactly matched
2621 * old_contents. If the file was modified, returns true. If, however, the
2622 * original contents of the file did not exactly match old_contents, then the
2623 * file is not modified, and false is returned. In either case, orig_contents
2624 * is filled with the original contents of the file.
2625 *
2626 * If the file does not exist, it is implicitly created, and its original
2627 * contents are empty.
2628 *
2629 * If an I/O error occurs on write, some of the file may or may not have been
2630 * written, and false is returned.
2631 *
2632 * Expressed in pseudo-code, the logic is:
2633 *
2634 * orig_contents = file.read(); if (orig_contents == old_contents) {
2635 * file.write(new_contents); return true; } return false;
2636 *
2637 * The operation is guaranteed to be atomic only if the only operations that
2638 * read and write to this file are atomic_compare_and_exchange_contents() and
2639 * atomic_read_contents().
2640 */
2641 bool Filename::
2642 atomic_compare_and_exchange_contents(string &orig_contents,
2643 const string &old_contents,
2644 const string &new_contents) const {
2645 #ifdef WIN32_VC
2646 wstring os_specific = to_os_specific_w();
2647 HANDLE hfile = CreateFileW(os_specific.c_str(), GENERIC_READ | GENERIC_WRITE,
2648 0, nullptr, OPEN_ALWAYS,
2649 FILE_ATTRIBUTE_NORMAL, nullptr);
2650 while (hfile == INVALID_HANDLE_VALUE) {
2651 DWORD error = GetLastError();
2652 if (error == ERROR_SHARING_VIOLATION) {
2653 // If the file is locked by another process, yield and try again.
2654 Sleep(0);
2655 hfile = CreateFileW(os_specific.c_str(), GENERIC_READ | GENERIC_WRITE,
2656 0, nullptr, OPEN_ALWAYS,
2657 FILE_ATTRIBUTE_NORMAL, nullptr);
2658 } else {
2659 cerr << "Couldn't open file: " << os_specific
2660 << ", error " << error << "\n";
2661 return false;
2662 }
2663 }
2664
2665 if (hfile == INVALID_HANDLE_VALUE) {
2666 cerr << "Couldn't open file: " << os_specific
2667 << ", error " << GetLastError() << "\n";
2668 return false;
2669 }
2670
2671 static const size_t buf_size = 512;
2672 char buf[buf_size];
2673
2674 orig_contents = string();
2675
2676 DWORD bytes_read;
2677 if (!ReadFile(hfile, buf, buf_size, &bytes_read, nullptr)) {
2678 cerr << "Error reading file: " << os_specific
2679 << ", error " << GetLastError() << "\n";
2680 CloseHandle(hfile);
2681 return false;
2682 }
2683 while (bytes_read > 0) {
2684 orig_contents += string(buf, bytes_read);
2685
2686 if (!ReadFile(hfile, buf, buf_size, &bytes_read, nullptr)) {
2687 cerr << "Error reading file: " << os_specific
2688 << ", error " << GetLastError() << "\n";
2689 CloseHandle(hfile);
2690 return false;
2691 }
2692 }
2693
2694 bool match = false;
2695 if (orig_contents == old_contents) {
2696 match = true;
2697 SetFilePointer(hfile, 0, 0, FILE_BEGIN);
2698 DWORD bytes_written;
2699 if (!WriteFile(hfile, new_contents.data(), new_contents.size(),
2700 &bytes_written, nullptr)) {
2701 cerr << "Error writing file: " << os_specific
2702 << ", error " << GetLastError() << "\n";
2703 CloseHandle(hfile);
2704 return false;
2705 }
2706 }
2707
2708 CloseHandle(hfile);
2709 return match;
2710
2711 #else // WIN32_VC
2712 string os_specific = to_os_specific();
2713 int fd = open(os_specific.c_str(), O_RDWR | O_CREAT, 0666);
2714 if (fd < 0) {
2715 perror(os_specific.c_str());
2716 return false;
2717 }
2718
2719 static const size_t buf_size = 512;
2720 char buf[buf_size];
2721
2722 orig_contents = string();
2723
2724 #ifdef PHAVE_LOCKF
2725 if (lockf(fd, F_LOCK, 0) != 0) {
2726 #else
2727 if (flock(fd, LOCK_EX) != 0) {
2728 #endif
2729 perror(os_specific.c_str());
2730 close(fd);
2731 return false;
2732 }
2733
2734 ssize_t bytes_read = read(fd, buf, buf_size);
2735 while (bytes_read > 0) {
2736 orig_contents += string(buf, bytes_read);
2737 bytes_read = read(fd, buf, buf_size);
2738 }
2739
2740 if (bytes_read < 0) {
2741 perror(os_specific.c_str());
2742 close(fd);
2743 return false;
2744 }
2745
2746 bool match = false;
2747 if (orig_contents == old_contents) {
2748 match = true;
2749 lseek(fd, 0, SEEK_SET);
2750 ssize_t bytes_written = write(fd, new_contents.data(), new_contents.size());
2751 if (bytes_written < 0) {
2752 perror(os_specific.c_str());
2753 close(fd);
2754 return false;
2755 }
2756 }
2757
2758 if (close(fd) < 0) {
2759 perror(os_specific.c_str());
2760 return false;
2761 }
2762
2763 return match;
2764 #endif // WIN32_VC
2765 }
2766
2767 /**
2768 * Uses native file-locking mechanisms to atomically read the contents of a
2769 * (small) file. This is the only way to read a file protected by
2770 * atomic_compare_and_exchange_contents(), and be confident that the read
2771 * operation is actually atomic with respect to that method.
2772 *
2773 * If the file does not exist, it is implicitly created, and its contents are
2774 * empty.
2775 *
2776 * If the file is read successfully, fills its contents in the indicated
2777 * string, and returns true. If the file cannot be read, returns false.
2778 */
2779 bool Filename::
2780 atomic_read_contents(string &contents) const {
2781 #ifdef WIN32_VC
2782 wstring os_specific = to_os_specific_w();
2783 HANDLE hfile = CreateFileW(os_specific.c_str(), GENERIC_READ,
2784 FILE_SHARE_READ, nullptr, OPEN_ALWAYS,
2785 FILE_ATTRIBUTE_NORMAL, nullptr);
2786 while (hfile == INVALID_HANDLE_VALUE) {
2787 DWORD error = GetLastError();
2788 if (error == ERROR_SHARING_VIOLATION) {
2789 // If the file is locked by another process, yield and try again.
2790 Sleep(0);
2791 hfile = CreateFileW(os_specific.c_str(), GENERIC_READ,
2792 FILE_SHARE_READ, nullptr, OPEN_ALWAYS,
2793 FILE_ATTRIBUTE_NORMAL, nullptr);
2794 } else {
2795 cerr << "Couldn't open file: " << os_specific
2796 << ", error " << error << "\n";
2797 return false;
2798 }
2799 }
2800
2801 static const size_t buf_size = 512;
2802 char buf[buf_size];
2803
2804 contents = string();
2805
2806 DWORD bytes_read;
2807 if (!ReadFile(hfile, buf, buf_size, &bytes_read, nullptr)) {
2808 cerr << "Error reading file: " << os_specific
2809 << ", error " << GetLastError() << "\n";
2810 CloseHandle(hfile);
2811 return false;
2812 }
2813 while (bytes_read > 0) {
2814 contents += string(buf, bytes_read);
2815
2816 if (!ReadFile(hfile, buf, buf_size, &bytes_read, nullptr)) {
2817 cerr << "Error reading file: " << os_specific
2818 << ", error " << GetLastError() << "\n";
2819 CloseHandle(hfile);
2820 return false;
2821 }
2822 }
2823
2824 CloseHandle(hfile);
2825 return true;
2826
2827 #else // WIN32_VC
2828 string os_specific = to_os_specific();
2829 int fd = open(os_specific.c_str(), O_RDWR | O_CREAT, 0666);
2830 if (fd < 0) {
2831 perror(os_specific.c_str());
2832 return false;
2833 }
2834
2835 static const size_t buf_size = 512;
2836 char buf[buf_size];
2837
2838 contents = string();
2839
2840 #ifdef PHAVE_LOCKF
2841 if (lockf(fd, F_LOCK, 0) != 0) {
2842 #else
2843 if (flock(fd, LOCK_EX) != 0) {
2844 #endif
2845 perror(os_specific.c_str());
2846 close(fd);
2847 return false;
2848 }
2849
2850 ssize_t bytes_read = read(fd, buf, buf_size);
2851 while (bytes_read > 0) {
2852 contents += string(buf, bytes_read);
2853 bytes_read = read(fd, buf, buf_size);
2854 }
2855
2856 if (bytes_read < 0) {
2857 perror(os_specific.c_str());
2858 close(fd);
2859 return false;
2860 }
2861
2862 close(fd);
2863 return true;
2864 #endif // WIN32_VC
2865 }
2866
2867 /**
2868 * After the string has been reassigned, search for the slash marking the
2869 * beginning of the basename, and set _dirname_end and _basename_start
2870 * correctly.
2871 */
2872 void Filename::
2873 locate_basename() {
2874 // Scan for the last slash, which marks the end of the directory part.
2875 if (_filename.empty()) {
2876 _dirname_end = 0;
2877 _basename_start = 0;
2878
2879 } else {
2880
2881 string::size_type slash = _filename.rfind('/');
2882 if (slash != string::npos) {
2883 _basename_start = slash + 1;
2884 _dirname_end = _basename_start;
2885
2886 // One exception: in case there are multiple slashes in a row, we want
2887 // to treat them as a single slash. The directory therefore actually
2888 // ends at the first of these; back up a bit.
2889 while (_dirname_end > 0 && _filename[_dirname_end-1] == '/') {
2890 _dirname_end--;
2891 }
2892
2893 // Another exception: if the dirname was nothing but slashes, it was the
2894 // root directory, or itself. In this case the dirname does include
2895 // the terminal slash (of course).
2896 if (_dirname_end == 0) {
2897 _dirname_end = 1;
2898 }
2899
2900 } else {
2901 _dirname_end = 0;
2902 _basename_start = 0;
2903 }
2904 }
2905
2906 // Now:
2907
2908 // _dirname_end is the last slash character, or 0 if there are no slash
2909 // characters.
2910
2911 // _basename_start is the character after the last slash character, or 0 if
2912 // there are no slash characters.
2913 }
2914
2915
2916 /**
2917 * Once the end of the directory prefix has been found, and _dirname_end and
2918 * _basename_start are set correctly, search for the dot marking the beginning
2919 * of the extension, and set _basename_end and _extension_start correctly.
2920 */
2921 void Filename::
2922 locate_extension() {
2923 // Now scan for the last dot after that slash.
2924 if (_filename.empty()) {
2925 _basename_end = string::npos;
2926 _extension_start = string::npos;
2927
2928 } else {
2929 string::size_type dot = _filename.length() - 1;
2930
2931 while (dot+1 > _basename_start && _filename[dot] != '.') {
2932 --dot;
2933 }
2934
2935 if (dot+1 > _basename_start) {
2936 _basename_end = dot;
2937 _extension_start = dot + 1;
2938 } else {
2939 _basename_end = string::npos;
2940 _extension_start = string::npos;
2941 }
2942 }
2943
2944 // Now:
2945
2946 // _basename_end is the last dot, or npos if there is no dot.
2947
2948 // _extension_start is the character after the last dot, or npos if there is
2949 // no dot.
2950 }
2951
2952 /**
2953 * Identifies the part of the filename that contains the sequence of hash
2954 * marks, if any.
2955 */
2956 void Filename::
2957 locate_hash() {
2958 if (!get_pattern()) {
2959 // If it's not a pattern-type filename, these are always set to the end of
2960 // the string.
2961 _hash_end = string::npos;
2962 _hash_start = string::npos;
2963
2964 } else {
2965 // If it is a pattern-type filename, we must search for the hash marks,
2966 // which could be anywhere (but are usually toward the end).
2967 _hash_end = _filename.rfind('#');
2968 if (_hash_end == string::npos) {
2969 _hash_end = string::npos;
2970 _hash_start = string::npos;
2971
2972 } else {
2973 _hash_start = _hash_end;
2974 ++_hash_end;
2975 while (_hash_start > 0 && _filename[_hash_start - 1] == '#') {
2976 --_hash_start;
2977 }
2978 }
2979 }
2980 }
2981
2982
2983 /**
2984 * Returns the length of the longest common initial substring of this string
2985 * and the other one that ends in a slash. This is the lowest directory
2986 * common to both filenames.
2987 */
2988 size_t Filename::
2989 get_common_prefix(const string &other) const {
2990 size_t len = 0;
2991
2992 // First, get the length of the common initial substring.
2993 while (len < length() && len < other.length() &&
2994 _filename[len] == other[len]) {
2995 len++;
2996 }
2997
2998 // Now insist that it ends in a slash.
2999 while (len > 0 && _filename[len-1] != '/') {
3000 len--;
3001 }
3002
3003 return len;
3004 }
3005
3006 /**
3007 * Returns the number of non-consecutive slashes in the indicated string, not
3008 * counting a terminal slash.
3009 */
3010 int Filename::
3011 count_slashes(const string &str) {
3012 int count = 0;
3013 string::const_iterator si;
3014 si = str.begin();
3015
3016 while (si != str.end()) {
3017 if (*si == '/') {
3018 count++;
3019
3020 // Skip consecutive slashes.
3021 ++si;
3022 while (*si == '/') {
3023 ++si;
3024 }
3025 if (si == str.end()) {
3026 // Oops, that was a terminal slash. Don't count it.
3027 count--;
3028 }
3029
3030 } else {
3031 ++si;
3032 }
3033 }
3034
3035 return count;
3036 }
3037
3038
3039 /**
3040 * The recursive implementation of make_canonical().
3041 */
3042 bool Filename::
3043 r_make_canonical(const Filename &cwd) {
3044 if (get_fullpath() == "/") {
3045 // If we reached the root, the whole path doesn't exist. Report failure.
3046 return false;
3047 }
3048
3049 #ifdef WIN32_VC
3050 // First, try to cd to the filename directly.
3051 wstring os_specific = to_os_specific_w();
3052 if (_wchdir(os_specific.c_str()) >= 0) {
3053 // That worked, save the full path string.
3054 (*this) = ExecutionEnvironment::get_cwd();
3055
3056 // And restore the current working directory.
3057 wstring osdir = cwd.to_os_specific_w();
3058 if (_wchdir(osdir.c_str()) < 0) {
3059 cerr << "Error! Cannot change back to " << osdir << "\n";
3060 }
3061 return true;
3062 }
3063 #else // WIN32_VC
3064 // First, try to cd to the filename directly.
3065 string os_specific = to_os_specific();
3066 if (::chdir(os_specific.c_str()) >= 0) {
3067 // That worked, save the full path string.
3068 (*this) = ExecutionEnvironment::get_cwd();
3069
3070 // And restore the current working directory.
3071 string osdir = cwd.to_os_specific();
3072 if (::chdir(osdir.c_str()) < 0) {
3073 cerr << "Error! Cannot change back to " << osdir << "\n";
3074 }
3075 return true;
3076 }
3077 #endif // WIN32_VC
3078
3079 // That didn't work; maybe it's not a directory. Recursively go to the
3080 // directory above.
3081
3082 Filename dir(get_dirname());
3083
3084 if (dir.empty()) {
3085 // No dirname means the file is in this directory.
3086 set_dirname(cwd);
3087 return true;
3088 }
3089
3090 if (!dir.r_make_canonical(cwd)) {
3091 return false;
3092 }
3093 set_dirname(dir);
3094 return true;
3095 }
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
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
Filename::set_basename
void set_basename(const std::string &s)
Replaces the basename part of the filename.
Definition: filename.cxx:754
Filename::compare_timestamps
int compare_timestamps(const Filename &other, bool this_missing_is_old=true, bool other_missing_is_old=true) const
Returns a number less than zero if the file named by this object is older than the given file,...
Definition: filename.cxx:1417
Filename::open_append
bool open_append(std::ofstream &stream) const
Opens the indicated ofstream for writing the file, if possible.
Definition: filename.cxx:1945
Filename::is_regular_file
bool is_regular_file() const
Returns true if the filename exists and is the name of a regular file (i.e.
Definition: filename.cxx:1297
Filename::scan_directory
bool scan_directory(vector_string &contents) const
Attempts to open the named filename as if it were a directory and looks for the non-hidden files with...
Definition: filename.cxx:1718
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:2438
Filename::resolve_filename
bool resolve_filename(const DSearchPath &searchpath, const std::string &default_extension=std::string())
Searches the given search path for the filename.
Definition: filename.cxx:1581
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::open_read_append
bool open_read_append(std::fstream &stream) const
Opens the indicated ifstream for reading and writing the file, if possible; writes are appended to th...
Definition: filename.cxx:2019
Filename::is_executable
bool is_executable() const
Returns true if the filename exists and is executable.
Definition: filename.cxx:1387
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_short_name
std::string to_os_short_name() const
This works like to_os_generic(), but it returns the "short name" version of the filename,...
Definition: filename.cxx:1202
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:1863
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:2557
Filename::rename_to
bool rename_to(const Filename &other) const
Renames the file to the indicated new filename.
Definition: filename.cxx:2339
Filename::open_read_write
bool open_read_write(std::fstream &stream, bool truncate=false) const
Opens the indicated fstream for read/write access to the file, if possible.
Definition: filename.cxx:1977
Filename::set_hash_to_end
void set_hash_to_end(const std::string &s)
Replaces the part of the filename from the beginning of the hash sequence to the end of the filename.
Definition: filename.cxx:856
Filename::get_hash
int get_hash() const
Returns a hash code that attempts to be mostly unique for different Filenames.
Definition: filename.cxx:2582
Filename::get_common_appdata_directory
static const Filename & get_common_appdata_directory()
Returns a path to a system-defined directory appropriate for creating a subdirectory for storing appl...
Definition: filename.cxx:645
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:2319
Filename::expand_from
static Filename expand_from(const std::string &user_string, Type type=T_general)
Returns the same thing as from_os_specific(), but embedded environment variable references (e....
Definition: filename.cxx:407
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_fullpath_wo_extension
void set_fullpath_wo_extension(const std::string &s)
Replaces the full filename–directory and basename parts–except for the extension.
Definition: filename.cxx:766
Filename::is_writable
bool is_writable() const
Returns true if the filename exists and is either a directory or a regular file that can be written t...
Definition: filename.cxx:1327
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::set_basename_wo_extension
void set_basename_wo_extension(const std::string &s)
Replaces the basename part of the filename, without the file extension.
Definition: filename.cxx:783
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::get_user_appdata_directory
static const Filename & get_user_appdata_directory()
Returns a path to a system-defined directory appropriate for creating a subdirectory for storing appl...
Definition: filename.cxx:589
Filename::chdir
bool chdir() const
Changes directory to the specified location.
Definition: filename.cxx:2303
Filename::touch
bool touch() const
Updates the modification time of the file to the current time.
Definition: filename.cxx:2225
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::make_dir
bool make_dir() const
Creates all the directories in the path to the file specified in the filename, except for the basenam...
Definition: filename.cxx:2484
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_timestamp
time_t get_timestamp() const
Returns a time_t value that represents the time the file was last modified, to within whatever precis...
Definition: filename.cxx:1497
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::to_os_generic
std::string to_os_generic() const
This is similar to to_os_specific(), but it is designed to generate a filename that can be understood...
Definition: filename.cxx:1182
Filename::find_on_searchpath
int find_on_searchpath(const DSearchPath &searchpath)
Performs the reverse of the resolve_filename() operation: assuming that the current filename is fully...
Definition: filename.cxx:1689
Filename::extract_components
void extract_components(vector_string &components) const
Extracts out the individual directory components of the path into a series of strings.
Definition: filename.cxx:872
Filename::is_directory
bool is_directory() const
Returns true if the filename exists and is a directory name, false otherwise.
Definition: filename.cxx:1359
Filename::to_os_long_name
std::string to_os_long_name() const
This is the opposite of to_os_short_name(): it returns the "long name" of the filename,...
Definition: filename.cxx:1236
Filename::atomic_compare_and_exchange_contents
bool atomic_compare_and_exchange_contents(std::string &orig_contents, const std::string &old_contents, const std::string &new_contents) const
Uses native file-locking mechanisms to atomically replace the contents of a (small) file with the spe...
Definition: filename.cxx:2642
Filename::get_fullpath_w
std::wstring get_fullpath_w() const
Returns the entire filename as a wide-character string.
Definition: filename.I:346
Filename::atomic_read_contents
bool atomic_read_contents(std::string &contents) const
Uses native file-locking mechanisms to atomically read the contents of a (small) file.
Definition: filename.cxx:2780
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:1899
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:1640
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:2540
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 disk, false otherwise.
Definition: filename.cxx:1267
Filename::get_access_timestamp
time_t get_access_timestamp() const
Returns a time_t value that represents the time the file was last accessed, if this information is av...
Definition: filename.cxx:1525
Filename::get_file_size
std::streamsize get_file_size() const
Returns the size of the file in bytes, or 0 if there is an error.
Definition: filename.cxx:1551
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
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.