(root)/
util-linux-2.39/
sys-utils/
switch_root.c
       1  /*
       2   * switchroot.c - switch to new root directory and start init.
       3   *
       4   * Copyright 2002-2009 Red Hat, Inc.  All rights reserved.
       5   *
       6   * This program is free software; you can redistribute it and/or modify
       7   * it under the terms of the GNU General Public License as published by
       8   * the Free Software Foundation; either version 2 of the License, or
       9   * (at your option) any later version.
      10   *
      11   * This program is distributed in the hope that it will be useful,
      12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14   * GNU General Public License for more details.
      15   *
      16   * You should have received a copy of the GNU General Public License
      17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
      18   *
      19   * Authors:
      20   *	Peter Jones <pjones@redhat.com>
      21   *	Jeremy Katz <katzj@redhat.com>
      22   */
      23  #include <sys/mount.h>
      24  #include <sys/types.h>
      25  #include <sys/stat.h>
      26  #include <sys/statfs.h>
      27  #include <sys/param.h>
      28  #include <fcntl.h>
      29  #include <stdio.h>
      30  #include <stdlib.h>
      31  #include <unistd.h>
      32  #include <string.h>
      33  #include <errno.h>
      34  #include <ctype.h>
      35  #include <dirent.h>
      36  #include <getopt.h>
      37  
      38  #include "c.h"
      39  #include "nls.h"
      40  #include "closestream.h"
      41  #include "statfs_magic.h"
      42  
      43  #ifndef MS_MOVE
      44  #define MS_MOVE 8192
      45  #endif
      46  
      47  #ifndef MNT_DETACH
      48  #define MNT_DETACH       0x00000002	/* Just detach from the tree */
      49  #endif
      50  
      51  /* remove all files/directories below dirName -- don't cross mountpoints */
      52  static int recursiveRemove(int fd)
      53  {
      54  	struct stat rb;
      55  	DIR *dir;
      56  	int rc = -1;
      57  	int dfd;
      58  
      59  	if (!(dir = fdopendir(fd))) {
      60  		warn(_("failed to open directory"));
      61  		goto done;
      62  	}
      63  
      64  	/* fdopendir() precludes us from continuing to use the input fd */
      65  	dfd = dirfd(dir);
      66  	if (fstat(dfd, &rb)) {
      67  		warn(_("stat failed"));
      68  		goto done;
      69  	}
      70  
      71  	while(1) {
      72  		struct dirent *d;
      73  		int isdir = 0;
      74  
      75  		errno = 0;
      76  		if (!(d = readdir(dir))) {
      77  			if (errno) {
      78  				warn(_("failed to read directory"));
      79  				goto done;
      80  			}
      81  			break;	/* end of directory */
      82  		}
      83  
      84  		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
      85  			continue;
      86  #ifdef _DIRENT_HAVE_D_TYPE
      87  		if (d->d_type == DT_DIR || d->d_type == DT_UNKNOWN)
      88  #endif
      89  		{
      90  			struct stat sb;
      91  
      92  			if (fstatat(dfd, d->d_name, &sb, AT_SYMLINK_NOFOLLOW)) {
      93  				warn(_("stat of %s failed"), d->d_name);
      94  				continue;
      95  			}
      96  
      97  			/* skip if device is not the same */
      98  			if (sb.st_dev != rb.st_dev)
      99  				continue;
     100  
     101  			/* remove subdirectories */
     102  			if (S_ISDIR(sb.st_mode)) {
     103  				int cfd;
     104  
     105  				cfd = openat(dfd, d->d_name, O_RDONLY);
     106  				if (cfd >= 0)
     107  					recursiveRemove(cfd);	/* it closes cfd too */
     108  				isdir = 1;
     109  			}
     110  		}
     111  
     112  		if (unlinkat(dfd, d->d_name, isdir ? AT_REMOVEDIR : 0))
     113  			warn(_("failed to unlink %s"), d->d_name);
     114  	}
     115  
     116  	rc = 0;	/* success */
     117  done:
     118  	if (dir)
     119  		closedir(dir);
     120  	else
     121  		close(fd);
     122  	return rc;
     123  }
     124  
     125  static int switchroot(const char *newroot)
     126  {
     127  	/*  Don't try to unmount the old "/", there's no way to do it. */
     128  	const char *umounts[] = { "/dev", "/proc", "/sys", "/run", NULL };
     129  	int i;
     130  	int cfd = -1;
     131  	struct stat newroot_stat, oldroot_stat, sb;
     132  
     133  	if (stat("/", &oldroot_stat) != 0) {
     134  		warn(_("stat of %s failed"), "/");
     135  		return -1;
     136  	}
     137  
     138  	if (stat(newroot, &newroot_stat) != 0) {
     139  		warn(_("stat of %s failed"), newroot);
     140  		return -1;
     141  	}
     142  
     143  	for (i = 0; umounts[i] != NULL; i++) {
     144  		char newmount[PATH_MAX];
     145  
     146  		snprintf(newmount, sizeof(newmount), "%s%s", newroot, umounts[i]);
     147  
     148  		if ((stat(umounts[i], &sb) == 0) && sb.st_dev == oldroot_stat.st_dev) {
     149  			/* mount point to move seems to be a normal directory or stat failed */
     150  			continue;
     151  		}
     152  
     153  		if ((stat(newmount, &sb) != 0) || (sb.st_dev != newroot_stat.st_dev)) {
     154  			/* mount point seems to be mounted already or stat failed */
     155  			umount2(umounts[i], MNT_DETACH);
     156  			continue;
     157  		}
     158  
     159  		if (mount(umounts[i], newmount, NULL, MS_MOVE, NULL) < 0) {
     160  			warn(_("failed to mount moving %s to %s"),
     161  				umounts[i], newmount);
     162  			warnx(_("forcing unmount of %s"), umounts[i]);
     163  			umount2(umounts[i], MNT_FORCE);
     164  		}
     165  	}
     166  
     167  	if (chdir(newroot)) {
     168  		warn(_("failed to change directory to %s"), newroot);
     169  		return -1;
     170  	}
     171  
     172  	cfd = open("/", O_RDONLY);
     173  	if (cfd < 0) {
     174  		warn(_("cannot open %s"), "/");
     175  		goto fail;
     176  	}
     177  
     178  	if (mount(newroot, "/", NULL, MS_MOVE, NULL) < 0) {
     179  		warn(_("failed to mount moving %s to /"), newroot);
     180  		goto fail;
     181  	}
     182  
     183  	if (chroot(".")) {
     184  		warn(_("failed to change root"));
     185  		goto fail;
     186  	}
     187  
     188  	if (chdir("/")) {
     189  		warn(_("cannot change directory to %s"), "/");
     190  		goto fail;
     191  	}
     192  
     193  	switch (fork()) {
     194  	case 0: /* child */
     195  	{
     196  		struct statfs stfs;
     197  
     198  		if (fstatfs(cfd, &stfs) == 0 &&
     199  		    (F_TYPE_EQUAL(stfs.f_type, STATFS_RAMFS_MAGIC) ||
     200  		     F_TYPE_EQUAL(stfs.f_type, STATFS_TMPFS_MAGIC)))
     201  			recursiveRemove(cfd);
     202  		else {
     203  			warn(_("old root filesystem is not an initramfs"));
     204  			close(cfd);
     205  		}
     206  		exit(EXIT_SUCCESS);
     207  	}
     208  	case -1: /* error */
     209  		break;
     210  
     211  	default: /* parent */
     212  		close(cfd);
     213  		return 0;
     214  	}
     215  
     216  fail:
     217  	if (cfd >= 0)
     218  		close(cfd);
     219  	return -1;
     220  }
     221  
     222  static void __attribute__((__noreturn__)) usage(void)
     223  {
     224  	FILE *output = stdout;
     225  	fputs(USAGE_HEADER, output);
     226  	fprintf(output, _(" %s [options] <newrootdir> <init> <args to init>\n"),
     227  		program_invocation_short_name);
     228  
     229  	fputs(USAGE_SEPARATOR, output);
     230  	fputs(_("Switch to another filesystem as the root of the mount tree.\n"), output);
     231  
     232  	fputs(USAGE_OPTIONS, output);
     233  	printf(USAGE_HELP_OPTIONS(16));
     234  	printf(USAGE_MAN_TAIL("switch_root(8)"));
     235  
     236  	exit(EXIT_SUCCESS);
     237  }
     238  
     239  int main(int argc, char *argv[])
     240  {
     241  	char *newroot, *init, **initargs;
     242  	int c;
     243  	static const struct option longopts[] = {
     244  		{"version", no_argument, NULL, 'V'},
     245  		{"help", no_argument, NULL, 'h'},
     246  		{NULL, 0, NULL, 0}
     247  	};
     248  
     249  	close_stdout_atexit();
     250  
     251  	while ((c = getopt_long(argc, argv, "+Vh", longopts, NULL)) != -1)
     252  		switch (c) {
     253  		case 'V':
     254  			print_version(EXIT_SUCCESS);
     255  		case 'h':
     256  			usage();
     257  		default:
     258  			errtryhelp(EXIT_FAILURE);
     259  		}
     260  	if (argc < 3) {
     261  		warnx(_("not enough arguments"));
     262  		errtryhelp(EXIT_FAILURE);
     263  	}
     264  
     265  	newroot = argv[1];
     266  	init = argv[2];
     267  	initargs = &argv[2];
     268  
     269  	if (!*newroot || !*init) {
     270  		warnx(_("bad usage"));
     271  		errtryhelp(EXIT_FAILURE);
     272  	}
     273  
     274  	if (switchroot(newroot))
     275  		errx(EXIT_FAILURE, _("failed. Sorry."));
     276  
     277  	if (access(init, X_OK))
     278  		warn(_("cannot access %s"), init);
     279  
     280  	execv(init, initargs);
     281  	errexec(init);
     282  }
     283