1/*
2 * Copyright (c) 2021-2024 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 "util.h"
17
18#include <array>
19#include <chrono>
20#include <cinttypes>
21#include <cstdarg>
22#include <fstream>
23#include <iostream>
24
25#include <sys/prctl.h>
26#include <sys/stat.h>
27#include <sys/syscall.h>
28#include <sys/time.h>
29#include <sys/types.h>
30#include <unistd.h>
31
32#include "aggregator.h"
33#include "config_multimodal.h"
34#include "define_multimodal.h"
35#include "error_multimodal.h"
36#include "mmi_log.h"
37#include "securec.h"
38
39#undef MMI_LOG_TAG
40#define MMI_LOG_TAG "Util"
41
42namespace OHOS {
43namespace MMI {
44namespace {
45constexpr int32_t FILE_SIZE_MAX { 0x6C445 };
46constexpr int32_t MAX_PRO_FILE_SIZE { 128000 };
47constexpr int32_t INVALID_FILE_SIZE { -1 };
48constexpr int32_t MIN_INTERVALTIME { 36 };
49constexpr int32_t MAX_INTERVALTIME { 100 };
50constexpr int32_t MIN_DELAYTIME { 300 };
51constexpr int32_t MAX_DELAYTIME { 1000 };
52constexpr int32_t COMMENT_SUBSCRIPT { 0 };
53const std::string CONFIG_ITEM_REPEAT = "Key.autorepeat";
54const std::string CONFIG_ITEM_DELAY = "Key.autorepeat.delaytime";
55const std::string CONFIG_ITEM_INTERVAL = "Key.autorepeat.intervaltime";
56const std::string CONFIG_ITEM_TYPE = "Key.keyboard.type";
57const std::string CURSORSTYLE_PATH = "/system/etc/multimodalinput/mouse_icon/";
58const std::string DATA_PATH = "/data";
59const std::string INPUT_PATH = "/system/";
60const std::string KEY_PATH = "/vendor/etc/keymap/";
61constexpr size_t BUF_TID_SIZE { 10 };
62constexpr size_t BUF_CMD_SIZE { 512 };
63constexpr size_t PROGRAM_NAME_SIZE { 256 };
64constexpr int32_t TIME_CONVERSION_UNIT { 1000 };
65} // namespace
66
67int64_t GetSysClockTime()
68{
69    struct timespec ts = { 0, 0 };
70    if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
71        MMI_HILOGD("clock_gettime failed:%{public}d", errno);
72        return 0;
73    }
74    return (ts.tv_sec * TIME_CONVERSION_UNIT * TIME_CONVERSION_UNIT) + (ts.tv_nsec / TIME_CONVERSION_UNIT);
75}
76
77int64_t GetMillisTime()
78{
79    auto timeNow = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now());
80    auto tmp = std::chrono::duration_cast<std::chrono::milliseconds>(timeNow.time_since_epoch());
81    return tmp.count();
82}
83
84static std::string GetThisThreadIdOfString()
85{
86    thread_local std::string threadLocalId;
87    if (threadLocalId.empty()) {
88        long tid = syscall(SYS_gettid);
89        char buf[BUF_TID_SIZE] = {};
90        const int32_t ret = sprintf_s(buf, BUF_TID_SIZE, "%06d", tid);
91        if (ret < 0) {
92            printf("ERR: in %s, #%d, call sprintf_s failed, ret = %d", __func__, __LINE__, ret);
93            return threadLocalId;
94        }
95        buf[BUF_TID_SIZE - 1] = '\0';
96        threadLocalId = buf;
97    }
98
99    return threadLocalId;
100}
101
102uint64_t GetThisThreadId()
103{
104    std::string stid = GetThisThreadIdOfString();
105    auto tid = std::atoll(stid.c_str());
106    return tid;
107}
108
109static size_t StringToken(std::string &str, const std::string &sep, std::string &token)
110{
111    token = "";
112    if (str.empty()) {
113        return str.npos;
114    }
115    size_t pos = str.npos;
116    size_t tmp = 0;
117    for (auto &item : sep) {
118        tmp = str.find(item);
119        if (str.npos != tmp) {
120            pos = (std::min)(pos, tmp);
121        }
122    }
123    if (str.npos != pos) {
124        token = str.substr(0, pos);
125        if (str.npos != pos + 1) {
126            str = str.substr(pos + 1, str.npos);
127        }
128        if (pos == 0) {
129            return StringToken(str, sep, token);
130        }
131    } else {
132        token = str;
133        str = "";
134    }
135    return token.size();
136}
137
138size_t StringSplit(const std::string &str, const std::string &sep, std::vector<std::string> &vecList)
139{
140    size_t size;
141    auto strs = str;
142    std::string token;
143    while (str.npos != (size = StringToken(strs, sep, token))) {
144        vecList.push_back(token);
145    }
146    return vecList.size();
147}
148
149std::string IdsListToString(const std::vector<int32_t> &list, const std::string &sep)
150{
151    std::string str;
152    for (const auto &it : list) {
153        str += std::to_string(it) + sep;
154    }
155    if (str.size() > 0) {
156        str.resize(str.size() - sep.size());
157    }
158    return str;
159}
160
161int32_t GetPid()
162{
163    return static_cast<int32_t>(getpid());
164}
165
166static std::string GetFileName(const std::string &strPath)
167{
168    size_t nPos = strPath.find_last_of('/');
169    if (strPath.npos == nPos) {
170        nPos = strPath.find_last_of('\\');
171    }
172    if (strPath.npos == nPos) {
173        return strPath;
174    }
175
176    return strPath.substr(nPos + 1, strPath.npos);
177}
178
179const char *GetProgramName()
180{
181    static char programName[PROGRAM_NAME_SIZE] = {};
182    if (programName[0] != '\0') {
183        return programName;
184    }
185
186    char buf[BUF_CMD_SIZE] = { 0 };
187    if (sprintf_s(buf, BUF_CMD_SIZE, "/proc/%d/cmdline", static_cast<int32_t>(getpid())) == -1) {
188        KMSG_LOGE("GetProcessInfo sprintf_s /proc/.../cmdline error");
189        return "";
190    }
191    FILE *fp = fopen(buf, "rb");
192    CHKPS(fp);
193    static constexpr size_t bufLineSize = 512;
194    char bufLine[bufLineSize] = { 0 };
195    if ((fgets(bufLine, bufLineSize, fp) == nullptr)) {
196        KMSG_LOGE("fgets failed");
197        if (fclose(fp) != 0) {
198            KMSG_LOGW("Close file:%s failed", buf);
199        }
200        fp = nullptr;
201        return "";
202    }
203    if (fclose(fp) != 0) {
204        KMSG_LOGW("Close file:%s failed", buf);
205    }
206    fp = nullptr;
207
208    std::string tempName(bufLine);
209    tempName = GetFileName(tempName);
210    if (tempName.empty()) {
211        KMSG_LOGE("tempName is empty");
212        return "";
213    }
214    const size_t copySize = std::min(tempName.size(), PROGRAM_NAME_SIZE - 1);
215    if (copySize == 0) {
216        KMSG_LOGE("The copySize is 0");
217        return "";
218    }
219    errno_t ret = memcpy_s(programName, PROGRAM_NAME_SIZE, tempName.c_str(), copySize);
220    if (ret != EOK) {
221        return "";
222    }
223    KMSG_LOGI("GetProgramName success. programName = %s", programName);
224
225    return programName;
226}
227
228void SetThreadName(const std::string &name)
229{
230    prctl(PR_SET_NAME, name.c_str());
231}
232
233static bool IsFileExists(const std::string &fileName)
234{
235    return (access(fileName.c_str(), F_OK) == 0);
236}
237
238static bool CheckFileExtendName(const std::string &filePath, const std::string &checkExtension)
239{
240    std::string::size_type pos = filePath.find_last_of('.');
241    if (pos == std::string::npos) {
242        MMI_HILOGE("File is not find extension");
243        return false;
244    }
245    return (filePath.substr(pos + 1, filePath.npos) == checkExtension);
246}
247
248static int32_t GetFileSize(const std::string &filePath)
249{
250    struct stat statbuf = { 0 };
251    if (stat(filePath.c_str(), &statbuf) != 0) {
252        MMI_HILOGE("Get file size error");
253        return INVALID_FILE_SIZE;
254    }
255    return statbuf.st_size;
256}
257
258static std::string ReadFile(const std::string &filePath)
259{
260    FILE *fp = fopen(filePath.c_str(), "r");
261    CHKPS(fp);
262    std::string dataStr;
263    char buf[256] = {};
264    while (fgets(buf, sizeof(buf), fp) != nullptr) {
265        dataStr += buf;
266    }
267    if (fclose(fp) != 0) {
268        MMI_HILOGW("Close file failed");
269    }
270    return dataStr;
271}
272
273static bool IsValidPath(const std::string &rootDir, const std::string &filePath)
274{
275    return (filePath.compare(0, rootDir.size(), rootDir) == 0);
276}
277
278bool IsValidJsonPath(const std::string &filePath)
279{
280    return IsValidPath(DATA_PATH, filePath) || IsValidPath(INPUT_PATH, filePath);
281}
282
283static bool IsValidProPath(const std::string &filePath)
284{
285    return IsValidPath(KEY_PATH, filePath);
286}
287
288static bool IsValidTomlPath(const std::string &filePath)
289{
290    return IsValidPath(KEY_PATH, filePath);
291}
292
293void ReadProFile(const std::string &filePath, int32_t deviceId,
294    std::map<int32_t, std::map<int32_t, int32_t>> &configMap)
295{
296    CALL_DEBUG_ENTER;
297    if (filePath.empty()) {
298        MMI_HILOGE("FilePath is empty");
299        return;
300    }
301    char realPath[PATH_MAX] = {};
302    CHKPV(realpath(filePath.c_str(), realPath));
303    if (!IsValidProPath(realPath)) {
304        MMI_HILOGE("File path is error");
305        return;
306    }
307    if (!IsFileExists(realPath)) {
308        MMI_HILOGE("File is not existent");
309        return;
310    }
311    if (!CheckFileExtendName(realPath, "pro")) {
312        MMI_HILOGE("Unable to parse files other than json format");
313        return;
314    }
315    auto fileSize = GetFileSize(realPath);
316    if ((fileSize == INVALID_FILE_SIZE) || (fileSize >= MAX_PRO_FILE_SIZE)) {
317        MMI_HILOGE("The configuration file size is incorrect");
318        return;
319    }
320    ReadProConfigFile(realPath, deviceId, configMap);
321}
322
323static inline bool IsNum(const std::string &str)
324{
325    std::istringstream sin(str);
326    double num;
327    return (sin >> num) && sin.eof();
328}
329
330void ReadProConfigFile(const std::string &realPath, int32_t deviceId,
331    std::map<int32_t, std::map<int32_t, int32_t>> &configKey)
332{
333    CALL_DEBUG_ENTER;
334    std::ifstream reader(realPath);
335    if (!reader.is_open()) {
336        MMI_HILOGE("Failed to open config file");
337        return;
338    }
339    std::string strLine;
340    int32_t sysKeyValue;
341    int32_t nativeKeyValue;
342    int32_t elementKey = 0;
343    int32_t elementValue = 0;
344    std::map<int32_t, int32_t> tmpConfigKey;
345    while (std::getline(reader, strLine)) {
346        const char* line = strLine.c_str();
347        int32_t len = strlen(line);
348        char* realLine = static_cast<char*>(malloc(len + 1));
349        CHKPV(realLine);
350        if (strcpy_s(realLine, len + 1, line) != EOK) {
351            MMI_HILOGE("strcpy_s error");
352            free(realLine);
353            realLine = nullptr;
354            return;
355        }
356        *(realLine + len + 1) = '\0';
357        int32_t ret = ReadConfigInfo(realLine, len, &elementKey, &elementValue);
358        free(realLine);
359        realLine = nullptr;
360        if (ret != RET_OK) {
361            MMI_HILOGE("Failed to read from line of config info");
362            reader.close();
363            return;
364        }
365        nativeKeyValue = elementKey;
366        sysKeyValue = elementValue;
367        MMI_HILOGD("The nativeKeyValue is:%{public}d, sysKeyValue is:%{public}d", nativeKeyValue, sysKeyValue);
368        tmpConfigKey.insert(std::pair<int32_t, int32_t>(nativeKeyValue, sysKeyValue));
369    }
370    reader.close();
371    auto iter = configKey.insert(std::make_pair(deviceId, tmpConfigKey));
372    if (!iter.second) {
373        MMI_HILOGE("The file name is duplicated");
374        return;
375    }
376}
377
378std::string ReadJsonFile(const std::string &filePath)
379{
380    if (filePath.empty()) {
381        MMI_HILOGE("FilePath is empty");
382        return "";
383    }
384    char realPath[PATH_MAX] = {};
385    CHKPS(realpath(filePath.c_str(), realPath));
386    if (!IsValidJsonPath(realPath)) {
387        MMI_HILOGE("File path is error");
388        return "";
389    }
390    if (!CheckFileExtendName(realPath, "json")) {
391        MMI_HILOGE("Unable to parse files other than json format");
392        return "";
393    }
394    if (!IsFileExists(realPath)) {
395        MMI_HILOGE("File is not existent");
396        return "";
397    }
398    int32_t fileSize = GetFileSize(realPath);
399    if ((fileSize <= 0) || (fileSize > FILE_SIZE_MAX)) {
400        MMI_HILOGE("File size out of read range");
401        return "";
402    }
403    return ReadFile(filePath);
404}
405
406static int32_t ConfigItemSwitch(const std::string &configItem, const std::string &value, DeviceConfig &devConf)
407{
408    CALL_DEBUG_ENTER;
409    if (configItem.empty() || value.empty()) {
410        MMI_HILOGE("Get key config item is invalid");
411        return RET_ERR;
412    }
413    if (!IsNum(value)) {
414        MMI_HILOGE("Get key config item is invalid");
415        return RET_ERR;
416    }
417    if (configItem == CONFIG_ITEM_REPEAT) {
418        devConf.autoSwitch = stoi(value);
419    } else if (configItem == CONFIG_ITEM_DELAY) {
420        devConf.delayTime = stoi(value);
421        if (devConf.delayTime < MIN_DELAYTIME || devConf.delayTime > MAX_DELAYTIME) {
422            MMI_HILOGE("Unusual the delaytime");
423            return RET_ERR;
424        }
425    } else if (configItem == CONFIG_ITEM_INTERVAL) {
426        devConf.intervalTime = stoi(value);
427        if (devConf.intervalTime < MIN_INTERVALTIME || devConf.intervalTime > MAX_INTERVALTIME) {
428            MMI_HILOGE("Unusual the intervaltime");
429            return RET_ERR;
430        }
431    } else if (configItem == CONFIG_ITEM_TYPE) {
432        devConf.keyboardType = stoi(value);
433    }
434    return RET_OK;
435}
436
437static int32_t ReadConfigFile(const std::string &realPath, DeviceConfig &devConf)
438{
439    CALL_DEBUG_ENTER;
440    std::ifstream cfgFile(realPath);
441    if (!cfgFile.is_open()) {
442        MMI_HILOGE("Failed to open config file");
443        return FILE_OPEN_FAIL;
444    }
445    std::string tmp;
446    while (std::getline(cfgFile, tmp)) {
447        RemoveSpace(tmp);
448        size_t pos = tmp.find('#');
449        if (pos != tmp.npos && pos != COMMENT_SUBSCRIPT) {
450            MMI_HILOGE("File format is error");
451            cfgFile.close();
452            return RET_ERR;
453        }
454        if (tmp.empty() || tmp.front() == '#') {
455            continue;
456        }
457        pos = tmp.find('=');
458        if ((pos == std::string::npos) || (tmp.back() == '=')) {
459            MMI_HILOGE("Find config item error");
460            cfgFile.close();
461            return RET_ERR;
462        }
463        std::string configItem = tmp.substr(0, pos);
464        std::string value = tmp.substr(pos + 1);
465        if (ConfigItemSwitch(configItem, value, devConf) == RET_ERR) {
466            MMI_HILOGE("Configuration item error");
467            cfgFile.close();
468            return RET_ERR;
469        }
470    }
471    cfgFile.close();
472    return RET_OK;
473}
474
475int32_t ReadTomlFile(const std::string &filePath, DeviceConfig &devConf)
476{
477    if (filePath.empty()) {
478        MMI_HILOGE("FilePath is empty");
479        return RET_ERR;
480    }
481    char realPath[PATH_MAX] = {};
482    CHKPR(realpath(filePath.c_str(), realPath), RET_ERR);
483    if (!IsValidTomlPath(realPath)) {
484        MMI_HILOGE("File path is error");
485        return RET_ERR;
486    }
487    if (!IsFileExists(realPath)) {
488        MMI_HILOGE("File is not existent");
489        return RET_ERR;
490    }
491    if (!CheckFileExtendName(realPath, "TOML")) {
492        MMI_HILOGE("Unable to parse files other than json format");
493        return RET_ERR;
494    }
495    int32_t fileSize = GetFileSize(realPath);
496    if ((fileSize <= 0) || (fileSize > FILE_SIZE_MAX)) {
497        MMI_HILOGE("File size out of read range");
498        return RET_ERR;
499    }
500    if (ReadConfigFile(realPath, devConf) == RET_ERR) {
501        MMI_HILOGE("Read device config file failed");
502        return RET_ERR;
503    }
504    return RET_OK;
505}
506
507int32_t ReadCursorStyleFile(const std::string &filePath)
508{
509    CALL_DEBUG_ENTER;
510    if (filePath.empty()) {
511        MMI_HILOGE("FilePath is empty");
512        return RET_ERR;
513    }
514    if (!IsFileExists(filePath)) {
515        MMI_HILOGE("File is not existent");
516        return RET_ERR;
517    }
518    char realPath[PATH_MAX] = {};
519    CHKPR(realpath(filePath.c_str(), realPath), RET_ERR);
520    int32_t fileSize = GetFileSize(realPath);
521    if ((fileSize <= 0) || (fileSize > FILE_SIZE_MAX)) {
522        MMI_HILOGE("File size out of read range");
523        return RET_ERR;
524    }
525    return RET_OK;
526}
527
528std::string StringPrintf(const char *format, ...)
529{
530    char space[1024];
531
532    va_list ap;
533    va_start(ap, format);
534    std::string result;
535    int32_t ret = vsnprintf_s(space, sizeof(space), sizeof(space) - 1, format, ap);
536    if (ret >= RET_OK && (size_t)ret < sizeof(space)) {
537        result = space;
538    } else {
539        MMI_HILOGE("The buffer is overflow");
540    }
541    va_end(ap);
542    return result;
543}
544
545std::string FileVerification(std::string &filePath, const std::string &checkExtension)
546{
547    if (filePath.empty()) {
548        MMI_HILOGE("FilePath is empty");
549        return "";
550    }
551    char realPath[PATH_MAX] = {};
552    CHKPS(realpath(filePath.c_str(), realPath));
553    if (!IsFileExists(realPath)) {
554        MMI_HILOGE("File is not existent");
555        return "";
556    }
557    if (!CheckFileExtendName(realPath, checkExtension)) {
558        MMI_HILOGE("Unable to parse files other than json format");
559        return "";
560    }
561    int32_t fileSize = GetFileSize(realPath);
562    if ((fileSize <= 0) || (fileSize > FILE_SIZE_MAX)) {
563        MMI_HILOGE("File size out of read range");
564        return "";
565    }
566    return realPath;
567}
568
569
570bool Aggregator::Record(const LogHeader &lh, const std::string &key, const std::string &record)
571{
572    constexpr int32_t oneSecond = 1000;
573    if (timerId_ != -1) {
574        resetTimer_(timerId_);
575    } else {
576        timerId_ = addTimer_(oneSecond, 1, [this, lh]() {
577            FlushRecords(lh);
578            timerId_ = -1;
579        });
580    }
581    if (key == key_) {
582        auto now = std::chrono::system_clock::now();
583        records_.push_back({record, now});
584        if (records_.size() >= maxRecordCount_) {
585            FlushRecords(lh);
586        }
587        return true;
588    } else {
589        FlushRecords(lh, key, record);
590        key_ = key;
591        return false;
592    }
593}
594
595void Aggregator::FlushRecords(const LogHeader &lh, const std::string &key, const std::string &extraRecord)
596{
597    constexpr uint32_t milliSecondWidth = 3;
598    constexpr uint32_t microToMilli = 1000;
599    size_t recordCount = records_.size();
600    std::ostringstream oss;
601    if (!records_.empty()) {
602        oss << key_;
603        oss << ", first: " << records_.front().record << "-(";
604        auto firstTime = records_.front().timestamp;
605        time_t firstTimeT = std::chrono::system_clock::to_time_t(firstTime);
606        std::tm *bt = std::localtime(&firstTimeT);
607        if (bt == nullptr) {
608            MMI_HILOGE("bt is nullptr, this is a invalid time");
609            return;
610        }
611        oss << std::put_time(bt, "%Y-%m-%d %H:%M:%S");
612        auto since_epoch = firstTime.time_since_epoch();
613        auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(since_epoch).count() % microToMilli;
614        oss << '.' << std::setfill('0') << std::setw(milliSecondWidth) << millis << "ms)";
615
616        if (records_.size() > 1) {
617            size_t i = records_.size() - 1;
618            const auto &recordInfo = records_[i];
619            oss << ", " << recordInfo.record;
620        }
621        records_.clear();
622        oss << ", count: " << recordCount;
623    }
624    if (!extraRecord.empty()) {
625        if (!oss.str().empty()) {
626            oss << ", last: ";
627        }
628        oss << key << ": " << extraRecord;
629    }
630    if (!oss.str().empty()) {
631        MMI_HILOG_HEADER(LOG_INFO, lh, "%{public}s", oss.str().c_str());
632    }
633}
634
635Aggregator::~Aggregator()
636{
637    FlushRecords(MMI_LOG_HEADER);
638}
639
640} // namespace MMI
641} // namespace OHOS
642