* 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 <cerrno>
#include <cinttypes>
#include <sys/mount.h>
#include <sys/stat.h>
#include <dirent.h>
#include <vector>
#include <cstring>
#include <set>
#include <string>
#include <thread>
#include <atomic>
#include <chrono>
#include <algorithm>
#include <queue>
#include <mutex>
#include <functional>
#include "sandbox_unlock_mount.h"
#include "sandbox_shared_mount.h"
#include "sandbox_common.h"
#include "appspawn_utils.h"
#include "appspawn_server.h"
#include "appspawn_manager.h"
#include "parameter.h"
#ifdef APPSPAWN_HISYSEVENT
#include "hisysevent_adapter.h"
#endif
#ifndef APPSPAWN_SANDBOX_NEW
constexpr const char *USER_ID_PLACEHOLDER = "<userId>";
constexpr const char *BUNDLE_NAME_PLACEHOLDER = "<bundleName>";
#define DIR_MODE 0711
* @brief Replace placeholders in path template
* @param templateStr Path template containing <userId> and <bundleName>
* @param userId User ID string
* @param bundleName Bundle name
* @return Path with placeholders replaced
*/
APPSPAWN_STATIC std::string ReplacePlaceholders(const std::string &templateStr, const std::string &userId,
const std::string &bundleName)
{
std::string result = templateStr;
size_t pos = 0;
while ((pos = result.find(USER_ID_PLACEHOLDER, pos)) != std::string::npos) {
result.replace(pos, strlen(USER_ID_PLACEHOLDER), userId);
pos += userId.length();
}
pos = 0;
while ((pos = result.find(BUNDLE_NAME_PLACEHOLDER, pos)) != std::string::npos) {
result.replace(pos, strlen(BUNDLE_NAME_PLACEHOLDER), bundleName);
pos += bundleName.length();
}
return result;
}
struct MountTask {
size_t configIndex;
size_t bundleIndex;
};
struct MountQueueContext {
std::queue<MountTask> taskQueue;
std::mutex queueMutex;
const UnlockMountEntry *mountConfig;
std::vector<LockBundleInfo> bundles;
std::atomic<int> successCount{0};
std::atomic<int> failCount{0};
std::atomic<int> skipCount{0};
unsigned int threadCount;
};
APPSPAWN_STATIC MountEntryResult MountSingleConfigEntry(const UnlockMountEntry &config, const LockBundleInfo &bundle)
{
MountEntryResult result = {0};
std::string userIdStr = std::to_string(bundle.uid / UID_BASE);
std::string srcPath = ReplacePlaceholders(config.srcPath, userIdStr, bundle.bundleName);
std::string destPath = ReplacePlaceholders(config.destPath, userIdStr, bundle.bundleName);
std::string fullDestPath = bundle.lockPath + destPath;
APPSPAWN_ONLY_EXPER(!fullDestPath.empty() && fullDestPath.back() == '/',
fullDestPath.pop_back());
APPSPAWN_ONLY_EXPER(access(fullDestPath.c_str(), F_OK) != 0,
APPSPAWN_LOGV("Dest path %{public}s not exist, skip", fullDestPath.c_str());
result.skip++;
return result);
SharedMountArgs arg = {
.srcPath = srcPath.c_str(),
.destPath = fullDestPath.c_str(),
.fsType = nullptr,
.mountFlags = MS_BIND | MS_REC,
.options = nullptr,
.mountSharedFlag = MS_SLAVE
};
int ret = DoSharedMount(&arg);
if (ret != 0) {
APPSPAWN_ONLY_EXPER(errno == ENOENT, OHOS::AppSpawn::SandboxCommon::VerifyDirRecursive(srcPath));
APPSPAWN_LOGW("mount %{public}s to %{public}s failed, ret=%{public}d, errno=%{public}d",
srcPath.c_str(), fullDestPath.c_str(), ret, errno);
APPSPAWN_KLOGE("UnlockMount: mount %{public}s to %{public}s failed", srcPath.c_str(), fullDestPath.c_str());
APPSPAWN_KLOGE("uid=%{public}d, ret=%{public}d, errno=%{public}d", bundle.uid, ret, errno);
result.fail++;
#ifdef APPSPAWN_HISYSEVENT
ReportUnlockMountAppFail(bundle.uid, bundle.bundleName.c_str(),
srcPath.c_str(), fullDestPath.c_str(), ret);
#endif
return result;
}
APPSPAWN_LOGV("Mounted %{public}s to %{public}s successfully", srcPath.c_str(), fullDestPath.c_str());
result.success++;
return result;
}
APPSPAWN_STATIC void MountQueueWorkerThread(MountQueueContext &ctx, unsigned int workerId)
{
size_t processedTasks = 0;
MountTask task;
while (true) {
{
std::lock_guard<std::mutex> lock(ctx.queueMutex);
if (ctx.taskQueue.empty()) {
break;
}
task = ctx.taskQueue.front();
ctx.taskQueue.pop();
}
const LockBundleInfo &bundle = ctx.bundles[task.bundleIndex];
auto r = MountSingleConfigEntry(ctx.mountConfig[task.configIndex], bundle);
ctx.successCount.fetch_add(r.success, std::memory_order_relaxed);
ctx.failCount.fetch_add(r.fail, std::memory_order_relaxed);
ctx.skipCount.fetch_add(r.skip, std::memory_order_relaxed);
processedTasks++;
}
APPSPAWN_LOGV("MountQueueWorkerThread[%{public}u]: processed %{public}zu tasks",
workerId, processedTasks);
}
* @brief Create and initialize mount queue context with all resources
* @return nullptr if no apps to mount, otherwise the context
*/
APPSPAWN_STATIC std::unique_ptr<MountQueueContext> CreateMountContext(int uid)
{
std::vector<LockBundleInfo> bundles = GetAllLockBundles();
std::vector<LockBundleInfo> filteredBundles;
for (const auto &bundle : bundles) {
APPSPAWN_ONLY_EXPER(bundle.uid / UID_BASE != static_cast<uint32_t>(uid), continue);
filteredBundles.push_back(bundle);
}
if (filteredBundles.empty()) {
APPSPAWN_LOGI("CreateMountContext: no apps to mount, uid=%{public}d", uid);
return nullptr;
}
#ifdef APPSPAWN_HISYSEVENT
ReportKeyEvent(UNLOCK_MOUNT_SCAN_DONE);
#endif
unsigned int threadCount = std::min(
static_cast<unsigned int>(filteredBundles.size()),
std::thread::hardware_concurrency() > 0 ? std::thread::hardware_concurrency() : 4
);
size_t configSize = 0;
const UnlockMountEntry* mountConfig = GetUnlockMountEntry(&configSize);
std::queue<MountTask> taskQueue;
for (size_t i = 0; i < configSize; i++) {
for (size_t j = 0; j < filteredBundles.size(); j++) {
taskQueue.push({i, j});
}
}
auto ctx = std::make_unique<MountQueueContext>();
ctx->taskQueue = std::move(taskQueue);
ctx->bundles = std::move(filteredBundles);
ctx->mountConfig = mountConfig;
ctx->threadCount = threadCount;
return ctx;
}
int DoSharedMountForUser(int uid)
{
APPSPAWN_LOGI("DoSharedMountForUser start, uid=%{public}d, g_lockBundleMap.size=%{public}zu",
uid, GetLockBundleMapSize());
auto mountStart = std::chrono::steady_clock::now();
auto ctx = CreateMountContext(uid);
if (ctx == nullptr) {
#ifdef APPSPAWN_HISYSEVENT
ReportKeyEvent(UNLOCK_MOUNT_NO_APPS);
#endif
return 0;
}
std::vector<std::thread> workers;
for (unsigned int i = 0; i < ctx->threadCount; i++) {
workers.emplace_back(MountQueueWorkerThread, std::ref(*ctx), i);
}
for (auto &worker : workers) {
APPSPAWN_ONLY_EXPER(worker.joinable(), worker.join());
}
auto mountEnd = std::chrono::steady_clock::now();
int64_t duration = std::chrono::duration_cast<std::chrono::milliseconds>(mountEnd - mountStart).count();
APPSPAWN_LOGI("DoSharedMountForUser done, %{public}d, %{public}d, %{public}d, %{public}d, %{public}" PRId64 " ms",
uid, ctx->successCount.load(), ctx->failCount.load(),
ctx->skipCount.load(), duration);
#ifdef APPSPAWN_HISYSEVENT
ReportUnlockMountResult(uid, static_cast<int32_t>(ctx->bundles.size()),
ctx->successCount.load(), ctx->failCount.load(), duration);
#endif
return 0;
}
* @brief 处理用户解锁挂载的钩子函数
* @param mgr AppSpawnMgr 实例
* @return 成功返回 0,失败返回负数
*/
int HandleUnlockMountForUser(AppSpawnMgr *mgr)
{
if (mgr == nullptr) {
return APPSPAWN_ARG_INVALID;
}
AppSpawnContent *content = &mgr->content;
int uid = content->currentUnlockUid;
if (uid <= 0) {
APPSPAWN_LOGE("Invalid uid for unlock mount");
return APPSPAWN_ARG_INVALID;
}
return DoSharedMountForUser(uid);
}
MODULE_CONSTRUCTOR(void)
{
(void)AddServerStageHook(STAGE_SERVER_LOCK, HOOK_PRIO_COMMON, HandleUnlockMountForUser);
APPSPAWN_LOGI("RegisterUnlockMountHook: unlock mount hook registered");
}
#endif