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 #include "file.h"
16 #include "serial_struct.h"
17 
18 namespace Hdc {
HdcFile(HTaskInfo hTaskInfo)19 HdcFile::HdcFile(HTaskInfo hTaskInfo)
20     : HdcTransferBase(hTaskInfo)
21 {
22     commandBegin = CMD_FILE_BEGIN;
23     commandData = CMD_FILE_DATA;
24     isStableBuf = hTaskInfo->isStableBuf;
25 }
26 
~HdcFile()27 HdcFile::~HdcFile()
28 {
29     WRITE_LOG(LOG_DEBUG, "~HdcFile channelId:%u", taskInfo->channelId);
30 };
31 
StopTask()32 void HdcFile::StopTask()
33 {
34     WRITE_LOG(LOG_DEBUG, "StopTask channelId:%u", taskInfo->channelId);
35     singalStop = true;
36 };
37 
BeginTransfer(CtxFile *context, const string &command)38 bool HdcFile::BeginTransfer(CtxFile *context, const string &command)
39 {
40     int argc = 0;
41     bool ret = false;
42     StartTraceScope("HdcFile::BeginTransfer");
43     char **argv = Base::SplitCommandToArgs(command.c_str(), &argc);
44     if (argc < CMD_ARG1_COUNT || argv == nullptr) {
45         LogMsg(MSG_FAIL, "Transfer path split failed");
46         if (argv) {
47             delete[](reinterpret_cast<char *>(argv));
48         }
49         return false;
50     }
51     if (!SetMasterParameters(context, command.c_str(), argc, argv)) {
52         delete[](reinterpret_cast<char *>(argv));
53         return false;
54     }
55     uv_fs_t *openReq = new uv_fs_t;
56     if (openReq == nullptr) {
57         delete[](reinterpret_cast<char *>(argv));
58         LogMsg(MSG_FAIL, "HdcFile::BeginTransfer new openReq failed");
59         return false;
60     }
61     memset_s(openReq, sizeof(uv_fs_t), 0, sizeof(uv_fs_t));
62     openReq->data = context;
63     do {
64         ++refCount;
65         uv_fs_open(loopTask, openReq, context->localPath.c_str(), O_RDONLY, S_IWUSR | S_IRUSR, OnFileOpen);
66         context->master = true;
67         ret = true;
68     } while (false);
69     if (!ret) {
70         LogMsg(MSG_FAIL, "Transfer path failed, Master:%s Slave:%s", context->localPath.c_str(),
71                context->remotePath.c_str());
72     }
73     delete[](reinterpret_cast<char *>(argv));
74     return ret;
75 }
76 
SetMasterParameters(CtxFile *context, const char *command, int argc, char **argv)77 bool HdcFile::SetMasterParameters(CtxFile *context, const char *command, int argc, char **argv)
78 {
79     int srcArgvIndex = 0;
80     string errStr;
81     const string cmdOptionTstmp = "-a";
82     const string cmdOptionSync = "-sync";
83     const string cmdOptionZip = "-z";
84     const string cmdOptionModeSync = "-m";
85 
86     for (int i = 0; i < argc; i++) {
87         if (argv[i] == cmdOptionZip) {
88             context->transferConfig.compressType = COMPRESS_LZ4;
89             ++srcArgvIndex;
90         } else if (argv[i] == cmdOptionSync) {
91             context->transferConfig.updateIfNew = true;
92             ++srcArgvIndex;
93         } else if (argv[i] == cmdOptionTstmp) {
94             // The time zone difference may cause the display time on the PC and the
95             // device to differ by several hours
96             //
97             // ls -al --full-time
98             context->transferConfig.holdTimestamp = true;
99             ++srcArgvIndex;
100         } else if (argv[i] == CMD_OPTION_CLIENTCWD) {
101             context->transferConfig.clientCwd = argv[i + 1];
102             srcArgvIndex += CMD_ARG1_COUNT;  // skip 2args
103         } else if (argv[i] == cmdOptionModeSync) {
104             context->fileModeSync = true;
105             ++srcArgvIndex;
106         } else if (argv[i] == CMDSTR_REMOTE_PARAMETER) {
107             ++srcArgvIndex;
108         } else if (argv[i][0] == '-') {
109             LogMsg(MSG_FAIL, "Unknown file option: %s", argv[i]);
110             return false;
111         }
112     }
113     if (argc == srcArgvIndex) {
114         LogMsg(MSG_FAIL, "There is no local and remote path");
115         return false;
116     }
117     context->remotePath = argv[argc - 1];
118     context->localPath = argv[argc - CMD_FILE_PENULT_PARAM];
119     if (taskInfo->serverOrDaemon) {
120         // master and server
121         if ((srcArgvIndex + 1) == argc) {
122             LogMsg(MSG_FAIL, "There is no remote path");
123             return false;
124         }
125         ExtractRelativePath(context->transferConfig.clientCwd, context->localPath);
126     } else {
127         if ((srcArgvIndex + 1) == argc) {
128             context->remotePath = ".";
129             context->localPath = argv[argc - 1];
130         }
131     }
132 
133     context->localName = Base::GetFullFilePath(context->localPath);
134 
135     mode_t mode = mode_t(~S_IFMT);
136     if (!Base::CheckDirectoryOrPath(context->localPath.c_str(), true, true, errStr, mode) && (mode & S_IFDIR)) {
137         context->isDir = true;
138         GetSubFilesRecursively(context->localPath, context->localName, &context->taskQueue);
139         if (context->taskQueue.size() == 0) {
140             LogMsg(MSG_FAIL, "Operation failed, because the source folder is empty.");
141             return false;
142         }
143         context->fileCnt = 0;
144         context->dirSize = 0;
145         context->localDirName = Base::GetPathWithoutFilename(context->localPath);
146 
147         WRITE_LOG(LOG_DEBUG, "localDirName = %s", context->localDirName.c_str());
148 
149         context->localName = context->taskQueue.back();
150         context->localPath = context->localDirName + context->localName;
151 
152         WRITE_LOG(LOG_DEBUG, "localPath = %s", context->localPath.c_str());
153         context->taskQueue.pop_back();
154     }
155     return true;
156 }
157 
CheckMaster(CtxFile *context)158 void HdcFile::CheckMaster(CtxFile *context)
159 {
160     StartTraceScope("HdcFile::CheckMaster");
161     if (context->fileModeSync) {
162         string s = SerialStruct::SerializeToString(context->fileMode);
163         SendToAnother(CMD_FILE_MODE, reinterpret_cast<uint8_t *>(const_cast<char *>(s.c_str())), s.size());
164     } else {
165         string s = SerialStruct::SerializeToString(context->transferConfig);
166         SendToAnother(CMD_FILE_CHECK, reinterpret_cast<uint8_t *>(const_cast<char *>(s.c_str())), s.size());
167     }
168 }
169 
WhenTransferFinish(CtxFile *context)170 void HdcFile::WhenTransferFinish(CtxFile *context)
171 {
172     WRITE_LOG(LOG_DEBUG, "WhenTransferFinish fileCnt:%d", context->fileCnt);
173     uint8_t flag = 1;
174     context->fileCnt++;
175     context->dirSize += context->indexIO;
176     SendToAnother(CMD_FILE_FINISH, &flag, 1);
177 }
178 
TransferSummary(CtxFile *context)179 void HdcFile::TransferSummary(CtxFile *context)
180 {
181     uint64_t nMSec = Base::GetRuntimeMSec() -
182                      (context->fileCnt > 1 ? context->transferDirBegin : context->transferBegin);
183     uint64_t fSize = context->fileCnt > 1 ? context->dirSize : context->indexIO;
184     double fRate = static_cast<double>(fSize) / nMSec; // / /1000 * 1000 = 0
185     if (context->indexIO >= context->fileSize || context->lastErrno == 0) {
186         LogMsg(MSG_OK, "FileTransfer finish, Size:%lld, File count = %d, time:%lldms rate:%.2lfkB/s",
187                fSize, context->fileCnt, nMSec, fRate);
188     } else {
189         constexpr int bufSize = 1024;
190         char buf[bufSize] = { 0 };
191         uv_strerror_r(static_cast<int>(-context->lastErrno), buf, bufSize);
192         LogMsg(MSG_FAIL, "Transfer Stop at:%lld/%lld(Bytes), Reason: %s", context->indexIO, context->fileSize,
193                buf);
194     }
195 }
196 
FileModeSync(const uint16_t cmd, uint8_t *payload, const int payloadSize)197 bool HdcFile::FileModeSync(const uint16_t cmd, uint8_t *payload, const int payloadSize)
198 {
199     StartTraceScope("HdcFile::FileModeSync");
200     if (ctxNow.master) {
201         WRITE_LOG(LOG_DEBUG, "FileModeSync master ctxNow.fileModeSync = %d size = %zu", ctxNow.fileModeSync,
202                   ctxNow.dirMode.size());
203         if (ctxNow.dirMode.size() > 0) {
204             auto mode = ctxNow.dirMode.back();
205             WRITE_LOG(LOG_DEBUG, "file = %s permissions: %o uId = %u, gId = %u conext = %s",
206                 mode.fullName.c_str(), mode.perm, mode.uId, mode.gId, mode.context.c_str());
207             string s = SerialStruct::SerializeToString(mode);
208             ctxNow.dirMode.pop_back();
209             SendToAnother(CMD_DIR_MODE, reinterpret_cast<uint8_t *>(const_cast<char *>(s.c_str())), s.size());
210         } else {
211             string s = SerialStruct::SerializeToString(ctxNow.transferConfig);
212             SendToAnother(CMD_FILE_CHECK, reinterpret_cast<uint8_t *>(const_cast<char *>(s.c_str())), s.size());
213         }
214     } else {
215         ctxNow.fileModeSync = true;
216         string serialString(reinterpret_cast<char *>(payload), payloadSize);
217         if (cmd == CMD_FILE_MODE) {
218             SerialStruct::ParseFromString(ctxNow.fileMode, serialString);
219         } else {
220             FileMode dirMode;
221             SerialStruct::ParseFromString(dirMode, serialString);
222 
223             WRITE_LOG(LOG_DEBUG, "file = %s permissions: %o uId = %u, gId = %u context = %s",
224                 dirMode.fullName.c_str(), dirMode.perm, dirMode.uId, dirMode.gId, dirMode.context.c_str());
225 
226             vector<string> dirsOfOptName;
227             if (dirMode.fullName.find('/') != string::npos) {
228                 WRITE_LOG(LOG_DEBUG, "dir mode create parent dir from linux system");
229                 Base::SplitString(dirMode.fullName, "/", dirsOfOptName);
230             } else if (dirMode.fullName.find('\\') != string::npos) {
231                 WRITE_LOG(LOG_DEBUG, "dir mode create parent dir from windows system");
232                 Base::SplitString(dirMode.fullName, "\\", dirsOfOptName);
233             } else {
234                 dirsOfOptName.emplace_back(dirMode.fullName);
235             }
236 
237             dirMode.fullName = "";
238             for (auto s : dirsOfOptName) {
239                 if (dirMode.fullName.empty()) {
240                     dirMode.fullName = s;
241                 } else {
242                     dirMode.fullName = dirMode.fullName + Base::GetPathSep() + s;
243                 }
244             }
245             WRITE_LOG(LOG_DEBUG, "dir = %s permissions: %o uId = %u, gId = %u context = %s",
246                 dirMode.fullName.c_str(), dirMode.perm, dirMode.uId, dirMode.gId, dirMode.context.c_str());
247             ctxNow.dirModeMap.insert(std::make_pair(dirMode.fullName, dirMode));
248         }
249         SendToAnother(CMD_FILE_MODE, nullptr, 0);
250     }
251     return true;
252 }
253 
SlaveCheck(uint8_t *payload, const int payloadSize)254 bool HdcFile::SlaveCheck(uint8_t *payload, const int payloadSize)
255 {
256     bool ret = true;
257     bool childRet = false;
258     string errStr;
259     // parse option
260     string serialString(reinterpret_cast<char *>(payload), payloadSize);
261     TransferConfig &stat = ctxNow.transferConfig;
262     SerialStruct::ParseFromString(stat, serialString);
263     ctxNow.fileSize = stat.fileSize;
264     ctxNow.localPath = stat.path;
265     ctxNow.master = false;
266 #ifdef HDC_DEBUG
267     WRITE_LOG(LOG_DEBUG, "HdcFile fileSize got %" PRIu64 "", ctxNow.fileSize);
268 #endif
269 
270     if (!CheckLocalPath(ctxNow.localPath, stat.optionalName, errStr)) {
271         LogMsg(MSG_FAIL, "%s", errStr.c_str());
272         return false;
273     }
274 
275     if (!CheckFilename(ctxNow.localPath, stat.optionalName, errStr)) {
276         LogMsg(MSG_FAIL, "%s", errStr.c_str());
277         return false;
278     }
279     // check path
280     childRet = SmartSlavePath(stat.clientCwd, ctxNow.localPath, stat.optionalName.c_str());
281     if (childRet && ctxNow.transferConfig.updateIfNew) {  // file exist and option need update
282         // if is newer
283         uv_fs_t fs = {};
284         uv_fs_stat(nullptr, &fs, ctxNow.localPath.c_str(), nullptr);
285         uv_fs_req_cleanup(&fs);
286         if ((uint64_t)fs.statbuf.st_mtim.tv_sec >= ctxNow.transferConfig.mtime) {
287             LogMsg(MSG_FAIL, "Target file is the same date or newer,path: %s", ctxNow.localPath.c_str());
288             return false;
289         }
290     }
291     uv_fs_t *openReq = new uv_fs_t;
292     if (openReq == nullptr) {
293         LogMsg(MSG_FAIL, "HdcFile::SlaveCheck new openReq failed");
294         return false;
295     }
296     memset_s(openReq, sizeof(uv_fs_t), 0, sizeof(uv_fs_t));
297     openReq->data = &ctxNow;
298     // begin work
299     ++refCount;
300     uv_fs_open(loopTask, openReq, ctxNow.localPath.c_str(), UV_FS_O_TRUNC | UV_FS_O_CREAT | UV_FS_O_WRONLY,
301                S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH, OnFileOpen);
302     if (ctxNow.transferDirBegin == 0) {
303         ctxNow.transferDirBegin = Base::GetRuntimeMSec();
304     }
305     ctxNow.transferBegin = Base::GetRuntimeMSec();
306     return ret;
307 }
308 
TransferNext(CtxFile *context)309 void HdcFile::TransferNext(CtxFile *context)
310 {
311     context->localName = context->taskQueue.back();
312     context->localPath = context->localDirName + context->localName;
313     context->taskQueue.pop_back();
314     WRITE_LOG(LOG_DEBUG, "TransferNext localPath = %s queuesize:%d",
315               context->localPath.c_str(), ctxNow.taskQueue.size());
316     uv_fs_t *openReq = new uv_fs_t;
317     if (openReq == nullptr) {
318         WRITE_LOG(LOG_FATAL, "HdcFile::TransferNext new openReq failed for file %s", context->localPath.c_str());
319         OnFileOpenFailed(context);
320         return;
321     }
322     memset_s(openReq, sizeof(uv_fs_t), 0, sizeof(uv_fs_t));
323     openReq->data = context;
324     do {
325         ++refCount;
326         uv_fs_open(loopTask, openReq, context->localPath.c_str(), O_RDONLY, S_IWUSR | S_IRUSR, OnFileOpen);
327     } while (false);
328 
329     return;
330 }
331 
CommandDispatch(const uint16_t command, uint8_t *payload, const int payloadSize)332 bool HdcFile::CommandDispatch(const uint16_t command, uint8_t *payload, const int payloadSize)
333 {
334     HdcTransferBase::CommandDispatch(command, payload, payloadSize);
335     bool ret = true;
336     StartTraceScope("HdcFile::CommandDispatch");
337     switch (command) {
338         case CMD_FILE_INIT: {  // initial
339             string s = string(reinterpret_cast<char *>(payload), payloadSize);
340             ret = BeginTransfer(&ctxNow, s);
341             ctxNow.transferBegin = Base::GetRuntimeMSec();
342             break;
343         }
344         case CMD_FILE_CHECK: {
345             ret = SlaveCheck(payload, payloadSize);
346             break;
347         }
348         case CMD_FILE_MODE:
349         case CMD_DIR_MODE: {
350             ret = FileModeSync(command, payload, payloadSize);
351             break;
352         }
353         case CMD_FILE_FINISH: {
354             if (*payload) {  // close-step3
355                 if (ctxNow.isFdOpen) {
356                     WRITE_LOG(LOG_DEBUG, "OnFileIO fs_close, localPath:%s result:%d, closeReqSubmitted:%d",
357                               ctxNow.localPath.c_str(), ctxNow.openFd, ctxNow.closeReqSubmitted);
358                     CloseFd(ctxNow.openFd);
359                     // solve the fd leak caused by early exit due to illegal operation on a directory.
360                     ctxNow.isFdOpen = false;
361                 }
362                 WRITE_LOG(LOG_DEBUG, "Dir = %d taskQueue size = %d", ctxNow.isDir, ctxNow.taskQueue.size());
363                 if (ctxNow.isDir && (ctxNow.taskQueue.size() > 0)) {
364                     TransferNext(&ctxNow);
365                 } else {
366                     ctxNow.ioFinish = true;
367                     ctxNow.transferDirBegin = 0;
368                     --(*payload);
369                     SendToAnother(CMD_FILE_FINISH, payload, 1);
370                 }
371             } else {  // close-step3
372                 TransferSummary(&ctxNow);
373                 TaskFinish();
374             }
375             break;
376         }
377         default:
378             break;
379     }
380     return ret;
381 }
382 }  // namespace Hdc
383