* Copyright (c) 2024 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 <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "appspawn_adapter.h"
#include "appspawn_hook.h"
#include "appspawn_manager.h"
#include "appspawn_utils.h"
#include "securec.h"
#include "parameter.h"
#define APPSPAWN_TAG_SUFFIX "_apptag"
#define NWEB_TAG_SUFFIX "_nwebtag"
#define NATIVE_TAG_SUFFIX "_nativetag"
#define CJ_TAG_SUFFIX "_cjtag"
#define HYBRID_TAG_SUFFIX "_hybridtag"
#define CGROUP_ROOT_PATH "/dev/pids/"
#define UID_DIR_MIN_LEN 3
#define UID_DIR_MAX_LEN 10
#define APP_DIR_PREFIX "app_"
#define APP_DIR_PREFIX_LEN 4
#define DEFAULT_UID "0"
#define APPSPAWN_GRACEFUL_STOP_PARAM "startup.appspawn.graceful_stop.%s"
#define APPSPAWN_GRACEFUL_STOP_PARAM_LEN 64
#define CLEANUP_FLAG_NEED "1"
#define CLEANUP_FLAG_NONE "0"
#define PARAM_VALUE_MAX_LEN 96
#define DEVICE_CTL_PARAM "startup.device.ctl"
#define DEVICE_CMD_STOP "stop"
typedef struct {
RunMode mode;
const char *tagSuffix;
const char *serverName;
} SpawnModeInfo;
static const SpawnModeInfo G_SPAWN_MODE_MAP[] = {
{MODE_FOR_APP_SPAWN, APPSPAWN_TAG_SUFFIX, APPSPAWN_SERVER_NAME },
{MODE_FOR_NWEB_SPAWN, NWEB_TAG_SUFFIX, NWEBSPAWN_SERVER_NAME },
{MODE_FOR_NATIVE_SPAWN, NATIVE_TAG_SUFFIX, NATIVESPAWN_SERVER_NAME },
{MODE_FOR_CJAPP_SPAWN, CJ_TAG_SUFFIX, CJAPPSPAWN_SERVER_NAME },
{MODE_FOR_HYBRID_SPAWN, HYBRID_TAG_SUFFIX, HYBRIDSPAWN_SERVER_NAME },
};
static const size_t G_SPAWN_MODE_MAP_SIZE = sizeof(G_SPAWN_MODE_MAP) / sizeof(G_SPAWN_MODE_MAP[0]);
static const SpawnModeInfo *GetSpawnModeInfo(const AppSpawnMgr *content)
{
APPSPAWN_CHECK_ONLY_EXPER(content != NULL, return NULL);
RunMode mode = content->content.mode;
for (size_t i = 0; i < G_SPAWN_MODE_MAP_SIZE; i++) {
if (G_SPAWN_MODE_MAP[i].mode == mode) {
return &G_SPAWN_MODE_MAP[i];
}
}
return NULL;
}
APPSPAWN_STATIC int GetCgroupPath(const AppSpawnedProcessInfo *appInfo, char *buffer, uint32_t buffLen)
{
const SpawnModeInfo *modeInfo = GetSpawnModeInfo(GetAppSpawnMgr());
APPSPAWN_CHECK(modeInfo != NULL, return -1, "Failed to get spawn mode info");
const char *tagSuffix = modeInfo->tagSuffix;
const int userId = appInfo->uid / UID_BASE;
#ifdef APPSPAWN_TEST
int ret = snprintf_s(buffer, buffLen, buffLen - 1, APPSPAWN_BASE_DIR "/dev/pids/testpids/%d/%s%s/app_%d/",
userId, appInfo->name, tagSuffix, appInfo->pid);
#else
int ret = snprintf_s(buffer, buffLen, buffLen - 1, "/dev/pids/%d/%s%s/app_%d/",
userId, appInfo->name, tagSuffix, appInfo->pid);
#endif
APPSPAWN_CHECK(ret > 0, return ret, "Failed to snprintf_s errno: %{public}d", errno);
APPSPAWN_LOGV("Cgroup path %{public}s ", buffer);
return 0;
}
APPSPAWN_STATIC int WriteToFile(const char *path, int truncated, pid_t pids[], uint32_t count)
{
char pidName[32] = {0};
int fd = open(path, O_RDWR | (truncated ? O_TRUNC : O_APPEND));
APPSPAWN_CHECK(fd >= 0, return APPSPAWN_SYSTEM_ERROR,
"Failed to open file errno: %{public}d path: %{public}s", errno, path);
int ret = 0;
for (uint32_t i = 0; i < count; i++) {
APPSPAWN_LOGV(" WriteToFile pid %{public}d ", pids[i]);
ret = snprintf_s(pidName, sizeof(pidName), sizeof(pidName) - 1, "%d\n", pids[i]);
APPSPAWN_CHECK(ret > 0, break, "Failed to snprintf_s errno: %{public}d", errno);
ret = write(fd, pidName, strlen(pidName));
APPSPAWN_CHECK(ret > 0, break,
"Failed to write file errno: %{public}d path: %{public}s %{public}s", errno, path, pidName);
ret = 0;
}
close(fd);
return ret;
}
APPSPAWN_STATIC void SetForkDeniedByPath(const char *appDirPath)
{
char pathForkDenied[PATH_MAX] = {};
int ret = snprintf_s(pathForkDenied, sizeof(pathForkDenied), sizeof(pathForkDenied) - 1,
"%s/pids.fork_denied", appDirPath);
APPSPAWN_CHECK(ret > 0, return, "Failed to build fork_denied path for %{public}s", appDirPath);
int fd = open(pathForkDenied, O_RDWR);
APPSPAWN_CHECK(fd >= 0, return,
"Failed to open fork_denied for %{public}s errno: %{public}d", appDirPath, errno);
do {
ret = write(fd, "1", 1);
APPSPAWN_CHECK(ret >= 0, break,
"Failed to write file errno: %{public}d path: %{public}s %{public}d", errno, pathForkDenied, ret);
APPSPAWN_CHECK_ONLY_LOG(fsync(fd) != -1, "Failed to fsync for target: %{public}d", errno);
APPSPAWN_LOGI("SetForkDenied success for %{public}s", appDirPath);
} while (0);
close(fd);
}
static void SetForkDenied(const AppSpawnedProcessInfo *appInfo)
{
char pathForkDenied[PATH_MAX] = {};
int ret = GetCgroupPath(appInfo, pathForkDenied, sizeof(pathForkDenied));
APPSPAWN_CHECK(ret == 0, return, "Failed to get cgroup path errno: %{public}d", errno);
size_t len = strlen(pathForkDenied);
APPSPAWN_ONLY_EXPER(len > 0 && pathForkDenied[len - 1] == '/', pathForkDenied[len - 1] = '\0');
SetForkDeniedByPath(pathForkDenied);
}
static void KillProcessesByCGroup(const char *path, AppSpawnMgr *content, const AppSpawnedProcessInfo *appInfo)
{
SetForkDenied(appInfo);
FILE *file = fopen(path, "r");
APPSPAWN_CHECK(file != NULL, return, "Open file fail %{public}s errno: %{public}d", path, errno);
pid_t pid = 0;
while (fscanf_s(file, "%d\n", &pid) == 1 && pid > 0) {
APPSPAWN_LOGV(" KillProcessesByCGroup pid %{public}d ", pid);
if (pid == appInfo->pid) {
continue;
}
AppSpawnedProcessInfo *tmp = GetSpawnedProcess(pid);
if (tmp != NULL) {
APPSPAWN_LOGI("Got app %{public}s in same group for pid %{public}d.", tmp->name, pid);
continue;
}
APPSPAWN_LOGI("Kill app pid %{public}d now ...", pid);
#ifndef APPSPAWN_TEST
if (kill(pid, SIGKILL) != 0) {
APPSPAWN_LOGE("unable to kill process, pid: %{public}d ret %{public}d", pid, errno);
}
#endif
}
(void)fclose(file);
}
APPSPAWN_STATIC int ProcessMgrRemoveApp(const AppSpawnMgr *content, const AppSpawnedProcessInfo *appInfo)
{
APPSPAWN_CHECK_ONLY_EXPER(content != NULL, return -1);
APPSPAWN_CHECK_ONLY_EXPER(appInfo != NULL, return -1);
if (IsNWebSpawnMode(content) || strcmp(appInfo->name, NWEBSPAWN_SERVER_NAME) == 0) {
return 0;
}
char cgroupPath[PATH_MAX] = {};
APPSPAWN_LOGV("ProcessMgrRemoveApp %{public}d %{public}d to cgroup ", appInfo->pid, appInfo->uid);
int ret = GetCgroupPath(appInfo, cgroupPath, sizeof(cgroupPath));
APPSPAWN_CHECK(ret == 0, return -1, "Failed to get real path errno: %{public}d", errno);
char procPath[PATH_MAX] = {};
ret = memcpy_s(procPath, sizeof(procPath), cgroupPath, sizeof(cgroupPath));
if (ret != 0) {
return APPSPAWN_ERROR_UTILS_MEM_FAIL;
}
ret = strcat_s(procPath, sizeof(procPath), "cgroup.procs");
APPSPAWN_CHECK(ret == 0, return ret, "Failed to strcat_s errno: %{public}d", errno);
KillProcessesByCGroup(procPath, (AppSpawnMgr *)content, appInfo);
ret = rmdir(cgroupPath);
APPSPAWN_CHECK(ret == 0, return APPSPAWN_ERROR_FILE_RMDIR_FAIL,
"Failed to rmdir in ProcessMgrRemoveApp %{public}s errno: %{public}d", cgroupPath, errno);
return ret;
}
APPSPAWN_STATIC int IsUidDir(const char *name)
{
if (name == NULL || *name == '\0') {
return 0;
}
if (strcmp(name, DEFAULT_UID) == 0) {
return 1;
}
size_t len = strlen(name);
if (len < UID_DIR_MIN_LEN || len > UID_DIR_MAX_LEN) {
return 0;
}
for (size_t i = 0; i < len; i++) {
if (name[i] < '0' || name[i] > '9') {
return 0;
}
}
return 1;
}
APPSPAWN_STATIC int IsTagDir(const char *name)
{
APPSPAWN_CHECK_ONLY_EXPER(name != NULL && *name != '\0', return 0);
const SpawnModeInfo *modeInfo = GetSpawnModeInfo(GetAppSpawnMgr());
APPSPAWN_CHECK(modeInfo != NULL, return 0, "Failed to get spawn mode info");
const char *suffix = modeInfo->tagSuffix;
size_t nameLen = strlen(name);
size_t suffixLen = strlen(suffix);
if (nameLen <= suffixLen) {
return 0;
}
return strcmp(name + nameLen - suffixLen, suffix) == 0;
}
APPSPAWN_STATIC int IsAppDir(const char *name)
{
APPSPAWN_CHECK_ONLY_EXPER(name != NULL && *name != '\0', return 0);
size_t prefixLen = APP_DIR_PREFIX_LEN;
APPSPAWN_CHECK_ONLY_EXPER(strncmp(name, APP_DIR_PREFIX, prefixLen) == 0, return 0);
for (const char *p = name + prefixLen; *p != '\0'; p++) {
APPSPAWN_CHECK_ONLY_EXPER(*p >= '0' && *p <= '9', return 0);
}
return *(name + prefixLen) != '\0';
}
static void KillOrphanedProcessesInFile(const char *procPath, const char *appDirName)
{
FILE *file = fopen(procPath, "r");
APPSPAWN_CHECK(file != NULL, return,
"Failed to open %{public}s errno: %{public}d", procPath, errno);
pid_t pid = 0;
while (fscanf_s(file, "%d\n", &pid) == 1 && pid > 0) {
APPSPAWN_LOGI("Kill orphaned cgroup child pid %{public}d in %{public}s", pid, appDirName);
APPSPAWN_CHECK(kill(pid, SIGKILL) == 0, continue,
"Unable to kill process, pid: %{public}d errno: %{public}d", pid, errno);
}
(void)fclose(file);
}
static void CleanupOrphanedAppDir(const char *tagDirPath, const char *appDirName)
{
char appDirPath[PATH_MAX] = {};
int ret = snprintf_s(appDirPath, sizeof(appDirPath), sizeof(appDirPath) - 1,
"%s/%s", tagDirPath, appDirName);
APPSPAWN_CHECK(ret > 0, return,
"Failed to build app dir path for %{public}s/%{public}s", tagDirPath, appDirName);
SetForkDeniedByPath(appDirPath);
char procPath[PATH_MAX] = {};
ret = snprintf_s(procPath, sizeof(procPath), sizeof(procPath) - 1,
"%s/cgroup.procs", appDirPath);
APPSPAWN_CHECK(ret > 0, return,
"Failed to build proc path for %{public}s/%{public}s", tagDirPath, appDirName);
KillOrphanedProcessesInFile(procPath, appDirName);
char filePath[PATH_MAX] = {};
ret = snprintf_s(filePath, sizeof(filePath), sizeof(filePath) - 1,
"%s/cgroup.procs", appDirPath);
APPSPAWN_ONLY_EXPER(ret > 0, (void)unlink(filePath));
ret = snprintf_s(filePath, sizeof(filePath), sizeof(filePath) - 1,
"%s/pids.fork_denied", appDirPath);
APPSPAWN_ONLY_EXPER(ret > 0, (void)unlink(filePath));
APPSPAWN_CHECK(rmdir(appDirPath) == 0, return,
"Failed to rmdir %{public}s errno: %{public}d", appDirPath, errno);
}
APPSPAWN_STATIC void CleanupOrphanedTagDir(const char *uidPath, const char *dirName)
{
APPSPAWN_CHECK_ONLY_EXPER(IsTagDir(dirName), return);
char tagDirPath[PATH_MAX] = {};
int ret = snprintf_s(tagDirPath, sizeof(tagDirPath), sizeof(tagDirPath) - 1, "%s/%s", uidPath, dirName);
APPSPAWN_CHECK(ret > 0, return, "Failed to build tag dir path for %{public}s", dirName);
DIR *tagDir = opendir(tagDirPath);
APPSPAWN_CHECK(tagDir != NULL, return,
"Failed to open tag dir %{public}s errno: %{public}d", tagDirPath, errno);
struct dirent *appDp = NULL;
while ((appDp = readdir(tagDir)) != NULL) {
if (appDp->d_type != DT_DIR || !IsAppDir(appDp->d_name)) {
continue;
}
CleanupOrphanedAppDir(tagDirPath, appDp->d_name);
}
(void)closedir(tagDir);
ret = rmdir(tagDirPath);
APPSPAWN_ONLY_EXPER(ret != 0 && errno != ENOENT,
APPSPAWN_LOGW("Failed to rmdir tag dir %{public}s errno: %{public}d", tagDirPath, errno));
}
APPSPAWN_STATIC void CleanupOrphanedCgroupProcesses(void)
{
DIR *dir = opendir(CGROUP_ROOT_PATH);
APPSPAWN_CHECK_ONLY_EXPER(dir != NULL, return);
struct dirent *dp = NULL;
while ((dp = readdir(dir)) != NULL) {
if (dp->d_type != DT_DIR || strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) {
continue;
}
if (!IsUidDir(dp->d_name)) {
continue;
}
char uidPath[PATH_MAX] = {};
int ret = snprintf_s(uidPath, sizeof(uidPath), sizeof(uidPath) - 1, "%s%s", CGROUP_ROOT_PATH, dp->d_name);
APPSPAWN_CHECK(ret > 0, continue, "Failed to build uid path for %{public}s", dp->d_name);
DIR *uidDir = opendir(uidPath);
APPSPAWN_CHECK(uidDir != NULL, continue,
"Failed to open uid dir %{public}s errno: %{public}d", uidPath, errno);
struct dirent *entry = NULL;
while ((entry = readdir(uidDir)) != NULL) {
if (entry->d_type != DT_DIR || strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
CleanupOrphanedTagDir(uidPath, entry->d_name);
}
(void)closedir(uidDir);
}
(void)closedir(dir);
}
APPSPAWN_STATIC int CgroupPreloadHook(AppSpawnMgr *content)
{
APPSPAWN_ONLY_EXPER(IsColdRunMode(content), return 0);
const SpawnModeInfo *modeInfo = GetSpawnModeInfo(content);
APPSPAWN_CHECK(modeInfo != NULL, return -1, "Failed to get spawn mode info");
char paramName[APPSPAWN_GRACEFUL_STOP_PARAM_LEN] = {};
int ret = snprintf_s(paramName, sizeof(paramName), sizeof(paramName) - 1,
APPSPAWN_GRACEFUL_STOP_PARAM, modeInfo->serverName);
APPSPAWN_CHECK(ret > 0, return -1, "Failed to build graceful stop param name");
char value[2] = {};
ret = GetParameter(paramName, CLEANUP_FLAG_NONE, value, sizeof(value));
APPSPAWN_ONLY_EXPER(ret > 0 && strcmp(value, CLEANUP_FLAG_NEED) == 0,
APPSPAWN_LOGI("Appspawn abnormal stop detected, cleanup orphaned cgroup processes");
CleanupOrphanedCgroupProcesses());
SetParameter(paramName, CLEANUP_FLAG_NEED);
return 0;
}
APPSPAWN_STATIC int CgroupExitHook(AppSpawnMgr *content)
{
char deviceValue[PARAM_VALUE_MAX_LEN] = {};
int deviceRet = GetParameter(DEVICE_CTL_PARAM, "false", deviceValue, sizeof(deviceValue));
APPSPAWN_ONLY_EXPER(deviceRet > 0 && strcmp(DEVICE_CMD_STOP, deviceValue) == 0,
APPSPAWN_LOGI("current status is %{public}s", deviceValue);
return 0);
const SpawnModeInfo *modeInfo = GetSpawnModeInfo(content);
APPSPAWN_CHECK(modeInfo != NULL, return -1, "Failed to get spawn mode info");
char paramName[APPSPAWN_GRACEFUL_STOP_PARAM_LEN] = {};
int ret = snprintf_s(paramName, sizeof(paramName), sizeof(paramName) - 1,
APPSPAWN_GRACEFUL_STOP_PARAM, modeInfo->serverName);
APPSPAWN_CHECK(ret > 0, return 0, "Failed to build graceful stop param name");
SetParameter(paramName, CLEANUP_FLAG_NONE);
APPSPAWN_LOGI("Clear abnormal stop flag before appspawn exit");
return 0;
}
APPSPAWN_STATIC int ProcessMgrAddApp(const AppSpawnMgr *content, const AppSpawnedProcessInfo *appInfo)
{
APPSPAWN_CHECK_ONLY_EXPER(content != NULL, return -1);
APPSPAWN_CHECK_ONLY_EXPER(appInfo != NULL, return -1);
APPSPAWN_ONLY_EXPER(IsNWebSpawnMode(content), return 0);
char path[PATH_MAX] = {};
APPSPAWN_LOGV("ProcessMgrAddApp %{public}d %{public}d to cgroup ", appInfo->pid, appInfo->uid);
int ret = GetCgroupPath(appInfo, path, sizeof(path));
APPSPAWN_CHECK(ret == 0, return -1, "Failed to get real path errno: %{public}d", errno);
(void)CreateSandboxDir(path, 0755);
ret = strcat_s(path, sizeof(path), "cgroup.procs");
APPSPAWN_CHECK(ret == 0, return ret, "Failed to strcat_s errno: %{public}d", errno);
ret = WriteToFile(path, 0, (pid_t *)&appInfo->pid, 1);
APPSPAWN_CHECK(ret == 0, return ret, "write pid to cgroup.procs fail %{public}s", path);
APPSPAWN_LOGV("Add app %{public}d to cgroup %{public}s success", appInfo->pid, path);
return 0;
}
MODULE_CONSTRUCTOR(void)
{
AddServerStageHook(STAGE_SERVER_PRELOAD, 0, CgroupPreloadHook);
AddServerStageHook(STAGE_SERVER_EXIT, 0, CgroupExitHook);
AddProcessMgrHook(STAGE_SERVER_APP_ADD, 0, ProcessMgrAddApp);
AddProcessMgrHook(STAGE_SERVER_APP_DIED, 0, ProcessMgrRemoveApp);
}