/* * 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 "config-stage1.h" #include "lomount.h" #include "tools.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 char * env[] = { "PATH=/usr/bin:/bin:/sbin:/usr/sbin", "HOME=/", "RUN_INITRD=1", "TERM=linux", "TERMINFO=/etc/terminfo", NULL }; char ** myenv = NULL; char *udevd[] = {"/sbin/udevd", "udevd", "--resolve-names=never", NULL}; char *udevtrigger[] = {"/sbin/udevadm", "udevadm", "trigger", NULL}; char *udevsettle[] = {"/sbin/udevadm", "udevadm", "settle", NULL}; 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(); void fatal(const char *) __attribute__((noreturn)); /* * 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 fatal(const char *msg) { printf("FATAL ERROR IN INIT: %s\nI can't recover from this," "please reboot manually and send bugreport.\n", msg); while (1); } void warn(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) */ pid_t doklog() { int in, out, i, ii; 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); while (1) if ((i = read(in, buf, sizeof(buf))) > 0) ii = write(out, buf, i); } pid_t spawn(char *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); } } 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]); } 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 = env; return; } close(fd); buf[i] = '\0'; if ((ep = myenv = malloc(sizeof(char *) * 32)) == NULL) { warn("can't malloc env"); myenv = 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]; int xlen; struct stat st; xlen = snprintf(path, bytes, "%s/%s", dir, name); /* assert(xlen < bytes); */ 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 */ static 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 */ 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) { struct stat rst, cst, ist, pst; struct statfs sfs; pid_t pid, klogpid, udevpid; sigset_t sig; int wait_status; int fd = -1; int fds[2]; char *init = NULL; 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 (statfs("/dev", &sfs)) fatal("statfs /dev"); /* Don't mount /dev if it is already mounted as tmpfs */ if (sfs.f_type != TMPFS_MAGIC) { /* Try mount devtmpfs into /dev */ if (mount("udevfs", "/dev", "devtmpfs", 0, "size=10M,mode=0755")) { if (errno == ENODEV) { /* There is no devtmpfs for current kernel, * try mount tmpfs */ if (mount("udev", "/dev", "tmpfs", 0, "size=10M,mode=0755")) fatal("failed to mount tmpfs filesystem"); } else 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 (_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.localdomain", sizeof("localhost.localdomain")) < 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 (mkdir("/dev/.initramfs", 0755) < 0 || mkdir("/dev/pts", 0755) < 0 || mkdir("/dev/shm", 0755) < 0) fatal("mkdir\n"); if ((fd = open("/proc/sys/kernel/hotplug", O_WRONLY, 0)) < 0 || write(fd, "\n", sizeof("\n")) < sizeof("\n")) fatal("/proc/sys/kernel/hotplug\n"); close(fd); klogpid = doklog(); spawn_hook(init_top); if (mkdir("/dev/.udev", 0755) < 0 || mkdir("/dev/.udev/db", 0755) < 0) fatal("/dev/.udev/db"); printf("Spawning udevd..."); udevpid = spawn(udevd); usleep(500); if (mkdir("/dev/.udev/queue", 0755) < 0 && errno != EEXIST) fatal("cannot create /dev/.udev/queue"); if (waitpid(spawn(udevtrigger), &wait_status, 0) < 0 || !(WIFEXITED(wait_status))) warn("udevtrigger"); if (waitpid(spawn(udevsettle), &wait_status, 0) < 0 || !(WIFEXITED(wait_status))) warn("udevsettle"); printf("done\n"); 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"); while (1); } take_env(fds[0]); if (waitpid(spawn(udevsettle), &wait_status, 0) < 0 || !(WIFEXITED(wait_status))) warn("udevsettle"); kill(udevpid, 9); waitpid(udevpid, &wait_status, 0); nuke_dir("/dev/.udev/queue"); if ((fd = open("/.udev_version", O_RDONLY, 0)) >0) { char buf[32]; int n = read(fd, buf, sizeof(buf)); close(fd); if ((fd = creat("/dev/.initramfs/udev_version", 0)) >0) { n = write(fd, buf, n); close(fd); } else fatal("creat()"); } 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", 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; }