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