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