#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Set a signal handler. */ #define SETSIG(sa, sig, fun, flags) \ do { \ sa.sa_handler = fun; \ sa.sa_flags = flags; \ sigemptyset(&sa.sa_mask); \ sigaction(sig, &sa, NULL); \ } while(0) #define REBOOT_TIMEOUT 20 #define ECHILD_TIMEOUT 5 const char *install_prog[] = { "/usr/sbin/install2", (char *) 0 }; const char *shell_prog[] = { "/bin/sh", (char *) 0 }; static sig_atomic_t got_ECHILD; static void chld_handler(int sig __attribute__ ((__unused__))) { int rc, status; int saved_errno = errno; while ((rc = waitpid(-1, &status, WNOHANG))) { if (rc < 0 && errno == ECHILD) { got_ECHILD = 1; break; } } errno = saved_errno; } static pid_t run(const char *cmd[]) { const char *exe = cmd[0]; pid_t pid; #ifdef DEBUG error(0, 0, "Executing %s", exe); #endif if ((pid = fork()) == -1) { error(0, errno, "fork"); return -1; } if (pid == 0) { execv(exe, (char *const *) cmd); error(0, errno, "%s: execv:", exe); _exit(EXIT_FAILURE); } return pid; } static int run_wait(const char *prog[]) { int status = 0; pid_t pid = run(prog); if (pid == -1) return EXIT_FAILURE; if (waitpid(pid, &status, 0) == -1) { error(0, errno, "waitpid"); return EXIT_FAILURE; } if (!status) return EXIT_SUCCESS; if (WIFEXITED(status)) error(0, 0, "%s: terminated with exit code %d", prog[0], WEXITSTATUS(status)); else if (WIFSIGNALED(status)) error(0, 0, "%s: terminated by signal %d", prog[0], WEXITSTATUS(status)); else error(0, 0, "%s: terminated, status=%d", prog[0], status); return EXIT_FAILURE; } static int is_cdrom_method(void) { const char *method = getenv("METHOD"); return method && !strcmp(method, "cdrom"); } static void rsleep(unsigned int sec) { struct timespec req = { sec, 0 }, rem; for (; (req.tv_sec || req.tv_nsec) && !got_ECHILD; req = rem) if (!nanosleep(&req, &rem)) break; } int linux_kbhit(void) { struct termios oldstuff; struct termios newstuff; struct timeval tv = {0,1}; /* set one microsecond timeout */ fd_set rdfds; int fd = fileno(stdin); /* is usually zero */ int retval; FD_ZERO(&rdfds); FD_SET(fd, &rdfds); tcgetattr(fd, &oldstuff); newstuff = oldstuff; /* save old attributes */ newstuff.c_lflag &= ~(ICANON | ECHO | IGNBRK); tcsetattr(fd, TCSANOW, &newstuff); /* set new attributes */ if (select(fd + 1, &rdfds, NULL, NULL, &tv) == -1) { perror("select"); exit(1); } if (FD_ISSET(fd, &rdfds)) { retval = 1; } else { retval = 0; } tcsetattr(fd, TCSANOW, &oldstuff); /* restore old attributes */ return retval; } static void countdown(void) { int i; fprintf(stderr, "%s", "\nYou may safely reboot your system\n"); fprintf(stderr, "%s", "Press any key to reboot\n"); fflush(stderr); /* Set countdown when METHOD = cdrom */ if (is_cdrom_method()) { for (i = REBOOT_TIMEOUT; i > 0; --i) { if (linux_kbhit()) break; fprintf(stderr, "\rAutomatic reboot after %d seconds... ", i); fflush(stderr); sleep(1); } } fprintf(stderr, "\r\033[K%s\n", "Automatic reboot in progress."); fflush(stderr); } static void killall(void) { int i; got_ECHILD = 0; fprintf(stderr, "Asking all remaining processes to terminate\n"); (void) kill(-1, SIGTERM); rsleep(1); fprintf(stderr, "Killing all remaining processes"); (void) kill(-1, SIGKILL); chld_handler(0); for (i = 0; i < ECHILD_TIMEOUT && !got_ECHILD; ++i) { rsleep(1); fputc('.', stderr); } fputc('\n', stderr); } static void __attribute__ ((noreturn)) sysreboot(void) { countdown(); if ((reboot(LINUX_REBOOT_CMD_RESTART)) == -1) error(EXIT_FAILURE, errno, "reboot"); for (;;) pause(); } static void mount_fs(void) { if (mount("proc", "/proc", "proc", 0UL, NULL) || mount("sysfs", "/sys", "sysfs", 0UL, NULL) || mount("tmpfs", "/tmp", "tmpfs", 0UL, NULL) || mount("tmpfs", "/mnt", "tmpfs", 0UL, NULL) || mkdir("/mnt/destination", 0700)) error(EXIT_FAILURE, errno, "mount failed"); } static int umount_once(void) { int unmounted = 0; FILE *f; struct mntent *e; if (!(f = fopen("/proc/mounts", "r"))) return 0; while ((e = getmntent(f))) { if (!strcmp(e->mnt_dir, "/")) continue; if (!umount2(e->mnt_dir, 0)) { ++unmounted; fprintf(stderr, "Unmounted: %s\n", e->mnt_dir); break; } } fclose(f); return unmounted; } static void list_mounted(void) { FILE *f; struct mntent *e; if (!(f = fopen("/proc/mounts", "r"))) return; while ((e = getmntent(f))) if (strcmp(e->mnt_dir, "/")) fprintf(stderr, "Remains mounted: %s\n", e->mnt_dir); fclose(f); } static void umount_fs(void) { while (umount_once()) ; } static void loop_change_fd(const char *device_path, const char *storage_path) { int device_fd = -1; int storage_fd = -1; do { if ((device_fd = open(device_path, O_RDONLY)) < 0) { error(0, errno, "open: %s", device_path); break; } if ((storage_fd = open(storage_path, O_RDONLY)) < 0) { error(0, errno, "open: %s", storage_path); break; } if (ioctl(device_fd, (unsigned long int) LOOP_CHANGE_FD, storage_fd) < 0) error(0, errno, "ioctl: LOOP_CHANGE_FD"); } while (0); close(device_fd); close(storage_fd); } static int copy_file(const char *src, const char *dst) { char rbuf[BUFSIZ]; char *wbuf; int ifd = -1, ofd = -1, rc = -1; ssize_t rlen, wlen; if ((ifd = open(src, O_RDONLY)) < 0) { error(0, errno, "open: %s", src); goto copy_file_exit; } if ((ofd = open(dst, O_CREAT | O_WRONLY, 0755)) < 0) { error(0, errno, "open: %s", dst); goto copy_file_exit; } while ((rlen = TEMP_FAILURE_RETRY(read(ifd, rbuf, sizeof rbuf))) > 0) { wlen = rlen; wbuf = rbuf; while (wlen > 0) { ssize_t len; len = TEMP_FAILURE_RETRY(write(ofd, wbuf, (size_t) wlen)); if (len < 0) { error(0, errno, "write: %s", dst); goto copy_file_exit; } wlen -= len; wbuf += len; } } if (rlen < 0) { error(0, errno, "read: %s", src); goto copy_file_exit; } if (fchmod(ofd, 0500)) { error(0, errno, "fchmod: %s", dst); goto copy_file_exit; } rc = 0; copy_file_exit: close(ofd); close(ifd); return rc; } static void reexec_self(const char *path) { const char *args[] = { path, (const char *) 0 }; if (copy_file("/proc/self/exe", path)) return; if (execv(path, (char *const *) args)) error(0, errno, "execv: %s", path); } static void do_install(const char *path) { int run_shell; #ifdef DEBUG run_shell = 1; #else run_shell = 0; #endif mount_fs(); if (run_wait(install_prog)) run_shell = 1; if (run_shell) { fprintf(stderr, "This shell remains here for debug purposes. Press Ctrl-D to exit.\n"); (void) run_wait(shell_prog); } killall(); reexec_self(path); } static void move_fs(const char *src, const char *dst) { if (mkdir(dst, 0700) || mount(src, dst, NULL, MS_MOVE, NULL)) error(0, errno, "move_fs: %s -> %s", src, dst); } #define pivot_root(new_root,put_old) syscall(SYS_pivot_root,new_root,put_old) static void switch_root(void) { const char *old_root = "/mnt/oldroot"; move_fs("/proc", "/mnt/proc"); move_fs("/sys", "/mnt/sys"); move_fs("/dev", "/mnt/dev"); if (mkdir(old_root, 0700) || pivot_root("/mnt", old_root) || chdir("/")) error(0, errno, "switch_root"); } int main(int ac, const char *av[]) { const char *tmp_init = "/mnt/init"; struct sigaction sa; if (geteuid() != 0) error(EXIT_FAILURE, EPERM, "init"); SETSIG(sa, SIGINT, SIG_IGN, SA_RESTART); SETSIG(sa, SIGTSTP, SIG_IGN, SA_RESTART); SETSIG(sa, SIGQUIT, SIG_IGN, SA_RESTART); SETSIG(sa, SIGCHLD, chld_handler, SA_RESTART); if (ac < 1 || strcmp(av[0], tmp_init)) do_install(tmp_init); switch_root(); umount_fs(); loop_change_fd("/dev/loop0", "/altinst"); umount_fs(); list_mounted(); sysreboot(); }