1 /*
2 * Copyright (c) 2023-2025 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 #include "recent_n_exporter.h"
16
17 #include <sys/stat.h>
18 #include <sys/time.h>
19 #include <tuple>
20 #include <unistd.h>
21
22 #include "access_token.h"
23 #include "accesstoken_kit.h"
24 #include "file_uri.h"
25 #include "file_utils.h"
26 #include "hilog_wrapper.h"
27 #include "ipc_skeleton.h"
28 #include "tokenid_kit.h"
29 #include "user_access_common_utils.h"
30
31 namespace OHOS {
32 namespace FileManagement {
33 namespace Recent {
34 using namespace std;
35 using namespace LibN;
36 using namespace AppFileService::ModuleFileUri;
37
38 using namespace FileAccessFwk;
39 static std::mutex g_recentPathMutex;
CheckPermission(const std::string & permission)40 static bool CheckPermission(const std::string &permission)
41 {
42 Security::AccessToken::AccessTokenID tokenCaller = IPCSkeleton::GetCallingTokenID();
43 return Security::AccessToken::AccessTokenKit::VerifyAccessToken(tokenCaller, permission) ==
44 Security::AccessToken::PermissionState::PERMISSION_GRANTED;
45 }
46
IsSystemApp()47 static bool IsSystemApp()
48 {
49 uint64_t accessTokenIDEx = OHOS::IPCSkeleton::GetCallingFullTokenID();
50 return OHOS::Security::AccessToken::TokenIdKit::IsSystemAppByFullTokenID(accessTokenIDEx);
51 }
52
CheckSystemAppAndPermission(const std::string & permission,napi_env env)53 static bool CheckSystemAppAndPermission(const std::string &permission, napi_env env)
54 {
55 if (!IsSystemApp()) {
56 HILOG_ERROR("FileTrashNExporter::Recover check IsSystemAppByFullTokenID failed");
57 NError(E_PERMISSION_SYS).ThrowErr(env);
58 return false;
59 }
60 if (!CheckPermission(FILE_ACCESS_PERMISSION)) {
61 HILOG_ERROR("Check permission error");
62 NError(E_PERMISSION).ThrowErr(env);
63 return false;
64 }
65 return true;
66 }
67
SetFileTime(napi_env env,const string & recentFilePath)68 static napi_value SetFileTime(napi_env env, const string &recentFilePath)
69 {
70 if (lutimes(recentFilePath.c_str(), nullptr) < 0) {
71 HILOG_ERROR("Failed to lutimes recent link, errno=%{public}d", errno);
72 NError(errno).ThrowErr(env);
73 return nullptr;
74 }
75 struct stat statBuf;
76 if (lstat(recentFilePath.c_str(), &statBuf) < 0) {
77 HILOG_ERROR("Failed to stat uri, errno=%{public}d", errno);
78 }
79 return NVal::CreateUndefined(env).val_;
80 }
81
AddRecentFile(napi_env env,napi_callback_info cbinfo)82 napi_value RecentNExporter::AddRecentFile(napi_env env, napi_callback_info cbinfo)
83 {
84 if (!CheckSystemAppAndPermission(FILE_ACCESS_PERMISSION, env)) {
85 HILOG_ERROR("AddRecentFile CheckSystemAppAndPermission error");
86 return nullptr;
87 }
88
89 NFuncArg funcArg(env, cbinfo);
90 if (!funcArg.InitArgs(NARG_CNT::ONE)) {
91 HILOG_ERROR("Number of arguments unmatched");
92 NError(EINVAL).ThrowErr(env);
93 return nullptr;
94 }
95 auto [succ, uri, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
96 FileUri fileUri(string(uri.get()));
97 auto filePath = fileUri.GetRealPath();
98 struct stat statBuf;
99 if (stat(filePath.c_str(), &statBuf) < 0) {
100 HILOG_ERROR("Failed to stat uri, errno=%{public}d", errno);
101 NError(errno).ThrowErr(env);
102 return nullptr;
103 }
104 string recentFilePath = RecentNExporter::recentPath_ + to_string(statBuf.st_dev) + "_" + to_string(statBuf.st_ino);
105 auto lstatRet = lstat(recentFilePath.c_str(), &statBuf);
106 if (lstatRet == 0) {
107 auto accessRet = access(recentFilePath.c_str(), F_OK);
108 if (accessRet < 0 && errno == ENOENT) {
109 if (unlink(recentFilePath.c_str()) < 0) {
110 HILOG_ERROR("Failed to unlink invalid recent link, errno=%{public}d", errno);
111 NError(errno).ThrowErr(env);
112 return nullptr;
113 }
114 } else if (accessRet == 0) {
115 return SetFileTime(env, recentFilePath);
116 }
117 } else if (lstatRet < 0 && errno != ENOENT) {
118 HILOG_ERROR("Failed to lstat uri, errno=%{public}d", errno);
119 NError(errno).ThrowErr(env);
120 return nullptr;
121 }
122 if (symlink(filePath.c_str(), recentFilePath.c_str()) < 0) {
123 HILOG_ERROR("Failed to symlink uri, errno=%{public}d", errno);
124 NError(errno).ThrowErr(env);
125 return nullptr;
126 }
127 return NVal::CreateUndefined(env).val_;
128 }
129
RemoveRecentFile(napi_env env,napi_callback_info cbinfo)130 napi_value RecentNExporter::RemoveRecentFile(napi_env env, napi_callback_info cbinfo)
131 {
132 if (!CheckSystemAppAndPermission(FILE_ACCESS_PERMISSION, env)) {
133 HILOG_ERROR("AddRecentFile CheckSystemAppAndPermission error");
134 return nullptr;
135 }
136
137 NFuncArg funcArg(env, cbinfo);
138 if (!funcArg.InitArgs(NARG_CNT::ONE)) {
139 HILOG_ERROR("Number of arguments unmatched");
140 NError(EINVAL).ThrowErr(env);
141 return nullptr;
142 }
143 auto [succ, uri, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
144 FileUri fileUri(string(uri.get()));
145 auto filePath = fileUri.GetPath();
146 struct stat statBuf;
147 if (stat(filePath.c_str(), &statBuf) < 0) {
148 HILOG_ERROR("Failed to stat uri, errno=%{public}d", errno);
149 NError(errno).ThrowErr(env);
150 return nullptr;
151 }
152 string recentFilePath = RecentNExporter::recentPath_ + to_string(statBuf.st_dev) + "_" + to_string(statBuf.st_ino);
153 if (unlink(recentFilePath.c_str()) < 0) {
154 HILOG_ERROR("Failed to unlink uri, errno=%{public}d", errno);
155 NError(errno).ThrowErr(env);
156 }
157 return nullptr;
158 }
159
Deleter(struct NameListArg * arg)160 static void Deleter(struct NameListArg *arg)
161 {
162 if (arg == nullptr) {
163 HILOG_ERROR("invalid argument arg is nullptr");
164 return;
165 }
166 if (arg->namelist == nullptr) {
167 HILOG_ERROR("arg->namelist is nullptr");
168 return;
169 }
170 for (int32_t i = 0; i < arg->direntNum; i++) {
171 free((arg->namelist)[i]);
172 (arg->namelist)[i] = nullptr;
173 }
174 free(arg->namelist);
175 arg->namelist = nullptr;
176 delete arg;
177 arg = nullptr;
178 }
179
GetFileMtime(const string & fileName)180 static int64_t GetFileMtime(const string &fileName)
181 {
182 string filePath = RecentNExporter::recentPath_ + fileName;
183 struct stat statBuf;
184 if (lstat(filePath.c_str(), &statBuf) < 0) {
185 HILOG_ERROR("Failed to lstat uri, errno=%{public}d, fileName=%{private}s", errno, fileName.c_str());
186 return errno;
187 }
188 return static_cast<int64_t>(statBuf.st_mtime);
189 }
190
GetName(const string & path)191 static string GetName(const string &path)
192 {
193 auto pos = path.find_last_of('/');
194 if (pos == string::npos) {
195 HILOGE("Failed to split filename from path.");
196 }
197 return path.substr(pos + 1);
198 }
199
SortReceneFile(const struct dirent ** a,const struct dirent ** b)200 static int SortReceneFile(const struct dirent **a, const struct dirent **b)
201 {
202 return GetFileMtime(string((*b)->d_name)) - GetFileMtime(string((*a)->d_name));
203 }
204
FilterFunc(const struct dirent * filename)205 static int FilterFunc(const struct dirent *filename)
206 {
207 if (filename == nullptr) {
208 HILOGE("filename is bullptr");
209 return false;
210 }
211 if (string_view(filename->d_name) == "." || string_view(filename->d_name) == "..") {
212 return false;
213 }
214 return true;
215 }
216
CheckRealFileExist(const string & recentFilePath)217 static tuple<int, struct stat> CheckRealFileExist(const string &recentFilePath)
218 {
219 struct stat statBuf;
220 int accessRet = access(recentFilePath.c_str(), F_OK);
221 if (accessRet < 0 && errno != ENOENT) {
222 HILOG_ERROR("Failed to access file, errno=%{public}d", errno);
223 return { errno, statBuf };
224 }
225 if (accessRet == 0) {
226 if (stat(recentFilePath.c_str(), &statBuf) < 0) {
227 HILOG_ERROR("Failed to stat file, errno=%{public}d", errno);
228 return { errno, statBuf };
229 }
230 string oldRecentFilePath =
231 RecentNExporter::recentPath_ + to_string(statBuf.st_dev) + "_" + to_string(statBuf.st_ino);
232 if (oldRecentFilePath == recentFilePath) {
233 return { 0, statBuf };
234 }
235 }
236 if (unlink(recentFilePath.c_str()) < 0) {
237 HILOG_ERROR("Failed to unlink non-existent file, errno=%{public}d", errno);
238 return { errno, statBuf };
239 }
240 return { -1, statBuf };
241 }
242
GetFileInfo(napi_env env,const string & filePath,const struct stat & realFileStatBuf,const struct stat & linkFileStatBuf)243 static napi_value GetFileInfo(napi_env env, const string &filePath, const struct stat &realFileStatBuf,
244 const struct stat &linkFileStatBuf)
245 {
246 FileUri fileUri(filePath);
247 NVal obj = NVal::CreateObject(env);
248 obj.AddProp({
249 NVal::DeclareNapiProperty("uri", NVal::CreateUTF8String(env, fileUri.ToString()).val_),
250 NVal::DeclareNapiProperty("srcPath", NVal::CreateUTF8String(env, filePath).val_),
251 NVal::DeclareNapiProperty("fileName", NVal::CreateUTF8String(env, GetName(filePath)).val_),
252 NVal::DeclareNapiProperty("size", NVal::CreateInt64(env, realFileStatBuf.st_size).val_),
253 NVal::DeclareNapiProperty("mtime", NVal::CreateInt64(env, realFileStatBuf.st_mtim.tv_sec).val_),
254 NVal::DeclareNapiProperty("ctime", NVal::CreateInt64(env, linkFileStatBuf.st_mtim.tv_sec).val_),
255 NVal::DeclareNapiProperty("mode", NVal::CreateInt64(env, realFileStatBuf.st_mode).val_),
256 });
257 return obj.val_;
258 }
259
GetListFileResult(napi_env env,struct NameListArg * pNameList)260 static napi_value GetListFileResult(napi_env env, struct NameListArg* pNameList)
261 {
262 napi_value res = nullptr;
263 if (napi_create_array(env, &res) != napi_ok) {
264 HILOG_ERROR("Failed to create array");
265 NError(UNKROWN_ERR).ThrowErr(env);
266 return nullptr;
267 }
268 auto buf = CreateUniquePtr<char[]>(BUF_SIZE);
269 int index = 0;
270 for (int32_t i = 0; i < pNameList->direntNum; ++i) {
271 string recentFilePath = RecentNExporter::recentPath_ + string((*(pNameList->namelist[i])).d_name);
272 if (index < MAX_RECENT_SIZE) {
273 auto [checkRealFileRes, realFileStatBuf] = CheckRealFileExist(recentFilePath);
274 if (checkRealFileRes < 0) {
275 continue;
276 } else if (checkRealFileRes > 0) {
277 NError(checkRealFileRes).ThrowErr(env);
278 return nullptr;
279 }
280 auto readLinkRes = readlink(recentFilePath.c_str(), buf.get(), BUF_SIZE);
281 if (readLinkRes < 0) {
282 HILOG_ERROR("Failed to readlink uri, errno=%{public}d", errno);
283 NError(errno).ThrowErr(env);
284 return nullptr;
285 }
286 struct stat linkFileStatBuf;
287 if (lstat(recentFilePath.c_str(), &linkFileStatBuf) < 0) {
288 HILOG_ERROR("Failed to lstat file, errno=%{public}d", errno);
289 NError(errno).ThrowErr(env);
290 return nullptr;
291 }
292 if (napi_set_element(env, res, index, GetFileInfo(env, string(buf.get(), readLinkRes),
293 realFileStatBuf, linkFileStatBuf)) != napi_ok) {
294 HILOG_ERROR("Failed to set element");
295 NError(UNKROWN_ERR).ThrowErr(env);
296 return nullptr;
297 }
298 ++index;
299 } else {
300 if (unlink(recentFilePath.c_str()) < 0) {
301 HILOG_ERROR("Failed to unlink file, errno=%{public}d", errno);
302 NError(errno).ThrowErr(env);
303 return nullptr;
304 }
305 }
306 }
307 HILOG_INFO("The count of recent file is %{public}d", index);
308 return res;
309 }
310
ListFileCore(napi_env env)311 static napi_value ListFileCore(napi_env env)
312 {
313 unique_ptr<struct NameListArg, decltype(Deleter)*> pNameList = { new (nothrow) struct NameListArg, Deleter };
314 if (!pNameList) {
315 HILOG_ERROR("Failed to request heap memory.");
316 NError(ENOMEM).ThrowErr(env);
317 return nullptr;
318 }
319 pNameList->direntNum = scandir(RecentNExporter::recentPath_.c_str(),
320 &(pNameList->namelist), FilterFunc, SortReceneFile);
321 if (pNameList->direntNum < 0) {
322 HILOG_ERROR("Failed to scan dir, errno=%{public}d", errno);
323 NError(errno).ThrowErr(env);
324 return nullptr;
325 }
326 return GetListFileResult(env, pNameList.get());
327 }
328
ListRecentFile(napi_env env,napi_callback_info cbinfo)329 napi_value RecentNExporter::ListRecentFile(napi_env env, napi_callback_info cbinfo)
330 {
331 if (!CheckSystemAppAndPermission(FILE_ACCESS_PERMISSION, env)) {
332 HILOG_ERROR("ListRecentFile CheckSystemAppAndPermission error");
333 return nullptr;
334 }
335
336 NFuncArg funcArg(env, cbinfo);
337 if (!funcArg.InitArgs(NARG_CNT::ZERO)) {
338 HILOG_ERROR("Number of arguments unmatched");
339 NError(EINVAL).ThrowErr(env);
340 return nullptr;
341 }
342 return ListFileCore(env);
343 }
344
Export()345 bool RecentNExporter::Export()
346 {
347 return exports_.AddProp({
348 NVal::DeclareNapiFunction("add", AddRecentFile),
349 NVal::DeclareNapiFunction("remove", RemoveRecentFile),
350 NVal::DeclareNapiFunction("listFile", ListRecentFile),
351 });
352 }
353
GetClassName()354 string RecentNExporter::GetClassName()
355 {
356 return RecentNExporter::className;
357 }
358
InitRecentPath()359 void RecentNExporter::InitRecentPath()
360 {
361 if (RecentNExporter::recentPath_.empty()) {
362 std::unique_lock<std::mutex> lock(g_recentPathMutex);
363 if (!RecentNExporter::recentPath_.empty()) {
364 return ;
365 }
366 RecentNExporter::recentPath_ = "/storage/Users/currentUser/.Recent/";
367 std::string deviceType;
368 if (IsFullMountEnable()) {
369 std::string userName;
370 if (GetUserName(userName) && userName != "") {
371 RecentNExporter::recentPath_ = "/storage/Users/" + userName + "/.Recent/";
372 }
373 }
374 HILOG_INFO("GetRecentDir end.");
375 }
376 }
377
RecentNExporter(napi_env env,napi_value exports)378 RecentNExporter::RecentNExporter(napi_env env, napi_value exports) : NExporter(env, exports)
379 {
380 InitRecentPath();
381 }
382
~RecentNExporter()383 RecentNExporter::~RecentNExporter() {}
384 } // namespace Recent
385 } // namespace FileManagement
386 } // namespace OHOS
387