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_file.h"
17 
18 #include <cstdint>
19 
20 #ifdef __has_include
21 #if __has_include(<filesystem>)
22 #include <filesystem>
23 #endif
24 #endif
25 
26 #ifndef HAS_FILESYSTEM
27 #include <cerrno>
28 #include <dirent.h>
29 
30 #ifndef _DIRENT_HAVE_D_TYPE
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 
34 #endif
35 #include <climits>
36 #define CORE_MAX_PATH PATH_MAX
37 #endif
38 
39 #include <base/containers/string.h>
40 #include <base/containers/string_view.h>
41 #include <core/io/intf_file.h>
42 #include <core/log.h>
43 #include <core/namespace.h>
44 
45 #include "io/std_directory.h"
46 
47 CORE_BEGIN_NAMESPACE()
48 using BASE_NS::string;
49 using BASE_NS::string_view;
50 
51 namespace {
OpenFileAccessMode(IFile::Mode mode)52 std::ios_base::openmode OpenFileAccessMode(IFile::Mode mode)
53 {
54     switch (mode) {
55         case IFile::Mode::INVALID:
56             CORE_ASSERT_MSG(false, "Invalid file access mode.");
57             return {};
58         case IFile::Mode::READ_ONLY:
59             return std::ios_base::binary | std::ios_base::in;
60         case IFile::Mode::READ_WRITE:
61             return std::ios_base::binary | std::ios_base::out | std::ios_base::in;
62         default:
63             return {};
64     }
65 }
66 
CreateFileAccessMode(IFile::Mode mode)67 std::ios_base::openmode CreateFileAccessMode(IFile::Mode mode)
68 {
69     switch (mode) {
70         case IFile::Mode::INVALID:
71         case IFile::Mode::READ_ONLY:
72             CORE_ASSERT_MSG(false, "Invalid create file access mode.");
73             return {};
74         case IFile::Mode::READ_WRITE:
75             return std::ios_base::binary | std::ios_base::out | std::ios_base::in | std::ios_base::trunc;
76         default:
77             return {};
78     }
79 }
80 
81 #if defined(HAS_FILESYSTEM)
U8Path(string_view str)82 std::filesystem::path U8Path(string_view str)
83 {
84     return std::filesystem::u8path(str.begin().ptr(), str.end().ptr());
85 }
86 #endif
87 } // namespace
88 
StdFile(Mode mode,std::fstream && stream)89 StdFile::StdFile(Mode mode, std::fstream&& stream) : mode_(mode), file_(BASE_NS::move(stream)) {}
90 
~StdFile()91 StdFile::~StdFile()
92 {
93     Close();
94 }
95 
GetMode() const96 IFile::Mode StdFile::GetMode() const
97 {
98     return mode_;
99 }
100 
IsValidPath(const string_view)101 bool StdFile::IsValidPath(const string_view /* path */)
102 {
103     // Path's should always be valid here.
104     return true;
105 }
106 
Open(const string_view path,Mode mode)107 IFile::Ptr StdFile::Open(const string_view path, Mode mode)
108 {
109 #if defined(HAS_FILESYSTEM)
110     std::error_code ec;
111     auto canonicalPath = std::filesystem::canonical(U8Path(path), ec);
112     if (ec) {
113         return {};
114     }
115 
116     if (std::filesystem::is_directory(canonicalPath)) {
117         return {};
118     }
119 
120     if (auto stream = std::fstream(canonicalPath, OpenFileAccessMode(mode)); stream) {
121         return IFile::Ptr { BASE_NS::make_unique<StdFile>(mode, BASE_NS::move(stream)).release() };
122     }
123 #else
124     char canonicalPath[CORE_MAX_PATH] = { 0 };
125     if (realpath(string(path).c_str(), canonicalPath) == nullptr) {
126         return {};
127     }
128 
129     if (auto stream = std::fstream(canonicalPath, OpenFileAccessMode(mode)); stream) {
130         return IFile::Ptr { BASE_NS::make_unique<StdFile>(mode, BASE_NS::move(stream)).release() };
131     }
132 
133 #endif
134     return {};
135 }
136 
Create(const string_view path,Mode mode)137 IFile::Ptr StdFile::Create(const string_view path, Mode mode)
138 {
139     if (path.empty()) {
140         return {};
141     }
142 
143 #if defined(HAS_FILESYSTEM)
144     std::error_code ec;
145     auto canonicalPath = std::filesystem::weakly_canonical(U8Path(path), ec);
146     if (ec) {
147         return {};
148     }
149     // Create the file.
150     if (auto stream = std::fstream(canonicalPath, CreateFileAccessMode(mode)); stream) {
151         return IFile::Ptr { BASE_NS::make_unique<StdFile>(mode, BASE_NS::move(stream)).release() };
152     }
153 #else
154     // NOTE: As realpath requires that the path exists, we check that the
155     // parent dir is valid instead of the full path of the file being created.
156     const string parentDir = StdDirectory::GetDirName(path);
157     char canonicalParentPath[CORE_MAX_PATH] = { 0 };
158     if (realpath(parentDir.c_str(), canonicalParentPath) == nullptr) {
159         return {};
160     }
161     const string filename = StdDirectory::GetBaseName(path);
162     const string fullPath = string_view(canonicalParentPath) + '/' + filename;
163 
164     // Create the file.
165     if (auto stream = std::fstream(fullPath.data(), CreateFileAccessMode(mode)); stream) {
166         return IFile::Ptr { BASE_NS::make_unique<StdFile>(mode, BASE_NS::move(stream)).release() };
167     }
168 #endif
169     return {};
170 }
171 
Close()172 void StdFile::Close()
173 {
174     if (file_.is_open()) {
175         file_ = {};
176         mode_ = Mode::INVALID;
177     }
178 }
179 
Read(void * buffer,uint64_t count)180 uint64_t StdFile::Read(void* buffer, uint64_t count)
181 {
182     file_.read(static_cast<char*>(buffer), static_cast<std::streamsize>(count));
183     if (file_.eof() && file_.fail()) {
184         file_.clear();
185     }
186     return static_cast<uint64_t>(file_.gcount());
187 }
188 
Write(const void * buffer,uint64_t count)189 uint64_t StdFile::Write(const void* buffer, uint64_t count)
190 {
191     const auto pos = file_.tellp();
192     file_.write(static_cast<const char*>(buffer), static_cast<std::streamsize>(count));
193     return static_cast<uint64_t>(file_.tellp() - pos);
194 }
195 
GetLength() const196 uint64_t StdFile::GetLength() const
197 {
198     const auto offset = file_.tellg();
199     if (offset == decltype(file_)::pos_type(-1)) {
200         return {};
201     }
202 
203     const auto end = file_.seekg(0, std::ios_base::end).tellg();
204     if (!file_.good()) {
205         return {};
206     }
207     const auto beg = file_.seekg(0, std::ios_base::beg).tellg();
208     file_.seekg(offset);
209     return static_cast<uint64_t>(end - beg);
210 }
211 
Seek(uint64_t offset)212 bool StdFile::Seek(uint64_t offset)
213 {
214     file_.seekg(static_cast<decltype(file_)::off_type>(offset), std::ios_base::beg);
215     return file_.good();
216 }
217 
GetPosition() const218 uint64_t StdFile::GetPosition() const
219 {
220     return static_cast<uint64_t>(file_.tellg());
221 }
222 CORE_END_NAMESPACE()
223