1 /*
2  * Copyright (c) 2021 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "switch_root.h"
17 #include <errno.h>
18 #include <dirent.h>
19 #include <limits.h>
20 #include <fcntl.h>
21 #include <stdbool.h>
22 #include <string.h>
23 #include <sys/mount.h>
24 #include <sys/stat.h>
25 #include "init_log.h"
26 #include "fs_manager/fs_manager.h"
27 #include "securec.h"
28 #include "init_utils.h"
29 
FreeOldRoot(DIR * dir,dev_t dev)30 static void FreeOldRoot(DIR *dir, dev_t dev)
31 {
32     if (dir == NULL) {
33         return;
34     }
35     int dfd = dirfd(dir);
36     bool isDir = false;
37     struct dirent *de = NULL;
38     while ((de = readdir(dir)) != NULL) {
39         if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) {
40             continue;
41         }
42         isDir = false;
43         if (de->d_type == DT_DIR || de->d_type == DT_UNKNOWN) {
44             struct stat st = {};
45             if (fstatat(dfd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
46                 INIT_LOGE("Failed to get stat of %s", de->d_name);
47                 continue;
48             }
49 
50             if (st.st_dev != dev) {
51                 continue; // Not the same device, ignore.
52             }
53             if (!S_ISDIR(st.st_mode)) {
54                 continue;
55             }
56             int fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY);
57             isDir = true;
58             if (fd < 0) {
59                 continue;
60             }
61             DIR *subDir = fdopendir(fd);
62             if (subDir != NULL) {
63                 FreeOldRoot(subDir, dev);
64                 closedir(subDir);
65             } else {
66                 close(fd);
67             }
68         }
69         if (unlinkat(dfd, de->d_name, isDir ? AT_REMOVEDIR : 0) < 0) {
70             INIT_LOGE("Failed to unlink %s, err = %d", de->d_name, errno);
71         }
72     }
73 }
74 
75 // For sub mountpoint under /dev, /sys, /proc
76 // There is no need to move them individually
77 // We will find a better solution to take care of
78 // all sub mount tree in the future.
UnderBasicMountPoint(const char * path)79 static bool UnderBasicMountPoint(const char *path)
80 {
81     unsigned int i;
82     if (path == NULL || *path == '\0') {
83         return false;
84     }
85     const char *basicMountPoint[] = {"/dev/", "/sys/", "/proc/"};
86     for (i = 0; i < ARRAY_LENGTH(basicMountPoint); i++) {
87         if (strncmp(path, basicMountPoint[i], strlen(basicMountPoint[i])) == 0) {
88             return true;
89         }
90     }
91     return false;
92 }
93 
MountToNewTarget(const char * target)94 static int MountToNewTarget(const char *target)
95 {
96     if (target == NULL || *target == '\0') {
97         return -1;
98     }
99     Fstab *fstab = ReadFstabFromFile("/proc/mounts", true);
100     if (fstab == NULL) {
101         INIT_LOGE("Fatal error. Read mounts info from \" /proc/mounts \" failed");
102         return -1;
103     }
104 
105     for (FstabItem *item = fstab->head; item != NULL; item = item->next) {
106         const char *mountPoint = item->mountPoint;
107         if (mountPoint == NULL || strcmp(mountPoint, "/") == 0 ||
108             strcmp(mountPoint, target) == 0) {
109             continue;
110         }
111         char newMountPoint[PATH_MAX] = {0};
112         if (snprintf_s(newMountPoint, PATH_MAX, PATH_MAX - 1, "%s%s", target, mountPoint) == -1) {
113             INIT_LOGW("Cannot build new mount point for old mount point \" %s \"", mountPoint);
114             // Just ignore this one or return error?
115             continue;
116         }
117         INIT_LOGV("new mount point is: %s", newMountPoint);
118         if (!UnderBasicMountPoint(mountPoint)) {
119             INIT_LOGV("Move mount %s to %s", mountPoint, newMountPoint);
120             if (mount(mountPoint, newMountPoint, NULL, MS_MOVE, NULL) < 0) {
121                 INIT_LOGE("Failed to mount moving %s to %s, err = %d", mountPoint, newMountPoint, errno);
122                 // If one mount entry cannot move to new mountpoint, umount it.
123                 umount2(mountPoint, MNT_FORCE);
124                 continue;
125             }
126         }
127     }
128     ReleaseFstab(fstab);
129     fstab = NULL;
130     return 0;
131 }
132 
FreeRootDir(DIR * oldRoot,dev_t dev)133 static void FreeRootDir(DIR *oldRoot, dev_t dev)
134 {
135     if (oldRoot != NULL) {
136         FreeOldRoot(oldRoot, dev);
137         closedir(oldRoot);
138     }
139 }
140 
141 // Switch root from ramdisk to system
SwitchRoot(const char * newRoot)142 int SwitchRoot(const char *newRoot)
143 {
144     if (newRoot == NULL || *newRoot == '\0') {
145         errno = EINVAL;
146         INIT_LOGE("Fatal error. Try to switch to new root with invalid new mount point");
147         return -1;
148     }
149 
150     struct stat oldRootStat = {};
151     INIT_ERROR_CHECK(stat("/", &oldRootStat) == 0, return -1, "Failed to get old root \"/\" stat");
152     DIR *oldRoot = opendir("/");
153     INIT_ERROR_CHECK(oldRoot != NULL, return -1, "Failed to open root dir \"/\"");
154     struct stat newRootStat = {};
155     if (stat(newRoot, &newRootStat) != 0) {
156         INIT_LOGE("Failed to get new root \" %s \" stat", newRoot);
157         FreeRootDir(oldRoot, oldRootStat.st_dev);
158         return -1;
159     }
160 
161     if (oldRootStat.st_dev == newRootStat.st_dev) {
162         INIT_LOGW("Try to switch root in same device, skip switching root");
163         FreeRootDir(oldRoot, oldRootStat.st_dev);
164         return 0;
165     }
166     if (MountToNewTarget(newRoot) < 0) {
167         INIT_LOGE("Failed to move mount to new root \" %s \" stat", newRoot);
168         FreeRootDir(oldRoot, oldRootStat.st_dev);
169         return -1;
170     }
171     // OK, we've done move mount.
172     // Now mount new root.
173     if (chdir(newRoot) < 0) {
174         INIT_LOGE("Failed to change directory to %s, err = %d", newRoot, errno);
175         FreeRootDir(oldRoot, oldRootStat.st_dev);
176         return -1;
177     }
178 
179     if (mount(newRoot, "/", NULL, MS_MOVE, NULL) < 0) {
180         INIT_LOGE("Failed to mount moving %s to %s, err = %d", newRoot, "/", errno);
181         FreeRootDir(oldRoot, oldRootStat.st_dev);
182         return -1;
183     }
184 
185     if (chroot(".") < 0) {
186         INIT_LOGE("Failed to change root directory");
187         FreeRootDir(oldRoot, oldRootStat.st_dev);
188         return -1;
189     }
190     FreeRootDir(oldRoot, oldRootStat.st_dev);
191     INIT_LOGI("SwitchRoot to %s finish", newRoot);
192     return 0;
193 }
194