/* * Guillaume Cottenceau (gc@mandrakesoft.com) * * Copyright 2000 MandrakeSoft * * This software may be freely redistributed under the terms of the GNU * public license. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ /* * Portions from Erik Troan (ewt@redhat.com) * * Copyright 1996 Red Hat Software * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config-stage1.h" #include "lomount.h" #include "tools.h" #include "common.h" #include "udev.h" #define RAMFS_MAGIC 0x858458f6 #define TMPFS_MAGIC 0x01021994 #ifndef MNT_DETACH /* sys/mount.h still doesn't define it */ #define MNT_DETACH 2 #endif /* * This need to safe symbols export */ #define fatal(m) init_fatal(m) #define warn(m) init_warn(m) char * const env[] = { "PATH=/usr/bin:/bin:/sbin:/usr/sbin", "HOME=/", "RUN_INITRD=1", "TERM=linux", "TERMINFO=/etc/terminfo", NULL }; char ** myenv = NULL; /* * Need to cooperate with make-inird scripts? * * 0=no, -1=yes. * * It is also turn on lazy initialization mode (after * /dev, /sys and /proc mounts, after udevd started). * This mode require make-initrd generated initramfs * image and patched make-initrd-propagator with hook * /scripts/pre/prepare/060-propagator. By default, * propagator is master to udev control. */ int cooperate_mode = 0; char *init_top[] = {"/sbin/init-top", "init-top", NULL}; char *init_premount[] = {"/sbin/init-premount", "init-premount", NULL}; char *init_bottom[] = {"/sbin/init-bottom", "init-bottom", NULL}; extern void stage1(); /* * this needs to handle the following cases: * * 1) run from a CD root filesystem * 2) run from a read only nfs rooted filesystem * 3) run from a floppy * 4) run from a floppy that's been loaded into a ramdisk */ void init_fatal(const char *msg) { printf("FATAL ERROR IN INIT: %s\nI can't recover from this, " "please reboot manually and send bugreport.\n", msg); for(;;) sleep(600); } void init_warn(const char *msg) { printf("W: %s\n", msg); } static int _mknod(const char *pathname, mode_t mode, dev_t dev) { int rc; rc = mknod(pathname, mode, dev); if (rc < 0 && errno == EEXIST) rc = 0; return rc; } /* fork to: * (1) watch /proc/kmsg and copy the stuff to /dev/tty4 * (2) listens to /dev/log and copy also this stuff (log from programs) */ static pid_t doklog() { int in, out, i; pid_t pid; char buf[1024]; /* open kernel message logger */ if ((in = open("/proc/kmsg", O_RDONLY, 0)) < 0) fatal("failed to open /proc/kmsg"); if (_mknod("/dev/tty4", S_IFCHR, MKDEV(4, 4)) < 0 || (out = open("/dev/tty4", O_WRONLY, 0)) < 0) fatal("failed to open /dev/tty4"); if ((pid = fork())) { if (pid < 0) fatal("doklog"); close(in); close(out); return pid; } /* child */ close(0); close(1); close(2); /* disable on-console syslog output */ klogctl(8, NULL, 1); for (;;) { if ((i = read(in, buf, sizeof(buf))) > 0) i = write(out, buf, i); } } pid_t spawn(char * const av[]) { pid_t pid; if ((pid = fork())) { if (pid < 0) fatal(av[0]); return pid; } else { execve(av[0], &av[1], env); perror(av[0]); exit(1); } } static void spawn_hook(char *av[]) { struct stat st; int status; if (stat(av[0], &st) || !S_ISREG(st.st_mode) || !(st.st_mode & S_IXUSR)) return; if (waitpid(spawn(av), &status, 0) < 0 || !(WIFEXITED(status))) fatal(av[0]); } static void take_env(int fd) { static char buf[PIPE_BUF]; char *p = buf; char **ep; int i; if ((i = read(fd, buf, sizeof(buf))) < 0) { warn("failed to read env from pipe"); myenv = (char **) env; return; } close(fd); buf[i] = '\0'; if ((ep = myenv = (char **) malloc(sizeof(char *) * 32)) == NULL) { warn("can't malloc env"); myenv = (char **) env; return; } do { *ep++ = p; p += strlen(p); } while(++p < buf+i); *ep = NULL; } /* initramfs cleaner */ static int nuke(const char *what); static int nuke_dirent(int len, const char *dir, const char *name, dev_t me) { int bytes = len+strlen(name)+2; char path[bytes]; struct stat st; #if 0 /* This is a way to suppress warning message about unused xlen */ int xlen = snprintf(path, bytes, "%s/%s", dir, name); assert(xlen < bytes); #else snprintf(path, bytes, "%s/%s", dir, name); #endif if ( lstat(path, &st) ) return ENOENT; /* Return 0 since already gone? */ if ( st.st_dev != me ) return 0; /* DO NOT recurse down mount points!!!!! */ return nuke(path); } /* Wipe the contents of a directory, but not the directory itself */ int nuke_dir(const char *what) { int len = strlen(what); DIR *dir; struct dirent *d; int err = 0; struct stat st; if ( lstat(what, &st) ) return errno; if ( !S_ISDIR(st.st_mode) ) return ENOTDIR; if ( !(dir = opendir(what)) ) { /* EACCES means we can't read it. Might be empty and removable; if not, the rmdir() in nuke() will trigger an error. */ return (errno == EACCES) ? 0 : errno; } while ( (d = readdir(dir)) ) { /* Skip . and .. */ if ( d->d_name[0] == '.' && (d->d_name[1] == '\0' || (d->d_name[1] == '.' && d->d_name[2] == '\0')) ) continue; err = nuke_dirent(len, what, d->d_name, st.st_dev); if ( err ) { closedir(dir); return err; } } closedir(dir); return 0; } static int nuke(const char *what) { int rv; int err = 0; rv = unlink(what); if ( rv < 0 ) { if ( errno == EISDIR ) { /* It's a directory. */ err = nuke_dir(what); if ( !err ) err = rmdir(what) ? errno : err; } else { err = errno; } } if ( err ) { errno = err; fatal(what); } return 0; } struct filesystem { char * dev; char * name; char * fs; int mounted; }; /* attempt to unmount all filesystems in /proc/mounts */ static void unmount_filesystems(void) { int fd, size; char buf[65535]; /* this should be big enough */ char *p; struct filesystem fs[500]; int numfs = 0; int i, nb; printf("unmounting filesystems...\n"); fd = open("/proc/mounts", O_RDONLY, 0); if (fd < 1) { warn("failed to open /proc/mounts"); sleep(2); return; } size = read(fd, buf, sizeof(buf) - 1); buf[size] = '\0'; close(fd); p = buf; while (*p) { fs[numfs].mounted = 1; fs[numfs].dev = p; while (*p != ' ') p++; *p++ = '\0'; fs[numfs].name = p; while (*p != ' ') p++; *p++ = '\0'; fs[numfs].fs = p; while (*p != ' ') p++; *p++ = '\0'; while (*p != '\n') p++; p++; if (strcmp(fs[numfs].name, "/") != 0) numfs++; /* skip if root, no need to take initrd root in account */ } /* Pixel's ultra-optimized sorting algorithm: multiple passes trying to umount everything until nothing moves anymore (a.k.a holy shotgun method) */ do { nb = 0; for (i = 0; i < numfs; i++) { /*printf("trying with %s\n", fs[i].name);*/ if (fs[i].mounted && umount(fs[i].name) == 0) { if (strncmp(fs[i].dev + sizeof("/dev/") - 1, "loop", sizeof("loop") - 1) == 0) del_loop(fs[i].dev); printf("\t%s\n", fs[i].name); fs[i].mounted = 0; nb++; } } } while (nb); for (i = nb = 0; i < numfs; i++) if (fs[i].mounted) { printf("\t%s umount failed\n", fs[i].name); if (strcmp(fs[i].fs, "ext2") == 0) nb++; /* don't count not-ext2 umount failed */ } if (nb) { fatal("failed to umount some filesystems\n"); } } int main(int argc, char **argv) { static const char localhost[] = "localhost.localdomain"; struct stat rst, cst, ist, pst; struct statfs sfs; pid_t pid, klogpid; sigset_t sig; int wait_status; int fd, fds[2]; char *init = NULL; /* auto-detect lazy initialization mode */ fd = open("/proc/mounts", O_RDONLY, 0); if (fd >= 0) { close(fd); cooperate_mode = -1; } fd = -1; if (!cooperate_mode) { if (mount("/proc", "/proc", "proc", 0, NULL)) fatal("failed to mount proc filesystem"); if (mount("sysfs", "/sys", "sysfs", 0, NULL)) fatal("failed to mount sysfs filesystem"); if (mount("udevfs", "/dev", "devtmpfs", 0, "size=8M,mode=0755")) { switch (errno) { case ENODEV: /* There is no devtmpfs for current kernel, try mount tmpfs */ if (mount("udev", "/dev", "tmpfs", 0, "size=8M,mode=0755")) fatal("failed to mount tmpfs filesystem"); case EBUSY: /* Don't mount /dev if it is already mounted */ break; default: fatal("failed to mount devtmpfs filesystem"); } } } /* ignore Control-C and keyboard stop signals */ sigemptyset(&sig); sigaddset(&sig, SIGINT); sigaddset(&sig, SIGTSTP); sigprocmask(SIG_BLOCK, &sig, NULL); if (!cooperate_mode) { if (_mknod("/dev/console", S_IFCHR, MKDEV(5, 1)) < 0 || (fd = open("/dev/console", O_RDWR, 0)) < 0) { fatal("failed to open /dev/console"); } dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); if (_mknod("/dev/null", S_IFCHR, MKDEV(1, 3)) < 0) fatal("failed to create /dev/null"); } /* I set me up as session leader (probably not necessary?) */ setsid(); if (ioctl(0, TIOCSCTTY, NULL)) { perror("TIOCSCTTY"); warn("could not set new controlling tty"); } if (sethostname(localhost, sizeof(localhost)) < 0) warn("could not set hostname"); /* the default domainname (as of 2.0.35) is "(none)", which confuses glibc */ if (setdomainname("", 0) < 0) warn("could not set domainname"); if (mkdirs_dev(".initramfs", "pts", "shm", NULL) < 0) fatal("mkdir\n"); klogpid = doklog(); spawn_hook(init_top); udev_start(); spawn_hook(init_premount); /* Go into normal init mode - keep going, and then do a orderly shutdown when: 1) install exits 2) we receive a SIGHUP */ printf("Running stage1...\n"); /* create a pipe for env passing */ if (pipe(fds) < 0) fatal("failed to create env pipe"); fcntl(fds[0], F_SETFD, 1); fcntl(fds[1], F_SETFD, 0); if ((pid = fork())) { if (pid < 0) fatal("Failed to spawn stage1"); close(fds[1]); while (pid != wait(&wait_status)); } else { stage1(); } if (!(WIFEXITED(wait_status))) { /* something went wrong */ printf("wait_status: %i, install exited abnormally ", wait_status); if (WIFSIGNALED(wait_status)) printf("-- received signal %d", WTERMSIG(wait_status)); printf("\n"); sync(); sync(); printf("sending termination signals..."); kill(-1, 15); sleep(2); printf("done\n"); printf("sending kill signals..."); kill(-1, 9); sleep(2); printf("done\n"); unmount_filesystems(); printf("you may safely reboot your system\n"); for (;;) sleep(600); } take_env(fds[0]); udev_stop(); spawn_hook(init_bottom); kill(klogpid, 9); waitpid(klogpid, &wait_status, 0); /* deallocate all unused consoles */ ioctl(0, VT_DISALLOCATE, 0); printf("Spawning init ..."); /* rest was seamlessy stolen from klibc */ /* First, change to the new root directory */ if (chdir(STAGE2_LOCATION)) fatal("chdir to new root"); if (stat("/bin/plymouth", &pst) == 0 ) { char * plymouth[] = { "/bin/plymouth", "plymouth", "--newroot=/root", NULL }; spawn(plymouth); } /* This is a potentially highly destructive program. Take some extra precautions. */ /* Make sure the current directory is not on the same filesystem as the root directory */ if (stat("/", &rst) || stat(".", &cst)) fatal("stat"); if ( rst.st_dev == cst.st_dev ) fatal("current directory on the same filesystem as the root"); /* The initramfs should have /init */ if ( stat("/init", &ist) || !S_ISREG(ist.st_mode) ) fatal("can't find /init on initramfs"); /* Make sure we're on a ramfs */ if ( statfs("/", &sfs) ) fatal("statfs /"); if ( sfs.f_type != RAMFS_MAGIC && sfs.f_type != TMPFS_MAGIC ) fatal("rootfs not a ramfs or tmpfs"); /* Okay, I think we should be safe... */ /* overmount image under new root if needed */ if ( statfs(IMAGE_LOCATION, &sfs) ) fatal("statfs() on "IMAGE_LOCATION); /* if something is mounted under IMAGE_LOCATION ? */ if ( sfs.f_type != RAMFS_MAGIC && sfs.f_type != TMPFS_MAGIC ) { if ( mount(IMAGE_LOCATION, "." IMAGE_LOCATION, NULL, MS_MOVE, NULL) ) fatal("overmounting "IMAGE_LOCATION); /* test for nested mount: disk or nfs with iso image */ if ( statfs(IMAGE_LOCATION, &sfs) ) fatal("statfs() on nested "IMAGE_LOCATION); if ( sfs.f_type != RAMFS_MAGIC && sfs.f_type != TMPFS_MAGIC ) if (umount2(IMAGE_LOCATION, MNT_DETACH)) fatal("lazy umounting nested "IMAGE_LOCATION); } umount("/sys"); umount("/proc/bus/usb"); umount("/proc"); if (mount("/dev", "./dev", NULL, MS_MOVE, NULL)) fatal("overmounting /dev"); /* Delete rootfs contents */ if (nuke_dir("/")) fatal("nuking initramfs contents"); /* Overmount the root */ if (mount(".", "/", NULL, MS_MOVE, NULL)) fatal("overmounting root"); /* chroot, chdir */ if (chroot(".") || chdir("/")) fatal("chroot"); /* Check for given init */ init = get_from_env("INIT", (const char* const*) myenv); if (init == NULL) init = STAGE2_BINNAME; if (stat(init, &ist) || !S_ISREG(ist.st_mode) || !(ist.st_mode & S_IXUSR)) fatal("can't find init on root fs"); /* Spawn init */ printf(" done.\n"); /* unblock signals */ sigprocmask(SIG_UNBLOCK, &sig, NULL); argv[0] = init; execve(argv[0], argv, myenv); fatal("stage2"); /* Failed to spawn init */ return 0; }