1 // Copyright 2018 The Chromium OS Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "filepath.h" 6 #include <string.h> 7 #include <sys/stat.h> 8 #include <sys/types.h> 9 #include <vector> 10 #include "utils.h" 11 12 inline void AppendToString(std::string* target, std::string& source) { 13 target->append(source); 14 } 15 16 FilePath FilePath::DirName() { 17 FilePath new_path(path_); 18 19 std::string::size_type last_separator = 20 new_path.path_.find_last_of(kSeparators, std::string::npos); 21 22 unsigned int letter = 0; 23 if (last_separator == std::string::npos) { 24 // path_ is in the current directory. 25 new_path.path_.resize(letter + 1); 26 } else if (last_separator == letter + 1) { 27 // path_ is in the root directory. 28 new_path.path_.resize(letter + 2); 29 } else if (last_separator == letter + 2 && 30 IsSeparator(new_path.path_[letter + 1])) { 31 // path_ is in "//" (possibly with a drive letter); leave the double 32 // separator intact indicating alternate root. 33 new_path.path_.resize(letter + 3); 34 } else if (last_separator != 0) { 35 // path_ is somewhere else, trim the basename. 36 new_path.path_.resize(last_separator); 37 } 38 new_path.StripTrailingSeparatorsInternal(); 39 if (!new_path.path_.length()) 40 new_path.path_ = kCurrentDirectory; 41 42 return new_path; 43 } 44 45 const std::string& FilePath::value() const { 46 return this->path_; 47 } 48 49 bool FilePath::IsSeparator(char character) { 50 for (size_t i = 0; i < strlen(kSeparators) - 1; ++i) { 51 if (character == kSeparators[i]) { 52 return true; 53 } 54 } 55 return false; 56 } 57 58 FilePath FilePath::Append(const FilePath& path) { 59 // return FilePath(this->path_ + path.path_); 60 std::string component = path.value(); 61 std::string appended = component; 62 std::string without_nuls; 63 64 std::string::size_type nul_pos = component.find(kStringTerminator); 65 if (nul_pos != std::string::npos) { 66 // without_nuls = component.substr(0, nul_pos); 67 appended = std::string(without_nuls); 68 } 69 70 // DCHECK(appended.length() <= 0 || appended[0] != 71 // this->separator.c_str()[0]); 72 if (path_.compare(kCurrentDirectory) == 0 && !appended.empty()) { 73 // Append normally doesn't do any normalization, but as a special case, 74 // when appending to kCurrentDirectory, just return a new path for the 75 // component argument. Appending component to kCurrentDirectory would 76 // serve no purpose other than needlessly lengthening the path, and 77 // it's likely in practice to wind up with FilePath objects containing 78 // only kCurrentDirectory when calling DirName on a single relative path 79 // component. 80 return FilePath(appended); 81 } 82 FilePath new_path(path_); 83 new_path.StripTrailingSeparatorsInternal(); 84 85 // Don't append a separator if the path is empty (indicating the current 86 // directory) or if the path component is empty (indicating nothing to 87 // append). 88 if (!appended.empty() && !new_path.path_.empty()) { 89 // Don't append a separator if the path still ends with a trailing 90 // separator after stripping (indicating the root directory). 91 char tmp = new_path.path_.back(); 92 if (!IsSeparator(tmp)) { 93 // Don't append a separator if the path is just a drive letter. 94 if (FindDriveLetter(new_path.path_) + 1 != new_path.path_.length()) { 95 new_path.path_.append(1, kSeparators[0]); 96 } 97 } 98 } 99 AppendToString(&new_path.path_, appended); 100 return new_path; 101 } 102 103 void FilePath::StripTrailingSeparatorsInternal() { 104 // If there is no drive letter, start will be 1, which will prevent 105 // stripping the leading separator if there is only one separator. If there 106 // is a drive letter, start will be set appropriately to prevent stripping 107 // the first separator following the drive letter, if a separator 108 // immediately follows the drive letter. 109 std::string::size_type start = FindDriveLetter(path_) + 2; 110 111 std::string::size_type last_stripped = std::string::npos; 112 for (std::string::size_type pos = path_.length(); 113 pos > start && IsSeparator(path_[pos - 1]); --pos) { 114 // If the string only has two separators and they're at the beginning, 115 // don't strip them, unless the string began with more than two 116 // separators. 117 if (pos != start + 1 || last_stripped == start + 2 || 118 !IsSeparator(path_[start - 1])) { 119 path_.resize(pos - 1); 120 last_stripped = pos; 121 } 122 } 123 } 124 125 std::string::size_type FindDriveLetter(std::string path) { 126 #if defined(FILE_PATH_USES_DRIVE_LETTERS) 127 // This is dependent on an ASCII-based character set, but that's a 128 // reasonable assumption. iswalpha can be too inclusive here. 129 if (path.length() >= 2 && path[1] == L':' && 130 ((path[0] >= L'A' && path[0] <= L'Z') || 131 (path[0] >= L'a' && path[0] <= L'z'))) { 132 return 1; 133 } 134 #endif // FILE_PATH_USES_DRIVE_LETTERS 135 return std::string::npos; 136 } 137 138 bool CreateDirectory(FilePath& full_path) { 139 std::vector<FilePath> subpaths; 140 141 // Collect a list of all parent directories. 142 FilePath last_path = full_path; 143 subpaths.push_back(full_path); 144 for (FilePath path = full_path.DirName(); path.value() != last_path.value(); 145 path = path.DirName()) { 146 subpaths.push_back(path); 147 last_path = path; 148 } 149 150 // Iterate through the parents and create the missing ones. 151 for (auto i = subpaths.rbegin(); i != subpaths.rend(); ++i) { 152 if (check_dir_existence(i->value().c_str())) 153 continue; 154 if (mkdir(i->value().c_str(), 0700) == 0) 155 continue; 156 // Mkdir failed, but it might have failed with EEXIST, or some other error 157 // due to the the directory appearing out of thin air. This can occur if 158 // two processes are trying to create the same file system tree at the same 159 // time. Check to see if it exists and make sure it is a directory. 160 if (!check_dir_existence(i->value().c_str())) { 161 return false; 162 } 163 } 164 return true; 165 } 166