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