2009-06-09 02:58:46 -05:00
|
|
|
/*
|
|
|
|
* switchroot.c - switch to new root directory and start init.
|
|
|
|
*
|
2009-06-09 09:35:07 -05:00
|
|
|
* Copyright 2002-2009 Red Hat, Inc. All rights reserved.
|
2009-06-09 02:58:46 -05:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
* Authors:
|
|
|
|
* Peter Jones <pjones@redhat.com>
|
|
|
|
* Jeremy Katz <katzj@redhat.com>
|
|
|
|
*/
|
|
|
|
#include <sys/mount.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
2014-04-02 09:41:30 -05:00
|
|
|
#include <sys/statfs.h>
|
2009-06-09 02:58:46 -05:00
|
|
|
#include <sys/param.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <dirent.h>
|
2017-05-09 13:10:51 -05:00
|
|
|
#include <getopt.h>
|
2011-10-30 08:45:14 -05:00
|
|
|
|
2011-01-25 15:44:52 -06:00
|
|
|
#include "c.h"
|
2011-10-30 08:45:14 -05:00
|
|
|
#include "nls.h"
|
2012-04-04 12:49:40 -05:00
|
|
|
#include "closestream.h"
|
2014-04-02 09:41:30 -05:00
|
|
|
#include "statfs_magic.h"
|
2009-06-09 02:58:46 -05:00
|
|
|
|
|
|
|
#ifndef MS_MOVE
|
|
|
|
#define MS_MOVE 8192
|
|
|
|
#endif
|
|
|
|
|
2011-11-14 07:11:01 -06:00
|
|
|
#ifndef MNT_DETACH
|
|
|
|
#define MNT_DETACH 0x00000002 /* Just detach from the tree */
|
|
|
|
#endif
|
|
|
|
|
2009-06-09 02:58:46 -05:00
|
|
|
/* remove all files/directories below dirName -- don't cross mountpoints */
|
2009-06-19 14:21:58 -05:00
|
|
|
static int recursiveRemove(int fd)
|
2009-06-09 03:37:45 -05:00
|
|
|
{
|
2009-06-09 06:24:11 -05:00
|
|
|
struct stat rb;
|
|
|
|
DIR *dir;
|
|
|
|
int rc = -1;
|
|
|
|
int dfd;
|
2009-06-09 03:37:45 -05:00
|
|
|
|
2009-06-19 14:21:58 -05:00
|
|
|
if (!(dir = fdopendir(fd))) {
|
2011-10-30 08:47:31 -05:00
|
|
|
warn(_("failed to open directory"));
|
2009-06-09 06:24:11 -05:00
|
|
|
goto done;
|
2009-06-09 03:37:45 -05:00
|
|
|
}
|
|
|
|
|
2009-06-19 14:21:58 -05:00
|
|
|
/* fdopendir() precludes us from continuing to use the input fd */
|
2009-06-09 06:24:11 -05:00
|
|
|
dfd = dirfd(dir);
|
|
|
|
if (fstat(dfd, &rb)) {
|
2012-07-15 03:17:53 -05:00
|
|
|
warn(_("stat failed"));
|
2009-06-09 06:24:11 -05:00
|
|
|
goto done;
|
2009-06-09 03:37:45 -05:00
|
|
|
}
|
|
|
|
|
2009-06-09 06:24:11 -05:00
|
|
|
while(1) {
|
|
|
|
struct dirent *d;
|
2014-03-04 04:45:44 -06:00
|
|
|
int isdir = 0;
|
2009-06-09 03:37:45 -05:00
|
|
|
|
|
|
|
errno = 0;
|
2009-06-09 06:24:11 -05:00
|
|
|
if (!(d = readdir(dir))) {
|
|
|
|
if (errno) {
|
2011-10-30 08:47:31 -05:00
|
|
|
warn(_("failed to read directory"));
|
2009-06-09 06:24:11 -05:00
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
break; /* end of directory */
|
|
|
|
}
|
2009-06-09 03:37:45 -05:00
|
|
|
|
2009-06-09 06:24:11 -05:00
|
|
|
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
|
2009-06-09 03:37:45 -05:00
|
|
|
continue;
|
2014-03-04 04:45:44 -06:00
|
|
|
#ifdef _DIRENT_HAVE_D_TYPE
|
|
|
|
if (d->d_type == DT_DIR || d->d_type == DT_UNKNOWN)
|
|
|
|
#endif
|
|
|
|
{
|
2009-06-09 06:24:11 -05:00
|
|
|
struct stat sb;
|
2009-06-09 03:37:45 -05:00
|
|
|
|
2009-06-09 06:24:11 -05:00
|
|
|
if (fstatat(dfd, d->d_name, &sb, AT_SYMLINK_NOFOLLOW)) {
|
2015-01-29 14:21:48 -06:00
|
|
|
warn(_("stat of %s failed"), d->d_name);
|
2009-06-09 06:24:11 -05:00
|
|
|
continue;
|
|
|
|
}
|
2009-06-09 03:37:45 -05:00
|
|
|
|
switch_root: unlink files without _DIRENT_HAVE_D_TYPE
When _DIRENT_HAVE_D_TYPE is not defined, we need to always fstat the
directory entry in order to determine whether it is a directory or not.
If we determine that the file is indeed a directory on the same device,
we proceed to recursively remove its contents as well. Otherwise, we
simply skip removing the entry altogether.
This logic is not entirely correct though. Note that we actually skip
deletion of the entry if it is either not a directory or if it is not on
the same device. The second condition is obviously correct here, as we
do not want to delete files on other mounts here. But skipping deletion
of the entry itself if it is not a directory is wrong.
When _DIRENT_HAVE_D_TYPE is defined, this condition should never be
triggered, as we have already determined that the entry is a directory.
But if it is not, we will always do the fstat and check. Because of
this, we will now skip deletion of all files which are not directories,
which is wrong.
Fix the issue by disentangling both conditions. We now first check
whether we are still on the same device - if not, we skip recursive
deletion as well as deletion of the directory entry. Afterwards, we
check whether it is a directory - if so, we do delete its contents
recursively. And finally, we will now unlink the entry disregarding
whether it is a directory or not.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
2017-04-22 19:33:04 -05:00
|
|
|
/* skip if device is not the same */
|
|
|
|
if (sb.st_dev != rb.st_dev)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* remove subdirectories */
|
|
|
|
if (S_ISDIR(sb.st_mode)) {
|
2009-06-19 14:21:58 -05:00
|
|
|
int cfd;
|
2009-06-09 06:24:11 -05:00
|
|
|
|
2009-06-19 14:21:58 -05:00
|
|
|
cfd = openat(dfd, d->d_name, O_RDONLY);
|
2020-10-16 04:56:35 -05:00
|
|
|
if (cfd >= 0)
|
|
|
|
recursiveRemove(cfd); /* it closes cfd too */
|
2014-03-04 04:45:44 -06:00
|
|
|
isdir = 1;
|
switch_root: unlink files without _DIRENT_HAVE_D_TYPE
When _DIRENT_HAVE_D_TYPE is not defined, we need to always fstat the
directory entry in order to determine whether it is a directory or not.
If we determine that the file is indeed a directory on the same device,
we proceed to recursively remove its contents as well. Otherwise, we
simply skip removing the entry altogether.
This logic is not entirely correct though. Note that we actually skip
deletion of the entry if it is either not a directory or if it is not on
the same device. The second condition is obviously correct here, as we
do not want to delete files on other mounts here. But skipping deletion
of the entry itself if it is not a directory is wrong.
When _DIRENT_HAVE_D_TYPE is defined, this condition should never be
triggered, as we have already determined that the entry is a directory.
But if it is not, we will always do the fstat and check. Because of
this, we will now skip deletion of all files which are not directories,
which is wrong.
Fix the issue by disentangling both conditions. We now first check
whether we are still on the same device - if not, we skip recursive
deletion as well as deletion of the directory entry. Afterwards, we
check whether it is a directory - if so, we do delete its contents
recursively. And finally, we will now unlink the entry disregarding
whether it is a directory or not.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
2017-04-22 19:33:04 -05:00
|
|
|
}
|
2009-06-09 03:37:45 -05:00
|
|
|
}
|
|
|
|
|
2014-03-04 04:45:44 -06:00
|
|
|
if (unlinkat(dfd, d->d_name, isdir ? AT_REMOVEDIR : 0))
|
2011-10-30 08:47:31 -05:00
|
|
|
warn(_("failed to unlink %s"), d->d_name);
|
2009-06-09 03:37:45 -05:00
|
|
|
}
|
|
|
|
|
2009-06-09 06:24:11 -05:00
|
|
|
rc = 0; /* success */
|
|
|
|
done:
|
|
|
|
if (dir)
|
|
|
|
closedir(dir);
|
2020-10-16 04:56:35 -05:00
|
|
|
else
|
|
|
|
close(fd);
|
2009-06-09 06:24:11 -05:00
|
|
|
return rc;
|
2009-06-09 03:37:45 -05:00
|
|
|
}
|
2009-06-09 02:58:46 -05:00
|
|
|
|
|
|
|
static int switchroot(const char *newroot)
|
|
|
|
{
|
|
|
|
/* Don't try to unmount the old "/", there's no way to do it. */
|
2011-10-20 03:05:20 -05:00
|
|
|
const char *umounts[] = { "/dev", "/proc", "/sys", "/run", NULL };
|
2009-06-09 02:58:46 -05:00
|
|
|
int i;
|
2020-10-16 04:56:35 -05:00
|
|
|
int cfd = -1;
|
2021-02-08 08:30:25 -06:00
|
|
|
struct stat newroot_stat, oldroot_stat, sb;
|
|
|
|
|
|
|
|
if (stat("/", &oldroot_stat) != 0) {
|
|
|
|
warn(_("stat of %s failed"), "/");
|
|
|
|
return -1;
|
|
|
|
}
|
2011-10-20 03:05:20 -05:00
|
|
|
|
|
|
|
if (stat(newroot, &newroot_stat) != 0) {
|
2015-01-29 14:21:48 -06:00
|
|
|
warn(_("stat of %s failed"), newroot);
|
2011-10-20 03:05:20 -05:00
|
|
|
return -1;
|
|
|
|
}
|
2009-06-09 02:58:46 -05:00
|
|
|
|
|
|
|
for (i = 0; umounts[i] != NULL; i++) {
|
|
|
|
char newmount[PATH_MAX];
|
2009-06-09 09:35:07 -05:00
|
|
|
|
|
|
|
snprintf(newmount, sizeof(newmount), "%s%s", newroot, umounts[i]);
|
|
|
|
|
2021-02-08 08:30:25 -06:00
|
|
|
if ((stat(umounts[i], &sb) == 0) && sb.st_dev == oldroot_stat.st_dev) {
|
|
|
|
/* mount point to move seems to be a normal directory or stat failed */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-10-20 03:05:20 -05:00
|
|
|
if ((stat(newmount, &sb) != 0) || (sb.st_dev != newroot_stat.st_dev)) {
|
|
|
|
/* mount point seems to be mounted already or stat failed */
|
2011-11-14 07:11:01 -06:00
|
|
|
umount2(umounts[i], MNT_DETACH);
|
2011-10-20 03:05:20 -05:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2009-06-09 02:58:46 -05:00
|
|
|
if (mount(umounts[i], newmount, NULL, MS_MOVE, NULL) < 0) {
|
2011-10-30 08:47:31 -05:00
|
|
|
warn(_("failed to mount moving %s to %s"),
|
2009-06-09 02:58:46 -05:00
|
|
|
umounts[i], newmount);
|
2011-10-30 08:47:31 -05:00
|
|
|
warnx(_("forcing unmount of %s"), umounts[i]);
|
2009-06-09 02:58:46 -05:00
|
|
|
umount2(umounts[i], MNT_FORCE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-06-09 08:54:00 -05:00
|
|
|
if (chdir(newroot)) {
|
2011-10-30 08:47:31 -05:00
|
|
|
warn(_("failed to change directory to %s"), newroot);
|
2009-11-20 08:11:59 -06:00
|
|
|
return -1;
|
2009-06-09 02:58:46 -05:00
|
|
|
}
|
2009-06-09 08:54:00 -05:00
|
|
|
|
2009-06-19 14:21:58 -05:00
|
|
|
cfd = open("/", O_RDONLY);
|
2012-09-07 06:02:42 -05:00
|
|
|
if (cfd < 0) {
|
|
|
|
warn(_("cannot open %s"), "/");
|
2020-10-16 04:56:35 -05:00
|
|
|
goto fail;
|
2012-09-07 06:02:42 -05:00
|
|
|
}
|
2009-06-09 08:54:00 -05:00
|
|
|
|
2009-06-09 02:58:46 -05:00
|
|
|
if (mount(newroot, "/", NULL, MS_MOVE, NULL) < 0) {
|
2011-10-30 08:47:31 -05:00
|
|
|
warn(_("failed to mount moving %s to /"), newroot);
|
2020-10-16 04:56:35 -05:00
|
|
|
goto fail;
|
2009-06-09 02:58:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (chroot(".")) {
|
2011-10-30 08:47:31 -05:00
|
|
|
warn(_("failed to change root"));
|
2020-10-16 04:56:35 -05:00
|
|
|
goto fail;
|
2009-06-09 02:58:46 -05:00
|
|
|
}
|
2009-06-19 14:44:32 -05:00
|
|
|
|
2020-08-09 06:51:11 -05:00
|
|
|
if (chdir("/")) {
|
|
|
|
warn(_("cannot change directory to %s"), "/");
|
2020-10-16 04:56:35 -05:00
|
|
|
goto fail;
|
2020-08-09 06:51:11 -05:00
|
|
|
}
|
|
|
|
|
2020-10-16 04:56:35 -05:00
|
|
|
switch (fork()) {
|
|
|
|
case 0: /* child */
|
|
|
|
{
|
2016-07-03 06:30:46 -05:00
|
|
|
struct statfs stfs;
|
|
|
|
|
|
|
|
if (fstatfs(cfd, &stfs) == 0 &&
|
|
|
|
(F_TYPE_EQUAL(stfs.f_type, STATFS_RAMFS_MAGIC) ||
|
|
|
|
F_TYPE_EQUAL(stfs.f_type, STATFS_TMPFS_MAGIC)))
|
|
|
|
recursiveRemove(cfd);
|
2020-10-16 04:56:35 -05:00
|
|
|
else {
|
2016-07-03 06:30:46 -05:00
|
|
|
warn(_("old root filesystem is not an initramfs"));
|
2020-10-16 04:56:35 -05:00
|
|
|
close(cfd);
|
|
|
|
}
|
|
|
|
exit(EXIT_SUCCESS);
|
2009-06-19 14:44:32 -05:00
|
|
|
}
|
2020-10-16 04:56:35 -05:00
|
|
|
case -1: /* error */
|
|
|
|
break;
|
2016-07-03 06:30:46 -05:00
|
|
|
|
2020-10-16 04:56:35 -05:00
|
|
|
default: /* parent */
|
|
|
|
close(cfd);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
fail:
|
|
|
|
if (cfd >= 0)
|
|
|
|
close(cfd);
|
|
|
|
return -1;
|
2009-06-09 02:58:46 -05:00
|
|
|
}
|
|
|
|
|
2017-06-19 13:52:50 -05:00
|
|
|
static void __attribute__((__noreturn__)) usage(void)
|
2009-06-09 02:58:46 -05:00
|
|
|
{
|
2017-06-19 13:52:50 -05:00
|
|
|
FILE *output = stdout;
|
2011-10-30 08:45:14 -05:00
|
|
|
fputs(USAGE_HEADER, output);
|
2011-10-30 08:47:31 -05:00
|
|
|
fprintf(output, _(" %s [options] <newrootdir> <init> <args to init>\n"),
|
2011-10-30 08:45:14 -05:00
|
|
|
program_invocation_short_name);
|
2014-12-22 15:57:17 -06:00
|
|
|
|
|
|
|
fputs(USAGE_SEPARATOR, output);
|
|
|
|
fputs(_("Switch to another filesystem as the root of the mount tree.\n"), output);
|
|
|
|
|
2011-10-30 08:45:14 -05:00
|
|
|
fputs(USAGE_OPTIONS, output);
|
2017-06-29 08:52:16 -05:00
|
|
|
printf(USAGE_HELP_OPTIONS(16));
|
|
|
|
printf(USAGE_MAN_TAIL("switch_root(8)"));
|
2009-06-09 09:16:46 -05:00
|
|
|
|
2017-06-19 13:52:50 -05:00
|
|
|
exit(EXIT_SUCCESS);
|
2009-06-09 02:58:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
2009-06-09 09:16:46 -05:00
|
|
|
char *newroot, *init, **initargs;
|
2017-05-09 13:10:51 -05:00
|
|
|
int c;
|
|
|
|
static const struct option longopts[] = {
|
|
|
|
{"version", no_argument, NULL, 'V'},
|
|
|
|
{"help", no_argument, NULL, 'h'},
|
|
|
|
{NULL, 0, NULL, 0}
|
|
|
|
};
|
|
|
|
|
2019-04-16 08:14:13 -05:00
|
|
|
close_stdout_atexit();
|
2009-06-09 02:58:46 -05:00
|
|
|
|
2018-11-09 10:02:11 -06:00
|
|
|
while ((c = getopt_long(argc, argv, "+Vh", longopts, NULL)) != -1)
|
2017-05-09 13:10:51 -05:00
|
|
|
switch (c) {
|
|
|
|
case 'V':
|
2019-04-16 08:14:13 -05:00
|
|
|
print_version(EXIT_SUCCESS);
|
2017-05-09 13:10:51 -05:00
|
|
|
case 'h':
|
2017-06-19 13:52:50 -05:00
|
|
|
usage();
|
2017-05-09 13:10:51 -05:00
|
|
|
default:
|
|
|
|
errtryhelp(EXIT_FAILURE);
|
|
|
|
}
|
2017-06-19 13:52:50 -05:00
|
|
|
if (argc < 3) {
|
|
|
|
warnx(_("not enough arguments"));
|
|
|
|
errtryhelp(EXIT_FAILURE);
|
|
|
|
}
|
2009-06-09 09:16:46 -05:00
|
|
|
|
|
|
|
newroot = argv[1];
|
|
|
|
init = argv[2];
|
|
|
|
initargs = &argv[2];
|
|
|
|
|
2017-06-19 13:52:50 -05:00
|
|
|
if (!*newroot || !*init) {
|
|
|
|
warnx(_("bad usage"));
|
|
|
|
errtryhelp(EXIT_FAILURE);
|
|
|
|
}
|
2009-06-09 02:58:46 -05:00
|
|
|
|
2009-06-09 08:54:00 -05:00
|
|
|
if (switchroot(newroot))
|
2011-10-30 08:47:31 -05:00
|
|
|
errx(EXIT_FAILURE, _("failed. Sorry."));
|
2009-06-09 08:54:00 -05:00
|
|
|
|
2009-06-09 09:16:46 -05:00
|
|
|
if (access(init, X_OK))
|
2011-10-30 08:47:31 -05:00
|
|
|
warn(_("cannot access %s"), init);
|
2009-06-09 02:58:46 -05:00
|
|
|
|
2009-06-09 09:16:46 -05:00
|
|
|
execv(init, initargs);
|
2018-02-01 08:44:25 -06:00
|
|
|
errexec(init);
|
2009-06-09 02:58:46 -05:00
|
|
|
}
|
|
|
|
|