* Copyright (c) 2026 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 "decompress.h"
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <dlfcn.h>
#include <fcntl.h>
#include <climits>
#include <mutex>
#include <sys/stat.h>
#include <unordered_set>
#include <unistd.h>
#include <securec.h>
#include "fuse_ioctl.h"
#include "utils_log.h"
namespace OHOS {
namespace FileManagement {
namespace Decompress {
using namespace CloudFile;
enum FileType {
SO_FILE = 1,
RESOURCE_FILE = 2,
};
struct HmfsPdedupExtent {
uint64_t outerOff;
uint64_t innerOff;
uint64_t len;
}__attribute__((packed));
struct HmfsPdedupCreate {
int32_t version;
int32_t innerFd;
uint64_t fileSize;
uint32_t nrExtents;
int32_t mode;
char name[256];
struct HmfsPdedupExtent extents[];
};
namespace {
constexpr const char* PDEDUP_FEATURE_NODE_PATH = "/sys/fs/hmfs/userdata/support_pdedup";
constexpr size_t MAX_PATH_LENGTH = PATH_MAX;
constexpr const char* CLOUD_SYNC_LIB_PATH = "libcloudsync_kit_inner.z.so";
constexpr int MAX_RETRY = 2;
constexpr int RETRY_DELAY_US = 100000;
constexpr uint32_t MAX_UNCOMPATIBLE_TARGET_VERSION = 20;
constexpr uint32_t API_VERSION_MOD = 1000;
}
using GetUnsupportedListFunc = int32_t(*)(std::vector<std::string>*);
using GetSystemFeatureFunc = int32_t(*)(bool*);
class CloudSyncLibraryManager {
public:
static CloudSyncLibraryManager& GetInstance()
{
static CloudSyncLibraryManager instance;
return instance;
}
void* GetHandle() const { return handle_; }
bool IsLoaded() const { return handle_ != nullptr; }
GetUnsupportedListFunc GetUnsupportedListSymbol()
{
return GetSymbolFunc<GetUnsupportedListFunc>("CloudSync_GetDecompressUnsupportedList");
}
GetSystemFeatureFunc GetSystemFeatureSymbol()
{
return GetSymbolFunc<GetSystemFeatureFunc>("CloudSync_GetDecompressSystemFeature");
}
private:
CloudSyncLibraryManager()
{
handle_ = dlopen(CLOUD_SYNC_LIB_PATH, RTLD_LAZY | RTLD_LOCAL | RTLD_NODELETE);
if (handle_ == nullptr) {
LOGE("dlopen failed for %{public}s: %{public}s", CLOUD_SYNC_LIB_PATH, dlerror());
}
}
~CloudSyncLibraryManager()
{
}
template<typename FuncType>
FuncType GetSymbolFunc(const char* symbol)
{
if (handle_ == nullptr) {
return nullptr;
}
auto func = reinterpret_cast<FuncType>(dlsym(handle_, symbol));
if (func == nullptr) {
LOGE("dlsym %{public}s failed: %{public}s", symbol, dlerror());
}
return func;
}
void* handle_ = nullptr;
};
static bool SplitTargetPath(const std::string &targetPath, std::string &dirPath, std::string &fileName)
{
size_t lastSlash = targetPath.find_last_of('/');
if (lastSlash == std::string::npos) {
LOGE("Target path must be absolute path: %{public}s", targetPath.c_str());
return false;
}
dirPath = targetPath.substr(0, lastSlash);
fileName = targetPath.substr(lastSlash + 1);
return true;
}
static bool ValidateSourcePath(const std::string &path)
{
if (path.empty() || path.length() > MAX_PATH_LENGTH) {
LOGE("Source path is empty or too long: %{public}zu", path.length());
return false;
}
char resolvedPath[PATH_MAX] = {0};
if (realpath(path.c_str(), resolvedPath) == nullptr) {
LOGE("realpath failed for source path: %{public}s, errno: %{public}d",
path.c_str(), errno);
return false;
}
return true;
}
static bool ValidateTargetPath(const std::string &targetPath)
{
if (targetPath.empty() || targetPath.length() > MAX_PATH_LENGTH) {
LOGE("Target path is empty or too long: %{public}zu", targetPath.length());
return false;
}
std::string dirPath;
std::string fileName;
if (!SplitTargetPath(targetPath, dirPath, fileName)) {
return false;
}
char resolvedDirPath[PATH_MAX] = {0};
if (realpath(dirPath.c_str(), resolvedDirPath) == nullptr) {
LOGE("realpath failed for target directory: %{public}s, errno: %{public}d",
dirPath.c_str(), errno);
return false;
}
return true;
}
static int OpenSourceFile(const std::string &sourcePath)
{
int innerFd = open(sourcePath.c_str(), O_RDONLY);
if (innerFd < 0) {
LOGE("Failed to open source file: %{public}s, errno: %{public}d", sourcePath.c_str(), errno);
return -1;
}
return innerFd;
}
static struct HmfsPdedupCreate *PreparePdedupCreate(int innerFd,
const std::string &fileName,
uint32_t offset, uint32_t size)
{
size_t createSize = sizeof(struct HmfsPdedupCreate) + sizeof(struct HmfsPdedupExtent);
struct HmfsPdedupCreate *create = static_cast<struct HmfsPdedupCreate *>(malloc(createSize));
if (!create) {
LOGE("Failed to allocate memory for HmfsPdedupCreate");
return nullptr;
}
errno_t ret = memset_s(create, createSize, 0, createSize);
if (ret != EOK) {
LOGE("memset_s failed: %{public}d", ret);
free(create);
return nullptr;
}
create->version = 1;
create->innerFd = innerFd;
create->fileSize = size;
create->nrExtents = 1;
create->mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
ret = strncpy_s(create->name, sizeof(create->name), fileName.c_str(), fileName.length());
if (ret != EOK) {
LOGE("strncpy_s failed: %{public}d", ret);
free(create);
return nullptr;
}
create->extents[0].outerOff = 0;
create->extents[0].innerOff = offset;
create->extents[0].len = size;
return create;
}
static bool ExecutePdedupCreate(int dirFd, struct HmfsPdedupCreate *create,
const std::string &targetPath, uint32_t offset, uint32_t size)
{
for (int i = 0; i < MAX_RETRY; i++) {
int ioctlRet = ioctl(dirFd, HMFS_IOC_PDEDLUP_CREATE, create);
if (ioctlRet >= 0) {
LOGD("Successfully created pdedup file: %{public}s, offset: %{public}u, size: %{public}u",
targetPath.c_str(), offset, size);
return true;
}
if (errno == EINTR || errno == EAGAIN || errno == EBUSY || errno == ENOMEM) {
LOGW("ioctl retry %{public}d/%{public}d for %{public}s, errno: %{public}d",
i + 1, MAX_RETRY, targetPath.c_str(), errno);
usleep(RETRY_DELAY_US);
continue;
}
break;
}
LOGE("Failed to create pdedup for %{public}s, offset: %{public}u, errno: %{public}d",
targetPath.c_str(), offset, errno);
return false;
}
bool CreateInnerFile(const std::string &sourcePath, const std::string &targetPath,
const uint32_t &offset, const uint32_t &size, bool isSystemApp)
{
LOGD("CreateInnerFile start, sourcePath: %{public}s, targetPath: %{public}s, offset: %{public}u, size: %{public}u",
sourcePath.c_str(), targetPath.c_str(), offset, size);
if (!ValidateSourcePath(sourcePath)) {
LOGE("Invalid source path: %{public}s", sourcePath.c_str());
return false;
}
if (!ValidateTargetPath(targetPath)) {
LOGE("Invalid target path: %{public}s", targetPath.c_str());
return false;
}
int innerFd = OpenSourceFile(sourcePath);
if (innerFd < 0) {
return false;
}
std::string dirPath;
std::string fileName;
if (!SplitTargetPath(targetPath, dirPath, fileName)) {
close(innerFd);
return false;
}
LOGD("SplitTargetPath result, dirPath: %{public}s, fileName: %{public}s", dirPath.c_str(), fileName.c_str());
int dirFd = open(dirPath.c_str(), O_RDONLY | O_DIRECTORY);
if (dirFd < 0) {
LOGE("Failed to open directory: %{public}s, errno: %{public}d", dirPath.c_str(), errno);
close(innerFd);
return false;
}
struct HmfsPdedupCreate *create = PreparePdedupCreate(innerFd, fileName, offset, size);
if (!create) {
close(dirFd);
close(innerFd);
return false;
}
bool success = ExecutePdedupCreate(dirFd, create, targetPath, offset, size);
LOGD("ExecutePdedupCreate result: %{public}s", success ? "true" : "false");
free(create);
close(dirFd);
close(innerFd);
return success;
}
bool CheckBundleSupported(const std::string &bundleName, const bool isKeepAlive)
{
if (isKeepAlive) {
LOGD("Bundle %{public}s iskeepAlive is true", bundleName.c_str());
return false;
}
static std::unordered_set<std::string> unsupportedCache;
static std::once_flag initFlag;
std::call_once(initFlag, []() {
auto& manager = CloudSyncLibraryManager::GetInstance();
if (!manager.IsLoaded()) {
LOGE("Failed to load cloud sync library");
return;
}
auto func = manager.GetUnsupportedListSymbol();
if (func != nullptr) {
std::vector<std::string> unsupportedList;
int32_t ret = func(&unsupportedList);
if (ret == 0) {
for (const auto& name : unsupportedList) {
unsupportedCache.insert(name);
}
LOGD("Cached unsupported list: %{public}zu items", unsupportedCache.size());
} else {
LOGE("Get unsupported list failed, ret: %{public}d", ret);
}
}
});
bool supported = unsupportedCache.find(bundleName) == unsupportedCache.end();
LOGD("Bundle %{public}s is %{public}s", bundleName.c_str(), supported ? "supported" : "not supported");
return supported;
}
bool CheckModuleApiVersionSupported(const std::string &moduleName,
const uint32_t targetVersion,
const uint32_t minVersion)
{
uint32_t apiVersion = targetVersion % API_VERSION_MOD;
LOGD("Module %{public}s apiVersion %{public}u, targetVersion: %{public}u, minVersion: %{public}u",
moduleName.c_str(), apiVersion, targetVersion, minVersion);
if (apiVersion <= MAX_UNCOMPATIBLE_TARGET_VERSION) {
return false;
}
return true;
}
static bool ReadPdedupFeatureNode()
{
FILE* file = fopen(PDEDUP_FEATURE_NODE_PATH, "r");
if (file == nullptr) {
LOGD("Failed to open pdedup feature node file: %{public}s", PDEDUP_FEATURE_NODE_PATH);
return false;
}
char* line = nullptr;
size_t len = 0;
ssize_t read = getline(&line, &len, file);
if (fclose(file) != 0) {
LOGE("Failed to close pdedup feature node file, errno: %{public}d", errno);
}
if (read == -1) {
LOGD("Failed to read pdedup feature node file");
if (line != nullptr) {
free(line);
}
return false;
}
bool supported = (line != nullptr && line[0] == '1');
LOGD("Pdedup feature node content: %{public}s, supported: %{public}s",
line ? line : "", supported ? "true" : "false");
if (line != nullptr) {
free(line);
}
return supported;
}
bool GetSystemFeature()
{
static bool cachedFeature = false;
static std::once_flag initFlag;
std::call_once(initFlag, []() {
bool nodeSupported = ReadPdedupFeatureNode();
if (!nodeSupported) {
LOGD("Pdedup feature node not supported");
return;
}
auto& manager = CloudSyncLibraryManager::GetInstance();
if (!manager.IsLoaded()) {
LOGE("Failed to load cloud sync library");
return;
}
auto func = manager.GetSystemFeatureSymbol();
if (func != nullptr) {
bool systemFeature = false;
int32_t ret = func(&systemFeature);
if (ret == 0) {
cachedFeature = systemFeature;
LOGD("System feature cached: %{public}s", cachedFeature ? "true" : "false");
} else {
LOGE("Get system feature via IPC failed, ret: %{public}d", ret);
}
}
});
return cachedFeature;
}
}
}
}