1 /*
2 * Copyright (C) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "io/std_directory.h"
17
18 #include <algorithm>
19
20 // MINGW has filesystem but it's buggy: https://sourceforge.net/p/mingw-w64/bugs/737/
21 // Not using it for now.
22 #ifdef _MSC_VER
23 #include <filesystem>
24 #define HAS_FILESYSTEM
25 #else
26 #include <dirent.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #endif
30
31 #include <core/log.h>
32 #include <core/namespace.h>
33
34 CORE_BEGIN_NAMESPACE()
35 using BASE_NS::make_unique;
36 using BASE_NS::string;
37 using BASE_NS::string_view;
38 using BASE_NS::vector;
39
40 #ifdef HAS_FILESYSTEM
41
42 // Hiding the directory implementation from the header.
43 struct DirImpl {
DirImplDirImpl44 DirImpl(const string_view path) : path_(path) {}
45 string path_;
46 };
47
48 namespace {
GetTimeStamp(const std::filesystem::directory_entry & entry)49 uint64_t GetTimeStamp(const std::filesystem::directory_entry& entry)
50 {
51 return static_cast<uint64_t>(entry.last_write_time().time_since_epoch().count());
52 }
53
GetEntryType(const std::filesystem::directory_entry & entry)54 IDirectory::Entry::Type GetEntryType(const std::filesystem::directory_entry& entry)
55 {
56 if (entry.is_directory()) {
57 return IDirectory::Entry::DIRECTORY;
58 } else if (entry.is_regular_file()) {
59 return IDirectory::Entry::FILE;
60 }
61
62 return IDirectory::Entry::UNKNOWN;
63 }
64 } // namespace
65
66 StdDirectory::StdDirectory() = default;
67
~StdDirectory()68 StdDirectory::~StdDirectory()
69 {
70 Close();
71 }
72
Close()73 void StdDirectory::Close()
74 {
75 if (dir_) {
76 dir_.reset();
77 }
78 }
79
Open(const string_view path)80 bool StdDirectory::Open(const string_view path)
81 {
82 CORE_ASSERT_MSG((dir_ == nullptr), "Dir already opened.");
83 std::error_code ec;
84 if (std::filesystem::is_directory({ path.begin().ptr(), path.end().ptr() }, ec)) {
85 dir_ = make_unique<DirImpl>(path);
86 return true;
87 }
88 return false;
89 }
90
GetEntries() const91 vector<IDirectory::Entry> StdDirectory::GetEntries() const
92 {
93 CORE_ASSERT_MSG(dir_, "Dir not open");
94 vector<IDirectory::Entry> result;
95 if (dir_) {
96 std::error_code ec;
97 for (auto& iter :
98 std::filesystem::directory_iterator({ dir_->path_.begin().ptr(), dir_->path_.end().ptr() }, ec)) {
99 const auto filename = iter.path().filename().u8string();
100 auto str = string(filename.c_str(), filename.length());
101 result.emplace_back(IDirectory::Entry { GetEntryType(iter), move(str), GetTimeStamp(iter) });
102 }
103 }
104
105 return result;
106 }
107
108 #else // HAS_FILESYSTEM not defined
109
110 // Hiding the directory implementation from the header.
111 struct DirImpl {
DirImplDirImpl112 DirImpl(const string_view path, DIR* aDir) : path_(path), dir_(aDir) {}
113 string path_;
114 DIR* dir_;
115 };
116
117 namespace {
CreateEntry(const string_view path,const dirent & entry)118 IDirectory::Entry CreateEntry(const string_view path, const dirent& entry)
119 {
120 const auto fullPath = path + string(entry.d_name);
121 struct stat statBuf {};
122 const auto statResult = (stat(fullPath.c_str(), &statBuf) == 0);
123
124 IDirectory::Entry::Type type;
125 #ifdef _DIRENT_HAVE_D_TYPE
126 switch (entry.d_type) {
127 case DT_DIR:
128 type = IDirectory::Entry::DIRECTORY;
129 break;
130 case DT_REG:
131 type = IDirectory::Entry::FILE;
132 break;
133 default:
134 type = IDirectory::Entry::UNKNOWN;
135 break;
136 }
137 #else
138 type = IDirectory::Entry::UNKNOWN;
139 if (statResult) {
140 if (S_ISDIR(statBuf.st_mode)) {
141 type = IDirectory::Entry::DIRECTORY;
142 } else if (S_ISREG(statBuf.st_mode)) {
143 type = IDirectory::Entry::FILE;
144 }
145 }
146 #endif
147
148 // Get the timestamp for the directory entry.
149 uint64_t timestamp = 0;
150 if (statResult) {
151 timestamp = statBuf.st_mtime;
152 }
153
154 return IDirectory::Entry { type, entry.d_name, timestamp };
155 }
156 } // namespace
157
StdDirectory()158 StdDirectory::StdDirectory() : dir_() {}
159
~StdDirectory()160 StdDirectory::~StdDirectory()
161 {
162 Close();
163 }
164
Close()165 void StdDirectory::Close()
166 {
167 if (dir_) {
168 closedir(dir_->dir_);
169 dir_.reset();
170 }
171 }
172
Open(const string_view path)173 bool StdDirectory::Open(const string_view path)
174 {
175 CORE_ASSERT_MSG((dir_ == nullptr), "Dir already opened.");
176 DIR* dir = opendir(string(path).c_str());
177 if (dir) {
178 dir_ = make_unique<DirImpl>(path, dir);
179 return true;
180 }
181 return false;
182 }
183
GetEntries() const184 vector<IDirectory::Entry> StdDirectory::GetEntries() const
185 {
186 CORE_ASSERT_MSG(dir_, "Dir not open");
187 vector<IDirectory::Entry> result;
188 if (dir_) {
189 // Go back to start.
190 rewinddir(dir_->dir_);
191
192 // Iterate all entries and write to result.
193 struct dirent* directoryEntry;
194 while ((directoryEntry = readdir(dir_->dir_)) != nullptr) {
195 if (!strcmp(directoryEntry->d_name, ".") || !strcmp(directoryEntry->d_name, "..")) {
196 continue;
197 }
198 result.emplace_back(CreateEntry(dir_->path_, *directoryEntry));
199 }
200 }
201
202 return result;
203 }
204
205 #endif // HAS_FILESYSTEM
206
ResolveAbsolutePath(const string_view pathIn,bool isDirectory)207 string StdDirectory::ResolveAbsolutePath(const string_view pathIn, bool isDirectory)
208 {
209 string absolutePath;
210 auto path = pathIn;
211 #ifdef _WIN32
212 // remove the '/' slash..
213 path = path.substr(1);
214 #endif
215
216 #ifdef HAS_FILESYSTEM
217 std::error_code ec;
218 std::filesystem::path fsPath = std::filesystem::canonical({ path.begin().ptr(), path.end().ptr() }, ec);
219 if (ec.value() == 0) {
220 const auto pathStr = fsPath.string();
221 absolutePath.assign(pathStr.data(), pathStr.size());
222 }
223 #elif defined(_WIN32)
224 char resolvedPath[_MAX_PATH] = {};
225 if (_fullpath(resolvedPath, string(path).c_str(), _MAX_PATH) != nullptr) {
226 if (isDirectory) {
227 auto handle = opendir(resolvedPath);
228 if (handle) {
229 closedir(handle);
230 absolutePath = resolvedPath;
231 }
232 } else {
233 auto handle = fopen(resolvedPath, "r");
234 if (handle) {
235 fclose(handle);
236 absolutePath = resolvedPath;
237 }
238 }
239 }
240 #elif defined(__linux__)
241 char resolvedPath[PATH_MAX];
242 if (realpath(string(path).c_str(), resolvedPath) != nullptr) {
243 absolutePath = resolvedPath;
244 }
245 #endif
246
247 FormatPath(absolutePath, isDirectory);
248
249 return absolutePath;
250 }
251
FormatPath(string & path,bool isDirectory)252 void StdDirectory::FormatPath(string& path, bool isDirectory)
253 {
254 const size_t length = path.length();
255
256 // Make directory separators consistent.
257 std::replace(path.begin(), path.end(), '\\', '/');
258
259 // Ensure there is last separator in place.
260 if (path.length() > 0 && isDirectory) {
261 if (path[length - 1] != '/') {
262 path += '/';
263 }
264 }
265 }
266
GetDirName(const string_view fullPath)267 string StdDirectory::GetDirName(const string_view fullPath)
268 {
269 auto path = string(fullPath);
270 StdDirectory::FormatPath(path, false);
271
272 const size_t separatorPos = path.find_last_of('/');
273 if (separatorPos == string::npos) {
274 return ".";
275 } else {
276 path.resize(separatorPos);
277 return path;
278 }
279 }
280
GetBaseName(const string_view fullPath)281 string StdDirectory::GetBaseName(const string_view fullPath)
282 {
283 auto path = string(fullPath);
284 StdDirectory::FormatPath(path, false);
285
286 const size_t separatorPos = path.find_last_of('/');
287 if (separatorPos == string::npos) {
288 return path;
289 } else {
290 path.erase(0, separatorPos + 1);
291 return path;
292 }
293 }
294 CORE_END_NAMESPACE()
295