1 /*
2  * Copyright (c) 2021-2022 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 "dir_n_exporter.h"
17 
18 #include <dirent.h>
19 #include <iostream>
20 #include <memory>
21 #include <mutex>
22 #include <sstream>
23 
24 #include "dir_entity.h"
25 #include "securec.h"
26 #include "../../common/napi/n_async/n_async_work_callback.h"
27 #include "../../common/napi/n_async/n_async_work_promise.h"
28 #include "../../common/napi/n_class.h"
29 #include "../../common/napi/n_func_arg.h"
30 #include "../class_dirent/dirent_entity.h"
31 #include "../class_dirent/dirent_n_exporter.h"
32 #include "../common_func.h"
33 
34 namespace OHOS {
35 namespace DistributedFS {
36 namespace ModuleFileIO {
37 using namespace std;
38 
GetDirEntity(napi_env env,napi_callback_info info)39 static DirEntity *GetDirEntity(napi_env env, napi_callback_info info)
40 {
41     NFuncArg funcArg(env, info);
42     if (!funcArg.InitArgs(NARG_CNT::ZERO)) {
43         UniError(EINVAL).ThrowErr(env, "Number of arguments unmatched");
44         return nullptr;
45     }
46 
47     auto dirEntity = NClass::GetEntityOf<DirEntity>(env, funcArg.GetThisVar());
48     if (!dirEntity) {
49         UniError(EIO).ThrowErr(env, "Cannot get entity of Dir");
50         return nullptr;
51     }
52     return dirEntity;
53 }
54 
CloseSync(napi_env env,napi_callback_info info)55 napi_value DirNExporter::CloseSync(napi_env env, napi_callback_info info)
56 {
57     DirEntity *dirEntity = GetDirEntity(env, info);
58     if (!dirEntity || !dirEntity->dir_) {
59         UniError(EBADF).ThrowErr(env, "Dir has been closed yet");
60         return nullptr;
61     }
62 
63     lock_guard(dirEntity->lock_);
64     dirEntity->dir_.reset();
65     return nullptr;
66 }
67 
Close(napi_env env,napi_callback_info info)68 napi_value DirNExporter::Close(napi_env env, napi_callback_info info)
69 {
70     NFuncArg funcArg(env, info);
71     if (!funcArg.InitArgs(NARG_CNT::ZERO, NARG_CNT::ONE)) {
72         UniError(EINVAL).ThrowErr(env, "Number of arguments unmatched");
73         return nullptr;
74     }
75 
76     auto dirEntity = NClass::GetEntityOf<DirEntity>(env, funcArg.GetThisVar());
77     if (!dirEntity) {
78         UniError(EIO).ThrowErr(env, "Cannot get entity of Dir");
79         return nullptr;
80     }
81 
82     if (dirEntity->dir_ == nullptr) {
83         UniError(EBADF).ThrowErr(env, "Dir has been closed yet");
84         return nullptr;
85     }
86 
87     auto cbExec = [dirEntity](napi_env env) -> UniError {
88         lock_guard(dirEntity->lock_);
89         DIR *dir = dirEntity->dir_.release();
90         int ret = closedir(dir);
91         if (ret == -1) {
92             return UniError(errno);
93         } else {
94             return UniError(ERRNO_NOERR);
95         }
96     };
97     auto cbCompl = [](napi_env env, UniError err) -> NVal {
98         if (err) {
99             return { env, err.GetNapiErr(env) };
100         } else {
101             return NVal::CreateUndefined(env);
102         }
103     };
104 
105     NVal thisVar(env, funcArg.GetThisVar());
106     static const string PROCEDURE_NAME = "fileioDirClose";
107     if (funcArg.GetArgc() == NARG_CNT::ZERO) {
108         return NAsyncWorkPromise(env, thisVar).Schedule(PROCEDURE_NAME, cbExec, cbCompl).val_;
109     } else {
110         NVal cb(env, funcArg[NARG_POS::FIRST]);
111         return NAsyncWorkCallback(env, thisVar, cb).Schedule(PROCEDURE_NAME, cbExec, cbCompl).val_;
112     }
113 }
114 
115 struct DirReadArgs {
116     NRef thisptrRef_;
117     struct dirent dirRes = {
118         .d_ino = 0,
119         .d_off = 0,
120         .d_reclen = 0,
121         .d_type = 0,
122         .d_name = { '\0' },
123     };
DirReadArgsOHOS::DistributedFS::ModuleFileIO::DirReadArgs124     explicit DirReadArgs(NVal obj) : thisptrRef_(obj) {}
125 };
126 
DoReadComplete(napi_env env,UniError err,shared_ptr<DirReadArgs> arg)127 static NVal DoReadComplete(napi_env env, UniError err, shared_ptr<DirReadArgs> arg)
128 {
129     if (err) {
130         return { env, err.GetNapiErr(env) };
131     } else {
132         napi_value objDirent = NClass::InstantiateClass(env, DirentNExporter::className_, {});
133         if (!objDirent) {
134             return { env, UniError(EINVAL).GetNapiErr(env) };
135         }
136         auto direntEntity = NClass::GetEntityOf<DirentEntity>(env, objDirent);
137         if (!direntEntity) {
138             return { env, UniError(EINVAL).GetNapiErr(env) };
139         }
140 
141         if (strlen(arg->dirRes.d_name) == 0) {
142             return { env, NVal::CreateUndefined(env).val_ };
143         } else {
144             direntEntity->dirent_ = arg->dirRes;
145             return { env, objDirent };
146         }
147     }
148 }
149 
Read(napi_env env,napi_callback_info info)150 napi_value DirNExporter::Read(napi_env env, napi_callback_info info)
151 {
152     NFuncArg funcArg(env, info);
153     if (!funcArg.InitArgs(NARG_CNT::ZERO, NARG_CNT::ONE)) {
154         UniError(EINVAL).ThrowErr(env, "Number of arguments unmatched");
155         return nullptr;
156     }
157 
158     auto dirEntity = NClass::GetEntityOf<DirEntity>(env, funcArg.GetThisVar());
159     if (!dirEntity) {
160         UniError(EIO).ThrowErr(env, "Cannot get entity of Dir");
161         return nullptr;
162     }
163 
164     if (!dirEntity || !dirEntity->dir_) {
165         UniError(EBADF).ThrowErr(env, "Dir has been closed yet");
166         return nullptr;
167     }
168 
169     DIR *dir = dirEntity->dir_.get();
170     auto arg = make_shared<DirReadArgs>(NVal(env, funcArg.GetThisVar()));
171     auto cbExec = [arg, dir, dirEntity](napi_env env) -> UniError {
172         struct dirent tmpDirent;
173         lock_guard(dirEntity->lock_);
174         errno = 0;
175         dirent *res = nullptr;
176         do {
177             res = readdir(dir);
178             if (res == nullptr && errno) {
179                 return UniError(errno);
180             } else if (res == nullptr) {
181                 return UniError(ERRNO_NOERR);
182             } else if (string(res->d_name) == "." || string(res->d_name) == "..") {
183                 continue;
184             } else {
185                 if (EOK != memcpy_s(&tmpDirent, sizeof(dirent), res, res->d_reclen)) {
186                     return UniError(errno);
187                 }
188                 break;
189             }
190         } while (true);
191 
192         arg->dirRes = tmpDirent;
193         return UniError(ERRNO_NOERR);
194     };
195     auto cbCompl = [arg](napi_env env, UniError err) -> NVal {
196         return DoReadComplete(env, err, arg);
197     };
198     NVal thisVar(env, funcArg.GetThisVar());
199     const string procedureName = "fileioDirRead";
200     if (funcArg.GetArgc() == NARG_CNT::ZERO) {
201         return NAsyncWorkPromise(env, thisVar).Schedule(procedureName, cbExec, cbCompl).val_;
202     } else {
203         NVal cb(env, funcArg[NARG_POS::FIRST]);
204         return NAsyncWorkCallback(env, thisVar, cb).Schedule(procedureName, cbExec, cbCompl).val_;
205     }
206 }
207 
ReadSync(napi_env env,napi_callback_info info)208 napi_value DirNExporter::ReadSync(napi_env env, napi_callback_info info)
209 {
210     NFuncArg funcArg(env, info);
211     if (!funcArg.InitArgs(NARG_CNT::ZERO)) {
212         UniError(EINVAL).ThrowErr(env, "Number of arguments unmatched");
213         return nullptr;
214     }
215 
216     DirEntity *dirEntity = GetDirEntity(env, info);
217     if (!dirEntity || !dirEntity->dir_) {
218         UniError(EBADF).ThrowErr(env, "Dir has been closed yet");
219         return nullptr;
220     }
221     DIR *dir = dirEntity->dir_.get();
222 
223     struct dirent tmpDirent;
224     {
225         lock_guard(dirEntity->lock_);
226         errno = 0;
227         dirent *res = nullptr;
228         do {
229             res = readdir(dir);
230             if (res == nullptr && errno) {
231                 UniError(errno).ThrowErr(env);
232                 return nullptr;
233             } else if (res == nullptr) {
234                 return NVal::CreateUndefined(env).val_;
235             } else if (string(res->d_name) == "." || string(res->d_name) == "..") {
236                 continue;
237             } else {
238                 if (EOK != memcpy_s(&tmpDirent, sizeof(dirent), res, res->d_reclen)) {
239                     UniError(errno).ThrowErr(env);
240                     return nullptr;
241                 }
242                 break;
243             }
244         } while (true);
245     }
246 
247     napi_value objDirent = NClass::InstantiateClass(env, DirentNExporter::className_, {});
248     if (!objDirent) {
249         return nullptr;
250     }
251 
252     auto direntEntity = NClass::GetEntityOf<DirentEntity>(env, objDirent);
253     if (!direntEntity) {
254         return nullptr;
255     }
256     direntEntity->dirent_ = tmpDirent;
257 
258     return objDirent;
259 }
260 
261 struct DirListFileArgs {
262     vector<dirent> dirents;
DirListFileArgsOHOS::DistributedFS::ModuleFileIO::DirListFileArgs263     explicit DirListFileArgs()
264     {
265         dirents = vector<dirent>();
266     }
267     ~DirListFileArgs() = default;
268 };
269 
CheckDirEntity(napi_env env,napi_value dir_entity)270 static DirEntity *CheckDirEntity(napi_env env, napi_value dir_entity)
271 {
272     auto dirEntity = NClass::GetEntityOf<DirEntity>(env, dir_entity);
273     if (!dirEntity) {
274         UniError(EIO).ThrowErr(env, "Cannot get entity of Dir");
275         return nullptr;
276     }
277 
278     if (!dirEntity || !dirEntity->dir_) {
279         UniError(EBADF).ThrowErr(env, "Dir has been closed yet");
280         return nullptr;
281     }
282     return dirEntity;
283 }
284 
ParseJsListNum(napi_env env,napi_value listNumFromJs)285 static tuple<bool, int> ParseJsListNum(napi_env env, napi_value listNumFromJs)
286 {
287     auto [succ, listNum] = NVal(env, listNumFromJs).ToInt32();
288     return {succ, listNum};
289 }
290 
DoListFileVector2NV(napi_env env,vector<dirent> dirents)291 static napi_value DoListFileVector2NV(napi_env env, vector<dirent> dirents)
292 {
293     napi_value res = nullptr;
294     napi_create_array(env, &res);
295     for (size_t i = 0; i < dirents.size(); i++) {
296         napi_value objDirent = NClass::InstantiateClass(env, DirentNExporter::className_, {});
297         if (!objDirent) {
298             UniError(EINVAL).ThrowErr(env);
299             return nullptr;
300         }
301         auto direntEntity = NClass::GetEntityOf<DirentEntity>(env, objDirent);
302         if (!direntEntity) {
303             UniError(EINVAL).ThrowErr(env);
304             return nullptr;
305         }
306         direntEntity->dirent_ = dirents[i];
307         napi_set_element(env, res, i, objDirent);
308     }
309     return res;
310 }
311 
DoListFileCompile(napi_env env,UniError err,shared_ptr<DirListFileArgs> arg)312 static NVal DoListFileCompile(napi_env env, UniError err, shared_ptr<DirListFileArgs> arg)
313 {
314     if (err) {
315         return { env, err.GetNapiErr(env) };
316     } else {
317         return { env, DoListFileVector2NV(env, arg->dirents) };
318     }
319 }
320 
ListFile(napi_env env,napi_callback_info info)321 napi_value DirNExporter::ListFile(napi_env env, napi_callback_info info)
322 {
323     NFuncArg funcArg(env, info);
324     if (!funcArg.InitArgs(NARG_CNT::ONE, NARG_CNT::TWO)) {
325         UniError(EINVAL).ThrowErr(env, "Number of arguments unmatched");
326         return nullptr;
327     }
328     auto dirEntity = CheckDirEntity(env, funcArg.GetThisVar());
329     if (!dirEntity) {
330         return nullptr;
331     }
332     auto [succ, num] = ParseJsListNum(env, funcArg[NARG_POS::FIRST]);
333     if (!succ) {
334         UniError(EINVAL).ThrowErr(env, "Invalid listNum");
335         return nullptr;
336     }
337 
338     DIR *dir = dirEntity->dir_.get();
339     auto arg = make_shared<DirListFileArgs>();
340     int listNum = num;
341     auto cbExec = [arg, dir, dirEntity, listNum](napi_env env) -> UniError {
342         lock_guard(dirEntity->lock_);
343         dirent *res = nullptr;
344         int listCount = 0;
345         do {
346             errno = 0;
347             res = readdir(dir);
348             if (res == nullptr && errno) {
349                 return UniError(errno);
350             } else if (res == nullptr) {
351                 return UniError(ERRNO_NOERR);
352             } else if (string(res->d_name) == "." || string(res->d_name) == "..") {
353                 continue;
354             } else {
355                 arg->dirents.push_back(*res);
356                 listCount++;
357             }
358         } while (listCount < listNum || listNum == 0);
359         return UniError(ERRNO_NOERR);
360     };
361     auto cbCompl = [arg](napi_env env, UniError err) -> NVal {
362         return DoListFileCompile(env, err, arg);
363     };
364     NVal thisVar(env, funcArg.GetThisVar());
365 
366     if (funcArg.GetArgc() == NARG_CNT::ONE) {
367         return NAsyncWorkPromise(env, thisVar).Schedule(listfileProcedureName, cbExec, cbCompl).val_;
368     } else {
369         NVal cb(env, funcArg[NARG_POS::SECOND]);
370         return NAsyncWorkCallback(env, thisVar, cb).Schedule(listfileProcedureName, cbExec, cbCompl).val_;
371     }
372 }
373 
ListFileSync(napi_env env,napi_callback_info info)374 napi_value DirNExporter::ListFileSync(napi_env env, napi_callback_info info)
375 {
376     NFuncArg funcArg(env, info);
377     if (!funcArg.InitArgs(NARG_CNT::ONE)) {
378         UniError(EINVAL).ThrowErr(env, "Number of arguments unmatched");
379         return nullptr;
380     }
381     auto dirEntity = CheckDirEntity(env, funcArg.GetThisVar());
382     if (!dirEntity) {
383         return nullptr;
384     }
385     auto [succ, listNum] = ParseJsListNum(env, funcArg[NARG_POS::FIRST]);
386     if (!succ) {
387         UniError(EINVAL).ThrowErr(env, "Invalid listNum");
388         return nullptr;
389     }
390 
391     vector<dirent> dirents;
392     {
393         lock_guard(dirEntity->lock_);
394         dirent *res = nullptr;
395         int listCount = 0;
396         auto dir = dirEntity->dir_.get();
397         do {
398             errno = 0;
399             res = readdir(dir);
400             if (res == nullptr && errno) {
401                 UniError(errno).ThrowErr(env);
402                 return nullptr;
403             } else if (res == nullptr) {
404                 break;
405             } else if (string(res->d_name) == "." || string(res->d_name) == "..") {
406                 continue;
407             } else {
408                 dirents.push_back(*res);
409                 listCount++;
410             }
411         } while (listCount < listNum || listNum == 0);
412     }
413     return DoListFileVector2NV(env, dirents);
414 }
415 
Constructor(napi_env env,napi_callback_info info)416 napi_value DirNExporter::Constructor(napi_env env, napi_callback_info info)
417 {
418     NFuncArg funcArg(env, info);
419     if (!funcArg.InitArgs(NARG_CNT::ZERO)) {
420         UniError(EINVAL).ThrowErr(env, "Number of arguments unmatched");
421         return nullptr;
422     }
423 
424     auto dirEntity = make_unique<DirEntity>();
425     if (!NClass::SetEntityFor<DirEntity>(env, funcArg.GetThisVar(), move(dirEntity))) {
426         UniError(EIO).ThrowErr(env, "INNER BUG. Failed to wrap entity for obj dir");
427         return nullptr;
428     }
429     return funcArg.GetThisVar();
430 }
431 
Export()432 bool DirNExporter::Export()
433 {
434     vector<napi_property_descriptor> props = {
435         NVal::DeclareNapiFunction("readSync", ReadSync),
436         NVal::DeclareNapiFunction("closeSync", CloseSync),
437         NVal::DeclareNapiFunction("listfileSync", ListFileSync),
438         NVal::DeclareNapiFunction("read", Read),
439         NVal::DeclareNapiFunction("close", Close),
440         NVal::DeclareNapiFunction("listfile", ListFile),
441     };
442 
443     string className = GetClassName();
444     bool succ = false;
445     napi_value classValue = nullptr;
446     tie(succ, classValue) = NClass::DefineClass(exports_.env_, className, DirNExporter::Constructor, std::move(props));
447     if (!succ) {
448         UniError(EIO).ThrowErr(exports_.env_, "INNER BUG. Failed to define class Dirent");
449         return false;
450     }
451 
452     succ = NClass::SaveClass(exports_.env_, className, classValue);
453     if (!succ) {
454         UniError(EIO).ThrowErr(exports_.env_, "INNER BUG. Failed to save class Dirent");
455         return false;
456     }
457 
458     return exports_.AddProp(className, classValue);
459 }
460 
GetClassName()461 string DirNExporter::GetClassName()
462 {
463     return DirNExporter::className_;
464 }
465 
DirNExporter(napi_env env,napi_value exports)466 DirNExporter::DirNExporter(napi_env env, napi_value exports) : NExporter(env, exports) {}
467 
~DirNExporter()468 DirNExporter::~DirNExporter() {}
469 } // namespace ModuleFileIO
470 } // namespace DistributedFS
471 } // namespace OHOS
472