1 /*
2  * Copyright (c) 2024 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 #ifdef __has_include
21 #if __has_include(<filesystem>)
22 #include <filesystem>
23 #include <chrono>
24 #include <system_error>
25 #endif
26 #endif
27 #if !defined(HAS_FILESYSTEM)
28 #include <dirent.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #endif
32 
33 #include <cstddef>
34 #include <cstdint>
35 
36 #include <base/containers/iterator.h>
37 #include <base/containers/string.h>
38 #include <base/containers/string_view.h>
39 #include <base/containers/type_traits.h>
40 #include <base/containers/unique_ptr.h>
41 #include <base/containers/vector.h>
42 #include <base/namespace.h>
43 #include <core/io/intf_directory.h>
44 #include <core/log.h>
45 #include <core/namespace.h>
46 
47 CORE_BEGIN_NAMESPACE()
48 using BASE_NS::make_unique;
49 using BASE_NS::string;
50 using BASE_NS::string_view;
51 using BASE_NS::vector;
52 
53 #if defined(HAS_FILESYSTEM)
54 
55 // Hiding the directory implementation from the header.
56 struct DirImpl {
DirImplDirImpl57     explicit DirImpl(string_view path) : path_(path) {}
58     string path_;
59 };
60 
61 namespace {
GetTimeStamp(const std::filesystem::directory_entry & entry)62 uint64_t GetTimeStamp(const std::filesystem::directory_entry& entry)
63 {
64     return static_cast<uint64_t>(entry.last_write_time().time_since_epoch().count());
65 }
66 
GetEntryType(const std::filesystem::directory_entry & entry)67 IDirectory::Entry::Type GetEntryType(const std::filesystem::directory_entry& entry)
68 {
69     if (entry.is_directory()) {
70         return IDirectory::Entry::DIRECTORY;
71     }
72     if (entry.is_regular_file()) {
73         return IDirectory::Entry::FILE;
74     }
75 
76     return IDirectory::Entry::UNKNOWN;
77 }
78 
U8Path(string_view str)79 std::filesystem::path U8Path(string_view str)
80 {
81     return std::filesystem::u8path(str.begin().ptr(), str.end().ptr());
82 }
83 } // namespace
84 
StdDirectory(BASE_NS::unique_ptr<DirImpl> dir)85 StdDirectory::StdDirectory(BASE_NS::unique_ptr<DirImpl> dir) : dir_(BASE_NS::move(dir)) {}
86 
~StdDirectory()87 StdDirectory::~StdDirectory()
88 {
89     Close();
90 }
91 
Close()92 void StdDirectory::Close()
93 {
94     if (dir_) {
95         dir_.reset();
96     }
97 }
98 
Create(BASE_NS::string_view path)99 IDirectory::Ptr StdDirectory::Create(BASE_NS::string_view path)
100 {
101     std::error_code ec;
102     if (std::filesystem::create_directory(U8Path(path), ec)) {
103         // Directory creation successful.
104         return IDirectory::Ptr { BASE_NS::make_unique<StdDirectory>(make_unique<DirImpl>(path)).release() };
105     }
106     return {};
107 }
108 
Open(const string_view path)109 IDirectory::Ptr StdDirectory::Open(const string_view path)
110 {
111     std::error_code ec;
112     if (std::filesystem::is_directory(U8Path(path), ec)) {
113         return IDirectory::Ptr { BASE_NS::make_unique<StdDirectory>(make_unique<DirImpl>(path)).release() };
114     }
115     const auto message = ec.message();
116     CORE_LOG_E("'%.*s ec %d '%s'", static_cast<int>(path.size()), path.data(), ec.value(), message.data());
117     return {};
118 }
119 
GetEntries() const120 vector<IDirectory::Entry> StdDirectory::GetEntries() const
121 {
122     CORE_ASSERT_MSG(dir_, "Dir not open");
123     vector<IDirectory::Entry> result;
124     if (dir_) {
125         std::error_code ec;
126         for (auto& iter : std::filesystem::directory_iterator(U8Path(dir_->path_), ec)) {
127             const auto filename = iter.path().filename().u8string();
128             auto str = string(filename.c_str(), filename.length());
129             result.push_back(IDirectory::Entry { GetEntryType(iter), move(str), GetTimeStamp(iter) });
130         }
131     }
132 
133     return result;
134 }
135 
136 #else // HAS_FILESYSTEM not defined
137 
138 // Hiding the directory implementation from the header.
139 struct DirImpl {
DirImplDirImpl140     DirImpl(const string_view path, DIR* aDir) : path_(path), dir_(aDir) {}
141     string path_;
142     DIR* dir_;
143 };
144 
145 namespace {
CreateEntry(const string_view path,const dirent & entry)146 IDirectory::Entry CreateEntry(const string_view path, const dirent& entry)
147 {
148     const auto fullPath = path + string(entry.d_name);
149     struct stat statBuf {};
150     const auto statResult = (stat(fullPath.c_str(), &statBuf) == 0);
151 
152     IDirectory::Entry::Type type = IDirectory::Entry::UNKNOWN;
153 #ifdef _DIRENT_HAVE_D_TYPE
154     switch (entry.d_type) {
155         case DT_DIR:
156             type = IDirectory::Entry::DIRECTORY;
157             break;
158         case DT_REG:
159             type = IDirectory::Entry::FILE;
160             break;
161         default:
162             type = IDirectory::Entry::UNKNOWN;
163             break;
164     }
165 #else
166     if (statResult) {
167         if (S_ISDIR(statBuf.st_mode)) {
168             type = IDirectory::Entry::DIRECTORY;
169         } else if (S_ISREG(statBuf.st_mode)) {
170             type = IDirectory::Entry::FILE;
171         }
172     }
173 #endif
174 
175     // Get the timestamp for the directory entry.
176     uint64_t timestamp = 0;
177     if (statResult) {
178         timestamp = static_cast<uint64_t>(statBuf.st_mtime);
179     }
180 
181     return IDirectory::Entry { type, entry.d_name, timestamp };
182 }
183 } // namespace
184 
StdDirectory(BASE_NS::unique_ptr<DirImpl> dir)185 StdDirectory::StdDirectory(BASE_NS::unique_ptr<DirImpl> dir) : dir_(BASE_NS::move(dir)) {}
186 
~StdDirectory()187 StdDirectory::~StdDirectory()
188 {
189     Close();
190 }
191 
Close()192 void StdDirectory::Close()
193 {
194     if (dir_) {
195         closedir(dir_->dir_);
196         dir_.reset();
197     }
198 }
199 
Create(BASE_NS::string_view path)200 IDirectory::Ptr StdDirectory::Create(BASE_NS::string_view path)
201 {
202     int result = mkdir(string(path).c_str(), S_IRWXU | S_IRWXO);
203     if (result == 0) {
204         // Directory creation successful.
205         return Open(path);
206     }
207     return {};
208 }
209 
Open(const string_view path)210 IDirectory::Ptr StdDirectory::Open(const string_view path)
211 {
212     DIR* dir = opendir(string(path).c_str());
213     if (dir) {
214         return IDirectory::Ptr { make_unique<StdDirectory>(make_unique<DirImpl>(path, dir)).release() };
215     }
216     return {};
217 }
218 
GetEntries() const219 vector<IDirectory::Entry> StdDirectory::GetEntries() const
220 {
221     CORE_ASSERT_MSG(dir_, "Dir not open");
222     vector<IDirectory::Entry> result;
223     if (dir_) {
224         // Go back to start.
225         rewinddir(dir_->dir_);
226 
227         // Iterate all entries and write to result.
228         struct dirent* directoryEntry = nullptr;
229         while ((directoryEntry = readdir(dir_->dir_)) != nullptr) {
230             if (!strcmp(directoryEntry->d_name, ".") || !strcmp(directoryEntry->d_name, "..")) {
231                 continue;
232             }
233             result.push_back(CreateEntry(dir_->path_, *directoryEntry));
234         }
235     }
236 
237     return result;
238 }
239 
240 #endif // HAS_FILESYSTEM
241 
ResolveAbsolutePath(const string_view pathIn,bool isDirectory)242 string StdDirectory::ResolveAbsolutePath(const string_view pathIn, bool isDirectory)
243 {
244     string absolutePath;
245     auto path = pathIn;
246 #ifdef _WIN32
247     // remove the '/' slash..
248     path = path.substr(1);
249 #endif
250 
251 #ifdef HAS_FILESYSTEM
252     std::error_code ec;
253     std::filesystem::path fsPath = std::filesystem::canonical(U8Path(path), ec);
254     if (ec.value() == 0) {
255         const auto pathStr = fsPath.string();
256         absolutePath.assign(pathStr.data(), pathStr.size());
257     }
258 #elif defined(_WIN32)
259     char resolvedPath[_MAX_PATH] = {};
260     if (_fullpath(resolvedPath, string(path).c_str(), _MAX_PATH) != nullptr) {
261         if (isDirectory) {
262             auto handle = opendir(resolvedPath);
263             if (handle) {
264                 closedir(handle);
265                 absolutePath = resolvedPath;
266             }
267         } else {
268             auto handle = fopen(resolvedPath, "r");
269             if (handle) {
270                 fclose(handle);
271                 absolutePath = resolvedPath;
272             }
273         }
274     }
275 #elif defined(__linux__)
276     char resolvedPath[PATH_MAX];
277     if (realpath(string(path).c_str(), resolvedPath) != nullptr) {
278         absolutePath = resolvedPath;
279     }
280 #endif
281 
282     FormatPath(absolutePath, isDirectory);
283 
284     return absolutePath;
285 }
286 
FormatPath(string & path,bool isDirectory)287 void StdDirectory::FormatPath(string& path, bool isDirectory)
288 {
289     const size_t length = path.length();
290 
291     // Make directory separators consistent.
292     std::replace(path.begin(), path.end(), '\\', '/');
293 
294     // Ensure there is last separator in place.
295     if (path.length() > 0 && isDirectory) {
296         if (path[length - 1] != '/') {
297             path += '/';
298         }
299     }
300 }
301 
GetDirName(const string_view fullPath)302 string StdDirectory::GetDirName(const string_view fullPath)
303 {
304     auto path = string(fullPath);
305     StdDirectory::FormatPath(path, false);
306 
307     const size_t separatorPos = path.find_last_of('/');
308     if (separatorPos == string::npos) {
309         return ".";
310     }
311     path.resize(separatorPos);
312     return path;
313 }
314 
GetBaseName(const string_view fullPath)315 string StdDirectory::GetBaseName(const string_view fullPath)
316 {
317     auto path = string(fullPath);
318     StdDirectory::FormatPath(path, false);
319 
320     const size_t separatorPos = path.find_last_of('/');
321     if (separatorPos == string::npos) {
322         return path;
323     }
324     path.erase(0, separatorPos + 1);
325     return path;
326 }
327 CORE_END_NAMESPACE()
328