summaryrefslogtreecommitdiffstats
path: root/loader2/init.c
diff options
context:
space:
mode:
authorJeremy Katz <katzj@redhat.com>2002-11-26 19:59:19 +0000
committerJeremy Katz <katzj@redhat.com>2002-11-26 19:59:19 +0000
commit32d18cdb5603ba71a27661cc59d78bfaf43a36cc (patch)
treecbef628263c93aba4f3dbb99573f0dc538db714f /loader2/init.c
parent48ad552b63a95aec286aba9cd91ec0a745152e95 (diff)
downloadanaconda-32d18cdb5603ba71a27661cc59d78bfaf43a36cc.tar.gz
anaconda-32d18cdb5603ba71a27661cc59d78bfaf43a36cc.tar.xz
anaconda-32d18cdb5603ba71a27661cc59d78bfaf43a36cc.zip
restructuring and clean up of some of the older unused code in the loader.
still a lot to do, but this gets to stage2 for cd, nfs, http, and ftp booting from either the cd initrd or the pxe initrd
Diffstat (limited to 'loader2/init.c')
-rw-r--r--loader2/init.c833
1 files changed, 833 insertions, 0 deletions
diff --git a/loader2/init.c b/loader2/init.c
new file mode 100644
index 000000000..d31e9742f
--- /dev/null
+++ b/loader2/init.c
@@ -0,0 +1,833 @@
+/*
+ * init.c
+ *
+ * This is the install type init
+ *
+ * Erik Troan (ewt@redhat.com)
+ *
+ * Copyright 1996 - 2002 Red Hat Software
+ *
+ * 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.
+ *
+ */
+
+#if USE_MINILIBC
+#include "minilibc.h"
+#ifndef SOCK_STREAM
+# define SOCK_STREAM 1
+#endif
+#else
+#include <ctype.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 <termios.h>
+
+/* Need to tell loop.h what the actual dev_t type is. */
+#undef dev_t
+#if defined(__alpha) || (defined(__sparc__) && defined(__arch64__))
+#define dev_t unsigned int
+#else
+#define dev_t unsigned short
+#endif
+#include <linux/loop.h>
+#undef dev_t
+#define dev_t dev_t
+
+#define syslog klogctl
+#endif
+
+struct unmountInfo {
+ char * name;
+ int mounted;
+ int loopDevice;
+ enum { FS, LOOP } what;
+} ;
+
+#include <linux/cdrom.h>
+
+#define KICK_FLOPPY 1
+#define KICK_BOOTP 2
+
+#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
+
+char * env[] = {
+ "PATH=/usr/bin:/bin:/sbin:/usr/sbin:/mnt/sysimage/bin:"
+ "/mnt/sysimage/usr/bin:/mnt/sysimage/usr/sbin:/mnt/sysimage/sbin",
+ /* we set a nicer ld library path specifically for bash -- a full
+ one makes anaconda unhappy */
+ "LD_LIBRARY_PATH=/lib:/usr/lib:/usr/X11R6/lib",
+ "HOME=/",
+ "TERM=linux",
+ "DEBUG=",
+ "TERMINFO=/etc/linux-terminfo",
+ "PYTHONPATH=/tmp/updates",
+ 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 printstr(char * string) {
+ write(1, string, strlen(string));
+}
+
+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
+}
+
+int doMke2fs(char * device, char * size) {
+ char * args[] = { "/usr/sbin/mke2fs", NULL, NULL, NULL };
+ int pid, status;
+
+ args[1] = device;
+ args[2] = size;
+
+ if (!(pid = fork())) {
+ /* child */
+ execve("/usr/sbin/mke2fs", args, env);
+ fatal_error(1);
+ }
+
+ wait4(-1, &status, 0, NULL);
+
+ return 0;
+}
+
+int hasNetConfiged(void) {
+ int rc;
+ int s;
+ struct ifconf configs;
+ struct ifreq devs[10];
+
+ #ifdef __i386__
+ return 0;
+ #endif
+
+ s = socket(AF_INET, SOCK_STREAM, 0);
+ if (s < 0) {
+ /* FIXME was perror*/
+ printf("error creating socket: %d\n", errno);
+ return 0;
+ } else {
+ /* this is just good enough to tell us if we have anything
+ configured */
+ configs.ifc_len = sizeof(devs);
+ configs.ifc_buf = (void *) devs;
+
+ rc = ioctl(s, SIOCGIFCONF, &configs);
+ if (rc < 0) {
+ /* FIXME was perror*/
+ printstr("SIOCGIFCONF");
+ return 0;
+ }
+ if (configs.ifc_len == 0) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+
+void doklog(char * fn) {
+ fd_set readset, unixs;
+ int in, out, i;
+ int log;
+ int s;
+ int sock = -1;
+ struct sockaddr_un sockaddr;
+ char buf[1024];
+ int readfd;
+
+ 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, buf, sizeof(buf));
+ if (i > 0) {
+ if (out >= 0) write(out, buf, i);
+ write(log, buf, i);
+ }
+ }
+
+ for (readfd = 0; readfd < 20; ++readfd) {
+ if (FD_ISSET(readfd, &readset) && FD_ISSET(readfd, &unixs)) {
+ i = read(readfd, buf, sizeof(buf));
+ if (i > 0) {
+ if (out >= 0) {
+ write(out, buf, i);
+ write(out, "\n", 1);
+ }
+
+ write(log, buf, i);
+ write(log, "\n", 1);
+ } 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) write(out, "error in accept\n", 16);
+ write(log, "error in accept\n", 16);
+ close(sock);
+ sock = -1;
+ } else {
+ FD_SET(readfd, &unixs);
+ }
+ }
+ }
+}
+
+int setupTerminal(int fd) {
+ struct winsize winsize;
+
+ 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);
+ }
+
+ env[ENV_TERM] = "TERM=vt100";
+
+ return 0;
+}
+
+void undoLoop(struct unmountInfo * fs, int numFs, int this);
+
+void undoMount(struct unmountInfo * fs, int numFs, int this) {
+ int len = strlen(fs[this].name);
+ int i;
+
+ if (!fs[this].mounted) return;
+ fs[this].mounted = 0;
+
+ /* unmount everything underneath this */
+ for (i = 0; i < numFs; i++) {
+ if (fs[i].name[len] == '/' &&
+ !strncmp(fs[this].name, fs[i].name, len)) {
+ if (fs[i].what == LOOP)
+ undoLoop(fs, numFs, i);
+ else
+ undoMount(fs, numFs, i);
+ }
+ }
+
+ printf("\t%s", fs[this].name);
+ /* don't need to unmount /tmp. it is busy anyway. */
+ if (!testing) {
+ if (umount2(fs[this].name, 0) < 0) {
+ printf(" umount failed (%d)", errno);
+ } else {
+ printf(" done");
+ }
+ }
+ printf("\n");
+}
+
+void undoLoop(struct unmountInfo * fs, int numFs, int this) {
+ int i;
+ int fd;
+
+ if (!fs[this].mounted) return;
+ fs[this].mounted = 0;
+
+ /* find the device mount */
+ for (i = 0; i < numFs; i++) {
+ if (fs[i].what == FS && (fs[i].loopDevice == fs[this].loopDevice))
+ break;
+ }
+
+ if (i < numFs) {
+ /* the device is mounted, unmount it (and recursively, anything
+ * underneath) */
+ undoMount(fs, numFs, i);
+ }
+
+ unlink("/tmp/loop");
+ mknod("/tmp/loop", 0600 | S_IFBLK, (7 << 8) | fs[this].loopDevice);
+ printf("\tdisabling /dev/loop%d", fs[this].loopDevice);
+ if ((fd = open("/tmp/loop", O_RDONLY, 0)) < 0) {
+ printf(" failed to open device: %d", errno);
+ } else {
+ if (!testing && ioctl(fd, LOOP_CLR_FD, 0))
+ printf(" LOOP_CLR_FD failed: %d", errno);
+ close(fd);
+ }
+
+ printf("\n");
+}
+
+void unmountFilesystems(void) {
+ int fd, size;
+ char buf[65535]; /* this should be big enough */
+ char * chptr, * start;
+ struct unmountInfo filesystems[500];
+ int numFilesystems = 0;
+ int i;
+ struct loop_info li;
+ char * device;
+ struct stat sb;
+
+ fd = open("/proc/mounts", O_RDONLY, 0);
+ if (fd < 1) {
+ /* FIXME: was perror */
+ printstr("failed to open /proc/mounts");
+ sleep(2);
+ return;
+ }
+
+ size = read(fd, buf, sizeof(buf) - 1);
+ buf[size] = '\0';
+
+ close(fd);
+
+ chptr = buf;
+ while (*chptr) {
+ device = chptr;
+ while (*chptr != ' ') chptr++;
+ *chptr++ = '\0';
+ start = chptr;
+ while (*chptr != ' ') chptr++;
+ *chptr++ = '\0';
+
+ if (strcmp(start, "/") && strcmp(start, "/tmp")) {
+ filesystems[numFilesystems].name = alloca(strlen(start) + 1);
+ strcpy(filesystems[numFilesystems].name, start);
+ filesystems[numFilesystems].what = FS;
+ filesystems[numFilesystems].mounted = 1;
+
+ stat(start, &sb);
+ if ((sb.st_dev >> 8) == 7) {
+ filesystems[numFilesystems].loopDevice = sb.st_dev & 0xf;
+ } else {
+ filesystems[numFilesystems].loopDevice = -1;
+ }
+
+ numFilesystems++;
+ }
+
+ while (*chptr != '\n') chptr++;
+ chptr++;
+ }
+
+ for (i = 0; i < 7; i++) {
+ unlink("/tmp/loop");
+ mknod("/tmp/loop", 0600 | S_IFBLK, (7 << 8) | i);
+ if ((fd = open("/tmp/loop", O_RDONLY, 0)) >= 0) {
+ if (!ioctl(fd, LOOP_GET_STATUS, &li) && li.lo_name[0]) {
+ filesystems[numFilesystems].name = alloca(strlen(li.lo_name)
+ + 1);
+ strcpy(filesystems[numFilesystems].name, li.lo_name);
+ filesystems[numFilesystems].what = LOOP;
+ filesystems[numFilesystems].mounted = 1;
+ filesystems[numFilesystems].loopDevice = i;
+ numFilesystems++;
+ }
+
+ close(fd);
+ }
+ }
+
+ for (i = 0; i < numFilesystems; i++) {
+ if (filesystems[i].what == LOOP) {
+ undoLoop(filesystems, numFilesystems, i);
+ }
+ }
+
+ for (i = 0; i < numFilesystems; i++) {
+ if (filesystems[i].mounted) {
+ undoMount(filesystems, numFilesystems, i);
+ }
+ }
+}
+
+void disableSwap(void) {
+ int fd;
+ char buf[4096];
+ int i;
+ char * start;
+ char * chptr;
+
+ if ((fd = open("/proc/swaps", O_RDONLY, 0)) < 0) return;
+
+ i = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+ if (i < 0) return;
+ buf[i] = '\0';
+
+ start = buf;
+ while (*start) {
+ while (*start != '\n' && *start) start++;
+ if (!*start) return;
+
+ start++;
+ if (*start != '/') return;
+ chptr = start;
+ while (*chptr && *chptr != ' ') chptr++;
+ if (!(*chptr)) return;
+ *chptr = '\0';
+ printf("\t%s", start);
+ if (swapoff(start))
+ printf(" failed (%d)", errno);
+ printf("\n");
+
+ start = chptr + 1;
+ }
+}
+
+void ejectCdrom(void) {
+ int ejectfd;
+ struct stat sb;
+
+ stat("/tmp/cdrom", &sb);
+
+ if ((sb.st_mode & S_IFBLK) == S_IFBLK) {
+ printf("ejecting /tmp/cdrom...");
+ if ((ejectfd = open("/tmp/cdrom", O_RDONLY | O_NONBLOCK, 0)) >= 0) {
+ if (ioctl(ejectfd, CDROMEJECT, 0))
+ printf("eject failed %d ", errno);
+ close(ejectfd);
+ } else {
+ printf("eject failed %d ", errno);
+ }
+ printf("\n");
+ }
+}
+
+int mystrstr(char *str1, char *str2) {
+ char *p;
+ int rc=0;
+
+ for (p=str1; *p; p++) {
+ if (*p == *str2) {
+ char *s, *t;
+
+ rc = 1;
+ for (s=p, t=str2; *s && *t; s++, t++)
+ if (*s != *t) {
+ rc = 0;
+ p++;
+ }
+
+ if (rc)
+ return rc;
+ }
+ }
+ return rc;
+}
+
+
+
+int main(int argc, char **argv) {
+ pid_t installpid, childpid;
+ int waitStatus;
+ int fd;
+ int nfsRoot = 0;
+ int roRoot = 0;
+ int cdRoot = 0;
+ int doReboot = 0;
+ int doShutdown =0;
+ int isSerial = 0;
+ int noKill = 0;
+#ifdef __alpha__
+ char * kernel;
+#endif
+ char * argvc[15];
+ char ** argvp = argvc;
+ char twelve = 12;
+ int i;
+ char buf[500];
+ int len;
+
+
+#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("Greetings.\n");
+
+ printf("Red Hat install 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("mounting /dev/pts (unix98 pty) filesystem... ");
+ if (!testing) {
+ if (mount("/dev/pts", "/dev/pts", "devpts", 0, NULL))
+ fatal_error(1);
+ }
+ printf("done\n");
+
+ signal(SIGINT, SIG_IGN);
+ signal(SIGTSTP, SIG_IGN);
+
+ /* these args are only for testing from commandline */
+ for (i = 1; i < argc; i++) {
+ if (!strcmp (argv[i], "serial")) {
+ isSerial = 1;
+ break;
+ }
+ }
+
+ /* 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 && mystrstr(buf, "nokill"))
+ noKill = 1;
+ }
+
+#if !defined(__s390__) && !defined(__s390x__)
+ if (ioctl (0, TIOCLINUX, &twelve) < 0) {
+ isSerial = 2;
+ }
+
+ if (isSerial) {
+ char *device = "/dev/ttyS0";
+
+ printf("Red Hat install init version %s using a serial console\n",
+ VERSION);
+
+ printf("remember, cereal is an important part of a nutritionally "
+ "balanced breakfast.\n\n");
+
+ 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 {
+ 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);
+
+ dup2(fd, 0);
+ dup2(fd, 1);
+ dup2(fd, 2);
+ close(fd);
+#endif
+
+ setsid();
+ if (ioctl(0, TIOCSCTTY, NULL)) {
+ printf("could not set new controlling tty\n");
+ }
+
+ if (!testing) {
+ sethostname("localhost.localdomain", 21);
+ /* the default domainname (as of 2.0.35) is "(none)", which confuses
+ glibc */
+ setdomainname("", 0);
+ }
+
+ printf("checking for NFS root filesystem...");
+ if (hasNetConfiged()) {
+ printf("yes\n");
+ roRoot = nfsRoot = 1;
+ } else {
+ printf("no\n");
+ }
+
+ if (!nfsRoot) {
+ printf("trying to remount root filesystem read write... ");
+ if (mount("/", "/", "ext2", MS_REMOUNT | MS_MGC_VAL, NULL)) {
+ printf("failed (but that's okay)\n");
+
+ roRoot = 1;
+ } else {
+ printf("done\n");
+
+ /* 2.0.18 (at least) lets us remount a CD r/w!! */
+ printf("checking for writeable /tmp... ");
+ fd = open("/tmp/tmp", O_WRONLY | O_CREAT, 0644);
+ if (fd < 0) {
+ printf("no (probably a CD rooted install)\n");
+ roRoot = 1;
+ } else {
+ close(fd);
+ unlink("/tmp/tmp");
+ printf("yes\n");
+ }
+ }
+ }
+
+ /* JKFIXME: bah, I don't like this but it has to stay like this until
+ * ramfs doesn't suck */
+#if !defined(__s390__) && !defined(__s390x__)
+#define RAMDISK_DEVICE "/dev/ram"
+#else
+#define RAMDISK_DEVICE "/dev/ram2"
+#endif
+
+ if (!testing && roRoot) {
+ printf("creating 300k of ramdisk space... ");
+ if (doMke2fs(RAMDISK_DEVICE, "300"))
+ fatal_error(0);
+
+ printf("done\n");
+
+ printf("mounting /tmp from ramdisk... ");
+ if (mount(RAMDISK_DEVICE, "/tmp", "ext2", 0, NULL))
+ fatal_error(1);
+
+ printf("done\n");
+
+ if (!nfsRoot) cdRoot = 1;
+ }
+
+ /* 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");
+
+ /* 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";
+ *argvp++ = NULL;
+
+ printf("running %s\n", argvc[0]);
+ execve(argvc[0], argvc, env);
+
+ exit(0);
+ }
+
+ while (!doShutdown) {
+ childpid = wait4(-1, &waitStatus, 0, NULL);
+
+ if (childpid == installpid)
+ doShutdown = 1;
+ }
+
+ if (!WIFEXITED(waitStatus) || WEXITSTATUS(waitStatus)) {
+ printf("install exited abnormally ");
+ if (WIFSIGNALED(waitStatus)) {
+ printf("-- received signal %d", WTERMSIG(waitStatus));
+ }
+ printf("\n");
+ } else {
+ doReboot = 1;
+ }
+
+ if (testing)
+ exit(0);
+
+ sync(); sync();
+
+ if (!testing && !noKill) {
+ printf("sending termination signals...");
+ kill(-1, 15);
+ sleep(2);
+ printf("done\n");
+
+ printf("sending kill signals...");
+ kill(-1, 9);
+ sleep(2);
+ printf("done\n");
+ }
+
+ printf("disabling swap...\n");
+ disableSwap();
+
+ printf("unmounting filesystems...\n");
+ unmountFilesystems();
+
+ ejectCdrom();
+
+ if (doReboot) {
+ printf("rebooting system\n");
+ sleep(2);
+
+#if USE_MINILIBC
+ reboot(0xfee1dead, 672274793, 0x1234567);
+#else
+# ifdef __alpha__
+ reboot(RB_HALT_SYSTEM);
+# else
+ reboot(RB_AUTOBOOT);
+# endif
+#endif
+ } else {
+ printf("you may safely reboot your system\n");
+ while (1);
+ }
+
+ exit(0);
+
+ return 0;
+}