master xplshn/aruu / cmd / linux / switch_root.c
  1/* See LICENSE file for copyright and license details. */
  2
  3#include <sys/mount.h>
  4#include <sys/stat.h>
  5#include <sys/vfs.h>
  6
  7#include <dirent.h>
  8#include <fcntl.h>
  9#include <limits.h>
 10#include <stdio.h>
 11#include <stdlib.h>
 12#include <string.h>
 13#include <unistd.h>
 14
 15#include "util.h"
 16
 17#define RAMFS_MAGIC 0x858458f6 /* some random number */
 18#define TMPFS_MAGIC 0x01021994
 19
 20static void
 21delete_content(const char *dir, dev_t curdevice)
 22{
 23  char           path[PATH_MAX];
 24  DIR           *d;
 25  struct stat    st;
 26  struct dirent *dent;
 27
 28  /* don't dive into other filesystems */
 29  if (lstat(dir, &st) < 0 || st.st_dev != curdevice)
 30    return;
 31  if (!(d = opendir(dir)))
 32    return;
 33  while ((dent = readdir(d))) {
 34    if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
 35      continue;
 36
 37    /* build path and dive deeper */
 38    if (strlcpy(path, dir, sizeof(path)) >= sizeof(path))
 39      eprintf("path too long\n");
 40    if (path[strlen(path) - 1] != '/')
 41      if (strlcat(path, "/", sizeof(path)) >= sizeof(path))
 42        eprintf("path too long\n");
 43    if (strlcat(path, dent->d_name, sizeof(path)) >= sizeof(path))
 44      eprintf("path too long\n");
 45
 46    if (lstat(path, &st) < 0)
 47      weprintf("lstat %s:", path);
 48
 49    if (S_ISDIR(st.st_mode)) {
 50      delete_content(path, curdevice);
 51      if (rmdir(path) < 0)
 52        weprintf("rmdir %s:", path);
 53    } else {
 54      if (unlink(path) < 0)
 55        weprintf("unlink %s:", path);
 56    }
 57  }
 58  closedir(d);
 59}
 60
 61static void
 62usage(void)
 63{
 64  eprintf("usage: %s [-c console] [newroot] [init] (PID 1)\n", argv0);
 65}
 66
 67// ?man switch_root: switch to another root filesystem
 68// ?man arguments: newroot [init] (PID 1)
 69// ?man switch to another filesystem as the root of the mount tree
 70int
 71main(int argc, char *argv[])
 72{
 73  char         *console = NULL;
 74  dev_t         curdev;
 75  struct stat   st;
 76  struct statfs stfs;
 77
 78  ARGBEGIN
 79  {
 80    // ?man -c:str: print count or perform stdout action
 81    case 'c':
 82      console = EARGF(usage());
 83      break;
 84    default:
 85      usage();
 86  }
 87  ARGEND;
 88
 89  /* check number of args and if we are PID 1 */
 90  if (argc != 2 || getpid() != 1)
 91    usage();
 92
 93  /* chdir to newroot and make sure it's a different fs */
 94  if (chdir(argv[0]))
 95    eprintf("chdir %s:", argv[0]);
 96
 97  if (stat("/", &st))
 98    eprintf("stat %s:", "/");
 99
100  curdev = st.st_dev;
101  if (stat(".", &st))
102    eprintf("stat %s:", ".");
103  if (st.st_dev == curdev)
104    usage();
105
106  /* avoids trouble with real filesystems */
107  if (stat("/init", &st) || !S_ISREG(st.st_mode))
108    eprintf("/init is not a regular file\n");
109
110  statfs("/", &stfs);
111  if ((unsigned)stfs.f_type != RAMFS_MAGIC && (unsigned)stfs.f_type != TMPFS_MAGIC)
112    eprintf("current filesystem is not a RAMFS or TMPFS\n");
113
114  /* wipe / */
115  delete_content("/", curdev);
116
117  /* overmount / with newroot and chroot into it */
118  if (mount(".", "/", NULL, MS_MOVE, NULL))
119    eprintf("mount %s:", ".");
120
121  if (chroot("."))
122    eprintf("chroot failed\n");
123
124  /* if -c is set, redirect stdin/stdout/stderr to console */
125  if (console) {
126    close(0);
127    if (open(console, O_RDWR) == -1)
128      eprintf("open %s:", console);
129    dup2(0, 1);
130    dup2(0, 2);
131  }
132
133  /* execute init */
134  execv(argv[1], argv);
135  eprintf("can't execute '%s':", argv[1]);
136  return 1;
137}