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