1 // Copyright 2008, Google Inc. 2 // All rights reserved. 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions are 6 // met: 7 // 8 // * Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // * Redistributions in binary form must reproduce the above 11 // copyright notice, this list of conditions and the following disclaimer 12 // in the documentation and/or other materials provided with the 13 // distribution. 14 // * Neither the name of Google Inc. nor the names of its 15 // contributors may be used to endorse or promote products derived from 16 // this software without specific prior written permission. 17 // 18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 // 30 // Authors: keith.ray (at) gmail.com (Keith Ray) 31 32 #include "gtest/internal/gtest-filepath.h" 33 #include "gtest/internal/gtest-port.h" 34 35 #include <stdlib.h> 36 37 #if GTEST_OS_WINDOWS_MOBILE 38 # include <windows.h> 39 #elif GTEST_OS_WINDOWS 40 # include <direct.h> 41 # include <io.h> 42 #elif GTEST_OS_SYMBIAN || GTEST_OS_NACL 43 // Symbian OpenC and NaCl have PATH_MAX in sys/syslimits.h 44 # include <sys/syslimits.h> 45 #else 46 # include <limits.h> 47 # include <climits> // Some Linux distributions define PATH_MAX here. 48 #endif // GTEST_OS_WINDOWS_MOBILE 49 50 #if GTEST_OS_WINDOWS 51 # define GTEST_PATH_MAX_ _MAX_PATH 52 #elif defined(PATH_MAX) 53 # define GTEST_PATH_MAX_ PATH_MAX 54 #elif defined(_XOPEN_PATH_MAX) 55 # define GTEST_PATH_MAX_ _XOPEN_PATH_MAX 56 #else 57 # define GTEST_PATH_MAX_ _POSIX_PATH_MAX 58 #endif // GTEST_OS_WINDOWS 59 60 #include "gtest/internal/gtest-string.h" 61 62 namespace testing { 63 namespace internal { 64 65 #if GTEST_OS_WINDOWS 66 // On Windows, '\\' is the standard path separator, but many tools and the 67 // Windows API also accept '/' as an alternate path separator. Unless otherwise 68 // noted, a file path can contain either kind of path separators, or a mixture 69 // of them. 70 const char kPathSeparator = '\\'; 71 const char kAlternatePathSeparator = '/'; 72 const char kAlternatePathSeparatorString[] = "/"; 73 # if GTEST_OS_WINDOWS_MOBILE 74 // Windows CE doesn't have a current directory. You should not use 75 // the current directory in tests on Windows CE, but this at least 76 // provides a reasonable fallback. 77 const char kCurrentDirectoryString[] = "\\"; 78 // Windows CE doesn't define INVALID_FILE_ATTRIBUTES 79 const DWORD kInvalidFileAttributes = 0xffffffff; 80 # else 81 const char kCurrentDirectoryString[] = ".\\"; 82 # endif // GTEST_OS_WINDOWS_MOBILE 83 #else 84 const char kPathSeparator = '/'; 85 const char kCurrentDirectoryString[] = "./"; 86 #endif // GTEST_OS_WINDOWS 87 88 // Returns whether the given character is a valid path separator. 89 static bool IsPathSeparator(char c) { 90 #if GTEST_HAS_ALT_PATH_SEP_ 91 return (c == kPathSeparator) || (c == kAlternatePathSeparator); 92 #else 93 return c == kPathSeparator; 94 #endif 95 } 96 97 // Returns the current working directory, or "" if unsuccessful. 98 FilePath FilePath::GetCurrentDir() { 99 #if GTEST_OS_WINDOWS_MOBILE 100 // Windows CE doesn't have a current directory, so we just return 101 // something reasonable. 102 return FilePath(kCurrentDirectoryString); 103 #elif GTEST_OS_WINDOWS 104 char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; 105 return FilePath(_getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd); 106 #else 107 char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; 108 return FilePath(getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd); 109 #endif // GTEST_OS_WINDOWS_MOBILE 110 } 111 112 // Returns a copy of the FilePath with the case-insensitive extension removed. 113 // Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns 114 // FilePath("dir/file"). If a case-insensitive extension is not 115 // found, returns a copy of the original FilePath. 116 FilePath FilePath::RemoveExtension(const char* extension) const { 117 String dot_extension(String::Format(".%s", extension)); 118 if (pathname_.EndsWithCaseInsensitive(dot_extension.c_str())) { 119 return FilePath(String(pathname_.c_str(), pathname_.length() - 4)); 120 } 121 return *this; 122 } 123 124 // Returns a pointer to the last occurrence of a valid path separator in 125 // the FilePath. On Windows, for example, both '/' and '\' are valid path 126 // separators. Returns NULL if no path separator was found. 127 const char* FilePath::FindLastPathSeparator() const { 128 const char* const last_sep = strrchr(c_str(), kPathSeparator); 129 #if GTEST_HAS_ALT_PATH_SEP_ 130 const char* const last_alt_sep = strrchr(c_str(), kAlternatePathSeparator); 131 // Comparing two pointers of which only one is NULL is undefined. 132 if (last_alt_sep != NULL && 133 (last_sep == NULL || last_alt_sep > last_sep)) { 134 return last_alt_sep; 135 } 136 #endif 137 return last_sep; 138 } 139 140 // Returns a copy of the FilePath with the directory part removed. 141 // Example: FilePath("path/to/file").RemoveDirectoryName() returns 142 // FilePath("file"). If there is no directory part ("just_a_file"), it returns 143 // the FilePath unmodified. If there is no file part ("just_a_dir/") it 144 // returns an empty FilePath (""). 145 // On Windows platform, '\' is the path separator, otherwise it is '/'. 146 FilePath FilePath::RemoveDirectoryName() const { 147 const char* const last_sep = FindLastPathSeparator(); 148 return last_sep ? FilePath(String(last_sep + 1)) : *this; 149 } 150 151 // RemoveFileName returns the directory path with the filename removed. 152 // Example: FilePath("path/to/file").RemoveFileName() returns "path/to/". 153 // If the FilePath is "a_file" or "/a_file", RemoveFileName returns 154 // FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does 155 // not have a file, like "just/a/dir/", it returns the FilePath unmodified. 156 // On Windows platform, '\' is the path separator, otherwise it is '/'. 157 FilePath FilePath::RemoveFileName() const { 158 const char* const last_sep = FindLastPathSeparator(); 159 String dir; 160 if (last_sep) { 161 dir = String(c_str(), last_sep + 1 - c_str()); 162 } else { 163 dir = kCurrentDirectoryString; 164 } 165 return FilePath(dir); 166 } 167 168 // Helper functions for naming files in a directory for xml output. 169 170 // Given directory = "dir", base_name = "test", number = 0, 171 // extension = "xml", returns "dir/test.xml". If number is greater 172 // than zero (e.g., 12), returns "dir/test_12.xml". 173 // On Windows platform, uses \ as the separator rather than /. 174 FilePath FilePath::MakeFileName(const FilePath& directory, 175 const FilePath& base_name, 176 int number, 177 const char* extension) { 178 String file; 179 if (number == 0) { 180 file = String::Format("%s.%s", base_name.c_str(), extension); 181 } else { 182 file = String::Format("%s_%d.%s", base_name.c_str(), number, extension); 183 } 184 return ConcatPaths(directory, FilePath(file)); 185 } 186 187 // Given directory = "dir", relative_path = "test.xml", returns "dir/test.xml". 188 // On Windows, uses \ as the separator rather than /. 189 FilePath FilePath::ConcatPaths(const FilePath& directory, 190 const FilePath& relative_path) { 191 if (directory.IsEmpty()) 192 return relative_path; 193 const FilePath dir(directory.RemoveTrailingPathSeparator()); 194 return FilePath(String::Format("%s%c%s", dir.c_str(), kPathSeparator, 195 relative_path.c_str())); 196 } 197 198 // Returns true if pathname describes something findable in the file-system, 199 // either a file, directory, or whatever. 200 bool FilePath::FileOrDirectoryExists() const { 201 #if GTEST_OS_WINDOWS_MOBILE 202 LPCWSTR unicode = String::AnsiToUtf16(pathname_.c_str()); 203 const DWORD attributes = GetFileAttributes(unicode); 204 delete [] unicode; 205 return attributes != kInvalidFileAttributes; 206 #else 207 posix::StatStruct file_stat; 208 return posix::Stat(pathname_.c_str(), &file_stat) == 0; 209 #endif // GTEST_OS_WINDOWS_MOBILE 210 } 211 212 // Returns true if pathname describes a directory in the file-system 213 // that exists. 214 bool FilePath::DirectoryExists() const { 215 bool result = false; 216 #if GTEST_OS_WINDOWS 217 // Don't strip off trailing separator if path is a root directory on 218 // Windows (like "C:\\"). 219 const FilePath& path(IsRootDirectory() ? *this : 220 RemoveTrailingPathSeparator()); 221 #else 222 const FilePath& path(*this); 223 #endif 224 225 #if GTEST_OS_WINDOWS_MOBILE 226 LPCWSTR unicode = String::AnsiToUtf16(path.c_str()); 227 const DWORD attributes = GetFileAttributes(unicode); 228 delete [] unicode; 229 if ((attributes != kInvalidFileAttributes) && 230 (attributes & FILE_ATTRIBUTE_DIRECTORY)) { 231 result = true; 232 } 233 #else 234 posix::StatStruct file_stat; 235 result = posix::Stat(path.c_str(), &file_stat) == 0 && 236 posix::IsDir(file_stat); 237 #endif // GTEST_OS_WINDOWS_MOBILE 238 239 return result; 240 } 241 242 // Returns true if pathname describes a root directory. (Windows has one 243 // root directory per disk drive.) 244 bool FilePath::IsRootDirectory() const { 245 #if GTEST_OS_WINDOWS 246 // TODO(wan (at) google.com): on Windows a network share like 247 // \\server\share can be a root directory, although it cannot be the 248 // current directory. Handle this properly. 249 return pathname_.length() == 3 && IsAbsolutePath(); 250 #else 251 return pathname_.length() == 1 && IsPathSeparator(pathname_.c_str()[0]); 252 #endif 253 } 254 255 // Returns true if pathname describes an absolute path. 256 bool FilePath::IsAbsolutePath() const { 257 const char* const name = pathname_.c_str(); 258 #if GTEST_OS_WINDOWS 259 return pathname_.length() >= 3 && 260 ((name[0] >= 'a' && name[0] <= 'z') || 261 (name[0] >= 'A' && name[0] <= 'Z')) && 262 name[1] == ':' && 263 IsPathSeparator(name[2]); 264 #else 265 return IsPathSeparator(name[0]); 266 #endif 267 } 268 269 // Returns a pathname for a file that does not currently exist. The pathname 270 // will be directory/base_name.extension or 271 // directory/base_name_<number>.extension if directory/base_name.extension 272 // already exists. The number will be incremented until a pathname is found 273 // that does not already exist. 274 // Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'. 275 // There could be a race condition if two or more processes are calling this 276 // function at the same time -- they could both pick the same filename. 277 FilePath FilePath::GenerateUniqueFileName(const FilePath& directory, 278 const FilePath& base_name, 279 const char* extension) { 280 FilePath full_pathname; 281 int number = 0; 282 do { 283 full_pathname.Set(MakeFileName(directory, base_name, number++, extension)); 284 } while (full_pathname.FileOrDirectoryExists()); 285 return full_pathname; 286 } 287 288 // Returns true if FilePath ends with a path separator, which indicates that 289 // it is intended to represent a directory. Returns false otherwise. 290 // This does NOT check that a directory (or file) actually exists. 291 bool FilePath::IsDirectory() const { 292 return !pathname_.empty() && 293 IsPathSeparator(pathname_.c_str()[pathname_.length() - 1]); 294 } 295 296 // Create directories so that path exists. Returns true if successful or if 297 // the directories already exist; returns false if unable to create directories 298 // for any reason. 299 bool FilePath::CreateDirectoriesRecursively() const { 300 if (!this->IsDirectory()) { 301 return false; 302 } 303 304 if (pathname_.length() == 0 || this->DirectoryExists()) { 305 return true; 306 } 307 308 const FilePath parent(this->RemoveTrailingPathSeparator().RemoveFileName()); 309 return parent.CreateDirectoriesRecursively() && this->CreateFolder(); 310 } 311 312 // Create the directory so that path exists. Returns true if successful or 313 // if the directory already exists; returns false if unable to create the 314 // directory for any reason, including if the parent directory does not 315 // exist. Not named "CreateDirectory" because that's a macro on Windows. 316 bool FilePath::CreateFolder() const { 317 #if GTEST_OS_WINDOWS_MOBILE 318 FilePath removed_sep(this->RemoveTrailingPathSeparator()); 319 LPCWSTR unicode = String::AnsiToUtf16(removed_sep.c_str()); 320 int result = CreateDirectory(unicode, NULL) ? 0 : -1; 321 delete [] unicode; 322 #elif GTEST_OS_WINDOWS 323 int result = _mkdir(pathname_.c_str()); 324 #else 325 int result = mkdir(pathname_.c_str(), 0777); 326 #endif // GTEST_OS_WINDOWS_MOBILE 327 328 if (result == -1) { 329 return this->DirectoryExists(); // An error is OK if the directory exists. 330 } 331 return true; // No error. 332 } 333 334 // If input name has a trailing separator character, remove it and return the 335 // name, otherwise return the name string unmodified. 336 // On Windows platform, uses \ as the separator, other platforms use /. 337 FilePath FilePath::RemoveTrailingPathSeparator() const { 338 return IsDirectory() 339 ? FilePath(String(pathname_.c_str(), pathname_.length() - 1)) 340 : *this; 341 } 342 343 // Removes any redundant separators that might be in the pathname. 344 // For example, "bar///foo" becomes "bar/foo". Does not eliminate other 345 // redundancies that might be in a pathname involving "." or "..". 346 // TODO(wan (at) google.com): handle Windows network shares (e.g. \\server\share). 347 void FilePath::Normalize() { 348 if (pathname_.c_str() == NULL) { 349 pathname_ = ""; 350 return; 351 } 352 const char* src = pathname_.c_str(); 353 char* const dest = new char[pathname_.length() + 1]; 354 char* dest_ptr = dest; 355 memset(dest_ptr, 0, pathname_.length() + 1); 356 357 while (*src != '\0') { 358 *dest_ptr = *src; 359 if (!IsPathSeparator(*src)) { 360 src++; 361 } else { 362 #if GTEST_HAS_ALT_PATH_SEP_ 363 if (*dest_ptr == kAlternatePathSeparator) { 364 *dest_ptr = kPathSeparator; 365 } 366 #endif 367 while (IsPathSeparator(*src)) 368 src++; 369 } 370 dest_ptr++; 371 } 372 *dest_ptr = '\0'; 373 pathname_ = dest; 374 delete[] dest; 375 } 376 377 } // namespace internal 378 } // namespace testing 379