diff options
Diffstat (limited to 'loader/init.c')
-rw-r--r-- | loader/init.c | 798 |
1 files changed, 798 insertions, 0 deletions
diff --git a/loader/init.c b/loader/init.c new file mode 100644 index 000000000..403789383 --- /dev/null +++ b/loader/init.c @@ -0,0 +1,798 @@ +/* + * init.c: This is the install type init + * + * Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 + * Red Hat, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author(s): Erik Troan (ewt@redhat.com) + * Jeremy Katz (katzj@redhat.com) + */ + +#if USE_MINILIBC +#include "minilibc.h" +#ifndef SOCK_STREAM +# define SOCK_STREAM 1 +#endif +#else +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <net/if.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/klog.h> +#include <sys/mount.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/swap.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/reboot.h> +#include <linux/vt.h> +#include <termios.h> +#include <libgen.h> + +#include "copy.h" +#include "devt.h" +#include "devices.h" + +#define syslog klogctl +#endif + +#include <asm/types.h> +#include <linux/cdrom.h> +#include <linux/serial.h> + +#ifndef MS_REMOUNT +#define MS_REMOUNT 32 +#endif + +#define ENV_PATH 0 +#define ENV_LD_LIBRARY_PATH 1 +#define ENV_HOME 2 +#define ENV_TERM 3 +#define ENV_DEBUG 4 +#define ENV_TERMINFO 5 +#define ENV_PYTHONPATH 6 +#define ENV_MALLOC_CHECK 7 +#define ENV_MALLOC_PERTURB 8 + +char * env[] = { + "PATH=/usr/bin:/bin:/sbin:/usr/sbin:/mnt/sysimage/bin:" + "/mnt/sysimage/usr/bin:/mnt/sysimage/usr/sbin:/mnt/sysimage/sbin:" + "/mnt/sysimage/usr/X11R6/bin", + + /* we set a nicer ld library path specifically for bash -- a full + one makes anaconda unhappy */ +#if defined(__x86_64__) || defined(__s390x__) || defined(__ppc64__) + "LD_LIBRARY_PATH=/lib64:/usr/lib64:/lib:/usr/lib", +#else + "LD_LIBRARY_PATH=/lib:/usr/lib", +#endif + "HOME=/", + "TERM=linux", + "DEBUG=", + "TERMINFO=/etc/linux-terminfo", + "PYTHONPATH=/tmp/updates", + "MALLOC_CHECK_=2", + "MALLOC_PERTURB_=204", + NULL +}; + +/* + * 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 + * + */ + +int testing=0; +void unmountFilesystems(void); +void disableSwap(void); +void shutDown(int noKill, int doReboot, int doPowerOff); +static int getNoKill(void); +struct termios ts; + +static void printstr(char * string) { + int ret; + ret = write(1, string, strlen(string)); +} + +static void fatal_error(int usePerror) { +/* FIXME */ +#if 0 + if (usePerror) + perror("failed:"); + else +#endif + printf("failed.\n"); + + printf("\nI can't recover from this.\n"); + if (testing) + exit(0); +#if !defined(__s390__) && !defined(__s390x__) + while (1) ; +#endif +} + +static int logChunk(int len, char *inbuf, char *outbuf) { + int inctr, outctr; + + for (inctr = 0, outctr = 0; inctr < len; inctr++) { + /* If the character is a NULL that's immediately followed by a open + * bracket, we've found the beginning of a new kernel message. Put in + * a line separator. + */ + if (inbuf[inctr] == '\0' && inctr+1 < len && inbuf[inctr+1] == '<') { + outbuf[outctr] = '\n'; + outctr++; + } + + /* Or, if we see a NULL right before the end of the chunk, that's also + * a good place to add a separator. + */ + else if (inbuf[inctr] == '\0' && inctr+1 == len) { + outbuf[outctr] = '\n'; + outctr++; + } + + /* Otherwise, simply output the character as long as it's not NULL. */ + else if (inbuf[inctr] != '\0') { + outbuf[outctr] = inbuf[inctr]; + outctr++; + } + } + + return outctr; +} + +static void doklog(char * fn) { + fd_set readset, unixs; + int in, out, i; + int log; + socklen_t s; + int sock = -1; + struct sockaddr_un sockaddr; + char inbuf[1024], outbuf[1024]; + int readfd; + int ret; + + in = open("/proc/kmsg", O_RDONLY,0); + if (in < 0) { + /* FIXME: was perror */ + printstr("open /proc/kmsg"); + return; + } + + out = open(fn, O_WRONLY, 0); + if (out < 0) + printf("couldn't open %s for syslog -- still using /tmp/syslog\n", fn); + + log = open("/tmp/syslog", O_WRONLY | O_CREAT, 0644); + if (log < 0) { + /* FIXME: was perror */ + printstr("error opening /tmp/syslog"); + sleep(5); + + close(in); + return; + } + + /* if we get this far, we should be in good shape */ + + if (fork()) { + /* parent */ + close(in); + close(out); + close(log); + return; + } + close(0); + close(1); + close(2); + + dup2(1, log); + +#if defined(USE_LOGDEV) + /* now open the syslog socket */ + sockaddr.sun_family = AF_UNIX; + strcpy(sockaddr.sun_path, "/dev/log"); + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + printf("error creating socket: %d\n", errno); + sleep(5); + } + printstr("got socket\n"); + if (bind(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr.sun_family) + + strlen(sockaddr.sun_path))) { + printf("bind error: %d\n", errno); + sleep(5); + } + printstr("bound socket\n"); + chmod("/dev/log", 0666); + if (listen(sock, 5)) { + printf("listen error: %d\n", errno); + sleep(5); + } +#endif + + syslog(8, NULL, 1); + + FD_ZERO(&unixs); + while (1) { + memcpy(&readset, &unixs, sizeof(unixs)); + + if (sock >= 0) + FD_SET(sock, &readset); + + FD_SET(in, &readset); + + i = select(20, &readset, NULL, NULL, NULL); + if (i <= 0) continue; + + if (FD_ISSET(in, &readset)) { + i = read(in, inbuf, sizeof(inbuf)); + if (i > 0) { + int loggedLen = logChunk(i, inbuf, outbuf); + + if (out >= 0) + ret = write(out, outbuf, loggedLen); + ret = write(log, outbuf, loggedLen); + } + } + + for (readfd = 0; readfd < 20; ++readfd) { + if (FD_ISSET(readfd, &readset) && FD_ISSET(readfd, &unixs)) { + i = read(readfd, inbuf, sizeof(inbuf)); + if (i > 0) { + int loggedLen = logChunk(i, inbuf, outbuf); + + if (out >= 0) + ret = write(out, outbuf, loggedLen); + + ret = write(log, outbuf, loggedLen); + } else if (i == 0) { + /* socket closed */ + close(readfd); + FD_CLR(readfd, &unixs); + } + } + } + + if (sock >= 0 && FD_ISSET(sock, &readset)) { + s = sizeof(sockaddr); + readfd = accept(sock, (struct sockaddr *) &sockaddr, &s); + if (readfd < 0) { + if (out >= 0) + ret = write(out, "error in accept\n", 16); + ret = write(log, "error in accept\n", 16); + close(sock); + sock = -1; + } else { + FD_SET(readfd, &unixs); + } + } + } +} + +static int setupTerminal(int fd) { + struct winsize winsize; + int fdn, len; + char buf[65535]; + + if (ioctl(fd, TIOCGWINSZ, &winsize)) { + printf("failed to get winsize"); + fatal_error(1); + } + + winsize.ws_row = 24; + winsize.ws_col = 80; + + if (ioctl(fd, TIOCSWINSZ, &winsize)) { + printf("failed to set winsize"); + fatal_error(1); + } + + /* use the no-advanced-video vt100 definition */ + env[ENV_TERM] = "TERM=vt100-nav"; + + /* unless the user specifies that they want utf8 */ + if ((fdn = open("/proc/cmdline", O_RDONLY, 0)) != -1) { + len = read(fdn, buf, sizeof(buf) - 1); + close(fdn); + if ((len > 0) && strstr(buf, "utf8")) + env[ENV_TERM] = "TERM=vt100"; + } + + return 0; +} + +#if !defined(__s390__) && !defined(__s390x__) +static int termcmp(struct termios *a, struct termios *b) { + if (a->c_iflag != b->c_iflag || a->c_oflag != b->c_oflag || + a->c_cflag != b->c_cflag || a->c_lflag != b->c_lflag || + a->c_ispeed != b->c_ispeed || a->c_ospeed != b->c_ospeed) + return 1; + return memcmp(a->c_cc, b->c_cc, sizeof(a->c_cc)); +} +#endif + +static void createDevices(void) { + int i; + + for (i = 0; devnodes[i].devname != NULL; i++) { + char devname[64]; + int type = -1; + + snprintf(devname, 63, "/dev/%s", devnodes[i].devname); + switch (devnodes[i].type) { + case DIRTYPE: + if (mkdir(devname, devnodes[i].perms) < 0) { + fprintf(stderr, "Unable to create directory %s: %m\n", + devname); + } + break; + case CHARDEV: + type = S_IFCHR; + break; + case BLOCKDEV: + type = S_IFBLK; + break; + } + if (type == -1) continue; + + if (mknod(devname, type | devnodes[i].perms, + makedev(devnodes[i].major, devnodes[i].minor)) < 0) + fprintf(stderr, "Unable to create device %s: %m\n", devname); + } +} + +static void termReset(void) { + /* change to tty1 */ + ioctl(0, VT_ACTIVATE, 1); + /* reset terminal */ + tcsetattr(0, TCSANOW, &ts); + /* Shift in, default color, move down 100 lines */ + /* ^O ^[[0m ^[[100E */ + printf("\017\033[0m\033[100E\n"); +} + +/* reboot handler */ +static void sigintHandler(int signum) { + termReset(); + shutDown(getNoKill(), 1, 0); +} + +/* halt handler */ +static void sigUsr1Handler(int signum) { + termReset(); + shutDown(getNoKill(), 0, 0); +} + +/* poweroff handler */ +static void sigUsr2Handler(int signum) { + termReset(); + shutDown(getNoKill(), 0, 1); +} + +static int getNoKill(void) { + int fd; + int len; + char buf[1024]; + + /* look through /proc/cmdline for special options */ + if ((fd = open("/proc/cmdline", O_RDONLY,0)) > 0) { + len = read(fd, buf, sizeof(buf) - 1); + close(fd); + if ((len > 0) && strstr(buf, "nokill")) + return 1; + } + return 0; +} + +static int getInitPid(void) { + int fd = 0, pid = -1, ret; + char * buf = calloc(1, 10); + + fd = open("/var/run/init.pid", O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Unable to find pid of init!!!\n"); + return -1; + } + ret = read(fd, buf, 9); + close(fd); + ret = sscanf(buf, "%d", &pid); + return pid; +} + +static void copyErrorFn (char *msg) { + printf(msg); +} + +int main(int argc, char **argv) { + pid_t installpid, childpid; + int waitStatus; + int fd = -1; + int doReboot = 0; + int doShutdown =0; + int isSerial = 0; + char * console = NULL; + int noKill = 0; + char * argvc[15]; + char ** argvp = argvc; + char twelve = 12; + struct serial_struct si; + int i, disable_keys; + + if (!strncmp(basename(argv[0]), "poweroff", 8)) { + printf("Running poweroff...\n"); + fd = getInitPid(); + if (fd > 0) + kill(fd, SIGUSR2); + exit(0); + } else if (!strncmp(basename(argv[0]), "halt", 4)) { + printf("Running halt...\n"); + fd = getInitPid(); + if (fd > 0) + kill(fd, SIGUSR1); + exit(0); + } else if (!strncmp(basename(argv[0]), "reboot", 6)) { + printf("Running reboot...\n"); + fd = getInitPid(); + if (fd > 0) + kill(fd, SIGINT); + exit(0); + } + +#if !defined(__s390__) && !defined(__s390x__) + testing = (getppid() != 0) && (getppid() != 1); +#endif + + if (!testing) { + /* turn off screen blanking */ + printstr("\033[9;0]"); + printstr("\033[8]"); + } else { + printstr("(running in test mode).\n"); + } + + umask(022); + + printstr("\nGreetings.\n"); + + printf("anaconda installer init version %s starting\n", VERSION); + + printf("mounting /proc filesystem... "); + if (!testing) { + if (mount("/proc", "/proc", "proc", 0, NULL)) + fatal_error(1); + } + printf("done\n"); + + printf("creating /dev filesystem... "); + if (!testing) { + if (mount("/dev", "/dev", "tmpfs", 0, NULL)) + fatal_error(1); + createDevices(); + printf("starting udev..."); + if (fork() == 0) { + execl("/sbin/udevd", "/sbin/udevd","--daemon",NULL); + exit(1); + } + } + printf("done\n"); + + printf("mounting /dev/pts (unix98 pty) filesystem... "); + if (!testing) { + if (mount("/dev/pts", "/dev/pts", "devpts", 0, NULL)) + fatal_error(1); + } + printf("done\n"); + + printf("mounting /sys filesystem... "); + if (!testing) { + if (mount("/sys", "/sys", "sysfs", 0, NULL)) + fatal_error(1); + } + printf("done\n"); + + /* these args are only for testing from commandline */ + for (i = 1; i < argc; i++) { + if (!strcmp (argv[i], "serial")) { + isSerial = 1; + break; + } + } + + noKill = getNoKill(); + +#if !defined(__s390__) && !defined(__s390x__) + static struct termios orig_cmode; + struct termios cmode, mode; + int cfd; + + cfd = open("/dev/console", O_RDONLY); + tcgetattr(cfd,&orig_cmode); + close(cfd); + + cmode = orig_cmode; + cmode.c_lflag &= (~ECHO); + + cfd = open("/dev/console", O_WRONLY); + tcsetattr(cfd,TCSANOW,&cmode); + close(cfd); + + /* handle weird consoles */ +#if defined(__powerpc__) + char * consoles[] = { "/dev/hvc0", /* hvc for JS20 */ + + "/dev/hvsi0", "/dev/hvsi1", + "/dev/hvsi2", /* hvsi for POWER5 */ + NULL }; +#elif defined (__ia64__) + char * consoles[] = { "/dev/ttySG0", "/dev/xvc0", "/dev/hvc0", NULL }; +#elif defined (__i386__) || defined (__x86_64__) + char * consoles[] = { "/dev/xvc0", "/dev/hvc0", NULL }; +#else + char * consoles[] = { NULL }; +#endif + for (i = 0; consoles[i] != NULL; i++) { + if ((fd = open(consoles[i], O_RDWR)) >= 0 && !tcgetattr(fd, &mode) && !termcmp(&cmode, &mode)) { + printf("anaconda installer init version %s using %s as console\n", + VERSION, consoles[i]); + isSerial = 3; + console = strdup(consoles[i]); + break; + } + close(fd); + } + + cfd = open("/dev/console", O_WRONLY); + tcsetattr(cfd,TCSANOW,&orig_cmode); + close(cfd); + + if ((fd < 0) && (ioctl (0, TIOCLINUX, &twelve) < 0)) { + isSerial = 2; + + if (ioctl(0, TIOCGSERIAL, &si) == -1) { + isSerial = 0; + } + } + + if (isSerial && (isSerial != 3)) { + char *device = "/dev/ttyS0"; + + printf("anaconda installer init version %s using a serial console\n", + VERSION); + + if (isSerial == 2) + device = "/dev/console"; + fd = open(device, O_RDWR, 0); + if (fd < 0) + device = "/dev/tts/0"; + + if (fd < 0) { + printf("failed to open %s\n", device); + fatal_error(1); + } + + setupTerminal(fd); + } else if (isSerial == 3) { + setupTerminal(fd); + } else if (fd < 0) { + fd = open("/dev/tty1", O_RDWR, 0); + if (fd < 0) + fd = open("/dev/vc/1", O_RDWR, 0); + + if (fd < 0) { + printf("failed to open /dev/tty1 and /dev/vc/1"); + fatal_error(1); + } + } + + if (testing) + exit(0); + + setsid(); + if (ioctl(0, TIOCSCTTY, NULL)) { + printf("could not set new controlling tty\n"); + } + + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + if (fd > 2) + close(fd); +#else + dup2(0, 1); + dup2(0, 2); +#endif + + /* disable Ctrl+Z, Ctrl+C, etc ... but not in rescue mode */ + disable_keys = 1; + if (argc > 1) + if (strstr(argv[1], "rescue")) + disable_keys = 0; + + if (disable_keys) { + tcgetattr(0, &ts); + ts.c_iflag &= ~BRKINT; + ts.c_iflag |= IGNBRK; + ts.c_iflag &= ~ISIG; + tcsetattr(0, TCSANOW, &ts); + } + + if (!testing) { + int ret; + ret = sethostname("localhost.localdomain", 21); + /* the default domainname (as of 2.0.35) is "(none)", which confuses + glibc */ + ret = setdomainname("", 0); + } + + printf("trying to remount root filesystem read write... "); + if (mount("/", "/", "ext2", MS_REMOUNT | MS_MGC_VAL, NULL)) { + fatal_error(1); + } + printf("done\n"); + + /* we want our /tmp to be ramfs, but we also want to let people hack + * their initrds to add things like a ks.cfg, so this has to be a little + * tricky */ + if (!testing) { + rename("/tmp", "/oldtmp"); + mkdir("/tmp", 0755); + + printf("mounting /tmp as ramfs... "); + if (mount("none", "/tmp", "ramfs", 0, NULL)) + fatal_error(1); + printf("done\n"); + + copyDirectory("/oldtmp", "/tmp", copyErrorFn, copyErrorFn); + unlink("/oldtmp"); + } + + /* Now we have some /tmp space set up, and /etc and /dev point to + it. We should be in pretty good shape. */ + + if (!testing) + doklog("/dev/tty4"); + + /* write out a pid file */ + if ((fd = open("/var/run/init.pid", O_WRONLY|O_CREAT, 0644)) > 0) { + char * buf = malloc(10); + int ret; + + snprintf(buf, 9, "%d", getpid()); + ret = write(fd, buf, strlen(buf)); + close(fd); + free(buf); + } else { + printf("unable to write init.pid (%d): %m\n", errno); + sleep(2); + } + + /* D-Bus */ + if (!testing) { + if (fork() == 0) { + execl("/sbin/dbus-uuidgen", "/sbin/dbus-uuidgen", "--ensure", NULL); + exit(1); + } + + if (fork() == 0) { + execl("/sbin/dbus-daemon", "/sbin/dbus-daemon", "--system", NULL); + exit(1); + } + } + + /* HAL daemon */ + if (!testing) { + if (fork() == 0) { + execl("/sbin/hald", "/sbin/hald", NULL); + exit(1); + } + } + + /* Go into normal init mode - keep going, and then do a orderly shutdown + when: + + 1) /bin/install exits + 2) we receive a SIGHUP + */ + + printf("running install...\n"); + + setsid(); + + if (!(installpid = fork())) { + /* child */ + *argvp++ = "/sbin/loader"; + + if (isSerial == 3) { + *argvp++ = "--virtpconsole"; + *argvp++ = console; + } + + *argvp++ = NULL; + + printf("running %s\n", argvc[0]); + execve(argvc[0], argvc, env); + + shutDown(1, 0, 0); + } + + /* signal handlers for halt/poweroff */ + signal(SIGUSR1, sigUsr1Handler); + signal(SIGUSR2, sigUsr2Handler); + + /* set up the ctrl+alt+delete handler to kill our pid, not pid 1 */ + signal(SIGINT, sigintHandler); + if ((fd = open("/proc/sys/kernel/cad_pid", O_WRONLY)) != -1) { + char buf[7]; + size_t count; + sprintf(buf, "%d", getpid()); + count = write(fd, buf, strlen(buf)); + close(fd); + /* if we succeeded in writing our pid, turn off the hard reboot + ctrl-alt-del handler */ + if (count == strlen(buf) && + (fd = open("/proc/sys/kernel/ctrl-alt-del", O_WRONLY)) != -1) { + int ret; + + ret = write(fd, "0", 1); + close(fd); + } + } + + while (!doShutdown) { + childpid = waitpid(-1, &waitStatus, 0); + + if (childpid == installpid) + doShutdown = 1; + } + + if (!WIFEXITED(waitStatus) || + (WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus))) { + printf("install exited abnormally [%d/%d] ", WIFEXITED(waitStatus), + WEXITSTATUS(waitStatus)); + if (WIFSIGNALED(waitStatus)) { + printf("-- received signal %d", WTERMSIG(waitStatus)); + } + printf("\n"); + } else { + doReboot = 1; + } + + if (testing) + exit(0); + + shutDown(noKill, doReboot, 0); + + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 ts=4: */ |