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 "copy_file.h"
17 
18 #include <cstring>
19 #include <fcntl.h>
20 #include <sys/sendfile.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23 #include <tuple>
24 #include <unistd.h>
25 
26 #include "../../common/file_helper/fd_guard.h"
27 #include "../../common/napi/n_async/n_async_work_callback.h"
28 #include "../../common/napi/n_async/n_async_work_promise.h"
29 #include "../../common/napi/n_func_arg.h"
30 
31 namespace OHOS {
32 namespace DistributedFS {
33 namespace ModuleFileIO {
34 using namespace std;
35 
36 namespace {
37 constexpr int COPY_BLOCK_SIZE = 4096;
38 }
39 
40 struct FileInfo {
41     bool isPath = false;
42     unique_ptr<char[]> path = nullptr;
43     FDGuard fdg;
44 };
45 
CopyFileCore(FileInfo & srcFile,FileInfo & destFile)46 static UniError CopyFileCore(FileInfo &srcFile, FileInfo &destFile)
47 {
48     if (srcFile.isPath) {
49         int ret = open(srcFile.path.get(), O_RDONLY);
50         if (ret < 0) {
51             return UniError(errno);
52         }
53         srcFile.fdg.SetFD(ret, true);
54     }
55 
56     struct stat statbf;
57     if (fstat(srcFile.fdg.GetFD(), &statbf) == -1) {
58         return UniError(errno);
59     }
60 
61     if (destFile.isPath) {
62         int ret = open(destFile.path.get(), O_WRONLY | O_CREAT, statbf.st_mode);
63         if (ret < 0) {
64             return UniError(errno);
65         }
66         destFile.fdg.SetFD(ret, true);
67     }
68 
69     auto copyBuf = make_unique<char[]>(COPY_BLOCK_SIZE);
70     do {
71         ssize_t readSize = read(srcFile.fdg.GetFD(), copyBuf.get(), COPY_BLOCK_SIZE);
72         if (readSize == -1) {
73             return UniError(errno);
74         } else if (readSize == 0) {
75             break;
76         }
77         ssize_t writeSize = write(destFile.fdg.GetFD(), copyBuf.get(), readSize);
78         if (writeSize != readSize) {
79             return UniError(errno);
80         }
81         if (readSize != COPY_BLOCK_SIZE) {
82             break;
83         }
84     } while (true);
85 
86     return UniError(ERRNO_NOERR);
87 }
88 
ParseJsModeAndProm(napi_env env,const NFuncArg & funcArg)89 static tuple<bool, int32_t> ParseJsModeAndProm(napi_env env, const NFuncArg &funcArg)
90 {
91     bool succ = false;
92     int32_t mode = 0;
93     if (funcArg.GetArgc() >= NARG_CNT::THREE) {
94         tie(succ, mode) = NVal(env, funcArg[NARG_POS::THIRD]).ToInt32(mode);
95         if (!succ || mode) {
96             return { false, mode };
97         }
98     }
99     return { true, mode };
100 }
101 
ParseJsOperand(napi_env env,NVal pathOrFdFromJsArg)102 static tuple<bool, FileInfo> ParseJsOperand(napi_env env, NVal pathOrFdFromJsArg)
103 {
104     auto [isPath, path, ignore] = pathOrFdFromJsArg.ToUTF8StringPath();
105     if (isPath) {
106         return {true, FileInfo{true, move(path), {}}};
107     }
108 
109     auto [isFd, fd] = pathOrFdFromJsArg.ToInt32();
110     if (isFd && fd > 0) {
111         return {true, FileInfo{false, {}, {fd, false}}};
112     }
113 
114     return {false, FileInfo{false, {}, {}}};
115 };
116 
Sync(napi_env env,napi_callback_info info)117 napi_value CopyFile::Sync(napi_env env, napi_callback_info info)
118 {
119     NFuncArg funcArg(env, info);
120     if (!funcArg.InitArgs(NARG_CNT::TWO, NARG_CNT::THREE)) {
121         UniError(EINVAL).ThrowErr(env, "Number of arguments unmatched");
122         return nullptr;
123     }
124 
125     auto [succSrc, src] = ParseJsOperand(env, {env, funcArg[NARG_POS::FIRST]});
126     auto [succDest, dest] = ParseJsOperand(env, {env, funcArg[NARG_POS::SECOND]});
127     if (!succSrc || !succDest) {
128         UniError(EINVAL).ThrowErr(env, "The first/second argument requires filepath/fd");
129         return nullptr;
130     }
131 
132     auto [succMode, mode] = ParseJsModeAndProm(env, funcArg);
133     if (!succMode) {
134         UniError(EINVAL).ThrowErr(env, "Invalid mode");
135         return nullptr;
136     }
137 
138     auto err = CopyFileCore(src, dest);
139     if (err) {
140         if (err.GetErrno(ERR_CODE_SYSTEM_POSIX) == ENAMETOOLONG) {
141             UniError(EINVAL).ThrowErr(env, "Filename too long");
142             return nullptr;
143         }
144         err.ThrowErr(env);
145         return nullptr;
146     }
147 
148     return NVal::CreateUndefined(env).val_;
149 }
150 
151 class Para {
152 public:
153     FileInfo src_;
154     FileInfo dest_;
155 
Para(FileInfo src,FileInfo dest)156     Para(FileInfo src, FileInfo dest) : src_(move(src)), dest_(move(dest)) {};
157 };
158 
Async(napi_env env,napi_callback_info info)159 napi_value CopyFile::Async(napi_env env, napi_callback_info info)
160 {
161     NFuncArg funcArg(env, info);
162     if (!funcArg.InitArgs(NARG_CNT::TWO, NARG_CNT::FOUR)) {
163         UniError(EINVAL).ThrowErr(env, "Number of arguments unmatched");
164         return nullptr;
165     }
166 
167     auto [succSrc, src] = ParseJsOperand(env, {env, funcArg[NARG_POS::FIRST]});
168     auto [succDest, dest] = ParseJsOperand(env, {env, funcArg[NARG_POS::SECOND]});
169     if (!succSrc || !succDest) {
170         UniError(EINVAL).ThrowErr(env, "The first/second argument requires filepath/fd");
171         return nullptr;
172     }
173 
174     auto [succMode, mode] = ParseJsModeAndProm(env, funcArg);
175     if (!succMode) {
176         UniError(EINVAL).ThrowErr(env, "Invalid mode");
177         return nullptr;
178     }
179 
180     auto cbExec = [para = make_shared<Para>(move(src), move(dest))](napi_env env) -> UniError {
181         return CopyFileCore(para->src_, para->dest_);
182     };
183 
184     auto cbCompl = [](napi_env env, UniError err) -> NVal {
185         if (err) {
186             if (err.GetErrno(ERR_CODE_SYSTEM_POSIX) == ENAMETOOLONG) {
187                 return {env, err.GetNapiErr(env, "Filename too long")};
188             }
189             return {env, err.GetNapiErr(env)};
190         }
191         return {NVal::CreateUndefined(env)};
192     };
193 
194     const string procedureName = "FileIOCopyFile";
195     NVal thisVar(env, funcArg.GetThisVar());
196     if (funcArg.GetArgc() == NARG_CNT::TWO || (funcArg.GetArgc() == NARG_CNT::THREE &&
197         !NVal(env, funcArg[NARG_POS::THIRD]).TypeIs(napi_function))) {
198         return NAsyncWorkPromise(env, thisVar).Schedule(procedureName, cbExec, cbCompl).val_;
199     } else {
200         NVal cb(env, funcArg[((funcArg.GetArgc() == NARG_CNT::THREE) ? NARG_POS::THIRD : NARG_POS::FOURTH)]);
201         return NAsyncWorkCallback(env, thisVar, cb).Schedule(procedureName, cbExec, cbCompl).val_;
202     }
203 }
204 } // namespace ModuleFileIO
205 } // namespace DistributedFS
206 } // namespace OHOS
207