* 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 "fs_manager/mount.h"
#include <cerrno>
#include <fcntl.h>
#include <string>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <unordered_set>
#include <vector>
#include <linux/fs.h>
#include "log/dump.h"
#include "log/log.h"
#include "scope_guard.h"
#include "securec.h"
#include "updater/updater_const.h"
#include "utils.h"
#include "uncache.h"
namespace Updater {
using Updater::Utils::SplitString;
static std::string g_defaultUpdaterFstab = "";
static Fstab *g_fstab = nullptr;
static const std::string PARTITION_PATH = "/dev/block/by-name";
static const std::string USERDATA_BLOCK_DEVICE_PATH = "/dev/block/by-name/userdata";
static std::unordered_set<std::string> g_skipMountPointList = {"/", "/tmp", "/sdcard", INTERNAL_DATA_PATH};
static constexpr uint32_t LINK_BUFF_LEN = 256;
void AddSkipMountPoint(const std::string &mountPoint)
{
if (g_skipMountPointList.find(mountPoint) != g_skipMountPointList.end()) {
return;
}
LOG(INFO) << "add skip mount point " << mountPoint;
g_skipMountPointList.insert(mountPoint);
}
static std::string GetFstabFile()
{
std::vector<const std::string> specificFstabFiles = {"/vendor/etc/fstab.updater"};
for (auto& fstabFile : specificFstabFiles) {
if (access(fstabFile.c_str(), F_OK) == 0) {
return fstabFile;
}
}
return "";
}
#ifndef UPDATE_PATCH_SHARED
MountStatus GetMountStatusForPath(const std::string &path)
{
FstabItem *item = FindFstabItemForPath(*g_fstab, path.c_str());
if (item == nullptr) {
return MountStatus::MOUNT_ERROR;
}
return GetMountStatusForMountPoint(item->mountPoint);
}
#endif
bool IsDmDeviceLink()
{
char linkBuf[LINK_BUFF_LEN] = {0};
if (readlink(USERDATA_BLOCK_DEVICE_PATH.c_str(), linkBuf, LINK_BUFF_LEN - 1) <= 0) {
LOG(ERROR) << "readlink fail, errno:" << errno;
return false;
}
LOG(INFO) << "linkBuf is " << linkBuf;
std::string dmSuffix = "/dev/block/dm-";
return std::string(linkBuf).rfind(dmSuffix, 0) == 0;
}
void SetUserdataBlockDeviceSymlink()
{
if (IsDmDeviceLink()) {
return;
}
if (g_fstab == nullptr) {
LOG(ERROR) << "g_fstab is nullptr";
return;
}
for (FstabItem *item = g_fstab->head; item != nullptr; item = item->next) {
if (item->deviceName != USERDATA_BLOCK_DEVICE_PATH) {
continue;
}
if (UpdateUserDataMEDevice(item) != 0) {
LOG(ERROR) << "UpdateUserDataMEDevice failed";
}
break;
}
}
void CreateRawUserdataDevice()
{
const char *target = "/dev/block/by-name/raw-userdata";
if (access(target, F_OK) == 0) {
LOG(ERROR) << target << " exist";
return;
}
char linkPath[LINK_BUFF_LEN] = {0};
const char *sourcePath = "/dev/block/by-name/userdata";
ssize_t len = readlink(sourcePath, linkPath, sizeof(linkPath) - 1);
if (len <= 0 || len >= LINK_BUFF_LEN) {
LOG(ERROR) <<"Failed to readlink " << sourcePath << ", err = " << errno;
return;
}
linkPath[len] = '\0';
LOG(INFO) << "Resolved symlink: " << sourcePath << " -> " << linkPath;
if (symlink(linkPath, target) != 0) {
LOG(ERROR) <<"Failed to symlink " << linkPath << " to " << target << ", err = " << errno;
return;
}
LOG(INFO) << "Symlink " << linkPath << " -> " << target << " success";
}
void LoadFstab(const bool initBlockDevice)
{
LoadFstab();
if (initBlockDevice) {
CreateRawUserdataDevice();
SetUserdataBlockDeviceSymlink();
}
}
void LoadFstab()
{
std::string fstabFile = g_defaultUpdaterFstab;
if (fstabFile.empty()) {
fstabFile = GetFstabFile();
if (fstabFile.empty()) {
fstabFile = "/etc/fstab.updater";
}
}
if (g_fstab != nullptr) {
ReleaseFstab(g_fstab);
g_fstab = nullptr;
}
if ((g_fstab = ReadFstabFromFile(fstabFile.c_str(), false)) == nullptr) {
LOG(WARNING) << "Read " << fstabFile << " failed";
return;
}
LOG(DEBUG) << "Updater filesystem config info:";
for (FstabItem *item = g_fstab->head; item != nullptr; item = item->next) {
LOG(DEBUG) << "\tDevice: " << item->deviceName;
LOG(DEBUG) << "\tMount point : " << item->mountPoint;
LOG(DEBUG) << "\tFs type : " << item->fsType;
LOG(DEBUG) << "\tMount options: " << item->mountOptions;
}
}
void LoadSpecificFstab(const std::string &fstabName)
{
g_defaultUpdaterFstab = fstabName;
LoadFstab();
g_defaultUpdaterFstab = "";
}
int UmountForPath(const std::string& path)
{
if (g_fstab == nullptr) {
LOG(ERROR) << "fstab is not loaded, g_fstab is null.";
return -1;
}
FstabItem *item = FindFstabItemForPath(*g_fstab, path.c_str());
if (item == nullptr) {
LOG(ERROR) << "Cannot find fstab item for " << path << " to umount.";
return -1;
}
LOG(DEBUG) << "Umount for path " << path;
MountStatus rc = GetMountStatusForMountPoint(item->mountPoint);
if (rc == MOUNT_ERROR) {
return -1;
} else if (rc == MOUNT_UMOUNTED) {
return 0;
} else {
if (path == "/data") {
Utils::SetParameter("updater.data.ready", "0");
}
int ret = umount(item->mountPoint);
if (ret == -1) {
LOG(ERROR) << "Umount " << item->mountPoint << "failed: " << errno;
return -1;
}
}
return 0;
}
static int LoopToMount(char *argv[], std::string source, std::string target)
{
int num = 0;
do {
pid_t child = fork();
if (child == 0) {
if (execv(argv[0], argv)) {
_exit(-1);
}
}
int status = -1;
if (waitpid(child, &status, 0) < 0) {
LOG(ERROR) << "waitpid failed, " << child;
}
if (WIFEXITED(status)) {
LOG(ERROR) << "child terminated by exit " << WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
LOG(ERROR) << "child terminated by signal " << WTERMSIG(status);
} else if (WIFSTOPPED(status)) {
LOG(ERROR) << "child stopped by signal " << WSTOPSIG(status);
}
if (status == 0) {
Utils::UsSleep(100);
LOG(INFO) << "success to mount " << source << " on " << target;
return 0;
} else {
if ((errno == ENOENT) || (errno == ENODEV) || (errno == ENOMEDIUM)) {
LOG(ERROR) << "SD card never insert, dont try again, failed to mount " << source << " on " << target;
return -1;
}
}
num++;
LOG(ERROR) << "failed to mount " << source << " on " << target << ", errno is " << errno;
} while (num < 3);
return -1;
}
static int MountNtfsWithRetry(std::string source, std::string target)
{
char *argv[] = {const_cast<char *>("system/bin/mount.ntfs"),
const_cast<char *>(source.c_str()), const_cast<char *>(target.c_str()), nullptr};
return LoopToMount(argv, source, target);
}
static int MountExfatWithRetry(std::string source, std::string target)
{
char *argv[] = {const_cast<char *>("system/bin/mount.exfat"),
const_cast<char *>(source.c_str()), const_cast<char *>(target.c_str()), nullptr};
return LoopToMount(argv, source, target);
}
int MountSdcard(std::string &path, std::string &mountPoint)
{
if (path.empty() || mountPoint.empty()) {
LOG(ERROR) << "path or mountPoint is null, mount fail";
return -1;
}
MountStatus rc = GetMountStatusForMountPoint(mountPoint.c_str());
if (rc == MountStatus::MOUNT_ERROR) {
return -1;
} else if (rc == MountStatus::MOUNT_MOUNTED) {
LOG(INFO) << path << " already mounted";
return 0;
}
const std::vector<const char *> fileSystemType = {"ext4", "vfat", "exfat"};
for (auto type : fileSystemType) {
if (mount(path.c_str(), mountPoint.c_str(), type, 0, nullptr) == 0) {
LOG(INFO) << "mount success, sdcard type is " << type;
return 0;
}
}
if (MountNtfsWithRetry(path, mountPoint) == 0) {
LOG(INFO) << "mount success, sdcard type is ntfs";
return 0;
}
if (MountExfatWithRetry(path, mountPoint) == 0) {
LOG(INFO) << "mount success, sdcard type is exfat";
return 0;
}
return -1;
}
int MountForPath(const std::string &path)
{
if (g_fstab == nullptr) {
LOG(ERROR) << "fstab is not loaded, g_fstab is null.";
return -1;
}
FstabItem *item = FindFstabItemForPath(*g_fstab, path.c_str());
int ret = -1;
if (item == nullptr) {
LOG(ERROR) << "Cannot find fstab item for " << path << " to mount.";
return -1;
}
LOG(DEBUG) << "Mount for path " << path;
MountStatus rc = GetMountStatusForMountPoint(item->mountPoint);
if (rc == MountStatus::MOUNT_ERROR) {
LOG(ERROR) << "GetMountStatusForMountPoint ret is MOUNT_ERROR";
ret = -1;
} else if (rc == MountStatus::MOUNT_MOUNTED) {
LOG(INFO) << path << " already mounted";
ret = 0;
} else {
ret = MountOneItem(item);
}
return ret;
}
static int WriteZeroData(int fileFd, uint64_t partitionSize)
{
size_t sectorSize = 0;
ssize_t bytesWritten = 0;
sectorSize = 4096;
std::unique_ptr<char[]> buffer = std::make_unique<char[]>(sectorSize);
(void)memset_s(buffer.get(), sectorSize, 0, sectorSize);
for (size_t offset = 0; offset < partitionSize; offset += sectorSize) {
bytesWritten = pwrite(fileFd, buffer.get(), sectorSize, static_cast<off_t>(offset));
if (bytesWritten <= 0 || static_cast<size_t>(bytesWritten) != sectorSize) {
LOG(ERROR) << "failed to write to device.";
return -1;
}
}
LOG(INFO) << "Write zero data success.";
return 0;
}
bool ErasePartition(const std::string &devPath, bool isWriteZero)
{
std::string realPath {};
if (!Utils::PathToRealPath(devPath, realPath)) {
LOG(ERROR) << "realpath failed:" << devPath;
return false;
}
int fd = open(realPath.c_str(), O_RDWR | O_LARGEFILE | O_UNCACHE_FLAG);
if (fd == -1) {
LOG(ERROR) << "open failed:" << realPath;
return false;
}
fdsan_exchange_owner_tag(fd, 0, FDSAN_UPDATER_TAG);
uint64_t size = 0;
int ret = ioctl(fd, BLKGETSIZE64, &size);
if (ret < 0) {
LOG(ERROR) << "get partition size failed:" << size;
fdsan_close_with_tag(fd, FDSAN_UPDATER_TAG);
return false;
}
LOG(INFO) << "erase partition size:" << size;
if (isWriteZero) {
ret = WriteZeroData(fd, size);
} else {
uint64_t range[] { 0, size };
ret = ioctl(fd, BLKDISCARD, &range);
}
if (ret < 0) {
LOG(ERROR) << "erase partition failed";
fdsan_close_with_tag(fd, FDSAN_UPDATER_TAG);
return false;
}
fdsan_close_with_tag(fd, FDSAN_UPDATER_TAG);
return true;
}
int UmountRetry(const std::string &path)
{
int retryCount = 6;
while (retryCount-- > 0) {
if (UmountForPath(path) == 0) {
LOG(INFO) << "Umount " << path << " success";
return 0;
}
sleep(1);
}
return -1;
}
int FormatPartition(const std::string &path, bool isZeroErase)
{
if (g_fstab == nullptr) {
LOG(ERROR) << "fstab is not loaded, g_fstab is null.";
return -1;
}
FstabItem *item = FindFstabItemForPath(*g_fstab, path.c_str());
if (item == nullptr) {
LOG(ERROR) << "Cannot find fstab item for " << path << " to format.";
return -1;
}
if (strcmp(item->mountPoint, "/") == 0) {
return 0;
}
if (!IsSupportedFilesystem(item->fsType)) {
LOG(ERROR) << "Try to format " << item->mountPoint << " with unsupported file system type: " << item->fsType;
return -1;
}
if (UmountRetry(path) != 0) {
LOG(ERROR) << "UmountRetry " << path << " failed";
return -1;
}
if (isZeroErase) {
ErasePartition(item->deviceName, false);
}
int ret = DoFormat(item->deviceName, item->fsType);
if (ret != 0) {
LOG(ERROR) << "Format " << path << " failed";
}
return ((ret != 0) ? -1 : 0);
}
bool MountMetadata()
{
bool mountSuccess = false;
unsigned int retryTimes = 3;
for (unsigned int retryCount = 1; retryCount <= retryTimes; retryCount++) {
LOG(INFO) << "the retry time is " << retryCount;
if (MountForPath("/metadata") == 0) {
LOG(INFO) << "mount metadata success";
mountSuccess = true;
break;
}
sleep(1);
}
return mountSuccess;
}
bool IsMetadataEncrypt()
{
std::string path = "/sys/kernel/metacrypt/status";
if (!Utils::IsFileExist(path)) {
return false;
}
std::string state;
if (!Utils::ReadStringFromProcFile(path, state)) {
LOG(ERROR) << "read " << path << " failed: " << strerror(errno);
return false;
}
std::string disableState = "27242";
if (state == disableState) {
return false;
}
LOG(INFO) << "It is the userdata metadata encrypt status";
return true;
}
std::string GetMetaEncryptLog()
{
std::string result;
std::vector<std::string> logList {
"/sys/kernel/metacrypt/status",
"/sys/kernel/metacrypt/stage"
};
for (const auto &file : logList) {
std::string errStr;
if (!Utils::ReadStringFromProcFile(file, errStr)) {
LOG(ERROR) << "read " << file << " failed: " << strerror(errno);
continue;
}
result += (file + ":" + errStr + ",");
}
return result;
}
void RecordMountFailMsg(const std::string &mountPoint)
{
UPDATER_INIT_RECORD;
LOG(ERROR) << "Expected partition " << mountPoint << " is not mounted.";
if (IsMetadataEncrypt() && !IsDmDeviceLink()) {
std::string errMsg = "Userdata not link dm device, " + GetMetaEncryptLog();
LOG(ERROR) << errMsg;
UPDATER_LAST_WORD(-1, errMsg);
return;
}
UPDATER_LAST_WORD(-1, "Expected partition " + mountPoint + " is not mounted.");
}
int SetupPartitions(bool isMountData, bool isMountMetadata)
{
UPDATER_INIT_RECORD;
if (!Utils::IsUpdaterMode()) {
LOG(ERROR) << "live update mode";
return 0;
}
if (g_fstab == NULL || g_fstab->head == NULL) {
LOG(ERROR) << "Fstab is invalid";
UPDATER_LAST_WORD(-1, "Fstab is invalid");
return -1;
}
for (const FstabItem *item = g_fstab->head; item != nullptr; item = item->next) {
std::string mountPoint(item->mountPoint);
std::string fsType(item->fsType);
if (g_skipMountPointList.find(mountPoint) != g_skipMountPointList.end() || fsType == "none") {
continue;
}
if (mountPoint == "/metadata" && isMountMetadata) {
if (!MountMetadata()) {
LOG(INFO) << "MountMetadata failed";
}
continue;
}
if (mountPoint == "/data" && isMountData) {
if (GetMountStatusForMountPoint(INTERNAL_DATA_PATH) != MOUNT_MOUNTED && MountForPath(mountPoint) != 0) {
RecordMountFailMsg(mountPoint);
return -1;
}
Utils::SetParameter("updater.data.ready", "1");
LOG(INFO) << "mount data not fail";
continue;
}
if (UmountForPath(mountPoint) != 0) {
LOG(ERROR) << "Umount " << mountPoint << " failed";
UPDATER_LAST_WORD(-1, "Umount " + mountPoint + " failed");
return -1;
}
}
return 0;
}
const std::string GetBlockDeviceByMountPoint(const std::string &mountPoint)
{
if (mountPoint.empty()) {
LOG(ERROR) << "mountPoint empty error.";
return "";
}
std::string blockDevice = PARTITION_PATH + mountPoint;
if (mountPoint[0] != '/') {
blockDevice = PARTITION_PATH + "/" + mountPoint;
}
if (g_fstab != nullptr) {
FstabItem *item = FindFstabItemForMountPoint(*g_fstab, mountPoint.c_str());
if (item != NULL) {
blockDevice = item->deviceName;
}
}
return blockDevice;
}
const std::vector<std::string> GetBlockDevicesByMountPoint(const std::string &mountPoint)
{
std::vector<std::string> blockDevices;
if (mountPoint.empty() || g_fstab == nullptr) {
LOG(ERROR) << "mountPoint or g_fstab empty error.";
return blockDevices;
}
for (FstabItem *item = g_fstab->head; item != NULL; item = item->next) {
if ((item->mountPoint != NULL) && item->mountPoint == mountPoint) {
blockDevices.push_back(item->deviceName);
}
}
if (blockDevices.empty()) {
std::string blockDevice = PARTITION_PATH + mountPoint;
if (mountPoint[0] != '/') {
blockDevice = PARTITION_PATH + "/" + mountPoint;
}
blockDevices.push_back(blockDevice);
}
return blockDevices;
}
}