/* * Copyright (c) 2021 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "utils.h" #include <algorithm> #include <cerrno> #include <cstdint> #include <cstdlib> #include <dirent.h> #include <limits> #include <linux/reboot.h> #include <string> #include <sys/reboot.h> #include <sys/stat.h> #include <sys/syscall.h> #include <unistd.h> #include <vector> #include "fs_manager/mount.h" #include "init_reboot.h" #include "log/log.h" #include "misc_info/misc_info.h" #ifdef WITH_SELINUX #include <policycoreutils.h> #include "selinux/selinux.h" #endif #include "package/pkg_manager.h" #include "securec.h" #include "updater/updater_const.h" namespace Updater { using namespace Hpackage; namespace Utils { constexpr uint8_t SHIFT_RIGHT_FOUR_BITS = 4; constexpr int USECONDS_PER_SECONDS = 1000000; // 1s = 1000000us constexpr int NANOSECS_PER_USECONDS = 1000; // 1us = 1000ns constexpr int MAX_TIME_SIZE = 20; void SaveLogs() { std::string updaterLogPath = std::string(UPDATER_LOG); std::string stageLogPath = std::string(UPDATER_STAGE_LOG); // save logs bool ret = CopyUpdaterLogs(TMP_LOG, updaterLogPath); if (!ret) { LOG(ERROR) << "Copy updater log failed!"; } mode_t mode = 0640; chmod(updaterLogPath.c_str(), mode); STAGE(UPDATE_STAGE_SUCCESS) << "PostUpdater"; ret = CopyUpdaterLogs(TMP_STAGE_LOG, stageLogPath); chmod(stageLogPath.c_str(), mode); if (!ret) { LOG(ERROR) << "Copy stage log failed!"; } } int32_t DeleteFile(const std::string& filename) { if (filename.empty()) { LOG(ERROR) << "Invalid filename"; return -1; } if (unlink(filename.c_str()) == -1 && errno != ENOENT) { LOG(ERROR) << "unlink " << filename << " failed"; return -1; } return 0; } int MkdirRecursive(const std::string &pathName, mode_t mode) { size_t slashPos = 0; struct stat info {}; while (true) { slashPos = pathName.find_first_of("/", slashPos); if (slashPos == std::string::npos) { break; } if (slashPos == 0) { slashPos++; continue; } if (slashPos > PATH_MAX) { LOG(ERROR) << "path too long for mkdir"; return -1; } auto subDir = pathName.substr(0, slashPos); LOG(INFO) << "subDir : " << subDir; if (stat(subDir.c_str(), &info) != 0) { int ret = mkdir(subDir.c_str(), mode); if (ret && errno != EEXIST) { return ret; } } slashPos++; } int ret = mkdir(pathName.c_str(), mode); if (ret && errno != EEXIST) { return ret; } return 0; } int64_t GetFilesFromDirectory(const std::string &path, std::vector<std::string> &files, bool isRecursive) { struct stat sb {}; if (stat(path.c_str(), &sb) == -1) { LOG(ERROR) << "Failed to stat"; return -1; } DIR *dirp = opendir(path.c_str()); struct dirent *dp; int64_t totalSize = 0; while ((dp = readdir(dirp)) != nullptr) { std::string fileName = path + "/" + dp->d_name; struct stat st {}; if (stat(fileName.c_str(), &st) == 0) { std::string tmpName = dp->d_name; if (tmpName == "." || tmpName == "..") { continue; } if (isRecursive && S_ISDIR(st.st_mode)) { totalSize += GetFilesFromDirectory(fileName, files, isRecursive); } files.push_back(fileName); totalSize += st.st_size; } } closedir(dirp); return totalSize; } std::vector<std::string> SplitString(const std::string &str, const std::string del) { std::vector<std::string> result; size_t found = std::string::npos; size_t start = 0; while (true) { found = str.find_first_of(del, start); result.push_back(str.substr(start, found - start)); if (found == std::string::npos) { break; } start = found + 1; } return result; } std::string Trim(const std::string &str) { if (str.empty()) { LOG(ERROR) << "str is empty"; return str; } size_t start = 0; size_t end = str.size() - 1; while (start < str.size()) { if (!isspace(str[start])) { break; } start++; } while (start < end) { if (!isspace(str[end])) { break; } end--; } if (end < start) { return ""; } return str.substr(start, end - start + 1); } std::string ConvertSha256Hex(const uint8_t* shaDigest, size_t length) { const std::string hexChars = "0123456789abcdef"; std::string haxSha256 = ""; unsigned int c; for (size_t i = 0; i < length; ++i) { auto d = shaDigest[i]; c = (d >> SHIFT_RIGHT_FOUR_BITS) & 0xf; // last 4 bits haxSha256.push_back(hexChars[c]); haxSha256.push_back(hexChars[d & 0xf]); } return haxSha256; } bool SetRebootMisc(const std::string& rebootTarget, const std::string &extData, struct UpdateMessage &msg) { static const int32_t maxCommandSize = 16; int result = 0; if (rebootTarget == "updater" && strcmp(msg.command, "boot_updater") != 0) { result = strcpy_s(msg.command, maxCommandSize, "boot_updater"); } else if (rebootTarget == "flashd" && strcmp(msg.command, "flashd") != 0) { result = strcpy_s(msg.command, maxCommandSize, "boot_flash"); } else if (rebootTarget == "bootloader" && strcmp(msg.command, "boot_loader") != 0) { result = strcpy_s(msg.command, maxCommandSize, "boot_loader"); } if (result != EOK) { LOG(ERROR) << "reboot set misc strcpy failed"; return false; } msg.command[maxCommandSize] = 0; if (extData.empty()) { (void)memset_s(msg.update, sizeof(msg.update), 0, sizeof(msg.update)); return true; } if (strcpy_s(msg.update, sizeof(msg.update) - 1, extData.c_str()) != EOK) { LOG(ERROR) << "failed to copy update"; return false; } msg.update[sizeof(msg.update) - 1] = 0; return true; } void UpdaterDoReboot(const std::string& rebootTarget, const std::string &extData) { LOG(INFO) << ", rebootTarget: " << rebootTarget; LoadFstab(); struct UpdateMessage msg = {}; if (rebootTarget.empty()) { if (WriteUpdaterMiscMsg(msg) != true) { LOG(INFO) << "UpdaterDoReboot: WriteUpdaterMessage empty error"; return; } } else { if (!ReadUpdaterMiscMsg(msg)) { LOG(ERROR) << "UpdaterDoReboot read misc failed"; return; } if (!SetRebootMisc(rebootTarget, extData, msg)) { LOG(ERROR) << "UpdaterDoReboot set misc failed"; return; } if (!WriteUpdaterMiscMsg(msg)) { LOG(INFO) << "UpdaterDoReboot: WriteUpdaterMiscMsg error"; return; } } sync(); #ifndef UPDATER_UT DoReboot(rebootTarget.c_str()); while (true) { pause(); } #else return; #endif } void DoShutdown() { UpdateMessage msg = {}; if (!WriteUpdaterMiscMsg(msg)) { LOG(ERROR) << "DoShutdown: WriteUpdaterMessage empty error"; return; } sync(); DoReboot("shutdown"); } std::string GetCertName() { #ifndef UPDATER_UT static std::string signingCertName = "/etc/certificate/signing_cert.crt"; #else static std::string signingCertName = "/data/updater/src/signing_cert.crt"; #endif return signingCertName; } bool WriteFully(int fd, const uint8_t *data, size_t size) { ssize_t written = 0; size_t rest = size; while (rest > 0) { do { written = write(fd, data, rest); } while (written < 0 && errno == EINTR); if (written < 0) { return false; } data += written; rest -= static_cast<size_t>(written); } return true; } bool ReadFully(int fd, void *data, size_t size) { auto p = reinterpret_cast<uint8_t *>(data); size_t remaining = size; while (remaining > 0) { ssize_t sread = read(fd, p, remaining); if (sread <= 0) { LOG(ERROR) << "Utils::ReadFully run error"; return false; } p += sread; remaining -= static_cast<size_t>(sread); } return true; } bool ReadFileToString(int fd, std::string &content) { struct stat sb {}; if (fstat(fd, &sb) != -1 && sb.st_size > 0) { content.resize(static_cast<size_t>(sb.st_size)); } ssize_t n; auto remaining = static_cast<size_t>(sb.st_size); auto p = reinterpret_cast<char *>(content.data()); while (remaining > 0) { n = read(fd, p, remaining); if (n <= 0) { return false; } p += n; remaining -= static_cast<size_t>(n); } return true; } bool WriteStringToFile(int fd, const std::string& content) { const char *p = content.data(); size_t remaining = content.size(); while (remaining > 0) { ssize_t n = write(fd, p, remaining); if (n == -1) { return false; } p += n; remaining -= static_cast<size_t>(n); } return true; } bool CopyFile(const std::string &src, const std::string &dest, bool isAppend) { char realPath[PATH_MAX + 1] = {0}; if (realpath(src.c_str(), realPath) == nullptr) { LOG(ERROR) << src << " get realpath fail"; return false; } std::ios_base::openmode mode = isAppend ? std::ios::app | std::ios::out : std::ios_base::out; std::ifstream fin(realPath); std::ofstream fout(dest, mode); if (!fin.is_open() || !fout.is_open()) { return false; } fout << fin.rdbuf(); if (fout.fail()) { fout.clear(); return false; } fout.flush(); return true; } std::string GetLocalBoardId() { return "HI3516"; } void CompressLogs(const std::string &logName) { PkgManager::PkgManagerPtr pkgManager = PkgManager::CreatePackageInstance(); if (pkgManager == nullptr) { LOG(ERROR) << "pkgManager is nullptr"; return; } std::vector<std::pair<std::string, ZipFileInfo>> files; // Build the zip file to be packaged std::vector<std::string> testFileNames; std::string realName = logName.substr(logName.find_last_of("/") + 1); std::string logPath = logName.substr(0, logName.find_last_of("/")); testFileNames.push_back(realName); for (auto name : testFileNames) { ZipFileInfo file; file.fileInfo.identity = name; file.fileInfo.packMethod = PKG_COMPRESS_METHOD_ZIP; file.fileInfo.digestMethod = PKG_DIGEST_TYPE_CRC; std::string fileName = logName; files.push_back(std::pair<std::string, ZipFileInfo>(fileName, file)); } PkgInfo pkgInfo; pkgInfo.signMethod = PKG_SIGN_METHOD_NONE; pkgInfo.digestMethod = PKG_SIGN_METHOD_NONE; pkgInfo.pkgType = PKG_PACK_TYPE_ZIP; char realTime[MAX_TIME_SIZE] = {0}; auto sysTime = std::chrono::system_clock::now(); auto currentTime = std::chrono::system_clock::to_time_t(sysTime); struct tm *localTime = std::localtime(¤tTime); if (localTime != nullptr) { std::strftime(realTime, sizeof(realTime), "%Y%m%d%H%M%S", localTime); } char pkgName[MAX_LOG_NAME_SIZE]; if (snprintf_s(pkgName, MAX_LOG_NAME_SIZE, MAX_LOG_NAME_SIZE - 1, "%s/%s_%s.zip", logPath.c_str(), realName.c_str(), realTime) == -1) { PkgManager::ReleasePackageInstance(pkgManager); return; } int32_t ret = pkgManager->CreatePackage(pkgName, GetCertName(), &pkgInfo, files); if (ret != 0) { LOG(WARNING) << "CompressLogs failed"; PkgManager::ReleasePackageInstance(pkgManager); return; } (void)DeleteFile(logName); PkgManager::ReleasePackageInstance(pkgManager); } int GetFileSize(const std::string &dLog) { int ret = 0; std::ifstream ifs(dLog, std::ios::binary | std::ios::in); if (ifs.is_open()) { ifs.seekg(0, std::ios::end); ret = ifs.tellg(); } return ret; } bool CopyUpdaterLogs(const std::string &sLog, const std::string &dLog) { std::size_t found = dLog.find_last_of("/"); if (found == std::string::npos) { LOG(ERROR) << "Dest filePath error"; return false; } std::string destPath = dLog.substr(0, found); if (MountForPath(destPath) != 0) { LOG(WARNING) << "MountForPath /data/log failed!"; return false; } #ifdef WITH_SELINUX RestoreconRecurse(destPath.c_str()); #endif // WITH_SELINUX if (access(destPath.c_str(), 0) != 0) { if (MkdirRecursive(destPath.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) != 0) { LOG(ERROR) << "MkdirRecursive error!"; return false; } if (chown(destPath.c_str(), USER_UPDATE_AUTHORITY, USER_UPDATE_AUTHORITY) != EOK && chmod(destPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != EOK) { LOG(ERROR) << "Chmod failed!"; return false; } } if (Utils::GetFileSize(sLog) > MAX_LOG_SIZE) { LOG(ERROR) << "Size bigger for" << sLog; STAGE(UPDATE_STAGE_FAIL) << "Log file error, unable to copy"; return false; } while (Utils::GetFileSize(sLog) + GetDirSizeForFile(dLog) > MAX_LOG_DIR_SIZE) { if (DeleteOldFile(destPath) != true) { break; } } if (!CopyFile(sLog, dLog, true)) { LOG(ERROR) << "copy log file failed."; return false; } if (GetFileSize(dLog) >= MAX_LOG_SIZE) { LOG(INFO) << "log size greater than 5M!"; CompressLogs(dLog); } sync(); return true; } bool CheckDumpResult() { std::ifstream ifs; const std::string resultPath = std::string(UPDATER_PATH) + "/" + std::string(UPDATER_RESULT_FILE); ifs.open(resultPath, std::ios::in); std::string buff; if (ifs.is_open() && getline(ifs, buff) && buff.find("fail:") != std::string::npos) { return true; } LOG(ERROR) << "open result file failed"; return false; } void WriteDumpResult(const std::string &result) { if (access(UPDATER_PATH, 0) != 0) { if (MkdirRecursive(UPDATER_PATH, 0755) != 0) { // 0755: -rwxr-xr-x LOG(ERROR) << "MkdirRecursive error!"; return; } } LOG(INFO) << "WriteDumpResult: " << result; const std::string resultPath = std::string(UPDATER_PATH) + "/" + std::string(UPDATER_RESULT_FILE); FILE *fp = fopen(resultPath.c_str(), "w+"); if (fp == nullptr) { LOG(ERROR) << "open result file failed"; return; } char buf[MAX_RESULT_BUFF_SIZE] = "Pass\n"; if (sprintf_s(buf, MAX_RESULT_BUFF_SIZE - 1, "%s\n", result.c_str()) < 0) { LOG(WARNING) << "sprintf status fialed"; } if (fwrite(buf, 1, strlen(buf) + 1, fp) <= 0) { LOG(WARNING) << "write result file failed, err:" << errno; } if (fclose(fp) != 0) { LOG(WARNING) << "close result file failed"; } (void)chown(resultPath.c_str(), USER_ROOT_AUTHORITY, GROUP_UPDATE_AUTHORITY); (void)chmod(resultPath.c_str(), 0660); // 0660: -rw-rw---- } void UsSleep(int usec) { auto seconds = usec / USECONDS_PER_SECONDS; long nanoSeconds = static_cast<long>(usec) % USECONDS_PER_SECONDS * NANOSECS_PER_USECONDS; struct timespec ts = { static_cast<time_t>(seconds), nanoSeconds }; while (nanosleep(&ts, &ts) < 0 && errno == EINTR) { } } bool PathToRealPath(const std::string &path, std::string &realPath) { if (path.empty()) { LOG(ERROR) << "path is empty!"; return false; } if ((path.length() >= PATH_MAX)) { LOG(ERROR) << "path len is error, the len is: " << path.length(); return false; } char tmpPath[PATH_MAX] = {0}; if (realpath(path.c_str(), tmpPath) == nullptr) { LOG(ERROR) << "path to realpath error " << path; return false; } realPath = tmpPath; return true; } bool IsUpdaterMode() { struct stat st {}; if (stat("/bin/updater", &st) == 0 && S_ISREG(st.st_mode)) { LOG(INFO) << "updater mode"; return true; } LOG(INFO) << "normal mode"; return false; } bool RemoveDir(const std::string &path) { if (path.empty()) { LOG(ERROR) << "input path is empty."; return false; } std::string strPath = path; if (strPath.at(strPath.length() - 1) != '/') { strPath.append("/"); } DIR *d = opendir(strPath.c_str()); if (d != nullptr) { struct dirent *dt = nullptr; dt = readdir(d); while (dt != nullptr) { if (strcmp(dt->d_name, "..") == 0 || strcmp(dt->d_name, ".") == 0) { dt = readdir(d); continue; } struct stat st {}; auto file_name = strPath + std::string(dt->d_name); stat(file_name.c_str(), &st); if (S_ISDIR(st.st_mode)) { RemoveDir(file_name); } else { remove(file_name.c_str()); } dt = readdir(d); } closedir(d); } return rmdir(strPath.c_str()) == 0 ? true : false; } bool IsFileExist(const std::string &path) { struct stat st {}; if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) { return true; } return false; } bool IsDirExist(const std::string &path) { struct stat st {}; if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) { return true; } return false; } long long int GetDirSize(const std::string &folderPath) { DIR* dir = opendir(folderPath.c_str()); if (dir == nullptr) { LOG(ERROR) << "Failed to open folder: " << folderPath << std::endl; return 0; } struct dirent* entry; long long int totalSize = 0; while ((entry = readdir(dir)) != nullptr) { std::string fileName = entry->d_name; std::string filePath = folderPath + "/" + fileName; struct stat fileStat; if (stat(filePath.c_str(), &fileStat) != 0) { LOG(ERROR) << "Failed to get file status: " << filePath << std::endl; continue; } if (S_ISDIR(fileStat.st_mode)) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } std::string subFolderPath = filePath; totalSize += GetDirSize(subFolderPath); } else { totalSize += fileStat.st_size; } } closedir(dir); return totalSize; } long long int GetDirSizeForFile(const std::string &filePath) { std::size_t found = filePath.find_last_of("/"); if (found == std::string::npos) { LOG(ERROR) << "filePath error"; return -1; } return GetDirSize(filePath.substr(0, found)); } bool DeleteOldFile(const std::string folderPath) { DIR* dir = opendir(folderPath.c_str()); if (dir == nullptr) { LOG(ERROR) << "Failed to open folder: " << folderPath << std::endl; return false; } struct dirent* entry; std::string oldestFilePath = ""; time_t oldestFileTime = std::numeric_limits<time_t>::max(); while ((entry = readdir(dir)) != nullptr) { std::string fileName = entry->d_name; std::string filePath = folderPath + "/" + fileName; struct stat fileStat; if (stat(filePath.c_str(), &fileStat) != 0) { LOG(ERROR) << "Failed to get file status: " << filePath; continue; } if (fileName == "." || fileName == "..") { continue; } if (fileStat.st_mtime < oldestFileTime) { oldestFileTime = fileStat.st_mtime; oldestFilePath = filePath; } } closedir(dir); if (oldestFilePath.empty()) { LOG(ERROR) << "Unable to delete file"; return false; } if (remove(oldestFilePath.c_str()) != 0) { LOG(ERROR) << "Failed to delete file: " << oldestFilePath; return false; } return true; } std::vector<std::string> ParseParams(int argc, char **argv) { struct UpdateMessage boot {}; // read from misc if (!ReadUpdaterMiscMsg(boot)) { LOG(ERROR) << "ReadUpdaterMessage MISC_FILE failed!"; } // if boot.update is empty, read from command.The Misc partition may have dirty data, // so strlen(boot.update) is not used, which can cause system exceptions. if (boot.update[0] == '\0' && !access(COMMAND_FILE, 0)) { if (!ReadUpdaterMessage(COMMAND_FILE, boot)) { LOG(ERROR) << "ReadUpdaterMessage COMMAND_FILE failed!"; } } STAGE(UPDATE_STAGE_OUT) << "Init Params: " << boot.update; boot.update[sizeof(boot.update) - 1] = '\0'; std::vector<std::string> parseParams = Utils::SplitString(boot.update, "\n"); if (argc != 0 && argv != nullptr) { parseParams.insert(parseParams.begin(), argv, argv + argc); } return parseParams; } bool CheckUpdateMode(const std::string &mode) { std::vector<std::string> args = ParseParams(0, nullptr); for(const auto &arg : args) { if (arg.find(mode) != std::string::npos) { return true; } } return false; } std::string DurationToString(std::chrono::duration<double> duration, int precision) { std::ostringstream oss; oss << std::fixed << std::setprecision(precision) << duration.count(); return oss.str(); } } // Utils } // namespace Updater