switch_root.c 6.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
/*
 * 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 "switch_root.h"
#include <errno.h>
#include <dirent.h>
#include <limits.h>
#include <fcntl.h>
#include <stdbool.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include "init_log.h"
#include "fs_manager/fs_manager.h"
#include "securec.h"

static void FreeOldRoot(DIR *dir, dev_t dev)
{
    if (dir == NULL) {
        return;
    }
    int dfd = dirfd(dir);
    bool isDir = false;
    struct dirent *de = NULL;
    while ((de = readdir(dir)) != NULL) {
        if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) {
            continue;
        }

        if (de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) {
            struct stat st = {};
            if (fstatat(dfd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
                INIT_LOGE("Failed to get stat of %s", de->d_name);
                continue;
            }

            if (st.st_dev != dev) {
                continue; // Not the same device, ignore.
            }
4
411148299@qq.com 已提交
52 53 54 55 56 57 58 59 60 61 62 63 64 65
            if (!S_ISDIR(st.st_mode)) {
                continue;
            }
            int fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY);
            isDir = true;
            if (fd < 0) {
                continue;
            }
            DIR *subDir = fdopendir(fd);
            if (subDir != NULL) {
                FreeOldRoot(subDir, dev);
                closedir(subDir);
            } else {
                close(fd);
66 67 68 69 70 71 72 73
            }
        }
        if (unlinkat(dfd, de->d_name, isDir ? AT_REMOVEDIR : 0) < 0) {
            INIT_LOGE("Failed to unlink %s, err = %d", de->d_name, errno);
        }
    }
}

S
sun_fan 已提交
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
// For sub mountpoint under /dev, /sys, /proc
// There is no need to move them individually
// We will find a better solution to take care of
// all sub mount tree in the future.
static bool UnderBasicMountPoint(const char *path)
{
    if (path == NULL || *path == '\0') {
        return false;
    }

    size_t pathSize = strlen(path);
    if (strncmp(path, "/dev", strlen("/dev")) == 0 && pathSize > strlen("/dev")) {
        return true;
    }

    if (strncmp(path, "/sys", strlen("/sys")) == 0 && pathSize > strlen("/sys")) {
        return true;
    }

    if (strncmp(path, "/proc", strlen("/proc")) == 0 && pathSize > strlen("/proc")) {
        return true;
    }
    return false;
}

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
static int MountToNewTarget(const char *target)
{
    if (target == NULL || *target == '\0') {
        return -1;
    }
    Fstab *fstab = ReadFstabFromFile("/proc/mounts", true);
    if (fstab == NULL) {
        INIT_LOGE("Fatal error. Read mounts info from \" /proc/mounts \" failed");
        return -1;
    }

    for (FstabItem *item = fstab->head; item != NULL; item = item->next) {
        const char *mountPoint = item->mountPoint;
        if (mountPoint == NULL || strcmp(mountPoint, "/") == 0 ||
            strcmp(mountPoint, target) == 0) {
            continue;
        }
        char newMountPoint[PATH_MAX] = {0};
        if (snprintf_s(newMountPoint, PATH_MAX, PATH_MAX - 1, "%s%s", target, mountPoint) == -1) {
            INIT_LOGW("Cannot build new mount point for old mount point \" %s \"", mountPoint);
            // Just ignore this one or return error?
            continue;
        }
S
sun_fan 已提交
122 123 124 125 126 127 128 129 130
        INIT_LOGD("new mount point is: %s", newMountPoint);
        if (!UnderBasicMountPoint(mountPoint)) {
            INIT_LOGD("Move mount %s to %s", mountPoint, newMountPoint);
            if (mount(mountPoint, newMountPoint, NULL, MS_MOVE, NULL) < 0) {
                INIT_LOGE("Failed to mount moving %s to %s, err = %d", mountPoint, newMountPoint, errno);
                // If one mount entry cannot move to new mountpoint, umount it.
                umount2(mountPoint, MNT_FORCE);
                continue;
            }
131 132 133 134 135 136 137
        }
    }
    ReleaseFstab(fstab);
    fstab = NULL;
    return 0;
}

X
xionglei6 已提交
138 139 140 141 142 143 144 145 146
static void FreeRootDir(DIR *oldRoot, dev_t dev)
{
    if (oldRoot != NULL) {
        FreeOldRoot(oldRoot, dev);
        closedir(oldRoot);
        oldRoot = NULL;
    }
}

147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
// Switch root from ramdisk to system
int SwitchRoot(const char *newRoot)
{
    if (newRoot == NULL || *newRoot == '\0') {
        errno = EINVAL;
        INIT_LOGE("Fatal error. Try to switch to new root with invalid new mount point");
        return -1;
    }

    struct stat oldRootStat = {};
    if (stat("/", &oldRootStat) != 0) {
        INIT_LOGE("Failed to get old root \"/\" stat");
        return -1;
    }
    DIR *oldRoot = opendir("/");
    if (oldRoot == NULL) {
        INIT_LOGE("Failed to open root dir \"/\"");
        return -1;
    }
    struct stat newRootStat = {};
    if (stat(newRoot, &newRootStat) != 0) {
        INIT_LOGE("Failed to get new root \" %s \" stat", newRoot);
X
xionglei6 已提交
169
        FreeRootDir(oldRoot, oldRootStat.st_dev);
170 171 172 173 174
        return -1;
    }

    if (oldRootStat.st_dev == newRootStat.st_dev) {
        INIT_LOGW("Try to switch root in same device, skip switching root");
X
xionglei6 已提交
175
        FreeRootDir(oldRoot, oldRootStat.st_dev);
176 177 178 179
        return 0;
    }
    if (MountToNewTarget(newRoot) < 0) {
        INIT_LOGE("Failed to move mount to new root \" %s \" stat", newRoot);
X
xionglei6 已提交
180
        FreeRootDir(oldRoot, oldRootStat.st_dev);
181 182 183 184 185 186
        return -1;
    }
    // OK, we've done move mount.
    // Now mount new root.
    if (chdir(newRoot) < 0) {
        INIT_LOGE("Failed to change directory to %s, err = %d", newRoot, errno);
X
xionglei6 已提交
187
        FreeRootDir(oldRoot, oldRootStat.st_dev);
188 189 190 191 192
        return -1;
    }

    if (mount(newRoot, "/", NULL, MS_MOVE, NULL) < 0) {
        INIT_LOGE("Failed to mount moving %s to %s, err = %d", newRoot, "/", errno);
X
xionglei6 已提交
193
        FreeRootDir(oldRoot, oldRootStat.st_dev);
194 195 196 197 198
        return -1;
    }

    if (chroot(".") < 0) {
        INIT_LOGE("Failed to change root directory");
X
xionglei6 已提交
199
        FreeRootDir(oldRoot, oldRootStat.st_dev);
200 201
        return -1;
    }
X
xionglei6 已提交
202
    FreeRootDir(oldRoot, oldRootStat.st_dev);
203 204
    return 0;
}