/* * 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 . * * Author(s): Erik Troan (ewt@redhat.com) * Jeremy Katz (katzj@redhat.com) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "init.h" #include "copy.h" #include "devt.h" #include "devices.h" #include "modules.h" #include "readvars.h" #include #include #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(__powerpc64__) || (defined(__sparc__) && defined(__arch64__)) "LD_LIBRARY_PATH=/lib64:/usr/lib64:/lib:/usr/lib", #else "LD_LIBRARY_PATH=/lib:/usr/lib", #endif "HOME=/root", "TERM=linux", "DEBUG=", "TERMINFO=/etc/linux-terminfo", "PYTHONPATH=/tmp/updates", "MALLOC_CHECK_=2", "MALLOC_PERTURB_=204", NULL }; static char *VIRTIO_PORT = "/dev/virtio-ports/org.fedoraproject.anaconda.log.0"; /* * 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 shutDown(int doKill, reboot_action rebootAction); static int getKillPolicy(void); static int getSyslog(gchar **, gchar **); static int onQEMU(void); struct termios ts; static int expected_exit = 0; static GHashTable *cmdline = NULL; static void doExit(int) __attribute__ ((noreturn)); static void doExit(int result) { expected_exit = 1; exit(result); } static void printstr(char * string) { int ret; ret = write(1, string, strlen(string)); } static void fatal_error(int usePerror) { printf("failed.\n"); printf("\nI can't recover from this.\n"); #if !defined(__s390__) && !defined(__s390x__) while (1) ; #endif } /* sets up and launches syslog */ static void startSyslog(void) { int conf_fd; int ret; gchar *addr = NULL, *virtiolog = NULL; const char *forward_tcp = "*.* @@"; const char *forward_format_tcp = "\n"; const char *forward_virtio = "*.* "; const char *forward_format_virtio = ";virtio_ForwardFormat\n"; /* update the config file with command line arguments first */ if (getSyslog(&addr, &virtiolog)) { conf_fd = open("/etc/rsyslog.conf", O_WRONLY|O_APPEND); if (conf_fd < 0) { printf("error opening /etc/rsyslog.conf: %d\n", errno); printf("syslog forwarding will not be enabled\n"); sleep(5); } else { if (addr != NULL) { ret = write(conf_fd, forward_tcp, strlen(forward_tcp)); ret = write(conf_fd, addr, strlen(addr)); ret = write(conf_fd, forward_format_tcp, strlen(forward_format_tcp)); } if (virtiolog != NULL) { ret = write(conf_fd, forward_virtio, strlen(forward_virtio)); ret = write(conf_fd, virtiolog, strlen(virtiolog)); ret = write(conf_fd, forward_format_virtio, strlen(forward_format_virtio)); } close(conf_fd); } } /* rsyslog is going to take care of things, so disable console logging */ klogctl(8, NULL, 1); /* now we really start the daemon. */ int status; status = system("/sbin/rsyslogd -c 4"); if (status < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) { printf("Unable to start syslog daemon.\n"); fatal_error(1); } } static int setupTerminal(int fd) { struct winsize winsize; gpointer value = NULL; 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); } if (!strcmp(ttyname(fd), "/dev/hvc0")) { /* using an HMC on a POWER system, use vt320 */ env[ENV_TERM] = "TERM=vt320"; } else { /* use the no-advanced-video vt100 definition */ env[ENV_TERM] = "TERM=vt100-nav"; /* unless the user specifies that they want utf8 */ if (g_hash_table_lookup_extended(cmdline, "utf8", NULL, &value)) { env[ENV_TERM] = "TERM=vt100"; } } return 0; } #if defined(__sparc__) 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) return 1; return memcmp(a->c_cc, b->c_cc, sizeof(a->c_cc)); } #endif #if !defined(__s390__) && !defined(__s390x__) && !defined(__sparc__) 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; /* unset the umask so devices are created with correct perms and not complemented by the previous umask call */ mode_t previous_umask = umask(0); 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); } /* Hurray for hacks, this stops /lib/udev/rules.d/65-md-incremental.rules from medling with mdraid sets. */ i = creat("/dev/.in_sysinit", 0644); close(i); /* Restore umask for minimal side affects */ umask(previous_umask); } 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(getKillPolicy(), REBOOT); } /* halt handler */ static void sigUsr1Handler(int signum) { termReset(); shutDown(getKillPolicy(), HALT); } /* poweroff handler */ static void sigUsr2Handler(int signum) { termReset(); shutDown(getKillPolicy(), POWEROFF); } static int getKillPolicy(void) { gpointer value = NULL; if (g_hash_table_lookup_extended(cmdline, "nokill", NULL, &value)) { return 0; } return 1; } /* * Detects the non-static part of rsyslog configuration. * * Remote TCP logging is enabled if syslog= is found on the kernel command * line. Remote virtio-serial logging is enabled if the declared virtio port * exists. */ static int getSyslog(gchar **addr, gchar **virtiolog) { gpointer value = NULL; int ret = 0; if (g_hash_table_lookup_extended(cmdline, "syslog", NULL, &value)) { *addr = (gchar *) value; /* address can be either a hostname or IPv4 or IPv6, with or without port; thus we only allow the following characters in the address: letters and digits, dots, colons, slashes, dashes and square brackets */ if (g_regex_match_simple("^[\\w.:/\\-\\[\\]]*$", *addr, 0, 0)) { ++ret; } else { /* malformed, disable use */ *addr = NULL; printf("The syslog= command line parameter is malformed and will be\n"); printf("ignored by the installer.\n"); sleep(5); } } if (onQEMU()) { /* look for virtio-serial logging on a QEMU machine. */ printf("Loading virtio_pci module... "); if (mlLoadModule("virtio_pci", NULL)) { fprintf(stderr, "Error loading virtio_pci module.\n"); sleep(5); } else { printf("done.\n"); } if (!access(VIRTIO_PORT, W_OK)) { /* that means we really have virtio-serial logging */ *virtiolog = VIRTIO_PORT; ++ret; } } return ret; } /* * Use anything you can find to determine if we are running on a QEMU virtual * machine. */ static int onQEMU(void) { const gchar *lookfor = "QEMU Virtual CPU"; gchar *contents = NULL; GError *fileErr = NULL; int ret = 0; if (!g_file_get_contents("/proc/cpuinfo", &contents, NULL, &fileErr)) { fprintf(stderr, "Unable to read /proc/cpuinfo.\n"); sleep(5); return 0; } if (strstr(contents, lookfor)) { ret = 1; } g_free(contents); return ret; } 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); } void initSegvHandler(int signum) { void *array[30]; size_t i; const char const * const errmsgs[] = { "init received SIG", "! Backtrace:\n", "init exited unexpectedly! Backtrace:\n", }; /* XXX This should really be in a glibc header somewhere... */ extern const char *const sys_sigabbrev[NSIG]; signal(signum, SIG_DFL); /* back to default */ if (signum == 0) { i = write(STDERR_FILENO, errmsgs[2], strlen(errmsgs[2])); } else { i = write(STDERR_FILENO, errmsgs[0], strlen(errmsgs[0])); i = write(STDERR_FILENO, sys_sigabbrev[signum], strlen(sys_sigabbrev[signum])); i = write(STDERR_FILENO, errmsgs[1], strlen(errmsgs[1])); } i = backtrace (array, 30); backtrace_symbols_fd(array, i, STDERR_FILENO); _exit(1); } void initExitHandler(void) { if (expected_exit) return; initSegvHandler(0); } static void setupBacktrace(void) { void *array; signal(SIGSEGV, initSegvHandler); signal(SIGABRT, initSegvHandler); atexit(initExitHandler); /* Turns out, there's an initializer at the top of backtrace() that * (on some arches) calls dlopen(). dlopen(), unsurprisingly, calls * malloc(). So, call backtrace() early in signal handler setup so * we can later safely call it from the signal handler itself. */ backtrace(&array, 1); } static char *setupMallocPerturb(char *value) { FILE *f; unsigned char x; size_t rc; char *ret = NULL; f = fopen("/dev/urandom", "r"); if (!f) return NULL; rc = fread(&x, 1, 1, f); fclose(f); if (rc < 1) return NULL; rc = asprintf(&ret, "MALLOC_PERTURB_=%hhu", x); if (rc < 0) return NULL; return ret; } /* these functions return a newly allocated string that never gets freed; * their lifetime is essentially that of main(), and we'd have to track which * are allocated and which aren't, which is pretty pointless... */ typedef char *(*setupEnvCallback)(char *entry); static void setupEnv(void) { struct { char *name; setupEnvCallback cb; } setupEnvCallbacks[] = { { "MALLOC_PERTURB_", setupMallocPerturb }, { NULL, NULL } }; int x; /* neither array is very big, so this algorithm isn't so bad. If env[] * gets bigger for some reason, we should probably just alphebatize both * (manually) and then only initialize y one time. */ for (x = 0; setupEnvCallbacks[x].name != NULL; x++) { int y; int l = strlen(setupEnvCallbacks[x].name) + 1; char cmpstr[l + 1]; strncpy(cmpstr, setupEnvCallbacks[x].name, l); strcat(cmpstr, "="); for (y = 0; env[y] != NULL; y++) { if (!strncmp(env[y], cmpstr, l)) { char *new = setupEnvCallbacks[x].cb(env[y] + l); if (new) env[y] = new; } } } } int main(int argc, char **argv) { pid_t installpid, childpid; int waitStatus; int fd = -1; int doShutdown =0; reboot_action shutdown_method = HALT; int isSerial = 0; gboolean isDevelMode = FALSE; char * console = NULL; int doKill = 1; char * argvc[15]; char ** argvp = argvc; char twelve = 12; struct serial_struct si; int i, disable_keys; int ret; gpointer value = NULL; if (!strncmp(basename(argv[0]), "poweroff", 8)) { printf("Running poweroff...\n"); fd = getInitPid(); if (fd > 0) kill(fd, SIGUSR2); doExit(0); } else if (!strncmp(basename(argv[0]), "halt", 4)) { printf("Running halt...\n"); fd = getInitPid(); if (fd > 0) kill(fd, SIGUSR1); doExit(0); } else if (!strncmp(basename(argv[0]), "reboot", 6)) { printf("Running reboot...\n"); fd = getInitPid(); if (fd > 0) kill(fd, SIGINT); doExit(0); } /* turn off screen blanking */ printstr("\033[9;0]"); printstr("\033[8]"); umask(022); /* set up signal handler */ setupBacktrace(); /* set up any environment variables that aren't totally static */ setupEnv(); printstr("\nGreetings.\n"); printf("anaconda installer init version %s starting\n", VERSION); printf("mounting /proc filesystem... "); if (mount("/proc", "/proc", "proc", 0, NULL)) fatal_error(1); printf("done\n"); cmdline = readvars_parse_file("/proc/cmdline"); /* check for development mode early */ if (g_hash_table_lookup_extended(cmdline, "devel", NULL, &value)) { printf("Enabling development mode - cores will be dumped\n"); isDevelMode = TRUE; } /* these args are only for testing from commandline */ for (i = 1; i < argc; i++) { if (!strcmp (argv[i], "serial")) { isSerial = 1; break; } } printf("creating /dev filesystem... "); if (mount("/dev", "/dev", "tmpfs", 0, NULL)) fatal_error(1); createDevices(); printf("done\n"); printf("starting udev..."); if ((childpid = fork()) == 0) { execl("/sbin/udevd", "/sbin/udevd", "--daemon", NULL); exit(1); } /* wait at least until the udevd process that we forked exits */ do { pid_t retpid; int waitstatus; retpid = wait(&waitstatus); if (retpid == -1) { if (errno == EINTR) continue; /* if the child exited before we called waitpid, we can get * ECHILD without anything really being wrong; we just lost * the race.*/ if (errno == ECHILD) break; printf("init: error waiting on udevd: %m\n"); exit(1); } else if ((retpid == childpid) && WIFEXITED(waitstatus)) { break; } } while (1); if (fork() == 0) { execl("/sbin/udevadm", "udevadm", "control", "--env=ANACONDA=1", NULL); exit(1); } printf("done\n"); printf("mounting /dev/pts (unix98 pty) filesystem... "); if (mount("/dev/pts", "/dev/pts", "devpts", 0, NULL)) fatal_error(1); printf("done\n"); printf("mounting /sys filesystem... "); if (mount("/sys", "/sys", "sysfs", 0, NULL)) fatal_error(1); printf("done\n"); /* if anaconda dies suddenly we are doomed, so at least make a coredump */ struct rlimit corelimit = { RLIM_INFINITY, RLIM_INFINITY}; ret = setrlimit(RLIMIT_CORE, &corelimit); if (ret) { perror("setrlimit failed - no coredumps will be available"); } doKill = getKillPolicy(); #if !defined(__s390__) && !defined(__s390x__) static struct termios orig_cmode; static int orig_flags; struct termios cmode, mode; int cfd; cfd = open("/dev/console", O_RDONLY); tcgetattr(cfd,&orig_cmode); orig_flags = fcntl(cfd, F_GETFL); 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); } } 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); } 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 tmpfs, 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 */ rename("/tmp", "/oldtmp"); mkdir("/tmp", 0755); printf("mounting /tmp as tmpfs... "); if (mount("none", "/tmp", "tmpfs", 0, "size=250m")) 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. */ startSyslog(); /* 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 (fork() == 0) { execl("/bin/dbus-uuidgen", "/bin/dbus-uuidgen", "--ensure", NULL); doExit(1); } if (fork() == 0) { execl("/bin/dbus-daemon", "/bin/dbus-daemon", "--system", NULL); doExit(1); } sleep(2); /* 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; } if (isDevelMode) { *argvp++ = "--devel"; } *argvp++ = NULL; printf("running %s\n", argvc[0]); execve(argvc[0], argvc, env); shutDown(1, HALT); } /* 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) { pid_t childpid; childpid = wait(&waitStatus); if (childpid == installpid) { doShutdown = 1; ioctl(0, VT_ACTIVATE, 1); } } if (!WIFEXITED(waitStatus) || (WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus))) { /* Restore terminal */ cfd = open("/dev/console", O_RDONLY); tcsetattr(cfd, TCSANOW, &orig_cmode); fcntl(cfd, F_SETFL, orig_flags); close(cfd); shutdown_method = DELAYED_REBOOT; printf("install exited abnormally [%d/%d] ", WIFEXITED(waitStatus), WEXITSTATUS(waitStatus)); if (WIFSIGNALED(waitStatus)) { printf("-- received signal %d", WTERMSIG(waitStatus)); } printf("\n"); /* If debug mode was requested, spawn shell */ if(isDevelMode) { pid_t shellpid; printf("Development mode requested spawning shell...\n"); if ((shellpid = fork()) == 0) { if (chdir("/root") == 0) { execl("/bin/bash", "/bin/bash", NULL); } else { perror("Unable to chdir to /root"); } } else if (shellpid > 0) { waitpid(shellpid, NULL, 0); } else { perror("Execution of debug shell failed."); } } } else { shutdown_method = REBOOT; } shutDown(doKill, shutdown_method); return 0; } /* vim:tw=78:ts=4:et:sw=4 */