diff options
author | 2011-06-15 16:59:54 +0000 | |
---|---|---|
committer | 2011-06-15 16:59:54 +0000 | |
commit | 2590d96369d0217e31dc2812690dde61dac417b5 (patch) | |
tree | 82276f787b08a28548e342c7921486f1acefab9f /loader | |
parent | first commit (diff) | |
download | anaconda-2590d96369d0217e31dc2812690dde61dac417b5.tar.gz anaconda-2590d96369d0217e31dc2812690dde61dac417b5.tar.bz2 anaconda-2590d96369d0217e31dc2812690dde61dac417b5.zip |
Initial import from Sabayon (ver 0.9.9.56)
Diffstat (limited to 'loader')
71 files changed, 19302 insertions, 0 deletions
diff --git a/loader/.gitignore b/loader/.gitignore new file mode 100644 index 0000000..021a962 --- /dev/null +++ b/loader/.gitignore @@ -0,0 +1,12 @@ +ctype.c +mkctype +loader +init +debug.log +loader.tr +.depend +font.bgf.gz +loader.po +shutdown +checkisomd5 +tr diff --git a/loader/Makefile.am b/loader/Makefile.am new file mode 100644 index 0000000..18f1fbe --- /dev/null +++ b/loader/Makefile.am @@ -0,0 +1,118 @@ +# loader/Makefile.am for anaconda +# +# Copyright (C) 2009 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Author: David Cantrell <dcantrell@redhat.com> + +bootdir = $(libdir)/$(PACKAGE_NAME) +shareddir = $(datadir)/$(PACKAGE_NAME) + +boot_PROGRAMS = loader +shared_DATA = loader.tr +dist_shared_DATA = unicode-linedraw-chars.txt +noinst_PROGRAMS = mkctype dirbrowser +noinst_DATA = ctype.c +noinst_HEADERS = *.h + +if IS_S390 +boot_PROGRAMS += shutdown +dist_shared_SCRIPTS = linuxrc.s390 +else +boot_PROGRAMS += init +endif + +if IS_KEYMAPS_OVERRIDE_ARCH +keymapsdir = $(datadir)/$(PACKAGE_NAME) +keymaps_DATA = keymaps-override-$(ARCH) +endif + +COMMON_CFLAGS = -DUSE_LOGDEV -DVERSION='"$(PACKAGE_VERSION)"' + +loader_CFLAGS = $(COMMON_CFLAGS) $(GLIB_CFLAGS) $(LIBNM_GLIB_CFLAGS) \ + $(LIBCURL_CFLAGS) $(IPV6_CFLAGS) $(LIBARCHIVE_CFLAGS) \ + $(RPM_CFLAGS) -DINCLUDE_LOCAL -DINCLUDE_NETWORK +loader_LDADD = $(NEWT_LIBS) $(GLIB_LIBS) $(LIBNL_LIBS) \ + $(LIBNM_GLIB_LIBS) $(CHECKISOMD5_LIBS) \ + $(LIBCURL_LIBS) $(LIBARCHIVE_LIBS) $(RPM_LIBS) \ + $(ISCSI_LIBS) $(top_srcdir)/isys/libisys.la -lm +loader_SOURCES = loader.c copy.c moduleinfo.c loadermisc.c \ + modules.c windows.c lang.c kbd.c driverdisk.c \ + selinux.c mediacheck.c kickstart.c driverselect.c \ + getparts.c dirbrowser.c fwloader.c ibft.c hardware.c \ + method.c cdinstall.c hdinstall.c nfsinstall.c \ + urlinstall.c net.c urls.c telnet.c telnetd.c \ + rpmextract.c + +init_CFLAGS = $(COMMON_CFLAGS) $(GLIB_CFLAGS) +init_LDADD = $(GLIB_LIBS) +init_SOURCES = init.c undomounts.c shutdown.c copy.c + +shutdown_CFLAGS = $(COMMON_CFLAGS) -DAS_SHUTDOWN=1 +shutdown_SOURCES = shutdown.c undomounts.c + +mkctype_CFLAGS = $(COMMON_CFLAGS) +mkctype_SOURCES = mkctype.c + +dirbrowser_CFLAGS = $(COMMON_CFLAGS) -DSTANDALONE +dirbrowser_LDADD = $(NEWT_LIBS) +dirbrowser_SOURCES = dirbrowser.c + +EXTRA_DIST = simplemot keymaps-* + +CLEANFILES = keymaps-override-$(ARCH) ctype.c tr/*.tr + +MAINTAINERCLEANFILES = Makefile.in + +sed_verbose = $(sed_verbose_$(V)) +sed_verbose_ = $(sed_verbose_$(AM_DEFAULT_VERBOSITY)) +sed_verbose_0 = @echo " SED "$@; + +cp_verbose = $(cp_verbose_$(V)) +cp_verbose_ = $(cp_verbose_$(AM_DEFAULT_VERBOSITY)) +cp_verbose_0 = @echo " CP "$@; + +mkctype_verbose = $(mkctype_verbose_$(V)) +mkctype_verbose_ = $(mkctype_verbose_$(AM_DEFAULT_VERBOSITY)) +mkctype_verbose_0 = @echo " MAKE "$@; + +msgmerge_verbose = $(msgmerge_verbose_$(V)) +msgmerge_verbose_ = $(msgmerge_verbose_$(AM_DEFAULT_VERBOSITY)) +msgmerge_verbose_0 = echo " MERGE "$${lang}.po; + +xgettext_verbose = $(xgettext_verbose_$(V)) +xgettext_verbose_ = $(xgettext_verbose_$(AM_DEFAULT_VERBOSITY)) +xgettext_verbose_0 = @echo " GETTXT "$@; + +keymaps-override-$(ARCH): keymaps-$(ARCH) + $(cp_verbose)cp -p $< $@ + +ctype.c: mkctype + $(mkctype_verbose)./mkctype > ctype.c + +loader.tr: $(top_srcdir)/lang-table loader.po + @LANGS="`cut -f 2 $(top_srcdir)/lang-table | egrep -v '(^en$$)'`" ; \ + if [ ! -d tr ]; then \ + mkdir -p tr ; \ + fi ; \ + for lang in $$LANGS ; do \ + $(msgmerge_verbose)msgmerge -q $(top_srcdir)/po/$$lang.po loader.po | msgconv -t utf-8 | ./simplemot > tr/$$lang.tr ; \ + done ; \ + (cd tr ; ls -1 *.tr | cpio --quiet -Hcrc -o | gzip -9) > $@ + +loader.po: + $(xgettext_verbose)xgettext --default-domain=loader --add-comments \ + --keyword=_ --keyword=N_ *.c + $(sed_verbose)sed -i 's/charset=CHARSET/charset=UTF-8/' $@ diff --git a/loader/cdinstall.c b/loader/cdinstall.c new file mode 100644 index 0000000..9d5cee1 --- /dev/null +++ b/loader/cdinstall.c @@ -0,0 +1,509 @@ +/* + * cdinstall.c - code to set up cdrom installs + * + * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003 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> + * Matt Wilson <msw@redhat.com> + * Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + */ + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <newt.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> +#include <sys/ioctl.h> +#include <unistd.h> +/* FIXME Remove hack when: https://bugzilla.redhat.com/show_bug.cgi?id=478663 + is resolved */ +/* Hack both __BIG_ENDIAN and __LITTLE_ENDIAN get defined by glibc, the + kernel headers we need do not like this! */ +#if __BYTE_ORDER == __LITTLE_ENDIAN +#undef __BIG_ENDIAN +#else +#undef __LITTLE_ENDIAN +#endif +#include <asm/types.h> +#include <limits.h> +#include <linux/cdrom.h> + +#include "kickstart.h" +#include "loader.h" +#include "loadermisc.h" +#include "lang.h" +#include "modules.h" +#include "method.h" +#include "cdinstall.h" +#include "mediacheck.h" +#include "windows.h" + +#include "../isys/imount.h" +#include "../isys/isys.h" +#include "../isys/log.h" + +/* boot flags */ +extern uint64_t flags; + +/* ejects the CD device the device node points at */ +static void ejectCdrom(char *device) { + int ejectfd; + + if (!device) return; + logMessage(INFO, "ejecting %s...",device); + if ((ejectfd = open(device, O_RDONLY | O_NONBLOCK, 0)) >= 0) { + ioctl(ejectfd, CDROM_LOCKDOOR, 0); + if (ioctl(ejectfd, CDROMEJECT, 0)) + logMessage(ERROR, "eject failed on device %s: %m", device); + close(ejectfd); + } else { + logMessage(ERROR, "could not open device %s: %m", device); + } +} + +static char *cdrom_drive_status(int rc) { + struct { + int code; + char *str; + } status_codes[] = + { + { CDS_NO_INFO, "CDS_NO_INFO" }, + { CDS_NO_DISC, "CDS_NO_DISC" }, + { CDS_TRAY_OPEN, "CDS_TRAY_OPEN" }, + { CDS_DRIVE_NOT_READY, "CDS_DRIVE_NOT_READY" }, + { CDS_DISC_OK, "CDS_DISC_OK" }, + { CDS_AUDIO, "CDS_AUDIO" }, + { CDS_DATA_1, "CDS_DATA_1" }, + { CDS_DATA_2, "CDS_DATA_2" }, + { CDS_XA_2_1, "CDS_XA_2_1" }, + { CDS_XA_2_2, "CDS_XA_2_2" }, + { CDS_MIXED, "CDS_MIXED" }, + { INT_MAX, NULL }, + }; + int i; + + if (rc < 0) + return strerror(-rc); + + for (i = 0; status_codes[i].code != INT_MAX; i++) { + if (status_codes[i].code == rc) + return status_codes[i].str; + } + return NULL; +} + +static int waitForCdromTrayClose(int fd) { + int rc; + int prev = INT_MAX; + + do { + char *status = NULL; + rc = ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT); + if (rc < 0) + rc = -errno; + + /* only bother to print the status if it changes */ + if (prev == INT_MAX || prev != rc) { + status = cdrom_drive_status(rc); + if (status != NULL) { + logMessage(INFO, "drive status is %s", status); + } else { + logMessage(INFO, "drive status is unknown status code %d", rc); + } + } + prev = rc; + if (rc == CDS_DRIVE_NOT_READY) + usleep(100000); + } while (rc == CDS_DRIVE_NOT_READY); + return rc; +} + +static void closeCdromTray(char *device) { + int fd; + + if (!device || !*device) + return; + + logMessage(INFO, "closing CD tray on %s .", device); + if ((fd = open(device, O_RDONLY | O_NONBLOCK, 0)) >= 0) { + if (ioctl(fd, CDROMCLOSETRAY, 0)) { + logMessage(ERROR, "closetray failed on device %s: %m", device); + } else { + waitForCdromTrayClose(fd); + ioctl(fd, CDROM_LOCKDOOR, 1); + } + close(fd); + } else { + logMessage(ERROR, "could not open device %s: %m", device); + } +} + +/* Given cd device cddriver, this function will attempt to check its internal + * checksum. + */ +static void mediaCheckCdrom(char *cddriver) { + int rc; + int first; + + first = 1; + do { + char *descr; + char *tstamp; + int ejectcd; + + /* init every pass */ + ejectcd = 0; + descr = NULL; + + closeCdromTray(cddriver); + + /* if first time through, see if they want to eject the CD */ + /* currently in the drive (most likely the CD they booted from) */ + /* and test a different disk. Otherwise just test the disk in */ + /* the drive since it was inserted in the previous pass through */ + /* this loop, so they want it tested. */ + if (first) { + first = 0; + rc = newtWinChoice(_("Media Check"), _("Test"), _("Eject Disc"), + _("Choose \"%s\" to test the disc currently in " + "the drive, or \"%s\" to eject the disc and " + "insert another for testing."), _("Test"), + _("Eject Disc")); + + if (rc == 2) + ejectcd = 1; + } + + if (!ejectcd) { + /* XXX MSFFIXME: should check return code for error */ + readStampFileFromIso(cddriver, &tstamp, &descr); + doMediaCheck(cddriver, descr); + + if (descr) + free(descr); + } + + ejectCdrom(cddriver); + + rc = newtWinChoice(_("Media Check"), _("Test"), _("Continue"), + _("If you would like to test additional media, " + "insert the next disc and press \"%s\". " + "Testing each disc is not strictly required, however " + "it is highly recommended. Minimally, the discs should " + "be tested prior to using them for the first time. " + "After they have been successfully tested, it is not " + "required to retest each disc prior to using it again."), + _("Test"), _("Continue")); + + if (rc == 2) { + closeCdromTray(cddriver); + return; + } else { + continue; + } + } while (1); +} + +/* output an error message when CD in drive is not the correct one */ +/* Used by mountCdromStage2() */ +static void wrongCDMessage(void) { + newtWinMessage(_("Error"), _("OK"), + _("The %s disc was not found " + "in any of your drives. Please insert " + "the %s disc and press %s to retry."), + getProductName(), getProductName(), _("OK")); +} + +/* ask about doing media check */ +static void queryCDMediaCheck(char *dev, char *location) { + int rc; + char *stage2loc; + + /* dont bother to test in automated installs */ + if (FL_KICKSTART(flags) && !FL_MEDIACHECK(flags)) + return; + + /* see if we should check image(s) */ + /* in rescue mode only test if they explicitly asked to */ + if (!FL_RESCUE(flags) || FL_MEDIACHECK(flags)) { + startNewt(); + rc = newtWinChoice(_("Disc Found"), _("OK"), _("Skip"), + _("To begin testing the media before installation press %s.\n\n" + "Choose %s to skip the media test and start the installation."), + _("OK"), _("Skip")); + + if (rc != 2) { + /* We already mounted the CD earlier to verify there's at least a + * stage2 image. Now we need to unmount to perform the check, then + * remount to pretend nothing ever happened. + */ + umount(location); + mediaCheckCdrom(dev); + + do { + if (doPwMount(dev, location, "iso9660", "ro", NULL)) { + ejectCdrom(dev); + wrongCDMessage(); + continue; + } + + checked_asprintf(&stage2loc, "%s/images/install.img", + location); + + if (access(stage2loc, R_OK)) { + free(stage2loc); + umount(location); + ejectCdrom(dev); + wrongCDMessage(); + continue; + } + + free(stage2loc); + break; + } while (1); + } + } +} + +/* Set up a CD/DVD drive to mount the stage2 image from. If successful, the + * stage2 image will be left mounted on /mnt/runtime. + * + * location: Where to mount the media at (usually /mnt/stage2) + * loaderData: The usual, can be NULL if no info + * interactive: Whether or not to prompt about questions/errors + * mediaCheck: Do we run media check or not? + */ +static char *setupCdrom(char *location, struct loaderData_s *loaderData, + int interactive, int mediaCheck) { + int i, rc; + int stage2inram = 0; + char *retbuf = NULL, *stage2loc, *stage2img; + struct device ** devices; + char *cddev = NULL; + + devices = getDevices(DEVICE_CDROM); + if (!devices) { + logMessage(ERROR, "got to setupCdrom without a CD device"); + return NULL; + } + + checked_asprintf(&stage2loc, "%s/images/install.img", location); + + /* JKFIXME: ASSERT -- we have a cdrom device when we get here */ + do { + for (i = 0; devices[i]; i++) { + char *tmp = NULL; + int fd; + + if (!devices[i]->device) + continue; + + if (strncmp("/dev/", devices[i]->device, 5)) { + checked_asprintf(&tmp, "/dev/%s", devices[i]->device); + + free(devices[i]->device); + devices[i]->device = tmp; + } + + logMessage(INFO, "trying to mount CD device %s on %s", + devices[i]->device, location); + + if (!FL_CMDLINE(flags)) + winStatus(60, 3, _("Scanning"), _("Looking for installation images on CD device %s\n"), devices[i]->device); + else + printf(_("Looking for installation images on CD device %s"), devices[i]->device); + + fd = open(devices[i]->device, O_RDONLY | O_NONBLOCK); + if (fd < 0) { + logMessage(ERROR, "Couldn't open %s: %m", devices[i]->device); + if (!FL_CMDLINE(flags)) + newtPopWindow(); + continue; + } + + rc = waitForCdromTrayClose(fd); + close(fd); + switch (rc) { + case CDS_NO_INFO: + logMessage(ERROR, "Drive tray reports CDS_NO_INFO"); + break; + case CDS_NO_DISC: + if (!FL_CMDLINE(flags)) + newtPopWindow(); + continue; + case CDS_TRAY_OPEN: + logMessage(ERROR, "Drive tray reports open when it should be closed"); + break; + default: + break; + } + + if (!FL_CMDLINE(flags)) + newtPopWindow(); + + if (!(rc=doPwMount(devices[i]->device, location, "iso9660", "ro", NULL))) { + cddev = devices[i]->device; + if (!access(stage2loc, R_OK)) { + char *updpath; + + if (mediaCheck) + queryCDMediaCheck(devices[i]->device, location); + + /* if in rescue mode lets copy stage 2 into RAM so we can */ + /* free up the CD drive and user can have it avaiable to */ + /* aid system recovery. */ + if (FL_RESCUE(flags) && !FL_TEXT(flags) && + totalMemory() > MIN_GUI_RAM ) { + rc = copyFile(stage2loc, "/tmp/install.img"); + stage2img = strdup("/tmp/install.img"); + stage2inram = 1; + } else { + stage2img = strdup(stage2loc); + stage2inram = 0; + } + + rc = mountStage2(stage2img); + free(stage2img); + + if (rc) { + logMessage(INFO, "mounting stage2 failed"); + umount(location); + continue; + } + + checked_asprintf(&updpath, "%s/images/updates.img", location); + + logMessage(INFO, "Looking for updates in %s", updpath); + copyUpdatesImg(updpath); + free(updpath); + + checked_asprintf(&updpath, "%s/images/product.img", location); + + logMessage(INFO, "Looking for product in %s", updpath); + copyProductImg(updpath); + free(updpath); + + /* if in rescue mode and we copied stage2 to RAM */ + /* we can now unmount the CD */ + if (FL_RESCUE(flags) && stage2inram) { + umount(location); + } + + checked_asprintf(&retbuf, "cdrom://%s:%s", + devices[i]->device, location); + } else { + /* this wasnt the CD we were looking for, clean up and */ + /* try the next CD drive */ + umount(location); + } + } + } + + if (!retbuf) { + if (interactive) { + char * buf; + + checked_asprintf(&buf, _("The %s disc was not found in any of your " + "CDROM drives. Please insert the %s disc " + "and press %s to retry."), + getProductName(), getProductName(), _("OK")); + + ejectCdrom(cddev); + rc = newtWinChoice(_("Disc Not Found"), + _("OK"), _("Back"), buf, _("OK")); + free(buf); + if (rc == 2) + goto err; + } else { + /* we can't ask them about it, so just return not found */ + goto err; + } + } + } while (!retbuf); + +err: + free(stage2loc); + return retbuf; +} + +/* try to find a install CD non-interactively */ +char * findAnacondaCD(char *location) { + return setupCdrom(location, NULL, 0, 1); +} + +/* look for a CD and mount it. if we have problems, ask */ +char * mountCdromImage(struct installMethod * method, + char * location, struct loaderData_s * loaderData) { + return setupCdrom(location, loaderData, 1, 1); +} + +void setKickstartCD(struct loaderData_s * loaderData, int argc, char ** argv) { + + logMessage(INFO, "kickstartFromCD"); + loaderData->method = METHOD_CDROM; +} + +int kickstartFromCD(char *kssrc) { + int rc, i; + char *p, *kspath; + struct device ** devices; + + logMessage(INFO, "getting kickstart file from first CDROM"); + + devices = getDevices(DEVICE_CDROM); + /* usb can take some time to settle, even with the various hacks we + * have in place. some systems use portable USB CD-ROM drives, try to + * make sure there really isn't one before bailing */ + for (i = 0; !devices && i < 10; ++i) { + logMessage(INFO, "sleeping to wait for a USB CD-ROM"); + sleep(2); + devices = getDevices(DEVICE_CDROM); + } + if (!devices) { + logMessage(ERROR, "No CDROM devices found!"); + return 1; + } + + /* format is cdrom:[/path/to/ks.cfg] */ + kspath = ""; + p = strchr(kssrc, ':'); + if (p) + kspath = p + 1; + + if (!p || strlen(kspath) < 1) + kspath = "/ks.cfg"; + + for (i=0; devices[i]; i++) { + if (!devices[i]->device) + continue; + + rc = getKickstartFromBlockDevice(devices[i]->device, kspath); + if (rc == 0) + return 0; + } + + startNewt(); + newtWinMessage(_("Error"), _("OK"), + _("Cannot find kickstart file on CDROM.")); + return 1; +} + +/* vim:set shiftwidth=4 softtabstop=4 et */ diff --git a/loader/cdinstall.h b/loader/cdinstall.h new file mode 100644 index 0000000..a0ecbc3 --- /dev/null +++ b/loader/cdinstall.h @@ -0,0 +1,34 @@ +/* + * cdinstall.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef H_CDINSTALL +#define H_CDINSTALL + +#include "method.h" + +char * mountCdromImage(struct installMethod * method, + char * location, struct loaderData_s * loaderData); + +char * findAnacondaCD(char * location); + +void setKickstartCD(struct loaderData_s * loaderData, int argc, + char ** argv); + +int kickstartFromCD(char *kssrc); +#endif diff --git a/loader/copy.c b/loader/copy.c new file mode 100644 index 0000000..1c61233 --- /dev/null +++ b/loader/copy.c @@ -0,0 +1,141 @@ +/* + * copy.c - functions for copying files and directories + * + * Copyright (C) 2007 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/>. + */ + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "lang.h" + +/* Recursive */ +int copyDirectory(char * from, char * to, void (*warnFn)(char *), + void (*errorFn)(char *)) { + char *msg; + DIR * dir; + struct dirent * ent; + int fd, outfd; + char buf[4096]; + int i; + struct stat sb; + char filespec[256]; + char filespec2[256]; + char link[1024]; + + mkdir(to, 0755); + + if (!(dir = opendir(from))) { + if (errorFn) { + if (asprintf(&msg, N_("Failed to read directory %s: %m"), from) == -1) { + fprintf(stderr, "%s: %d: %m\n", __func__, __LINE__); + fflush(stderr); + abort(); + } + + errorFn(msg); + free(msg); + } + + return 1; + } + + errno = 0; + while ((ent = readdir(dir))) { + if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) + continue; + + sprintf(filespec, "%s/%s", from, ent->d_name); + sprintf(filespec2, "%s/%s", to, ent->d_name); + + lstat(filespec, &sb); + + if (S_ISDIR(sb.st_mode)) { + if (copyDirectory(filespec, filespec2, warnFn, errorFn)) { + closedir(dir); + return 1; + } + } else if (S_ISLNK(sb.st_mode)) { + i = readlink(filespec, link, sizeof(link) - 1); + link[i] = '\0'; + if (symlink(link, filespec2)) { + if (warnFn) { + if (asprintf(&msg, "Failed to symlink %s to %s: %m", + filespec2, link) == -1) { + fprintf(stderr, "%s: %d: %m\n", __func__, __LINE__); + fflush(stderr); + abort(); + } + + warnFn(msg); + free(msg); + } + } + } else { + fd = open(filespec, O_RDONLY); + if (fd == -1) { + if (errorFn) { + if (asprintf(&msg, "Failed to open %s: %m", filespec) == -1) { + fprintf(stderr, "%s: %d: %m\n", __func__, __LINE__); + fflush(stderr); + abort(); + } + + errorFn(msg); + free(msg); + } + + closedir(dir); + return 1; + } + outfd = open(filespec2, O_RDWR | O_TRUNC | O_CREAT, 0644); + if (outfd == -1) { + if (warnFn) { + if (asprintf(&msg, "Failed to create %s: %m", filespec2) == -1) { + fprintf(stderr, "%s: %d: %m\n", __func__, __LINE__); + fflush(stderr); + abort(); + } + + warnFn(msg); + free(msg); + } + } else { + fchmod(outfd, sb.st_mode & 07777); + + while ((i = read(fd, buf, sizeof(buf))) > 0) + i = write(outfd, buf, i); + close(outfd); + } + + close(fd); + } + + errno = 0; + } + + closedir(dir); + + return 0; +} diff --git a/loader/copy.h b/loader/copy.h new file mode 100644 index 0000000..1153bf8 --- /dev/null +++ b/loader/copy.h @@ -0,0 +1,26 @@ +/* + * copy.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef H_COPY +#define H_COPY + +int copyDirectory (char *from, char *to, void (*warnFn)(char *), + void (*errorFn)(char *)); + +#endif diff --git a/loader/devices.h b/loader/devices.h new file mode 100644 index 0000000..974e792 --- /dev/null +++ b/loader/devices.h @@ -0,0 +1,103 @@ +/* + * devices.h: handle declaration of devices to be created under /dev + * + * Copyright (C) 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/>. + */ + +#ifndef LOADER_INIT_DEVICES_H +#define LOADER_INIT_DEVICES_H + +struct devnode { + char * devname; + int type; + int major; + int minor; + int perms; + char * owner; + char * group; +}; + +#define CHARDEV 0 +#define BLOCKDEV 1 +#define DIRTYPE 2 + +struct devnode devnodes[] = { + /* consoles */ + {"console", CHARDEV, 5, 1, 0600, "root", "root"}, + {"ttyS0", CHARDEV, 4, 64, 0600, "root", "root"}, + {"ttyS1", CHARDEV, 4, 65, 0600, "root", "root"}, + {"ttyS2", CHARDEV, 4, 66, 0600, "root", "root"}, + {"ttyS3", CHARDEV, 4, 67, 0600, "root", "root"}, +#ifdef __ia64__ + {"ttySG0", CHARDEV, 204, 40, 0600, "root", "root"}, +#endif +#ifdef __powerpc__ + {"hvsi0", CHARDEV, 229, 128, 0600, "root", "root"}, + {"hvsi1", CHARDEV, 229, 129, 0600, "root", "root"}, + {"hvsi2", CHARDEV, 229, 130, 0600, "root", "root"}, +#endif + {"hvc0", CHARDEV, 229, 0, 0600, "root", "root"}, +#if defined(__i386__) || defined(__x86_64__) || defined(__ia64__) + {"xvc0", CHARDEV, 204, 191, 0600, "root", "root"}, +#endif + /* base unix */ + {"null", CHARDEV, 1, 3, 0666, "root", "root"}, + {"zero", CHARDEV, 1, 5, 0666, "root", "root"}, + {"mem", CHARDEV, 1, 1, 0600, "root", "root"}, + /* ttys */ + {"pts", DIRTYPE, 0, 0, 0755, "root", "root"}, + {"ptmx", CHARDEV, 5, 2, 0666, "root", "root"}, + {"tty", CHARDEV, 5, 0, 0666, "root", "root"}, + {"tty0", CHARDEV, 4, 0, 0600, "root", "tty"}, + {"tty1", CHARDEV, 4, 1, 0600, "root", "tty"}, + {"tty2", CHARDEV, 4, 2, 0600, "root", "tty"}, + {"tty3", CHARDEV, 4, 3, 0600, "root", "tty"}, + {"tty4", CHARDEV, 4, 4, 0600, "root", "tty"}, + {"tty5", CHARDEV, 4, 5, 0600, "root", "tty"}, + {"tty6", CHARDEV, 4, 6, 0600, "root", "tty"}, + {"tty7", CHARDEV, 4, 7, 0600, "root", "tty"}, + {"tty8", CHARDEV, 4, 8, 0600, "root", "tty"}, + {"tty9", CHARDEV, 4, 9, 0600, "root", "tty"}, + /* fb */ + {"fb0", CHARDEV, 29, 0, 0600, "root", "tty"}, + /* sparc specific */ +#ifdef __sparc__ + {"openprom", CHARDEV, 10, 139, 0644, "root", "root"}, + {"sunmouse", CHARDEV, 10, 6, 0644, "root", "root"}, + {"kbd", CHARDEV, 11, 0, 0644, "root", "root"}, +#endif + /* X */ + {"agpgart", CHARDEV, 10, 175, 0664, "root", "root"}, + {"psaux", CHARDEV, 10, 1, 0644, "root", "root"}, + {"input", DIRTYPE, 0, 0, 0755, "root", "root"}, + {"input/mice", CHARDEV, 13, 63, 0664, "root", "root"}, + /* floppies */ + {"fd0", BLOCKDEV, 2, 0, 0644, "root", "root"}, + {"fd1", BLOCKDEV, 2, 1, 0644, "root", "root"}, + /* random */ + {"random", CHARDEV, 1, 8, 0644, "root", "root"}, + {"urandom", CHARDEV, 1, 9, 0644, "root", "root"}, + /* mac stuff */ +#ifdef __powerpc__ + {"nvram", CHARDEV, 10, 144, 0644, "root", "root"}, + {"adb", CHARDEV, 56, 0, 0644, "root", "root"}, + {"iseries", DIRTYPE, 0, 0, 0755, "root", "root" }, +#endif + {"rtc", CHARDEV, 10, 135, 0644, "root", "root"}, + { NULL, 0, 0, 0, 0, NULL, NULL }, +}; + +#endif diff --git a/loader/devt.h b/loader/devt.h new file mode 100644 index 0000000..364a134 --- /dev/null +++ b/loader/devt.h @@ -0,0 +1,39 @@ +/* + * devt.h: handle declaration of dev_t to be sane for loopback purposes + * + * Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003 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/>. + */ + +#ifndef DEVT_H +#define DEVT_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 +#if defined(__x86_64__) +#define dev_t unsigned long +#else +#define dev_t unsigned short +#endif +#endif +#include <linux/loop.h> +#undef dev_t +#define dev_t dev_t + +#endif diff --git a/loader/dirbrowser.c b/loader/dirbrowser.c new file mode 100644 index 0000000..9199850 --- /dev/null +++ b/loader/dirbrowser.c @@ -0,0 +1,199 @@ +/* + * dirbrowser.c - newt-based directory browser to get a file name + * + * Copyright (C) 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): Jeremy Katz <katzj@redhat.com> + */ + +#include <newt.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <dirent.h> +#include <errno.h> +#include <string.h> +#include <sys/stat.h> + +#ifndef STANDALONE +#include "../isys/log.h" + +#include "loader.h" +#include "loadermisc.h" +#include "lang.h" +#endif + +#ifdef STANDALONE +#define _(x) x + +static int simpleStringCmp(const void * a, const void * b) { + const char * first = *((const char **) a); + const char * second = *((const char **) b); + + return strcmp(first, second); +} +#endif + +#define FSTEP 10 + +static char ** get_file_list(char * dirname, + int (*filterfunc)(char *, struct dirent *)) { + DIR * dir; + struct dirent *entry; + char ** files; + int numfiles = FSTEP, i = 0; + + dir = opendir(dirname); + if (dir == NULL) { + fprintf(stderr, "error opening %s: %m", dirname); + return NULL; + } + + files = malloc(numfiles * sizeof(char *)); + + while ((entry = readdir(dir))) { + if ((strlen(entry->d_name) == 1) && !strncmp(entry->d_name, ".", 1)) + continue; + if ((strlen(entry->d_name) == 2) && !strncmp(entry->d_name, "..", 2)) + continue; + if (filterfunc && filterfunc(dirname, entry)) + continue; + + files[i] = strdup(entry->d_name); + if (i++ >= (numfiles - 1)) { + numfiles += FSTEP; + files = realloc(files, numfiles * sizeof(char *)); + } + } + files[i] = NULL; + closedir(dir); + + qsort(files, i, sizeof(*files), simpleStringCmp); + return files; +} + +/* Browse through a directory structure looking for a file. + * Returns the full path to the file. + * + * Parameters: + * title: Title for newt dialog window + * dirname: Directory to use for root of browsing. NOTE: you cannot go + * up above this root. + * filterfunc: An (optional) function to filter out files based on whatever + * criteria you want. Returns 1 if it passes, 0 if not. + * Function should take arguments of the directory name and + * the dirent for the file. + */ +char * newt_select_file(char * title, char * text, char * dirname, + int (*filterfunc)(char *, struct dirent *)) { + char ** files; + char * fn = NULL; + int i, done = 0; + char * topdir = dirname; + char * dir = malloc(PATH_MAX); + char * path = NULL; + newtGrid grid, buttons; + newtComponent f, tb, listbox, ok, cancel; + struct stat sb; + struct newtExitStruct es; + + dir = realpath(dirname, dir); + + do { + files = get_file_list(dir, filterfunc); + + f = newtForm(NULL, NULL, 0); + grid = newtCreateGrid(1, 4); + + tb = newtTextboxReflowed(-1, -1, text, 60, 0, 10, 0); + + listbox = newtListbox(12, 65, 10, + NEWT_FLAG_SCROLL | NEWT_FLAG_RETURNEXIT); + + newtListboxSetWidth(listbox, 55); + buttons = newtButtonBar(_("OK"), &ok, _("Cancel"), &cancel, NULL); + newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, tb, + 0, 0, 0, 1, 0, 0); + newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, listbox, + 0, 0, 0, 1, 0, 0); + newtGridSetField(grid, 0, 3, NEWT_GRID_SUBGRID, buttons, + 0, 0, 0, 0, 0, NEWT_GRID_FLAG_GROWX); + + /* if this isn't our topdir, we want to let them go up a dir */ + if (strcmp(topdir, dir)) + newtListboxAppendEntry(listbox, "../", ".."); + + for (i = 0; (files[i] != NULL); i++) { + if ((files[i] == NULL) || (strlen(files[i]) == 0)) continue; + path = malloc(strlen(files[i]) + strlen(dir) + 2); + sprintf(path, "%s/%s", dir, files[i]); + stat(path, &sb); + free(path); + if (S_ISDIR(sb.st_mode)) { + char *dir = malloc(strlen(files[i]) + 2); + sprintf(dir, "%s/", files[i]); + newtListboxAppendEntry(listbox, dir, files[i]); + } else { + newtListboxAppendEntry(listbox, files[i], files[i]); + } + } + + newtGridWrappedWindow(grid, title); + newtGridAddComponentsToForm(grid, f, 1); + newtFormRun(f, &es); + + if (es.reason == NEWT_EXIT_COMPONENT && es.u.co == cancel) { + fn = NULL; + done = -1; + } else { + fn = (char *) newtListboxGetCurrent(listbox); + path = malloc(strlen(fn) + strlen(dir) + 2); + sprintf(path, "%s/%s", dir, fn); + + stat(path, &sb); + if (!S_ISDIR(sb.st_mode)) { + fn = path; + done = 1; + } else { + dir = realpath(path, dir); + free(path); + } + } + + newtGridFree(grid, 1); + newtFormDestroy(f); + newtPopWindow(); + } while (done == 0); + + return fn; +} + +#ifdef STANDALONE +int main(int argc, char ** argv) { + char * foo; + + newtInit(); + newtCls(); + + foo = newt_select_file("Get File Name", "foo, blah blah blah", + "/etc", NULL); + newtFinished(); + printf("got %s\n", foo); + return 0; +} +#endif diff --git a/loader/dirbrowser.h b/loader/dirbrowser.h new file mode 100644 index 0000000..7fb22f2 --- /dev/null +++ b/loader/dirbrowser.h @@ -0,0 +1,28 @@ +/* + * dirbrowser.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef DIRBROWSER_H +#define DIRBROWSER_H + +#include <dirent.h> + +char * newt_select_file(char * title, char * text, char * dirname, + int (*filterfunc)(char *, struct dirent *)); + +#endif diff --git a/loader/driverdisk.c b/loader/driverdisk.c new file mode 100644 index 0000000..22543b0 --- /dev/null +++ b/loader/driverdisk.c @@ -0,0 +1,892 @@ +/* + * driverdisk.c - driver disk functionality + * + * Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 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): Jeremy Katz <katzj@redhat.com> + */ + +#include <errno.h> +#include <fcntl.h> +#include <newt.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <glib.h> + +#include <blkid/blkid.h> + +#include <glob.h> +#include <rpm/rpmlib.h> +#include <sys/utsname.h> + +#include "copy.h" +#include "loader.h" +#include "loadermisc.h" +#include "lang.h" +#include "fwloader.h" +#include "method.h" +#include "modules.h" +#include "moduleinfo.h" +#include "windows.h" +#include "hardware.h" +#include "driverdisk.h" +#include "getparts.h" +#include "dirbrowser.h" + +#include "nfsinstall.h" +#include "urlinstall.h" + +#include "rpmextract.h" + +#include "../isys/isys.h" +#include "../isys/imount.h" +#include "../isys/eddsupport.h" +#include "../isys/log.h" + +/* boot flags */ +extern uint64_t flags; + +/* modprobe DD mode */ +int modprobeDDmode(void) +{ + FILE *f = fopen("/etc/depmod.d/ddmode.conf", "w"); + if (f) { + struct utsname unamedata; + + if (uname(&unamedata)) + fprintf(f, " pblacklist /lib/modules\n"); + else + fprintf(f, " pblacklist /lib/modules/%s\n", unamedata.release); + fclose(f); + } + + return f==NULL; +} + +int modprobeNormalmode(void) +{ + /* remove depmod overrides */ + if (unlink("/etc/depmod.d/ddmode.conf")) { + logMessage(ERROR, "removing ddmode.conf failed"); + return -1; + } + + /* run depmod to refresh modules db */ + if (system("depmod -a")) { + logMessage(ERROR, "depmod -a failed"); + return -1; + } + + return 0; +} + +/* + * check if the RPM in question provides + * Provides: userptr + * we use it to check kernel-modules-<kernelversion> + */ +int dlabelProvides(const char* dep, void *userptr) +{ + char *kernelver = (char*)userptr; + + logMessage(DEBUGLVL, "Provides: %s\n", dep); + + return strcmp(dep, kernelver); +} + +/* + * during cpio extraction, only extract files we need + * eg. module .ko files and firmware directory + */ +int dlabelFilter(const char* name, const struct stat *fstat, void *userptr) +{ + int l = strlen(name); + + logMessage(DEBUGLVL, "Unpacking %s\n", name); + + /* we want firmware files */ + if (!strncmp("lib/firmware/", name, 13)) return 0; + + if (l<3) + return 1; + l-=3; + + /* and we want only .ko files */ + if (strcmp(".ko", name+l)) + return 1; + + /* TODO we are unpacking kernel module, read it's description */ + + return 0; +} + +char* moduleDescription(const char* modulePath) +{ + char *command = NULL; + FILE *f = NULL; + char *description = NULL; + int size; + + checked_asprintf(&command, "modinfo --description '%s'", modulePath); + f = popen(command, "r"); + free(command); + + if (f==NULL) + return NULL; + + description = malloc(sizeof(char)*256); + if (!description) + return NULL; + + size = fread(description, 1, 255, f); + if (size == 0) { + free(description); + return NULL; + } + + description[size-1]=0; /* strip the trailing newline */ + pclose(f); + + return description; +} + +int globErrFunc(const char *epath, int eerrno) +{ + /* TODO check fatal errors */ + + return 0; +} + +int dlabelUnpackRPMDir(char* rpmdir, char* destination) +{ + char *kernelver; + struct utsname unamedata; + char *oldcwd; + char *globpattern; + int rc; + + /* get current working directory */ + oldcwd = getcwd(NULL, 0); + if (!oldcwd) { + logMessage(ERROR, "getcwd() failed: %m"); + return 1; + } + + /* set the cwd to destination */ + if (chdir(destination)) { + logMessage(ERROR, "We weren't able to CWD to \"%s\": %m", destination); + free(oldcwd); + return 1; + } + + /* get running kernel version */ + rc = uname(&unamedata); + checked_asprintf(&kernelver, "kernel-modules-%s", + rc ? "unknown" : unamedata.release); + logMessage(DEBUGLVL, "Kernel version: %s\n", kernelver); + + checked_asprintf(&globpattern, "%s/*.rpm", rpmdir); + glob_t globres; + char** globitem; + if (!glob(globpattern, GLOB_NOSORT|GLOB_NOESCAPE, globErrFunc, &globres)) { + /* iterate over all rpm files */ + globitem = globres.gl_pathv; + while (globres.gl_pathc>0 && globitem != NULL) { + explodeRPM(*globitem, dlabelFilter, dlabelProvides, NULL, kernelver); + } + globfree(&globres); + /* end of iteration */ + } + free(globpattern); + + /* restore CWD */ + if (chdir(oldcwd)) { + logMessage(WARNING, "We weren't able to restore CWD to \"%s\": %m", oldcwd); + } + + /* cleanup */ + free(kernelver); + free(oldcwd); + return rc; +} + + +static char * driverDiskFiles[] = { "repodata", NULL }; + +static int verifyDriverDisk(char *mntpt) { + char ** fnPtr; + char file[200]; + struct stat sb; + + /* check for dd descriptor */ + sprintf(file, "%s/rhdd3", mntpt); + if (access(file, R_OK)) { + logMessage(ERROR, "can't find driver disk identifier, bad " + "driver disk"); + return LOADER_BACK; + } + + /* side effect: file is still mntpt/ddident */ + stat(file, &sb); + if (!sb.st_size) + return LOADER_BACK; + for (fnPtr = driverDiskFiles; *fnPtr; fnPtr++) { + snprintf(file, 200, "%s/%s/%s", mntpt, getProductArch(), *fnPtr); + if (access(file, R_OK)) { + logMessage(ERROR, "cannot find %s, bad driver disk", file); + return LOADER_BACK; + } + } + + return LOADER_OK; +} + +static void copyWarnFn (char *msg) { + logMessage(WARNING, msg); +} + +static void copyErrorFn (char *msg) { + newtWinMessage(_("Error"), _("OK"), _(msg)); +} + +/* this copies the contents of the driver disk to a ramdisk and loads + * the moduleinfo, etc. assumes a "valid" driver disk mounted at mntpt */ +static int loadDriverDisk(struct loaderData_s *loaderData, char *mntpt) { + /* FIXME moduleInfoSet modInfo = loaderData->modInfo; */ + char file[200], dest[200], src[200]; + char *title; + char *fwdir = NULL; + struct moduleBallLocation * location; + struct stat sb; + static int disknum = 0; + int fd, ret; + + /* check for new version */ + sprintf(file, "%s/rhdd3", mntpt); + if (access(file, R_OK)) { + /* this can't happen, we already verified it! */ + return LOADER_BACK; + } + stat(file, &sb); + title = malloc(sb.st_size + 1); + + fd = open(file, O_RDONLY); + ret = read(fd, title, sb.st_size); + if (title[sb.st_size - 1] == '\n') + sb.st_size--; + title[sb.st_size] = '\0'; + close(fd); + + sprintf(file, DD_RPMDIR_TEMPLATE, disknum); + mkdirChain(file); + mkdirChain(DD_MODULES); + mkdirChain(DD_FIRMWARE); + + if (!FL_CMDLINE(flags)) { + startNewt(); + winStatus(40, 3, _("Loading"), _("Reading driver disk")); + } + + location = malloc(sizeof(struct moduleBallLocation)); + location->title = strdup(title); + checked_asprintf(&location->path, DD_MODULES); + + sprintf(dest, DD_RPMDIR_TEMPLATE, disknum); + sprintf(src, "%s/rpms/%s", mntpt, getProductArch()); + copyDirectory(src, dest, copyWarnFn, copyErrorFn); + + /* unpack packages from dest into location->path */ + if (dlabelUnpackRPMDir(dest, DD_EXTRACTED)) { + /* fatal error, log this and jump to exception handler */ + logMessage(ERROR, "Error unpacking RPMs from driver disc no.%d", + disknum); + goto loadDriverDiscException; + } + + /* run depmod to refresh modules db */ + if (system("depmod -a")) { + /* this is not really fatal error, it might still work, log it */ + logMessage(ERROR, "Error running depmod -a for driverdisc no.%d", disknum); + } + + checked_asprintf(&fwdir, DD_FIRMWARE); + if (!access(fwdir, R_OK|X_OK)) { + add_fw_search_dir(loaderData, fwdir); + stop_fw_loader(loaderData); + start_fw_loader(loaderData); + } + free(fwdir); + + /* TODO generate and read module info + * + * sprintf(file, "%s/modinfo", mntpt); + * readModuleInfo(file, modInfo, location, 1); + */ + +loadDriverDiscException: + + if (!FL_CMDLINE(flags)) + newtPopWindow(); + + disknum++; + return 0; +} + +/* Get the list of removable devices (floppy/cdrom) available. Used to + * find suitable devices for update disk / driver disk source. + * Returns the number of devices. ***devNames will be a NULL-terminated list + * of device names + */ +int getRemovableDevices(char *** devNames) { + struct device **devs; + int numDevices = 0; + int i = 0; + + devs = getDevices(DEVICE_DISK | DEVICE_CDROM); + + if (!devs) + return numDevices; + + for (i = 0; devs[i] ; i++) { + if (devs[i]->priv.removable) { + *devNames = realloc(*devNames, (numDevices + 2) * sizeof(char *)); + (*devNames)[numDevices] = strdup(devs[i]->device); + (*devNames)[numDevices+1] = NULL; + numDevices ++; + } + } + if (!numDevices) { + logMessage(ERROR, "no devices found to load drivers from"); + } + return numDevices; +} + +/* Prompt for loading a driver from "media" + * + * class: type of driver to load. + * usecancel: if 1, use cancel instead of back + */ +int loadDriverFromMedia(int class, struct loaderData_s *loaderData, + int usecancel, int noprobe) { + char * device = NULL, * part = NULL, * ddfile = NULL; + char ** devNames = NULL; + enum { DEV_DEVICE, DEV_PART, DEV_CHOOSEFILE, DEV_LOADFILE, + DEV_INSERT, DEV_LOAD, DEV_PROBE, + DEV_DONE } stage = DEV_DEVICE; + int rc, num = 0; + int dir = 1; + int found = 0, before = 0; + + while (stage != DEV_DONE) { + switch(stage) { + case DEV_DEVICE: + rc = getRemovableDevices(&devNames); + if (rc == 0) + return LOADER_BACK; + + /* we don't need to ask which to use if they only have one */ + if (rc == 1) { + device = strdup(devNames[0]); + free(devNames); + if (dir == -1) + return LOADER_BACK; + + stage = DEV_PART; + break; + } + dir = 1; + + startNewt(); + rc = newtWinMenu(_("Driver Disk Source"), + _("You have multiple devices which could serve " + "as sources for a driver disk. Which would " + "you like to use?"), 40, 10, 10, + rc < 6 ? rc : 6, devNames, + &num, _("OK"), + (usecancel) ? _("Cancel") : _("Back"), NULL); + + if (rc == 2) { + free(devNames); + return LOADER_BACK; + } + device = strdup(devNames[num]); + free(devNames); + + stage = DEV_PART; + case DEV_PART: { + char ** part_list = getPartitionsList(device); + int nump = 0, num = 0; + + /* Do not crash if the device disappeared */ + if (!part_list) { + stage = DEV_DEVICE; + break; + } + + if (part != NULL) + free(part); + + if ((nump = lenPartitionsList(part_list)) == 0) { + if (dir == -1) + stage = DEV_DEVICE; + else + stage = DEV_INSERT; + break; + } + dir = 1; + + startNewt(); + rc = newtWinMenu(_("Driver Disk Source"), + _("There are multiple partitions on this device " + "which could contain the driver disk image. " + "Which would you like to use?"), 40, 10, 10, + nump < 6 ? nump : 6, part_list, &num, _("OK"), + _("Back"), NULL); + + if (rc == 2) { + freePartitionsList(part_list); + stage = DEV_DEVICE; + dir = -1; + break; + } + + part = strdup(part_list[num]); + stage = DEV_CHOOSEFILE; + + } + + case DEV_CHOOSEFILE: { + if (part == NULL) { + logMessage(ERROR, "somehow got to choosing file with a NULL part, going back"); + stage = DEV_PART; + break; + } + /* make sure nothing is mounted when we get here */ + num = umount("/tmp/dpart"); + if (num == -1) { + logMessage(ERROR, "error unmounting: %m"); + if ((errno != EINVAL) && (errno != ENOENT)) + exit(1); + } + + logMessage(INFO, "trying to mount %s as partition", part); + if (doPwMount(part, "/tmp/dpart", "auto", "ro", NULL)) { + newtWinMessage(_("Error"), _("OK"), + _("Failed to mount partition.")); + stage = DEV_PART; + break; + } + + ddfile = newt_select_file(_("Select driver disk image"), + _("Select the file which is your driver " + "disk image."), + "/tmp/dpart", NULL); + if (ddfile == NULL) { + umount("/tmp/dpart"); + stage = DEV_PART; + dir = -1; + break; + } + dir = 1; + + stage = DEV_LOADFILE; + } + + case DEV_LOADFILE: { + if (ddfile == NULL) { + logMessage(DEBUGLVL, "trying to load dd from NULL"); + stage = DEV_CHOOSEFILE; + break; + } + if (dir == -1) { + umountLoopback("/tmp/drivers", "/dev/loop6"); + unlink("/tmp/drivers"); + ddfile = NULL; + stage = DEV_CHOOSEFILE; + break; + } + if (mountLoopback(ddfile, "/tmp/drivers", "/dev/loop6")) { + newtWinMessage(_("Error"), _("OK"), + _("Failed to load driver disk from file.")); + stage = DEV_CHOOSEFILE; + break; + } + stage = DEV_LOAD; + break; + } + + case DEV_INSERT: { + char * buf; + + checked_asprintf(&buf, + _("Insert your driver disk into /dev/%s " + "and press \"OK\" to continue."), device); + + rc = newtWinChoice(_("Insert Driver Disk"), _("OK"), _("Back"), + buf); + free(buf); + if (rc == 2) { + stage = DEV_DEVICE; + dir = -1; + break; + } + dir = 1; + + logMessage(INFO, "trying to mount %s", device); + if (doPwMount(device, "/tmp/drivers", "auto", "ro", NULL)) { + newtWinMessage(_("Error"), _("OK"), + _("Failed to mount driver disk.")); + stage = DEV_INSERT; + break; + } + + rc = verifyDriverDisk("/tmp/drivers"); + if (rc == LOADER_BACK) { + newtWinMessage(_("Error"), _("OK"), + _("Driver disk is invalid for this " + "release of %s."), getProductName()); + umount("/tmp/drivers"); + stage = DEV_INSERT; + break; + } + + stage = DEV_LOAD; + break; + } + case DEV_LOAD: { + struct device ** devices; + + before = 0; + found = 0; + + devices = getDevices(class); + if (devices) + for(; devices[before]; before++); + + rc = loadDriverDisk(loaderData, "/tmp/drivers"); + umount("/tmp/drivers"); + if (rc == LOADER_BACK) { + dir = -1; + if (ddfile != NULL) + stage = DEV_CHOOSEFILE; + else + stage = DEV_INSERT; + break; + } + /* fall through to probing */ + stage = DEV_PROBE; + + if (ddfile != NULL) { + umountLoopback("/tmp/drivers", "/dev/loop6"); + unlink("/tmp/drivers"); + umount("/tmp/dpart"); + } + } + + case DEV_PROBE: { + struct device ** devices; + + /* if they didn't specify that we should probe, then we should + * just fall out */ + if (noprobe) { + stage = DEV_DONE; + break; + } + + busProbe(0); + + devices = getDevices(class); + if (devices) + for(; devices[found]; found++); + + if (found > before) { + stage = DEV_DONE; + break; + } + + /* we don't have any more modules of the proper class. ask + * them to manually load */ + rc = newtWinTernary(_("Error"), _("Manually choose"), + _("Continue"), _("Load another disk"), + _("No devices of the appropriate type were " + "found on this driver disk. Would you " + "like to manually select the driver, " + "continue anyway, or load another " + "driver disk?")); + + if (rc == 2) { + /* if they choose to continue, just go ahead and continue */ + stage = DEV_DONE; + } else if (rc == 3) { + /* if they choose to load another disk, back to the + * beginning with them */ + stage = DEV_DEVICE; + } else { + rc = chooseManualDriver(class, loaderData); + /* if they go back from a manual driver, we'll ask again. + * if they load something, assume it's what we need */ + if (rc == LOADER_OK) { + stage = DEV_DONE; + } + } + + break; + } + + case DEV_DONE: + break; + } + } + + return LOADER_OK; +} + + +/* looping way to load driver disks */ +int loadDriverDisks(int class, struct loaderData_s *loaderData) { + int rc; + + rc = newtWinChoice(_("Driver disk"), _("Yes"), _("No"), + _("Do you have a driver disk?")); + if (rc != 1) + return LOADER_OK; + + rc = loadDriverFromMedia(DEVICE_ANY, loaderData, 1, 0); + if (rc == LOADER_BACK) + return LOADER_OK; + + do { + rc = newtWinChoice(_("More Driver Disks?"), _("Yes"), _("No"), + _("Do you wish to load any more driver disks?")); + if (rc != 1) + break; + loadDriverFromMedia(DEVICE_ANY, loaderData, 0, 0); + } while (1); + + return LOADER_OK; +} + +static void loadFromLocation(struct loaderData_s * loaderData, char * dir) { + if (verifyDriverDisk(dir) == LOADER_BACK) { + logMessage(ERROR, "not a valid driver disk"); + return; + } + + loadDriverDisk(loaderData, dir); + busProbe(0); +} + +void getDDFromSource(struct loaderData_s * loaderData, char * src) { + char *path = "/tmp/dd.img"; + int unlinkf = 0; + + if (!strncmp(src, "nfs:", 4)) { + unlinkf = 1; + if (getFileFromNfs(src + 4, "/tmp/dd.img", loaderData)) { + logMessage(ERROR, "unable to retrieve driver disk: %s", src); + return; + } + } else if (!strncmp(src, "ftp://", 6) || !strncmp(src, "http", 4)) { + unlinkf = 1; + if (getFileFromUrl(src, "/tmp/dd.img", loaderData)) { + logMessage(ERROR, "unable to retrieve driver disk: %s", src); + return; + } + /* FIXME: this is a hack so that you can load a driver disk from, eg, + * scsi cdrom drives */ +#if !defined(__s390__) && !defined(__s390x__) + } else if (!strncmp(src, "cdrom", 5)) { + loadDriverDisks(DEVICE_ANY, loaderData); + return; +#endif + } else if (!strncmp(src, "path:", 5)) { + path = src + 5; + } else { + newtWinMessage(_("Kickstart Error"), _("OK"), + _("Unknown driver disk kickstart source: %s"), src); + return; + } + + if (!mountLoopback(path, "/tmp/drivers", "/dev/loop6")) { + loadFromLocation(loaderData, "/tmp/drivers"); + umountLoopback("/tmp/drivers", "/dev/loop6"); + unlink("/tmp/drivers"); + if (unlinkf) unlink(path); + } + +} + +static void getDDFromDev(struct loaderData_s * loaderData, char * dev); + +void useKickstartDD(struct loaderData_s * loaderData, + int argc, char ** argv) { + char * dev = NULL; + char * biospart = NULL, * p = NULL; + gchar *fstype = NULL, *src = NULL; + gint usebiosdev = 0; + gchar **remaining = NULL; + GOptionContext *optCon = g_option_context_new(NULL); + GError *optErr = NULL; + GOptionEntry ksDDOptions[] = { + /* The --type option is deprecated and now has no effect. */ + { "type", 0, 0, G_OPTION_ARG_STRING, &fstype, NULL, NULL }, + { "source", 0, 0, G_OPTION_ARG_STRING, &src, NULL, NULL }, + { "biospart", 0, 0, G_OPTION_ARG_INT, &usebiosdev, NULL, NULL }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &remaining, + NULL, NULL }, + { NULL }, + }; + + g_option_context_set_help_enabled(optCon, FALSE); + g_option_context_add_main_entries(optCon, ksDDOptions, NULL); + + if (!g_option_context_parse(optCon, &argc, &argv, &optErr)) { + newtWinMessage(_("Kickstart Error"), _("OK"), + _("The following invalid argument was specified for " + "the kickstart driver disk command: %s"), + optErr->message); + g_error_free(optErr); + g_option_context_free(optCon); + g_strfreev(remaining); + return; + } + + g_option_context_free(optCon); + + if ((remaining != NULL) && (g_strv_length(remaining) == 1)) { + dev = remaining[0]; + } + + if (!dev && !src) { + logMessage(ERROR, "bad arguments to kickstart driver disk command"); + return; + } + + if (usebiosdev != 0) { + p = strchr(dev,'p'); + if (!p){ + logMessage(ERROR, "Bad argument for biospart"); + return; + } + *p = '\0'; + + biospart = getBiosDisk(dev); + if (biospart == NULL) { + logMessage(ERROR, "Unable to locate BIOS dev %s",dev); + return; + } + dev = malloc(strlen(biospart) + strlen(p + 1) + 2); + sprintf(dev, "%s%s", biospart, p + 1); + } + + if (dev) { + getDDFromDev(loaderData, dev); + } else { + getDDFromSource(loaderData, src); + } + + g_strfreev(remaining); + return; +} + +static void getDDFromDev(struct loaderData_s * loaderData, char * dev) { + if (doPwMount(dev, "/tmp/drivers", "auto", "ro", NULL)) { + logMessage(ERROR, "unable to mount driver disk %s", dev); + return; + } + + loadFromLocation(loaderData, "/tmp/drivers"); + umount("/tmp/drivers"); + unlink("/tmp/drivers"); +} + + +/* + * Look for partition with specific label (part of #316481) + */ +GSList* findDriverDiskByLabel(void) +{ + char *ddLabel = "OEMDRV"; + GSList *ddDevice = NULL; + blkid_cache bCache; + + int res; + blkid_dev_iterate bIter; + blkid_dev bDev; + + if (blkid_get_cache(&bCache, NULL)<0) { + logMessage(ERROR, "Cannot initialize cache instance for blkid"); + return NULL; + } + if ((res = blkid_probe_all(bCache))<0) { + logMessage(ERROR, "Cannot probe devices in blkid: %d", res); + return NULL; + } + if ((res = blkid_probe_all_removable(bCache))<0) { + logMessage(ERROR, "Cannot probe removable devices in blkid: %d", res); + } + + bIter = blkid_dev_iterate_begin(bCache); + blkid_dev_set_search(bIter, "LABEL", ddLabel); + while ((res = blkid_dev_next(bIter, &bDev)) == 0) { + bDev = blkid_verify(bCache, bDev); + if (!bDev) + continue; + + char *devname = strdup(blkid_dev_devname(bDev)); + logMessage(DEBUGLVL, "Adding driver disc %s to the list " + "of available DDs.", devname); + ddDevice = g_slist_prepend(ddDevice, (gpointer)devname); + /* Freeing bDev is taken care of by the put cache call */ + } + blkid_dev_iterate_end(bIter); + + blkid_put_cache(bCache); + + return ddDevice; +} + +int loadDriverDiskFromPartition(struct loaderData_s *loaderData, char* device) +{ + int rc; + + logMessage(INFO, "trying to mount %s", device); + if (doPwMount(device, "/tmp/drivers", "auto", "ro", NULL)) { + logMessage(ERROR, "Failed to mount driver disk."); + return -1; + } + + rc = verifyDriverDisk("/tmp/drivers"); + if (rc == LOADER_BACK) { + logMessage(ERROR, "Driver disk is invalid for this " + "release of %s.", getProductName()); + umount("/tmp/drivers"); + return -2; + } + + rc = loadDriverDisk(loaderData, "/tmp/drivers"); + umount("/tmp/drivers"); + if (rc == LOADER_BACK) { + return -3; + } + + return 0; +} + diff --git a/loader/driverdisk.h b/loader/driverdisk.h new file mode 100644 index 0000000..98bfd4a --- /dev/null +++ b/loader/driverdisk.h @@ -0,0 +1,54 @@ +/* + * driverdisk.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef DRIVERDISK_H +#define DRIVERDISK_H + +#include "loader.h" +#include "modules.h" +#include "moduleinfo.h" + +#define DD_RPMDIR_TEMPLATE "/tmp/DD-%d" +#define DD_EXTRACTED "/tmp/DD" +#define DD_MODULES "/tmp/DD/lib/modules" +#define DD_FIRMWARE "/tmp/DD/lib/firmware" + +extern char *ddFsTypes[]; + +int loadDriverFromMedia(int class, struct loaderData_s *loaderData, + int usecancel, int noprobe); + +int loadDriverDisks(int class, struct loaderData_s *loaderData); + +int getRemovableDevices(char *** devNames); + +int chooseManualDriver(int class, struct loaderData_s *loaderData); +void useKickstartDD(struct loaderData_s * loaderData, int argc, + char ** argv); + +void getDDFromSource(struct loaderData_s * loaderData, char * src); + +int loadDriverDiskFromPartition(struct loaderData_s *loaderData, char* device); + +GSList* findDriverDiskByLabel(void); + +int modprobeNormalmode(); +int modprobeDDmode(); + +#endif diff --git a/loader/driverselect.c b/loader/driverselect.c new file mode 100644 index 0000000..19aa357 --- /dev/null +++ b/loader/driverselect.c @@ -0,0 +1,251 @@ +/* + * driverselect.c - functionality for manually selecting drivers + * + * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 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> + */ + +#include <ctype.h> +#include <newt.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "../isys/log.h" + +#include "modules.h" +#include "moduleinfo.h" +#include "loader.h" +#include "loadermisc.h" +#include "lang.h" +#include "driverdisk.h" + +struct sortModuleList { + int index; + moduleInfoSet modInfo; +}; + +static int sortDrivers(const void * a, const void * b) { + const struct sortModuleList * one = a; + const struct sortModuleList * two = b; + + return strcmp(one->modInfo->moduleList[one->index].description, + one->modInfo->moduleList[two->index].description); +} + +static int getManualModuleArgs(struct moduleInfo * mod, gchar *** moduleArgs) { + newtGrid grid, buttons; + newtComponent text, f, ok, back, entry; + struct newtExitStruct es; + int done = 0, i; + char * buf; + char *argsEntry = ""; + + if (*moduleArgs) { + for (i = 0; (*moduleArgs)[i]; i++) + argsEntry = strcat(argsEntry, (*moduleArgs)[i]); + } + + f = newtForm(NULL, NULL, 0); + checked_asprintf(&buf, + _("Please enter any parameters which you wish to pass " + "to the %s module separated by spaces. If you don't " + "know what parameters to supply, skip this screen " + "by pressing the \"OK\" button."), mod->moduleName); + + text = newtTextboxReflowed(-1, -1, buf, 60, 0, 10, 0); + entry = newtEntry(-1, -1, argsEntry, 50, (const char **) &argsEntry, + NEWT_ENTRY_SCROLL); + + newtFormAddHotKey(f, NEWT_KEY_F12); + + buttons = newtButtonBar(_("OK"), &ok, _("Back"), &back, NULL); + + grid = newtCreateGrid(1, 3); + newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, text, + 0, 0, 0, 1, 0, 0); + newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, entry, + 0, 0, 0, 1, 0, 0); + newtGridSetField(grid, 0, 2, NEWT_GRID_SUBGRID, buttons, + 0, 0, 0, 0, 0, NEWT_GRID_FLAG_GROWX); + + newtGridWrappedWindow(grid, _("Enter Module Parameters")); + newtGridAddComponentsToForm(grid, f, 1); + + do { + newtFormRun(f, &es); + + if (es.reason == NEWT_EXIT_COMPONENT && es.u.co == back) { + done = -1; + } else { + done = 1; + } + } while (done == 0); + + free(buf); + newtGridFree(grid, 1); + + if (done == -1) { + newtFormDestroy(f); + newtPopWindow(); + + return LOADER_BACK; + } + logMessage(INFO, "specified args of %s for %s", argsEntry, mod->moduleName); + + if (strlen(argsEntry) > 0) { + *moduleArgs = g_strsplit(argsEntry, " ", 0); + } + + newtFormDestroy(f); + newtPopWindow(); + + return LOADER_OK; +} + +int chooseManualDriver(int class, struct loaderData_s *loaderData) { + int i, numSorted, num = 0, done = 0; + enum driverMajor type; + struct sortModuleList * sortedOrder; + char giveArgs = ' '; + gchar **moduleArgs = NULL; + moduleInfoSet modInfo = loaderData->modInfo; + + newtComponent text, f, ok, back, argcheckbox, listbox; + newtGrid grid, buttons; + struct newtExitStruct es; + + if (class == DEVICE_NETWORK) + type = DRIVER_NET; + else if (class == DEVICE_DISK || class == DEVICE_CDROM) + type = DRIVER_SCSI; + else + type = DRIVER_ANY; + + do { + sortedOrder = malloc(sizeof(*sortedOrder) * modInfo->numModules); + numSorted = 0; + + for (i = 0; i < modInfo->numModules; i++) { + sortedOrder[numSorted].index = i; + sortedOrder[numSorted++].modInfo = modInfo; + } + + if (numSorted == 0) { + i = newtWinChoice(_("No drivers found"), _("Load driver disk"), + _("Back"), _("No drivers were found to manually " + "insert. Would you like to use " + "a driver disk?")); + if (i != 1) + return LOADER_BACK; + + loadDriverFromMedia(class, loaderData, 1, 1); + continue; + } else { + break; + } + } while (1); + + qsort(sortedOrder, numSorted, sizeof(*sortedOrder), sortDrivers); + + f = newtForm(NULL, NULL, 0); + + text = newtTextboxReflowed(-1, -1, + _("Please select the driver below which you " + "wish to load. If it does not appear and " + "you have a driver disk, press F2."), + 60, 0, 10, 0); + + listbox = newtListbox(-1, -1, 6, NEWT_FLAG_SCROLL | NEWT_FLAG_RETURNEXIT); + newtListboxSetWidth(listbox, 55); + + buttons = newtButtonBar(_("OK"), &ok, _("Back"), &back, NULL); + argcheckbox = newtCheckbox(-1, -1, _("Specify optional module arguments"), + giveArgs, NULL, &giveArgs); + + newtFormAddHotKey(f, NEWT_KEY_F2); + newtFormAddHotKey(f, NEWT_KEY_F12); + + for (i = 0; i < numSorted; i++) { + char *buf = NULL; + + checked_asprintf(&buf, "%s (%s)", + modInfo->moduleList[sortedOrder[i].index].description, + modInfo->moduleList[sortedOrder[i].index].moduleName); + + newtListboxAppendEntry(listbox, buf, + INT_TO_POINTER(sortedOrder[i].index)); + } + + grid = newtCreateGrid(1, 4); + newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, text, 0, 0, 0, 1, 0, 0); + newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, listbox, + 0, 0, 0, 1, 0, 0); + newtGridSetField(grid, 0, 2, NEWT_GRID_COMPONENT, argcheckbox, + 0, 0, 0, 1, 0, 0); + newtGridSetField(grid, 0, 3, NEWT_GRID_SUBGRID, buttons, + 0, 0, 0, 0, 0, NEWT_GRID_FLAG_GROWX); + newtGridWrappedWindow(grid, _("Select Device Driver to Load")); + + newtGridAddComponentsToForm(grid, f, 1); + + do { + newtFormRun(f, &es); + + num = POINTER_TO_INT(newtListboxGetCurrent(listbox)); + + if (es.reason == NEWT_EXIT_COMPONENT && es.u.co == back) { + done = -1; + } else if (es.reason == NEWT_EXIT_HOTKEY && es.u.key == NEWT_KEY_F2) { + done = -2; + } else { + if (giveArgs != ' ') { + i = getManualModuleArgs(&(modInfo->moduleList[num]), + &moduleArgs); + if (i == LOADER_BACK) + done = 0; + else + done = 1; + } else { + done = 1; + } + } + } while (done == 0); + + newtGridFree(grid, 1); + newtFormDestroy(f); + newtPopWindow(); + + if (done == -1) + return LOADER_BACK; + if (done == -2) { + loadDriverFromMedia(class, loaderData, 1, 1); + return chooseManualDriver(class, loaderData); + } + + mlLoadModule(modInfo->moduleList[num].moduleName, moduleArgs); + free(sortedOrder); + + if (moduleArgs) { + g_strfreev(moduleArgs); + } + + return LOADER_OK; +} diff --git a/loader/fwloader.c b/loader/fwloader.c new file mode 100644 index 0000000..5ad1d8e --- /dev/null +++ b/loader/fwloader.c @@ -0,0 +1,675 @@ +/* + * fwloader.c -- a small firmware loader. + * + * Copyright (C) 2006, 2007 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): Peter Jones (pjones@redhat.com) + */ + +#define _GNU_SOURCE 1 + +#include <argz.h> +#include <envz.h> +#include <fcntl.h> +#include <poll.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/poll.h> +#include <sys/prctl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <asm/types.h> +#include <linux/netlink.h> + +#include "../isys/log.h" + +#include "loader.h" +#include "fwloader.h" +#include "udelay.h" + +#ifndef FWDEBUG +#define logMessage(x, ...) +#endif + +struct fw_loader { + int netlinkfd; + sigset_t sigmask; + char *fw_pathz; + size_t fw_pathz_len; + struct pollfd *fds; +}; + +int done = 0; + +static inline int set_fd_coe(int fd, int enable) +{ + int rc; + long flags = 0; + + rc = fcntl(fd, F_GETFD, &flags); + if (rc < 0) + return rc; + + if (enable) + flags |= FD_CLOEXEC; + else + flags &= ~FD_CLOEXEC; + + rc = fcntl(fd, F_SETFD, flags); + return rc; +} + +static int open_uevent_socket(struct fw_loader *fwl) +{ + int fd, rc; + struct sockaddr_nl sa; + + fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); + if (fd < 0) + return -1; + set_fd_coe(fd, 1); + + memset(&sa, '\0', sizeof (sa)); + sa.nl_family = AF_NETLINK; + sa.nl_pid = getpid(); + sa.nl_groups = -1; + + if (bind(fd, (struct sockaddr *)&sa, sizeof (sa)) < 0) { + close(fd); + return -1; + } + + fwl->netlinkfd = fd; + + fd = open("/proc/sys/kernel/hotplug", O_RDWR); + if (fd >= 0) { + rc = ftruncate(fd, 0); + rc = write(fd, "\n", 1); + close(fd); + } + + fd = open("/sys/class/firmware/timeout", O_RDWR); + if (fd >= 0) { + rc = write(fd, "10", 2); + close(fd); + } + + return 0; +} + +extern void loaderSegvHandler(int signum); + +static void kill_hotplug_signal(int signum) +{ + signal(signum, kill_hotplug_signal); + logMessage(DEBUGLVL, "fwloader: got exit signal, quitting"); + done = 1; +} + +static int daemonize(struct fw_loader *fwl) +{ + int fd; + int rc; + + signal(SIGTERM, kill_hotplug_signal); + signal(SIGSEGV, loaderSegvHandler); + signal(SIGTTOU, SIG_IGN); + signal(SIGTTIN, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + + sigfillset(&fwl->sigmask); + sigdelset(&fwl->sigmask, SIGTERM); + sigdelset(&fwl->sigmask, SIGSEGV); + sigemptyset(&fwl->sigmask); + + prctl(PR_SET_NAME, "hotplug", 0, 0, 0); + rc = chdir("/"); + + fd = open("/proc/self/oom_adj", O_RDWR); + if (fd >= 0) { + rc = write(fd, "-17", 3); + close(fd); + } + + for (fd = 0; fd < getdtablesize(); fd++) { + if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) + continue; + if (fd == tty_logfd || fd == file_logfd) + continue; + close(fd); + } + + setsid(); + fd = open("/dev/null", O_RDONLY); + close(STDIN_FILENO); + dup2(fd, STDIN_FILENO); + set_fd_coe(STDIN_FILENO, 1); + close(fd); + fd = open("/dev/null", O_WRONLY); + close(STDOUT_FILENO); + dup2(fd, STDOUT_FILENO); + set_fd_coe(STDOUT_FILENO, 1); + close(STDERR_FILENO); + dup2(fd, STDERR_FILENO); + set_fd_coe(STDERR_FILENO, 1); + close(fd); + + logMessage(DEBUGLVL, "fwloader: starting up (pid %d)", getpid()); + return 0; +} + +struct uevent { + char *msg; + char *path; + char *envz; + size_t envz_len; +}; + +static int get_netlink_msg(struct fw_loader *fwl, struct uevent *uevent) +{ + size_t len; + ssize_t size; + static char buffer[2560]; + char *pos; + char *msg = NULL, *path = NULL, *envz = NULL; + char *argv[] = { NULL }; + size_t envz_len; + error_t errnum; + + size = recv(fwl->netlinkfd, &buffer, sizeof (buffer), 0); + if (size < 0) + return -1; + + if ((size_t)size > sizeof (buffer) - 1) + size = sizeof (buffer) - 1; + buffer[size] = '\0'; + + len = strcspn(buffer, "@"); + if (!buffer[len]) + return -1; + + if ((errnum = argz_create(argv, &envz, &envz_len)) > 0) + goto err; + + pos = buffer; + msg = strndup(pos, len++); + pos += len; + path = strdup(pos); + + pos += strlen(pos) + 1; + if (len < size + 1) { + while (pos[0]) { + char *value = strchr(pos, '='); + if (value) + *(value++) = '\0'; + + if ((errnum = envz_add(&envz, &envz_len, pos, value)) > 0) + goto err; + pos += strlen(pos) + 1; + if (*pos) + pos += strlen(pos) + 1; + } + } + + uevent->msg = msg; + uevent->path = path; + uevent->envz = envz; + uevent->envz_len = envz_len; + return 0; +err: + if (msg) + free(msg); + if (path) + free(path); + while(envz) + argz_delete(&envz, &envz_len, envz); + errno = errnum; + return -1; +} + +/* Set the 'loading' attribute for a firmware device. + * 1 == currently loading + * 0 == done loading + * -1 == error + */ +static int +get_loading_fd(const char *device) +{ + int fd = -1; + char *loading_path = NULL; + + if (asprintf(&loading_path, "%s/loading", device) < 0) { + logMessage(ERROR, "fwloader: device %s: asprintf: %m", device); + return -1; + } + logMessage(DEBUGLVL, "fwloader: looking for loading file at %s", loading_path); + fd = open(loading_path, O_RDWR | O_SYNC ); + if (fd < 0) + logMessage(ERROR, "fwloader: open %s: %m", loading_path); + free(loading_path); + return fd; +} + +static int +set_loading(int fd, int value) +{ + int rc = 0; + + if (value == -1) + rc = write(fd, "-1", 3); + else if (value == 0) + rc = write(fd, "0", 2); + else if (value == 1) + rc = write(fd, "1", 2); + fsync(fd); + fdatasync(fd); + + return rc < 0 ? rc : 0; +} + +static int +fd_map(int fd, char **buf, size_t *bufsize) +{ + struct stat stats; + int en = 0; + + if (fstat(fd, &stats) < 0) { + en = errno; + close(fd); + errno = en; + return -1; + } + + *buf = mmap(NULL, stats.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (*buf == MAP_FAILED) { + *buf = NULL; + en = errno; + close(fd); + errno = en; + return -1; + } + *bufsize = stats.st_size; + return 0; +} + +static int +file_map(const char *filename, char **buf, size_t *bufsize, int flags) +{ + int fd, en, rc = 0; + + if ((fd = open(filename, flags ? flags : O_RDONLY)) < 0) + return -1; + + if (fd_map(fd, buf, bufsize) < 0) + rc = -1; + + en = errno; + close(fd); + errno = en; + + return rc; +} + +static void +file_unmap(void *buf, size_t bufsize) +{ + munmap(buf, bufsize); +} + +static int +fetcher(char *inpath, int outfd) +{ + char *inbuf = NULL; + size_t inlen; + int count; + int en = 0; + int rc; + + errno = 0; + if (access(inpath, F_OK)) + goto out; + + if (file_map(inpath, &inbuf, &inlen, O_RDONLY) < 0) + goto out; + + lseek(outfd, 0, SEEK_SET); + rc = ftruncate(outfd, 0); + rc = ftruncate(outfd, inlen); + + count = 0; + while (count < inlen) { + ssize_t c; + c = write(outfd, inbuf + count, inlen - count); + if (c <= 0) + goto out; + count += c; + } + +out: + en = errno; + if (inbuf) + file_unmap(inbuf, inlen); + if (en) { + errno = en; + return -1; + } + return 0; +} + + +static int +_load_firmware(struct fw_loader *fwl, int fw_fd, char *sysdir, int timeout) +{ + int rc = 0; + char *fw_buf = NULL, *data = NULL; + size_t fw_len = 0; + int dfd = -1, lfd = -1; + int loading = -2; + size_t count; + + logMessage(DEBUGLVL, "fwloader: waiting for firmware dir at %s", sysdir); + timeout *= 1000000; + while (access(sysdir, F_OK) && timeout) { + udelay(100); + timeout -= 100; + } + if (!timeout) { + logMessage(ERROR, "fwloader: never found firmware dir at %s", sysdir); + return -ENOENT; + } + + if ((lfd = get_loading_fd(sysdir)) < 0) + return lfd; + + set_loading(lfd, 1); + loading = -1; + + if (fd_map(fw_fd, &fw_buf, &fw_len) < 0) { + rc = -errno; + goto out; + } + + if (asprintf(&data, "%s/data", sysdir) < 0) { + rc = -errno; + goto out; + } + if ((dfd = open(data, O_RDWR)) < 0) { + rc = -errno; + goto out; + } + count = 0; + while (count < fw_len) { + ssize_t c; + if ((c = write(dfd, fw_buf + count, fw_len - count)) <= 0) + goto out; + count += c; + } + loading = 0; + +out: + if (dfd >= 0) + close(dfd); + if (fw_buf) + file_unmap(fw_buf, fw_len); + if (loading != -2) + set_loading(lfd, loading); + if (lfd >= 0) + close(lfd); + if (data) + free(data); + + return rc; +} + +static void load_firmware(struct fw_loader *fwl, struct uevent *uevent) +{ + char *devpath = NULL, *firmware = NULL, *timeout; + char *fw_file = NULL, *sys_file = NULL; + char *entry; + int timeout_secs; + char *tempfile; + int fd = -1; + + tempfile = strdup("/tmp/fw-XXXXXX"); + fd = mkstemp(tempfile); + if (fd < 0) { + logMessage(ERROR, "fwloader: mkstemp(\"%s\") failed: %m", tempfile); + free(tempfile); + return; + } + unlink(tempfile); + free(tempfile); + + devpath = envz_get(uevent->envz, uevent->envz_len, "DEVPATH"); + firmware = envz_get(uevent->envz, uevent->envz_len, "FIRMWARE"); + timeout = envz_get(uevent->envz, uevent->envz_len, "TIMEOUT"); + + if (!devpath || !firmware) { + argz_stringify(uevent->envz, uevent->envz_len, ' '); + logMessage(ERROR, "fwloader: environment: %s", uevent->envz); + return; + } + + errno = 0; + timeout_secs = strtol(timeout, NULL, 10); + + if ((errno == ERANGE && (timeout_secs == LONG_MIN || + timeout_secs == LONG_MAX)) || + (errno != 0 && timeout_secs == 0)) { + logMessage(ERROR, "%s: %d: %m", __func__, __LINE__); + abort(); + } + + /* find the file */ + for (entry = fwl->fw_pathz; entry; + entry = argz_next(fwl->fw_pathz, fwl->fw_pathz_len, entry)) { + if (asprintf(&fw_file, "%s/%s", entry, firmware) < 0) + return; + + logMessage(INFO, "fwloader: trying to find %s at %s", firmware, fw_file); + + if (fetcher(fw_file, fd) >= 0) + break; + + free(fw_file); + fw_file = NULL; + if (errno == ENOENT || errno == EPERM) + continue; + break; + } + if (!fw_file) + goto out; + + if (asprintf(&sys_file, "/sys%s/", devpath) < 0) + goto out; + + _load_firmware(fwl, fd, sys_file, timeout_secs); + +out: + if (fw_file) + free(fw_file); + if (sys_file) + free(sys_file); + if (fd != -1) + close(fd); +} + +static void handle_single_uevent(struct fw_loader *fwl, struct uevent *uevent) +{ + char *action = NULL; + char *subsystem = NULL; + + action = envz_get(uevent->envz, uevent->envz_len, "ACTION"); + subsystem = envz_get(uevent->envz, uevent->envz_len, "SUBSYSTEM"); + + logMessage(DEBUGLVL, "fwloader: subsystem %s got action %s", subsystem, action); + if (!strcmp(action, "add") && !strcmp(subsystem, "firmware")) + load_firmware(fwl, uevent); +} + +static void handle_events(struct fw_loader *fwl) +{ + int rc; + struct uevent uevent; + if (fwl->fds == NULL) + fwl->fds = calloc(1, sizeof (struct pollfd)); + + do { + do { + if (done) + exit(0); + fwl->fds[0].events = POLLIN | POLLPRI; + fwl->fds[0].revents = 0; + fwl->fds[0].fd = fwl->netlinkfd; + + //logMessage(DEBUGLVL, "fwloader: polling on netlink socket"); + errno = 0; + rc = poll(fwl->fds, 1, -1); + //logMessage(DEBUGLVL, "fwloader: poll returned %d", rc); + + if (done) + exit(0); + } while (rc < 1 || (rc < 0 && errno == EINTR)); + + memset(&uevent, '\0', sizeof (uevent)); + if (get_netlink_msg(fwl, &uevent) < 0) + continue; + + handle_single_uevent(fwl, &uevent); + } while (1); + + if (fwl->fds) { + free(fwl->fds); + fwl->fds = NULL; + } +} + +void set_fw_search_path(struct loaderData_s *loaderData, char *path) +{ + char *old = loaderData->fw_search_pathz, *new = NULL; + size_t old_len = loaderData->fw_search_pathz_len; + + loaderData->fw_search_pathz = NULL; + loaderData->fw_search_pathz_len = -1; + if (!path) { + if (old) + free(old); + return; + } + + if ((new = strdup(path)) == NULL) + goto out; + + loaderData->fw_search_pathz = NULL; + loaderData->fw_search_pathz_len = 0; + if (argz_create_sep(new, ':', &loaderData->fw_search_pathz, + &loaderData->fw_search_pathz_len) != 0) + goto out; + + if (old) + free(old); + + return; +out: + if (new) + free(new); + loaderData->fw_search_pathz = old; + loaderData->fw_search_pathz_len = old_len; + + return; +} + +void add_fw_search_dir(struct loaderData_s *loaderData, char *dir) +{ + argz_add(&loaderData->fw_search_pathz, &loaderData->fw_search_pathz_len, + dir); +} + +void do_fw_loader(struct loaderData_s *loaderData) +{ + struct fw_loader fwl; + int rc; + + memset(&fwl, '\0', sizeof (fwl)); + fwl.netlinkfd = -1; + + fwl.fw_pathz = loaderData->fw_search_pathz; + fwl.fw_pathz_len = loaderData->fw_search_pathz_len; + + logMessage(INFO, "fwloader: starting firmware loader"); + + rc = daemonize(&fwl); + if (rc < 0) { + logMessage(ERROR, "fwloader: daemonize() failed with %d: %m", rc); + exit(1); + } + + if (open_uevent_socket(&fwl) < 0) { + logMessage(ERROR, "fwloader: open_uevent_socket() failed: %m"); + exit(1); + } + + logMessage(DEBUGLVL, "fwloader: entering event loop"); + handle_events(&fwl); + + exit(1); +} + + +void start_fw_loader(struct loaderData_s *loaderData) { + pid_t loader; + + loader = fork(); + if (loader > 0) + loaderData->fw_loader_pid = loader; + if (loader != 0) + return; + + do_fw_loader(loaderData); +} + +void stop_fw_loader(struct loaderData_s *loaderData) { + int x = 0, rc; + siginfo_t siginfo; + if (loaderData->fw_loader_pid > 0) + kill(loaderData->fw_loader_pid, SIGTERM); + while (x <= 100) { + if (x > 90) + kill(loaderData->fw_loader_pid, SIGKILL); + memset(&siginfo, '\0', sizeof (siginfo)); + rc = waitid(P_PID, loaderData->fw_loader_pid, &siginfo, WNOHANG|WEXITED); + if (rc < 0 && errno == ECHILD) + return; + else if (rc == 0 && siginfo.si_pid != 0) + return; + else if (rc == 0) + x++; + usleep(10000); + } + return; +} + + +/* + * vim:ts=8:sw=4:sts=4:et + */ diff --git a/loader/fwloader.h b/loader/fwloader.h new file mode 100644 index 0000000..e0b0fe8 --- /dev/null +++ b/loader/fwloader.h @@ -0,0 +1,35 @@ +/* + * fwloader.h -- a small firmware loader. + * + * Copyright (C) 2006, 2007 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): Peter Jones <pjones@redhat.com> + */ + +#ifndef FWLOADER_H +#define FWLOADER_H 1 + +#include "loader.h" + +extern void set_fw_search_path(struct loaderData_s *loaderData, char *path); +extern void add_fw_search_dir(struct loaderData_s *loaderData, char *dir); +extern void start_fw_loader(struct loaderData_s *loaderData); +extern void stop_fw_loader(struct loaderData_s *loaderData); + +#endif /* FWLOADER_H */ +/* + * vim:ts=8:sw=4:sts=4:et + */ diff --git a/loader/getparts.c b/loader/getparts.c new file mode 100644 index 0000000..c60dc83 --- /dev/null +++ b/loader/getparts.c @@ -0,0 +1,180 @@ +/* + * getparts.c - functions associated with getting partitions for a disk + * + * Copyright (C) 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): Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> +#include <string.h> + +#include "../isys/log.h" + +/* see if this is a partition name or not */ +static int isPartitionName(char *pname) { + + /* if it doesnt start with a alpha its not one */ + if (!isalpha(*pname) || strstr(pname, "ram")) + return 0; + + /* if it has a '/' in it then treat it specially */ + if (strchr(pname, '/') && !strstr(pname, "iseries") && + !strstr(pname, "i2o")) { + /* assume its either a /dev/ida/ or /dev/cciss device */ + /* these have form of c?d?p? if its a partition */ + return strchr(pname, 'p') != NULL; + } else { + /* if it ends with a digit we're ok */ + return isdigit(pname[strlen(pname)-1]); + } +} + +/* return NULL terminated array of pointers to names of partitons in + * /proc/partitions + */ +char **getPartitionsList(char * disk) { + FILE *f; + int numfound = 0; + char **rc=NULL; + + f = fopen("/proc/partitions", "r"); + if (!f) { + logMessage(ERROR, "getPartitionsList: could not open /proc/partitions"); + return NULL; + } + + /* read through /proc/partitions and parse out partitions */ + while (1) { + char *tmpptr, *pptr; + char tmpstr[4096]; + + tmpptr = fgets(tmpstr, sizeof(tmpstr), f); + + if (tmpptr) { + char *a, *b; + int toknum = 0; + + a = tmpstr; + while (1) { + b = strsep(&a, " \n"); + + /* if no fields left abort */ + if (!b) + break; + + /* if field was empty means we hit another delimiter */ + if (!*b) + continue; + + /* make sure this is a valid partition line, should start */ + /* with a numeral */ + if (toknum == 0) { + if (!isdigit(*b)) + break; + } else if (toknum == 2) { + /* if size is exactly 1 then ignore it as an extended */ + if (!strcmp(b, "1")) + break; + } else if (toknum == 3) { + /* this should be the partition name */ + /* now we need to see if this is the block device or */ + /* actually a partition name */ + if (!isPartitionName(b)) + break; + + /* make sure that either we don't care about the disk + * or it's this one */ + if ((disk != NULL) && (strncmp(disk, b, strlen(disk)))) + break; + + /* we found a partition! */ + pptr = (char *) malloc(strlen(b) + 7); + sprintf(pptr, "/dev/%s", b); + + if (!rc) { + rc = (char **) malloc(2*sizeof(char *)); + rc[0] = pptr; + rc[1] = NULL; + } else { + int idx; + + rc = (char **) realloc(rc, (numfound+2)*sizeof(char *)); + idx = 0; + while (idx < numfound) { + if (strcmp(pptr, rc[idx]) < 0) + break; + + idx++; + } + + /* move existing out of way if necessary */ + if (idx != numfound) + memmove(rc+idx+1, rc+idx, (numfound-idx)*sizeof(char *)); + + rc[idx] = pptr; + rc[numfound+1] = NULL; + } + numfound++; + break; + } + toknum++; + } + } else { + break; + } + } + + fclose(f); + + return rc; +} + +/* returns length of partitionlist */ +int lenPartitionsList(char **list) { + char **part; + int rc; + + if (!list) return 0; + for (rc = 0, part = list; *part; rc++, part++); + + return rc; +} + +/* frees partition list */ +void freePartitionsList(char **list) { + char **part; + + if (!list) + return; + + for (part = list; *part; part++) { + if (*part) { + free(*part); + *part = NULL; + } + } + + free(list); + list = NULL; +} diff --git a/loader/getparts.h b/loader/getparts.h new file mode 100644 index 0000000..b672a77 --- /dev/null +++ b/loader/getparts.h @@ -0,0 +1,27 @@ +/* + * getparts.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef GETPARTS_H +#define GETPARTS_H + +char **getPartitionsList(char * disk); +int lenPartitionsList(char **list); +void freePartitionsList(char **list); + +#endif diff --git a/loader/hardware.c b/loader/hardware.c new file mode 100644 index 0000000..ad20ed5 --- /dev/null +++ b/loader/hardware.c @@ -0,0 +1,150 @@ +/* + * hardware.c - various hardware probing functionality + * + * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003 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> + * Matt Wilson <msw@redhat.com> + * Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + */ + +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <strings.h> +#include <stdlib.h> +#include <unistd.h> +#include <ctype.h> +#include <sys/wait.h> +#include <glib.h> + +#include "loader.h" +#include "hardware.h" + +/* FIXME: for turning off dma */ +#include <sys/ioctl.h> +#include <linux/hdreg.h> +#include "../isys/isys.h" +#include "../isys/log.h" + +/* boot flags */ +extern uint64_t flags; + +static int detectHardware() { + int child, rc, status; + int timeout = 0; /* FIXME: commandline option for this */ + + fprintf(stderr, "detecting hardware...\n"); + logMessage(DEBUGLVL, "probing buses"); + + if (!(child = fork())) { + int fd = open("/dev/tty3", O_RDWR); + + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + close(fd); + + rc = execl("/sbin/udevadm", "udevadm", "trigger", NULL); + _exit(1); + } + + waitpid(child, &status, 0); + if (!WIFEXITED(status) || (WIFEXITED(status) && WEXITSTATUS(status))) { + rc = 1; + } else { + rc = 0; + } + + fprintf(stderr, "waiting for hardware to initialize...\n"); + logMessage(DEBUGLVL, "waiting for hardware to initialize"); + + if (!(child = fork())) { + char *args[] = { "/sbin/udevadm", "settle", NULL, NULL }; + int fd = open("/dev/tty3", O_RDWR); + + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + close(fd); + + if (timeout) { + checked_asprintf(&args[2], "--timeout=%d", timeout); + } + + rc = execv("/sbin/udevadm", args); + _exit(1); + } + + waitpid(child, &status, 0); + if (!WIFEXITED(status) || (WIFEXITED(status) && WEXITSTATUS(status))) { + rc = 1; + } else { + rc = 0; + } + if (rc) { + return LOADER_ERROR; + } + return LOADER_OK; +} + +/* this allows us to do an early load of modules specified on the + * command line to allow automating the load order of modules so that + * eg, certain scsi controllers are definitely first. + * FIXME: this syntax is likely to change in a future release + * but is done as a quick hack for the present. + */ +int earlyModuleLoad(int justProbe) { + int fd, len, i; + char buf[1024], *cmdLine; + gint argc = 0; + gchar **argv = NULL; + GError *optErr = NULL; + + /* FIXME: reparsing /proc/cmdline to avoid major loader changes. + * should probably be done in loader.c:parseCmdline() like everything + * else + */ + if ((fd = open("/proc/cmdline", O_RDONLY)) < 0) return 1; + len = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (len <= 0) return 1; + + buf[len] = '\0'; + cmdLine = buf; + + if (!g_shell_parse_argv(cmdLine, &argc, &argv, &optErr)) { + g_error_free(optErr); + return 1; + } + + for (i=0; i < argc; i++) { + if (!strncasecmp(argv[i], "driverload=", 11)) { + logMessage(INFO, "loading %s early", argv[i] + 11); + mlLoadModuleSet(argv[i] + 11); + } + } + return 0; +} + +int busProbe(int justProbe) { + /* autodetect whatever we can */ + if (justProbe) + return 0; + return detectHardware(); +} diff --git a/loader/hardware.h b/loader/hardware.h new file mode 100644 index 0000000..47c34d2 --- /dev/null +++ b/loader/hardware.h @@ -0,0 +1,28 @@ +/* + * hardware.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef LOADERHW_H +#define LOADERHW_H + +#include "modules.h" + +int earlyModuleLoad(int justProbe); +int busProbe(int justProbe); + +#endif diff --git a/loader/hdinstall.c b/loader/hdinstall.c new file mode 100644 index 0000000..42f1a61 --- /dev/null +++ b/loader/hdinstall.c @@ -0,0 +1,489 @@ +/* + * hdinstall.c - code to set up hard drive installs + * + * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003 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> + * Matt Wilson <msw@redhat.com> + * Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <newt.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> +#include <unistd.h> +#include <glib.h> + +#include "driverdisk.h" +#include "hdinstall.h" +#include "getparts.h" +#include "kickstart.h" +#include "loader.h" +#include "loadermisc.h" +#include "lang.h" +#include "modules.h" +#include "method.h" +#include "mediacheck.h" +#include "cdinstall.h" +#include "windows.h" + +#include "../isys/imount.h" +#include "../isys/isys.h" +#include "../isys/eddsupport.h" +#include "../isys/log.h" + +/* boot flags */ +extern uint64_t flags; + +/* given a partition device and directory, tries to mount hd install image */ +static char * setupIsoImages(char * device, char * dirName, char * location) { + int rc = 0; + char *url = NULL, *dirspec, *updpath, *path; + + logMessage(INFO, "mounting device %s for hard drive install", device); + + if (doPwMount(device, "/mnt/isodir", "auto", "ro", NULL)) + return NULL; + + checked_asprintf(&dirspec, "/mnt/isodir%.*s", + (int) (strrchr(dirName, '/') - dirName), dirName); + checked_asprintf(&path, "/mnt/isodir%s", dirName); + + if (path) { + logMessage(INFO, "Path to stage2 image is %s", path); + + rc = copyFile(path, "/tmp/install.img"); + rc = mountStage2("/tmp/install.img"); + + free(path); + + if (rc) { + umountLoopback("/mnt/runtime", "/dev/loop0"); + umount("/mnt/isodir"); + goto err; + } + + checked_asprintf(&updpath, "%s/updates.img", dirspec); + + logMessage(INFO, "Looking for updates for HD in %s", updpath); + copyUpdatesImg(updpath); + free(updpath); + + checked_asprintf(&updpath, "%s/product.img", dirspec); + + logMessage(INFO, "Looking for product for HD in %s", updpath); + copyProductImg(updpath); + + free(updpath); + free(dirspec); + umount("/mnt/isodir"); + + checked_asprintf(&url, "hd:%s:/%s", device, + dirName ? dirName : "."); + + return url; + } else { + free(dirspec); + free(path); + + if (rc) { + umount("/mnt/isodir"); + return NULL; + } + } + +err: + newtWinMessage(_("Error"), _("OK"), + _("An error occured finding the installation image " + "on your hard drive. Please check your images and " + "try again.")); + return NULL; +} + +/* setup hard drive based install from a partition with a filesystem and + * ISO images on that filesystem + */ +char * mountHardDrive(struct installMethod * method, + char * location, struct loaderData_s * loaderData) { + int rc; + int i; + + newtComponent listbox, label, dirEntry, form, okay, back, text; + struct newtExitStruct es; + newtGrid entryGrid, grid, buttons; + + int done = 0; + char * dir = strdup(""); + char * tmpDir; + char * url = NULL; + char * buf, *substr; + int numPartitions; + + char **partition_list; + char *selpart; + char *kspartition = NULL, *ksdirectory = NULL; + + /* handle kickstart/stage2= data first if available */ + if (loaderData->method == METHOD_HD && loaderData->stage2Data) { + kspartition = ((struct hdInstallData *)loaderData->stage2Data)->partition; + ksdirectory = ((struct hdInstallData *)loaderData->stage2Data)->directory; + logMessage(INFO, "partition is %s, dir is %s", kspartition, ksdirectory); + + /* if exist, duplicate */ + if (kspartition) + kspartition = strdup(kspartition); + if (ksdirectory) { + ksdirectory = strdup(ksdirectory); + } else { + ksdirectory = strdup("/images/install.img"); + } + + if (!kspartition || !ksdirectory) { + logMessage(ERROR, "missing partition or directory specification"); + loaderData->method = -1; + + if (loaderData->inferredStage2) + loaderData->invalidRepoParam = 1; + } else { + /* if we start with /dev, strip it (#121486) */ + char *kspart = kspartition; + if (!strncmp(kspart, "/dev/", 5)) + kspart = kspart + 5; + + url = setupIsoImages(kspart, ksdirectory, location); + if (!url) { + logMessage(ERROR, "unable to find %s installation images on hd", + getProductName()); + loaderData->method = -1; + + if (loaderData->inferredStage2) + loaderData->invalidRepoParam = 1; + } else { + free(kspartition); + free(ksdirectory); + return url; + } + } + } else { + kspartition = NULL; + ksdirectory = NULL; + } + + /* if we're here its either because this is interactive, or the */ + /* hd kickstart directive was faulty and we have to prompt for */ + /* location of harddrive image */ + + partition_list = NULL; + while (!done) { + /* if we're doing another pass free this up first */ + if (partition_list) + freePartitionsList(partition_list); + + partition_list = getPartitionsList(NULL); + numPartitions = lenPartitionsList(partition_list); + + /* no partitions found, try to load a device driver disk for storage */ + if (!numPartitions) { + rc = newtWinChoice(_("Hard Drives"), _("Yes"), _("Back"), + _("You don't seem to have any hard drives on " + "your system! Would you like to configure " + "additional devices?")); + if (rc == 2) { + loaderData->stage2Data = NULL; + return NULL; + } + + rc = loadDriverFromMedia(DEVICE_DISK, loaderData, 0, 0); + continue; + } + + /* now find out which partition has the stage2 image */ + checked_asprintf(&buf, _("What partition and directory on that " + "partition holds the installation image " + "for %s? If you don't see the disk drive " + "you're using listed here, press F2 to " + "configure additional devices."), + getProductName()); + + text = newtTextboxReflowed(-1, -1, buf, 62, 5, 5, 0); + free(buf); + + listbox = newtListbox(-1, -1, numPartitions > 5 ? 5 : numPartitions, + NEWT_FLAG_RETURNEXIT | + (numPartitions > 5 ? NEWT_FLAG_SCROLL : 0)); + + for (i = 0; i < numPartitions; i++) + newtListboxAppendEntry(listbox,partition_list[i],partition_list[i]); + + /* if we had ks data around use it to prime entry, then get rid of it*/ + if (kspartition) { + newtListboxSetCurrentByKey(listbox, kspartition); + free(kspartition); + kspartition = NULL; + } + + label = newtLabel(-1, -1, _("Directory holding image:")); + + dirEntry = newtEntry(28, 11, dir, 28, (const char **) &tmpDir, + NEWT_ENTRY_SCROLL); + + /* if we had ks data around use it to prime entry, then get rid of it*/ + if (ksdirectory) { + newtEntrySet(dirEntry, ksdirectory, 1); + free(ksdirectory); + ksdirectory = NULL; + } + + entryGrid = newtGridHStacked(NEWT_GRID_COMPONENT, label, + NEWT_GRID_COMPONENT, dirEntry, + NEWT_GRID_EMPTY); + + buttons = newtButtonBar(_("OK"), &okay, _("Back"), &back, NULL); + + grid = newtCreateGrid(1, 4); + newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, text, + 0, 0, 0, 1, 0, 0); + newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, listbox, + 0, 0, 0, 1, 0, 0); + newtGridSetField(grid, 0, 2, NEWT_GRID_SUBGRID, entryGrid, + 0, 0, 0, 1, 0, 0); + newtGridSetField(grid, 0, 3, NEWT_GRID_SUBGRID, buttons, + 0, 0, 0, 0, 0, NEWT_GRID_FLAG_GROWX); + + newtGridWrappedWindow(grid, _("Select Partition")); + + form = newtForm(NULL, NULL, 0); + newtFormAddHotKey(form, NEWT_KEY_F2); + newtFormAddHotKey(form, NEWT_KEY_F12); + + newtGridAddComponentsToForm(grid, form, 1); + newtGridFree(grid, 1); + + newtFormRun(form, &es); + + selpart = newtListboxGetCurrent(listbox); + + free(dir); + if (tmpDir && *tmpDir) { + /* Protect from form free. */ + dir = strdup(tmpDir); + } else { + dir = strdup(""); + } + + newtFormDestroy(form); + newtPopWindow(); + + if (es.reason == NEWT_EXIT_COMPONENT && es.u.co == back) { + loaderData->stage2Data = NULL; + return NULL; + } else if (es.reason == NEWT_EXIT_HOTKEY && es.u.key == NEWT_KEY_F2) { + rc = loadDriverFromMedia(DEVICE_DISK, loaderData, 0, 0); + continue; + } + + logMessage(INFO, "partition %s selected", selpart); + + /* If the user-provided URL points at a repo instead of a stage2 + * image, fix that up now. + */ + substr = strstr(dir, ".img"); + if (!substr || (substr && *(substr+4) != '\0')) { + checked_asprintf(&dir, "%s/images/install.img", dir); + } + + loaderData->invalidRepoParam = 1; + + url = setupIsoImages(selpart, dir, location); + if (!url) { + newtWinMessage(_("Error"), _("OK"), + _("Device %s does not appear to contain " + "an installation image."), selpart, getProductName()); + continue; + } + + done = 1; + } + + free(dir); + + return url; +} + +void setKickstartHD(struct loaderData_s * loaderData, int argc, + char ** argv) { + char *p; + gchar *biospart = NULL, *partition = NULL, *dir = NULL; + GOptionContext *optCon = g_option_context_new(NULL); + GError *optErr = NULL; + GOptionEntry ksHDOptions[] = { + { "biospart", 0, 0, G_OPTION_ARG_STRING, &biospart, NULL, NULL }, + { "partition", 0, 0, G_OPTION_ARG_STRING, &partition, NULL, NULL }, + { "dir", 0, 0, G_OPTION_ARG_STRING, &dir, NULL, NULL }, + { NULL }, + }; + + logMessage(INFO, "kickstartFromHD"); + + g_option_context_set_help_enabled(optCon, FALSE); + g_option_context_add_main_entries(optCon, ksHDOptions, NULL); + + if (!g_option_context_parse(optCon, &argc, &argv, &optErr)) { + startNewt(); + newtWinMessage(_("Kickstart Error"), _("OK"), + _("Bad argument to HD kickstart method " + "command: %s"), optErr->message); + g_error_free(optErr); + g_option_context_free(optCon); + return; + } + + g_option_context_free(optCon); + + if (biospart) { + char * dev; + + p = strchr(biospart,'p'); + if(!p){ + logMessage(ERROR, "Bad argument for --biospart"); + return; + } + *p = '\0'; + dev = getBiosDisk(biospart); + if (dev == NULL) { + logMessage(ERROR, "Unable to location BIOS partition %s", biospart); + return; + } + partition = malloc(strlen(dev) + strlen(p + 1) + 2); + sprintf(partition, "%s%s", dev, p + 1); + } + + loaderData->method = METHOD_HD; + loaderData->stage2Data = calloc(sizeof(struct hdInstallData *), 1); + if (partition) + ((struct hdInstallData *)loaderData->stage2Data)->partition = partition; + if (dir) + ((struct hdInstallData *)loaderData->stage2Data)->directory = dir; + + logMessage(INFO, "results of hd ks, partition is %s, dir is %s", partition, + dir); +} + +int kickstartFromHD(char *kssrc) { + int rc; + char *p, *np = NULL, *tmpstr, *ksdev, *kspath; + + logMessage(INFO, "getting kickstart file from harddrive"); + + /* format is hd:[device]:/path/to/ks.cfg */ + /* split up pieces */ + tmpstr = strdup(kssrc); + p = strchr(tmpstr, ':'); + if (p) + np = strchr(p+1, ':'); + + /* no second colon, assume its the old format of */ + /* hd:[device]/path/to/ks.cfg */ + /* this format is bad however because some devices have '/' in them! */ + if (!np) + np = strchr(p+1, '/'); + + if (!p || !np) { + logMessage(WARNING, "Format of command line is ks=hd:[device]:/path/to/ks.cfg"); + free(tmpstr); + return 1; + } + + *np = '\0'; + ksdev = p+1; + kspath = np+1; + + logMessage(INFO, "Loading ks from device %s on path %s", ksdev, kspath); + if ((rc=getKickstartFromBlockDevice(ksdev, kspath))) { + if (rc == 3) { + startNewt(); + newtWinMessage(_("Error"), _("OK"), + _("Cannot find kickstart file on hard drive.")); + } + return 1; + } + + return 0; +} + + +int kickstartFromBD(char *kssrc) { + int rc; + char *p, *np = NULL, *r = NULL, *tmpstr, *ksdev, *kspath, *biosksdev; + + logMessage(INFO, "getting kickstart file from biosdrive"); + + /* format is bd:[device]:/path/to/ks.cfg */ + /* split of pieces */ + tmpstr = strdup(kssrc); + p = strchr(tmpstr, ':'); + if (p) + np = strchr(p+1, ':'); + + if (!p || !np) { + logMessage(WARNING, "Format of command line is ks=bd:device:/path/to/ks.cfg"); + free(tmpstr); + return 1; + } + + *np = '\0'; + kspath = np+1; + + r = strchr(p+1,'p'); + if(!r){ + logMessage(INFO, "Format of biosdisk is 80p1"); + free(tmpstr); + return 1; + } + + *r = '\0'; + biosksdev = getBiosDisk((p + 1)); + if(!biosksdev){ + startNewt(); + newtWinMessage(_("Error"), _("OK"), + _("Cannot find hard drive for BIOS disk %s"), + p + 1); + return 1; + } + + + ksdev = malloc(strlen(biosksdev) + 3); + sprintf(ksdev, "%s%s", biosksdev, r + 1); + logMessage(INFO, "Loading ks from device %s on path %s", ksdev, kspath); + if ((rc=getKickstartFromBlockDevice(ksdev, kspath))) { + if (rc == 3) { + startNewt(); + newtWinMessage(_("Error"), _("OK"), + _("Cannot find kickstart file on hard drive.")); + } + return 1; + } + + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4: */ diff --git a/loader/hdinstall.h b/loader/hdinstall.h new file mode 100644 index 0000000..44351a3 --- /dev/null +++ b/loader/hdinstall.h @@ -0,0 +1,38 @@ +/* + * hdinstall.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef H_HDINSTALL +#define H_HDINSTALL + +#include "method.h" + +struct hdInstallData { + char * partition; + char * directory; +}; + + +void setKickstartHD(struct loaderData_s * loaderData, int argc, + char ** argv); +char * mountHardDrive(struct installMethod * method, + char * location, struct loaderData_s * loaderData); +int kickstartFromHD(char *kssrc); +int kickstartFromBD(char *kssrc); + +#endif diff --git a/loader/ibft.c b/loader/ibft.c new file mode 100644 index 0000000..b3a3827 --- /dev/null +++ b/loader/ibft.c @@ -0,0 +1,105 @@ +/* + File name: ibft.c + Date: 2008/09/02 + Author: Martin Sivak <msivak@redhat.com> + + Copyright (C) 2008 Red Hat + + 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 + in a file called COPYING along with this program; if not, write to + the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA + 02139, USA. +*/ + + +#include <stddef.h> +#include <stdio.h> +#include <string.h> + +#include <libiscsi.h> +#include "ibft.h" + +struct libiscsi_network_config ibft_context; +int ibft_ispresent = 0; +int ibft_initialized = 0; + +int ibft_init(void) +{ + int ret; + + memset(&ibft_context, 0, sizeof(ibft_context)); + + ret = libiscsi_get_firmware_network_config(&ibft_context); + + /* ret == 0 -> OK */ + ibft_ispresent = !ret; + ibft_initialized = 1; + + return ibft_initialized; +} + +/* Is iBFT available on this system */ +int ibft_present() +{ + if(!ibft_initialized) + ibft_init(); + + return ibft_ispresent; +} + +/* Is the iBFT network configured to use DHCP */ +int ibft_iface_dhcp() +{ + if(!ibft_initialized) + ibft_init(); + + if(!ibft_present()) + return -1; + + return ibft_context.dhcp; +} + +#define ibft_iface_charfunc(name, var) char* ibft_iface_##name()\ +{\ + if(!ibft_initialized)\ + ibft_init();\ +\ + if(!ibft_present())\ + return NULL;\ +\ + if(!strlen(ibft_context.var))\ + return NULL;\ +\ + return ibft_context.var;\ +} + + +/* Get the iBFT MAC address */ +ibft_iface_charfunc(mac, mac_address) + +/* Get the iBFT ip address */ +ibft_iface_charfunc(ip, ip_address) + +/* Get the iBFT subnet mask */ +ibft_iface_charfunc(mask, netmask) + +/* Get the iBFT gateway */ +ibft_iface_charfunc(gw, gateway) + +/* Get the iBFT iface name */ +ibft_iface_charfunc(iface, iface_name) + +/* Get the iBFT dns servers */ +ibft_iface_charfunc(dns1, primary_dns) +ibft_iface_charfunc(dns2, secondary_dns) + diff --git a/loader/ibft.h b/loader/ibft.h new file mode 100644 index 0000000..a922c91 --- /dev/null +++ b/loader/ibft.h @@ -0,0 +1,45 @@ +/* + File name: ibft.h + Date: 2008/09/02 + Author: Martin Sivak + + Copyright (C) 2008 Red Hat + + 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 + in a file called COPYING along with this program; if not, write to + the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA + 02139, USA. +*/ + + +#ifndef __IBFT_H__ +#define __IBFT_H__ + + +int ibft_init(); +int ibft_present(); + +int ibft_iface_dhcp(); + +char* ibft_iface_mac(); +char* ibft_iface_ip(); +char* ibft_iface_mask(); +char* ibft_iface_gw(); +char* ibft_iface_iface(); +char* ibft_iface_dns1(); +char* ibft_iface_dns2(); + + +#endif + +/* end of ibft.h */ diff --git a/loader/init.c b/loader/init.c new file mode 100644 index 0000000..a2095de --- /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 +#define klogctl syslog +#else +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <execinfo.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 <glib.h> + +#include "init.h" +#include "copy.h" +#include "devt.h" +#include "devices.h" + +#endif + +#include <asm/types.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(__powerpc64__) || (defined(__sparc__) && defined(__arch64__)) + "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 + * + */ + +void shutDown(int doKill, reboot_action rebootAction); +static int getKillPolicy(void); +static void getSyslog(char*); +struct termios ts; + +static int expected_exit = 0; + +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; + char addr[128]; + char forwardtcp[] = "*.* @@"; + + /* update the config file with command line arguments first */ + getSyslog(addr); + if (strlen(addr) > 0) { + 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 { + ret = write(conf_fd, forwardtcp, strlen(forwardtcp)); + ret = write(conf_fd, addr, strlen(addr)); + ret = write(conf_fd, "\n", 1); + 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; + 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); + } + + 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 ((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(__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) { + 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 0; + } + return 1; +} + +/* Looks through /proc/cmdline for remote syslog paramters. */ +static void getSyslog(char *addr) { + int fd; + int len; + char buf[1024]; + + /* assume nothing gets found */ + addr[0] = '\0'; + if ((fd = open("/proc/cmdline", O_RDONLY,0)) <= 0) { + return; + } + len = read(fd, buf, sizeof(buf) - 1); + close(fd); + buf[len] = '\0'; + + /* Parse the command line into argument vector using glib */ + int i; + int argc; + char** argv; + GError* err = NULL; + if (!g_shell_parse_argv(buf, &argc, &argv, &err )) { + g_error_free(err); + return; + } + for (i = 0; i < argc; ++i) { + /* find what we are looking for */ + if (!strncmp(argv[i], "syslog=", 7)) { + strncpy(addr, argv[i] + 7, 127); + addr[127] = '\0'; + break; + } + } + g_strfreev(argv); + + /* 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)) { + /* the parameter is malformed, disable its use */ + addr[0] = '\0'; + printf("The syslog= command line parameter is malformed and will be\n"); + printf("ignored by the installer.\n"); + sleep(5); + } + +} + +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); +} + +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; + char * console = NULL; + int doKill = 1; + 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); + 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(); + + 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"); + + 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 = waitpid(childpid, &waitstatus, 0); + 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 (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"); + + /* these args are only for testing from commandline */ + for (i = 1; i < argc; i++) { + if (!strcmp (argv[i], "serial")) { + isSerial = 1; + break; + } + } + + doKill = getKillPolicy(); + +#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); + } + } + + 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); + } + + 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 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("/sbin/dbus-uuidgen", "/sbin/dbus-uuidgen", "--ensure", NULL); + doExit(1); + } + + if (fork() == 0) { + execl("/sbin/dbus-daemon", "/sbin/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; + } + + *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 = waitpid(-1, &waitStatus, 0); + + if (childpid == installpid) { + doShutdown = 1; + ioctl(0, VT_ACTIVATE, 1); + } + } + + if (!WIFEXITED(waitStatus) || + (WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus))) { + 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"); + } else { + shutdown_method = REBOOT; + } + + shutDown(doKill, shutdown_method); + + return 0; +} + +/* vim:tw=78:ts=4:et:sw=4 + */ diff --git a/loader/init.h b/loader/init.h new file mode 100644 index 0000000..e1e5b70 --- /dev/null +++ b/loader/init.h @@ -0,0 +1,31 @@ +/* + * init.h + * + * Copyright (C) 2009 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/>. + */ +#ifndef INIT_H +#define INIT_H + +typedef enum { + REBOOT, + POWEROFF, + HALT, + /* gives user a chance to read the trace before scrolling the text out + with disk unmounting and termination info */ + DELAYED_REBOOT +} reboot_action; + +#endif /* INIT_H */ diff --git a/loader/kbd.c b/loader/kbd.c new file mode 100644 index 0000000..b94f920 --- /dev/null +++ b/loader/kbd.c @@ -0,0 +1,164 @@ +/* + * kbd.c - keyboard handling + * + * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 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> + * Matt Wilson <msw@redhat.com> + * Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + */ + +#include <alloca.h> +#include <errno.h> +#include <newt.h> +#include <stdlib.h> +#include <string.h> + +#include "loader.h" +#include "loadermisc.h" +#include "lang.h" +#include "windows.h" + +#include "../isys/stubs.h" +#include "../isys/lang.h" +#include "../isys/log.h" + +/* boot flags */ +extern uint64_t flags; + +int chooseKeyboard(struct loaderData_s * loaderData, char ** kbdtypep) { + int num = -1; + int rc; + gzFile f; + struct kmapHeader hdr; + struct kmapInfo * infoTable; + struct langInfo * languages; + int numLanguages; + char ** kbds; + char buf[16384]; /* I hope this is big enough */ + int i; + char * defkbd = loaderData->kbd ? loaderData->kbd : NULL; + char *lang; + +#if defined(__s390__) || defined(__s390x__) + return LOADER_NOOP; +#endif + + if (FL_SERIAL (flags) || FL_VIRTPCONSOLE(flags)) return LOADER_NOOP; + + numLanguages = getLangInfo(&languages); + + lang = getenv("LANG"); + if (!lang) + lang = loaderData->lang; + + if (!defkbd && lang) { + for (i = 0; i < numLanguages; i++) { + if (!strncmp(languages[i].lc_all, lang, 2)) { + defkbd = languages[i].keyboard; + break; + } + } + } + + if (!defkbd) + defkbd = "us"; + + f = gunzip_open("/etc/keymaps.gz"); + if (!f) { + errorWindow("cannot open /etc/keymaps.gz: %s"); + return LOADER_ERROR; + } + + if (gunzip_read(f, &hdr, sizeof(hdr)) != sizeof(hdr)) { + errorWindow("failed to read keymaps header: %s"); + gunzip_close(f); + return LOADER_ERROR; + } + + logMessage(INFO, "%d keymaps are available", hdr.numEntries); + + i = hdr.numEntries * sizeof(*infoTable); + infoTable = alloca(i); + if (gunzip_read(f, infoTable, i) != i) { + errorWindow("failed to read keymap information: %s"); + gunzip_close(f); + return LOADER_ERROR; + } + + if (num == -1 ) { + kbds = alloca(sizeof(*kbds) * (hdr.numEntries + 1)); + for (i = 0; i < hdr.numEntries; i++) { + kbds[i] = infoTable[i].name; + } + + kbds[i] = NULL; + qsort(kbds, i, sizeof(*kbds), simpleStringCmp); + + for (i = 0; i < hdr.numEntries; i++) + if (!strcmp(kbds[i], defkbd)) + num = i; + + rc = newtWinMenu(_("Keyboard Type"), + _("What type of keyboard do you have?"), + 40, 5, 5, 8, kbds, &num, _("OK"), _("Back"), NULL); + if (rc == 2) return LOADER_BACK; + + /* num needs to index the right keyboard infoTable */ + for (i = 0; i < hdr.numEntries; i++) + if (!strcmp(kbds[num], infoTable[i].name)) break; + num = i; + } + + rc = 0; + + for (i = 0; i < num; i++) { + if (gunzip_read(f, buf, infoTable[i].size) != infoTable[i].size) { + logMessage(ERROR, "error reading %d bytes from file: %m", + infoTable[i].size); + gunzip_close(f); + rc = LOADER_ERROR; + } + } + + if (!rc) rc = loadKeymap(f); + + /* normalize the error condition */ + /* MSWFIXME - do we want to warn the user that setting the + keyboard didn't work? + */ + if (rc != 0) + rc = LOADER_ERROR; + else + gunzip_close(f); + + loaderData->kbd = strdup(infoTable[num].name); + + return rc; +} + +void setKickstartKeyboard(struct loaderData_s * loaderData, int argc, + char ** argv) { + if (argc < 2) { + logMessage(ERROR, "no argument passed to keyboard kickstart command"); + return; + } + + loaderData->kbd = argv[1]; + loaderData->kbd_set = 1; +} diff --git a/loader/kbd.h b/loader/kbd.h new file mode 100644 index 0000000..26c7111 --- /dev/null +++ b/loader/kbd.h @@ -0,0 +1,27 @@ +/* + * kbd.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef H_KBD +#define H_KBD + +int chooseKeyboard(struct loaderData_s * loaderData, char ** kbdtypep); +void setKickstartKeyboard(struct loaderData_s * loaderData, int argc, + char ** argv); + +#endif diff --git a/loader/keymaps-i386 b/loader/keymaps-i386 Binary files differnew file mode 100644 index 0000000..93f2e58 --- /dev/null +++ b/loader/keymaps-i386 diff --git a/loader/keymaps-ppc b/loader/keymaps-ppc Binary files differnew file mode 100644 index 0000000..914f4bf --- /dev/null +++ b/loader/keymaps-ppc diff --git a/loader/keymaps-x86_64 b/loader/keymaps-x86_64 Binary files differnew file mode 100644 index 0000000..93f2e58 --- /dev/null +++ b/loader/keymaps-x86_64 diff --git a/loader/kickstart.c b/loader/kickstart.c new file mode 100644 index 0000000..4ff800f --- /dev/null +++ b/loader/kickstart.c @@ -0,0 +1,549 @@ +/* + * kickstart.c - kickstart file handling + * + * Copyright (C) 1999, 2000, 2001, 2002, 2003 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> + * Matt Wilson <msw@redhat.com> + * Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + */ + +#include <alloca.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <newt.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <glib.h> + +#include "loader.h" +#include "loadermisc.h" +#include "lang.h" +#include "kickstart.h" +#include "modules.h" + +#include "kbd.h" +#include "driverdisk.h" +#include "net.h" +#include "method.h" + +#include "nfsinstall.h" +#include "urlinstall.h" +#include "cdinstall.h" +#include "hdinstall.h" + +#include "../isys/imount.h" +#include "../isys/isys.h" +#include "../isys/log.h" + +/* boot flags */ +extern uint64_t flags; + +struct ksCommandNames { + int code; + char * name; + void (*setupData) (struct loaderData_s *loaderData, + int argc, char ** argv); +} ; + +struct ksCommand { + int code, argc; + char ** argv; +}; + +static void setTextMode(struct loaderData_s * loaderData, int argc, + char ** argv); +static void setGraphicalMode(struct loaderData_s * loaderData, int argc, + char ** argv); +static void setCmdlineMode(struct loaderData_s * loaderData, int argc, + char ** argv); +static void setSELinux(struct loaderData_s * loaderData, int argc, + char ** argv); +static void setPowerOff(struct loaderData_s * loaderData, int argc, + char ** argv); +static void setHalt(struct loaderData_s * loaderData, int argc, + char ** argv); +static void setShutdown(struct loaderData_s * loaderData, int argc, + char ** argv); +static void setMediaCheck(struct loaderData_s * loaderData, int argc, + char ** argv); +static void setUpdates(struct loaderData_s * loaderData, int argc, + char ** argv); + +struct ksCommandNames ksTable[] = { + { KS_CMD_NFS, "nfs", setKickstartNfs }, + { KS_CMD_CDROM, "cdrom", setKickstartCD }, + { KS_CMD_HD, "harddrive", setKickstartHD }, + { KS_CMD_TEXT, "text", setTextMode }, + { KS_CMD_GRAPHICAL, "graphical", setGraphicalMode }, + { KS_CMD_URL, "url", setKickstartUrl }, + { KS_CMD_NETWORK, "network", setKickstartNetwork }, + { KS_CMD_KEYBOARD, "keyboard", setKickstartKeyboard }, + { KS_CMD_LANG, "lang", setKickstartLanguage }, + { KS_CMD_DD, "driverdisk", useKickstartDD }, + { KS_CMD_DEVICE, "device", loadKickstartModule }, + { KS_CMD_CMDLINE, "cmdline", setCmdlineMode }, + { KS_CMD_SELINUX, "selinux", setSELinux }, + { KS_CMD_POWEROFF, "poweroff", setPowerOff }, + { KS_CMD_HALT, "halt", setHalt }, + { KS_CMD_SHUTDOWN, "shutdown", setShutdown }, + { KS_CMD_MEDIACHECK, "mediacheck", setMediaCheck }, + { KS_CMD_UPDATES, "updates", setUpdates }, + { KS_CMD_NONE, NULL, NULL } +}; + +struct ksCommand * commands = NULL; +int numCommands = 0; + +int ksReadCommands(char * cmdFile) { + int fd; + char * buf; + struct stat sb; + char * start, * end, * chptr; + char oldch; + int line = 0; + gint argc = 0; + gchar **argv = NULL; + GError *optErr = NULL; + int inSection = 0; /* in a section such as %post, %pre or %packages */ + struct ksCommandNames * cmd; + int commandsAlloced = 5; + + if ((fd = open(cmdFile, O_RDONLY)) < 0) { + startNewt(); + newtWinMessage(_("Kickstart Error"), _("OK"), + _("Error opening kickstart file %s: %m"), + cmdFile); + return LOADER_ERROR; + } + + fstat(fd, &sb); + buf = alloca(sb.st_size + 1); + if (read(fd, buf, sb.st_size) != sb.st_size) { + startNewt(); + newtWinMessage(_("Kickstart Error"), _("OK"), + _("Error reading contents of kickstart file %s: %m"), + cmdFile); + close(fd); + return LOADER_ERROR; + } + + close(fd); + + buf[sb.st_size] = '\0'; + + commands = malloc(sizeof(*commands) * commandsAlloced); + + start = buf; + while (*start && !inSection) { + line++; + if (!(end = strchr(start, '\n'))) + end = start + strlen(start); + + oldch = *end; + *end = '\0'; + + while (*start && isspace(*start)) start++; + + chptr = end - 1; + while (chptr > start && isspace(*chptr)) chptr--; + + if (isspace(*chptr)) + *chptr = '\0'; + else + *(chptr + 1) = '\0'; + + if (!*start || *start == '#' || !strncmp(start, "%include", 8)) { + /* keep parsing the file */ + } else if (*start == '%') { + /* assumed - anything starting with %something is a section */ + inSection = 1; + } else if (*chptr == '\\') { + /* JKFIXME: this should be handled better, but at least we + * won't segfault now */ + } else { + if (!g_shell_parse_argv(start, &argc, &argv, &optErr) && argc) { + newtWinMessage(_("Kickstart Error"), _("OK"), + _("Error in %s on line %d of kickstart " + "file %s."), argv[0], line, cmdFile); + g_error_free(optErr); + } else if (!argc) { + newtWinMessage(_("Kickstart Error"), _("OK"), + _("Missing options on line %d of kickstart " + "file %s."), line, cmdFile); + } else { + for (cmd = ksTable; cmd->name; cmd++) + if (!strcmp(cmd->name, argv[0])) break; + + if (cmd->name) { + if (numCommands == commandsAlloced) { + commandsAlloced += 5; + commands = realloc(commands, + sizeof(*commands) * commandsAlloced); + } + + commands[numCommands].code = cmd->code; + commands[numCommands].argc = argc; + commands[numCommands].argv = argv; + numCommands++; + } + } + } + + if (oldch) + start = end + 1; + else + start = end; + } + + return 0; +} + + +int ksHasCommand(int cmd) { + int i; + + for(i = 0; i < numCommands; i++) + if (commands[i].code == cmd) return 1; + + return 0; +} + +int ksGetCommand(int cmd, char ** last, int * argc, char *** argv) { + int i = 0; + + if (last) { + for (i = 0; i < numCommands; i++) { + if (commands[i].argv == last) break; + } + + i++; + } + + for (; i < numCommands; i++) { + if (commands[i].code == cmd) { + if (argv) *argv = commands[i].argv; + if (argc) *argc = commands[i].argc; + return 0; + } + } + + return 1; +} + +int kickstartFromRemovable(char *kssrc) { + struct device ** devices; + char *p, *kspath; + int i, rc; + + logMessage(INFO, "doing kickstart from removable media"); + devices = getDevices(DEVICE_DISK); + /* usb can take some time to settle, even with the various hacks we + * have in place. some systems use portable USB CD-ROM drives, try to + * make sure there really isn't one before bailing. */ + for (i = 0; !devices && i < 10; ++i) { + logMessage(INFO, "sleeping to wait for a USB disk"); + sleep(2); + devices = getDevices(DEVICE_DISK); + } + if (!devices) { + logMessage(ERROR, "no disks"); + return 1; + } + + for (i = 0; devices[i]; i++) { + if (devices[i]->priv.removable == 1) { + logMessage(INFO, "first removable media is %s", devices[i]->device); + break; + } + } + + if (!devices[i] || (devices[i]->priv.removable == 0)) { + logMessage(ERROR, "no removable devices"); + return 1; + } + + /* format is floppy:[/path/to/ks.cfg] */ + kspath = ""; + p = strchr(kssrc, ':'); + if (p) + kspath = p + 1; + + if (!p || strlen(kspath) < 1) + kspath = "/ks.cfg"; + + if ((rc=getKickstartFromBlockDevice(devices[i]->device, kspath))) { + if (rc == 3) { + startNewt(); + newtWinMessage(_("Error"), _("OK"), + _("Cannot find ks.cfg on removable media.")); + } + return 1; + } + + return 0; +} + + +/* given a device name (w/o '/dev' on it), try to get ks file */ +/* Error codes: + 1 - could not create device node + 2 - could not mount device as ext2, vfat, or iso9660 + 3 - kickstart file named path not there +*/ +int getKickstartFromBlockDevice(char *device, char *path) { + return getFileFromBlockDevice(device, path, "/tmp/ks.cfg"); +} + +static char *newKickstartLocation(const char *origLocation) { + const char *location; + char *retval = NULL; + newtComponent f, okay, cancel, answer, locationEntry; + newtGrid grid, buttons; + + startNewt(); + + locationEntry = newtEntry(-1, -1, NULL, 60, &location, NEWT_FLAG_SCROLL); + newtEntrySet(locationEntry, origLocation, 1); + + /* button bar at the bottom of the window */ + buttons = newtButtonBar(_("OK"), &okay, _("Cancel"), &cancel, NULL); + + grid = newtCreateGrid(1, 3); + + newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, + newtTextboxReflowed(-1, -1, _("Unable to download the kickstart file. Please modify the kickstart parameter below or press Cancel to proceed as an interactive installation."), 60, 0, 0, 0), + 0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, locationEntry, + 0, 1, 0, 0, NEWT_ANCHOR_LEFT, 0); + newtGridSetField(grid, 0, 2, NEWT_GRID_SUBGRID, buttons, + 0, 1, 0, 0, 0, NEWT_GRID_FLAG_GROWX); + + f = newtForm(NULL, NULL, 0); + newtGridAddComponentsToForm(grid, f, 1); + newtGridWrappedWindow(grid, _("Error downloading kickstart file")); + newtGridFree(grid, 1); + + /* run the form */ + answer = newtRunForm(f); + + if (answer != cancel) + retval = strdup(location); + + newtFormDestroy(f); + newtPopWindow(); + + return retval; +} + +int isKickstartFileRemote(char *ksFile) { + char *location = NULL; + + if (ksFile == NULL) { + return 0; + } + + if (!strcmp(ksFile, "ks")) { + return 1; + } else if (!strncmp(ksFile, "ks=", 3)) { + location = ksFile + 3; + } + + if (!strncmp(location, "http", 4) || + !strncmp(location, "ftp://", 6) || + !strncmp(location, "nfs:", 4)) { + return 1; + } else { + return 0; + } +} + +void getKickstartFile(struct loaderData_s *loaderData) { + char *c; + int rc = 1; + + /* Chop off the parameter name, if given. */ + if (!strncmp(loaderData->ksFile, "ks=", 3)) + c = loaderData->ksFile+3; + else + c = loaderData->ksFile; + + while (rc != 0) { + if (!strncmp(c, "ks", 2)) { + rc = kickstartFromNfs(NULL, loaderData); + loaderData->ksFile = strdup("/tmp/ks.cfg"); + } else if (!strncmp(c, "http", 4) || !strncmp(c, "ftp://", 6)) { + rc = kickstartFromUrl(c, loaderData); + loaderData->ksFile = strdup("/tmp/ks.cfg"); + } else if (!strncmp(c, "nfs:", 4)) { + rc = kickstartFromNfs(c+4, loaderData); + loaderData->ksFile = strdup("/tmp/ks.cfg"); + } else if (!strncmp(c, "floppy", 6)) { + rc = kickstartFromRemovable(c); + loaderData->ksFile = strdup("/tmp/ks.cfg"); + } else if (!strncmp(c, "hd:", 3)) { + rc = kickstartFromHD(c); + loaderData->ksFile = strdup("/tmp/ks.cfg"); + } else if (!strncmp(c, "bd:", 3)) { + rc = kickstartFromBD(c); + loaderData->ksFile = strdup("/tmp/ks.cfg"); + } else if (!strncmp(c, "cdrom", 5)) { + rc = kickstartFromCD(c); + loaderData->ksFile = strdup("/tmp/ks.cfg"); + } else if (!strncmp(c, "file:", 5)) { + loaderData->ksFile = c+5; + break; + } + + if (rc != 0) { + char *newLocation; + + if (!strcmp(c, "ks")) + newLocation = newKickstartLocation(""); + else + newLocation = newKickstartLocation(c); + + if (loaderData->ksFile != NULL) + free(loaderData->ksFile); + + if (newLocation != NULL) { + loaderData->ksFile = strdup(newLocation); + free(newLocation); + return getKickstartFile(loaderData); + } + else + return; + } + } + + flags |= LOADER_FLAGS_KICKSTART; + return; +} + +static void setUpdates(struct loaderData_s * loaderData, int argc, + char ** argv) { + if (argc == 1) + flags |= LOADER_FLAGS_UPDATES; + else if (argc == 2) + loaderData->updatessrc = strdup(argv[1]); + else + logMessage(WARNING, "updates command given with incorrect arguments"); +} + +static void setTextMode(struct loaderData_s * loaderData, int argc, + char ** argv) { + logMessage(INFO, "kickstart forcing text mode"); + flags |= LOADER_FLAGS_TEXT; + return; +} + +static void setGraphicalMode(struct loaderData_s * loaderData, int argc, + char ** argv) { + logMessage(INFO, "kickstart forcing graphical mode"); + flags |= LOADER_FLAGS_GRAPHICAL; + return; +} + +static void setCmdlineMode(struct loaderData_s * loaderData, int argc, + char ** argv) { + logMessage(INFO, "kickstart forcing cmdline mode"); + flags |= LOADER_FLAGS_CMDLINE; + return; +} + +static void setSELinux(struct loaderData_s * loaderData, int argc, + char ** argv) { + flags |= LOADER_FLAGS_SELINUX; + return; +} + +static void setPowerOff(struct loaderData_s * loaderData, int argc, + char ** argv) { + if (!FL_NOKILL(flags)) + flags |= LOADER_FLAGS_POWEROFF; + return; +} + +static void setHalt(struct loaderData_s * loaderData, int argc, + char ** argv) { + if (!FL_NOKILL(flags)) + flags |= LOADER_FLAGS_HALT; + return; +} + +static void setShutdown(struct loaderData_s * loaderData, int argc, + char ** argv) { + gint eject = 0, reboot = 0, halt = 0, poweroff = 0; + GOptionContext *optCon = g_option_context_new(NULL); + GError *optErr = NULL; + GOptionEntry ksOptions[] = { + { "eject", 'e', 0, G_OPTION_ARG_INT, &eject, NULL, NULL }, + { "reboot", 'r', 0, G_OPTION_ARG_INT, &reboot, NULL, NULL }, + { "halt", 'h', 0, G_OPTION_ARG_INT, &halt, NULL, NULL }, + { "poweroff", 'p', 0, G_OPTION_ARG_INT, &poweroff, NULL, NULL }, + { NULL }, + }; + + g_option_context_set_help_enabled(optCon, FALSE); + g_option_context_add_main_entries(optCon, ksOptions, NULL); + + if (!g_option_context_parse(optCon, &argc, &argv, &optErr)) { + startNewt(); + newtWinMessage(_("Kickstart Error"), _("OK"), + _("Bad argument to shutdown kickstart method " + "command: %s"), optErr->message); + g_error_free(optErr); + g_option_context_free(optCon); + return; + } + + g_option_context_free(optCon); + + if (FL_NOKILL(flags)) { + flags |= LOADER_FLAGS_HALT; + } else { + if (poweroff) + flags |= LOADER_FLAGS_POWEROFF; + if ((!poweroff && !reboot) || (halt)) + flags |= LOADER_FLAGS_HALT; + } +} + +static void setMediaCheck(struct loaderData_s * loaderData, int argc, + char ** argv) { + flags |= LOADER_FLAGS_MEDIACHECK; + return; +} + +void runKickstart(struct loaderData_s * loaderData) { + struct ksCommandNames * cmd; + int argc; + char ** argv; + + logMessage(INFO, "setting up kickstart"); + for (cmd = ksTable; cmd->name; cmd++) { + if ((!ksGetCommand(cmd->code, NULL, &argc, &argv)) && cmd->setupData) { + cmd->setupData(loaderData, argc, argv); + } + } +} + +/* vim:set sw=4 sts=4 et: */ diff --git a/loader/kickstart.h b/loader/kickstart.h new file mode 100644 index 0000000..3418cac --- /dev/null +++ b/loader/kickstart.h @@ -0,0 +1,53 @@ +/* + * kickstart.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef H_KICKSTART + +#include "loader.h" + +#define KS_CMD_NONE 0 +#define KS_CMD_NFS 1 +#define KS_CMD_CDROM 2 +#define KS_CMD_HD 3 +#define KS_CMD_URL 4 +#define KS_CMD_NETWORK 5 +#define KS_CMD_TEXT 6 +#define KS_CMD_KEYBOARD 7 +#define KS_CMD_LANG 8 +#define KS_CMD_DD 9 +#define KS_CMD_DEVICE 10 +#define KS_CMD_CMDLINE 11 +#define KS_CMD_GRAPHICAL 12 +#define KS_CMD_SELINUX 13 +#define KS_CMD_POWEROFF 14 +#define KS_CMD_HALT 15 +#define KS_CMD_SHUTDOWN 16 +#define KS_CMD_MEDIACHECK 17 +#define KS_CMD_UPDATES 18 + +int ksReadCommands(char * cmdFile); +int ksGetCommand(int cmd, char ** last, int * argc, char *** argv); +int ksHasCommand(int cmd); + +int isKickstartFileRemote(char *ksFile); +void getKickstartFile(struct loaderData_s * loaderData); +void runKickstart(struct loaderData_s * loaderData); +int getKickstartFromBlockDevice(char *device, char *path); + +#endif diff --git a/loader/lang.c b/loader/lang.c new file mode 100644 index 0000000..31749fc --- /dev/null +++ b/loader/lang.c @@ -0,0 +1,399 @@ +/* + * lang.c - determines language, handles translations + * + * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003 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> + * Matt Wilson <msw@redhat.com> + * Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + */ + +#include <alloca.h> +#include <errno.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <newt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <wchar.h> + +#include "loader.h" +#include "lang.h" +#include "loadermisc.h" +#include "windows.h" + +#include "../isys/stubs.h" +#include "../isys/cpio.h" +#include "../isys/lang.h" +#include "../isys/isys.h" +#include "../isys/log.h" + +/* boot flags */ +extern uint64_t flags; + +struct aString { + unsigned int hash; + short length; + char * str; +} ; + +struct aString * strings = NULL; +int numStrings = 0, allocedStrings = 0; + +static int english = 0; + +static char * topLineWelcome = N_("Welcome to %s for %s"); +static char * topLineWelcomeRescue = N_("Welcome to %s for %s - Rescue Mode"); +static char * bottomHelpLine = N_(" <Tab>/<Alt-Tab> between elements | <Space> selects | <F12> next screen "); + +static int aStringCmp(const void * a, const void * b) { + const struct aString * first = a; + const struct aString * second = b; + + if (first->hash < second->hash) + return -1; + else if (first->hash == second->hash) + return 0; + + return 1; +} + +char * translateString(char * str) { + unsigned int sum = 0, xor = 0; + int len = 0; + char * chptr; + struct aString * match; + struct aString key; + + for (chptr = str; *chptr; chptr++) { + sum += *chptr; + xor ^= *chptr; + len++; + } + + key.hash = (sum << 16) | ((xor & 0xFF) << 8) | (len & 0xFF); + match = bsearch(&key, strings, numStrings, sizeof(*strings), aStringCmp); + if (!match) + return str; + + return match->str; +} + +static struct langInfo * languages = NULL; +static int numLanguages = 0; + +static void loadLanguageList(void) { + char * file = "/etc/lang-table"; + FILE * f; + char line[256]; + char name[256], key[256], font[256], code[256], + keyboard[256], timezone[256]; + int lineNum = 0; + + wcwidth(0); + f = fopen(file, "r"); + if (!f) { + newtWinMessage(_("Error"), _("OK"), "cannot open %s: %m", file); + return; + } + + while (fgets(line, sizeof(line), f)) { + lineNum++; + languages = realloc(languages, sizeof(*languages) * (numLanguages + 1)); + if (sscanf(line, "%[^\t]\t%[^\t]\t%[^\t]\t%[^\t]\t%[^\t]\t%[^\t]\n", + name, key, font, code, keyboard, timezone) != 6) { + printf("bad line %d in lang-table", lineNum); + logMessage(WARNING, "bad line %d in lang-table", lineNum); + } else { + languages[numLanguages].lang = strdup(name); + languages[numLanguages].key = strdup(key); + languages[numLanguages].font = strdup(font); + languages[numLanguages].lc_all = strdup(code); + languages[numLanguages++].keyboard = strdup(keyboard); + } + } + fclose(f); +} + +int getLangInfo(struct langInfo ** langs) { + if (!languages) + loadLanguageList(); + + *langs = languages; + return numLanguages; +} + +void loadLanguage (char * file) { + char filename[200]; + gzFile stream; + int fd, hash, rc; + char * key = getenv("LANGKEY"); + + if (strings) { + free(strings), strings = NULL; + numStrings = allocedStrings = 0; + } + + /* english requires no files */ + if (!strcmp(key, "en")) + return; + + if (!file) { + file = filename; + sprintf(filename, "/etc/loader.tr"); + } + + stream = gunzip_open(file); + + if (!stream) { + newtWinMessage("Error", "OK", "Translation for %s is not available. " + "The Installation will proceed in English.", key); + return ; + } + + sprintf(filename, "%s.tr", key); + + rc = installCpioFile(stream, filename, "/tmp/translation", 1); + gunzip_close(stream); + + if (rc || access("/tmp/translation", R_OK)) { + newtWinMessage("Error", "OK", "Cannot get translation file %s.\n", + filename); + return; + } + + fd = open("/tmp/translation", O_RDONLY); + if (fd < 0) { + newtWinMessage("Error", "OK", "Failed to open /tmp/translation: %m\n"); + return; + } + + while (read(fd, &hash, 4) == 4) { + if (allocedStrings == numStrings) { + allocedStrings += 10; + strings = realloc(strings, sizeof(*strings) * allocedStrings); + } + + strings[numStrings].hash = ntohl(hash); + rc = read(fd, &strings[numStrings].length, 2); + strings[numStrings].length = ntohs(strings[numStrings].length); + strings[numStrings].str = malloc(strings[numStrings].length + 1); + rc = read(fd, strings[numStrings].str, strings[numStrings].length); + strings[numStrings].str[strings[numStrings].length] = '\0'; + numStrings++; + } + + close(fd); + unlink("/tmp/translation"); + + qsort(strings, numStrings, sizeof(*strings), aStringCmp); +} + + +/* give the index of the language to set to -- sets the appropriate + * lang variables if we have a font. + * + * ASSUMPTION: languages exists + */ +static void setLangEnv (int i) { + if (i > numLanguages) + return; + + if (strcmp(languages[i].font, "latarcyrheb-sun16")) + return; + logMessage(INFO, "setting language to %s", languages[i].lc_all); + + setenv("LANG", languages[i].lc_all, 1); + setenv("LANGKEY", languages[i].key, 1); + setenv("LINGUAS", languages[i].lang, 1); + loadLanguage (NULL); +} + +/* choice is the index of the chosen language in languages */ +static int setupLanguage(int choice, int forced) { + char * buf; + int i; + + logMessage(DEBUGLVL, "going to set language to %s", languages[choice].lc_all); + /* load the language only if it is displayable. if they're using + * a serial console or iSeries vioconsole, we hope it's smart enough */ + if ((strcmp(languages[choice].font, "latarcyrheb-sun16") && !FL_SERIAL(flags) && + !FL_VIRTPCONSOLE(flags) && !isVioConsole())) { + if (forced == 1) return 0; + + newtWinMessage("Language Unavailable", "OK", + "%s display is unavailable in text mode. The " + "installation will continue in English until the " + "display of %s is possible.", languages[choice].lang, + languages[choice].lang); + setLangEnv(english); + return 0; + } + + setLangEnv (choice); + isysLoadFont(); + + /* clear out top line */ + buf = alloca(81); /* reserve one byte for \0 */ + for (i=0; i < 80; i++) + buf[i] = ' '; + buf[80] = 0; /* and set the \0 */ + newtDrawRootText(0, 0, buf); + + char *fmt = FL_RESCUE(flags) ? _(topLineWelcomeRescue) : _(topLineWelcome); + checked_asprintf(&buf, fmt, getProductName(), getProductArch()); + + newtDrawRootText(0, 0, buf); + free(buf); + newtPopHelpLine(); + newtPushHelpLine(_(bottomHelpLine)); + + return 0; + +} + +/* this is pretty simple. we want to break down the language specifier + * into its short form (eg, en_US) + */ +static char * getLangShortForm(char * oldLang) { + char * lang; + char * c; + + lang = strdup(oldLang); + + c = strchr(lang, '@'); + if (c) { + *c = '\0'; + } + + c = strchr(lang, '.'); + if (c) { + *c = '\0'; + } + + return lang; +} + +/* return the nick of a language -- eg en_US -> en */ +static char * getLangNick(char * oldLang) { + char * lang; + char * c; + + lang = strdup(oldLang); + + c = strchr(lang, '_'); + if (c) { + *c = '\0'; + } + + return lang; +} + +int setLanguage (char * key, int forced) { + int i; + + if (!languages) loadLanguageList(); + + for (i = 0; i < numLanguages; i++) { + if (!strcmp(languages[i].lc_all, key)) { + return setupLanguage(i, forced | !FL_KICKSTART(flags)); + } + } + + /* we didn't specify anything that's exactly in the lang-table. check + * against short forms and nicks */ + for (i = 0; i < numLanguages; i++) { + if (!strcmp(getLangShortForm(languages[i].lc_all), key)) { + return setupLanguage(i, forced | !FL_KICKSTART(flags)); + } + } + + for (i = 0; i < numLanguages; i++) { + if (!strcmp(getLangNick(languages[i].lc_all), key)) { + return setupLanguage(i, forced | !FL_KICKSTART(flags)); + } + } + + logMessage(ERROR, "unable to set to requested language %s", key); + return -1; +} + +int chooseLanguage(char ** lang) { + int choice = 0; + char ** langs; + int i; + int current = -1; + char * currentLangName = getenv("LANG"); + int numLangs = 0; + char * langPicked; + + if (!languages) loadLanguageList(); + + langs = alloca(sizeof(*langs) * (numLanguages + 1)); + + for (i = 0; i < numLanguages; i++) { + if (!strncmp(languages[i].key, "en", 2)) + english = numLangs; + if (currentLangName && + !strcmp(languages[i].lang, currentLangName)) + current = numLangs; + + langs[numLangs++] = languages[i].lang; + } + + langs[numLangs] = NULL; + + if (current >= 0) + choice = current; + else + choice = english; + + if (!FL_CMDLINE(flags)) + newtWinMenu(_("Choose a Language"), + _("What language would you like to use during the " + "installation process?"), 40, 5, 5, 8, + langs, &choice, _("OK"), NULL); + + langPicked = langs[choice]; + for (i = 0; i < numLanguages; i++) { + if (!strcmp(langPicked, languages[i].lang)) { + *lang = languages[i].lc_all; + choice = i; + break; + } + } + + /* this can't happen */ + if (i == numLanguages) abort(); + + return setupLanguage(choice, 0); +} + +void setKickstartLanguage(struct loaderData_s * loaderData, int argc, + char ** argv) { + if (argc < 2) { + logMessage(ERROR, "no argument passed to lang kickstart command"); + return; + } + + loaderData->lang = argv[1]; + loaderData->lang_set = 1; +} diff --git a/loader/lang.h b/loader/lang.h new file mode 100644 index 0000000..965f5a0 --- /dev/null +++ b/loader/lang.h @@ -0,0 +1,41 @@ +/* + * lang.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef _LANG_H_ +#define _LANG_H_ + +#include "loader.h" + +#define _(x) translateString (x) +#define N_(foo) (foo) + +struct langInfo { + char * lang, * key, * font, * lc_all, * keyboard; +} ; + + +int chooseLanguage(char ** lang); +char * translateString(char * str); +int setLanguage (char * key, int forced); +int getLangInfo(struct langInfo **langs); + +void setKickstartLanguage(struct loaderData_s * loaderData, int argc, + char ** argv); + +#endif /* _LANG_H_ */ diff --git a/loader/linuxrc.s390 b/loader/linuxrc.s390 new file mode 100644 index 0000000..2f0beba --- /dev/null +++ b/loader/linuxrc.s390 @@ -0,0 +1,3107 @@ +#! /bin/bash + +# linuxrc.s390: init process of Red Hat's installer initrd for s390(x) +# Copyright (C) 2000-2004 by +# Bernhard Rosenkraenzer <bero@redhat.com> +# Oliver Paukstadt <opaukstadt@millenux.com> +# Karsten Hopp <karsten@redhat.de> +# Florian La Roche <laroche@redhat.com> +# Nils Philippsen <nils@redhat.de> +# Helge Deller <hdeller@redhat.de> +# David Sainty <dsainty@redhat.com> +# Copyright (C) IBM Corp. 2008,2009 +# Author: Steffen Maier <maier@de.ibm.com> +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# prerequisites of this script to run inside the installer initrd: +# - udevadm and udevd need to be there +# - have /etc/udev/udev.conf with at least one comment line as content +# - if necessary, have udev rules +# - lsznet.raw and znetcontrolunits from s390utils-base in /lib/s390-tools +# - pack kernel modules and module-init-tools (no longer use busybox for that) +# - "multi on" in /etc/host.conf [RH bugs 486457,486461,483244] + +# TODOs: +# - make sure driver modules get loaded automatically +# - udev rule for lcs/ctcm vs. cu3088 + +# debug: set -x + +VERSION=1.2 + +export TEXTDOMAIN=s390installer +export TEXTDOMAINDIR=/usr/lib/locale + +# helper function to execute command in arguments and print command on stdout +function debug() { + # uncomment the following echo "$*" to enable debug output + #echo "$*" + $* +} + +# FIXME: maybe change to "$$" for production use, in case it wouldn't be init +declare -r INITPID="1" + +unset testing +[ "$$" != "$INITPID" ] && testing="1" +# uncomment the following test="1" to never execute sensitive commands +#testing="1" + +if [ "$RUNKS" = "0" ]; then + RUNKS="" +fi + +# helper function to disable commands while running outside the initrd +function tv() { + if [ -z "$testing" ]; then + $* + else + return 0 + fi +} + +function checkipv6() +{ + local ip=$1 + [ -z "$ip" ] && return 1 + /sbin/ipcalc -c -6 "$ip" >/dev/null 2>&1 + return $? +} + +function checkipv4() +{ + local ip=$1 + [ -z "$ip" ] && return 1 + /sbin/ipcalc -c -4 "$ip" >/dev/null 2>&1 + return $? +} + +function doshutdown() +{ + echo $"about to exec shutdown" + exec /sbin/shutdown + exit 0 +} + +function doreboot() +{ + if [ -e "/sys/firmware/reipl" ]; then + read REIPL_TYPE < /sys/firmware/reipl/reipl_type + echo "reipl_type=$REIPL_TYPE" + pushd /sys/firmware/reipl/$REIPL_TYPE >/dev/null 2>&1 + for i in *; do + echo "$i=`cat $i`" + done + popd >/dev/null 2>&1 + fi + + echo $"about to exec shutdown -r" + exec /sbin/shutdown -r + exit 0 +} + +function sysecho () { + file=$1 + shift + local i=1 + while [ $i -le 10 ] ; do + if [ ! -f "$file" ]; then + sleep 1 + i=$((i+1)) + else + break + fi + done + [ -f "$file" ] && echo $* > $file +} + +function dasd_settle() { + local dasd_status=/sys/bus/ccw/devices/$1/status + if [ ! -f $dasd_status ]; then + return 1 + fi + local i=1 + while [ $i -le 30 ] ; do + local status + read status < $dasd_status + case $status in + online|unformatted) + return 0 ;; + *) + sleep 0.1 + i=$((i+1)) ;; + esac + done + return 1 +} + +function dasd_settle_all() { + for dasdccw in $(cut -d '(' -f 1 /proc/dasd/devices) ; do + if ! dasd_settle $dasdccw ; then + echo $"Could not access DASD $dasdccw in time" + return 1 + fi + done + return 0 +} + +function startinetd() +{ + echo + echo $"Starting sshd to allow login over the network." + if [ -z "$testing" ]; then + echo $"Welcome to the anaconda install environment $VERSION for $S390ARCH" > /etc/issue.net + echo $"Welcome to the anaconda install environment $VERSION for $S390ARCH" > /etc/motd + echo >> /etc/motd + fi # testing + + /sbin/sshd -f /etc/ssh/sshd_config.anaconda + if [ -z "$RUNKS" ]; then + echo + echo $"Connect now to $IPADDR and log in as user install to start the installation." + echo $"E.g. using: ssh -x install@$IPADDR" + echo $"You may log in as the root user to start an interactive shell." + read + while : ; do + /bin/sh --login + [ $? = 0 ] || break + done + fi +} + +# prints a canonocalized device bus ID for a given devno of any format +function canonicalize_devno() +{ + case ${#1} in + 3) echo "0.0.0${1}" ;; + 4) echo "0.0.${1}" ;; + *) echo "${1}" ;; + esac + return 0 +} + +# read file from CMS and write it to /tmp +function readcmsfile() # $1=dasdport $2=filename +{ + local dev + if [ $# -ne 2 ]; then return; fi + # precondition: udevd created dasda block device node + if ! sysecho /proc/cio_ignore "free $1"; then + echo $"DASD $1 could not be cleared from device blacklist" + return 1 + fi + # /proc/cio_ignore won't block on freeing devices until resensing + # has been completed, so wait until the udev event queue depletes + # (without udevadm settle we could wait 2 seconds unconditionally) + #debug ls -laF /dev/.udev + udevadm settle + # even though the device might now be online, some of its + # sysfs attributes might not yet be available + sleep 1 + # precondition: dasd_eckd_mod driver incl. dependencies loaded, + # dasd_mod must be loaded without setting any DASD online + dev=$(canonicalize_devno $1) + if ! sysecho /sys/bus/ccw/devices/$dev/online 1; then + echo $"DASD $dev could not be set online" + return 1 + fi + udevadm settle + if ! dasd_settle $dev ; then + echo $"Could not access DASD $dev in time" + return 1 + fi + udevadm settle + if ! cmsfscat -d /dev/dasda -a $2 > /tmp/$2; then + echo $"Could not read conf file $2 on CMS DASD $1." + fi + if ! sysecho /sys/bus/ccw/devices/$dev/online 0; then + echo $"DASD $dev could not be set offline again" + return 1 + fi + udevadm settle + # consequences of no more module unload: loader can no longer + # use DASD module option to online DASDs and set other DASD parameters! +} + +# adaption of the same function in init.c (udevd gets started later) +function createDevices() +{ + awk '{ printf("mknod /dev/%s %s %s %s\n", $1, $2, $3, $4); + printf("chmod %s /dev/%s\n", $5, $1); + printf("chown %s /dev/%s\n", $6, $1); + }' <<EOF | sh +console c 5 1 600 root:root +null c 1 3 666 root:root +zero c 1 5 666 root:root +mem c 1 1 600 root:root +ptmx c 5 2 666 root:root +tty c 5 0 666 root:root +tty0 c 4 0 600 root:tty +tty1 c 4 1 600 root:tty +random c 1 8 644 root:root +urandom c 1 9 644 root:root +rtc c 10 135 644 root:root +EOF + # tty handling is different from init.c since s390 does not have all + for i in 2 3 4 5 6 7 8 9 ; do + ln -s console /dev/tty$i + done + mkdir /dev/pts + ln -s /proc/self/fd /dev/fd +} + +# approximately the main() function of init.c +function init_main() { + S390ARCH=$(uname -m) + if [ "$S390ARCH" = "s390" ]; then + export S390ARCH="S/390" + else + export S390ARCH="zSeries" + fi + + echo + echo $"Starting the $S390ARCH initrd to configure networking. Version is $VERSION" + + # set up env vars as we do in init.c + if [ $(uname -m) = "s390x" ]; then + LD_LIBRARY_PATH=/lib64:/usr/lib64:/usr/X11R6/lib64:/usr/kerberos/lib64:/lib:/usr/lib:/usr/X11R6/lib:/usr/kerberos/lib + else + LD_LIBRARY_PATH=/lib:/usr/lib:/usr/X11R6/lib:/usr/kerberos/lib + fi + export LD_LIBRARY_PATH + + PATH="$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" + export PATH + HOME=/ + export HOME + PYTHONPATH=/tmp/updates + export PYTHONPATH + + if [ -z "$testing" ]; then + + mount -t proc none /proc + + mount -t tmpfs none /dev + createDevices + # udevd req'd by udevadm settle (/dev/.udev/queue) + # in readcmsfile, dialog_network_table, semantic_check_subchannels. + # (important: start udevd at the right time, e.g. after setup of /dev) + echo $"Starting udev..." + udevd --daemon + # debug: udevadm control --log-priority=debug + + udevadm control --env=ANACONDA=1 + + mount -t devpts /dev/pts /dev/pts + mount -t sysfs none /sys + + # remount root fs rw + mount /dev/root / -o remount,rw + + # limit output on 3270 console + # (console_loglevel of 4 is just right to not get driver info, + # e.g. from qeth, since that would mix up with the user dialog) + echo "4 4 1 7" > /proc/sys/kernel/printk + + # make /tmp/ramfs + mount -t ramfs none /tmp + + ifconfig lo 127.0.0.1 netmask 255.0.0.0 + route add -host 127.0.0.1 dev lo + + echo -e "127.0.0.1\tlocalhost.localdomain localhost localhost4 localhost4.localdomain4" > /etc/hosts + echo -e "::1\t\tlocalhost.localdomain localhost localhost6 localhost6.localdomain6" >> /etc/hosts + + /sbin/dbus-uuidgen --ensure & + [ $? != 0 ] && echo "error on calling /sbin/dbus-uuidgen --ensure" + /sbin/dbus-daemon --system & + [ $? != 0 ] && echo "error on calling /sbin/dbus-daemon --system" + + fi # testing +} + +# trigger udev to automatically load device drivers +function udev_setup() { + if [ -z "$testing" ]; then + # debug: udevadm monitor & + udevadm trigger + udevadm settle + fi # testing +} + +# from here on accesses to sysfs try to follow +# linux/Documentation/sysfs-rules.txt + +### lsznet.raw integration + +declare -a nettable + +function read_lsznet_output() { + count=0 + local line + while read line; do + nettable[$count]="$line" + count=$((count + 1)) + # using the more sophisticated process substitution instead of temp file + # requires the symlink /dev/fd -> /proc/self/fd => createDevices + done < <(/lib/s390-tools/lsznet.raw) +} + +function print_nettable() { + local fmtstring="%3s %-14s %-7s %-5s %-4s %-6s %-7s %s\n" + printf "$fmtstring" \ + "NUM" "CARD" "CU" "CHPID" "TYPE" "DRIVER" "IF" "DEVICES" + local i + for ((i=0; i < count; i++)); do + local item cutype chp chpidtype devdrv devname chlist cardtype + read item cutype chp chpidtype devdrv devname chlist cardtype <<< ${nettable[$i]} + printf "$fmtstring" \ + $item "$cardtype" $cutype $chp "$chpidtype" $devdrv $devname $chlist + done +} + +function clear_screen() { + # FIXME: find a way to clear screen despite 3215 line mode terminal + echo +} + +function dialog_network_table() { + while : ; do + echo $"Scanning for available network devices..." + # This may take a long time so we show "progress": + #( while true; do echo -n "."; sleep 1; done ) & + #local childpid=$! + read_lsznet_output + #kill $childpid + #echo + echo $"Autodetection found ${count} devices." + # count==0: there might still be a blacklist the user wants to clear. + # do not flood user with long list if there are many devices + if [ "$count" -le 15 ]; then + # Show list + answer=s + else # [ $count -gt 15 ] + echo + while : ; do + echo $"s) show all, m) manual config:" + local answer + read answer + case $answer in + s|m) break ;; + esac + done + fi + [ "$answer" = "m" ] && break + # show network table to select network hardware configuration from + if [ "$count" -gt 0 ]; then + clear_screen + print_nettable + echo + fi + # account for possibly ignored common I/O devices + # cio_wc_bytes is NOT local so it can be re-used outside this function + cio_wc_bytes=0 + local cio_wc_filename cio_wc_foo + if [ -f /proc/cio_ignore ]; then + local cio_wc=$(wc -c /proc/cio_ignore) + read cio_wc_bytes cio_wc_filename cio_wc_foo <<< "$cio_wc" + if [ "$cio_wc_bytes" != "0" ]; then + echo $"Note: There is a device blacklist active! (Clearing might take long)" + #cat /proc/cio_ignore | tr '\n' ',' + #echo + else + if [ "$count" -eq 0 ]; then + # count==0 AND no device blacklist => manual mode + echo $"Entering manual configuration mode." + break + fi + fi + fi + # selection dialog + while : ; do + [ "$count" -gt 0 ] && echo -n $"<num>) use config, " + [ "$cio_wc_bytes" != "0" ] && echo -n $"c) clear blacklist, " + echo $"m) manual config, r) rescan, s) shell:" + local choice + read choice + [ -z "$choice" ] && continue + if [ "$choice" = "s" ]; then + echo $"Enter 'exit' at the shell prompt to get back to the installation dialog." + /bin/bash + continue 2 + fi + [ "$choice" = "m" ] && break + [ "$choice" = "r" ] && continue 2 + [ "$cio_wc_bytes" != "0" -a "$choice" = "c" ] && break + [[ "$choice" =~ ^[[:digit:]]+$ ]] + case $? in + 0) + # string matched the pattern + [ "$choice" -ge 1 -a "$choice" -le "$count" ] && break + ;; + 1) + # string did not match the pattern + continue + ;; + 2) + echo "l.$LINENO: syntax error in regex of match operator =~, code needs to be fixed" 1>&2 + ;; + *) + echo "l.$LINENO: unexpected return code of regex match operator =~, code needs to be fixed" 1>&2 + ;; + esac + done + if [ "$choice" = "c" ]; then + echo $"Clearing device blacklist..." + if sysecho /proc/cio_ignore "free all"; then + cio_wc_bytes=0 + # /proc/cio_ignore won't block on freeing devices + # until resensing has been completed, so wait until + # the udev event queue depletes. + # This may take a long time so we show "progress": + #( while true; do echo -n "."; sleep 3; done ) & + #local childpid=$! + #debug ls -laF /dev/.udev + udevadm settle + # (virtual) CTC/A takes some more time to appear in sysfs + # FIXME: how long to wait? 3 seconds seems to be enough. + sleep 3 + #kill $childpid + #echo + continue + else + echo $"Device blacklist could not be cleared" + fi + fi + [ "$choice" = "m" ] && break + # finally extract config info from selected item + # array nettable starts at index zero, user input starts at index one + choice=$((choice - 1)) + local item cutype chp chpidtype devdrv devname chlist cardtype + read item cutype chp chpidtype devdrv devname chlist cardtype <<< ${nettable[$choice]} + # $NETTYPE happens to be exactly the network driver name + if [ "$devdrv" = "ctcm" ]; then + NETTYPE="ctc" + else + NETTYPE=$devdrv + fi + SUBCHANNELS=$chlist + break + done + echo +} + +declare -r PREFIXFORMAT=[[:xdigit:]]* +declare -r SSIDFORMAT=[0-3] +declare -r BUSIDFORMAT=[[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:]] +declare -r IDFORMAT=$PREFIXFORMAT.$SSIDFORMAT.$BUSIDFORMAT +declare -r SUBCHANNEL_TYPE_IO=0 + +. /lib/s390-tools/znetcontrolunits + +function cardtype2cleartext() { + local cardtype=$1 + case $cardtype in + OSD_10GIG) echo "OSA card in OSD mode, 10 Gigabit Ethernet" ;; + OSD_1000) echo "OSA card in OSD mode, Gigabit Ethernet" ;; + OSD_100) echo "OSA card in OSD mode, Fast Ethernet" ;; + OSD_GbE_LANE) echo "OSA card in OSD mode, Gigabit Ethernet, LAN Emulation" ;; + OSD_FE_LANE) echo "OSA card in OSD mode, Fast Ethernet, LAN Emulation" ;; + OSD_TR_LANE) echo "OSA card in OSD mode, Token Ring, LAN Emulation" ;; + OSD_ATM_LANE) echo "OSA card in OSD mode, ATM, LAN Emulation" ;; + OSD_Express) echo "OSA card in OSD mode, unknown link type" ;; + HSTR) echo "OSA card in OSD mode, High Speed Token Ring" ;; + OSN) echo "OSA for NCP, ESCON/CDLC bridge" ;; + HiperSockets) echo "HiperSockets with CHPID type IQD" ;; + "GuestLAN QDIO") echo "GuestLAN based on OSA (QDIO)" ;; + "GuestLAN Hiper") echo "GuestLAN based on HiperSockets" ;; + unknown) echo "other" ;; + *) echo "unknown" + echo "l.$LINENO: found unknown card_type, code needs to be fixed" 1>&2 + ;; + esac +} + +# returns true iff running under z/VM +function isVM() { + local cpu_version=$(cat /proc/cpuinfo |grep "^processor " | head -n1 | sed 's/.*version = \([[:xdigit:]][[:xdigit:]]\).*/\1/' | tr '[:lower:]' '[:upper:]') + if [ "$cpu_version" = "FF" ]; then + return 0 + else + return 1 + fi +} + +# watch out: potential error message as side effect +function isLayer2Default() { + # Read default from sysfs because according to device + # drivers book there are differences in the default between + # OSA (l2), hipersockets (l3). + # This only works here in installer where nobody has overwritten + # the default setting with another custom value already! + if [ ! -f /sys/devices/${NETTYPE}/$SCH_R_DEVBUSID/layer2 ]; then + echo $"Could not read layer mode from sysfs" + return 1 + fi + local layer2 + read layer2 < /sys/devices/${NETTYPE}/$SCH_R_DEVBUSID/layer2 + if [ "$layer2" = "1" ]; then + return 0 + else + return 1 + fi +} + +# returns true iff either LAYER2 has been set to 1 or is the default +# watch out: potential error message as side effect +function isLayer2() { + case "x$LAYER2" in + x0) return 1 ;; # layer 3 + x1) return 0 ;; # layer 2 + x) # LAYER2 is unset or empty => qeth driver default applies. + isLayer2Default + return $? + ;; + *) echo "l.$LINENO: unknown value \"$LAYER2\" for LAYER2, code needs to be fixed" 1>&2 + return 2 ;; + esac +} + +# returns true iff qeth device $SCH_R_DEVBUSID +# is capable of supporting IPv6 +# watch out: potential error message as side effect +function ipv6_capable() { + [ "$NETTYPE" = "qeth" ] || return 1 + case $cardtype in + OSD_10GIG|OSD_1000|OSD_100|OSD_Express|HiperSockets|"GuestLAN QDIO") + return 0 ;; + OSD_GbE_LANE|OSD_FE_LANE|OSD_TR_LANE|OSD_ATM_LANE) return 1 ;; + HSTR|OSN|unknown) return 1 ;; + "GuestLAN Hiper") return 1 ;; + *) echo $"Unknown card_type to determine IPv6 support" + return 1 ;; + esac +} + +# sets device online _and_ retrieves DEVICE at the same time +function set_device_online() { + echo $"Activating network device..." + local sysnettype + case "${NETTYPE}" in + qeth|lcs) sysnettype=${NETTYPE} ;; + ctc) sysnettype=ctcm ;; + esac + if ! [ -f /sys/devices/${sysnettype}/$SCH_R_DEVBUSID/online ]; then + echo $"Sysfs path to set device online does not exist." + return 1 + fi + if ! sysecho /sys/devices/${sysnettype}/$SCH_R_DEVBUSID/online "1"; then + echo $"Could not set device ($SUBCHANNELS) online" + return 1 + fi + udevadm settle + local i=1 + while : ; do + local online + read online < /sys/devices/${sysnettype}/$SCH_R_DEVBUSID/online + [ "$online" == "1" ] && break + sleep 1 + i=$((i+1)) + if [ "$i" -gt 10 ]; then + echo $"Could not set device ($SUBCHANNELS) online within timeout" + return 1 + fi + done + if [ "$NETTYPE" = "lcs" -o "$NETTYPE" = "ctc" ]; then + # KH FIXME: Workaround for missing sysfs interface + # DEVICE=$(cat /sys/devices/lcs/${SUBCHANNELS//,*/}/if_name) + # replaced with flexible solution: + # https://bugzilla.redhat.com/show_bug.cgi?id=204803#c9 + # "sys/bus/ccwgroup/devices/${SUBCHANNEL}/net\:* + # for lcs after setting online" + if [ ! -h /sys/devices/${sysnettype}/$SCH_R_DEVBUSID/net:* ]; then + echo $"Device $SUBCHANNELS does not have required sysfs attribute 'net:*'" + return 1 + fi + DEVICE=$(echo /sys/devices/${sysnettype}/$SCH_R_DEVBUSID/net:*) + DEVICE=${DEVICE//*:/} + if [ "$DEVICE" = "" ]; then + echo $"Could not get device name for $SUBCHANNELS" + return 1 + fi + else # qeth + if [ ! -f /sys/devices/qeth/$SCH_R_DEVBUSID/if_name ]; then + echo $"Device $SUBCHANNELS does not have required sysfs attribute 'if_name'" + return 1 + fi + # (device needs to be online to read if_name from sysfs attribute!) + read DEVICE < /sys/devices/qeth/$SCH_R_DEVBUSID/if_name + if [ "$DEVICE" = "" ]; then + echo $"Could not get device name for $SUBCHANNELS" + return 1 + fi + if [ -f /sys/devices/qeth/$SCH_R_DEVBUSID/card_type ]; then + read cardtype < /sys/devices/qeth/$SCH_R_DEVBUSID/card_type + #debug echo "$cardtype" + # device is now online and link type will be known + echo -n $"Detected: " + cardtype2cleartext "$cardtype" + else + echo $"Could not read qeth network card type from sysfs." + fi + fi +} + +# sets device up and blocks until device appears to be up +function set_device_up() { + if [ -z "$DEVICE" ]; then + echo $"Could not determine interface name to bring up device $SUBCHANNELS" + return 1 + fi + # Device does not come up fast enough to use "ip" to configure, so block. + # While OSA come up themselves after setting online, + # e.g. HiperSockets won't => set them up explicitly for the following check + debug ip link set up $DEVICE + local i=1 + while : ; do + local tst=$(ip -o link show up dev $DEVICE) + [ -n "$tst" ] && break + sleep 1 + i=$((i+1)) + if [ "$i" -gt 10 ]; then + echo $"Could not bring up device $DEVICE within timeout" + return 1 + fi + done + return 0 +} + +function syntax_check_domainname() { + # - match against regex adopted from RFC1035,sec.2.3.1 or RFC1034,sec.3.5 + # (Internationalized Domain Names in Applications (IDNA) [RFC4690] + # have to be entered after encoding by punycode [RFC3492]) + [[ "$1" =~ ^[[:alpha:]]([[:alnum:]-]{0,61}[[:alnum:]])?(\.[[:alpha:]]([[:alnum:]-]{0,61}[[:alnum:]])?)*$ ]] + case $? in + 0) + # string matched the pattern + return 0 + ;; + 1) + # string did not match the pattern + echo "$2" + ;; + 2) + echo "l.$LINENO: syntax error in regex of match operator =~, code needs to be fixed" 1>&2 + ;; + *) + echo "l.$LINENO: unexpected return code of regex match operator =~, code needs to be fixed" 1>&2 + ;; + esac + return 1 +} + +function modprobe_alias() { + if [ ":$NETTYPE" = ":ctc" ]; then + echo "alias $DEVICE ctcm" >> /tmp/modprobe.conf + else + echo "alias $DEVICE $NETTYPE" >> /tmp/modprobe.conf + fi + if [ $? -ne 0 ]; then + echo $"Could not append alias for network device $DEVICE to modprobe.conf" + return 1 + fi + return 0 +} + +function disable_ipv6_autoconf() { + sysctl -w net.ipv6.conf.all.accept_ra=0 > /dev/null + sysctl -w net.ipv6.conf.all.accept_redirects=0 > /dev/null + sysctl -w net.ipv6.conf.all.autoconf=0 > /dev/null + sysctl -w net.ipv6.conf.default.accept_ra=0 > /dev/null + sysctl -w net.ipv6.conf.default.accept_redirects=0 > /dev/null + sysctl -w net.ipv6.conf.default.autoconf=0 > /dev/null +} + +function configure_ipv6_address() { + # device needs to be online + # arp flag needs to be on for ipv6 over osa because of ndisc. + # happens automatically by the driver. do NOT mess with default setting. + #NO#debug ip link set dev $DEVICE arp on + if ! debug ip -6 address add $IPADDR/$NETMASK dev $DEVICE; then + echo $"Could net set IPv6 address $IPADDR/$NETMASK for device $DEVICE" + return 1 + fi + # network route has been set by above "ip address add" already + # take care of MTU, which is bundled with ifconfig in the other IPv4 cases + if [ -n "$MMTU" ]; then + if ! debug ip link set $DEVICE $MMTU; then + echo $"Could net set maximum transfer unit ($MMTU) for device $DEVICE" + return 1 + fi + fi + return 0 +} + +function configure_ipv4_address() { + # it's IPv4 and we can make use of ipcalc for better usability + if ipcalc -bmnp $ipcalc_arg > /tmp/ipcalc.$$.out 2> /dev/null; then + . /tmp/ipcalc.$$.out + else + echo $"Could not calculate network address and broadcast address from" + echo $" IPv4 address $IPADDR and netmask $NETMASK" + return 1 + fi + rm /tmp/ipcalc.$$.out + # device needs to be online + if ! debug ifconfig $DEVICE $IPADDR $MMTU netmask $NETMASK broadcast $BROADCAST; then + echo $"Could not set IPv4 address $IPADDR for device $DEVICE" + echo $" with network mask $NETMASK and broadcast address $BROADCAST" + [ -n "$MMTU" ] && echo $" and maximum transfer unit: $MMTU" + return 1 + fi + # This network route is already there after ifconfig! + #if ! debug route add -net $NETWORK netmask $NETMASK dev $DEVICE; then + # echo $"Could not add network route to $NETWORK/$NETMASK on device $DEVICE" + # return 1 + #fi + return 0 +} + +function handle_mtu() { + # don't ask for MTU, but use it if it has been set in the .parm file + # don't overwrite MMTU if it has been set for CTC + [ -n "$MTU" -a -z "$MMTU" ] && MMTU="mtu $MTU" +} + +function rollback_config() { + # each transaction to roll back may fail, if previous setup has not + # made progress that far to reach a certain transation + # => error output is misleading and should be avoided + [ -n "$DEVICE" ] && tv ip -4 route flush default dev $DEVICE + [ -n "$DEVICE" ] && tv ip -6 route flush default dev $DEVICE + # address flush seems to be effective for all address families + [ -n "$DEVICE" ] && ip address flush dev $DEVICE + if [ -n "$NETTYPE" ]; then + if [ -n "$SCH_R_DEVBUSID" ]; then + local sysnettype + case "${NETTYPE}" in + qeth|lcs) sysnettype=${NETTYPE} ;; + ctcm) sysnettype=ctcm ;; + esac + [ -f /sys/devices/${sysnettype}/$SCH_R_DEVBUSID/online ] && \ + sysecho /sys/devices/${sysnettype}/$SCH_R_DEVBUSID/online "0" + udevadm settle + [ -f /sys/devices/${sysnettype}/$SCH_R_DEVBUSID/ungroup ] && \ + sysecho /sys/devices/${sysnettype}/$SCH_R_DEVBUSID/ungroup "1" + udevadm settle + fi + fi + [ -z "$mtu_was_set" ] && unset MTU + [ -z "$mmtu_was_set" ] && unset MMTU + [ -z "$vswitch_was_set" ] && unset VSWITCH + # prevent possible reuse of an old DEVICE on restarting dialog + unset DEVICE + # set activated DASDs offline again + local dasd + while read dasd < /proc/dasd/devices; do + dasd=${dasd%%(*} + sysecho /sys/bus/ccw/devices/$dasd/online 0 + done + udevadm settle +} + +### workflow helper functions + +# workflow ideas: +# - setting/applying single configuration steps right away save us explicit +# syntactical & semantic checks PLUS we get direct feedback on error +# - check error level of forked external programs and react on errors + +unset reenter +unset redoitem +unset interaction_happened + +function reenter() { + [ -z "$reenter" ] && return 1 + # reenter menu should only be shown if NOT redoing item + if [ -n "$redoitem" ]; then + # unset redoitem # wrong => do NOT do this here + return 1 + fi + return 0 +} + +function reenter_menu() { + local oldvalue=$1 + interaction_happened="yes" + # unsetting input here is not sufficient, since reenter_menu + # is not called for predefined parameters + # which then might get assigned a previous old input of another parameter! + #unset input + reenter || return 0 + # don't present reenter menu for empty parameters + # (currently ignoring parameters that are allowed to be empty!) + # this could be improved by checking if variable has been set/defined + #[ -z "$1" ] && return 0 + while : ; do + if [ -n "$helptext" ]; then + echo $"0) default is previous \"$oldvalue\", 1) new value, ?) help" + else + echo $"0) default is previous \"$oldvalue\", 1) new value" + fi + # uncoded alternative: 2) skip parameter + local answer + read answer + [ -z "$answer" ] && return 1 + case $answer in + 0) return 1 ;; + 1) # Deciding to enter new value gets user out of reenter-mode + # temporarily for this parameter. + # To put it differently: redoing does NOT present old values. + redoitem="yes" + echo -n $"new value: " + return 0 + ;; + "?") input="?" + return 1 + ;; + esac + done +} + +function workflow_item_menu() { + local noredo=$1 + # default is to continue if running kickstart to prevent interaction + [ -n "$RUNKS" ] && return 0 + interaction_happened="yes" + while : ; do + unset redoitem + if [ "$noredo" = "noredo" ]; then + echo $"1) continue, 2) restart dialog, 3) halt, 4) shell" + else + echo $"0) redo this parameter, 1) continue, 2) restart dialog, 3) halt, 4) shell" + fi + local answer + read answer + case $answer in + 0) [ "$noredo" = "noredo" ] && continue + redoitem="yes" + continue 2 + ;; + 1) return 0 ;; # can be used to break at caller on ignore + 2) reenter="yes" + rollback_config + continue 3 + ;; + 3) tv doshutdown + exit 0 + ;; + 4) echo $"Enter 'exit' at the shell prompt to get back to the installation dialog." + /bin/bash + if [ "$noredo" != "noredo" ] && [ -n "$question_prefix" ]; then + $question_prefix + echo + fi + ;; # stay in workflow item menu + esac + done +} + +# input variables: PARMNAME, question_prefix, question_choices, +# "options" ... +# output variables: $question_prefix, $helptext +# modifies: the variable named $PARMNAME, $OPTIND +function ask() { + [ $# -lt 3 ] && echo "l.$LINENO: too few arguments (<3), please fix calling code." 1>&2 + local PARMNAME=$1 + shift + question_prefix=$1 + shift + local question_choices=$1 + shift + local exception + local syntax_check + unset helptext + local handle + local finish + local optname + OPTIND=1 + while getopts ":e:s:h:c:f:" optname; do + case $optname in + e) exception=$OPTARG ;; + s) syntax_check=$OPTARG ;; + h) helptext=$OPTARG ;; + c) handle=$OPTARG ;; + f) finish=$OPTARG ;; + "?") ;; # ignore invalid option + :) echo "l.$LINENO: Missing parameter to option -$OPTARG" 1>&2 ;; + esac + done + while : ; do + unset input + local input + # actually ask question if one of the following is true: + # - $PARMNAME parameter has not been set yet, e.g. not in parm file + # - on 2nd and further attempts, i.e. redoing the parameter + # - on having restarted the whole dialog + # describing the same from another viewpoint: + # - if $PARMNAME has been set, try to check syntax and apply + # - on redo, $PARMNAME has been set and reenter is false, + # but still ask question again + # - on reenter, $PARMNAME might have been set, but still ask question + if [ -z "${!PARMNAME}" -o -n "$redoitem" -o -n "$reenter" ]; then + # one empty line to separate parameter questions from each other + echo + $question_prefix + if reenter; then + echo + else + $question_choices + fi + # on reenter, give choice between old value and entering new one + reenter_menu ${!PARMNAME} && read input \ + && [ "$input" != "?" ] && eval ${PARMNAME}=\$input + # escaping the $ in the RHS of the eval statement makes it safe + fi + if [ -n "$helptext" ] && [ "$input" = "?" ]; then + $helptext + continue + fi + # optional: default or exceptional handling + [ -n "$exception" ] && $exception + if [ -n "$syntax_check" -a -z "$handle" ]; then + # some parameters have only syntax check (and deferred config): + if $syntax_check; then + break + else + workflow_item_menu && break + fi + elif [ -n "$syntax_check" -a -n "$handle" ]; then + # most common parameters have syntax and configuration: + # user might still continue on syntax error + $syntax_check || workflow_item_menu + # optional: actual configuration + if $handle; then + # parmname has been configured successfully + break + else + # user might still continue on configuration failure + workflow_item_menu && break + fi + elif [ -n "$finish" ]; then + # few parameters need special handling done by their own function: + $finish + else + echo $"Unsupported calling of ask function, please fix calling code" + fi + done # PARMNAME + # disable potential temporary redoing-mode during reenter-mode + unset redoitem +} + +### NETTYPE + +function syntax_check_nettype() { + # - NETTYPE \in {qeth,lcs,ctc} + [[ "$NETTYPE" =~ (^qeth$)|(^lcs$)|(^ctc$) ]] + case $? in + 0) + # string matched the pattern + return 0 + ;; + 1) + # string did not match the pattern + ;; + 2) + echo "l.$LINENO: syntax error in regex of match operator =~, code needs to be fixed" 1>&2 + ;; + *) + echo "l.$LINENO: unexpected return code of regex match operator =~, code needs to be fixed" 1>&2 + ;; + esac + echo $"Incorrect format or value for network type (NETTYPE): $NETTYPE" + return 1 +} + +function question_prefix_nettype() { + echo -n $"Network type" +} + +function question_choices_nettype() { + echo $" (qeth, lcs, ctc, ? for help). Default is qeth:" +} + +function helptext_nettype() { + echo $" Help text for network type:" + echo $" qeth: OSA-Express Fast Ethernet, Gigabit Ethernet (including 1000Base-T)," + echo $" High Speed Token Ring, Hipersockets, and ATM (running Ethernet LAN emulation)" + echo $" features in QDIO mode." + echo $" [default]" + echo $" lcs: OSA-2 Ethernet/Token Ring, OSA-Express Fast Ethernet in non-QDIO mode," + echo $" OSA-Express High Speed Token Ring in non-QDIO mode and Gigabit Ethernet" + echo $" in non-QDIO mode." + echo $" ctc: Deprecated, useful for migration." +} + +function exception_nettype() { + # - default is qeth since it should be common + if [ -z "$NETTYPE" ]; then + NETTYPE=qeth + break + fi +} + +function finish_nettype() { + if syntax_check_nettype; then + break + else + # necessary parts which would otherwise be done by workflow_item_menu + interaction_happened="yes" + redoitem="yes" + fi +} + +function do_nettype() { + ask NETTYPE \ + question_prefix_nettype question_choices_nettype \ + -h helptext_nettype -e exception_nettype -f finish_nettype +} + +### CHANDEV + +function do_chandev() { + echo + echo $"The CHANDEV variable isn't used anymore, please update your " + echo $".parm or the .conf file to use NETTYPE, SUBCHANNELS, etc. instead." + echo +} + +### SUBCHANNELS + +function syntax_check_subchannels() { + SUBCHANNELS=$(echo $SUBCHANNELS | tr ABCDEF abcdef) + # - make subchannel question dependent on NETTYPE (2 vs. 3 subchannels) + if [ "$NETTYPE" = "qeth" ]; then + # - match against regex, depending on qeth + [[ "$SUBCHANNELS" =~ ^[[:xdigit:]]+\.[0-3]\.[[:xdigit:]]{4},[[:xdigit:]]+\.[0-3]\.[[:xdigit:]]{4},[[:xdigit:]]+\.[0-3]\.[[:xdigit:]]{4}$ ]] + else + # - match against regex, depending on lcs/ctc + [[ "$SUBCHANNELS" =~ ^[[:xdigit:]]+\.[0-3]\.[[:xdigit:]]{4},[[:xdigit:]]+\.[0-3]\.[[:xdigit:]]{4}$ ]] + fi + case $? in + 0) + # string matched the pattern + return 0 + ;; + 1) + # string did not match the pattern + echo $"Incorrect format for channels (SUBCHANNELS): $SUBCHANNELS" + ;; + 2) + echo "l.$LINENO: syntax error in regex of match operator =~, code needs to be fixed" 1>&2 + ;; + *) + echo "l.$LINENO: unexpected return code of regex match operator =~, code needs to be fixed" 1>&2 + ;; + esac + return 1 +} + +function semantic_check_subchannels() { + local subch_count + if [ "$NETTYPE" = "qeth" ]; then + subch_count=3 + else + subch_count=2 + fi + # done: make subchannel handling more robust by not relying on REMATCH + local -a subch_array + IFS=, + read -a subch_array <<< "indexzero,$SUBCHANNELS" + unset IFS + local i + local all_subch_good=0 + for ((i=1; i <= $subch_count; i++)); do + local devbusid=${subch_array[$i]} + # remember first subchannel for potential undo of ccwgroup + # (via /sys/devices/qeth/$SCH_R_DEVBUSID/ungroup) + [ "$i" -eq 1 ] && SCH_R_DEVBUSID=$devbusid + local prefix ssid devno foo + IFS=. + read prefix ssid devno foo <<< "$devbusid" + unset IFS + local dev_p=$(echo /sys/devices/css$prefix/$IDFORMAT/$devbusid) + # - check for existence of devnos in sysfs + if [ ! -d "$dev_p" -a "$cio_wc_bytes" != "0" ]; then + # - try to free from /proc/cio_ignore if they don't exist + echo $"Device $devbusid not present, trying to clear from blacklist and resense..." + if sysecho /proc/cio_ignore "free $devbusid"; then + # /proc/cio_ignore won't block on freeing devices + # until resensing has been completed, so wait until + # the udev event queue depletes (without udevadm settle we + # could wait 2 seconds unconditionally) + #debug ls -laF /dev/.udev + udevadm settle + # even though the device might now be online, some of its + # sysfs attributes (e.g. cutype) might not yet be available + sleep 1 + else + echo $"Device $devbusid could not be cleared from device blacklist" + fi + fi + # reevaluate since globbing might not have worked before device existed + dev_p=$(echo /sys/devices/css$prefix/$IDFORMAT/$devbusid) + if [ ! -d "$dev_p" ]; then + echo $"Device $devbusid does not exist" + all_subch_good=1 + continue + fi + # devno does exist now + local subch_p=${dev_p%/*} + local subch=${subch_p##*/} + # filter definitely unusable subchannels ... + # - check for subchannel type I/O + if [ -f $subch_p/type ]; then + local type + read type < $subch_p/type + if [ "$type" != "$SUBCHANNEL_TYPE_IO" ]; then + echo $"Channel $subch (device $devbusid) is not of type I/O" + all_subch_good=1 + continue + fi + fi + # - check for correct CU type/model, depending on qeth/lcs/ctc + if [ ! -f $dev_p/cutype ]; then + echo $"Device $devbusid does not have required sysfs attribute 'cutype'" + all_subch_good=1 + continue + fi + local cutype + read cutype < $dev_p/cutype + if search_cu $cutype; then + local driver + if [ "$NETTYPE" = "ctc" ]; then + driver="ctcm" + else + driver=$NETTYPE + fi + if [ "${CU_DEVDRV[$cu_idx]}" != "$driver" ]; then + echo $"Device $devbusid has control unit type $cutype," + echo $" which does not match your selected network type $NETTYPE" + all_subch_good=1 + continue + fi + else + echo $"Device $devbusid has control unit type $cutype which is unknown" + all_subch_good=1 + continue + fi + # read CHPIDs information about subchannels + if [ ! -f $subch_p/chpids ]; then + echo $"Channel $subch (device $devbusid) does not have required sysfs attribute 'chpids'" + all_subch_good=1 + continue + fi + local chpid_list + read chpid_list < $subch_p/chpids + local -a chpids + read -a chpids <<< "$chpid_list" + if [ ${#chpids[@]} -ne 8 ]; then + echo $"sysfs reported ${#chpids[@]} CHPIDs instead of expected 8, code needs fix" + fi + if [ ! -f $subch_p/pimpampom ]; then + echo $"Channel $subch (device $devbusid) does not have required sysfs attribute 'pimpampom'" + all_subch_good=1 + continue + fi + local pim pam pom foo + read pim pam pom foo < $subch_p/pimpampom + local pimchpidZ="" + for ((chp=0; chp < 8; chp++)); do + local mask=$((0x80 >> chp)) + if (( 0x$pim & $mask )); then + pimchpidZ=${pimchpidZ}${chpids[chp]} + else + pimchpidZ=${pimchpidZ}"ZZ" + fi + done + local pimchpids=${pimchpidZ//ZZ/} + if [ "x$pimchpids" == "x" ]; then + echo $"Channel $subch (device $devbusid) does not have any installed channel path" + all_subch_good=1 + continue + fi + # compare parts of different subchannels for required matches + if [ "$i" -eq 1 ]; then + # remember parts of first subchannel for comparison + local sch_r_prefix=$prefix + local sch_r_ssid=$ssid + local sch_r_devno=$devno + local sch_r_pimchipidZ=$pimchpidZ + local sch_r_cutype=$cutype + else + local comparison=0 + # $sch_r_... might be empty if first channel was wrong + # => be sure to quote all variable accesses in test statements. + # - all subchannels must be of same CU type/model + if [ "$cutype" != "$sch_r_cutype" ]; then + echo $"Device $devbusid does not have the same control unit type as device $SCH_R_DEVBUSID" + comparison=1 + fi + # - all subchannels must have same CHPIDs + if [ "$pimchpidZ" != "$sch_r_pimchipidZ" ]; then + echo $"Device $devbusid does not have the same CHPIDs as device $SCH_R_DEVBUSID" + comparison=1 + fi + # - all subchannels should have same prefix & ssid ? + if [ "$prefix" != "$sch_r_prefix" \ + -o "$ssid" != "$sch_r_ssid" ]; then + echo $"Device $devbusid does not have the same prefix and subchannel set ID as device $SCH_R_DEVBUSID" + comparison=1 + fi + if [ "$i" -eq 2 ]; then + local sch_w_devbusid=$devbusid + local sch_w_devno=$devno + # TODO: not true for CTCM => relax + # - write_devbusid == read_devbusid+1 + if [ $((0x$devno)) -ne $((0x$sch_r_devno + 1)) ]; then + echo $"Device bus ID of write channel (dev $devbusid) must be one larger than" + echo $" that of read channel (dev $SCH_R_DEVBUSID)" + comparison=1 + fi + elif [ "$i" -eq 3 ]; then + # check data subchannel unequal to read/write subchannel + # (also seems to be handled by ccwgroup kernel subsystem) + if [ "$devbusid" = "$sch_w_devbusid" \ + -o "$devbusid" = "$SCH_R_DEVBUSID" ]; then + echo $"Device bus ID of data channel (dev $devbusid) must be different to that of" + echo $" read channel ($SCH_R_DEVBUSID) and write channel ($sch_w_devbusid)" + comparison=1 + fi + fi + if [ "$comparison" != 0 ]; then + all_subch_good=1 + continue + fi + fi + # filter potentially good subchannels ... + if [ -h $dev_p/group_device ]; then + echo $"Device $devbusid is already in a ccwgroup and thus unavailable" + all_subch_good=1 + continue + fi + if [ ! -f $dev_p/online ]; then + echo $"Device $devbusid does not have required sysfs attribute 'online'" + all_subch_good=1 + continue + fi + local online + read online < $dev_p/online + if [ "$online" = "1" ]; then + echo $"Device $devbusid is already in use and thus unavailable" + all_subch_good=1 + continue + fi + # - check availability + if [ ! -f $dev_p/availability ]; then + echo $"Device $devbusid does not have required sysfs attribute 'availability'" + all_subch_good=1 + continue + fi + local availability + read availability < $dev_p/availability + if [ "$availability" != "good" ]; then + echo $"Device $devbusid is not available but '$availiability'" + all_subch_good=1 + continue + fi + + done # for ((i=1; i <= $subch_count; i++)) + if [ "$all_subch_good" = "0" ]; then + return 0 + fi + return 1 +} + +function handle_subchannels() { + # - try to establish ccwgroup right here and fail out on error + local driver + if [ "$NETTYPE" = "ctc" ]; then + driver="ctcm" + else + driver=$NETTYPE + fi + if sysecho /sys/bus/ccwgroup/drivers/${driver}/group "$SUBCHANNELS"; then + udevadm settle + case "$NETTYPE" in + qeth) + # Just preliminary card_type info until device goes online! + # In fact it seems enough to separate OSA from HiperSockets. + if [ -f /sys/devices/qeth/$SCH_R_DEVBUSID/card_type ]; then + read cardtype < /sys/devices/qeth/$SCH_R_DEVBUSID/card_type + else + echo $"Could not read qeth network card type from sysfs." + fi + ;; + ctc|lcs) + if [ -f /sys/devices/$driver/$SCH_R_DEVBUSID/type ]; then + local type + read type < /sys/devices/$driver/$SCH_R_DEVBUSID/type + [ "$type" = "CTC/A" ] && \ + type="channel-to-channel adapter (CTC/A)" + echo $"Detected: $type" + else + echo $"Could not read ctc network card type from sysfs." + fi + ;; + esac + return 0 + else + echo $"Channels $SUBCHANNELS could not be grouped" + fi + return 1 +} + +function question_prefix_subchannels() { + if [ "$NETTYPE" = "qeth" ]; then + echo -n $"Read,write,data channel" + else + echo -n $"Read,write channel" + fi +} + +function question_choices_subchannels() { + if [ "$NETTYPE" = "qeth" ]; then + echo $" (e.g. 0.0.0300,0.0.0301,0.0.0302 or ? for help)." + else + echo $" (e.g. 0.0.0600,0.0.0601 or ? for help)" + fi +} + +function helptext_subchannels() { + if [ "$NETTYPE" = "qeth" ]; then + echo $" Help text for qeth channels:" + echo $" Enter the device bus ID of your CCW devices." + echo $" QETH needs three channels for read, write, and data," + echo $" e.g. 0.0.0300,0.0.0301,0.0.0302" + else + echo $" Help text for lcs/ctc channels:" + echo $" Enter the device bus ID of your CCW devices." + echo $" CTC/ESCON and LCS need two channels for read and write," + echo $" e.g. 0.0.0600,0.0.0601 will configure the CTC or ESCON interface" + echo $" with the channels 0x600 and 0x601" + fi +} + +function finish_subchannels() { + syntax_check_subchannels || workflow_item_menu + # continuing on syntax error is doomed to fail, + # since handle_subchannels relies on the regex-based strict parsing + # in syntax_check_subchannels which does not match anything then + # news: relaxed by splitting semantic check and actual handling + semantic_check_subchannels || workflow_item_menu + if handle_subchannels; then + break + else + workflow_item_menu && break + fi +} + +function do_subchannels() { + ask SUBCHANNELS \ + question_prefix_subchannels question_choices_subchannels \ + -h helptext_subchannels -f finish_subchannels +} + +### PORTNAME (qeth) + +function syntax_check_portname() { + # - 1-8 characters, we convert it to upper case + PORTNAME=$(echo $PORTNAME | tr '[:lower:]' '[:upper:]') + local portname_len=${#PORTNAME} + if [ "$portname_len" -ge 1 -a "$portname_len" -le 8 ]; then + return 0 + fi + echo $"Incorrect string length [1..8] for portname (PORTNAME): $PORTNAME" + return 1 +} + +function handle_portname() { + [ -n "$PORTNAME" ] || return 0 + # - try to set portname right here w/ error handling + if sysecho /sys/devices/${NETTYPE}/$SCH_R_DEVBUSID/portname "$PORTNAME"; then + return 0 + else + echo $"Portname '$PORTNAME' could not be configured for $SUBCHANNELS" + fi + return 1 +} + +function hint_portname() { + if [ -f /sys/devices/${NETTYPE}/$SCH_R_DEVBUSID/portname ]; then + local pname_hint + read pname_hint < /sys/devices/${NETTYPE}/$SCH_R_DEVBUSID/portname + if [ "$pname_hint" = "no portname required" ]; then + echo $" * Your configuration does not require a portname. *" + fi + fi +} + +function question_prefix_portname(){ + echo -n $"Portname" +} + +function question_choices_portname(){ + echo $" (1..8 characters, or ? for help). Default is no portname:" +} + +function helptext_portname(){ + echo $" Help text for portname:" + # updated text describing when portname is obsolete; + # taken from: + # SA22-7935-09, Open Systems Adapter-Express Customer's + # Guide and Reference, 10th ed. May 2008, IBM, p.17f. + # SC33-8411-00, Device Drivers, Features, and Commands, + # 1st ed. May 2008, IBM, p.116. + echo $" Portname of the OSA-Express feature in QDIO mode and z/VM Guest LAN." + echo $" This parameter is optional with:" + echo $" - z/VM 4.4.0 or z/VM 4.3.0 with APARs VM63308 and PQ73878" + echo $" - z800, z900 with >= Driver 3G - EC stream J11204, MCL032 (OSA level 3.33)" + echo $" - z890, z990, z9, z10 mainframes" + hint_portname + echo $" If portname is used, all operating systems sharing port must use same name." + echo $" Input empty string if you don't want to enter a portname. [default]" +} + +function exception_portname(){ + [ -z "$PORTNAME" ] && break +} + +function do_portname() { + ask PORTNAME \ + question_prefix_portname question_choices_portname \ + -h helptext_portname \ + -e exception_portname -s syntax_check_portname -c handle_portname +} + +### PORTNO (qeth) + +function syntax_check_qeth_portno() { + case $PORTNO in + 0|1) + return 0 + ;; + esac + echo $"Incorrect format or value for relative port number (PORTNO): $PORTNO" + return 1 +} + +function handle_qeth_portno() { + if sysecho /sys/devices/qeth/$SCH_R_DEVBUSID/portno "$PORTNO"; then + return 0 + fi + echo $"Could not configure relative port number $PORTNO for $SUBCHANNELS" + return 1 +} + +function question_prefix_portno() { + echo -n $"Relative port number for OSA" +} + +function question_choices_portno() { + echo $" (0, 1, or ? for help). Default is 0:" +} + +function helptext_portno() { + echo $" Help text for relative port number for OSA with 2 ports per CHPID:" + echo $" This applies to:" + echo $" - OSA-Express3 Gigabit Ethernet on z10 systems" + echo $" - OSA-Express ATM on zSeries 800 and 900 systems" + echo $" 0 for relative port number 0 [default]" + echo $" 1 for relative port number 1" + echo $" Input empty string to not modify the default configuration." +} + +function exception_portno() { + # Writing portno of e.g. hipersockets device fails. + # Therefore, do not configure on empty default value. + [ -z "$PORTNO" ] && break +} + +function do_portno() { + ask PORTNO \ + question_prefix_portno question_choices_portno \ + -h helptext_portno -e exception_portno \ + -s syntax_check_qeth_portno -c handle_qeth_portno +} + +### LAYER2 + +function syntax_check_layer2() { + # - $LAYER2 \in {0,1} + case $LAYER2 in + 0|1) + return 0 + ;; + esac + echo $"Incorrect format or value for layer2 mode (LAYER2): $LAYER2" + return 1 +} + +function handle_layer2() { + [ "$NETTYPE" == "qeth" ] || return 0 + [ -n "$LAYER2" ] || return 0 + # - try to set layer2 mode right here w/ error handling + if sysecho /sys/devices/${NETTYPE}/$SCH_R_DEVBUSID/layer2 "$LAYER2"; then + return 0 + else + echo $"Layer2 mode '$LAYER2' could not be configured for $SUBCHANNELS" + fi + return 1 +} + +function question_prefix_layer2() { + echo -n $"Layer mode" +} + +function question_choices_layer2() { + echo -n $" (0 for layer3, 1 for layer2, or ? for help)." + if [ "$isLayer2Default" = "yes" ]; then + echo $" Default is 1:" + else + echo $" Default is 0:" + fi +} + +function helptext_layer2() { + echo $" Help text for OSA mode of operation: layer 2 vs. layer 3" + if [ "$isLayer2Default" = "yes" ]; then + echo $" 0 for layer 3 mode (may not work with dhcp, tcpdump, etc.)" + echo $" 1 for layer 2 mode [default]" + else + echo $" 0 for layer 3 mode [default] (may not work with dhcp, tcpdump, etc.)" + echo $" 1 for layer 2 mode" + fi +} + +function exception_layer2() { + if [ -z "$LAYER2" ]; then + isLayer2Default && LAYER2=1 || LAYER2=0 + # do not break, always apply, default may differ from online layer mode + #break + fi +} + +function do_layer2() { + isLayer2Default && isLayer2Default=yes || isLayer2Default=no + ask LAYER2 \ + question_prefix_layer2 question_choices_layer2 \ + -h helptext_layer2 -e exception_layer2 \ + -s syntax_check_layer2 -c handle_layer2 +} + +### MACADDR + +function syntax_check_macaddr() { + # - match against regex + [[ "$MACADDR" =~ ^[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]$ ]] + case $? in + 0) + # string matched the pattern + return 0 + ;; + 1) + # string did not match the pattern + echo $"Incorrect format for mac address (MACADDR): $MACADDR" + ;; + 2) + echo "l.$LINENO: syntax error in regex of match operator =~, code needs to be fixed" 1>&2 + ;; + *) + echo "l.$LINENO: unexpected return code of regex match operator =~, code needs to be fixed" 1>&2 + ;; + esac + return 1 +} + +function handle_macaddr() { + # - try to set macaddr right here w/ error handlg. + # device needs to be online + if debug ifconfig $DEVICE hw ether $MACADDR; then + return 0 + fi + echo $"MAC address $MACADDR could not be configured for" + echo $" $SUBCHANNELS (network device $DEVICE)" + return 1 +} + +function question_prefix_macaddr() { + echo -n $"Unique MAC address" +} + +function question_choices_macaddr() { + macaddr_default=$(ifconfig $DEVICE | grep 'HWaddr' | sed 's/.*HWaddr \([[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]:[[:xdigit:]][[:xdigit:]]\).*/\1/') + echo $" (e.g. 02:00:00:00:00:00, ? for help). Default is $macaddr_default:" +} + +function helptext_macaddr() { + echo $" Help text for MAC address:" + if [ -z "${cardtype//OSD_*/}" ]; then + echo $" For real OSA in layer 2 mode, a random MAC address is automatically assigned." + else + echo $" If connecting to a layer 2 VSWITCH, a MAC address is automatically assigned." + fi + echo $" You may accept the automatic MAC address with an empty input. [default]" + echo $" If the automatic address is not unique, please provide a MAC address." + [ -z "${cardtype//OSD_*/}" ] && \ + echo $" For real OSA, the provided address must be different from that of the OSA." + echo $" You may override the automatic MAC address with non-empty input." + echo $" An example MAC address would be: 02:00:00:00:00:00" +} + +function exception_macaddr() { + if [ -z "$MACADDR" ]; then + if [ -z "${cardtype//OSD_*/}" ]; then + # keep random default MAC address of real OSA, + # so the OSA comes up with the same MAC each time in the future + MACADDR=$macaddr_default + else + # virtual OSA in layer2 is GuestLAN or VSWITCH + VSWITCH=1 + fi + break + fi +} + +function do_macaddr() { + ask MACADDR \ + question_prefix_macaddr question_choices_macaddr \ + -h helptext_macaddr -e exception_macaddr \ + -s syntax_check_macaddr -c handle_macaddr +} + +### CTCPROT + +function syntax_check_ctcprot() { + case "x$CTCPROT" in + x|x0) + unset CTCPROT + return 0 + ;; + x1|x3) + return 0 + ;; + x2) + echo $"CTC tty's are not usable for this installation (CTCPROT)" + ;; + *) + echo $"Incorrect format or value for CTC protocol (CTCPROT): $CTCPROT" + ;; + esac + return 1 +} + +function handle_ctcprot() { + [ -n "$CTCPROT" ] || return 0 + if sysecho /sys/devices/ctcm/${SCH_R_DEVBUSID}/protocol "$CTCPROT"; then + return 0 + fi + echo $"Could not configure CTC protocol $CTCPROT for $SUBCHANNELS" + return 1 +} + +function question_prefix_ctcprot() { + echo -n $"CTC protocol" +} + +function question_choices_ctcprot() { + echo $" (0, 1, 3, or ? for help). Default is 0:" +} + +function helptext_ctcprot() { + echo $" Help text for CTC protocol:" + echo $" Protocol which should be used for the CTC interface" + echo $" 0 for compatibility with p.e. VM TCP service machine [default]" + echo $" 1 for enhanced package checking for Linux peers" + echo $" 3 for compatibility with OS/390 or z/OS peers" +} + +function do_ctcprot() { + ask CTCPROT \ + question_prefix_ctcprot question_choices_ctcprot \ + -h helptext_ctcprot -s syntax_check_ctcprot -c handle_ctcprot +} + +### PORTNAME (LCS portno) + +function syntax_check_lcs_portno() { + [[ "$PORTNAME" =~ ^[[:digit:]]+$ ]] + case $? in + 0) + # string matched the pattern + return 0 + ;; + 1) + # string did not match the pattern + ;; + 2) + echo "l.$LINENO: syntax error in regex of match operator =~, code needs to be fixed" 1>&2 + ;; + *) + echo "l.$LINENO: unexpected return code of regex match operator =~, code needs to be fixed" 1>&2 + ;; + esac + echo $"Incorrect format for LCS port number (PORTNAME): $PORTNAME" + return 1 +} + +function handle_lcs_portno() { + [ -n "$PORTNAME" ] || return 0 + if sysecho /sys/devices/lcs/$SCH_R_DEVBUSID/portno "$PORTNAME"; then + return 0 + fi + echo $"Could not configure relative port number $PORTNAME for $SUBCHANNELS" + return 1 +} + +function question_prefix_lcs_portno() { + echo -n $"Relative port number of your LCS device" +} + +function question_choices_lcs_portno() { + echo $" (number or ? for help). Default is 0:" +} + +function helptext_lcs_portno() { + echo $" Help text for relative port number of LCS device:" + echo $" Required for OSA-Express ATM cards only." +} + +function exception_lcs_portno() { + [ -z "$PORTNAME" ] && break +} + +function do_lcs_portno() { + # LCS portno and QETH portname share the parameter variable PORTNAME. + # For compatibility with existing parm files we keep this scheme. + ask PORTNAME \ + question_prefix_lcs_portno question_choices_lcs_portno \ + -e exception_lcs_portno \ + -h helptext_lcs_portno -s syntax_check_lcs_portno -c handle_lcs_portno +} + +### HOSTNAME + +function syntax_check_hostname() { + syntax_check_domainname "$HOSTNAME" "Incorrect format for hostname (HOSTNAME): $HOSTNAME" +} + +function handle_hostname() { + if ! hostname $HOSTNAME; then + echo $"Could not configure hostname $HOSTNAME" + return 1 + fi + return 0 +} + +function question_prefix_hostname() { + echo -n $"Hostname of your new Linux guest" +} + +function question_choices_hostname() { + echo $" (FQDN e.g. s390.redhat.com or ? for help):" +} + +function helptext_hostname() { + echo $" Help text for hostname:" + echo $" Enter the full qualified domain name of your host." +} + +function do_hostname() { + ask HOSTNAME \ + question_prefix_hostname question_choices_hostname \ + -h helptext_hostname -s syntax_check_hostname -c handle_hostname +} + +### IPADDR + +function syntax_check_ipaddr() { + unset ipv4 + unset ipv6 + if checkipv4 $IPADDR; then + ipv4="yes" + return 0 + elif [ "$ipv6_capable" = "yes" ] && checkipv6 $IPADDR; then + ipv6="yes" + return 0 + fi + echo $"Incorrect format for IP address (IPADDR): $IPADDR" + return 1 +} + +function question_prefix_ipaddr() { + echo -n $"IPv4 address" + [ "$ipv6_capable" = "yes" ] && echo -n $" / IPv6 addr." +} + +function question_choices_ipaddr() { + echo -n $" (e.g. 10.0.0.2" + [ "$ipv6_capable" = "yes" ] && echo -n $" / 2001:0DB8::" + echo $" or ? for help)" +} + +function helptext_ipaddr() { + echo $" Help text for IP address:" + echo $" Enter a valid IPv4 address of your new Linux guest (e.g. 10.0.0.2)" + if [ "$ipv6_capable" = "yes" ]; then + echo $" or alternatively a valid IPv6 address without CIDR prefix (e.g. 2001:0DB8::)" + echo $" IPv6 is supported on:" + echo $" - Ethernet interfaces of the OSA-Express adapter running in QDIO mode." + echo $" - HiperSockets interfaces" + echo $" - z/VM guest LAN interfaces running in QDIO mode." + echo $" IPv6 is not supported on HiperSockets guest LAN, OSA-Express Token Ring, ATM." + fi +} + +function do_ipaddr() { + ipv6_capable && ipv6_capable=yes || ipv6_capable=no + ask IPADDR \ + question_prefix_ipaddr question_choices_ipaddr \ + -h helptext_ipaddr -s syntax_check_ipaddr + if [ "$ipv6" ]; then + # qeth_l3 would load ipv6 automatically but not qeth_l2 + modprobe ipv6 + tv disable_ipv6_autoconf + fi + + # no handling/configuring of IPADDR yet, since more parameters needed +} + +### NETMASK (IPv4) + +function syntax_check_netmask_v4() { + # also support CIDR prefix + if [[ "$NETMASK" =~ ^[[:digit:]]+$ ]]; then + if [ "$NETMASK" -ge 1 -a "$NETMASK" -le 32 ]; then + ipcalc_arg="$IPADDR/$NETMASK" + return 0 + fi + echo $"Incorrect value for network prefix [1..32] (NETMASK): $NETMASK" + return 1 + elif checkipv4 $NETMASK; then + ipcalc_arg="$IPADDR $NETMASK" + return 0 + fi + echo $"Incorrect format or value for network mask (NETMASK): $NETMASK" + return 1 +} + +function question_prefix_netmask() { + echo -n $"IPv4 netmask or CIDR prefix" +} + +function hint_netmask_v4() { + # default based on class a/b/c address + local a b c d + IFS=. + read a b c d <<< "$IPADDR" + unset IFS + local ip=$(( ( a << 24 ) + ( b << 16 ) + ( c << 8 ) + ( d ) )) + # <<EOF convince syntax highlighter that above shifts are no here documents + if [ $(( ip & 0x80000000 )) -eq $(( 0x00000000 )) ]; then + # class a + echo "255.0.0.0" + elif [ $(( ip & 0xC0000000 )) -eq $(( 0x80000000 )) ]; then + # class b + echo "255.255.0.0" + elif [ $(( ip & 0xE0000000 )) -eq $(( 0xC0000000 )) ]; then + # class c + echo "255.255.255.0" + else + # some other class that should not be used as host address + return 1 + fi + return 0 +} + +function question_choices_netmask() { + echo -n $" (e.g. 255.255.255.0 or 1..32 or ? for help)" + local default=$(hint_netmask_v4) + if [ -n "$default" ]; then + echo $". Default is $default:" + else + echo $":" + echo $"The IP address you entered previously should probably not be used for a host." + fi +} + +function helptext_netmask() { + echo $" Help text for IPv4 netmask or CIDR prefix:" + echo $" Enter a valid IPv4 netmask or CIDR prefix (e.g. 255.255.255.0 or 1..32)" + local default=$(hint_netmask_v4) + if [ -n "$default" ]; then + echo $" Default is $default" + else + echo $"The IP address you entered previously should probably not be used for a host." + fi +} + +function exception_netmask() { + if [ -z "$NETMASK" ]; then + NETMASK=$(hint_netmask_v4) + fi +} + +function do_netmask() { + ask NETMASK \ + question_prefix_netmask question_choices_netmask \ + -h helptext_netmask \ + -s syntax_check_netmask_v4 -e exception_netmask + # no handling/configuring of NETMASK yet, since more parameters needed +} + +### NETWORK + +function do_network() { + echo + echo $"The NETWORK parameter isn't used anymore and will be ignored." + echo $" It is sufficient to specify IPADDR and NETMASK." + echo +} + +### BROADCAST + +function do_broadcast() { + echo + echo $"The BROADCAST parameter isn't used anymore and will be ignored." + echo $" It is sufficient to specify IPADDR and NETMASK." + echo +} + +### NETMASK (IPv6) + +function syntax_check_prefix_v6() { + if [[ "$NETMASK" =~ ^[[:digit:]]+$ ]]; then + if [ "$NETMASK" -ge 1 -a "$NETMASK" -le 128 ]; then + return 0 + fi + fi + echo $"Incorrect value for network prefix [1..128] (NETMASK): $NETMASK" + return 1 +} + +function question_prefix_netmask_v6() { + echo -n $"CIDR prefix for the IPv6 address" +} + +function question_choices_netmask_v6() { + echo $" (1..128):" +} + +function do_netmask_v6() { + ask NETMASK \ + question_prefix_netmask_v6 question_choices_netmask_v6 \ + -s syntax_check_prefix_v6 + # no handling/configuring of NETMASK yet, since more parameters needed +} + +### GATEWAY (IPv4) + +function configure_ipv4_gateway() { + # FIXME: + # - Strictly speaking we should first check reachability of gateway + # and then configure the gateway route. + # This would require a new intermediate workflow_item step + # so that the user might continue despite unreachable gateway. + # done: Only adding default route might add multiple undesired default + # routes on redoing the parameter item, so delete default route + # before adding a new one. + ip -4 route del default dev $DEVICE >& /dev/null + [ -z "$GATEWAY" ] && return 0 + if ! tv route add default gw $GATEWAY dev $DEVICE; then + echo $"Could net set default route on device $DEVICE via gateway $GATEWAY" + return 1 + fi + # BH FIXME: Workaround for manual MACADDR, need ping to update arp table + echo $"Trying to reach gateway $GATEWAY..." + if [ "$NETTYPE" = "ctc" ]; then + # (virtual) CTC(/A) seems to need some time to get functional + local i=1 + while : ; do + ping -c 1 -w 5 $GATEWAY >& /dev/null && break + i=$((i+1)) + if [ "$i" -gt 3 ]; then + echo $"Could not reach gateway $GATEWAY within timeout" + return 1 + fi + done + else + if ! ping -c 1 -w 5 $GATEWAY >& /dev/null; then + echo $"Could not reach your default gateway $GATEWAY" + return 1 + fi + fi + return 0 +} + +function hint_ipv4_gateway() { + # - provide default suggestion based on network, + # for a class C network this would be either .1 or .254 at the end + local a b c d + IFS=. + read a b c d <<< "$NETWORK" + unset IFS + local ip=$(( ( a << 24 ) + ( b << 16 ) + ( c << 8 ) + ( d ) )) + # <<EOF convince syntax highlighter that above shifts are no here documents + local lo=$(( ip | 1 )) + local lo_a=$(( (lo & 0xFF000000) >> 24 )) + local lo_b=$(( (lo & 0x00FF0000) >> 16 )) + local lo_c=$(( (lo & 0x0000FF00) >> 8 )) + local lo_d=$(( (lo & 0x000000FF) )) + local hi=$(( ip | ( (2**(32 - PREFIX)) - 1 ) )) + local hi_a=$(( (hi & 0xFF000000) >> 24 )) + local hi_b=$(( (hi & 0x00FF0000) >> 16 )) + local hi_c=$(( (hi & 0x0000FF00) >> 8 )) + local hi_d=$(( (hi & 0x000000FE) )) + echo $" Depending on your network design patterns, the default gateway" + echo $" might be $lo_a.$lo_b.$lo_c.$lo_d or $hi_a.$hi_b.$hi_c.$hi_d" +} + +function question_prefix_gateway() { + echo -n $"IPv4 address of your default gateway" +} + +function question_choices_gateway() { + echo $" or ? for help:" +} + +function helptext_gateway() { + echo $" Help text for IPv4 default gateway:" + echo $" For HiperSockets with internal traffic only you may want to leave this empty" + echo $" and choose continue afterwards to go on without gateway." + hint_ipv4_gateway +} + +function finish_gateway() { + if ! checkipv4 $GATEWAY; then + # above checkipv4 is silent, so make up for syntax error + echo $"Incorrect format for IPv4 address of gateway (GATEWAY): $GATEWAY" + workflow_item_menu + fi + if configure_ipv4_gateway; then + break + else + workflow_item_menu && break + fi +} + +# FIXME: allow empty/no gateway? + +function do_gateway() { + ask GATEWAY \ + question_prefix_gateway question_choices_gateway \ + -h helptext_gateway -f finish_gateway +} + +### GATEWAY (IPv6) + +function configure_ipv6_gateway() { + # FIXME: + # - Strictly speaking we should first check reachability of gateway + # and then configure the gateway route. + # This would require a new intermediate workflow_item step + # so that the user might continue despite unreachable gateway. + # done: Only adding default route might add multiple undesired default + # routes on redoing the parameter item, so delete default route + # before adding a new one. + ip -6 route del default dev $DEVICE >& /dev/null + [ -z "$GATEWAY" ] && return 0 + # IPv6 http://www.ibiblio.org/pub/Linux/docs/HOWTO/other-formats/html_single/Linux+IPv6-HOWTO.html#AEN1147 + # ip -6 route add ::/0 dev $DEVICE via $GATEWAY + # (Could also be learned by autoconfiguration on the link: + # after IP address setup and device up, + # see if default route has been learned + # ip -6 route show | grep ^default + # However, we currently use manual IPv6 configuration only.) + if ! debug ip -6 route add ::/0 dev $DEVICE via $GATEWAY; then + echo $"Could net set default route on device $DEVICE" + echo $" via gateway $GATEWAY" + return 1 + fi + # BH FIXME: Workaround for manual MACADDR, need ping to update arp table + echo $"Trying to reach gateway $GATEWAY..." + if ! ping6 -c 1 $GATEWAY >& /dev/null; then + echo $"Could not reach your default gateway $GATEWAY" + return 1 + fi + return 0 +} + +function question_prefix_gateway_v6() { + echo -n $"IPv6 address of your default gateway" +} + +function question_choices_gateway_v6() { + echo $":" +} + +function helptext_gateway_v6() { + echo $" Help text for IPv6 default gateway:" + echo $" For HiperSockets with internal traffic only you may want to leave this empty" + echo $" and choose continue afterwards to go on without gateway." +} + +function finish_gateway_v6() { + if ! checkipv6 $GATEWAY; then + # above checkipv6 is silent, so make up for syntax error + echo $"Incorrect format for IPv6 address of gateway (GATEWAY): $GATEWAY" + workflow_item_menu + fi + if configure_ipv6_gateway; then + break + else + workflow_item_menu && break + fi +} + +# FIXME: allow empty/no gateway? + +function do_gateway_v6() { + ask GATEWAY \ + question_prefix_gateway_v6 question_choices_gateway_v6 \ + -h helptext_gateway_v6 -f finish_gateway_v6 +} + +### GATEWAY (IPv4, point-to-point) + +function configure_ipv4_ptp() { + # device needs to be online + if debug ifconfig $DEVICE $IPADDR $MMTU pointopoint $GATEWAY; then + configure_ipv4_gateway + return $? + fi + echo $"Could not set IPv4 address $IPADDR for device $DEVICE" + echo $" to peer $GATEWAY" + [ -n "$MMTU" ] && echo $" and maximum transfer unit: $MMTU" + return 1 +} + +function question_prefix_ptp_gateway() { + echo -n $"IPv4 address of your point-to-point partner" +} + +function question_choices_ptp_gateway() { + echo $" or ? for help:" + # no hinting possible here +} + +function helptext_ptp_gateway() { + echo $" Help text for point-to-point partner:" + echo $" IPv4 address of your CTC or ESCON point-to-point partner." +} + +function finish_ptp_gateway() { + if checkipv4 $GATEWAY; then + if [ "$GATEWAY" = "$IPADDR" ]; then + echo $"IPv4 address of partner should probably be different from the guest's address" + workflow_item_menu && break + else + break + fi + else + # above checkipv4 is silent, so make up for syntax error + echo $"Incorrect format for IPv4 address of partner (GATEWAY): $GATEWAY" + workflow_item_menu && break + fi + # too early to actually configure gateway +} + +function do_ptp_gateway() { + ask GATEWAY \ + question_prefix_ptp_gateway question_choices_ptp_gateway \ + -h helptext_ptp_gateway -f finish_ptp_gateway +} + +### DNS + +function syntax_check_dns() { + if [ -z "$DNS" ]; then + echo $"You might encounter problems without a nameserver, especially with FTP installs" + return 1 + fi + local dnsitem + local allgood="yes" + if [ "$ipv6" ]; then + while read dnsitem; do + if ! checkipv6 $dnsitem; then + echo $"Not a valid IPv6 address for DNS server: $dnsitem" + allgood="no" + fi + done < <(echo $DNS | sed 's/,/\n/g') + else + while read dnsitem; do + if ! checkipv4 $dnsitem; then + echo $"Not a valid IPv4 address for DNS server: $dnsitem" + allgood="no" + fi + done < <(echo $DNS | sed 's/:/\n/g') + fi + if [ "$allgood" = "yes" ]; then + return 0 + else + return 1 + fi +} + +function handle_dns() { + # - foreach DNS try if server is reachable by one ping + [ -z "$DNS" ] && return 0 + local dnsitem + local allgood="yes" + echo $"Trying to reach DNS servers..." + if [ "$ipv6" ]; then + while read dnsitem; do + if ! ping6 -c 1 $dnsitem >& /dev/null; then + echo $"Could not ping DNS server (might still serve DNS requests): $dnsitem" + allgood="no" + # this should not be a hard failure since some network + # environments may prevent pings to DNS servers + # => prevent workflow_item_menu in kickstart mode + fi + done < <(echo $DNS | sed 's/,/\n/g') + else + while read dnsitem; do + # Some network environment may prevent a DNS server from being + # reachable by ping, so it would make sense to use nslookup. + # However, nslookup fails with "Resolver Error 0 (no error)" + # at this stage of the setup progress => not useful + if ! ping -c 1 -w 5 $dnsitem >& /dev/null; then + echo $"Could not ping DNS server: $dnsitem" +# if nslookup $dnsitem $dnsitem >& /dev/null; then +# echo $" but could resolve DNS server with itself: $dnsitem" +# else +# echo $"Could not resolve DNS server with itself: $dnsitem" +# allgood="no" +# fi +# elif ! nslookup $dnsitem $dnsitem >& /dev/null; then +# echo $"Could not resolve DNS server with itself: $dnsitem" + allgood="no" + fi + done < <(echo $DNS | sed 's/:/\n/g') + fi + if [ "$allgood" = "yes" ]; then + return 0 + else + return 1 + fi +} + +function question_prefix_dns() { + if [ "$ipv6" ]; then + echo -n $"IPv6 addresses of DNS servers" + else + echo -n $"IPv4 addresses of DNS servers" + fi +} + +function question_choices_dns() { + if [ "$ipv6" ]; then + echo $" (separated by commas ',' or ? for help):" + else + echo $" (separated by colons ':' or ? for help):" + fi +} + +function helptext_dns() { + echo $" Help text for DNS servers:" + if [ "$ipv6" ]; then + echo $" Enter IPv6 addresses of DNS servers separated by commas ','" + else + echo $" Enter IPv4 addresses of DNS servers separated by colons ':'" + fi + echo $" Default are no DNS servers at all." + echo $" However, you might encounter problems without a nameserver," + echo $" especially with FTP installs." + if [ "$ipv6" ]; then + echo $" An example with 2 servers would be: 2001:0DB8::42,2001:0DB8::BE:AF" + else + echo $" An example with 2 servers would be: 10.0.0.250:10.1.1.1" + fi +} + +function do_dns() { + ask DNS \ + question_prefix_dns question_choices_dns \ + -h helptext_dns -s syntax_check_dns -c handle_dns +} + +### SEARCHDNS + +function syntax_check_searchdns() { + [ -z "$SEARCHDNS" ] && return 0 + local dnsitem + local allgood="yes" + while read dnsitem; do + syntax_check_domainname "$dnsitem" $"Not a valid DNS search domain: $dnsitem" || allgood="no" + done < <(echo $SEARCHDNS | sed 's/:/\n/g') + if [ "$allgood" = "yes" ]; then + return 0 + else + return 1 + fi +} + +function question_prefix_searchdns() { + echo -n $"DNS search domains" +} + +function question_choices_searchdns() { + echo $" (separated by colons ':' or ? for help):" +} + +function helptext_searchdns() { + echo $" Help text for DNS search domains:" + echo $" Enter search domains according to hostname syntax separated by colons." + echo $" Default are no DNS search domains at all." + echo $" An example would be: subdomain.domain.com:domain.com" +} + +function do_searchdns() { + ask SEARCHDNS \ + question_prefix_searchdns question_choices_searchdns \ + -h helptext_searchdns -s syntax_check_searchdns +} + +### DASD + +function parse_dasd() { + local handle + [ "$1" = "-h" ] && handle=yes || unset handle + local dasditem + local allgood="yes" + local cio_wc=$(wc -c /proc/cio_ignore) + read cio_wc_bytes cio_wc_filename cio_wc_foo <<< "$cio_wc" + if [ "$handle" = "yes" -a "$cio_wc_bytes" != "0" ]; then + echo $"Trying to clear specified DASDs from device blacklist..." + mkdir -p /etc/modprobe.d + echo "options dasd_mod dasd=$DASD" > /etc/modprobe.d/dasd_mod.conf + if ! dasd_cio_free; then + echo $"Not all specified DASDs could be detected within timeout." + allgood="no" + fi + fi + while read dasditem; do + unset range features range lo hi rangegood \ + attrs devno lodevno hidevno devbusid sys + case $dasditem in + autodetect) + [ -z "$handle" ] && continue + cio_wc=$(wc -c /proc/cio_ignore) + read cio_wc_bytes cio_wc_filename cio_wc_foo <<< "$cio_wc" + # above we only freed the devices specified in $DASD, + # so there might still be other DASDs in the blacklist + if [ "$cio_wc_bytes" != "0" ]; then + echo $"Note: There is a device blacklist active! Only activating visible DASDs." + fi + local sys + while read sys; do + if ! sysecho $sys/online 1; then + echo $"Could not set DASD ${sys##*/} online" + fi + done < <(find /sys/bus/ccw/drivers/dasd-eckd/ -name "*.?.????" 2>/dev/null;\ + find /sys/bus/ccw/drivers/dasd-fba/ -name "*.?.????" 2>/dev/null) + ;; + probeonly|nopav|nofcx) + if [ -z "$handle" ]; then + echo $"DASD option $dasditem not supported by installer" + fi + ;; + "") continue ;; # empty range + *) local range features rangegood="yes" + IFS='(' + read range features <<< "$dasditem" + unset IFS + # parse: dev OR dev'-'dev + local lo=${range%%-*} + [[ "$lo" =~ (^[[:xdigit:]]+\.[0-3]\.[[:xdigit:]]{4}$)|(^[[:xdigit:]]{3,4}$) ]] + case $? in + 0) # string matched the pattern + lo=$(canonicalize_devno $lo) ;; + 1) # string did not match the pattern + rangegood="no" + if [ -z "$handle" ]; then + echo $"Incorrect format for lower bound of DASD range $range: $lo" + allgood="no" + fi + ;; + 2) echo "l.$LINENO: syntax error in regex of match operator =~, code needs to be fixed" 1>&2 ;; + *) echo "l.$LINENO: unexpected return code of regex match operator =~, code needs to be fixed" 1>&2 ;; + esac + if [ "${range//*-*/}" = "" ]; then + local hi=${range##*-} + [[ "$hi" =~ (^[[:xdigit:]]+\.[0-3]\.[[:xdigit:]]{4}$)|(^[[:xdigit:]]{3,4}$) ]] + case $? in + 0) # string matched the pattern + hi=$(canonicalize_devno $hi) + if [ "${lo%.*}" != "${hi%.*}" ]; then + echo $"Prefixes of DASD range $range do not match: ${lo%.*} != ${hi%.*}" + rangegood="no" + allgood="no" + fi + ;; + 1) # string did not match the pattern + rangegood="no" + if [ -z "$handle" ]; then + echo $"Incorrect format for upper bound of DASD range $range: $hi" + allgood="no" + fi + ;; + 2) echo "l.$LINENO: syntax error in regex of match operator =~, code needs to be fixed" 1>&2 ;; + *) echo "l.$LINENO: unexpected return code of regex match operator =~, code needs to be fixed" 1>&2 ;; + esac + fi + if [ "${features//*)/}" != "" ]; then + if [ -z "$handle" ]; then + echo $"Missing closing parenthesis at features of DASD range $range: ($features" + allgood="no" + fi + fi + local attrs="" + if [ -n "$features" ]; then + features="${features%)}" + while read feature; do + case $feature in + ro) attrs=$attrs" readonly" ;; + diag) attrs=$attrs" use_diag" ;; + erplog|failfast) attrs=$attrs" "$feature ;; + *) if [ -z "$handle" ]; then + echo $"Unknown DASD feature for device range $range: $feature" + allgood="no" + fi + ;; + esac + done < <(echo $features | sed 's/:/\n/g') + fi + [ "$rangegood" = "yes" ] || continue + [ "$handle" = "yes" ] || continue + # now apply $attrs and set DASDs $lo to $hi online + [ -z "$hi" ] && hi=$lo + local devno lodevno=$((0x${lo##*.})) hidevno=$((0x${hi##*.})) + for ((devno=$lodevno; $devno <= $hidevno; ++devno)); do + local devbusid=$(printf "%s.%04x" ${lo%.*} $devno) + local sys="/sys/bus/ccw/devices/"$devbusid + for attr in $attrs; do + if [ "$attr" = "use_diag" ]; then + # diag discipline cannot be auto-loaded + modprobe dasd_diag_mod + fi + if [ ! -f $sys/$attr ]; then + echo $"DASD $devbusid does not provide attribute $attr" + continue + fi + if ! sysecho $sys/$attr 1; then + echo $"Could not set attribute $attr for DASD $devbusid" + fi + done + if [ ! -f $sys/online ]; then + echo $"DASD $devbusid not found" + continue + fi + if ! sysecho $sys/online 1; then + echo $"Could not set DASD $devbusid online" + fi + done + ;; + esac + done < <(echo $DASD | sed 's/,/\n/g') + if [ "$handle" = "yes" ]; then + udevadm settle + dasd_settle_all || return 1 + echo $"Activated DASDs:" + cat /proc/dasd/devices | sed -e 's/ at ([^)]*) is//' -e 's/ at/,/' + fi + if [ "$allgood" = "yes" ]; then + return 0 + else + return 1 + fi +} + +function syntax_check_dasd() { + parse_dasd + return $? +} + +function handle_dasd() { + parse_dasd -h + return $? +} + +function question_prefix_dasd() { + echo -n $"DASD range" +} + +function question_choices_dasd() { + echo $" (e.g. 200-203,205 or ? for help). Default is autoprobing:" +} + +function helptext_dasd() { + echo $" Help text for DASD range:" + echo $" Comma separated list of ranges of device bus IDs." + echo $" Default is autoprobing (not recommended)." + echo $" Examples would be: 200-203 or 200,201,202,203 or 0.0.0200-0.0.0203,0.0.0205" +} + +function exception_dasd() { + [ -z "$DASD" ] && DASD="autodetect" +} + +function do_dasd() { + ask DASD \ + question_prefix_dasd question_choices_dasd \ + -h helptext_dasd -e exception_dasd -s syntax_check_dasd -c handle_dasd +} + +### FCP + +function syntax_check_fcp() { + local allgood="yes" + local i + for i in ${!FCP_*}; do + local -a fcp + local devno wwpn lun + read -a fcp <<< "${!i}" + case ${#fcp[@]} in + 3) + devno=${fcp[0]} + wwpn=${fcp[1]} + lun=${fcp[2]} + ;; + 5) + devno=${fcp[0]} + wwpn=${fcp[2]} + lun=${fcp[4]} + echo $"Deprecated number of FCP arguments (5 instead of 3): " + echo $" $i=\"${!i}\"" + echo $" should instead be: " + echo $" $i=\"$devno $wwpn $lun\"" + ;; + *) + echo $"Unsupported number of FCP arguments (${#fcp[@]} instead of 3) in:" + echo $" $i=\"${!i}\"" + allgood="no" + continue + ;; + esac + [[ "$devno" =~ (^[[:xdigit:]]+\.[0-3]\.[[:xdigit:]]{4}$)|(^[[:xdigit:]]{3,4}$) ]] + case $? in + 0) ;; # string matched the pattern + 1) # string did not match the pattern + echo $"Incorrect format for FCP device $devno in:" + echo $" $i=\"${!i}\"" + allgood="no" + ;; + 2) + echo "l.$LINENO: syntax error in regex of match operator =~, code needs to be fixed" 1>&2 + ;; + *) + echo "l.$LINENO: unexpected return code of regex match operator =~, code needs to be fixed" 1>&2 + ;; + esac + # zfcp.py:class ZFCPDevice would also accept WWPN without leading 0x + [[ "$wwpn" =~ ^0x[[:xdigit:]]{16}$ ]] + case $? in + 0) ;; # string matched the pattern + 1) # string did not match the pattern + echo $"Incorrect format for FCP WWPN $wwpn in:" + echo $" $i=\"${!i}\"" + allgood="no" + ;; + 2) echo "l.$LINENO: syntax error in regex of match operator =~, code needs to be fixed" 1>&2 ;; + *) echo "l.$LINENO: unexpected return code of regex match operator =~, code needs to be fixed" 1>&2 ;; + esac + # zfcp.py:class ZFCPDevice would also accept LUN without leading 0x + # zfcp.py:class ZFCPDevice would also accept 16 bit LUN and pads with 0 + [[ "$lun" =~ ^0x[[:xdigit:]]{8}0{8}$ ]] + case $? in + 0) ;; # string matched the pattern + 1) # string did not match the pattern + echo $"Incorrect format for FCP LUN $lun in:" + echo $" $i=\"${!i}\"" + allgood="no" + ;; + 2) + echo "l.$LINENO: syntax error in regex of match operator =~, code needs to be fixed" 1>&2 + ;; + *) + echo "l.$LINENO: unexpected return code of regex match operator =~, code needs to be fixed" 1>&2 + ;; + esac + done + if [ "$allgood" = "yes" ]; then + return 0 + else + return 1 + fi +} + +### + +function show_parms() { + # The only issue with this stateless approach to showing parameters based + # on their content being non-empty is, that parameters with defaults + # such as LAYER2, (PORTNAME,) CTCPROT, PORTNO (,MACADDR) won't be shown + # if the user just hit enter, so the parm file would be "incomplete". + # However this is not easy to fix in here, since it would require the + # inter-parameter dependenies coded below in the main part, e.g. an + # empty LAYER2 should only be printed with default value if $NETTYPE=qeth. + # For the time being, at least the parameters LAYER2, PORTNAME, and CTCPROT + # only get asked on being empty if not running in kickstart mode. + cat << EOF +NETTYPE=$NETTYPE +IPADDR=$IPADDR +NETMASK=$NETMASK +GATEWAY=$GATEWAY +HOSTNAME=$HOSTNAME +EOF + [ "$SUBCHANNELS" ] && echo "SUBCHANNELS=$SUBCHANNELS" + [ "$LAYER2" ] && echo "LAYER2=$LAYER2" + [ "$VSWITCH" ] && echo "VSWITCH=$VSWITCH" + [ "$MACADDR" ] && echo "MACADDR=$MACADDR" + [ "$PORTNAME" ] && echo "PORTNAME=$PORTNAME" + [ "$PORTNO" ] && echo "PORTNO=$PORTNO" + [ "$PEERID" ] && echo "PEERID=$PEERID" + [ "$CTCPROT" ] && echo "CTCPROT=$CTCPROT" + if [ -n "$mmtu_was_set" ]; then + echo "MMTU=\"$MMTU\"" + elif [ -n "$mtu_was_set" ]; then + echo "MTU=$MTU" + fi + [ "$DNS" ] && echo "DNS=$DNS" + [ "$SEARCHDNS" ] && echo "SEARCHDNS=$SEARCHDNS" + [ "$DASD" ] && echo "DASD=$DASD" +} + +function final_check() { + # final check && break + if [ -z "$interaction_happened" ]; then + # if parm file was good enough just continue without interaction + break + return 0 + fi + while : ; do + # optionally consider "continue" as default + # but then again the user may inadvertently continue + echo + echo $"c) continue, p) parm file/configuration, n) network state, r) restart, s) shell" + local answer + read answer + case $answer in + c) return 0 ;; + p) echo + show_parms ;; + n) # show interfaces and routing table + ifconfig -a + if [ "$ipv6" ]; then + #route -n -A inet6 + # the following produces more compact output for 80 columns + ip -6 route show | grep -v "^unreachable " + else + route -n + fi + ;; + d) # show active DASDs with some useful details + echo $"Activated DASDs:" + cat /proc/dasd/devices|sed -e 's/ at ([^)]*) is//' -e 's/ at/,/' + ;; + r) break ;; + s) echo $"Enter 'exit' at the shell prompt to get back to the installation dialog." + /bin/bash + ;; + esac + done + return 1 +} + +### MAIN ### + +init_main +udev_setup + +# Parse configuration +if [ -n "$CMSDASD" -a -n "$CMSCONFFILE" ]; then + readcmsfile $CMSDASD $CMSCONFFILE + source /tmp/$CMSCONFFILE #2>/dev/null +fi + +if [ -r /sys/firmware/ipl/ipl_type ]; then + #local ipl_type + read ipl_type < /sys/firmware/ipl/ipl_type + if [ "$ipl_type" = "fcp" ]; then + while : ; do + echo $"Your IPL device is set to FCP." + echo $"Would you like to perform a CD-ROM/DVD-ROM installation? (y/n)" + #local do_cd_install + read do_cd_install + case $do_cd_install in + y|Y|[Yy][Ee][Ss]) + # precondition: zfcp driver incl. dependencies loaded + #local CD_DEVICE WWPN LUN + read CD_DEVICE < /sys/firmware/ipl/device + read WWPN < /sys/firmware/ipl/wwpn + read LUN < /sys/firmware/ipl/lun + if sysecho /proc/cio_ignore "free $CD_DEVICE"; then + udevadm settle + # even though device might now be online, some of its + # sysfs attributes might not yet be available + sleep 1 + else + echo $"Device $CD_DEVICE could not be cleared from device blacklist" + fi + sysecho /sys/bus/ccw/drivers/zfcp/$CD_DEVICE/online 1 \ + || echo $"Could not set FCP device $CD_DEVICE online" + udevadm settle + # port (WWPN) appears automatically + sysecho /sys/bus/ccw/drivers/zfcp/$CD_DEVICE/$WWPN/unit_add $LUN \ + || echo $"Could not add LUN $LUN at WWPN $WWPN on FCP device $CD_DEVICE" + udevadm settle + break + ;; + n|N|[Nn][Oo]) + break + ;; + *) + echo + echo $"*** INVALID ANSWER: $do_cd_install" + echo + unset do_cd_install + ;; + esac + done + fi +fi + +# Perform a network installation + +[ -n "$MTU" ] && mtu_was_set=$MTU +[ -n "$MMTU" ] && mmtu_was_set=$MMTU +[ -n "$VSWITCH" ] && vswitch_was_set=$VSWITCH + +[ -n "$CHANDEV" ] && do_chandev +[ -n "$NETWORK" ] && do_network +[ -n "$BROADCAST" ] && do_broadcast + +# [ -z "${cardtype//OSD_*/}" ] can be used to check for real OSA + +# Check for missing parameters, prompt for them if necessary +while : ; do + + # do not show list of possible network device configurations, if: + # - running unattended install with kickstart + # - relevant parameters have already been specified in parm file + # (a possible optimization would be matching those parms to table entry) + # - dialog has not been restarted + [ -n "$reenter" \ + -o -z "$RUNKS" -a \( -z "$NETTYPE" -o -z "$SUBCHANNELS" \) ] && \ + dialog_network_table + if isVM; then + echo $"* NOTE: To enter default or empty values press enter twice. *" + fi + do_nettype + + # precondition: driver (qeth/lcs/ctcm) loaded incl. dependencies + do_subchannels + if [ "$NETTYPE" = "qeth" ]; then + [ -z "$reenter" -a -n "$RUNKS" -a -z "$PORTNAME" ] || \ + [ -n "${cardtype//OSD_*/}" ] || do_portname + # See also https://bugzilla.redhat.com/show_bug.cgi?id=439461 + # + # If running in kickstart mode (unattended), we assume no + # interaction and the user won't get asked for PORTNO. + # Otherwise the user will be asked for PORTNO. + # If the user specified PORTNO in parm/conf file, PORTNO gets + # respected (or the user will be asked if it was wrong). + if [ -f /sys/devices/qeth/$SCH_R_DEVBUSID/portno ]; then + # driver support exists since RHEL5.2 + [ -z "$reenter" -a -n "$RUNKS" -a -z "$PORTNO" ] || \ + [ -n "${cardtype//OSD_*/}" ] || do_portno + fi + do_layer2 + # set device online to know the device name + # and to know if it's OSD/HiperSockets/GuestLAN BUT do not + # try to ifconfig the device up since that requires + # setting the mac address before (if applicable). + set_device_online || workflow_item_menu noredo + # MAC address handling is not part of + # https://bugzilla.redhat.com/show_bug.cgi?id=233376 + # Instead the additions come from + # https://bugzilla.redhat.com/show_bug.cgi?id=248049 + # The new parms VSWITCH and MACADDR are described in + # the RHEL 5.1 release notes. + if isLayer2; then + if [ -z "$VSWITCH" -o "$VSWITCH" == 0 ]; then + do_macaddr + fi + fi + elif [ "$NETTYPE" = "ctc" ]; then + [ -z "$reenter" -a -n "$RUNKS" -a -z "$CTCPROT" ] || do_ctcprot + set_device_online || workflow_item_menu noredo + elif [ "$NETTYPE" = "lcs" ]; then + [ -n "$RUNKS" -a -z "$PORTNAME" ] && PORTNAME=0 + do_lcs_portno + set_device_online || workflow_item_menu noredo + fi + + # device needs to be up before configuring with ifconfig/ip in + # configure_ipv6_address/configure_ipv4_address/configure_ipv4_address + set_device_up || workflow_item_menu noredo + + [ "$HOSTNAME" = "(none)" ] && unset HOSTNAME + do_hostname + + # Note: The workflow_item_menu does a rollback_config on restart + # dialog, i.e. hardware config has been reset and it is impossible to + # only restart halfway at IPADDR. + do_ipaddr + if [ "$ipv6" ]; then + # this branch is all IPv6 and at the same time also NETTYPE==qeth + do_netmask_v6 + handle_mtu + configure_ipv6_address || workflow_item_menu noredo + do_gateway_v6 + else + # Consider IPv4 as default, even for unknown IP versions + # due to invalid input for IPADDR ignored by the user previously + # (neither ipv6 nor ipv4 is set). + # Otherwise we would skip things like NETMASK or GATEWAY + # and jump forward to DNS which is probably not what we want. + if [ "$NETTYPE" = "qeth" ] || [ "$NETTYPE" = "lcs" ]; then + do_netmask + handle_mtu + configure_ipv4_address || workflow_item_menu noredo + do_gateway + else # ctc0 + if [ -z "$NETMASK" ]; then + # If the user did not supply netmask, we add the right one. + # Netmask MUST be present, + # or pumpSetupInterface() blows routes. + NETMASK="255.255.255.255" + fi + # don't ask for MTU, but use it if set in the parm file + # don't overwrite MMTU if it has been set for CTC + [ "$NETTYPE" = "ctc" -a -z "$MTU" -a -z "$MMTU" ] && \ + MMTU="mtu 1500" + do_ptp_gateway + configure_ipv4_ptp || workflow_item_menu noredo + fi + fi + + modprobe_alias + do_dns + [ -n "$DNS" ] && do_searchdns + + do_dasd + + echo $"Initial configuration completed." + final_check && break + rollback_config + reenter="yes" + +done # outer dialog loop + +if [ -z "$testing" ]; then + + # convert to space-separated lists + if [ -n "$SEARCHDNS" ]; then + SEARCHDNS=$(echo $SEARCHDNS |sed -e 's/:/ /g') + for i in "$SEARCHDNS"; do echo "search $i"; done >> /etc/resolv.conf + fi + if [ -n "$DNS" ]; then + if [ "$ipv6" ]; then + RESOLVDNS=$(echo $DNS |sed -e 's/,/ /g') + else + RESOLVDNS=$(echo $DNS |sed -e 's/:/ /g') + fi + for i in $RESOLVDNS; do echo "nameserver $i"; done >> /etc/resolv.conf + fi + + # make sure we have an /etc/hosts file (originally required for telnetd) + if [ ! -z "$HOSTNAME" -a ! -z "$IPADDR" ]; then + echo -e "$IPADDR\t$HOSTNAME $(echo $HOSTNAME | cut -d '.' -f 1)" >> /etc/hosts + fi + +fi # testing + +# syntax check to give user early feedback on parameters provided in parm file +# (he probably won't notice the logs written by anaconda later on) +syntax_check_fcp +# currently we ignore failed syntax checks since FCP parms are non-interactive +for i in ${!FCP_*}; do + echo "${!i}" >> /tmp/fcpconfig +done +# cio_ignore handling for FCP should happen when the content of /tmp/fcpconfig +# will actually be processed which is in anaconda's zfcp.py ZFCP::readConfig() + +# TODO/FIXME: also need to pass IPv6 decision to loader/anaconda +# [ "$ipv6" ] && echo "IPV6=yes" + +# transfer options into install environment +cat > /tmp/install.cfg << EOF +LANG="$LANG" +S390ARCH="$S390ARCH" +TEXTDOMAIN="$TEXTDOMAIN" +TEXTDOMAINDIR="$TEXTDOMAINDIR" +PORTNAME="$PORTNAME" +HOSTNAME="$HOSTNAME" +DEVICE="$DEVICE" +NETTYPE="$NETTYPE" +IPADDR="$IPADDR" +GATEWAY="$GATEWAY" +MTU="$MTU" +NETWORK="$NETWORK" +NETMASK="$NETMASK" +BROADCAST="$BROADCAST" +SEARCHDNS="$SEARCHDNS" +PEERID="$PEERID" +SUBCHANNELS="$SUBCHANNELS" +ONBOOT="yes" +CTCPROT="$CTCPROT" +EOF +if [ "$ipv6" ]; then + DNS1=$(echo $DNS | cut -d ',' -f 1) + echo DNS=\"$DNS1\" >> /tmp/install.cfg + echo DNS1=\"$DNS1\" >> /tmp/install.cfg + echo DNS2=\"$(echo $DNS | cut -d ',' -f 2)\" >> /tmp/install.cfg +else + DNS1=$(echo $DNS | cut -d ':' -f 1) + echo DNS=\"$DNS1\" >> /tmp/install.cfg + echo DNS1=\"$DNS1\" >> /tmp/install.cfg + echo DNS2=\"$(echo $DNS | cut -d ':' -f 2)\" >> /tmp/install.cfg +fi +cat >> /tmp/install.cfg << EOF +export LANG PORTNAME S390ARCH TEXTDOMAIN TEXTDOMAINDIR +export HOSTNAME DEVICE NETTYPE IPADDR GATEWAY MTU +export NETWORK NETMASK BROADCAST DNS DNS1 DNS2 SEARCHDNS +export PEERID ONBOOT SUBCHANNELS CTCPROT +EOF +# immediately read it in again to export these into the shell below +. /tmp/install.cfg +if [ -z "$testing" ]; then + cat /tmp/install.cfg >> /etc/profile +fi # testing + +NETSCRIPTS="/etc/sysconfig/network-scripts" +IFCFGFILE="$NETSCRIPTS/ifcfg-$DEVICE" +if [ ! -d "$NETSCRIPTS" ]; then + mkdir -p $NETSCRIPTS +fi + +# to please NetworkManager on startup in loader before loader reconfigures net +cat > /etc/sysconfig/network << EOF +HOSTNAME=$HOSTNAME +EOF + +cat > $IFCFGFILE << EOF +DEVICE=$DEVICE +ONBOOT=yes +BOOTPROTO=static +GATEWAY=$GATEWAY +BROADCAST=$BROADCAST +MTU=$MTU +SUBCHANNELS=$SUBCHANNELS +EOF +if [ "$ipv6" ]; then + cat >> $IFCFGFILE << EOF +IPV6INIT=yes +IPV6_AUTOCONF=no +IPV6ADDR=$IPADDR/$NETMASK +IPV6_DEFAULTGW=$GATEWAY +EOF + # FIXME: /etc/sysconfig/network:IPV6_DEFAULTGW=$GATEWAY + # /etc/sysconfig/network:NETWORKING_IPV6=yes +else + cat >> $IFCFGFILE << EOF +IPADDR=$IPADDR +NETMASK=$NETMASK +EOF +fi +[ "$DNS1" != "" ] && echo "DNS1=$DNS1" >> $IFCFGFILE +[ "$DNS2" != "" ] && echo "DNS2=$DNS2" >> $IFCFGFILE +# colons in SEARCHDNS already replaced with spaces above for /etc/resolv.conf +[ "$SEARCHDNS" != "" ] && echo "DOMAIN=\"$SEARCHDNS\"" >> $IFCFGFILE +[ "$NETTYPE" != "" ] && echo "NETTYPE=$NETTYPE" >> $IFCFGFILE +[ "$PEERID" != "" ] && echo "PEERID=$PEERID" >> $IFCFGFILE +[ "$PORTNAME" != "" ] && echo "PORTNAME=$PORTNAME" >> $IFCFGFILE +[ "$CTCPROT" != "" ] && echo "CTCPROT=$CTCPROT" >> $IFCFGFILE +[ "$MACADDR" != "" ] && echo "MACADDR=$MACADDR" >> $IFCFGFILE +optstr="" +for option in LAYER2 PORTNO; do + [ -z "${!option}" ] && continue + [ -n "$optstr" ] && optstr=${optstr}" " + optstr=${optstr}$(echo ${option} | tr [[:upper:]] [[:lower:]])"="${!option} +done +# write single quotes since network.py removes double quotes but we need quotes +echo "OPTIONS='$optstr'" >> $IFCFGFILE +unset option +unset optstr + +if [ -z "$testing" ]; then + + # so that the vars get propagated into the sshd shells + mkdir /.ssh + cat >> /.ssh/environment <<EOF +LD_LIBRARY_PATH=$LD_LIBRARY_PATH +PATH=$PATH +HOME=$HOME +PYTHONPATH=$PYTHONPATH +EOF + + cat >> /etc/profile <<EOF +LD_LIBRARY_PATH=$LD_LIBRARY_PATH +PATH=$PATH +HOME=$HOME +PYTHONPATH=$PYTHONPATH +export LD_LIBRARY_PATH PATH HOME PYTHONPATH +EOF + + if [ -n "$DISPLAY" ]; then + echo "export DISPLAY=$DISPLAY" >> /etc/profile + fi + + # I'm tired of typing this out... + echo "loader" >> /.bash_history + + echo -n $$ > /var/run/init.pid + + # shutdown (halt) on SIGUSR1 + trap doshutdown SIGUSR1 + # reboot on SIGUSR2 + trap doreboot SIGUSR2 + + startinetd + + if [ -n "$RUNKS" ]; then + /sbin/loader + fi + + doshutdown + +fi # testing + +# ;;; Local Variables: *** +# ;;; mode: sh *** +# ;;; end: *** diff --git a/loader/loader.c b/loader/loader.c new file mode 100644 index 0000000..b96923b --- /dev/null +++ b/loader/loader.c @@ -0,0 +1,2406 @@ +/* + * loader.c + * + * This is the installer loader. Its job is to somehow load the rest + * of the installer into memory and run it. This may require setting + * up some devices and networking, etc. The main point of this code is + * to stay SMALL! Remember that, live by that, and learn to like it. + * + * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006, 2007 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> + * Matt Wilson <msw@redhat.com> + * Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + */ + +#include <ctype.h> +#include <errno.h> +#include <execinfo.h> +#include <fcntl.h> +#include <newt.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <stdint.h> +#include <dirent.h> +#include <arpa/inet.h> + +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/utsname.h> + +#include <linux/fb.h> +#include <linux/serial.h> +#include <linux/vt.h> + +#include <glib.h> + +#ifdef USE_MTRACE +#include <mcheck.h> +#endif + +#include "copy.h" +#include "getparts.h" +#include "loader.h" +#include "loadermisc.h" /* JKFIXME: functions here should be split out */ +#include "lang.h" +#include "fwloader.h" +#include "kbd.h" +#include "kickstart.h" +#include "windows.h" + +/* module stuff */ +#include "modules.h" +#include "moduleinfo.h" + +#include "driverdisk.h" + +/* hardware stuff */ +#include "hardware.h" + +/* install method stuff */ +#include "method.h" +#include "cdinstall.h" +#include "nfsinstall.h" +#include "hdinstall.h" +#include "urls.h" +#include "urlinstall.h" + +#include "net.h" +#include "telnetd.h" + +#include <selinux/selinux.h> +#include "selinux.h" + +#include "../isys/imount.h" +#include "../isys/isys.h" +#include "../isys/stubs.h" +#include "../isys/lang.h" +#include "../isys/eddsupport.h" +#include "../isys/log.h" + +/* maximum number of extra arguments that can be passed to the second stage */ +#define MAX_EXTRA_ARGS 128 +static char * extraArgs[MAX_EXTRA_ARGS]; +static int hasGraphicalOverride(); + +static int newtRunning = 0; + +/* boot flags -- we need these in a lot of places */ +uint64_t flags = LOADER_FLAGS_SELINUX; + +#ifdef INCLUDE_LOCAL +#include "cdinstall.h" +#include "hdinstall.h" +#endif +#ifdef INCLUDE_NETWORK +#include "nfsinstall.h" +#include "urlinstall.h" +#endif + +int num_link_checks = 5; +int post_link_sleep = 0; + +static pid_t init_pid = 1; +static int init_sig = SIGUSR1; /* default to shutdown=halt */ +static const char *LANG_DEFAULT = "en_US.UTF-8"; + +static struct installMethod installMethods[] = { + { N_("Local CD/DVD"), 0, DEVICE_CDROM, mountCdromImage }, + { N_("Hard drive"), 0, DEVICE_DISK, mountHardDrive }, + { N_("NFS directory"), 1, DEVICE_NETWORK, mountNfsImage }, + { "URL", 1, DEVICE_NETWORK, mountUrlImage }, +}; +static int numMethods = sizeof(installMethods) / sizeof(struct installMethod); + +static int expected_exit = 0; + +static void doExit(int) __attribute__ ((noreturn)); +static void doExit(int result) +{ + expected_exit = 1; + exit(result); +} + +void doSuspend(void) { + newtFinished(); + doExit(1); +} + +void doShell(void) { + pid_t child; + int status; + + newtSuspend(); + child = fork(); + + if (child == 0) { + if (execl("/sbin/bash", "/sbin/bash", "-i", NULL) == -1) { + logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); + _exit(1); + } + } else if (child == -1) { + logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); + newtResume(); + } else { + if (waitpid(child, &status, 0) == -1) { + logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); + } + + newtResume(); + } +} + +void doGdbserver(struct loaderData_s *loaderData) { + int child, fd; + char *pid; + iface_t iface; + + /* If gdbserver is found, go ahead and run it on the loader process now + * before anything bad happens. + */ + if (loaderData->gdbServer && !access("/usr/bin/gdbserver", X_OK)) { + pid_t loaderPid = getpid(); + iface_init_iface_t(&iface); + + if (kickstartNetworkUp(loaderData, &iface)) { + logMessage(ERROR, "can't run gdbserver due to no network"); + return; + } + + checked_asprintf(&pid, "%d", loaderPid); + + if (!(child = fork())) { + logMessage(INFO, "starting gdbserver: %s %s %s %s", + "/usr/bin/gdbserver", "--attach", loaderData->gdbServer, + pid); + + fd = open("/dev/null", O_RDONLY); + close(STDIN_FILENO); + dup2(fd, STDIN_FILENO); + close(fd); + fd = open("/dev/null", O_WRONLY); + close(STDOUT_FILENO); + dup2(fd, STDOUT_FILENO); + close(STDERR_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + + if (execl("/usr/bin/gdbserver", "/usr/bin/gdbserver", "--attach", + loaderData->gdbServer, pid, NULL) == -1) + logMessage(ERROR, "error running gdbserver: %m"); + + _exit(1); + } + } +} + +void startNewt(void) { + if (!newtRunning) { + char *buf; + char *arch = getProductArch(); + checked_asprintf(&buf, _("Welcome to %s for %s"), getProductName(), arch); + + /* + * Because currently initrd.img only has got the default English locale + * support, pretend for newtInit() it is actually the used LANG so Newt + * knows how to compute character widths etc. + */ + char *lang = getenv("LANG"); + if (lang) { + lang = strdup(lang); + } + setenv("LANG", LANG_DEFAULT, 1); + newtInit(); + unsetenv("LANG"); + /* restore the original LANG value */ + if (lang) { + setenv("LANG", lang, 1); + free(lang); + } + + newtCls(); + newtDrawRootText(0, 0, buf); + free(buf); + + newtPushHelpLine(_(" <Tab>/<Alt-Tab> between elements | <Space> selects | <F12> next screen ")); + + newtRunning = 1; + if (!access("/bin/sh", X_OK)) + newtSetSuspendCallback((void *) doShell, NULL); + } +} + +void stopNewt(void) { + if (newtRunning) newtFinished(); + newtRunning = 0; +} + +static gchar *productName = NULL; +static gchar *productPath = NULL; +static gchar *productArch = NULL; + +static void initProductInfo(void) { + gchar *contents = NULL; + gchar **lines = NULL, **stamp = NULL; + GError *fileErr = NULL; + + if (!g_file_get_contents("/.buildstamp", &contents, NULL, &fileErr)) { + logMessage(ERROR, "error reading .buildstamp: %s", fileErr->message); + g_error_free(fileErr); + productName = g_strdup("anaconda"); + productArch = g_strdup("unknown architecture"); + productPath = g_strdup("anaconda"); + return; + } + + /* .buildstamp uses the first 3 lines in this format: + * STAMP.productArch + * productName + * productPath + */ + lines = g_strsplit(contents, "\n", 0); + g_free(contents); + + if ((lines != NULL) && (g_strv_length(lines) >= 3)) { + /* STAMP.productArch */ + stamp = g_strsplit(lines[0], ".", 0); + + if ((stamp != NULL) && (g_strv_length(stamp) == 2)) { + productArch = g_strdup(stamp[1]); + } else { + productArch = g_strdup("unknown architecture"); + } + + if (stamp) { + g_strfreev(stamp); + } + + productName = g_strdup(lines[1]); + productPath = g_strdup(lines[2]); + } else { + productName = g_strdup("anaconda"); + productArch = g_strdup("unknown architecture"); + productPath = g_strdup("anaconda"); + } + + if (lines) { + g_strfreev(lines); + } + + return; +} + +char * getProductName(void) { + if (!productName) { + initProductInfo(); + } + return productName; +} + +char * getProductArch(void) { + if (!productArch) { + initProductInfo(); + } + return productArch; +} + +char * getProductPath(void) { + if (!productPath) { + initProductInfo(); + } + return productPath; +} + +void initializeConsole() { + /* enable UTF-8 console */ + setenv("LANG", LANG_DEFAULT, 1); + printf("\033%%G"); + fflush(stdout); + + isysLoadFont(); + isysSetUnicodeKeymap(); +} + +/* fbcon is buggy and resets our color palette if we allocate a terminal + * after initializing it, so we initialize 9 of them before we need them. + * If it doesn't work, the user gets to suffer through having an ugly palette, + * but things are still usable. */ +static void initializeTtys(void) { + int fd, n; + char dev[] = "/dev/ttyX"; + + for (n = 9; n > 0; n--) { + sprintf(dev, "/dev/tty%d", n); + mknod(dev, 0600 | S_IFCHR, makedev(4, n)); + fd = open(dev, O_RDWR|O_NOCTTY); + if (fd >= 0) { + ioctl(fd, VT_ACTIVATE, n); + if (n == 1) + ioctl(fd, VT_WAITACTIVE, n); + close(fd); + } else + logMessage(ERROR, "failed to initialize %s", dev); + } +} + +static void spawnShell(void) { + pid_t pid; + + if (FL_SERIAL(flags) || FL_NOSHELL(flags)) { + logMessage(INFO, "not spawning a shell"); + return; + } else if (access("/bin/sh", X_OK)) { + logMessage(ERROR, "cannot open shell - /bin/sh doesn't exist"); + return; + } + + if (!(pid = fork())) { + int fd; + + fd = open("/dev/tty2", O_RDWR|O_NOCTTY); + if (fd < 0) { + logMessage(ERROR, "cannot open /dev/tty2 -- no shell will be provided"); + return; + } + + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + + close(fd); + setsid(); + + /* enable UTF-8 console */ + printf("\033%%G"); + fflush(stdout); + isysLoadFont(); + + if (ioctl(0, TIOCSCTTY, NULL)) { + logMessage(ERROR, "could not set new controlling tty"); + } + + signal(SIGINT, SIG_DFL); + signal(SIGTSTP, SIG_DFL); + + if (!access("/tmp/updates/pyrc.py", R_OK|X_OK)) + setenv("PYTHONSTARTUP", "/tmp/updates/pyrc.py", 1); + else if (!access("/usr/share/anaconda/pyrc.py", R_OK|X_OK)) + setenv("PYTHONSTARTUP", "/usr/share/anaconda/pyrc.py", 1); + setenv("LD_LIBRARY_PATH", LIBPATH, 1); + setenv("LANG", "C", 1); + + if (execl("/bin/sh", "-/bin/sh", NULL) == -1) { + logMessage(CRITICAL, "exec of /bin/sh failed: %m"); + exit(1); + } + } + + return; +} + + +static void copyWarnFn (char *msg) { + logMessage(WARNING, msg); +} + +static void copyErrorFn (char *msg) { + newtWinMessage(_("Error"), _("OK"), _(msg)); +} + +void loadUpdates(struct loaderData_s *loaderData) { + char *device = NULL, *part = NULL, *buf; + char **devNames = NULL; + enum { UPD_DEVICE, UPD_PART, UPD_PROMPT, UPD_LOAD, UPD_DONE } stage = UPD_DEVICE; + int rc, num = 0; + int dir = 1; + + while (stage != UPD_DONE) { + switch (stage) { + case UPD_DEVICE: { + rc = getRemovableDevices(&devNames); + if (rc == 0) + return; + + /* we don't need to ask which to use if they only have one */ + if (rc == 1) { + device = strdup(devNames[0]); + free(devNames); + devNames = NULL; + if (dir == -1) + return; + + stage = UPD_PART; + break; + } + dir = 1; + + startNewt(); + rc = newtWinMenu(_("Update Disk Source"), + _("You have multiple devices which could serve " + "as sources for an update disk. Which would " + "you like to use?"), 40, 10, 10, + rc < 6 ? rc : 6, devNames, + &num, _("OK"), _("Cancel"), NULL); + + if (rc == 2) { + free(devNames); + devNames = NULL; + return; + } + + device = strdup(devNames[num]); + free(devNames); + devNames = NULL; + stage = UPD_PART; + } + + case UPD_PART: { + char ** part_list = getPartitionsList(device); + int nump = 0, num = 0; + + if (part != NULL) { + free(part); + part = NULL; + } + + if ((nump = lenPartitionsList(part_list)) == 0) { + if (dir == -1) { + stage = UPD_DEVICE; + } else { + checked_asprintf(&part, "/dev/%s", device); + stage = UPD_PROMPT; + } + + break; + } + dir = 1; + + startNewt(); + rc = newtWinMenu(_("Update Disk Source"), + _("There are multiple partitions on this device " + "which could contain the update disk image. " + "Which would you like to use?"), 40, 10, 10, + nump < 6 ? nump : 6, part_list, &num, _("OK"), + _("Back"), NULL); + + if (rc == 2) { + freePartitionsList(part_list); + stage = UPD_DEVICE; + dir = -1; + break; + } + + part = strdup(part_list[num]); + stage = UPD_LOAD; + } + + case UPD_PROMPT: + checked_asprintf(&buf, _("Insert your updates disk into %s and " + "press \"OK\" to continue."), part); + + rc = newtWinChoice(_("Updates Disk"), _("OK"), _("Back"), buf); + free(buf); + buf = NULL; + + if (rc == 2) { + stage = UPD_PART; + dir = -1; + break; + } + + stage = UPD_LOAD; + break; + + case UPD_LOAD: + logMessage(INFO, "UPDATES device is %s", part); + + if (doPwMount(part, "/tmp/update-disk", "auto", "ro", NULL)) { + newtWinMessage(_("Error"), _("OK"), + _("Failed to mount updates disk")); + stage = UPD_PROMPT; + break; + } else { + /* Copy everything to /tmp/updates so we can unmount the disk */ + winStatus(40, 3, _("Updates"), _("Reading anaconda updates")); + if (!copyDirectory("/tmp/update-disk", "/tmp/updates", copyWarnFn, + copyErrorFn)) { + dir = 1; + stage = UPD_DONE; + } + + newtPopWindow(); + umount("/tmp/update-disk"); + } + + case UPD_DONE: + break; + } + } + + return; +} + +static char *newUpdatesLocation(const char *origLocation) { + const char *location; + char *retval = NULL; + newtComponent f, okay, cancel, answer, locationEntry; + newtGrid grid, buttons; + + startNewt(); + + locationEntry = newtEntry(-1, -1, NULL, 60, &location, NEWT_FLAG_SCROLL); + newtEntrySet(locationEntry, origLocation, 1); + + /* button bar at the bottom of the window */ + buttons = newtButtonBar(_("OK"), &okay, _("Cancel"), &cancel, NULL); + + grid = newtCreateGrid(1, 3); + + newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, + newtTextboxReflowed(-1, -1, _("Unable to download the updates image. Please modify the updates location below or press Cancel to proceed without updates.."), 60, 0, 0, 0), + 0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, locationEntry, + 0, 1, 0, 0, NEWT_ANCHOR_LEFT, 0); + newtGridSetField(grid, 0, 2, NEWT_GRID_SUBGRID, buttons, + 0, 1, 0, 0, 0, NEWT_GRID_FLAG_GROWX); + + f = newtForm(NULL, NULL, 0); + newtGridAddComponentsToForm(grid, f, 1); + newtGridWrappedWindow(grid, _("Error downloading updates image")); + newtGridFree(grid, 1); + + /* run the form */ + answer = newtRunForm(f); + + if (answer != cancel) + retval = strdup(location); + + newtFormDestroy(f); + newtPopWindow(); + + return retval; +} + +static int loadUpdatesFromRemote(char * url, struct loaderData_s * loaderData) { + int rc = getFileFromUrl(url, "/tmp/updates.img", loaderData); + + if (rc != 0) { + char *newLocation = newUpdatesLocation(url); + + if (!newLocation) + return rc; + else + return loadUpdatesFromRemote(newLocation, loaderData); + } + + copyUpdatesImg("/tmp/updates.img"); + unlink("/tmp/updates.img"); + return 0; +} + +static void writeVNCPasswordFile(char *pfile, char *password) { + FILE *f; + + f = fopen(pfile, "w+"); + fprintf(f, "%s\n", password); + fclose(f); +} + +/* XXX: read information from /etc/sysconfig/network-scripts/ifcfg-$INTERFACE + * (written by linuxrc), the linuxrc mess should be firing up NM too + */ +static void readNetInfo(struct loaderData_s ** ld) { + int i; + struct loaderData_s * loaderData = *ld; + DIR *dp = NULL; + FILE *f = NULL; + struct dirent *ent = NULL; + char *cfgfile = NULL; + int bufsiz = 100; + char buf[bufsiz]; + char *vname = NULL; + char *vparm = NULL; + + /* when this function is called, we can assume only one network device + * config file has been written to /etc/sysconfig/network-scripts, so + * find it and read it + */ + dp = opendir("/etc/sysconfig/network-scripts"); + if (dp == NULL) { + return; + } + + while ((ent = readdir(dp)) != NULL) { + if (!strncmp(ent->d_name, "ifcfg-", 6)) { + checked_asprintf(&cfgfile, "/etc/sysconfig/network-scripts/%s", + ent->d_name); + + break; + } + } + + if (dp != NULL) { + if (closedir(dp) == -1) { + logMessage(DEBUGLVL, "%s (%d): %m", __func__, __LINE__); + abort(); + } + } + + if (cfgfile == NULL) { + logMessage(DEBUGLVL, "no ifcfg files found in /etc/sysconfig/network-scripts"); + return; + } + + + if ((f = fopen(cfgfile, "r")) == NULL) { + logMessage(DEBUGLVL, "%s (%d): %m", __func__, __LINE__); + free(cfgfile); + return; + } + + if ((vname = (char *) malloc(sizeof(char) * 15)) == NULL) { + logMessage(DEBUGLVL, "%s (%d): %m", __func__, __LINE__); + abort(); + } + + if ((vparm = (char *) malloc(sizeof(char) * 85)) == NULL) { + logMessage(DEBUGLVL, "%s (%d): %m", __func__, __LINE__); + abort(); + } + + /* make sure everything is NULL before we begin copying info */ + loaderData->ipv4 = NULL; + loaderData->netmask = NULL; + loaderData->gateway = NULL; + loaderData->dns = NULL; + loaderData->peerid = NULL; + loaderData->subchannels = NULL; + loaderData->portname = NULL; + loaderData->nettype = NULL; + loaderData->ctcprot = NULL; + loaderData->layer2 = NULL; + loaderData->portno = NULL; + loaderData->macaddr = NULL; +#ifdef ENABLE_IPV6 + loaderData->ipv6 = NULL; + loaderData->gateway6 = NULL; +#endif + + /* + * The /tmp/netinfo file is written out by /sbin/init on s390x (which is + * really the linuxrc.s390 script). It's a shell-sourcable file with + * various system settings needing for the system instance. + * + * The goal of this function is to read in only the network settings + * and populate the loaderData structure. + */ + while(fgets(buf, bufsiz, f)) { + /* trim whitespace from end */ + i = 0; + while (!isspace(buf[i]) && i < (bufsiz-1)) + i++; + buf[i] = '\0'; + + /* break up var name and value */ + if (strstr(buf, "=")) { + vname = strtok(buf, "="); + if (vname == NULL) + continue; + + vparm = strtok(NULL, "="); + if (vparm == NULL) + continue; + + if (!strncmp(vname, "IPADDR", 6)) + loaderData->ipv4 = strdup(vparm); + + if (!strncmp(vname, "NETMASK", 7)) + loaderData->netmask = strdup(vparm); + + if (!strncmp(vname, "GATEWAY", 7)) + loaderData->gateway = strdup(vparm); + + if (!strncmp(vname, "DNS", 3)) + loaderData->dns = strdup(vparm); + + if (!strncmp(vname, "MTU", 3)) { + errno = 0; + loaderData->mtu = strtol(vparm, NULL, 10); + + if ((errno == ERANGE && (loaderData->mtu == LONG_MIN || + loaderData->mtu == LONG_MAX)) || + (errno != 0 && loaderData->mtu == 0)) { + logMessage(ERROR, "%s: %d: %m", __func__, __LINE__); + abort(); + } + } + + if (!strncmp(vname, "PEERID", 6)) + loaderData->peerid = strdup(vparm); + + if (!strncmp(vname, "SUBCHANNELS", 12)) + loaderData->subchannels = strdup(vparm); + + if (!strncmp(vname, "PORTNAME", 8)) + loaderData->portname = strdup(vparm); + + if (!strncmp(vname, "NETTYPE", 7)) + loaderData->nettype = strdup(vparm); + + if (!strncmp(vname, "CTCPROT", 7)) + loaderData->ctcprot = strdup(vparm); + + if (!strncmp(vname, "LAYER2", 6)) + loaderData->layer2 = strdup(vparm); + + if (!strncmp(vname, "PORTNO", 6)) + loaderData->portno = strdup(vparm); + + if (!strncmp(vname, "MACADDR", 7)) + loaderData->macaddr = strdup(vparm); + + if (!strncmp(vname, "HOSTNAME", 8)) + loaderData->hostname = strdup(vparm); + } + } + + if (loaderData->ipv4 && loaderData->netmask) { + flags |= LOADER_FLAGS_HAVE_CMSCONF; + } + + if (fclose(f) == -1) { + logMessage(ERROR, "%s: %d: %m", __func__, __LINE__); + abort(); + } + + if (cfgfile != NULL) { + free(cfgfile); + } + + return; +} + +/* parse anaconda or pxelinux-style ip= arguments + * pxelinux format: ip=<client-ip>:<boot-server-ip>:<gw-ip>:<netmask> + * anaconda format: ip=<client-ip> netmask=<netmask> gateway=<gw-ip> +*/ +static void parseCmdLineIp(struct loaderData_s * loaderData, char *argv) +{ + /* Detect pxelinux */ + if (strstr(argv, ":") != NULL) { + char *start, *end; + + /* IP */ + start = argv + 3; + end = strstr(start, ":"); + loaderData->ipv4 = strndup(start, end-start); + loaderData->ipinfo_set = 1; + + /* Boot server */ + if (end + 1 == '\0') + return; + start = end + 1; + end = strstr(start, ":"); + if (end == NULL) + return; + + /* Gateway */ + if (end + 1 == '\0') + return; + start = end + 1; + end = strstr(start, ":"); + if (end == NULL) { + loaderData->gateway = strdup (start); + return; + } else { + loaderData->gateway = strndup(start, end-start); + } + + /* Netmask */ + if (end + 1 == '\0') + return; + start = end + 1; + loaderData->netmask = strdup(start); + } else { + loaderData->ipv4 = strdup(argv + 3); + loaderData->ipinfo_set = 1; + } + + if (loaderData->ipinfo_set) + flags |= LOADER_FLAGS_IP_PARAM; +} + +#ifdef ENABLE_IPV6 +/* + * parse anaconda ipv6= arguments + */ +static void parseCmdLineIpv6(struct loaderData_s * loaderData, char *argv) +{ + /* right now we only accept ipv6= arguments equal to: + * dhcp DHCPv6 call + * auto RFC 2461 neighbor discovery + */ + loaderData->ipv6 = NULL; + + if (!strncasecmp(argv, "ipv6=dhcp", 9)) { + loaderData->ipv6 = strdup("dhcp"); + } else if (!strncasecmp(argv, "ipv6=auto", 9)) { + loaderData->ipv6 = strdup("auto"); + } + + if (loaderData->ipv6 != NULL) { + loaderData->ipv6info_set = 1; + flags |= LOADER_FLAGS_IPV6_PARAM; + } + + return; +} +#endif + +static long argToLong(char *arg, int offset) { + long retval; + + errno = 0; + + retval = strtol(arg+offset, NULL, 10); + if ((errno == ERANGE && (retval == LONG_MIN || retval == LONG_MAX)) || + (errno != 0 && retval == 0)) { + logMessage(ERROR, "%s: %d: %m", __func__, __LINE__); + abort(); + } + + return retval; +} + +/* parses /proc/cmdline for any arguments which are important to us. + * NOTE: in test mode, can specify a cmdline with --cmdline + */ +static void parseCmdLineFlags(struct loaderData_s * loaderData, + char * cmdLine) { + int fd; + char buf[1024]; + int len; + gint argc = 0; + gchar **argv = NULL; + GError *optErr = NULL; + int numExtraArgs = 0; + int i; + char *front; + + /* we want to default to graphical and allow override with 'text' */ + flags |= LOADER_FLAGS_GRAPHICAL; + + /* if we have any explicit cmdline (probably test mode), we don't want + * to parse /proc/cmdline */ + if (!cmdLine) { + if ((fd = open("/proc/cmdline", O_RDONLY)) < 0) return; + len = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (len <= 0) { + logMessage(INFO, "kernel command line was empty"); + return; + } + + buf[len] = '\0'; + cmdLine = buf; + } + + logMessage(INFO, "kernel command line: %s", cmdLine); + + if (!g_shell_parse_argv(cmdLine, &argc, &argv, &optErr)) { + g_error_free(optErr); + return; + } + + for (i=0; i < argc; i++) { + if (!strcasecmp(argv[i], "askmethod")) + flags |= LOADER_FLAGS_ASKMETHOD; + else if (!strcasecmp(argv[i], "asknetwork")) + flags |= LOADER_FLAGS_ASKNETWORK; + else if (!strcasecmp(argv[i], "noshell")) + flags |= LOADER_FLAGS_NOSHELL; + else if (!strcasecmp(argv[i], "nokill")) + flags |= LOADER_FLAGS_NOKILL; + else if (!strcasecmp(argv[i], "mediacheck")) + flags |= LOADER_FLAGS_MEDIACHECK; + else if (!strcasecmp(argv[i], "allowwireless")) + flags |= LOADER_FLAGS_ALLOW_WIRELESS; + else if (!strcasecmp(argv[i], "telnet")) + flags |= LOADER_FLAGS_TELNETD; + else if (!strcasecmp(argv[i], "noprobe")) + flags |= LOADER_FLAGS_NOPROBE; + else if (!strcasecmp(argv[i], "text")) { + logMessage(INFO, "text mode forced from cmdline"); + flags |= LOADER_FLAGS_TEXT; + flags &= ~LOADER_FLAGS_GRAPHICAL; + } + else if (!strcasecmp(argv[i], "graphical")) { + logMessage(INFO, "graphical mode forced from cmdline"); + flags |= LOADER_FLAGS_GRAPHICAL; + } else if (!strcasecmp(argv[i], "cmdline")) { + logMessage(INFO, "cmdline mode forced from cmdline"); + flags |= LOADER_FLAGS_CMDLINE; + } else if (!strncasecmp(argv[i], "updates=", 8)) + loaderData->updatessrc = strdup(argv[i] + 8); + else if (!strncasecmp(argv[i], "updates", 7)) + flags |= LOADER_FLAGS_UPDATES; + else if (!strncasecmp(argv[i], "dogtail=", 8)) + loaderData->dogtailurl = strdup(argv[i] + 8); + else if (!strncasecmp(argv[i], "dd=", 3) || + !strncasecmp(argv[i], "driverdisk=", 11)) { + loaderData->ddsrc = strdup(argv[i] + + (argv[i][1] == 'r' ? 11 : 3)); + } + else if (!strcasecmp(argv[i], "dd") || + !strcasecmp(argv[i], "driverdisk")) + flags |= LOADER_FLAGS_MODDISK; + else if (!strcasecmp(argv[i], "dlabel=on")) + flags |= LOADER_FLAGS_AUTOMODDISK; + else if (!strcasecmp(argv[i], "dlabel=off")) + flags &= ~LOADER_FLAGS_AUTOMODDISK; + else if (!strcasecmp(argv[i], "rescue")) + flags |= LOADER_FLAGS_RESCUE; + else if (!strcasecmp(argv[i], "nopass")) + flags |= LOADER_FLAGS_NOPASS; + else if (!strcasecmp(argv[i], "serial")) + flags |= LOADER_FLAGS_SERIAL; + else if (!strcasecmp(argv[i], "noipv4")) + flags |= LOADER_FLAGS_NOIPV4; +#ifdef ENABLE_IPV6 + else if (!strcasecmp(argv[i], "noipv6")) + flags |= LOADER_FLAGS_NOIPV6; +#endif + else if (!strcasecmp(argv[i], "kssendmac")) + flags |= LOADER_FLAGS_KICKSTART_SEND_MAC; + else if (!strcasecmp(argv[i], "kssendsn")) + flags |= LOADER_FLAGS_KICKSTART_SEND_SERIAL; + /* deprecated hardware bits */ + else if (!strcasecmp(argv[i], "nousbstorage")) + mlAddBlacklist("usb-storage"); + else if (!strcasecmp(argv[i], "nousb")) { + mlAddBlacklist("ehci-hcd"); + mlAddBlacklist("ohci-hcd"); + mlAddBlacklist("uhci-hcd"); + } else if (!strcasecmp(argv[i], "nofirewire")) + mlAddBlacklist("firewire-ohci"); + else if (!strncasecmp(argv[i], "loglevel=", 9)) { + if (!strcasecmp(argv[i]+9, "debug")) { + loaderData->logLevel = strdup(argv[i]+9); + setLogLevel(DEBUGLVL); + } + else if (!strcasecmp(argv[i]+9, "info")) { + loaderData->logLevel = strdup(argv[i]+9); + setLogLevel(INFO); + } + else if (!strcasecmp(argv[i]+9, "warning")) { + loaderData->logLevel = strdup(argv[i]+9); + setLogLevel(WARNING); + } + else if (!strcasecmp(argv[i]+9, "error")) { + loaderData->logLevel = strdup(argv[i]+9); + setLogLevel(ERROR); + } + else if (!strcasecmp(argv[i]+9, "critical")) { + loaderData->logLevel = strdup(argv[i]+9); + setLogLevel(CRITICAL); + } + } + else if (!strncasecmp(argv[i], "ksdevice=", 9)) { + loaderData->netDev = strdup(argv[i] + 9); + loaderData->netDev_set = 1; + } + else if (!strncmp(argv[i], "BOOTIF=", 7)) { + /* +10 so that we skip over the leading 01- */ + loaderData->bootIf = strdup(argv[i] + 10); + + /* scan the BOOTIF value and replace '-' with ':' */ + front = loaderData->bootIf; + if (front) { + while (*front != '\0') { + if (*front == '-') + *front = ':'; + front++; + } + } + + loaderData->bootIf_set = 1; + } else if (!strncasecmp(argv[i], "dhcpclass=", 10)) { + loaderData->netCls = strdup(argv[i] + 10); + loaderData->netCls_set = 1; + } + else if (!strcasecmp(argv[i], "ks") || !strncasecmp(argv[i], "ks=", 3)) + loaderData->ksFile = strdup(argv[i]); + else if (!strncasecmp(argv[i], "display=", 8)) + setenv("DISPLAY", argv[i] + 8, 1); + else if ((!strncasecmp(argv[i], "lang=", 5)) && + (strlen(argv[i]) > 5)) { + loaderData->lang = strdup(argv[i] + 5); + loaderData->lang_set = 1; + } + else if (!strncasecmp(argv[i], "keymap=", 7) && + (strlen(argv[i]) > 7)) { + loaderData->kbd = strdup(argv[i] + 7); + loaderData->kbd_set = 1; + } + else if (!strncasecmp(argv[i], "method=", 7)) { + logMessage(WARNING, "method= is deprecated. Please use repo= instead."); + loaderData->instRepo = strdup(argv[i] + 7); + } + else if (!strncasecmp(argv[i], "repo=", 5)) + loaderData->instRepo = strdup(argv[i] + 5); + else if (!strncasecmp(argv[i], "stage2=", 7)) + setStage2LocFromCmdline(argv[i] + 7, loaderData); + else if (!strncasecmp(argv[i], "hostname=", 9)) + loaderData->hostname = strdup(argv[i] + 9); + else if (!strncasecmp(argv[i], "ip=", 3)) + parseCmdLineIp(loaderData, argv[i]); +#ifdef ENABLE_IPV6 + else if (!strncasecmp(argv[i], "ipv6=", 5)) + parseCmdLineIpv6(loaderData, argv[i]); +#endif + else if (!strncasecmp(argv[i], "netmask=", 8)) + loaderData->netmask = strdup(argv[i] + 8); + else if (!strncasecmp(argv[i], "gateway=", 8)) + loaderData->gateway = strdup(argv[i] + 8); + else if (!strncasecmp(argv[i], "dns=", 4)) + loaderData->dns = strdup(argv[i] + 4); + else if (!strncasecmp(argv[i], "ethtool=", 8)) + loaderData->ethtool = strdup(argv[i] + 8); + else if (!strncasecmp(argv[i], "essid=", 6)) + loaderData->essid = strdup(argv[i] + 6); + else if (!strncasecmp(argv[i], "mtu=", 4)) + loaderData->mtu = argToLong(argv[i], 4); + else if (!strncasecmp(argv[i], "wepkey=", 7)) + loaderData->wepkey = strdup(argv[i] + 7); + else if (!strncasecmp(argv[i], "linksleep=", 10)) + num_link_checks = argToLong(argv[i], 10); + else if (!strncasecmp(argv[i], "nicdelay=", 9)) + post_link_sleep = argToLong(argv[i], 9); + else if (!strncasecmp(argv[i], "dhcptimeout=", 12)) + loaderData->dhcpTimeout = argToLong(argv[i], 12); + else if (!strncasecmp(argv[i], "selinux=0", 9)) + flags &= ~LOADER_FLAGS_SELINUX; + else if (!strncasecmp(argv[i], "selinux", 7)) + flags |= LOADER_FLAGS_SELINUX; + else if (!strncasecmp(argv[i], "gdb=", 4)) + loaderData->gdbServer = strdup(argv[i] + 4); + else if (!strncasecmp(argv[i], "proxy=", 6)) + splitProxyParam(argv[i]+6, &loaderData->proxyUser, + &loaderData->proxyPassword, &loaderData->proxy); + else if (numExtraArgs < (MAX_EXTRA_ARGS - 1)) { + /* go through and append args we just want to pass on to */ + /* the anaconda script, but don't want to represent as a */ + /* LOADER_FLAGS_XXX since loader doesn't care about these */ + /* particular options. */ + /* do vncpassword case first */ + if (!strncasecmp(argv[i], "vncpassword=", 12)) { + writeVNCPasswordFile("/tmp/vncpassword.dat", argv[i]+12); + } + else if (!strncasecmp(argv[i], "resolution=", 11) || + !strncasecmp(argv[i], "nomount", 7) || + !strncasecmp(argv[i], "vnc", 3) || + !strncasecmp(argv[i], "vncconnect=", 11) || + !strncasecmp(argv[i], "headless", 8) || + !strncasecmp(argv[i], "usefbx", 6) || + !strncasecmp(argv[i], "mpath", 6) || + !strncasecmp(argv[i], "nompath", 8) || + !strncasecmp(argv[i], "dmraid", 6) || + !strncasecmp(argv[i], "nodmraid", 8) || + !strncasecmp(argv[i], "xdriver=", 8) || + !strncasecmp(argv[i], "vesa", 4) || + !strncasecmp(argv[i], "syslog=", 7)) { + + /* vnc implies graphical */ + if (!strncasecmp(argv[i], "vnc", 3)) { + logMessage(INFO, "vnc forced graphical mode from cmdline"); + flags |= LOADER_FLAGS_GRAPHICAL; + } + + /* the following things require networking to be configured + * by loader, so an active connection is ready once we get + * to anaconda + */ + if (!strncasecmp(argv[i], "syslog", 6) || + !strncasecmp(argv[i], "vnc", 3)) { + logMessage(INFO, "early networking required for %s", + argv[i]); + flags |= LOADER_FLAGS_EARLY_NETWORKING; + } + if (isKickstartFileRemote(loaderData->ksFile)) { + logMessage(INFO, "early networking required for remote kickstart configuration"); + flags |= LOADER_FLAGS_EARLY_NETWORKING; + } + + if (!strncasecmp(argv[i], "vesa", 4)) { + checked_asprintf(&extraArgs[numExtraArgs], + "--xdriver=vesa"); + + logMessage(WARNING, "\"vesa\" command line argument is deprecated. use \"xdriver=vesa\"."); + } else { + checked_asprintf(&extraArgs[numExtraArgs],"--%s", + argv[i]); + } + + numExtraArgs += 1; + + if (numExtraArgs > (MAX_EXTRA_ARGS - 2)) { + logMessage(WARNING, "Too many command line arguments (max " + "allowed is %d), rest will be dropped.", + MAX_EXTRA_ARGS); + } + } + } + } + + readNetInfo(&loaderData); + + /* NULL terminates the array of extra args */ + extraArgs[numExtraArgs] = NULL; + + return; +} + +/* make sure they have enough ram */ +static void checkForRam(void) { + if (totalMemory() < MIN_RAM) { + char *buf; + + checked_asprintf(&buf, _("You do not have enough RAM to install %s " + "on this machine."), getProductName()); + + startNewt(); + newtWinMessage(_("Error"), _("OK"), buf); + free(buf); + stopNewt(); + doExit(0); + } +} + +static int haveDeviceOfType(int type) { + struct device ** devices; + + devices = getDevices(type); + if (devices) { + return 1; + } + return 0; +} + +static char *doLoaderMain(struct loaderData_s *loaderData, + moduleInfoSet modInfo) { + enum { STEP_LANG, STEP_KBD, STEP_METHOD, STEP_DRIVER, + STEP_DRIVERDISK, STEP_NETWORK, STEP_IFACE, + STEP_IP, STEP_STAGE2, STEP_DONE } step; + + char *url = NULL, *ret = NULL, *devName = NULL, *kbdtype = NULL; + static iface_t iface; + int i, rc = LOADER_NOOP, dir = 1; + int needsNetwork = 0, class = -1; + int skipMethodDialog = 0, skipLangKbd = 0; + + char *installNames[10]; + int numValidMethods = 0; + int validMethods[10]; + + for (i = 0; i < numMethods; i++, numValidMethods++) { + installNames[numValidMethods] = installMethods[i].name; + validMethods[numValidMethods] = i; + } + installNames[numValidMethods] = NULL; + + /* Before anything else, see if there's a CD/DVD with a stage2 image on + * it. However if stage2= was given, use that value as an override here. + * That will also then bypass any method selection UI in loader. + */ + if (!FL_ASKMETHOD(flags)) { + url = findAnacondaCD("/mnt/stage2"); + if (url) { + setStage2LocFromCmdline(url, loaderData); + skipMethodDialog = 1; + + logMessage(INFO, "Detected stage 2 image on CD (url: %s)", url); + winStatus(50, 3, _("Media Detected"), + _("Found local installation media"), 0); + sleep(3); + newtPopWindow(); + + skipLangKbd = 1; + flags |= LOADER_FLAGS_NOPASS; + } else if (!loaderData->stage2Data && loaderData->instRepo) { + /* If no CD/DVD with a stage2 image was found and we were given a + * repo=/method= parameter, try to piece together a valid setting + * for the stage2= parameter based on that. + */ + char *tmp; + + checked_asprintf(&tmp, "%s/images/install.img", + loaderData->instRepo); + + logMessage(INFO, "no stage2= given, assuming %s", tmp); + setStage2LocFromCmdline(tmp, loaderData); + free(tmp); + + /* If we had to infer a stage2= location, but the repo= parameter + * we based this guess on was wrong, we need to correct the typo + * in both places. Unfortunately we can't really know what the + * user meant, so the best we can do is take the results of + * running stage2= through the UI and chop off any /images/whatever + * path that's at the end of it. + */ + loaderData->inferredStage2 = 1; + skipMethodDialog = 1; + } else if (loaderData->stage2Data) { + skipMethodDialog = 1; + } + } else { + /* Needed because they have already been set when parsing cmdline. + * (Leaks a little.) + */ + loaderData->method = -1; + loaderData->stage2Data = NULL; + } + + i = 0; + step = STEP_LANG; + + while (step != STEP_DONE) { + switch(step) { + case STEP_LANG: { + if (loaderData->lang && (loaderData->lang_set == 1)) + setLanguage(loaderData->lang, 1); + else if (FL_RESCUE(flags) || !skipLangKbd) + chooseLanguage(&loaderData->lang); + + step = STEP_KBD; + dir = 1; + break; + } + + case STEP_KBD: { + if (loaderData->kbd && (loaderData->kbd_set == 1)) { + /* JKFIXME: this is broken -- we should tell of the + * failure; best by pulling code out in kbd.c to use */ + if (isysLoadKeymap(loaderData->kbd)) { + logMessage(WARNING, "requested keymap %s is not valid, asking", + loaderData->kbd); + loaderData->kbd = NULL; + loaderData->kbd_set = 0; + break; + } + rc = LOADER_NOOP; + } else if (FL_RESCUE(flags) || !skipLangKbd) { + /* JKFIXME: should handle kbdtype, too probably... but it + * just matters for sparc */ + if (!FL_CMDLINE(flags)) + rc = chooseKeyboard(loaderData, &kbdtype); + else + rc = LOADER_NOOP; + } else { + step = STEP_METHOD; + dir = 1; + } + + if (rc == LOADER_NOOP) { + if (dir == -1) + step = STEP_LANG; + else + step = STEP_METHOD; + + break; + } + + if (rc == LOADER_BACK) { + step = STEP_LANG; + dir = -1; + } else { + step = STEP_METHOD; + dir = 1; + } + + break; + } + + case STEP_METHOD: { + if (loaderData->method != -1) + skipMethodDialog = 1; + else if (FL_CMDLINE(flags)) { + fprintf(stderr, "No method given for cmdline mode, aborting\n"); + doExit(EXIT_FAILURE); + } + + /* If we already found a stage2 image, skip the prompt. */ + if (skipMethodDialog) { + if (dir == 1) + rc = 1; + else + rc = -1; + } else { + /* we need to set these each time through so that we get + * updated for language changes (#83672) */ + for (i = 0; i < numMethods; i++) { + installNames[i] = _(installMethods[i].name); + } + installNames[i] = NULL; + + rc = newtWinMenu(FL_RESCUE(flags) ? _("Rescue Method") : + _("Installation Method"), + FL_RESCUE(flags) ? + _("What type of media contains the rescue " + "image?") : + _("What type of media contains the installation " + "image?"), + 30, 10, 20, 6, installNames, &loaderData->method, + _("OK"), _("Back"), NULL); + if (rc == 2) { + loaderData->method = -1; + } + } + + if (rc && (rc != 1)) { + step = STEP_KBD; + dir = -1; + } else { + class = installMethods[validMethods[loaderData->method]].type; + step = STEP_DRIVER; + dir = 1; + } + break; + } + + case STEP_DRIVER: { + if ((FL_EARLY_NETWORKING(flags) && haveDeviceOfType(DEVICE_NETWORK)) || + (class == -1 || haveDeviceOfType(class))) { + step = STEP_NETWORK; + dir = 1; + class = -1; + break; + } + + if (skipLangKbd) { + skipLangKbd = 0; + step = STEP_KBD; + break; + } + + rc = newtWinTernary(_("No driver found"), _("Select driver"), + _("Use a driver disk"), _("Back"), + _("Unable to find any devices of the type " + "needed for this installation type. " + "Would you like to manually select your " + "driver or use a driver disk?")); + if (rc == 2) { + step = STEP_DRIVERDISK; + dir = 1; + break; + } else if (rc == 3) { + step = STEP_METHOD; + loaderData->method = -1; + dir = -1; + break; + } + + chooseManualDriver(installMethods[validMethods[loaderData->method]].type, + loaderData); + /* it doesn't really matter what we return here; we just want + * to reprobe and make sure we have the driver */ + step = STEP_DRIVER; + break; + } + + case STEP_DRIVERDISK: { + if (skipLangKbd) { + skipLangKbd = 0; + step = STEP_KBD; + break; + } + + rc = loadDriverFromMedia(class, loaderData, 0, 0); + if (rc == LOADER_BACK) { + step = STEP_DRIVER; + dir = -1; + break; + } + + /* need to come back to driver so that we can ensure that we found + * the right kind of driver after loading the driver disk */ + step = STEP_DRIVER; + break; + } + + case STEP_NETWORK: { + if (((installMethods[validMethods[loaderData->method]].type != + DEVICE_NETWORK) && (!hasGraphicalOverride()) && + !FL_ASKNETWORK(flags) && + !FL_EARLY_NETWORKING(flags)) || + (is_nm_connected())) { + needsNetwork = 0; + if (dir == 1) + step = STEP_STAGE2; + else if (dir == -1) + step = STEP_METHOD; + break; + } + + needsNetwork = 1; + if (!haveDeviceOfType(DEVICE_NETWORK)) { + class = DEVICE_NETWORK; + step = STEP_DRIVER; + break; + } + logMessage(INFO, "need to set up networking"); + + memset(&iface, 0, sizeof(iface)); + + /* fall through to interface selection */ + } + + case STEP_IFACE: { + logMessage(INFO, "going to pick interface"); + + /* skip configureTCPIP() screen for kickstart (#260621) */ + if (loaderData->ksFile) + flags |= LOADER_FLAGS_IS_KICKSTART; + + if (FL_HAVE_CMSCONF(flags)) { + loaderData->ipinfo_set = 1; +#ifdef ENABLE_IPV6 + loaderData->ipv6info_set = 1; +#endif + } + + rc = chooseNetworkInterface(loaderData); + if ((rc == LOADER_BACK) || (rc == LOADER_ERROR) || + ((dir == -1) && (rc == LOADER_NOOP))) { + /* don't skip method dialog iff we don't have url from ks or boot params */ + if (!loaderData->stage2Data) { + loaderData->method = -1; + } + step = STEP_METHOD; + dir = -1; + break; + } + + devName = loaderData->netDev; + strcpy(iface.device, devName); + + /* continue to ip config */ + step = STEP_IP; + dir = 1; + break; + } + + case STEP_IP: { + if (!needsNetwork || dir == -1) { + step = STEP_METHOD; /* only hit going back */ + break; + } + + if ((ret = malloc(INET6_ADDRSTRLEN+1)) == NULL) { + logMessage(ERROR, "malloc failure for ret in STEP_IP"); + doExit(EXIT_FAILURE); + } + + logMessage(INFO, "going to do getNetConfig"); + + /* s390 provides all config info by way of the CMS conf file */ + if (FL_HAVE_CMSCONF(flags)) { + loaderData->ipinfo_set = 1; +#ifdef ENABLE_IPV6 + loaderData->ipv6info_set = 1; +#endif + } + + /* populate netDev based on any kickstart data */ + setupIfaceStruct(&iface, loaderData); + rc = readNetConfig(devName, &iface, loaderData->netCls, loaderData->method); + + /* set the hostname if we have that */ + if (loaderData->hostname) { + if (sethostname(loaderData->hostname, + strlen(loaderData->hostname))) { + logMessage(ERROR, "error setting hostname to %s", + loaderData->hostname); + } + } + + free(ret); + ret = NULL; + + if ((rc == LOADER_BACK) || + ((dir == -1) && (rc == LOADER_NOOP))) { + needsNetwork = 1; + step = STEP_IFACE; + dir = -1; + break; + } + /* retry */ + if (rc == LOADER_ERROR) { + needsNetwork = 1; + break; + } + + writeEnabledNetInfo(&iface); + step = STEP_STAGE2; + dir = 1; + break; + } + + case STEP_STAGE2: { + if (url) { + logMessage(INFO, "stage2 url is %s", url); + return url; + } + + logMessage(INFO, "starting STEP_STAGE2"); + url = installMethods[validMethods[loaderData->method]].mountImage( + installMethods + validMethods[loaderData->method], + "/mnt/stage2", loaderData); + if (!url) { + step = STEP_IP; + loaderData->ipinfo_set = 0; +#ifdef ENABLE_IPV6 + loaderData->ipv6info_set = 0; +#endif + loaderData->method = -1; + skipMethodDialog = 0; + dir = -1; + } else { + logMessage(INFO, "got stage2 at url %s", url); + step = STEP_DONE; + dir = 1; + + if (loaderData->invalidRepoParam) { + char *newInstRepo; + + /* Doesn't contain /images? Let's not even try. */ + if (strstr(url, "/images") == NULL) + break; + + checked_asprintf(&newInstRepo, "%.*s", + (int) (strstr(url, "/images")-url), url); + + free(loaderData->instRepo); + loaderData->instRepo = newInstRepo; + logMessage(INFO, "reset repo= parameter to %s", + loaderData->instRepo); + } + } + + break; + } + + case STEP_DONE: + break; + } + } + + return url; +} +static int manualDeviceCheck(struct loaderData_s *loaderData) { + char ** devices; + int i, j, rc, num = 0; + unsigned int width = 40; + char * buf; + + do { + /* FIXME */ + devices = malloc(1 * sizeof(*devices)); + j = 0; + devices[j] = NULL; + + if (width > 70) + width = 70; + + if (j > 0) { + buf = _("The following devices have been found on your system."); + } else { + buf = _("No device drivers have been loaded for your system. " + "Would you like to load any now?"); + } + + rc = newtWinMenu(_("Devices"), buf, width, 10, 20, + (j > 6) ? 6 : j, devices, &num, _("Done"), + _("Add Device"), NULL); + + /* no leaky */ + for (i = 0; i < j; i++) + free(devices[j]); + free(devices); + + if (rc != 2) + break; + + chooseManualDriver(DEVICE_ANY, loaderData); + } while (1); + return 0; +} + +/* JKFIXME: I don't really like this, but at least it isolates the ifdefs */ +/* Either move dirname to %s_old or unlink depending on arch (unlink on all + * !s390{,x} arches). symlink to /mnt/runtime/dirname. dirname *MUST* start + * with a '/' */ +static void migrate_runtime_directory(char * dirname) { + char * runtimedir; + int ret; + + checked_asprintf(&runtimedir, "/mnt/runtime%s", dirname); + + if (!access(runtimedir, X_OK)) { + if (unlink(dirname) == -1) { + char * olddir; + + checked_asprintf(&olddir, "%s_old", dirname); + + ret = rename(dirname, olddir); + free(olddir); + } + ret = symlink(runtimedir, dirname); + } + free(runtimedir); +} + + +static int hasGraphicalOverride() { + int i; + + if (getenv("DISPLAY")) + return 1; + + for (i = 0; extraArgs[i] != NULL; i++) { + if (!strncasecmp(extraArgs[i], "--vnc", 5)) + return 1; + } + return 0; +} + +void loaderSegvHandler(int signum) { + void *array[30]; + size_t i; + const char const * const errmsgs[] = { + "loader received SIG", + "! Backtrace:\n", + "Loader 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 */ + + newtFinished(); + 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 loaderExitHandler(void) +{ + if (expected_exit) + return; + + loaderSegvHandler(0); +} + +static void setupBacktrace(void) +{ + void *array; + + signal(SIGSEGV, loaderSegvHandler); + signal(SIGABRT, loaderSegvHandler); + atexit(loaderExitHandler); + + /* 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); +} + +void loaderUsrXHandler(int signum) { + logMessage(INFO, "Remembering signal %d\n", signum); + init_sig = signum; +} + +static int anaconda_trace_init(void) { +#ifdef USE_MTRACE + setenv("MALLOC_TRACE","/malloc",1); + mtrace(); +#endif + /* We have to do this before we init bogl(), which doLoaderMain will do + * when setting fonts for different languages. It's also best if this + * is well before we might take a SEGV, so they'll go to tty8 */ + initializeTtys(); + + /* set up signal handler */ + setupBacktrace(); + + return 0; +} + +static void add_to_path_env(const char *env, const char *val) +{ + char *oldenv, *newenv; + + oldenv = getenv(env); + if (oldenv) { + checked_asprintf(&newenv, "%s:%s", val, oldenv); + + oldenv = strdupa(newenv); + free(newenv); + newenv = oldenv; + } else { + newenv = strdupa(val); + } + + setenv(env, newenv, 1); +} + +static void loadScsiDhModules(void) +{ + struct utsname utsname; + char *modules = NULL; + char *tmp = NULL; + struct dirent *ent = NULL; + + uname(&utsname); + checked_asprintf(&tmp, + "/lib/modules/%s/kernel/drivers/scsi/device_handler", utsname.release); + + DIR *dir = opendir(tmp); + free(tmp); + if (!dir) + return; + + int fd = dirfd(dir); + while ((ent = readdir(dir)) != NULL) { + struct stat sb; + + if (fstatat(fd, ent->d_name, &sb, 0) < 0) + continue; + + size_t len = strlen(ent->d_name) - 3; + if (strcmp(ent->d_name+len, ".ko")) + continue; + + if (S_ISREG(sb.st_mode)) { + char modname[len+1]; + strncpy(modname, ent->d_name, len); + modname[len] = '\0'; + + if (modules && modules[0]) { + checked_asprintf(&tmp, "%s:%s", modules, modname); + } else { + checked_asprintf(&tmp, "%s", modname); + } + + free(modules); + modules = tmp; + } + } + closedir(dir); + + mlLoadModuleSet(modules); + free(modules); +} + +int main(int argc, char ** argv) { + int rc, ret, pid, status; + + struct stat sb; + struct serial_struct si; + char * arg; + FILE *f; + + char twelve = 12; + + moduleInfoSet modInfo; + + char *url = NULL; + + char ** argptr, ** tmparg; + char * anacondaArgs[50]; + + struct loaderData_s loaderData; + + char *path, *fmt; + GSList *dd, *dditer; + + gchar *cmdLine = NULL, *ksFile = NULL, *virtpcon = NULL; + gboolean mediacheck = FALSE; + gchar **remaining = NULL; + GOptionContext *optCon = g_option_context_new(NULL); + GError *optErr = NULL; + GOptionEntry optionTable[] = { + { "cmdline", 0, 0, G_OPTION_ARG_STRING, &cmdLine, NULL, NULL }, + { "ksfile", 0, 0, G_OPTION_ARG_STRING, &ksFile, NULL, NULL }, + { "mediacheck", 0, 0, G_OPTION_ARG_NONE, &mediacheck, NULL, NULL }, + { "virtpconsole", 0, 0, G_OPTION_ARG_STRING, &virtpcon, NULL, NULL }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &remaining, + NULL, NULL }, + { NULL }, + }; + + /* get init PID if we have it */ + if ((f = fopen("/var/run/init.pid", "r")) != NULL) { + char linebuf[256]; + + while (fgets(linebuf, sizeof(linebuf), f) != NULL) { + errno = 0; + init_pid = strtol(linebuf, NULL, 10); + if (errno == EINVAL || errno == ERANGE) { + logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); + init_pid = 1; + } + } + + fclose(f); + } + + signal(SIGUSR1, loaderUsrXHandler); + signal(SIGUSR2, loaderUsrXHandler); + + /* Make sure sort order is right. */ + setenv ("LC_COLLATE", "C", 1); + + /* Very first thing, set up tracebacks and debug features. */ + rc = anaconda_trace_init(); + + /* now we parse command line options */ + g_option_context_set_help_enabled(optCon, FALSE); + g_option_context_add_main_entries(optCon, optionTable, NULL); + + if (!g_option_context_parse(optCon, &argc, &argv, &optErr)) { + fprintf(stderr, "bad option: %s\n", optErr->message); + g_error_free(optErr); + g_option_context_free(optCon); + doExit(1); + } + + g_option_context_free(optCon); + + if (remaining) { + fprintf(stderr, "unexpected argument: %s\n", remaining[0]); + g_strfreev(remaining); + doExit(1); + } + + g_strfreev(remaining); + + if (!access("/var/run/loader.run", R_OK)) { + printf(_("loader has already been run. Starting shell.\n")); + execl("/bin/sh", "-/bin/sh", NULL); + doExit(0); + } + + f = fopen("/var/run/loader.run", "w+"); + fprintf(f, "%d\n", getpid()); + fclose(f); + + /* The fstat checks disallows serial console if we're running through + a pty. This is handy for Japanese. */ + fstat(0, &sb); + if (major(sb.st_rdev) != 3 && major(sb.st_rdev) != 136 && + (virtpcon == NULL)){ + if ((ioctl (0, TIOCLINUX, &twelve) < 0) && + (ioctl(0, TIOCGSERIAL, &si) != -1)) + flags |= LOADER_FLAGS_SERIAL; + } + + if (mediacheck) flags |= LOADER_FLAGS_MEDIACHECK; + if (ksFile) flags |= LOADER_FLAGS_KICKSTART; + if (virtpcon) flags |= LOADER_FLAGS_VIRTPCONSOLE; + + /* uncomment to send mac address in ks=http:/ header by default*/ + flags |= LOADER_FLAGS_KICKSTART_SEND_MAC; + + /* JKFIXME: I do NOT like this... it also looks kind of bogus */ +#if defined(__s390__) || defined(__s390x__) + flags |= LOADER_FLAGS_NOSHELL; +#endif + + openLog(); + + /* XXX if RHEL, enable the AUTODD feature by default, + * but we should come with more general way how to control this */ + if (!strncmp(getProductName(), "Red Hat", 7)) { + flags |= LOADER_FLAGS_AUTOMODDISK; + } + + memset(&loaderData, 0, sizeof(loaderData)); + loaderData.method = -1; + loaderData.fw_loader_pid = -1; + loaderData.fw_search_pathz_len = -1; + loaderData.dhcpTimeout = -1; + + extraArgs[0] = NULL; + parseCmdLineFlags(&loaderData, cmdLine); + + logMessage(INFO, "anaconda version %s on %s starting", VERSION, getProductArch()); + + if ((FL_SERIAL(flags) || FL_VIRTPCONSOLE(flags)) && + !hasGraphicalOverride()) { + logMessage(INFO, "text mode forced due to serial/virtpconsole"); + flags |= LOADER_FLAGS_TEXT; + } + set_fw_search_path(&loaderData, "/firmware:/lib/firmware"); + start_fw_loader(&loaderData); + + arg = "/lib/modules/module-info"; + modInfo = newModuleInfoSet(); + if (readModuleInfo(arg, modInfo, NULL, 0)) { + fprintf(stderr, "failed to read %s\n", arg); + sleep(5); + stop_fw_loader(&loaderData); + doExit(1); + } + initializeConsole(); + + checkForRam(); + + /* iSeries vio console users will be ssh'ing in to the primary + partition, so use a terminal type that is appripriate */ + if (isVioConsole()) + setenv("TERM", "vt100", 1); + + mlLoadModuleSet("cramfs:squashfs:iscsi_tcp"); + + loadScsiDhModules(); + +#if !defined(__s390__) && !defined(__s390x__) + mlLoadModuleSet("floppy:edd:pcspkr:iscsi_ibft"); +#endif + +#ifdef ENABLE_IPV6 + if (!FL_NOIPV6(flags)) + mlLoadModule("ipv6", NULL); +#endif + + /* now let's do some initial hardware-type setup */ +#if defined(__powerpc__) + mlLoadModule("spufs", NULL); +#endif + + if (loaderData.lang && (loaderData.lang_set == 1)) { + setLanguage(loaderData.lang, 1); + } + + /* FIXME: this is a bit of a hack */ + loaderData.modInfo = modInfo; + + /* Setup depmod & modprobe so we can load multiple DDs */ + modprobeDDmode(); + + /* If there is /.rundepmod file present, rerun depmod */ + if (!access("/.rundepmod", R_OK)){ + if (system("depmod -a")) { + /* this is not really fatal error, it might still work, log it */ + logMessage(ERROR, "Error running depmod -a for initrd overlay"); + } + } + + if (FL_AUTOMODDISK(flags)) { + /* Load all autodetected DDs */ + logMessage(INFO, "Trying to detect vendor driver discs"); + dd = findDriverDiskByLabel(); + dditer = dd; + while(dditer) { + /* load the DD */ + if (loadDriverDiskFromPartition(&loaderData, (char*)(dditer->data))) { + logMessage(ERROR, "Automatic driver disk loader failed for %s.", (char*)(dditer->data)); + } + else { + logMessage(INFO, "Automatic driver disk loader succeeded for %s.", (char*)(dditer->data)); + } + + /* clean the device record */ + free((char*)(dditer->data)); + dditer->data = NULL; + + /* next DD */ + dditer = g_slist_next(dditer); + } + g_slist_free(dd); + } + + if (FL_MODDISK(flags)) { + startNewt(); + loadDriverDisks(DEVICE_ANY, &loaderData); + } + + if (!access("/dd.img", R_OK)) { + logMessage(INFO, "found /dd.img, loading drivers"); + getDDFromSource(&loaderData, "path:/dd.img"); + } + + /* Reset depmod & modprobe to normal mode and get the rest of drivers*/ + modprobeNormalmode(); + + /* this allows us to do an early load of modules specified on the + * command line to allow automating the load order of modules so that + * eg, certain scsi controllers are definitely first. + * FIXME: this syntax is likely to change in a future release + * but is done as a quick hack for the present. + */ + if (!mlInitModuleConfig()) { + logMessage(ERROR, "unable to initialize kernel module loading"); + abort(); + } + + earlyModuleLoad(0); + + busProbe(FL_NOPROBE(flags)); + + /* Disable all network interfaces in NetworkManager by default */ +#if !defined(__s390__) && !defined(__s390x__) + int i; + + if ((i = writeDisabledNetInfo()) != 0) { + logMessage(ERROR, "writeDisabledNetInfo failure: %d", i); + } +#endif + + /* Start NetworkManager now so it's always available to talk to. */ + if (iface_start_NetworkManager()) + logMessage(INFO, "failed to start NetworkManager"); + + if (!FL_CMDLINE(flags)) + startNewt(); + + /* can't run gdbserver until after network modules are loaded */ + doGdbserver(&loaderData); + + /* JKFIXME: we'd really like to do this before the busprobe, but then + * we won't have network devices available (and that's the only thing + * we support with this right now */ + if (loaderData.ddsrc != NULL) { + getDDFromSource(&loaderData, loaderData.ddsrc); + } + + /* JKFIXME: loaderData->ksFile is set to the arg from the command line, + * and then getKickstartFile() changes it and sets FL_KICKSTART. + * kind of weird. */ + if (loaderData.ksFile || ksFile) { + logMessage(INFO, "getting kickstart file"); + + if (!ksFile) + getKickstartFile(&loaderData); + if (FL_KICKSTART(flags) && + (ksReadCommands((ksFile)?ksFile:loaderData.ksFile)!=LOADER_ERROR)) { + runKickstart(&loaderData); + } + } + + if (FL_TELNETD(flags)) + startTelnetd(&loaderData); + + url = doLoaderMain(&loaderData, modInfo); + + /* unlink dirs and link to the ones in /mnt/runtime */ + migrate_runtime_directory("/usr"); + migrate_runtime_directory("/lib"); + migrate_runtime_directory("/lib64"); + ret = symlink("/mnt/runtime/etc/selinux", "/etc/selinux"); + copyDirectory("/mnt/runtime/etc","/etc", NULL, copyErrorFn); + copyDirectory("/mnt/runtime/var","/var", NULL, copyErrorFn); + + /* now load SELinux policy before exec'ing anaconda and the shell + * (if we're using SELinux) */ + if (FL_SELINUX(flags)) { + if (mount("/selinux", "/selinux", "selinuxfs", 0, NULL)) { + logMessage(ERROR, "failed to mount /selinux: %m, disabling SELinux"); + flags &= ~LOADER_FLAGS_SELINUX; + } else { + if (loadpolicy() == 0) { + setexeccon(ANACONDA_CONTEXT); + } else { + logMessage(ERROR, "failed to load policy, disabling SELinux"); + flags &= ~LOADER_FLAGS_SELINUX; + } + } + } + + logMessage(INFO, "getting ready to spawn shell now"); + + spawnShell(); /* we can attach gdb now :-) */ + + if (FL_NOPROBE(flags) && !loaderData.ksFile) { + startNewt(); + manualDeviceCheck(&loaderData); + } + + if (loaderData.updatessrc) + loadUpdatesFromRemote(loaderData.updatessrc, &loaderData); + else if (FL_UPDATES(flags)) + loadUpdates(&loaderData); + + /* make sure /tmp/updates exists so that magic in anaconda to */ + /* symlink rhpl/ will work */ + if (access("/tmp/updates", F_OK)) + mkdirChain("/tmp/updates"); + + add_fw_search_dir(&loaderData, "/tmp/updates/firmware"); + add_fw_search_dir(&loaderData, "/tmp/product/firmware"); + + add_to_path_env("PYTHONPATH", "/tmp/updates"); + add_to_path_env("PYTHONPATH", "/tmp/updates/iw"); + add_to_path_env("PYTHONPATH", "/tmp/updates/textw"); + add_to_path_env("PYTHONPATH", "/tmp/product"); + add_to_path_env("LD_LIBRARY_PATH", "/tmp/updates"); + add_to_path_env("LD_LIBRARY_PATH", "/tmp/product"); + add_to_path_env("PATH", "/tmp/updates"); + add_to_path_env("PATH", "/tmp/product"); + + stop_fw_loader(&loaderData); + start_fw_loader(&loaderData); + + mlLoadModuleSet("raid0:raid1:raid5:raid6:raid456:raid10:linear:dm-mod:dm-zero:dm-mirror:dm-snapshot:dm-multipath:dm-round-robin:dm-crypt:cbc:sha256:lrw:xts"); + + if (!access("/mnt/runtime/usr/lib/libunicode-lite.so.1", R_OK)) + setenv("LD_PRELOAD", "/mnt/runtime/usr/lib/libunicode-lite.so.1", 1); + if (!access("/mnt/runtime/usr/lib64/libunicode-lite.so.1", R_OK)) + setenv("LD_PRELOAD", "/mnt/runtime/usr/lib64/libunicode-lite.so.1", 1); + + argptr = anacondaArgs; + + path = getenv("PATH"); + while (path && path[0]) { + int n = strcspn(path, ":"); + char c, *binpath; + + c = path[n]; + path[n] = '\0'; + checked_asprintf(&binpath, "%s/anaconda", path); + path[n] = c; + + if (!access(binpath, X_OK)) { + *argptr++ = strdupa(binpath); + free(binpath); + break; + } + free(binpath); + path += n + 1; + } + + logMessage(INFO, "Running anaconda script %s", *(argptr-1)); + + *argptr++ = "--stage2"; + if (strncmp(url, "ftp:", 4)) { + *argptr++ = url; + } else { + int fd, ret; + + fd = open("/tmp/ftp-stage2", O_CREAT | O_TRUNC | O_RDWR, 0600); + ret = write(fd, url, strlen(url)); + ret = write(fd, "\r", 1); + close(fd); + *argptr++ = "@/tmp/ftp-stage2"; + } + + /* add extra args - this potentially munges extraArgs */ + tmparg = extraArgs; + while (*tmparg) { + char *idx; + + logMessage(DEBUGLVL, "adding extraArg %s", *tmparg); + idx = strchr(*tmparg, '='); + if (idx && ((idx-*tmparg) < strlen(*tmparg))) { + *idx = '\0'; + *argptr++ = *tmparg; + *argptr++ = idx+1; + } else { + *argptr++ = *tmparg; + } + + tmparg++; + } + + if (FL_AUTOMODDISK(flags)) + *argptr++ = "--dlabel"; + + if (FL_NOIPV4(flags)) + *argptr++ = "--noipv4"; + +#ifdef ENABLE_IPV6 + if (FL_NOIPV6(flags)) + *argptr++ = "--noipv6"; +#endif + +#if defined(__s390__) || defined(__s390x__) + *argptr++ = "--headless"; +#endif + + if (FL_KICKSTART(flags)) { + *argptr++ = "--kickstart"; + *argptr++ = loaderData.ksFile; + } + + if (FL_SERIAL(flags)) + *argptr++ = "--serial"; + + if (FL_RESCUE(flags)) { + *argptr++ = "--rescue"; + } else { + if (FL_TEXT(flags)) + *argptr++ = "-T"; + else if (FL_GRAPHICAL(flags)) + *argptr++ = "--graphical"; + if (FL_CMDLINE(flags)) + *argptr++ = "-C"; + if (!FL_SELINUX(flags)) + *argptr++ = "--noselinux"; + else if (FL_SELINUX(flags)) + *argptr++ = "--selinux"; + + if (FL_VIRTPCONSOLE(flags)) { + *argptr++ = "--virtpconsole"; + *argptr++ = virtpcon; + } + + if (loaderData.updatessrc && FL_UPDATES(flags)) { + *argptr++ = "--updates"; + *argptr++ = loaderData.updatessrc; + } + + if (loaderData.dogtailurl) { + *argptr++ = "--dogtail"; + *argptr++ = loaderData.dogtailurl; + } + + if ((loaderData.lang) && !FL_NOPASS(flags)) { + *argptr++ = "--lang"; + *argptr++ = loaderData.lang; + } + + if ((loaderData.kbd) && !FL_NOPASS(flags)) { + *argptr++ = "--keymap"; + *argptr++ = loaderData.kbd; + } + + if (loaderData.logLevel) { + *argptr++ = "--loglevel"; + *argptr++ = loaderData.logLevel; + } + + if (loaderData.instRepo) { + *argptr++ = "--repo"; + if (strncmp(loaderData.instRepo, "ftp:", 4)) { + *argptr++ = loaderData.instRepo; + } else { + int fd, ret; + + fd = open("/tmp/ftp-repo", O_CREAT | O_TRUNC | O_RDWR, 0600); + ret = write(fd, loaderData.instRepo, strlen(loaderData.instRepo)); + ret = write(fd, "\r", 1); + close(fd); + *argptr++ = "@/tmp/ftp-repo"; + } + } + + if (loaderData.proxy && strcmp("", loaderData.proxy)) { + *argptr++ = "--proxy"; + + *argptr++ = strdup(loaderData.proxy); + + if (loaderData.proxyUser && strcmp(loaderData.proxyUser, "")) { + int fd, ret; + + fd = open("/tmp/proxy", O_CREAT|O_TRUNC|O_RDWR, 0600); + ret = write(fd, loaderData.proxyUser, strlen(loaderData.proxyUser)); + ret = write(fd, "\r\n", 2); + + if (loaderData.proxyPassword && strcmp(loaderData.proxyPassword, "")) { + ret = write(fd, loaderData.proxyPassword, strlen(loaderData.proxyPassword)); + ret = write(fd, "\r\n", 2); + } + + close(fd); + + *argptr++ = "--proxyAuth"; + *argptr++ = "/tmp/proxy"; + } + } + } + + *argptr = NULL; + + stopNewt(); + closeLog(); + + if (FL_RESCUE(flags)) { + fmt = _("Running anaconda %s, the %s rescue mode - please wait.\n"); + } else { + fmt = _("Running anaconda %s, the %s system installer - please wait.\n"); + } + printf(fmt, VERSION, getProductName()); + + if (!(pid = fork())) { + if (execv(anacondaArgs[0], anacondaArgs) == -1) { + fprintf(stderr,"exec of anaconda failed: %m\n"); + doExit(1); + } + } + + waitpid(pid, &status, 0); + + if (!WIFEXITED(status) || (WIFEXITED(status) && WEXITSTATUS(status))) { + rc = 1; + } else { + rc = 0; + } + + if ((rc == 0) && (FL_POWEROFF(flags) || FL_HALT(flags))) { + if (!(pid = fork())) { + char * cmd = (FL_POWEROFF(flags) ? strdup("/sbin/poweroff") : + strdup("/sbin/halt")); + if (execl(cmd, cmd, NULL) == -1) { + fprintf(stderr, "exec of poweroff failed: %m\n"); + doExit(1); + } + } + waitpid(pid, &status, 0); + } + + stop_fw_loader(&loaderData); +#if defined(__s390__) || defined(__s390x__) + /* at the latest possibility signal init=linuxrc.s390 to reboot/halt */ + logMessage(INFO, "Sending signal %d to process %d\n", + init_sig, init_pid); + kill(init_pid, init_sig); +#endif + doExit(rc); + + doExit(1); +} + +/* vim:set sw=4 sts=4 et: */ diff --git a/loader/loader.h b/loader/loader.h new file mode 100644 index 0000000..c88457f --- /dev/null +++ b/loader/loader.h @@ -0,0 +1,192 @@ +/* + * loader.h + * + * Copyright (C) 2007 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/>. + */ + +#include <stdint.h> + +#ifndef LOADER_H +#define LOADER_H + +#define LOADER_OK 0 +#define LOADER_BACK 1 +#define LOADER_NOOP 2 +#define LOADER_ERROR -1 + +/* #0 unused */ +/* #1 unused */ +#define LOADER_FLAGS_TEXT (((uint64_t) 1) << 2) +#define LOADER_FLAGS_RESCUE (((uint64_t) 1) << 3) +#define LOADER_FLAGS_KICKSTART (((uint64_t) 1) << 4) +#define LOADER_FLAGS_KICKSTART_SEND_MAC (((uint64_t) 1) << 5) +#define LOADER_FLAGS_POWEROFF (((uint64_t) 1) << 6) +#define LOADER_FLAGS_NOPROBE (((uint64_t) 1) << 7) +#define LOADER_FLAGS_MODDISK (((uint64_t) 1) << 8) +#define LOADER_FLAGS_EARLY_NETWORKING (((uint64_t) 1) << 9) +#define LOADER_FLAGS_SERIAL (((uint64_t) 1) << 10) +#define LOADER_FLAGS_UPDATES (((uint64_t) 1) << 11) +#define LOADER_FLAGS_KSFILE (((uint64_t) 1) << 12) +#define LOADER_FLAGS_HALT (((uint64_t) 1) << 13) +#define LOADER_FLAGS_SELINUX (((uint64_t) 1) << 14) +#define LOADER_FLAGS_VIRTPCONSOLE (((uint64_t) 1) << 15) +/* #16 unused */ +#define LOADER_FLAGS_NOSHELL (((uint64_t) 1) << 17) +/* #18 unused */ +#define LOADER_FLAGS_TELNETD (((uint64_t) 1) << 19) +#define LOADER_FLAGS_NOPASS (((uint64_t) 1) << 20) +/* #21 unused */ +#define LOADER_FLAGS_MEDIACHECK (((uint64_t) 1) << 22) +/* #23 unused */ +#define LOADER_FLAGS_ASKMETHOD (((uint64_t) 1) << 24) +#define LOADER_FLAGS_ASKNETWORK (((uint64_t) 1) << 25) +/* #26 unused */ +/* #27 unused */ +#define LOADER_FLAGS_CMDLINE (((uint64_t) 1) << 28) +#define LOADER_FLAGS_GRAPHICAL (((uint64_t) 1) << 29) +#define LOADER_FLAGS_NOIPV4 (((uint64_t) 1) << 31) +#ifdef ENABLE_IPV6 +#define LOADER_FLAGS_NOIPV6 (((uint64_t) 1) << 32) +#endif +#define LOADER_FLAGS_IP_PARAM (((uint64_t) 1) << 33) +#ifdef ENABLE_IPV6 +#define LOADER_FLAGS_IPV6_PARAM (((uint64_t) 1) << 34) +#endif +#define LOADER_FLAGS_IS_KICKSTART (((uint64_t) 1) << 35) +#define LOADER_FLAGS_ALLOW_WIRELESS (((uint64_t) 1) << 36) +#define LOADER_FLAGS_HAVE_CMSCONF (((uint64_t) 1) << 37) +#define LOADER_FLAGS_NOKILL (((uint64_t) 1) << 38) +#define LOADER_FLAGS_KICKSTART_SEND_SERIAL (((uint64_t) 1) << 39) +#define LOADER_FLAGS_AUTOMODDISK (((uint64_t) 1) << 40) + +#define FL_TEXT(a) ((a) & LOADER_FLAGS_TEXT) +#define FL_RESCUE(a) ((a) & LOADER_FLAGS_RESCUE) +#define FL_KICKSTART(a) ((a) & LOADER_FLAGS_KICKSTART) +#define FL_KICKSTART_SEND_MAC(a) ((a) & LOADER_FLAGS_KICKSTART_SEND_MAC) +#define FL_POWEROFF(a) ((a) & LOADER_FLAGS_POWEROFF) +#define FL_NOPROBE(a) ((a) & LOADER_FLAGS_NOPROBE) +#define FL_MODDISK(a) ((a) & LOADER_FLAGS_MODDISK) +#define FL_EARLY_NETWORKING(a) ((a) & LOADER_FLAGS_EARLY_NETWORKING) +#define FL_SERIAL(a) ((a) & LOADER_FLAGS_SERIAL) +#define FL_UPDATES(a) ((a) & LOADER_FLAGS_UPDATES) +#define FL_KSFILE(a) ((a) & LOADER_FLAGS_KSFILE) +#define FL_NOSHELL(a) ((a) & LOADER_FLAGS_NOSHELL) +#define FL_TELNETD(a) ((a) & LOADER_FLAGS_TELNETD) +#define FL_NOPASS(a) ((a) & LOADER_FLAGS_NOPASS) +#define FL_MEDIACHECK(a) ((a) & LOADER_FLAGS_MEDIACHECK) +#define FL_ASKMETHOD(a) ((a) & LOADER_FLAGS_ASKMETHOD) +#define FL_GRAPHICAL(a) ((a) & LOADER_FLAGS_GRAPHICAL) +#define FL_CMDLINE(a) ((a) & LOADER_FLAGS_CMDLINE) +#define FL_HALT(a) ((a) & LOADER_FLAGS_HALT) +#define FL_SELINUX(a) ((a) & LOADER_FLAGS_SELINUX) +#define FL_VIRTPCONSOLE(a) ((a) & LOADER_FLAGS_VIRTPCONSOLE) +#define FL_ASKNETWORK(a) ((a) & LOADER_FLAGS_ASKNETWORK) +#define FL_NOIPV4(a) ((a) & LOADER_FLAGS_NOIPV4) +#ifdef ENABLE_IPV6 +#define FL_NOIPV6(a) ((a) & LOADER_FLAGS_NOIPV6) +#endif +#define FL_IP_PARAM(a) ((a) & LOADER_FLAGS_IP_PARAM) +#ifdef ENABLE_IPV6 +#define FL_IPV6_PARAM(a) ((a) & LOADER_FLAGS_IPV6_PARAM) +#endif +#define FL_IS_KICKSTART(a) ((a) & LOADER_FLAGS_IS_KICKSTART) +#define FL_ALLOW_WIRELESS(a) ((a) & LOADER_FLAGS_ALLOW_WIRELESS) +#define FL_HAVE_CMSCONF(a) ((a) & LOADER_FLAGS_HAVE_CMSCONF) +#define FL_NOKILL(a) ((a) & LOADER_FLAGS_NOKILL) +#define FL_KICKSTART_SEND_SERIAL(a) ((a) & LOADER_FLAGS_KICKSTART_SEND_SERIAL) +#define FL_AUTOMODDISK(a) ((a) & LOADER_FLAGS_AUTOMODDISK) + +void startNewt(void); +void stopNewt(void); +char * getProductName(void); +char * getProductPath(void); +char * getProductArch(void); + +#include "moduleinfo.h" +#include "../isys/devices.h" +/* JKFIXME: I don't like all of the _set attribs, but without them, + * we can't tell if it was explicitly set by kickstart/cmdline or + * if we just got it going through the install. */ +struct loaderData_s { + char * lang; + int lang_set; + char * kbd; + int kbd_set; + char * netDev; + int netDev_set; + char * bootIf; + int bootIf_set; + char * netCls; + int netCls_set; + char *ipv4, *netmask, *gateway, *dns, *hostname, *peerid, *ethtool, *subchannels, *portname, *essid, *wepkey, *nettype, *ctcprot, *layer2, *portno, *macaddr; +#ifdef ENABLE_IPV6 + char *ipv6; + int ipv6info_set; + char *gateway6; +#endif + int mtu; + int noDns; + int dhcpTimeout; + int ipinfo_set; + char * ksFile; + int method; + char * ddsrc; + void * stage2Data; + char * logLevel; + char * updatessrc; + char * dogtailurl; + char * gdbServer; + char * instRepo; + + pid_t fw_loader_pid; + char *fw_search_pathz; + size_t fw_search_pathz_len; + + moduleInfoSet modInfo; + + int inferredStage2, invalidRepoParam; + + /* Proxy info needs to be in the loaderData so we can get these + * settings off the command line, too. + */ + char *proxy; + char *proxyUser; + char *proxyPassword; +}; + +/* 64 bit platforms, definitions courtesy of glib */ +#if defined (__x86_64__) || defined(__ia64__) || defined(__alpha__) || defined(__powerpc64__) || defined(__s390x__) || (defined(__sparc__) && defined(__arch64__)) +#define POINTER_TO_INT(p) ((int) (long) (p)) +#define INT_TO_POINTER(i) ((void *) (long) (i)) +#else +#define POINTER_TO_INT(p) ((int) (p)) +#define INT_TO_POINTER(i) ((void *) (i)) +#endif + +/* library paths */ +#if defined(__x86_64__) || defined(__s390x__) || defined(__powerpc64__) +#define LIBPATH "/lib64:/usr/lib64:/usr/X11R6/lib64:/usr/kerberos/lib64:/mnt/usr/lib64:/mnt/sysimage/lib64:/mnt/sysimage/usr/lib64" +#else +#define LIBPATH "/lib:/usr/lib:/usr/X11R6/lib:/usr/kerberos/lib:/mnt/usr/lib:/mnt/sysimage/lib:/mnt/sysimage/usr/lib" +#endif + +#define checked_asprintf(...) \ + if (asprintf( __VA_ARGS__ ) == -1) { \ + logMessage(CRITICAL, "%s: %d: %m", __func__, __LINE__); \ + abort(); \ + } + +#endif diff --git a/loader/loadermisc.c b/loader/loadermisc.c new file mode 100644 index 0000000..64e80a2 --- /dev/null +++ b/loader/loadermisc.c @@ -0,0 +1,150 @@ +/* + * loadermisc.c - miscellaneous loader functions that don't seem to fit + * anywhere else (yet) (was misc.c) + * JKFIXME: need to break out into reasonable files based on function + * + * Copyright (C) 1999, 2000, 2001, 2002 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> + * Matt Wilson <msw@redhat.com> + * Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#include <stdarg.h> +#include <stdlib.h> + +#include "../isys/log.h" + +#include "windows.h" + +int copyFileFd(int infd, char * dest, progressCB pbcb, + struct progressCBdata *data, long long total) { + int outfd; + char buf[4096]; + int i; + int rc = 0; + long long count = 0; + + outfd = open(dest, O_CREAT | O_RDWR, 0666); + + if (outfd < 0) { + logMessage(ERROR, "failed to open %s: %m", dest); + return 1; + } + + while ((i = read(infd, buf, sizeof(buf))) > 0) { + if (write(outfd, buf, i) != i) { + rc = 1; + break; + } + + count += i; + + if (pbcb && data && total) { + pbcb(data, count, total); + } + } + + close(outfd); + + return rc; +} + +int copyFile(char * source, char * dest) { + int infd = -1; + int rc; + + infd = open(source, O_RDONLY); + + if (infd < 0) { + logMessage(ERROR, "failed to open %s: %m", source); + return 1; + } + + rc = copyFileFd(infd, dest, NULL, NULL, 0); + + close(infd); + + return rc; +} + +int simpleStringCmp(const void * a, const void * b) { + const char * first = *((const char **) a); + const char * second = *((const char **) b); + + return strverscmp(first, second); +} + +/* look for available memory. note: won't ever report more than the + * 900 megs or so supported by the -BOOT kernel due to not using e820 */ +int totalMemory(void) { + int fd; + int bytesRead; + char buf[4096]; + char * chptr, * start; + int total = 0; + + fd = open("/proc/meminfo", O_RDONLY); + if (fd < 0) { + logMessage(ERROR, "failed to open /proc/meminfo: %m"); + return 0; + } + + bytesRead = read(fd, buf, sizeof(buf) - 1); + if (bytesRead < 0) { + logMessage(ERROR, "failed to read from /proc/meminfo: %m"); + close(fd); + return 0; + } + + close(fd); + buf[bytesRead] = '\0'; + + chptr = buf; + while (*chptr && !total) { + if (strncmp(chptr, "MemTotal:", 9)) { + chptr++; + continue; + } + + start = ++chptr ; + while (*chptr && *chptr != '\n') chptr++; + + *chptr = '\0'; + + while (!isdigit(*start) && *start) start++; + if (!*start) { + logMessage(WARNING, "no number appears after MemTotal tag"); + return 0; + } + + chptr = start; + while (*chptr && isdigit(*chptr)) { + total = (total * 10) + (*chptr - '0'); + chptr++; + } + } + + logMessage(INFO, "%d kB are available", total); + + return total; +} diff --git a/loader/loadermisc.h b/loader/loadermisc.h new file mode 100644 index 0000000..23ebf4a --- /dev/null +++ b/loader/loadermisc.h @@ -0,0 +1,33 @@ +/* + * loadermisc.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef H_LOADER_MISC_H +#define H_LOADER_MISC_H +#include <stdio.h> +#include <stdarg.h> + +#include "windows.h" + +int copyFile(char * source, char * dest); +int copyFileFd(int infd, char * dest, progressCB pbcb, + struct progressCBdata *data, long long total); +int simpleStringCmp(const void * a, const void * b); +int totalMemory(void); + +#endif diff --git a/loader/mediacheck.c b/loader/mediacheck.c new file mode 100644 index 0000000..4bec1c8 --- /dev/null +++ b/loader/mediacheck.c @@ -0,0 +1,115 @@ +/* + * simple program to check implanted md5sum in an iso 9660 image + * + * Copyright (C) 2001, 2005 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): Michael Fulbright <msf@redhat.com> + * Dustin Kirkland <dustin.kirkland@gmail.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <newt.h> +#include <libcheckisomd5.h> + +#include "../isys/log.h" + +#include "lang.h" +#include "windows.h" + +int doMediaCheck(char *file, char *descr) { + struct progressCBdata data; + newtComponent t, f, scale, label; + int rc; + int dlen; + int llen; + char tmpstr[1024]; + + if (access(file, R_OK) < 0) { + newtWinMessage(_("Error"), _("OK"), _("Unable to find install image " + "%s"), file); + return -1; + } + + if (descr) + snprintf(tmpstr, sizeof(tmpstr), _("Checking \"%s\"."), descr); + else + snprintf(tmpstr, sizeof(tmpstr), _("Checking media.")); + + dlen = strlen(tmpstr); + if (dlen > 65) + dlen = 65; + + newtCenteredWindow(dlen+8, 6, _("Media Check")); + t = newtTextbox(1, 1, dlen+4, 3, NEWT_TEXTBOX_WRAP); + + newtTextboxSetText(t, tmpstr); + llen = strlen(tmpstr); + + label = newtLabel(llen+1, 1, "-"); + f = newtForm(NULL, NULL, 0); + newtFormAddComponent(f, t); + scale = newtScale(3, 3, dlen, 100); + newtFormAddComponent(f, scale); + + newtDrawForm(f); + newtRefresh(); + + data.scale = scale; + data.label = label; + + rc = mediaCheckFile(file, progressCallback, &data); + + newtFormDestroy(f); + newtPopWindow(); + + if (rc == ISOMD5SUM_CHECK_NOT_FOUND) { + logMessage(WARNING, "mediacheck: %s (%s) has no checksum info", file, descr); + newtWinMessage(_("Error"), _("OK"), + _("Unable to find the checksum in the " + "image. This probably " + "means the disc was created without adding the " + "checksum.")); + } else if (rc == ISOMD5SUM_FILE_NOT_FOUND) { + logMessage(WARNING, "mediacheck: %s (%s) open failed", file, descr); + newtWinMessage(_("Error"), _("OK"), + _("Unable to open the image.")); + } else if (rc == ISOMD5SUM_CHECK_FAILED) { + logMessage(ERROR, "mediacheck: %s (%s) FAILED", file, descr); + newtWinMessage(_("Error"), _("OK"), + _("The image which was just tested has errors. " + "This could be due to a " + "corrupt download or a bad disc. " + "If applicable, please clean the disc " + "and try again. If this test continues to fail you " + "should not continue the install.")); + } else if (rc == ISOMD5SUM_CHECK_PASSED) { + logMessage(INFO, "mediacheck: %s (%s) PASSED", file, descr); + newtWinMessage(_("Success"), _("OK"), + _("The image which was just tested was successfully " + "verified. It should be OK to install from this " + "media. Note that not all media/drive errors can " + "be detected by the media check.")); + } + + + return rc; +} diff --git a/loader/mediacheck.h b/loader/mediacheck.h new file mode 100644 index 0000000..ab2f887 --- /dev/null +++ b/loader/mediacheck.h @@ -0,0 +1,25 @@ +/* + * mediacheck.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef MEDIACHECK_H +#define MEDIACHECK_H + +int doMediaCheck(char *file, char *descr); + +#endif diff --git a/loader/method.c b/loader/method.c new file mode 100644 index 0000000..d6f83e6 --- /dev/null +++ b/loader/method.c @@ -0,0 +1,545 @@ +/* + * method.c - generic install method setup functions + * + * Copyright (C) 2002 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> + * Matt Wilson <msw@redhat.com> + * Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + */ + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <libgen.h> +#include <newt.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <libgen.h> + +#include "copy.h" +#include "loader.h" +#include "loadermisc.h" +#include "lang.h" +#include "mediacheck.h" +#include "method.h" + +#include "../isys/imount.h" +#include "../isys/isys.h" +#include "../isys/cpio.h" +#include "../isys/log.h" + +#include "devt.h" + +#include "nfsinstall.h" +#include "hdinstall.h" +#include "urlinstall.h" + +/* boot flags */ +extern uint64_t flags; + +int umountLoopback(char * mntpoint, char * device) { + int loopfd; + + umount(mntpoint); + + logMessage(INFO, "umounting loopback %s %s", mntpoint, device); + + loopfd = open(device, O_RDONLY); + + if (ioctl(loopfd, LOOP_CLR_FD, 0) == -1) + logMessage(ERROR, "LOOP_CLR_FD failed for %s %s: %m", mntpoint, device); + + close(loopfd); + + return 0; +} + +int mountLoopback(char *fsystem, char *mntpoint, char *device) { + char *opts, *err = NULL; + + if (device == NULL) { + logMessage(ERROR, "no loopback device given"); + return LOADER_ERROR; + } + + if (access(fsystem, F_OK) != 0) { + logMessage(ERROR, "file %s is not accessible", fsystem); + return LOADER_ERROR; + } + + checked_asprintf(&opts, "ro,loop=%s", device); + + if (doPwMount(fsystem, mntpoint, "auto", opts, &err)) { + logMessage(ERROR, "failed to mount loopback device %s on %s as %s: %s", + device, mntpoint, fsystem, err); + return LOADER_ERROR; + } + + logMessage(INFO, "mounted loopback device %s on %s as %s", mntpoint, device, fsystem); + + return 0; +} + +/* returns the *absolute* path (malloced) to the #1 iso image */ +/* get timestamp and description of ISO image from stamp file */ +/* returns 0 on success, -1 otherwise */ +int readStampFileFromIso(char *file, char **timestamp, char **releasedescr) { + DIR * dir; + FILE *f; + struct dirent * ent; + struct stat sb; + char *stampfile; + char *descr, *tstamp; + char tmpstr[1024]; + int filetype; + int rc; + + lstat(file, &sb); + if (S_ISBLK(sb.st_mode)) { + filetype = 1; + if (doPwMount(file, "/tmp/testmnt", "iso9660", "ro", NULL)) { + logMessage(ERROR, "Failed to mount device %s to get description", + file); + return -1; + } + } else if (S_ISREG(sb.st_mode)) { + filetype = 2; + if (mountLoopback(file, "/tmp/testmnt", "/dev/loop6")) { + logMessage(ERROR, "Failed to mount iso %s to get description", + file); + return -1; + } + } else { + logMessage(ERROR, "Unknown type of file %s to get description", + file); + return -1; + } + + if (!(dir = opendir("/tmp/testmnt"))) { + umount("/tmp/testmnt"); + if (filetype == 2) + umountLoopback("/tmp/testmnt", "/dev/loop6"); + return -1; + } + + errno = 0; + stampfile = NULL; + while ((ent = readdir(dir))) { + if (!strncmp(ent->d_name, ".discinfo", 9)) { + stampfile = strdup(".discinfo"); + break; + } + } + + closedir(dir); + descr = NULL; + tstamp = NULL; + if (stampfile) { + snprintf(tmpstr, sizeof(tmpstr), "/tmp/testmnt/%s", stampfile); + f = fopen(tmpstr, "r"); + if (f) { + char *tmpptr; + + /* readtime stamp line */ + tmpptr = fgets(tmpstr, sizeof(tmpstr), f); + + if (tmpptr) + tstamp = strdup(tmpstr); + + /* now read OS description line */ + if (tmpptr) + tmpptr = fgets(tmpstr, sizeof(tmpstr), f); + + if (tmpptr) + descr = strdup(tmpstr); + + /* skip over arch */ + if (tmpptr) + tmpptr = fgets(tmpstr, sizeof(tmpstr), f); + + /* now get the CD number */ + if (tmpptr) { + unsigned int len; + char *p, *newstr; + + tmpptr = fgets(tmpstr, sizeof(tmpstr), f); + + /* nuke newline from end of descr, stick number on end*/ + for (p=descr+strlen(descr); p != descr && !isspace(*p); p--); + + *p = '\0'; + len = strlen(descr) + strlen(tmpstr) + 10; + newstr = malloc(len); + strncpy(newstr, descr, len-1); + strncat(newstr, " ", len-1); + + /* is this a DVD or not? If disc id has commas, like */ + /* "1,2,3", its a DVD */ + if (strchr(tmpstr, ',')) + strncat(newstr, "DVD\n", len-1); + else { + strncat(newstr, "disc ", len-1); + strncat(newstr, tmpstr, len-1); + } + + free(descr); + descr = newstr; + } + + fclose(f); + } + } + + free(stampfile); + + umount("/tmp/testmnt"); + if (filetype == 2) + umountLoopback("/tmp/testmnt", "/dev/loop6"); + + if (descr != NULL && tstamp != NULL) { + descr[strlen(descr)-1] = '\0'; + *releasedescr = descr; + + tstamp[strlen(tstamp)-1] = '\0'; + *timestamp = tstamp; + + rc = 0; + } else { + rc = 1; + } + + return rc; +} + +/* XXX this ignores "location", which should be fixed + * + * Given a starting isoFile, will offer choice to mediacheck it and + * all other ISO images in the same directory with the same stamp + */ +void queryIsoMediaCheck(char *isoFile) { + DIR * dir; + struct dirent * ent; + char *isoDir; + char isoImage[1024]; + char tmpmessage[1024]; + char *master_timestamp; + char *tmpstr; + int rc, first; + + /* dont bother to test in automated installs */ + if (FL_KICKSTART(flags) && !FL_MEDIACHECK(flags)) + return; + + /* if they did not specify to mediacheck explicitely then return */ + if (!FL_MEDIACHECK(flags)) + return; + + /* check that file is actually an iso */ + if (!fileIsIso(isoFile)) + return; + + /* get stamp of isoFile, free descr since we dont care */ + readStampFileFromIso(isoFile, &master_timestamp, &tmpstr); + free(tmpstr); + + /* get base path from isoFile */ + tmpstr = strdup(isoFile); + isoDir = strdup(dirname(tmpstr)); + free(tmpstr); + + logMessage(DEBUGLVL, "isoFile = %s", isoFile); + logMessage(DEBUGLVL, "isoDir = %s", isoDir); + logMessage(DEBUGLVL, "Master Timestemp = %s", master_timestamp); + + if (!(dir = opendir(isoDir))) { + newtWinMessage(_("Error"), _("OK"), + _("Failed to read directory %s: %m"), + isoDir); + free(isoDir); + free(master_timestamp); + return; + } + + /* Walk through the directories looking for a CD images. */ + errno = 0; + first = 0; + while (1) { + char *nextname; + char *tdescr, *tstamp; + + if (first) { + first = 1; + nextname = isoFile; + } else { + ent = readdir(dir); + if (!ent) + break; + + nextname = ent->d_name; + } + + /* synthesize name of iso from isoDir and file entry */ + snprintf(isoImage, sizeof(isoImage), "%s/%s", isoDir, nextname); + + /* see if this is an iso image */ + if (!fileIsIso(isoImage)) { + errno = 0; + continue; + } + + /* see if its part of the current CD set */ + readStampFileFromIso(isoImage, &tstamp, &tdescr); + if (strcmp(tstamp, master_timestamp)) { + errno = 0; + continue; + } + + /* found a valid candidate, proceed */ + snprintf(tmpmessage, sizeof(tmpmessage), + _("Would you like to perform a checksum " + "test of the ISO image:\n\n %s?"), isoImage); + + rc = newtWinChoice(_("Checksum Test"), _("Test"), _("Skip"), + tmpmessage); + + if (rc == 2) { + logMessage(INFO, "mediacheck: skipped checking of %s", isoImage); + if (tdescr) + free(tdescr); + continue; + } else { + doMediaCheck(isoImage, tdescr); + if (tdescr) + free(tdescr); + + continue; + } + } + + free(isoDir); + free(master_timestamp); + closedir(dir); +} + +static void copyWarnFn (char *msg) { + logMessage(WARNING, msg); +} + +static void copyErrorFn (char *msg) { + newtWinMessage(_("Error"), _("OK"), _(msg)); +} + +/* + * unpack a gzipped cpio ball into a tree rooted at rootDir + * returns 0 on success, 1 on failure + */ +int unpackCpioBall(char * ballPath, char * rootDir) { + gzFile fd; + char *buf, *cwd; + int rc = 1; + + if (access(ballPath, R_OK)) + return 1; + + if (access(rootDir, R_OK)) + mkdirChain(rootDir); + + buf = (char *)malloc(PATH_MAX); + cwd = getcwd(buf, PATH_MAX); + if ((rc = chdir(rootDir)) == 0) { + fd = gunzip_open(ballPath); + if (fd) { + if (!installCpioFile(fd, NULL, NULL, 0)) { + logMessage(INFO, "copied contents of %s into %s", ballPath, + rootDir); + rc = chdir(cwd); + return 0; + } + gunzip_close(fd); + } + rc = chdir(cwd); + } + + return 1; +} + +void copyUpdatesImg(char * path) { + if (!access(path, R_OK)) { + if (!mountLoopback(path, "/tmp/update-disk", "/dev/loop7")) { + copyDirectory("/tmp/update-disk", "/tmp/updates", copyWarnFn, + copyErrorFn); + umountLoopback("/tmp/update-disk", "/dev/loop7"); + unlink("/tmp/update-disk"); + } else { + unpackCpioBall(path, "/tmp/updates"); + } + } +} + +void copyProductImg(char * path) { + if (!access(path, R_OK)) { + if (!mountLoopback(path, "/tmp/product-disk", "/dev/loop7")) { + copyDirectory("/tmp/product-disk", "/tmp/product", copyWarnFn, + copyErrorFn); + umountLoopback("/tmp/product-disk", "/dev/loop7"); + unlink("/tmp/product-disk"); + } + } +} + +/* unmount a second stage, if mounted. Used for CDs and mediacheck mostly, + so we can eject CDs. */ +void umountStage2(void) { + umountLoopback("/mnt/runtime", "/dev/loop0"); +} + +/* mount a second stage, verify the stamp file, copy updates + * Returns 0 on success, 1 on failure to mount, -1 on bad stamp */ +int mountStage2(char *stage2path) { + if (access(stage2path, R_OK)) { + return 1; + } + + if (mountLoopback(stage2path, "/mnt/runtime", "/dev/loop0")) { + return 1; + } + + return 0; +} + + +/* copies a second stage from fd to dest and mounts on mntpoint */ +int copyFileAndLoopbackMount(int fd, char * dest, char * device, char * mntpoint, + progressCB pbcb, struct progressCBdata *data, + long long total) { + int rc; + struct stat sb; + + rc = copyFileFd(fd, dest, pbcb, data, total); + stat(dest, &sb); + logMessage(DEBUGLVL, "copied %" PRId64 " bytes to %s (%s)", sb.st_size, dest, + ((rc) ? " incomplete" : "complete")); + + if (rc) { + /* just to make sure */ + unlink(dest); + return 1; + } + + if (mountLoopback(dest, mntpoint, device)) { + /* JKFIXME: this used to be fatal, but that seems unfriendly */ + logMessage(ERROR, "Error mounting %s on %s: %m", device, mntpoint); + return 1; + } + + return 0; +} + +/* given a device name (w/o '/dev' on it), try to get a file */ +/* Error codes: + 1 - could not create device node + 2 - could not mount device as ext2, vfat, or iso9660 + 3 - file named path not there +*/ +int getFileFromBlockDevice(char *device, char *path, char * dest) { + int rc, i; + char file[4096]; + + logMessage(INFO, "getFileFromBlockDevice(%s, %s)", device, path); + + /* some USB thumb drives and hard drives are slow to initialize */ + /* retry up to 5 times or 31 seconds */ + rc = doPwMount(device, "/tmp/mnt", "auto", "ro", NULL); + for (i = 0; mountMightSucceedLater(rc) && i < 5; ++i) { + logMessage(INFO, "sleeping to wait for USB storage devices"); + sleep(1 << i); + rc = doPwMount(device, "/tmp/mnt", "auto", "ro", NULL); + logMessage(ERROR, "error code: %d", rc); + } + if (rc) { + logMessage(ERROR, "failed to mount /dev/%s: %m", device); + return 2; + } + + snprintf(file, sizeof(file), "/tmp/mnt/%s", path); + logMessage(INFO, "Searching for file on path %s", file); + + if (access(file, R_OK)) { + rc = 3; + } else { + copyFile(file, dest); + rc = 0; + logMessage(INFO, "file copied to %s", dest); + } + + umount("/tmp/mnt"); + unlink("/tmp/mnt"); + return rc; +} + +void setStage2LocFromCmdline(char * arg, struct loaderData_s * ld) { + if (!strncmp(arg, "nfs:", 4)) { + ld->method = METHOD_NFS; + ld->stage2Data = calloc(sizeof(struct nfsInstallData *), 1); + + parseNfsHostPathOpts(arg + 4, + &(((struct nfsInstallData *)ld->stage2Data)->host), + &(((struct nfsInstallData *)ld->stage2Data)->directory), + &(((struct nfsInstallData *)ld->stage2Data)->mountOpts)); + } else if (!strncmp(arg, "nfsiso:", 7)) { + ld->method = METHOD_NFS; + ld->stage2Data = calloc(sizeof(struct nfsInstallData *), 1); + + parseNfsHostPathOpts(arg + 7, + &(((struct nfsInstallData *)ld->stage2Data)->host), + &(((struct nfsInstallData *)ld->stage2Data)->directory), + &(((struct nfsInstallData *)ld->stage2Data)->mountOpts)); + } else if (!strncmp(arg, "ftp:", 4) || + !strncmp(arg, "http", 4)) { + ld->method = METHOD_URL; + ld->stage2Data = calloc(sizeof(struct urlInstallData *), 1); + ((urlInstallData *)ld->stage2Data)->url = strdup(arg); + } else if (!strncmp(arg, "cdrom:", 6)) { + ld->method = METHOD_CDROM; + } else if (!strncmp(arg, "harddrive:", 10) || + !strncmp(arg, "hd:", 3)) { + size_t offset; + + arg += strcspn(arg, ":"); + if (!*arg || !*(arg+1)) + return; + arg += 1; + offset = strcspn(arg, ":"); + + ld->method = METHOD_HD; + ld->stage2Data = calloc(sizeof(struct hdInstallData *), 1); + ((struct hdInstallData *)ld->stage2Data)->partition = strndup(arg, offset); + arg += offset; + if (*arg && *(arg+1)) + ((struct hdInstallData *)ld->stage2Data)->directory = strdup(arg+1); + else + ((struct hdInstallData *)ld->stage2Data)->directory = NULL; + } +} diff --git a/loader/method.h b/loader/method.h new file mode 100644 index 0000000..1b5e2d3 --- /dev/null +++ b/loader/method.h @@ -0,0 +1,60 @@ +/* + * method.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef H_METHOD +#define H_METHOD + +#include "loader.h" +#include "windows.h" + +/* method identifiers, needs to match struct installMethod order in loader.c */ +enum { + METHOD_CDROM, + METHOD_HD, + METHOD_NFS, + METHOD_URL +}; + +struct installMethod { + char * name; + int network; + enum deviceType type; + char * (*mountImage)(struct installMethod * method, + char * location, struct loaderData_s * loaderData); +}; + +int umountLoopback(char * mntpoint, char * device); +int mountLoopback(char * fsystem, char * mntpoint, char * device); + +int readStampFileFromIso(char *file, char **descr, char **timestamp); +void queryIsoMediaCheck(char * isoDir); + +void umountStage2(void); +int mountStage2(char *stage2path); +int copyFileAndLoopbackMount(int fd, char *dest, char *device, char *mntpoint, + progressCB pbcb, struct progressCBdata *data, long long total); +int getFileFromBlockDevice(char *device, char *path, char * dest); + +int unpackCpioBall(char * ballPath, char * rootDir); +void copyUpdatesImg(char * path); +void copyProductImg(char * path); + +void setStage2LocFromCmdline(char * arg, struct loaderData_s * ld); + +#endif diff --git a/loader/mkctype.c b/loader/mkctype.c new file mode 100644 index 0000000..12eaba0 --- /dev/null +++ b/loader/mkctype.c @@ -0,0 +1,76 @@ +/* + * mkctype.c + * + * Copyright (C) 2007 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/>. + */ + +#include <ctype.h> +#include <stdio.h> + +#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ > 2) +# define __ctype_b (*__ctype_b_loc()) +# define __ctype_tolower (*__ctype_tolower_loc()) +# define __ctype_toupper (*__ctype_toupper_loc()) +#endif + +int main(int argc, char ** argv) { + int i; + + printf("#include <sys/types.h>\n\n"); + + printf("static const unsigned short int __ctype_b_internal[] = {"); + + for (i = -128; i < 256; i++) { + if (!(i % 8)) { + printf("\n"); + } + + printf("\t0x%x,", __ctype_b[i]); + } + + printf("\n};\n\n"); + printf("const unsigned short int * __ctype_b = __ctype_b_internal + 128;\n\n"); + + printf("const int __ctype_toupper_internal[] = {"); + for (i = -128; i < 256; i++) { + if (!(i % 8)) { + printf("\n"); + } + + printf("\t0x%x,", __ctype_toupper[i]); + } + + printf("\n};\n\n"); + printf("const int * __ctype_toupper = __ctype_toupper_internal + 128;\n\n"); + + printf("const int __ctype_tolower_internal[] = {"); + for (i = -128; i < 256; i++) { + if (!(i % 8)) { + printf("\n"); + } + + printf("\t0x%x,", __ctype_tolower[i]); + } + + printf("\n};\n\n"); + printf("const int * __ctype_tolower = __ctype_tolower_internal + 128;\n\n"); + + printf ("const unsigned short int **__ctype_b_loc (void) { return &__ctype_b; }\n"); + printf ("const int **__ctype_toupper_loc (void) { return &__ctype_toupper; }\n"); + printf ("const int **__ctype_tolower_loc (void) { return &__ctype_tolower; }\n\n"); + + return 0; +}; diff --git a/loader/moduleinfo.c b/loader/moduleinfo.c new file mode 100644 index 0000000..2e0ab77 --- /dev/null +++ b/loader/moduleinfo.c @@ -0,0 +1,276 @@ +/* + * moduleinfo.c - module info functionality + * + * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 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> + * Matt Wilson <msw@redhat.com> + */ + +#include <alloca.h> +#include <ctype.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <stdio.h> + +#include "moduleinfo.h" + +struct moduleInfo * getModuleList(moduleInfoSet mis, + enum driverMajor major) { + struct moduleInfo * miList, * next; + int i; + + next = miList = malloc(sizeof(*miList) * mis->numModules + 1); + for (i = 0; i < mis->numModules; i++) { + if (mis->moduleList[i].major == major || major == DRIVER_NONE) { + *next = mis->moduleList[i]; + next++; + } + } + + if (next == miList) { + free(next); + return NULL; + } + + next->moduleName = NULL; + next++; + + miList = realloc(miList, sizeof(*miList) * (next - miList)); + return miList; +} + +struct moduleInfo * findModuleInfo(moduleInfoSet mis, + const char * moduleName) { + int i; + struct moduleInfo * found = NULL; + + for (i = 0; i < mis->numModules; i++) { + if (!strcmp(moduleName, mis->moduleList[i].moduleName)) { + if (!found) + found = mis->moduleList + i; + else if (found->locationID && !mis->moduleList[i].locationID) + ; + else + found = mis->moduleList + i; + } + } + + return found; +} + +moduleInfoSet newModuleInfoSet(void) { + return calloc(sizeof(struct moduleInfoSet_s), 1); +} + +/* filename: file to read module-info from + * mis: moduleInfoSet + * location: moduleBallLocation struct describing the location of + * these modules. (may be NULL) + * override: 1 if modules from this module ball should override old ones + * of the same name. + */ +int readModuleInfo(const char * filename, moduleInfoSet mis, + void * location, int override) { + int fd, isIndented; + char * buf, * start, * next = NULL, * chptr; + struct stat sb; + char oldch; + struct moduleInfo * nextModule; + int modulesAlloced; + int i; + int found = 0, skipModule = 0; + + fd = open(filename, O_RDONLY); + if (fd < 0) return -1; + + fstat(fd, &sb); + buf = alloca(sb.st_size + 1); + i = read(fd, buf, sb.st_size); + buf[sb.st_size] = '\0'; + close(fd); + + if (i != sb.st_size) + return -1; + + nextModule = NULL; + modulesAlloced = mis->numModules; + + if (strncmp(buf, "Version 0\n", 10)) return -1; + + start = buf + 10; + while (start && *start) { + chptr = strchr(start, '\n'); + if (chptr) { + /* slice and dice */ + next = chptr + 1; + } else { + chptr += strlen(start) - 1; + } + + chptr--; + while (isspace(*chptr)) chptr--; + chptr++; + *chptr = '\0'; + + isIndented = 0; + if (isspace(*start)) { + while (isspace(*start) && *start != '\n') start++; + isIndented = 1; + } + + if (*start != '\n' && *start && *start != '#') { + if (!isIndented) { + if (nextModule && nextModule->moduleName && + nextModule == (mis->moduleList + mis->numModules)) { + mis->numModules++; + } + + if (mis->numModules == modulesAlloced) { + modulesAlloced += 5; + mis->moduleList = realloc(mis->moduleList, + modulesAlloced * sizeof(*mis->moduleList)); + } + + nextModule = NULL; + found = 0; + skipModule = 0; + for (i = 0; i < mis->numModules; i++) { + if (!strcmp(mis->moduleList[i].moduleName, start)) { + if (override) + nextModule = mis->moduleList + i; + else + skipModule = 1; + found = 1; + break; + } + } + + if (!found && !nextModule) { + nextModule = mis->moduleList + mis->numModules; + + nextModule->moduleName = strdup(start); + } + + if (nextModule) { + nextModule->major = DRIVER_NONE; + nextModule->minor = DRIVER_MINOR_NONE; + nextModule->description = NULL; + nextModule->flags = 0; + nextModule->args = NULL; + nextModule->numArgs = 0; + nextModule->locationID = location; + } + } else if (!nextModule && skipModule) { + /* we're skipping this one (not overriding), do nothing */ + } else if (!nextModule && skipModule) { + /* ACK! syntax error */ + fprintf(stderr, "module-info syntax error in %s\n", filename); + return 1; + } else if (nextModule->major == DRIVER_NONE) { + chptr = start + strlen(start) - 1; + while (!isspace(*chptr) && chptr > start) chptr--; + if (chptr != start) chptr++; + + if (!strcmp(chptr, "eth")) { + nextModule->major = DRIVER_NET; + nextModule->minor = DRIVER_MINOR_ETHERNET; + } else if (!strcmp(chptr, "tr")) { + nextModule->major = DRIVER_NET; + nextModule->minor = DRIVER_MINOR_TR; + } else if (!strcmp(chptr, "scsi_hostadapter") || + !strcmp(chptr, "scsi")) { + nextModule->major = DRIVER_SCSI; + } else if (!strcmp(chptr, "pcmcia")) { + nextModule->major = DRIVER_PCMCIA; + } else if (!strcmp(chptr, "fs")) { + nextModule->major = DRIVER_FS; + } else if (!strcmp(chptr, "cdrom")) { + nextModule->major = DRIVER_CDROM; + } else if (!strcmp(chptr, "ide")) { + nextModule->major = DRIVER_IDE; + } else { + nextModule->major = DRIVER_OTHER; + } + } else if (!nextModule->description) { + chptr = start + strlen(start) - 1; + if (*start == '"' && *chptr == '"') { + start++; + *chptr = '\0'; + nextModule->description = strdup(start); + } + } else { + nextModule->args = realloc(nextModule->args, + sizeof(*nextModule->args) * (nextModule->numArgs + 1)); + chptr = start; + while (!isspace(*chptr) && *chptr) chptr++; + if (*chptr) { + oldch = *chptr; + *chptr = '\0'; + nextModule->args[nextModule->numArgs].arg = strdup(start); + + start = chptr + 1; + while (*start && isspace(*start)) start++; + + if (*start == '"') { + start++; + chptr = strchr(start, '"'); + if (chptr) { + *chptr = '\0'; + nextModule->args[nextModule->numArgs].description = + strdup(start); + nextModule->numArgs++; + } + } + } + } + } + + start = next; + } + + /* do we need to add in this last module? */ + if (nextModule && ((nextModule - mis->moduleList) == mis->numModules)) + mis->numModules++; + + return 0; +} + +void freeModuleInfoSet(moduleInfoSet mis) { + int i, j; + + for (i = 0; i < mis->numModules; i++) { + if (mis->moduleList[i].moduleName) + free(mis->moduleList[i].moduleName); + + if (mis->moduleList[i].description) + free(mis->moduleList[i].description); + + for (j = 0; i < mis->moduleList[i].numArgs; j++) { + if (mis->moduleList[i].args[j].arg) + free(mis->moduleList[i].args[j].arg) ; + if (mis->moduleList[i].args[j].description) + free(mis->moduleList[i].args[j].description) ; + } + } + + free(mis); +} diff --git a/loader/moduleinfo.h b/loader/moduleinfo.h new file mode 100644 index 0000000..72f6d71 --- /dev/null +++ b/loader/moduleinfo.h @@ -0,0 +1,78 @@ +/* + * moduleinfo.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef MODULEINFO_H +#define MODULEINFO_H + +enum driverMajor { DRIVER_NONE = 0, DRIVER_SCSI, DRIVER_NET, DRIVER_CDROM, + DRIVER_PCMCIA, DRIVER_FS, DRIVER_IDE, DRIVER_OTHER = 1000, + DRIVER_ANY = 5000 }; +enum driverMinor { DRIVER_MINOR_NONE = 0, DRIVER_MINOR_ETHERNET, + DRIVER_MINOR_TR }; + +struct moduleArg { + char * arg; + char * description; +}; + +#define MI_FLAG_NOMISCARGS (1 << 0) + +struct moduleInfo { + char * moduleName; + char * description; + enum driverMajor major; + enum driverMinor minor; + int numArgs; + struct moduleArg * args; + int flags; + void * locationID; +}; + +struct moduleInfoSet_s { + struct moduleInfo * moduleList; + int numModules; +}; + +struct moduleBallLocation { + char * path; /* path to module ball that this driver is from. if NULL, + * implies /modules/modules.cgz */ + char * title; /* title used for driver disk -- may be NULL */ + int version; /* module ball version, used to determine layout */ +}; +#define CURRENT_MODBALLVER 1 + +/* valid moduleball versions + * 0: old single-arch module ball, modules are in uname.release + * 1: multi-arch, modules are in uname.release/arch + */ + +typedef struct moduleInfoSet_s * moduleInfoSet; + +moduleInfoSet newModuleInfoSet(void); +void freeModuleInfoSet(moduleInfoSet mis); +int readModuleInfo(const char * filename, moduleInfoSet mis, void * path, int override); +struct moduleInfo * findModuleInfo(moduleInfoSet mis, + const char * moduleName); + +/* NULL moduleName indicates the end of the list; the list must be freed() */ +struct moduleInfo * getModuleList(moduleInfoSet mis, + enum driverMajor major); + + +#endif diff --git a/loader/modules.c b/loader/modules.c new file mode 100644 index 0000000..0944b97 --- /dev/null +++ b/loader/modules.c @@ -0,0 +1,411 @@ +/* + * modules.c - module loading functionality + * + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, + * 2008, 2009 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> + * Matt Wilson <msw@redhat.com> + * Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + * David Cantrell <dcantrell@redhat.com> + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <newt.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/utsname.h> +#include <sys/wait.h> +#include <unistd.h> +#include <glib.h> + +#include "../isys/log.h" + +#include "loader.h" +#include "modules.h" +#include "windows.h" + +/* boot flags */ +extern uint64_t flags; + +static GSList *modopts = NULL; +static GSList *blacklist = NULL; + +static gboolean _isValidModule(gchar *module) { + gint fd = -1, i = 0; + gchar *path = NULL, *buf = NULL, *modname = NULL; + gchar *ends[] = { ".ko.gz:", ".ko:", NULL }; + struct utsname utsbuf; + struct stat sbuf; + + if (uname(&utsbuf) == -1) { + logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); + return FALSE; + } + + if (asprintf(&path, "/lib/modules/%s/modules.dep", utsbuf.release) == -1) { + logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); + return FALSE; + } + + if (stat(path, &sbuf) == -1) { + logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); + free(path); + return FALSE; + } + + if ((fd = open(path, O_RDONLY)) == -1) { + logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); + free(path); + return FALSE; + } else { + free(path); + } + + buf = mmap(0, sbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (!buf || buf == MAP_FAILED) { + close(fd); + return FALSE; + } + + close(fd); + + while (ends[i] != NULL) { + if (asprintf(&modname, "/%s%s", module, ends[i]) == -1) { + logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); + return FALSE; + } + + if (g_strstr_len(buf, -1, modname) != NULL) { + munmap(buf, sbuf.st_size); + free(modname); + return TRUE; + } + + free(modname); + modname = NULL; + i++; + } + + munmap(buf, sbuf.st_size); + return FALSE; +} + +static void _addOption(const gchar *module, const gchar *option) { + gboolean found = FALSE; + GSList *iterator = modopts; + module_t *modopt = NULL; + gchar *tmpopt = g_strdup(option); + + while (iterator != NULL) { + modopt = (module_t *) iterator->data; + + if (!strncmp(modopt->name, module, strlen(modopt->name))) { + found = TRUE; + break; + } + + iterator = g_slist_next(iterator); + } + + if (found) { + modopt->options = g_slist_append(modopt->options, tmpopt); + } else { + if ((modopt = g_malloc0(sizeof(module_t))) == NULL) { + logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); + abort(); + } + + modopt->name = g_strdup(module); + modopt->options = NULL; + modopt->options = g_slist_append(modopt->options, tmpopt); + modopts = g_slist_append(modopts, modopt); + } + + return; +} + +static gboolean _writeModulesConf(gchar *conf) { + gint fd = -1, rc = 0, len = 0; + GSList *iterator = modopts; + GString *buf = g_string_new("# Module options and blacklists written by anaconda\n"); + + if (conf == NULL) { + /* XXX: should this use mkstemp() ? */ + conf = "/tmp/modprobe.conf"; + } + + if ((fd = open(conf, O_WRONLY | O_CREAT, 0644)) == -1) { + logMessage(ERROR, "error opening to %s: %m", conf); + return FALSE; + } + + while (iterator != NULL) { + module_t *modopt = iterator->data; + GSList *optiterator = modopt->options; + g_string_append_printf(buf, "options %s", modopt->name); + + while (optiterator != NULL) { + gchar *option = (gchar *) optiterator->data; + g_string_append_printf(buf, " %s", option); + optiterator = g_slist_next(optiterator); + } + + g_string_append(buf, "\n"); + iterator = g_slist_next(iterator); + } + + iterator = blacklist; + + while (iterator != NULL) { + gchar *module = (gchar *) iterator->data; + g_string_append_printf(buf, "blacklist %s\n", module); + iterator = g_slist_next(iterator); + } + + len = buf->len; + rc = write(fd, buf->str, len); + close(fd); + g_string_free(buf, TRUE); + + return (rc == len); +} + +static gboolean _doLoadModule(const gchar *module, gchar **args) { + gint child; + gint status; + + if (!(child = fork())) { + gint i, rc; + gchar **argv = NULL; + gint fd = -1; + + if ((argv = g_malloc0(3 * sizeof(*argv))) == NULL) { + logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); + abort(); + } + + if ((fd = open("/dev/tty3", O_RDWR)) == -1) { + logMessage(ERROR, "%s (%d): %m", __func__, __LINE__); + } else { + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + close(fd); + } + + argv[0] = "/sbin/modprobe"; + argv[1] = g_strdup(module); + argv[2] = NULL; + + if (args) { + for (i = 0; args[i] ; i++) { + _addOption(module, args[i]); + } + _writeModulesConf(MODULES_CONF); + } + + rc = execv("/sbin/modprobe", argv); + g_strfreev(argv); + _exit(rc); + } + + waitpid(child, &status, 0); + + if (!WIFEXITED(status) || (WIFEXITED(status) && WEXITSTATUS(status))) { + return TRUE; + } else { + return FALSE; + } +} + +gboolean mlInitModuleConfig(void) { + gint i = 0; + gchar *cmdline = NULL; + gchar **options = NULL; + GError *readErr = NULL; + + /* read module options out of /proc/cmdline and into a structure */ + if (!g_file_get_contents("/proc/cmdline", &cmdline, NULL, &readErr)) { + logMessage(ERROR, "unable to read /proc/cmdline: %s", readErr->message); + g_error_free(readErr); + return _writeModulesConf(MODULES_CONF); + } + + cmdline = g_strchomp(cmdline); + options = g_strsplit(cmdline, " ", 0); + g_free(cmdline); + + if (options == NULL) { + return _writeModulesConf(MODULES_CONF); + } + + while (options[i] != NULL) { + gchar *tmpmod = NULL; + gchar **fields = NULL; + + if (g_strstr_len(options[i], -1, "=") == NULL) { + i++; + continue; + } + + if (!strncmp(options[i], "blacklist=", 10)) { + if ((fields = g_strsplit(options[i], "=", 0)) != NULL) { + if (g_strv_length(fields) == 2) { + tmpmod = g_strdup(fields[1]); + blacklist = g_slist_append(blacklist, tmpmod); + } + } + } else if ((fields = g_strsplit(options[i], ".", 0)) != NULL) { + if (g_strv_length(fields) == 2) { + if (_isValidModule(fields[0])) { + _addOption(fields[0], fields[1]); + } + } + } + + if (fields != NULL) { + g_strfreev(fields); + } + + i++; + } + + if (options != NULL) { + g_strfreev(options); + } + + return _writeModulesConf(MODULES_CONF); +} + +/* load a module with a given list of arguments */ +gboolean mlLoadModule(const gchar *module, gchar **args) { + return _doLoadModule(module, args); +} + +/* loads a : separated list of modules */ +gboolean mlLoadModuleSet(const gchar *modNames) { + gchar **mods = NULL, **iterator = NULL; + gboolean rc = FALSE; + + if (modNames == NULL) { + return FALSE; + } + + if ((mods = g_strsplit(modNames, ":", 0)) != NULL) { + iterator = mods; + + while (*iterator != NULL) { + rc |= _doLoadModule(*iterator, NULL); + iterator++; + } + } else { + return FALSE; + } + + g_strfreev(mods); + return rc; +} + +gboolean mlAddBlacklist(gchar *module) { + gchar *tmpmod = NULL; + + if (module == NULL) { + return FALSE; + } + + tmpmod = g_strdup(module); + blacklist = g_slist_append(blacklist, tmpmod); + return _writeModulesConf(MODULES_CONF); +} + +gboolean mlRemoveBlacklist(gchar *module) { + GSList *iterator = blacklist; + + if (module == NULL) { + return FALSE; + } + + while (iterator != NULL) { + if (!strcmp((gchar *) iterator->data, module)) { + iterator = g_slist_delete_link(blacklist, iterator); + continue; + } else { + iterator = g_slist_next(iterator); + } + } + + return TRUE; +} + +void loadKickstartModule(struct loaderData_s * loaderData, + int argc, char **argv) { + gchar *opts = NULL; + gchar *module = NULL; + gchar **args = NULL, **remaining = NULL; + gboolean rc; + GOptionContext *optCon = g_option_context_new(NULL); + GError *optErr = NULL; + GOptionEntry ksDeviceOptions[] = { + { "opts", 0, 0, G_OPTION_ARG_STRING, &opts, NULL, NULL }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &remaining, + NULL, NULL }, + { NULL }, + }; + + g_option_context_set_help_enabled(optCon, FALSE); + g_option_context_add_main_entries(optCon, ksDeviceOptions, NULL); + + if (!g_option_context_parse(optCon, &argc, &argv, &optErr)) { + startNewt(); + newtWinMessage(_("Kickstart Error"), _("OK"), + _("Bad argument to device kickstart method " + "command: %s"), optErr->message); + g_error_free(optErr); + g_option_context_free(optCon); + return; + } + + g_option_context_free(optCon); + + if ((remaining != NULL) && (g_strv_length(remaining) == 1)) { + module = remaining[0]; + } + + if (!module) { + startNewt(); + newtWinMessage(_("Kickstart Error"), _("OK"), + _("A module name must be specified for " + "the kickstart device command.")); + return; + } + + if (opts) { + args = g_strsplit(opts, " ", 0); + } + + rc = mlLoadModule(module, args); + g_strfreev(args); + return; +} diff --git a/loader/modules.h b/loader/modules.h new file mode 100644 index 0000000..88fa25f --- /dev/null +++ b/loader/modules.h @@ -0,0 +1,43 @@ +/* + * modules.h + * + * Copyright (C) 2007, 2009 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): David Cantrell <dcantrell@redhat.com> + */ + +#ifndef H_MODULES +#define H_MODULES + +#include <glib.h> +#include "loader.h" +#include "moduleinfo.h" + +#define MODULES_CONF "/etc/modprobe.d/anaconda.conf" + +typedef struct _module_t { + gchar *name; + GSList *options; +} module_t; + +gboolean mlInitModuleConfig(void); +gboolean mlLoadModule(const gchar *, gchar **); +gboolean mlLoadModuleSet(const gchar *); +gboolean mlAddBlacklist(gchar *); +gboolean mlRemoveBlacklist(gchar *); +void loadKickstartModule(struct loaderData_s *, int, char **); + +#endif diff --git a/loader/net.c b/loader/net.c new file mode 100644 index 0000000..eff4782 --- /dev/null +++ b/loader/net.c @@ -0,0 +1,2112 @@ +/* + * net.c + * + * Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 Red Hat, Inc. + * 2006, 2007, 2008, 2009 + * + * 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): David Cantrell <dcantrell@redhat.com> + */ + +#include <netdb.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/utsname.h> +#include <arpa/inet.h> +#include <errno.h> +#include <resolv.h> +#include <net/if.h> +#include <newt.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> + +#include <glib.h> +#include <NetworkManager.h> +#include <nm-client.h> + +#include "../isys/isys.h" +#include "../isys/ethtool.h" +#include "../isys/iface.h" +#include "../isys/log.h" + +#include "lang.h" +#include "loader.h" +#include "loadermisc.h" +#include "method.h" +#include "net.h" +#include "windows.h" +#include "ibft.h" + +/* boot flags */ +extern uint64_t flags; + +/** + * Callback function for the CIDR entry boxes on the manual TCP/IP + * configuration window. + * + * @param co The entry field that triggered the callback. + * @param dptr Pointer to intfconfig_s data structure for this field. + * @see intfconfig_s + */ +static void cidrCallback(newtComponent co, void * dptr) { + struct intfconfig_s * data = dptr; + int cidr, upper = 0; + struct in_addr addr; + + if (co == data->cidr4Entry) { + if (data->cidr4 == NULL && data->ipv4 == NULL) + return; + + if (inet_pton(AF_INET, data->cidr4, &addr) >= 1) + return; + + errno = 0; + cidr = strtol(data->cidr4, NULL, 10); + if ((errno == ERANGE && (cidr == LONG_MIN || cidr == LONG_MAX)) || + (errno != 0 && cidr == 0)) { + logMessage(ERROR, "%s: %d: %m", __func__, __LINE__); + abort(); + } + + if (strcmp(data->ipv4, "")) + upper = 32; +#ifdef ENABLE_IPV6 + } else if (co == data->cidr6Entry) { + if (data->cidr6 == NULL && data->ipv6 == NULL) + return; + + errno = 0; + cidr = strtol(data->cidr6, NULL, 10); + if ((errno == ERANGE && (cidr == LONG_MIN || cidr == LONG_MAX)) || + (errno != 0 && cidr == 0)) { + logMessage(ERROR, "%s: %d: %m", __func__, __LINE__); + abort(); + } + + if (strcmp(data->ipv6, "")) + upper = 128; +#endif + } + + if (upper != 0) { + if (cidr < 1 || cidr > upper) { + newtWinMessage(_("Invalid Prefix"), _("Retry"), + _("Prefix must be between 1 and 32 " + "for IPv4 networks or between 1 and 128 " + "for IPv6 networks")); + } + } +} + +static void ipCallback(newtComponent co, void * dptr) { + int i; + char *buf, *octet; + struct intfconfig_s * data = dptr; + + if (co == data->ipv4Entry) { + /* do we need to guess a netmask for the user? */ + if (data->cidr4 == NULL && data->ipv4 != NULL) { + buf = strdup(data->ipv4); + octet = strtok(buf, "."); + errno = 0; + i = strtol(octet, NULL, 10); + + if ((errno == ERANGE && (i == LONG_MIN || i == LONG_MAX)) || + (errno != 0 && i == 0)) { + logMessage(ERROR, "%s: %d: %m", __func__, __LINE__); + abort(); + } + + free(buf); + free(octet); + + if (i >= 0 && i <= 127) + newtEntrySet(data->cidr4Entry, "8", 1); + else if (i >= 128 && i <= 191) + newtEntrySet(data->cidr4Entry, "16", 1); + else if (i >= 192 && i <= 222) + newtEntrySet(data->cidr4Entry, "24", 1); + } + + return; +#ifdef ENABLE_IPV6 + } else if (co == data->ipv6Entry) { + /* users must provide a mask, we can't guess for ipv6 */ + return; +#endif + } +} + +static void setMethodSensitivity(void *dptr, int radio_button_count) { + int i = 0; + + for (i = 0; i < radio_button_count; i++) { + newtCheckboxSetFlags(*((newtComponent *) dptr), NEWT_FLAG_DISABLED, + NEWT_FLAGS_TOGGLE); + dptr += sizeof (newtComponent); + } + + return; +} + +static void v4MethodCallback(newtComponent co, void *dptr) { + setMethodSensitivity(dptr, 2); + return; +} + +#ifdef ENABLE_IPV6 +static void v6MethodCallback(newtComponent co, void *dptr) { + setMethodSensitivity(dptr, 3); + return; +} +#endif + +static void parseEthtoolSettings(struct loaderData_s * loaderData) { + char * option, * buf; + ethtool_duplex duplex = ETHTOOL_DUPLEX_UNSPEC; + ethtool_speed speed = ETHTOOL_SPEED_UNSPEC; + + buf = strdup(loaderData->ethtool); + option = strtok(buf, " "); + while (option) { + if (option[strlen(option) - 1] == '\"') + option[strlen(option) - 1] = '\0'; + if (option[0] == '\"') + option++; + if (!strncmp(option, "duplex", 6)) { + if (!strncmp(option + 7, "full", 4)) + duplex = ETHTOOL_DUPLEX_FULL; + else if (!strncmp(option + 7, "half", 4)) + duplex = ETHTOOL_DUPLEX_HALF; + else + logMessage(WARNING, "Unknown duplex setting: %s", option + 7); + option = strtok(NULL, " "); + } else if (!strncmp("speed", option, 5)) { + if (!strncmp(option + 6, "1000", 4)) + speed = ETHTOOL_SPEED_1000; + else if (!strncmp(option + 6, "100", 3)) + speed = ETHTOOL_SPEED_100; + else if (!strncmp(option + 6, "10", 2)) + speed = ETHTOOL_SPEED_10; + else + logMessage(WARNING, "Unknown speed setting: %s", option + 6); + option = strtok(NULL, " "); + } else { + logMessage(WARNING, "Unknown ethtool setting: %s", option); + } + option = strtok(NULL, " "); + } + setEthtoolSettings(loaderData->netDev, speed, duplex); + free(buf); +} + +/* given loader data from kickstart, populate network configuration struct */ +void setupIfaceStruct(iface_t * iface, struct loaderData_s * loaderData) { + struct in_addr addr; + struct in6_addr addr6; + char * c; + + memset(&addr, 0, sizeof(addr)); + memset(&addr6, 0, sizeof(addr6)); + + if (loaderData->ethtool) { + parseEthtoolSettings(loaderData); + } + + if (loaderData->netCls_set) { + iface->vendorclass = loaderData->netCls; + } else { + iface->vendorclass = NULL; + } + + if (loaderData->ipinfo_set && loaderData->ipv4 != NULL) { + /* this is iBFT configured device */ + if (!strncmp(loaderData->ipv4, "ibft", 4)) { + char *devmacaddr = iface_mac2str(loaderData->netDev); + iface->ipv4method = IPV4_IBFT_METHOD; + iface->isiBFT = 1; + + /* Problems with getting the info from iBFT or iBFT uses dhcp*/ + if(!devmacaddr || !ibft_present()){ + iface->ipv4method = IPV4_DHCP_METHOD; + logMessage(INFO, "iBFT is not present"); + } + /* MAC address doesn't match */ + else if(strcasecmp(ibft_iface_mac(), devmacaddr)){ + iface->ipv4method = IPV4_DHCP_METHOD; + logMessage(INFO, "iBFT doesn't know what NIC to use - falling back to DHCP"); + } + else if(ibft_iface_dhcp()){ + iface->ipv4method = IPV4_IBFT_DHCP_METHOD; + logMessage(INFO, "iBFT is configured to use DHCP"); + } + if(devmacaddr) free(devmacaddr); + } + /* this is how we specify dhcp */ + else if (!strncmp(loaderData->ipv4, "dhcp", 4)) { + iface->dhcptimeout = loaderData->dhcpTimeout; + iface->ipv4method = IPV4_DHCP_METHOD; + } else if (inet_pton(AF_INET, loaderData->ipv4, &addr) >= 1) { + iface->ipaddr.s_addr = addr.s_addr; + iface->ipv4method = IPV4_MANUAL_METHOD; + } else { /* invalid ip information, disable the setting of ip info */ + loaderData->ipinfo_set = 0; + iface->ipv4method = 0; + loaderData->ipv4 = NULL; + } + } + + if (loaderData->netmask != NULL) { + if (inet_pton(AF_INET, loaderData->netmask, &iface->netmask) <= 0) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, + strerror(errno)); + } + } + + if (loaderData->gateway != NULL) { + if (inet_pton(AF_INET, loaderData->gateway, &iface->gateway) <= 0) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, + strerror(errno)); + } + } + +#ifdef ENABLE_IPV6 + if (loaderData->ipv6info_set && loaderData->ipv6 != NULL) { + if (!strncmp(loaderData->ipv6, "dhcp", 4)) { + iface->ipv6method = IPV6_DHCP_METHOD; + } else if (!strncmp(loaderData->ipv6, "auto", 4)) { + iface->ipv6method = IPV6_AUTO_METHOD; + } else if (inet_pton(AF_INET6, loaderData->ipv6, &addr6) >= 1) { + memcpy(&iface->ip6addr, &addr6, sizeof(struct in6_addr)); + iface->ipv6method = IPV6_MANUAL_METHOD; + } else { + iface->ipv6method = 0; + loaderData->ipv6info_set = 0; + loaderData->ipv6 = NULL; + } + } + + if (loaderData->gateway6 != NULL) { + if (inet_pton(AF_INET6, loaderData->gateway6, &iface->gateway6) <= 0) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, + strerror(errno)); + } + } +#endif + + /* iBFT configured DNS */ + if(iface->ipv4method == IPV4_IBFT_METHOD){ + if(iface->numdns<MAXNS){ + if(ibft_iface_dns1() && inet_pton(AF_INET, ibft_iface_dns1(), &addr)>=1){ + iface->dns[iface->numdns] = strdup(ibft_iface_dns1()); + iface->numdns++; + logMessage(INFO, "adding iBFT dns server %s", ibft_iface_dns1()); + } + } + if(iface->numdns<MAXNS){ + if(ibft_iface_dns2() && inet_pton(AF_INET, ibft_iface_dns2(), &addr)>=1){ + iface->dns[iface->numdns] = strdup(ibft_iface_dns2()); + iface->numdns++; + logMessage(INFO, "adding iBFT dns server %s", ibft_iface_dns2()); + } + } + } + + if (loaderData->dns) { + char * buf; + char ret[INET6_ADDRSTRLEN+1]; + buf = strdup(loaderData->dns); + + /* Scan the dns parameter for multiple comma-separated IP addresses */ + c = strtok(buf, ","); + while ((iface->numdns < MAXNS) && (c != NULL)) { + if (inet_pton(AF_INET, c, &addr) >= 1) { + iface->dns[iface->numdns] = strdup(c); + iface->numdns++; + + if (inet_ntop(AF_INET, &addr, ret, INET_ADDRSTRLEN) == NULL) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, strerror(errno)); + } else { + logMessage(DEBUGLVL, "adding dns4 %s", ret); + c = strtok(NULL, ","); + } + } else if (inet_pton(AF_INET6, c, &addr6) >= 1) { + iface->dns[iface->numdns] = strdup(c); + iface->numdns++; + + if (inet_ntop(AF_INET6, &addr6, ret, INET6_ADDRSTRLEN) == NULL) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, strerror(errno)); + } else { + logMessage(DEBUGLVL, "adding dns6 %s", ret); + c = strtok(NULL, ","); + } + } + } + + + + logMessage(INFO, "dnsservers is %s", loaderData->dns); + } + + if (loaderData->hostname) { + logMessage(INFO, "setting specified hostname of %s", + loaderData->hostname); + iface->hostname = strdup(loaderData->hostname); + } + + if (loaderData->mtu) { + iface->mtu = loaderData->mtu; + } + + if (loaderData->peerid) { + iface->peerid = strdup(loaderData->peerid); + } + + if (loaderData->subchannels) { + iface->subchannels = strdup(loaderData->subchannels); + } + + if (loaderData->ctcprot) { + iface->ctcprot = strdup(loaderData->ctcprot); + } + + if (loaderData->portname) { + iface->portname = strdup(loaderData->portname); + } + + if (loaderData->nettype) { + iface->nettype = strdup(loaderData->nettype); + } + + if (loaderData->ethtool) { + parseEthtoolSettings(loaderData); + } + + if (loaderData->layer2) { + iface->layer2 = strdup(loaderData->layer2); + } + + if (loaderData->portno) { + iface->portno = strdup(loaderData->portno); + } + + if (loaderData->noDns) { + iface->flags |= IFACE_FLAGS_NO_WRITE_RESOLV_CONF; + } + + iface->dhcptimeout = loaderData->dhcpTimeout; + + return; +} + +int readNetConfig(char * device, iface_t * iface, + char * dhcpclass, int methodNum) { + int err; + int ret; + int i = 0; + struct netconfopts opts; + struct in_addr addr; + struct intfconfig_s ipcomps; + + /* ipcomps contains the user interface components */ + ipcomps.ipv4 = NULL; + ipcomps.cidr4 = NULL; + ipcomps.gw = NULL; +#ifdef ENABLE_IPV6 + ipcomps.ipv6 = NULL; + ipcomps.cidr6 = NULL; + ipcomps.gw6 = NULL; +#endif + ipcomps.ns = NULL; + + /* init opts */ + opts.ipv4Choice = 0; +#ifdef ENABLE_IPV6 + opts.ipv6Choice = 0; +#endif + + /* JKFIXME: we really need a way to override this and be able to change + * our network config */ + if (!FL_ASKNETWORK(flags) && + ((iface->ipv4method > IPV4_UNUSED_METHOD) || + (iface->ipv6method > IPV4_UNUSED_METHOD))) { + logMessage(INFO, "doing kickstart... setting it up"); + + err = writeEnabledNetInfo(iface); + if (err) { + logMessage(ERROR, "failed to write %s data for %s (%d)", + SYSCONFIG_PATH, iface->device, err); + return LOADER_BACK; + } + + i = get_connection(iface); + newtPopWindow(); + + if (i > 0) { + if (FL_CMDLINE(flags)) { + fprintf(stderr, _("There was an error configuring your network " + "interface.")); + fprintf(stderr, _("\nThis cannot be corrected in cmdline mode.\n" + "Halting.\n")); + exit(1); + } + + newtWinMessage(_("Network Error"), _("Retry"), + _("There was an error configuring your network " + "interface.")); + return LOADER_BACK; + } + + return LOADER_NOOP; + } + + /* dhcp/manual network configuration loop */ + i = 1; + while (i == 1) { + ret = configureTCPIP(device, iface, &opts, methodNum); + + if (ret == LOADER_NOOP) { + /* dhcp selected, proceed */ + i = 0; + } else if (ret == LOADER_OK) { + /* do manual configuration */ + ret = manualNetConfig(device, iface, &ipcomps, &opts); + + if (ret == LOADER_BACK) { + continue; + } else if (ret == LOADER_OK) { + i = 0; + } + } else if (ret == LOADER_BACK) { + return LOADER_BACK; + } + } + + /* calculate any missing IPv4 pieces */ + if (opts.ipv4Choice == '*') { + memset(&addr, 0, sizeof(addr)); + addr.s_addr = (iface->ipaddr.s_addr) & (iface->netmask.s_addr); + + if (iface->broadcast.s_addr == 0) { + iface->broadcast.s_addr = addr.s_addr | ~(iface->netmask.s_addr); + } + } + + /* bring up the interface */ + err = writeEnabledNetInfo(iface); + if (err) { + logMessage(ERROR, "failed to write %s data for %s (%d)", + SYSCONFIG_PATH, iface->device, err); + iface->ipv4method = IPV4_UNUSED_METHOD; + iface->ipv6method = IPV6_UNUSED_METHOD; + return LOADER_BACK; + } + + i = get_connection(iface); + newtPopWindow(); + + if (i > 0) { + newtWinMessage(_("Network Error"), _("Retry"), + _("There was an error configuring your network " + "interface.")); + iface->ipv4method = IPV4_UNUSED_METHOD; + iface->ipv6method = IPV6_UNUSED_METHOD; + return LOADER_ERROR; + } + + return LOADER_OK; +} + +int configureTCPIP(char * device, iface_t * iface, + struct netconfopts * opts, int methodNum) { + int i = 0, z = 0, skipForm = 0, ret; + newtComponent f, okay, back, answer; + newtComponent ipv4Checkbox, v4Method[2]; +#ifdef ENABLE_IPV6 + newtComponent ipv6Checkbox, v6Method[3]; +#endif + newtGrid grid, checkgrid, buttons; + + /* UI WINDOW 1: ask for ipv4 choice, ipv6 choice, and conf methods */ + + /* IPv4 checkbox */ + if (!opts->ipv4Choice) { + if (FL_NOIPV4(flags) && !FL_IP_PARAM(flags)) + opts->ipv4Choice = ' '; + else + opts->ipv4Choice = '*'; + } + + ipv4Checkbox = newtCheckbox(-1, -1, _("Enable IPv4 support"), + opts->ipv4Choice, NULL, &(opts->ipv4Choice)); + v4Method[0] = newtRadiobutton(-1, -1, DHCP_METHOD_STR, 1, NULL); + v4Method[1] = newtRadiobutton(-1, -1, MANUAL_METHOD_STR, 0, v4Method[0]); + +#ifdef ENABLE_IPV6 + /* IPv6 checkbox */ + if (!opts->ipv6Choice) { + if (FL_NOIPV6(flags) && !FL_IPV6_PARAM(flags)) + opts->ipv6Choice = ' '; + else + opts->ipv6Choice = '*'; + } + + ipv6Checkbox = newtCheckbox(-1, -1, _("Enable IPv6 support"), + opts->ipv6Choice, NULL, &(opts->ipv6Choice)); + v6Method[0] = newtRadiobutton(-1, -1, AUTO_METHOD_STR, 1, NULL); + v6Method[1] = newtRadiobutton(-1, -1, DHCPV6_METHOD_STR, 0, v6Method[0]); + v6Method[2] = newtRadiobutton(-1, -1, MANUAL_METHOD_STR, 0, v6Method[1]); +#endif + + /* button bar at the bottom of the window */ + buttons = newtButtonBar(_("OK"), &okay, _("Back"), &back, NULL); + + /* checkgrid contains the toggle options for net configuration */ +#ifdef ENABLE_IPV6 + checkgrid = newtCreateGrid(1, 8); +#else + checkgrid = newtCreateGrid(1, 3); +#endif + + newtGridSetField(checkgrid, 0, 0, NEWT_GRID_COMPONENT, ipv4Checkbox, + 0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + for (i = 1; i < 3; i++) + newtGridSetField(checkgrid, 0, i, NEWT_GRID_COMPONENT, v4Method[i-1], + 7, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + +#ifdef ENABLE_IPV6 + newtGridSetField(checkgrid, 0, 4, NEWT_GRID_COMPONENT, ipv6Checkbox, + 0, 1, 0, 0, NEWT_ANCHOR_LEFT, 0); + for (i = 5; i < 8; i++) + newtGridSetField(checkgrid, 0, i, NEWT_GRID_COMPONENT, v6Method[i-5], + 7, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); +#endif + + /* main window layout */ + grid = newtCreateGrid(1, 2); + newtGridSetField(grid, 0, 0, NEWT_GRID_SUBGRID, checkgrid, + 0, 0, 0, 1, 0, 0); + newtGridSetField(grid, 0, 1, NEWT_GRID_SUBGRID, buttons, + 0, 0, 0, 0, 0, NEWT_GRID_FLAG_GROWX); + + f = newtForm(NULL, NULL, 0); + newtGridAddComponentsToForm(grid, f, 1); + newtGridWrappedWindow(grid, _("Configure TCP/IP")); + newtGridFree(grid, 1); + + /* callbacks */ + newtComponentAddCallback(ipv4Checkbox, v4MethodCallback, &v4Method); +#ifdef ENABLE_IPV6 + newtComponentAddCallback(ipv6Checkbox, v6MethodCallback, &v6Method); +#endif + + /* match radio button sensitivity to initial checkbox choices */ + if (opts->ipv4Choice == ' ') + setMethodSensitivity(&v4Method, 2); + +#ifdef ENABLE_IPV6 + if (opts->ipv6Choice == ' ') + setMethodSensitivity(&v6Method, 3); +#endif + +#ifdef ENABLE_IPV6 + /* If the user provided any of the following boot paramters, skip + * prompting for network configuration information: + * ip=<val> ipv6=<val> + * noipv4 noipv6 + * ip=<val> noipv6 + * ipv6=<val> noipv4 + */ + if ((FL_IP_PARAM(flags) && FL_IPV6_PARAM(flags)) || + (FL_IP_PARAM(flags) && FL_NOIPV6(flags)) || + (FL_IPV6_PARAM(flags) && FL_NOIPV4(flags)) || + (FL_NOIPV4(flags) && FL_NOIPV6(flags))) { + skipForm = 1; + newtPopWindow(); + } +#else + if (FL_IP_PARAM(flags) || FL_NOIPV4(flags)) { + skipForm = 1; + newtPopWindow(); + } +#endif + + /* run the form */ + do { + if (!skipForm) { + answer = newtRunForm(f); + + if (answer == back) { + newtFormDestroy(f); + newtPopWindow(); + return LOADER_BACK; + } + + /* need at least one stack */ +#ifdef ENABLE_IPV6 + if (opts->ipv4Choice == ' ' && opts->ipv6Choice == ' ') { +#else + if (opts->ipv4Choice == ' ') { +#endif + newtWinMessage(_("Missing Protocol"), _("Retry"), + _("You must select at least one protocol (IPv4 " + "or IPv6).")); + continue; + } + + /* NFS only works over IPv4 */ + if (opts->ipv4Choice == ' ' && methodNum == METHOD_NFS) { + newtWinMessage(_("IPv4 Needed for NFS"), _("Retry"), + _("NFS installation method requires IPv4 support.")); + continue; + } + } + + /* what TCP/IP stacks do we use? what conf methods? */ + if (opts->ipv4Choice == '*') { + flags &= ~LOADER_FLAGS_NOIPV4; + for (z = IPV4_FIRST_METHOD; z <= IPV4_LAST_METHOD; z++) + if (newtRadioGetCurrent(v4Method[0]) == v4Method[z-1]) + iface->ipv4method = z; + } else { + flags |= LOADER_FLAGS_NOIPV4; + } + +#ifdef ENABLE_IPV6 + if (opts->ipv6Choice == '*') { + flags &= ~LOADER_FLAGS_NOIPV6; + for (z = IPV6_FIRST_METHOD; z <= IPV6_LAST_METHOD; z++) + if (newtRadioGetCurrent(v6Method[0]) == v6Method[z-1]) + iface->ipv6method = z; + } else { + flags |= LOADER_FLAGS_NOIPV6; + } +#endif + + /* do interface configuration (call DHCP here, or return for manual) */ +#ifdef ENABLE_IPV6 + if ((!FL_NOIPV4(flags) && iface->ipv4method == IPV4_DHCP_METHOD) || + (!FL_NOIPV6(flags) && (iface->ipv6method == IPV6_AUTO_METHOD || + iface->ipv6method == IPV6_DHCP_METHOD))) { +#else + if (!FL_NOIPV4(flags) && iface->ipv4method == IPV4_DHCP_METHOD) { +#endif + /* DHCP selected, exit the loop */ + ret = LOADER_NOOP; + i = 1; +#ifdef ENABLE_IPV6 + } else if ((!FL_NOIPV4(flags) && iface->ipv4method == IPV4_MANUAL_METHOD) || + (!FL_NOIPV6(flags) && iface->ipv6method == IPV6_MANUAL_METHOD)) { +#else + } else if (!FL_NOIPV4(flags) && iface->ipv4method == IPV4_MANUAL_METHOD) { +#endif + + /* manual IP configuration selected */ + ret = LOADER_OK; + i = 1; + } + } while (i != 1); + + newtFormDestroy(f); + newtPopWindow(); + return ret; +} + +int manualNetConfig(char * device, iface_t * iface, + struct intfconfig_s * ipcomps, struct netconfopts * opts) { + int i, rows, pos, cidr, have[2], stack[2]; + char *buf = NULL; + char ret[48]; + struct in_addr addr; +#ifdef ENABLE_IPV6 + struct in6_addr addr6; + int prefix; +#endif + struct in_addr *tmpaddr = NULL; + newtComponent f, okay, back, answer; + newtGrid egrid = NULL; + newtGrid qgrid = NULL; +#ifdef ENABLE_IPV6 + newtGrid rgrid = NULL; +#endif + newtGrid buttons, grid; + newtComponent text = NULL; + + memset(ret, '\0', INET6_ADDRSTRLEN+1); + + /* so we don't perform this test over and over */ + stack[IPV4] = opts->ipv4Choice == '*' && + iface->ipv4method == IPV4_MANUAL_METHOD; +#ifdef ENABLE_IPV6 + stack[IPV6] = opts->ipv6Choice == '*' && + iface->ipv6method == IPV6_MANUAL_METHOD; +#endif + + /* UI WINDOW 2 (optional): manual IP config for non-DHCP installs */ + rows = 2; + for (i = 0; i < 2; i++) { + if (stack[i]) { + rows++; + } + } + egrid = newtCreateGrid(4, rows); + + pos = 0; + + /* IPv4 entry items */ + if (stack[IPV4]) { + newtGridSetField(egrid, 0, pos, NEWT_GRID_COMPONENT, + newtLabel(-1, -1, _("IPv4 address:")), + 0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + + ipcomps->ipv4Entry = newtEntry(-1, -1, NULL, 16, &ipcomps->ipv4, 0); + ipcomps->cidr4Entry = newtEntry(-1, -1, NULL, 16, &ipcomps->cidr4, 0); + + /* use a nested grid for ipv4 addr & netmask */ + qgrid = newtCreateGrid(3, 1); + + newtGridSetField(qgrid, 0, 0, NEWT_GRID_COMPONENT, + ipcomps->ipv4Entry, 1, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + newtGridSetField(qgrid, 1, 0, NEWT_GRID_COMPONENT, + newtLabel(-1, -1, _("/")), + 1, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + newtGridSetField(qgrid, 2, 0, NEWT_GRID_COMPONENT, + ipcomps->cidr4Entry, 1, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + + newtGridSetField(egrid, 1, pos, NEWT_GRID_SUBGRID, qgrid, + 0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + + newtComponentAddCallback(ipcomps->ipv4Entry, ipCallback, ipcomps); + newtComponentAddCallback(ipcomps->cidr4Entry, cidrCallback, ipcomps); + + /* populate fields if we have data already */ + if (iface_have_in_addr(&iface->ipaddr)) { + if (inet_ntop(AF_INET, &iface->ipaddr, ret, + INET_ADDRSTRLEN) == NULL) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, + strerror(errno)); + } + } else if (iface_have_in_addr(&iface->ipaddr)) { + if (inet_ntop(AF_INET, &iface->ipaddr, ret, + INET_ADDRSTRLEN) == NULL) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, + strerror(errno)); + } + } + + if (*ret) { + newtEntrySet(ipcomps->ipv4Entry, ret, 1); + } + + if (iface_have_in_addr(&iface->netmask)) { + if (inet_ntop(AF_INET, &iface->netmask, ret, + INET_ADDRSTRLEN) == NULL) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, + strerror(errno)); + } + } else if (iface_have_in_addr(&iface->netmask)) { + if (inet_ntop(AF_INET, &iface->netmask, ret, + INET_ADDRSTRLEN) == NULL) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, + strerror(errno)); + } + } + + if (*ret) { + newtEntrySet(ipcomps->cidr4Entry, ret, 1); + } + + pos++; + } + +#ifdef ENABLE_IPV6 + /* IPv6 entry items */ + if (stack[IPV6]) { + newtGridSetField(egrid, 0, pos, NEWT_GRID_COMPONENT, + newtLabel(-1, -1, _("IPv6 address:")), + 0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + + ipcomps->ipv6Entry = newtEntry(-1, -1, NULL, 41, &ipcomps->ipv6, 0); + ipcomps->cidr6Entry = newtEntry(-1, -1, NULL, 4, &ipcomps->cidr6, 0); + + /* use a nested grid for ipv6 addr & netmask */ + rgrid = newtCreateGrid(3, 1); + + newtGridSetField(rgrid, 0, 0, NEWT_GRID_COMPONENT, + ipcomps->ipv6Entry, 1, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + newtGridSetField(rgrid, 1, 0, NEWT_GRID_COMPONENT, + newtLabel(-1, -1, _("/")), + 1, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + newtGridSetField(rgrid, 2, 0, NEWT_GRID_COMPONENT, + ipcomps->cidr6Entry, 1, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + + newtGridSetField(egrid, 1, pos, NEWT_GRID_SUBGRID, rgrid, + 0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + + newtComponentAddCallback(ipcomps->ipv6Entry, ipCallback, ipcomps); + newtComponentAddCallback(ipcomps->cidr6Entry, cidrCallback, ipcomps); + + /* populate fields if we have data already */ + if (iface_have_in6_addr(&iface->ip6addr)) { + if (inet_ntop(AF_INET6, &iface->ip6addr, ret, + INET6_ADDRSTRLEN) == NULL) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, + strerror(errno)); + } + } else if (iface_have_in6_addr(&iface->ip6addr)) { + if (inet_ntop(AF_INET6, &iface->ip6addr, ret, + INET6_ADDRSTRLEN) == NULL) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, + strerror(errno)); + } + } + + if (*ret) { + newtEntrySet(ipcomps->ipv6Entry, ret, 1); + } + + if (iface->ip6prefix) { + if (asprintf(&buf, "%d", iface->ip6prefix) == -1) { + buf = NULL; + } + } else if (iface->ip6prefix) { + if (asprintf(&buf, "%d", iface->ip6prefix) == -1) { + buf = NULL; + } + } + + if (buf != NULL) { + newtEntrySet(ipcomps->cidr6Entry, buf, 1); + free(buf); + } + + pos++; + } +#endif + + /* common entry items */ + ipcomps->gwEntry = newtEntry(-1, -1, NULL, 41, &ipcomps->gw, 0); + ipcomps->nsEntry = newtEntry(-1, -1, NULL, 41, &ipcomps->ns, 0); + + newtGridSetField(egrid, 0, pos, NEWT_GRID_COMPONENT, + newtLabel(-1, -1, _("Gateway:")), + 0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + newtGridSetField(egrid, 1, pos, NEWT_GRID_COMPONENT, + ipcomps->gwEntry, 1, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + + pos++; + + newtGridSetField(egrid, 0, pos, NEWT_GRID_COMPONENT, + newtLabel(-1, -1, _("Name Server:")), + 0, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + newtGridSetField(egrid, 1, pos, NEWT_GRID_COMPONENT, + ipcomps->nsEntry, 1, 0, 0, 0, NEWT_ANCHOR_LEFT, 0); + + if (iface_have_in_addr(&iface->gateway)) { + if (inet_ntop(AF_INET, &iface->gateway, ret, + INET_ADDRSTRLEN) == NULL) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, + strerror(errno)); + } else { + newtEntrySet(ipcomps->gwEntry, ret, 1); + } + } else if (iface_have_in6_addr(&iface->gateway6)) { + if (inet_ntop(AF_INET6, &iface->gateway6, ret, + INET6_ADDRSTRLEN) == NULL) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, + strerror(errno)); + } else { + newtEntrySet(ipcomps->gwEntry, ret, 1); + } + } + + if (iface->numdns) { + newtEntrySet(ipcomps->nsEntry, iface->dns[0], 1); + } else if (iface->numdns) { + newtEntrySet(ipcomps->nsEntry, iface->dns[0], 1); + } + + newtComponentAddCallback(ipcomps->gwEntry, ipCallback, ipcomps); + newtComponentAddCallback(ipcomps->nsEntry, ipCallback, ipcomps); + + /* button bar at the bottom of the window */ + buttons = newtButtonBar(_("OK"), &okay, _("Back"), &back, NULL); + + /* main window layout */ + grid = newtCreateGrid(1, 3); + + checked_asprintf(&buf, + _("Enter the IPv4 and/or the IPv6 address and prefix " + "(address / prefix). For IPv4, the dotted-quad " + "netmask or the CIDR-style prefix are acceptable. " + "The gateway and name server fields must be valid IPv4 " + "or IPv6 addresses.")); + + text = newtTextboxReflowed(-1, -1, buf, 52, 0, 10, 0); + + newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, text, + 0, 0, 0, 1, NEWT_ANCHOR_LEFT, 0); + newtGridSetField(grid, 0, 1, NEWT_GRID_SUBGRID, egrid, + 0, 0, 0, 1, NEWT_ANCHOR_LEFT, 0); + newtGridSetField(grid, 0, 2, NEWT_GRID_SUBGRID, buttons, + 0, 0, 0, 0, 0, NEWT_GRID_FLAG_GROWX); + + f = newtForm(NULL, NULL, 0); + newtGridAddComponentsToForm(grid, f, 1); + newtGridWrappedWindow(grid, _("Manual TCP/IP Configuration")); + newtGridFree(grid, 1); + + have[IPV4] = 0; + have[IPV6] = 0; + + for (i = IPV4; i <= IPV6; i++) { + if (!stack[i]) { + have[i] = 2; + } + } + + /* run the form */ + while ((have[IPV4] != 2) || (have[IPV6] != 2)) { + answer = newtRunForm(f); + + /* collect IPv4 data */ + if (stack[IPV4]) { + if (ipcomps->ipv4) { + if (inet_pton(AF_INET, ipcomps->ipv4, &iface->ipaddr) <= 0) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, + strerror(errno)); + } else { + have[IPV4]++; + } + } + + if (ipcomps->cidr4) { + if (inet_pton(AF_INET, ipcomps->cidr4, &iface->netmask) >= 1) { + have[IPV4]++; + } else { + errno = 0; + cidr = strtol(ipcomps->cidr4, NULL, 10); + + if ((errno == ERANGE && (cidr == LONG_MIN || + cidr == LONG_MAX)) || + (errno != 0 && cidr == 0)) { + logMessage(ERROR, "%s: %d: %m", __func__, __LINE__); + abort(); + } + + if (cidr >= 1 && cidr <= 32) { + tmpaddr = iface_prefix2netmask(cidr); + if (tmpaddr != NULL) { + memcpy(&iface->netmask, tmpaddr, + sizeof(struct in_addr)); + have[IPV4]++; + } else { + iface->netmask.s_addr = 0; + } + } + } + } + } + +#ifdef ENABLE_IPV6 + /* collect IPv6 data */ + if (stack[IPV6]) { + if (ipcomps->ipv6) { + if (inet_pton(AF_INET6, ipcomps->ipv6, &iface->ip6addr) <= 0) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, + strerror(errno)); + } else { + have[IPV6]++; + } + } + + if (ipcomps->cidr6) { + errno = 0; + prefix = strtol(ipcomps->cidr6, NULL, 10); + + if ((errno == ERANGE && (prefix == LONG_MIN || + prefix == LONG_MAX)) || + (errno != 0 && prefix == 0)) { + logMessage(ERROR, "%s: %d: %m", __func__, __LINE__); + abort(); + } + + if (prefix > 0 || prefix <= 128) { + iface->ip6prefix = prefix; + have[IPV6]++; + } + } + } +#endif + + /* collect common network settings */ + if (ipcomps->gw) { + if (inet_pton(AF_INET, ipcomps->gw, &iface->gateway) <= 0) { + memset(&iface->gateway, 0, sizeof(iface->gateway)); + + if (inet_pton(AF_INET6, ipcomps->gw, &iface->gateway6) <= 0) { + logMessage(ERROR, "%s (%d): %s", __func__, __LINE__, + strerror(errno)); + memset(&iface->gateway6, 0, sizeof(iface->gateway6)); + } + } + } + + /* gather nameservers */ + if (ipcomps->ns) { +#ifdef ENABLE_IPV6 + if ((inet_pton(AF_INET, ipcomps->ns, &addr) >= 1) || + (inet_pton(AF_INET6, ipcomps->ns, &addr6) >= 1)) { +#else + if (inet_pton(AF_INET, ipcomps->ns, &addr) >= 1) { +#endif + iface->dns[0] = strdup(ipcomps->ns); + if (iface->numdns < 1) + iface->numdns = 1; + } + } + + /* user selected back, but we've saved what they entered already */ + if (answer == back) { + newtFormDestroy(f); + newtPopWindow(); + free(buf); + return LOADER_BACK; + } + + /* we might be done now */ + if (stack[IPV4] && have[IPV4] != 2) { + have[IPV4] = 0; + newtWinMessage(_("Missing Information"), _("Retry"), + _("You must enter both a valid IPv4 address and a " + "network mask or CIDR prefix.")); + } + +#ifdef ENABLE_IPV6 + if (stack[IPV6] && have[IPV6] != 2) { + have[IPV6] = 0; + newtWinMessage(_("Missing Information"), _("Retry"), + _("You must enter both a valid IPv6 address and a " + "CIDR prefix.")); + } +#endif + + strcpy(iface->device, device); + } + + free(buf); + newtFormDestroy(f); + newtPopWindow(); + + return LOADER_OK; +} + +/* + * By default, we disable all network interfaces and then only + * bring up the ones the user wants. + */ +int writeDisabledNetInfo(void) { + int i = 0; + char *ofile = NULL; + char *nfile = NULL; + FILE *fp = NULL; + struct device **devs = NULL; + + devs = getDevices(DEVICE_NETWORK); + + if (devs == NULL) { + return 1; + } + + for (i = 0; devs[i]; i++) { + /* remove dhclient-DEVICE.conf if we have it */ + if (asprintf(&ofile, "/etc/dhcp/dhclient-%s.conf", devs[i]->device) == -1) { + return 5; + } + + if (!access(ofile, R_OK|W_OK)) { + if (unlink(ofile)) { + logMessage(ERROR, "error removing %s", ofile); + } + } + + if (ofile) { + free(ofile); + ofile = NULL; + } + + /* write disabled ifcfg-DEVICE file */ + + checked_asprintf(&ofile, "%s/.ifcfg-%s", + NETWORK_SCRIPTS_PATH, + devs[i]->device); + checked_asprintf(&nfile, "%s/ifcfg-%s", + NETWORK_SCRIPTS_PATH, + devs[i]->device); + + if ((fp = fopen(ofile, "w")) == NULL) { + free(ofile); + return 2; + } + + fprintf(fp, "DEVICE=%s\n", devs[i]->device); + fprintf(fp, "HWADDR=%s\n", iface_mac2str(devs[i]->device)); + fprintf(fp, "ONBOOT=no\n"); + fprintf(fp, "NM_CONTROLLED=no\n"); + + if (fclose(fp) == EOF) { + return 3; + } + + if (rename(ofile, nfile) == -1) { + free(ofile); + free(nfile); + return 4; + } + + if (ofile) { + free(ofile); + ofile = NULL; + } + + if (nfile) { + free(nfile); + nfile = NULL; + } + } + + return 0; +} + +/* + * Write out network interface control files: + * /etc/sysconfig/network-scripts/ifcfg-DEVICE + * /etc/sysconfig/network + */ +int writeEnabledNetInfo(iface_t *iface) { + int i = 0, osa_layer2 = 0, osa_portno = 0; + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + FILE *fp = NULL; + char buf[INET6_ADDRSTRLEN+1]; + char *ofile = NULL; + char *nfile = NULL; + struct utsname kv; + + memset(&buf, '\0', sizeof(buf)); + + if ((mkdir(NETWORK_SCRIPTS_PATH, mode) == -1) && (errno != EEXIST)) { + return 16; + } + + /* write vendor class */ + if (iface->vendorclass == NULL) { + if (uname(&kv) == -1) { + iface->vendorclass = "anaconda"; + } else { + if (asprintf(&iface->vendorclass, "anaconda-%s %s %s", + kv.sysname, kv.release, kv.machine) == -1 ) { + return 20; + } + } + } + + if (asprintf(&ofile, "/etc/dhcp/dhclient-%s.conf", iface->device) == -1) { + return 17; + } + + if ((fp = fopen(ofile, "w")) == NULL) { + free(ofile); + return 18; + } + + fprintf(fp, "send vendor-class-identifier \"%s\";\n", + iface->vendorclass); + + if (fclose(fp) == EOF) { + free(ofile); + return 19; + } + + if (ofile) { + free(ofile); + ofile = NULL; + } + + /* write out new ifcfg-DEVICE file */ + if (asprintf(&ofile, "%s/.ifcfg-%s", + NETWORK_SCRIPTS_PATH, iface->device) == -1) { + return 1; + } + + if (asprintf(&nfile, "%s/ifcfg-%s", + NETWORK_SCRIPTS_PATH, iface->device) == -1) { + return 13; + } + + if ((fp = fopen(ofile, "w")) == NULL) { + free(ofile); + return 2; + } + + fprintf(fp, "DEVICE=%s\n", iface->device); + fprintf(fp, "HWADDR=%s\n", iface_mac2str(iface->device)); + fprintf(fp, "ONBOOT=yes\n"); + + if (!FL_NOIPV4(flags)) { + if (iface->ipv4method == IPV4_IBFT_METHOD) { + /* When initrd and NM support iBFT, we should just write + * BOOTPROTO=ibft and let NM deal with it. Until than, + * just use static and do it ourselves. */ + fprintf(fp, "BOOTPROTO=static\n"); + if(ibft_iface_ip()) fprintf(fp, "IPADDR=%s\n", ibft_iface_ip()); + if(ibft_iface_mask()) fprintf(fp, "NETMASK=%s\n", ibft_iface_mask()); + if(ibft_iface_gw()) fprintf(fp, "GATEWAY=%s\n", ibft_iface_gw()); + } else if (iface->ipv4method == IPV4_IBFT_DHCP_METHOD) { + fprintf(fp, "BOOTPROTO=dhcp\n"); + } else if (iface->ipv4method == IPV4_DHCP_METHOD) { + fprintf(fp, "BOOTPROTO=dhcp\n"); + } else if (iface->ipv4method == IPV4_MANUAL_METHOD) { + fprintf(fp, "BOOTPROTO=static\n"); + + if (iface_have_in_addr(&iface->ipaddr)) { + if (inet_ntop(AF_INET, &iface->ipaddr, buf, + INET_ADDRSTRLEN) == NULL) { + free(ofile); + fclose(fp); + return 3; + } + + fprintf(fp, "IPADDR=%s\n", buf); + } + + if (iface_have_in_addr(&iface->netmask)) { + if (inet_ntop(AF_INET, &iface->netmask, buf, + INET_ADDRSTRLEN) == NULL) { + free(ofile); + fclose(fp); + return 4; + } + + fprintf(fp, "NETMASK=%s\n", buf); + } + + if (iface_have_in_addr(&iface->broadcast)) { + if (inet_ntop(AF_INET, &iface->broadcast, buf, + INET_ADDRSTRLEN) == NULL) { + free(ofile); + fclose(fp); + return 5; + } + + fprintf(fp, "BROADCAST=%s\n", buf); + } + + if (iface_have_in_addr(&iface->gateway)) { + if (inet_ntop(AF_INET, &iface->gateway, buf, + INET_ADDRSTRLEN) == NULL) { + free(ofile); + fclose(fp); + return 6; + } + + fprintf(fp, "GATEWAY=%s\n", buf); + } + } + } + +#ifdef ENABLE_IPV6 + if (!FL_NOIPV6(flags)) { + if (iface->ipv6method == IPV6_AUTO_METHOD || + iface->ipv6method == IPV6_DHCP_METHOD || + iface->ipv6method == IPV6_MANUAL_METHOD) { + fprintf(fp, "IPV6INIT=yes\n"); + + if (iface->ipv6method == IPV6_AUTO_METHOD) { + fprintf(fp, "IPV6_AUTOCONF=yes\n"); + } else if (iface->ipv6method == IPV6_DHCP_METHOD) { + fprintf(fp, "DHCPV6C=yes\n"); + } else if (iface->ipv6method == IPV6_MANUAL_METHOD) { + if (iface_have_in6_addr(&iface->ip6addr)) { + if (inet_ntop(AF_INET6, &iface->ip6addr, buf, + INET6_ADDRSTRLEN) == NULL) { + free(ofile); + fclose(fp); + return 7; + } + + if (iface->ip6prefix) { + fprintf(fp, "IPV6ADDR=%s/%d\n", buf, iface->ip6prefix); + } else { + fprintf(fp, "IPV6ADDR=%s\n", buf); + } + } + } + + if (iface_have_in6_addr(&iface->gateway6)) { + if (inet_ntop(AF_INET6, &iface->gateway6, buf, + INET6_ADDRSTRLEN) == NULL) { + free(ofile); + fclose(fp); + return 8; + } + + fprintf(fp, "IPV6_DEFAULTGW=%s\n", buf); + } + } + } +#endif + + if (iface->numdns > 0) { + for (i = 0; i < iface->numdns; i++) { + fprintf(fp, "DNS%d=%s\n", i+1, iface->dns[i]); + } + } + + if (iface->hostname) { + fprintf(fp, "HOSTNAME=%s\n", iface->hostname); + } + + if (iface->domain) { + fprintf(fp, "DOMAIN=%s\n", iface->domain); + } + + if (iface->mtu) { + fprintf(fp, "MTU=%d\n", iface->mtu); + } + + if (iface->peerid) { + fprintf(fp, "PEERID=%s\n", iface->peerid); + } + + if (iface->subchannels) { + fprintf(fp, "SUBCHANNELS=%s\n", iface->subchannels); + } + + if (iface->portname) { + fprintf(fp, "PORTNAME=%s\n", iface->portname); + } + + if (iface->nettype) { + fprintf(fp, "NETTYPE=%s\n", iface->nettype); + } + + if (iface->ctcprot) { + fprintf(fp, "CTCPROT=%s\n", iface->ctcprot); + } + + if (iface->layer2 && !strcmp(iface->layer2, "1")) { + osa_layer2 = 1; + } + + if (iface->portno && !strcmp(iface->portno, "1")) { + osa_portno = 1; + } + + if (osa_layer2 || osa_portno) { + fprintf(fp, "OPTIONS=\""); + + if (osa_layer2) { + fprintf(fp, "layer2=1"); + } + + if (osa_layer2 && osa_portno) { + fprintf(fp, " "); + } + + if (osa_portno) { + fprintf(fp, "portno=1"); + } + + fprintf(fp, "\"\n"); + } + + if (iface->macaddr) { + fprintf(fp, "MACADDR=%s\n", iface->macaddr); + } + + if (fclose(fp) == EOF) { + free(ofile); + free(nfile); + return 8; + } + + if (rename(ofile, nfile) == -1) { + free(ofile); + free(nfile); + return 14; + } + + if (ofile) { + free(ofile); + } + + if (nfile) { + free(nfile); + } + + /* Global settings */ + if ((fp = fopen(SYSCONFIG_PATH"/.network", "w")) == NULL) { + return 9; + } + + if (!FL_NOIPV4(flags)) { + fprintf(fp, "NETWORKING=yes\n"); + } + +#ifdef ENABLE_IPV6 + if (!FL_NOIPV6(flags)) { + fprintf(fp, "NETWORKING_IPV6=yes\n"); + } +#endif + + if (iface->hostname != NULL) { + fprintf(fp, "HOSTNAME=%s\n", iface->hostname); + } + + if (iface_have_in_addr(&iface->gateway)) { + if (inet_ntop(AF_INET, &iface->gateway, buf, + INET_ADDRSTRLEN) == NULL) { + fclose(fp); + return 10; + } + + fprintf(fp, "GATEWAY=%s\n", buf); + } + +#ifdef ENABLE_IPV6 + if (iface_have_in6_addr(&iface->gateway6)) { + if (inet_ntop(AF_INET6, &iface->gateway6, buf, + INET6_ADDRSTRLEN) == NULL) { + fclose(fp); + return 11; + } + + fprintf(fp, "IPV6_DEFAULTGW=%s\n", buf); + } +#endif + + if (fclose(fp) == EOF) { + return 12; + } + + if (rename(SYSCONFIG_PATH"/.network", + SYSCONFIG_PATH"/network") == -1) { + return 15; + } + + return 0; +} + +void setKickstartNetwork(struct loaderData_s * loaderData, int argc, + char ** argv) { + iface_t iface; + gchar *bootProto = NULL, *device = NULL, *class = NULL, *ethtool = NULL; + gchar *essid = NULL, *wepkey = NULL, *onboot = NULL; + gint mtu = 1500, dhcpTimeout = -1; + gboolean noipv4 = FALSE, noipv6 = FALSE, noDns = FALSE, noksdev = FALSE; + GOptionContext *optCon = g_option_context_new(NULL); + GError *optErr = NULL; + GOptionEntry ksOptions[] = { + { "bootproto", 0, 0, G_OPTION_ARG_STRING, &bootProto, NULL, NULL }, + { "device", 0, 0, G_OPTION_ARG_STRING, &device, NULL, NULL }, + { "dhcpclass", 0, 0, G_OPTION_ARG_STRING, &class, NULL, NULL }, + { "gateway", 'g', 0, G_OPTION_ARG_STRING, &loaderData->gateway, + NULL, NULL }, + { "ip", 'i', 0, G_OPTION_ARG_STRING, &loaderData->ipv4, NULL, NULL }, + { "mtu", 0, 0, G_OPTION_ARG_INT, &mtu, NULL, NULL }, + { "nameserver", 'n', 0, G_OPTION_ARG_STRING, &loaderData->dns, + NULL, NULL }, + { "netmask", 'm', 0, G_OPTION_ARG_STRING, &loaderData->netmask, + NULL, NULL }, + { "noipv4", 0, 0, G_OPTION_ARG_NONE, &noipv4, NULL, NULL }, + { "noipv6", 0, 0, G_OPTION_ARG_NONE, &noipv6, NULL, NULL }, + { "nodns", 0, 0, G_OPTION_ARG_NONE, &noDns, NULL, NULL }, + { "hostname", 'h', 0, G_OPTION_ARG_STRING, &loaderData->hostname, + NULL, NULL }, + { "ethtool", 0, 0, G_OPTION_ARG_STRING, ðtool, NULL, NULL }, + { "essid", 0, 0, G_OPTION_ARG_STRING, &essid, NULL, NULL }, + { "wepkey", 0, 0, G_OPTION_ARG_STRING, &wepkey, NULL, NULL }, + { "onboot", 0, 0, G_OPTION_ARG_STRING, &onboot, NULL, NULL }, + { "notksdevice", 0, 0, G_OPTION_ARG_NONE, &noksdev, NULL, NULL }, + { "dhcptimeout", 0, 0, G_OPTION_ARG_INT, &dhcpTimeout, NULL, NULL }, + { NULL }, + }; + + iface_init_iface_t(&iface); + + g_option_context_set_help_enabled(optCon, FALSE); + g_option_context_add_main_entries(optCon, ksOptions, NULL); + + if (!g_option_context_parse(optCon, &argc, &argv, &optErr)) { + newtWinMessage(_("Kickstart Error"), _("OK"), + _("Bad argument to kickstart network command: %s"), + optErr->message); + g_error_free(optErr); + } + + g_option_context_free(optCon); + + /* if they've specified dhcp/bootp use dhcp for the interface */ + if (bootProto && (!strncmp(bootProto, "dhcp", 4) || + !strncmp(bootProto, "bootp", 4))) { + loaderData->ipv4 = strdup("dhcp"); + loaderData->ipinfo_set = 1; + } else if (loaderData->ipv4) { + /* JKFIXME: this assumes a bit... */ + loaderData->ipinfo_set = 1; + } + + /* now make sure the specified bootproto is valid */ + if (bootProto && strcmp(bootProto, "dhcp") && strcmp(bootProto, "bootp") && + strcmp(bootProto, "static") && strcmp(bootProto, "query")) { + newtWinMessage(_("Kickstart Error"), _("OK"), + _("Bad bootproto %s specified in network command"), + bootProto); + } + + if (!noksdev) { + if (device) { + /* If --device=MAC was given, translate into a device name now. */ + if (index(device, ':') != NULL) + loaderData->netDev = iface_mac2device(device); + else + loaderData->netDev = strdup(device); + + loaderData->netDev_set = 1; + } + + if (class) { + loaderData->netCls = strdup(class); + loaderData->netCls_set = 1; + } + + if (ethtool) { + if (loaderData->ethtool) + free(loaderData->ethtool); + loaderData->ethtool = strdup(ethtool); + free(ethtool); + } + + if (essid) { + if (loaderData->essid) + free(loaderData->essid); + loaderData->essid = strdup(essid); + free(essid); + } + + if (wepkey) { + if (loaderData->wepkey) + free(loaderData->wepkey); + loaderData->wepkey = strdup(wepkey); + free(wepkey); + } + + if (mtu) { + loaderData->mtu = mtu; + } + + if (noipv4) + flags |= LOADER_FLAGS_NOIPV4; + +#ifdef ENABLE_IPV6 + if (noipv6) + flags |= LOADER_FLAGS_NOIPV6; +#endif + } + + if (noDns) { + loaderData->noDns = 1; + } + + /* Make sure the network is always up if there's a network line in the + * kickstart file, as %post/%pre scripts might require that. + */ + if (loaderData->method != METHOD_NFS && loaderData->method != METHOD_URL) { + if (kickstartNetworkUp(loaderData, &iface)) + logMessage(ERROR, "unable to bring up network"); + } +} + +/* if multiple interfaces get one to use from user. */ +/* NOTE - uses kickstart data available in loaderData */ +int chooseNetworkInterface(struct loaderData_s * loaderData) { + int i, rc, ask, idrc, secs, deviceNums = 0, deviceNum, foundDev = 0; + unsigned int max = 40; + char **devices; + char **deviceNames; + char *ksMacAddr = NULL, *seconds = strdup("10"), *idstr = NULL; + struct device **devs; + int lookForLink = 0; + struct newtWinEntry entry[] = {{N_("Seconds:"), (char **) &seconds, 0}, + {NULL, NULL, 0 }}; + + devs = getDevices(DEVICE_NETWORK); + if (!devs) { + logMessage(ERROR, "no network devices in choose network device!"); + return LOADER_ERROR; + } + + for (i = 0; devs[i]; i++); + + devices = alloca((i + 1) * sizeof(*devices)); + deviceNames = alloca((i + 1) * sizeof(*devices)); + if (loaderData->netDev && (loaderData->netDev_set) == 1) { + if ((loaderData->bootIf && (loaderData->bootIf_set) == 1) && + !strcasecmp(loaderData->netDev, "bootif")) { + ksMacAddr = g_ascii_strup(loaderData->bootIf, -1); + } else { + ksMacAddr = g_ascii_strup(loaderData->netDev, -1); + } + } + + for (i = 0; devs[i]; i++) { + if (!devs[i]->device) + continue; + + if (devs[i]->description) { + deviceNames[deviceNums] = alloca(strlen(devs[i]->device) + + strlen(devs[i]->description) + 4); + sprintf(deviceNames[deviceNums],"%s - %.50s", + devs[i]->device, devs[i]->description); + + if (strlen(deviceNames[deviceNums]) > max) + max = strlen(deviceNames[deviceNums]); + + devices[deviceNums] = devs[i]->device; + } else { + devices[deviceNums] = devs[i]->device; + deviceNames[deviceNums] = devs[i]->device; + } + + deviceNums++; + + /* this device has been set and we don't really need to ask + * about it again... */ + if (loaderData->netDev && (loaderData->netDev_set == 1)) { + if (!strcmp(loaderData->netDev, devs[i]->device)) { + foundDev = 1; + } else if (ksMacAddr != NULL) { + /* maybe it's a mac address */ + char *devmacaddr = iface_mac2str(devs[i]->device); + + if ((devmacaddr != NULL) && !strcmp(ksMacAddr, devmacaddr)) { + foundDev = 1; + free(loaderData->netDev); + loaderData->netDev = devs[i]->device; + if (devmacaddr != NULL) + free(devmacaddr); + break; + } + + if (devmacaddr != NULL) + free(devmacaddr); + } + } + } + + if (ksMacAddr) + free(ksMacAddr); + if (foundDev == 1) + return LOADER_NOOP; + + devices[deviceNums] = NULL; + deviceNames[deviceNums] = NULL; + qsort(devices, deviceNums, sizeof(*devices), simpleStringCmp); + qsort(deviceNames, deviceNums, sizeof(*devices), simpleStringCmp); + + /* ASSERT: we should *ALWAYS* have a network device when we get here */ + if (!deviceNums) { + logMessage(CRITICAL, "no network device in chooseNetworkInterface"); + return LOADER_ERROR; + } + + /* JKFIXME: if we only have one interface and it doesn't have link, + * do we go ahead? */ + if (deviceNums == 1) { + logMessage(INFO, "only have one network device: %s", devices[0]); + loaderData->netDev = devices[0]; + return LOADER_NOOP; + } + + while ((loaderData->netDev && (loaderData->netDev_set == 1)) && + !strcmp(loaderData->netDev, "ibft")) { + char *devmacaddr = NULL; + char *ibftmacaddr = ""; + + /* get MAC from the iBFT table */ + if (!(ibftmacaddr = ibft_iface_mac())) { /* iBFT not present or error */ + lookForLink = 0; + break; + } + + logMessage(INFO, "looking for iBFT configured device %s with link", + ibftmacaddr); + lookForLink = 0; + + for (i = 0; devs[i]; i++) { + if (!devs[i]->device) + continue; + + devmacaddr = iface_mac2str(devs[i]->device); + + if(!strcasecmp(devmacaddr, ibftmacaddr)){ + logMessage(INFO, + "%s has the right MAC (%s), checking for link", + devmacaddr, devices[i]); + free(devmacaddr); + + if (get_link_status(devices[i]) == 1) { + lookForLink = 0; + loaderData->netDev = devices[i]; + logMessage(INFO, "%s has link, using it", devices[i]); + + /* set the IP method to ibft if not requested differently */ + if (loaderData->ipv4 == NULL) { + loaderData->ipv4 = strdup("ibft"); + logMessage(INFO, + "%s will be configured using iBFT values", + devices[i]); + } + + return LOADER_NOOP; + } else { + logMessage(INFO, "%s has no link, skipping it", devices[i]); + } + + break; + } else { + free(devmacaddr); + } + } + + break; + } + + if ((loaderData->netDev && (loaderData->netDev_set == 1)) && + !strcmp(loaderData->netDev, "link")) { + lookForLink = 1; + } + + if (lookForLink) { + logMessage(INFO, "looking for first netDev with link"); + + for (rc = 0; rc < 5; rc++) { + for (i = 0; i < deviceNums; i++) { + if (get_link_status(devices[i]) == 1) { + loaderData->netDev = devices[i]; + logMessage(INFO, "%s has link, using it", devices[i]); + return LOADER_NOOP; + } + } + + sleep(1); + } + + logMessage(WARNING, + "wanted netdev with link, but none present. prompting"); + } + + if (FL_CMDLINE(flags)) { + fprintf(stderr, "No way to determine which NIC to use, and cannot " + "prompt in cmdline\nmode. Halting.\n"); + fprintf(stderr, "Please use the ksdevice= parameter to specify the " + "device name (e.g., eth0)\n or the MAC address of " + "the NIC to use for installation.\n"); + exit(1); + } + + startNewt(); + + if (max > 70) + max = 70; + + /* JKFIXME: should display link status */ + deviceNum = 0; + ask = 1; + while (ask) { + rc = newtWinMenu(_("Networking Device"), + _("You have multiple network devices on this system. " + "Which would you like to install through?"), + max, 10, 10, + deviceNums < 6 ? deviceNums : 6, deviceNames, + &deviceNum, _("OK"), _("Identify"), _("Back"), NULL); + + if (rc == 2) { + if (!devices[deviceNum]) { + logMessage(ERROR, "NIC %d contains no device name", deviceNum); + continue; + } + + checked_asprintf(&idstr, "%s %s %s", + _("You can identify the physical port for"), + devices[deviceNum], + _("by flashing the LED lights for a number of " + "seconds. Enter a number between 1 and 30 to " + "set the duration to flash the LED port " + "lights.")); + + i = 1; + while (i) { + idrc = newtWinEntries(_("Identify NIC"), idstr, 50, 5, 15, 24, + entry, _("OK"), _("Back"), NULL); + + if (idrc == 0 || idrc == 1) { + errno = 0; + secs = strtol((const char *) seconds, NULL, 10); + if (errno == EINVAL || errno == ERANGE) { + logMessage(ERROR, "strtol() failure in %s: %m", + __func__); + continue; + } + + if (secs <=0 || secs > 300) { + newtWinMessage(_("Invalid Duration"), _("OK"), + _("You must enter the number of " + "seconds as an integer between 1 " + "and 30.")); + continue; + } + + idrc = 41 + strlen(devices[deviceNum]); + if (secs > 9) { + idrc += 1; + } + + winStatus(idrc, 3, NULL, + _("Flashing %s port lights for %d seconds."), + devices[deviceNum], secs); + + if (identifyNIC(devices[deviceNum], secs)) { + logMessage(ERROR, + "error during physical NIC identification"); + } + + newtPopWindow(); + i = 0; + } else if (idrc == 2) { + i = 0; + } + } + } else if (rc == 3) { + ask = 0; + return LOADER_BACK; + } else { + ask = 0; + } + } + + loaderData->netDev = devices[deviceNum]; + return LOADER_OK; +} + +/* JKFIXME: bad name. this function brings up networking early on a + * kickstart install so that we can do things like grab the ks.cfg from + * the network */ +int kickstartNetworkUp(struct loaderData_s * loaderData, iface_t * iface) { + int rc, err; + + if (is_nm_connected() == TRUE) + return 0; + + memset(iface, 0, sizeof(*iface)); + + do { + do { + /* this is smart and does the right thing based on whether or not + * we have ksdevice= specified */ + rc = chooseNetworkInterface(loaderData); + + if (rc == LOADER_ERROR) { + /* JKFIXME: ask for a driver disk? */ + logMessage(ERROR, "no network drivers for doing kickstart"); + return -1; + } else if (rc == LOADER_BACK) { + return -1; + } + + /* insert device into iface structure */ + strcpy(iface->device, loaderData->netDev); + + break; + } while (1); + + /* we don't want to end up asking about interface more than once + * if we're in a kickstart-ish case (#100724) */ + loaderData->netDev_set = 1; + + /* default to DHCP for IPv4 if nothing is provided */ + if (loaderData->ipv4 == NULL) { + loaderData->ipv4 = strdup("dhcp"); + loaderData->ipinfo_set = 1; + } + + setupIfaceStruct(iface, loaderData); + rc = readNetConfig(loaderData->netDev, iface, loaderData->netCls, + loaderData->method); + + if (rc == LOADER_ERROR) { + logMessage(ERROR, "unable to setup networking"); + return -1; + } else if (rc == LOADER_BACK) { + /* Going back to the interface selection screen, so unset anything + * we set before attempting to bring the incorrect interface up. + */ + if ((rc = writeDisabledNetInfo()) != 0) { + logMessage(ERROR, "writeDisabledNetInfo failure (%s): %d", + __func__, rc); + } + + loaderData->netDev_set = 0; + loaderData->ipinfo_set = 0; + free(loaderData->ipv4); + loaderData->ipv4 = NULL; + break; + } else { + break; + } + + err = writeEnabledNetInfo(iface); + if (err) { + logMessage(ERROR, + "failed to write %s data for %s (%d)", + SYSCONFIG_PATH, iface->device, err); + return -1; + } + + err = get_connection(iface); + newtPopWindow(); + + if (err) { + logMessage(ERROR, "failed to start NetworkManager (%d)", err); + return -1; + } + } while (1); + + return 0; +} + +void splitHostname (char *str, char **host, char **port) +{ + char *rightbrack = strchr(str, ']'); + char *firstcolon = strchr(str, ':'); + char *secondcolon = strrchr(str, ':'); + + *host = NULL; + *port = NULL; + + if (*str == '[' && rightbrack) { + /* An IPv6 address surrounded by brackets, optionally with a colon and + * port number. + */ + char *colon = strrchr(rightbrack, ':'); + + if (colon) { + *host = strndup(str+1, rightbrack-1-str); + *port = strdup(colon+1); + } + else + *host = strndup(str+1, rightbrack-1-str); + } else if (firstcolon && secondcolon && firstcolon != secondcolon) { + /* An IPv6 address without brackets. Don't make the user surround the + * address with brackets if there's no port number. + */ + *host = strdup(str); + } else { + /* An IPv4 address, optionally with a colon and port number. */ + char *colon = strrchr(str, ':'); + + if (colon) { + *host = strndup(str, colon-str); + *port = strdup(colon+1); + } + else + *host = strdup(str); + } +} + +/* + * Start NetworkManager and wait for a valid link, return non-zero on error. + */ +int get_connection(iface_t *iface) { + int count = 0; + NMClient *client = NULL; + NMState state; + GMainLoop *loop; + GMainContext *ctx; + + if (iface == NULL) { + return 1; + } + + logMessage(DEBUGLVL, "configuring device %s", iface->device); + + /* display status */ + if (FL_CMDLINE(flags)) { + printf(_("Waiting for NetworkManager to configure %s.\n"), + iface->device); + } else { + winStatus(55, 3, NULL, + _("Waiting for NetworkManager to configure %s.\n"), + iface->device, 0); + } + + g_type_init(); + + client = nm_client_new(); + if (!client) { + logMessage(ERROR, "%s (%d): could not connect to system bus", + __func__, __LINE__); + return 2; + } + + /* Create a loop for processing dbus signals */ + loop = g_main_loop_new(NULL, FALSE); + ctx = g_main_loop_get_context(loop); + + /* pump the loop until all the messages are clear */ + while (g_main_context_pending (ctx)) { + g_main_context_iteration (ctx, FALSE); + } + + /* send message and block until a reply or error comes back */ + while (count < 45) { + /* pump the loop again to clear the messages */ + while (g_main_context_pending (ctx)) { + g_main_context_iteration (ctx, FALSE); + } + state = nm_client_get_state(client); + + if (state == NM_STATE_CONNECTED) { + logMessage(INFO, "%s (%d): NetworkManager connected", + __func__, __LINE__); + res_init(); + g_object_unref(client); + return 0; + } + + sleep(1); + count++; + } + + g_main_loop_unref(loop); + g_object_unref(client); + return 3; +} + +/* vim:set shiftwidth=4 softtabstop=4: */ diff --git a/loader/net.h b/loader/net.h new file mode 100644 index 0000000..8245084 --- /dev/null +++ b/loader/net.h @@ -0,0 +1,77 @@ +/* + * net.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef H_LOADER_NET +#define H_LOADER_NET + +#include <newt.h> +#include "../isys/iface.h" +#include "loader.h" + +#define DHCP_METHOD_STR _("Dynamic IP configuration (DHCP)") +#define MANUAL_METHOD_STR _("Manual configuration") +#ifdef ENABLE_IPV6 +#define DHCPV6_METHOD_STR _("Dynamic IP configuration (DHCPv6)") +#define AUTO_METHOD_STR _("Automatic neighbor discovery") +#endif + +#define SYSCONFIG_PATH "/etc/sysconfig" +#define NETWORK_SCRIPTS_PATH "/etc/sysconfig/network-scripts" + +struct intfconfig_s { + newtComponent ipv4Entry, cidr4Entry; + newtComponent gwEntry, nsEntry; + const char *ipv4, *cidr4; +#ifdef ENABLE_IPV6 + newtComponent ipv6Entry, cidr6Entry; + const char *ipv6, *cidr6; + const char *gw6; +#endif + const char *gw, *ns; +}; + +struct netconfopts { + char ipv4Choice; +#ifdef ENABLE_IPV6 + char ipv6Choice; +#endif +}; + +typedef int int32; + +int readNetConfig(char * device, iface_t * iface, + char * dhcpclass, int methodNum); +int configureTCPIP(char * device, iface_t * iface, struct netconfopts * opts, + int methodNum); +int manualNetConfig(char * device, iface_t * iface, + struct intfconfig_s * ipcomps, struct netconfopts * opts); +void debugNetworkInfo(iface_t * iface); +int writeDisabledNetInfo(void); +int writeEnabledNetInfo(iface_t * iface); +int chooseNetworkInterface(struct loaderData_s * loaderData); +void setupIfaceStruct(iface_t * iface, struct loaderData_s * loaderData); +int setupWireless(iface_t * iface); +void setKickstartNetwork(struct loaderData_s * loaderData, int argc, + char ** argv); +int kickstartNetworkUp(struct loaderData_s * loaderData, + iface_t * iface); +void splitHostname (char *str, char **host, char **port); +int get_connection(iface_t * iface); + +#endif diff --git a/loader/nfsinstall.c b/loader/nfsinstall.c new file mode 100644 index 0000000..ae13874 --- /dev/null +++ b/loader/nfsinstall.c @@ -0,0 +1,556 @@ +/* + * nfsinstall.c - code to set up nfs installs + * + * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, + * 2006, 2007 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> + * Matt Wilson <msw@redhat.com> + * Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + */ + +#include <fcntl.h> +#include <newt.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <glib.h> +#include <nm-client.h> +#include <nm-device.h> +#include <nm-dhcp4-config.h> + +#include "copy.h" +#include "loader.h" +#include "lang.h" +#include "loadermisc.h" +#include "kickstart.h" +#include "method.h" +#include "nfsinstall.h" +#include "net.h" +#include "cdinstall.h" +#include "windows.h" + +#include "../isys/imount.h" +#include "../isys/iface.h" +#include "../isys/log.h" + +/* boot flags */ +extern uint64_t flags; + +static int nfsGetSetup(char ** hostptr, char ** dirptr, char ** optsptr) { + struct newtWinEntry entries[4]; + char * buf; + char * newServer = *hostptr ? strdup(*hostptr) : NULL; + char * newDir = *dirptr ? strdup(*dirptr) : NULL; + char * newMountOpts = *optsptr ? strdup(*optsptr) : NULL; + int rc; + + entries[0].text = _("NFS server name:"); + entries[0].value = &newServer; + entries[0].flags = NEWT_FLAG_SCROLL; + + checked_asprintf(&entries[1].text, _("%s directory:"), getProductName()); + + entries[1].value = &newDir; + entries[1].flags = NEWT_FLAG_SCROLL; + entries[2].text = _("NFS mount options (optional):"); + entries[2].value = &newMountOpts; + entries[2].flags = NEWT_FLAG_SCROLL; + entries[3].text = NULL; + entries[3].value = NULL; + + if (asprintf(&buf, _("Please enter the server and path to your %s " + "installation image and optionally additional " + "NFS mount options."), getProductName()) == -1) { + logMessage(CRITICAL, "%s: %d: %m", __func__, __LINE__); + abort(); + } + + do { + rc = newtWinEntries(_("NFS Setup"), buf, 60, 5, 15, + 24, entries, _("OK"), _("Back"), NULL); + } while ((!strcmp(newServer, "") || !strcmp(newDir, "")) && rc != 2); + + free(buf); + free(entries[1].text); + + if (rc == 2) { + if (newServer) free(newServer); + if (newDir) free(newDir); + if (newMountOpts) free(newMountOpts); + return LOADER_BACK; + } + + if (*hostptr) free(*hostptr); + if (*dirptr) free(*dirptr); + if (*optsptr) free(*optsptr); + *hostptr = newServer; + *dirptr = newDir; + *optsptr = newMountOpts; + + return 0; +} + +void parseNfsHostPathOpts(char *url, char **host, char **path, char **opts) { + char *tmp; + char *hostsrc; + + logMessage(DEBUGLVL, "parseNfsHostPathOpts url: |%s|", url); + + hostsrc = strdup(url); + *host = hostsrc; + tmp = strchr(*host, ':'); + + if (tmp) { + *path = strdup(tmp + 1); + *tmp = '\0'; + } + else { + *path = malloc(sizeof(char *)); + **path = '\0'; + } + + tmp = strchr(*path, ':'); + if (tmp && (strlen(tmp) > 1)) { + char * c = tmp; + *opts = *host; + *host = *path; + *path = strdup(c + 1); + *c = '\0'; + } else { + *opts = NULL; + } + + logMessage(DEBUGLVL, "parseNfsHostPathOpts host: |%s|", *host); + logMessage(DEBUGLVL, "parseNfsHostPathOpts path: |%s|", *path); + logMessage(DEBUGLVL, "parseNfsHostPathOpts opts: |%s|", *opts); +} + +static void addDefaultKickstartFile(char **file, char *ip) { + /* if the filename ends with / or is null, use default kickstart + * name of IP_ADDRESS-kickstart appended to *file + */ + if ((*file) && (((*file)[strlen(*file) - 1] == '/') || + ((*file)[strlen(*file) - 1] == '\0'))) { + checked_asprintf(file, "%s%s-kickstart", *file, ip); + logMessage(DEBUGLVL, "addDefaultKickstartFile file: |%s|", *file); + } +} + +char * mountNfsImage(struct installMethod * method, + char * location, struct loaderData_s * loaderData) { + char * host = NULL; + char * directory = NULL; + char * mountOpts = NULL; + char * fullPath = NULL; + char * url = NULL; + + enum { NFS_STAGE_NFS, NFS_STAGE_MOUNT, NFS_STAGE_DONE, + NFS_STAGE_UPDATES } stage = NFS_STAGE_NFS; + + int rc; + + /* JKFIXME: ASSERT -- we have a network device setup when we get here */ + while (stage != NFS_STAGE_DONE) { + switch (stage) { + case NFS_STAGE_NFS: + if (loaderData->method == METHOD_NFS && loaderData->stage2Data) { + host = ((struct nfsInstallData *)loaderData->stage2Data)->host; + directory = ((struct nfsInstallData *)loaderData->stage2Data)->directory; + + if (((struct nfsInstallData *) + loaderData->stage2Data)->mountOpts == NULL) { + mountOpts = strdup("ro"); + } else { + checked_asprintf(&mountOpts, "ro,%s", + ((struct nfsInstallData *) + loaderData->stage2Data)->mountOpts); + } + + logMessage(INFO, "host is %s, dir is %s, opts are '%s'", host, directory, mountOpts); + + if (!host || !directory) { + logMessage(ERROR, "missing host or directory specification"); + + if (loaderData->inferredStage2) + loaderData->invalidRepoParam = 1; + + loaderData->method = -1; + break; + } else { + host = strdup(host); + directory = strdup(directory); + } + } else { + char *colonopts, *substr, *tmp; + + logMessage(INFO, "going to do nfsGetSetup"); + if (nfsGetSetup(&host, &directory, &mountOpts) == LOADER_BACK) { + loaderData->stage2Data = NULL; + return NULL; + } + + /* If the user-provided URL points at a repo instead of a + * stage2 image, fix that up now. + */ + substr = strstr(directory, ".img"); + if (!substr || (substr && *(substr+4) != '\0')) { + if (mountOpts && strlen(mountOpts)) { + checked_asprintf(&colonopts, ":%s", mountOpts); + } else { + colonopts = strdup(""); + } + + checked_asprintf(&(loaderData->instRepo), "nfs%s:%s:%s", + colonopts, host, directory); + checked_asprintf(&tmp, "nfs%s:%s:%s/images/install.img", + colonopts, host, directory); + + setStage2LocFromCmdline(tmp, loaderData); + free(host); + free(directory); + free(mountOpts); + free(colonopts); + free(tmp); + continue; + } + + loaderData->invalidRepoParam = 1; + } + + stage = NFS_STAGE_MOUNT; + break; + + case NFS_STAGE_MOUNT: { + char *buf; + + checked_asprintf(&fullPath, "%s:%.*s", host, + (int) (strrchr(directory, '/')-directory), + directory); + logMessage(INFO, "mounting nfs path %s", fullPath); + + stage = NFS_STAGE_NFS; + + if (!doPwMount(fullPath, "/mnt/stage2", "nfs", mountOpts, NULL)) { + checked_asprintf(&buf, "/mnt/stage2/%s", + strrchr(directory, '/')); + + if (!access(buf, R_OK)) { + logMessage(INFO, "can access %s", buf); + rc = mountStage2(buf); + + if (rc == 0) { + stage = NFS_STAGE_UPDATES; + checked_asprintf(&url, "nfs:%s:%s", host, + directory); + free(buf); + break; + } else { + logMessage(WARNING, "unable to mount %s", buf); + free(buf); + break; + } + } else { + logMessage(WARNING, "unable to access %s", buf); + free(buf); + umount("/mnt/stage2"); + } + } else { + newtWinMessage(_("Error"), _("OK"), + _("That directory could not be mounted from " + "the server.")); + if (loaderData->method >= 0) + loaderData->method = -1; + + if (loaderData->inferredStage2) + loaderData->invalidRepoParam = 1; + + break; + } + + checked_asprintf(&buf, + _("That directory does not seem to " + "contain a %s installation image."), + getProductName()); + + newtWinMessage(_("Error"), _("OK"), buf); + free(buf); + + if (loaderData->method >= 0) + loaderData->method = -1; + + if (loaderData->inferredStage2) + loaderData->invalidRepoParam = 1; + + break; + } + + case NFS_STAGE_UPDATES: { + char *buf; + + checked_asprintf(&buf, "%.*s/RHupdates", + (int) (strrchr(fullPath, '/')-fullPath), + fullPath); + + logMessage(INFO, "mounting nfs path %s for updates", buf); + + if (!doPwMount(buf, "/tmp/update-disk", "nfs", mountOpts, NULL)) { + logMessage(INFO, "Using RHupdates/ for NFS install"); + copyDirectory("/tmp/update-disk", "/tmp/updates", NULL, NULL); + umount("/tmp/update-disk"); + unlink("/tmp/update-disk"); + } else { + logMessage(INFO, "No RHupdates/ directory found, skipping"); + } + + stage = NFS_STAGE_DONE; + break; + } + + case NFS_STAGE_DONE: + break; + } + } + + free(host); + free(directory); + if (mountOpts) + free(mountOpts); + if (fullPath) + free(fullPath); + + return url; +} + + +void setKickstartNfs(struct loaderData_s * loaderData, int argc, + char ** argv) { + char *substr = NULL; + gchar *host = NULL, *dir = NULL, *mountOpts = NULL; + GOptionContext *optCon = g_option_context_new(NULL); + GError *optErr = NULL; + GOptionEntry ksNfsOptions[] = { + { "server", 0, 0, G_OPTION_ARG_STRING, &host, NULL, NULL }, + { "dir", 0, 0, G_OPTION_ARG_STRING, &dir, NULL, NULL }, + { "opts", 0, 0, G_OPTION_ARG_STRING, &mountOpts, NULL, NULL }, + { NULL }, + }; + + logMessage(INFO, "kickstartFromNfs"); + + g_option_context_set_help_enabled(optCon, FALSE); + g_option_context_add_main_entries(optCon, ksNfsOptions, NULL); + + if (!g_option_context_parse(optCon, &argc, &argv, &optErr)) { + startNewt(); + newtWinMessage(_("Kickstart Error"), _("OK"), + _("Bad argument to NFS kickstart method " + "command: %s"), optErr->message); + g_error_free(optErr); + g_option_context_free(optCon); + return; + } + + g_option_context_free(optCon); + + if (!host || !dir) { + logMessage(ERROR, "host and directory for nfs kickstart not specified"); + return; + } + + loaderData->method = METHOD_NFS; + loaderData->stage2Data = NULL; + + substr = strstr(dir, ".img"); + if (!substr || (substr && *(substr+4) != '\0')) { + checked_asprintf(&(loaderData->instRepo), "nfs:%s:%s", host, dir); + + logMessage(INFO, "results of nfs, host is %s, dir is %s, opts are '%s'", + host, dir, mountOpts); + } else { + loaderData->stage2Data = calloc(sizeof(struct nfsInstallData *), 1); + ((struct nfsInstallData *)loaderData->stage2Data)->host = host; + ((struct nfsInstallData *)loaderData->stage2Data)->directory = dir; + ((struct nfsInstallData *)loaderData->stage2Data)->mountOpts = mountOpts; + + logMessage(INFO, "results of nfs, host is %s, dir is %s, opts are '%s'", + ((struct nfsInstallData *) loaderData->stage2Data)->host, + ((struct nfsInstallData *) loaderData->stage2Data)->directory, + ((struct nfsInstallData *) loaderData->stage2Data)->mountOpts); + } +} + + +int getFileFromNfs(char * url, char * dest, struct loaderData_s * loaderData) { + char * host = NULL, *path = NULL, * file = NULL, * opts = NULL; + char * chk = NULL, *ip = NULL; + int failed = 0, i = 0; + iface_t iface; + NMClient *client = NULL; + NMState state; + const GPtrArray *devices; + + if (kickstartNetworkUp(loaderData, &iface)) { + logMessage(ERROR, "unable to bring up network"); + return 1; + } + + /* if they just did 'linux ks', they want us to figure it out from + * the dhcp/bootp information + */ + if (!url) { + g_type_init(); + + client = nm_client_new(); + if (!client) { + logMessage(CRITICAL, "%s (%d): failure creating NM proxy", + __func__, __LINE__); + return 1; + } + + state = nm_client_get_state(client); + if (state != NM_STATE_CONNECTED) { + logMessage(ERROR, "%s (%d): no active network devices", + __func__, __LINE__); + g_object_unref(client); + return 1; + } + + devices = nm_client_get_devices(client); + for (i = 0; i < devices->len; i++) { + NMDevice *candidate = g_ptr_array_index(devices, i); + const char *devname = nm_device_get_iface(candidate); + NMDHCP4Config *dhcp = NULL; + const char *server_name = NULL; + const char *filename = NULL; + struct in_addr addr; + char nextserver[INET_ADDRSTRLEN+1]; + + if (nm_device_get_state(candidate) != NM_DEVICE_STATE_ACTIVATED) + continue; + + if (strcmp(iface.device, devname)) + continue; + + dhcp = nm_device_get_dhcp4_config(candidate); + if (!dhcp) { + logMessage(ERROR, "no boot options received by DHCP"); + continue; + } + + server_name = nm_dhcp4_config_get_one_option(dhcp, "server_name"); + if (!server_name) { + logMessage(ERROR, "no bootserver was found"); + g_object_unref(client); + return 1; + } + + /* 'server_name' may be a hostname or an IPv4 address */ + memset(&nextserver, '\0', sizeof(nextserver)); + if (inet_pton(AF_INET, server_name, &addr) >= 1) { + strcpy(nextserver, server_name); + } else { + struct hostent *he = gethostbyname(server_name); + if (he != NULL) { + if (inet_ntop(AF_INET, he->h_addr_list[0], + nextserver, INET_ADDRSTRLEN) == NULL) { + memset(&nextserver, '\0', sizeof(nextserver)); + } + } + } + + filename = nm_dhcp4_config_get_one_option(dhcp, "filename"); + if (filename == NULL) { + checked_asprintf(&url, "%s:/kickstart/", nextserver); + logMessage(ERROR, "bootp: no bootfile received"); + } else { + checked_asprintf(&url, "%s:%s", nextserver, filename); + logMessage(INFO, "bootp: bootfile is %s", filename); + } + + break; + } + + g_object_unref(client); + } + + /* get the IP of the target system */ + if ((ip = iface_ip2str(loaderData->netDev, AF_INET)) == NULL) { + logMessage(ERROR, "iface_ip2str returned NULL"); + return 1; + } + + logMessage(INFO, "url is %s", url); + + parseNfsHostPathOpts(url, &host, &path, &opts); + addDefaultKickstartFile(&path, ip); + + /* nfs has to be a little bit different... split off the last part as + * the file and then concatenate host + dir path */ + file = strrchr(path, '/'); + if (!file) { + file = path; + } else { + *file++ ='\0'; + chk = host + strlen(host)-1; + + if (*chk == '/' || *path == '/') { + checked_asprintf(&host, "%s:%s", host, path); + } else { + checked_asprintf(&host, "%s:/%s", host, path); + } + } + + logMessage(INFO, "file location: nfs:%s/%s", host, file); + + if (!doPwMount(host, "/tmp/mnt", "nfs", opts, NULL)) { + char * buf; + + checked_asprintf(&buf, "/tmp/mnt/%s", file); + + if (copyFile(buf, dest)) { + logMessage(ERROR, "failed to copy file to %s", dest); + failed = 1; + } + + free(buf); + } else { + logMessage(ERROR, "failed to mount nfs source"); + failed = 1; + } + + free(host); + free(path); + if (ip) free(ip); + + umount("/tmp/mnt"); + unlink("/tmp/mnt"); + + return failed; +} + +int kickstartFromNfs(char * url, struct loaderData_s * loaderData) { + return getFileFromNfs(url, "/tmp/ks.cfg", loaderData); +} + +/* vim:set shiftwidth=4 softtabstop=4: */ diff --git a/loader/nfsinstall.h b/loader/nfsinstall.h new file mode 100644 index 0000000..99a8b06 --- /dev/null +++ b/loader/nfsinstall.h @@ -0,0 +1,40 @@ +/* + * nfsinstall.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef NFSINSTALL_H +#define NFSINSTALL_H + +#include "method.h" + +struct nfsInstallData { + char * host; + char * directory; + char * mountOpts; +}; + + +void setKickstartNfs(struct loaderData_s * loaderData, int argc, + char ** argv); +int kickstartFromNfs(char * url, struct loaderData_s * loaderData); +char * mountNfsImage(struct installMethod * method, + char * location, struct loaderData_s * loaderData); +int getFileFromNfs(char * url, char * dest, struct loaderData_s * loaderData); +void parseNfsHostPathOpts(char * url, char ** host, char ** path, char ** opts); + +#endif diff --git a/loader/rpmextract.c b/loader/rpmextract.c new file mode 100644 index 0000000..d1549b8 --- /dev/null +++ b/loader/rpmextract.c @@ -0,0 +1,325 @@ +/* unpack the payload of RPM package to the current directory + * + * File name: rpmextract.c + * Date: 2009/12/18 + * Author: Martin Sivak <msivak at redhat dot com> + * + * Copyright (C) 2009 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/>. + * + * */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <rpm/rpmlib.h> /* rpmReadPackageFile .. */ +#include <rpm/rpmtag.h> +#include <rpm/rpmio.h> +#include <rpm/rpmpgp.h> + +#include <rpm/rpmts.h> + +#include <stdio.h> +#include <archive.h> +#include <archive_entry.h> + +#include "loader.h" +#include "rpmextract.h" + +#include "../isys/log.h" + +/* + * internal structure to pass to libarchive callbacks + */ + +struct cpio_mydata { + FD_t gzdi; + char *buffer; +}; + +/* + * libarchive callbacks + */ + +ssize_t rpm_myread(struct archive *a, void *client_data, const void **buff) +{ + struct cpio_mydata *mydata = client_data; + *buff = mydata->buffer; + return Fread(mydata->buffer, BUFFERSIZE, 1, mydata->gzdi); +} + +int rpm_myclose(struct archive *a, void *client_data) +{ + struct cpio_mydata *mydata = client_data; + if (mydata->gzdi > 0) + Fclose(mydata->gzdi); + return ARCHIVE_OK; +} + +/* read data from RPM header */ + +const char * headerGetString(Header h, rpmTag tag) +{ + const char *res = NULL; + struct rpmtd_s td; + + if (headerGet(h, tag, &td, HEADERGET_MINMEM)) { + if (rpmtdCount(&td) == 1) { + res = rpmtdGetString(&td); + } + rpmtdFreeData(&td); + } + return res; +} + +/* + * explode source RPM into the current directory + * use filters to skip packages and files we do not need + */ +int explodeRPM(const char *source, + filterfunc filter, + dependencyfunc provides, + dependencyfunc deps, + void* userptr) +{ + char buffer[BUFFERSIZE+1]; /* make space for trailing \0 */ + FD_t fdi; + Header h; + char * rpmio_flags = NULL; + rpmRC rc; + FD_t gzdi; + struct archive *cpio; + struct archive_entry *cpio_entry; + struct cpio_mydata cpio_mydata; + + rpmts ts; + rpmVSFlags vsflags; + const char *compr; + + if (strcmp(source, "-") == 0) + fdi = fdDup(STDIN_FILENO); + else + fdi = Fopen(source, "r.ufdio"); + + if (Ferror(fdi)) { + const char *srcname = (strcmp(source, "-") == 0) ? "<stdin>" : source; + logMessage(ERROR, "%s: %s\n", srcname, Fstrerror(fdi)); + return EXIT_FAILURE; + } + rpmReadConfigFiles(NULL, NULL); + + /* Initialize RPM transaction */ + ts = rpmtsCreate(); + vsflags = 0; + + /* Do not check digests, signatures or headers */ + vsflags |= _RPMVSF_NODIGESTS; + vsflags |= _RPMVSF_NOSIGNATURES; + vsflags |= RPMVSF_NOHDRCHK; + (void) rpmtsSetVSFlags(ts, vsflags); + + rc = rpmReadPackageFile(ts, fdi, "rpm2dir", &h); + + ts = rpmtsFree(ts); + + switch (rc) { + case RPMRC_OK: + case RPMRC_NOKEY: + case RPMRC_NOTTRUSTED: + break; + case RPMRC_NOTFOUND: + logMessage(ERROR, "%s is not an RPM package", source); + return EXIT_FAILURE; + break; + case RPMRC_FAIL: + default: + logMessage(ERROR, "error reading header from %s package\n", source); + return EXIT_FAILURE; + break; + } + + /* Retrieve all dependencies and run them through deps function */ + while (deps) { + struct rpmtd_s td; + const char *depname; + + if (!headerGet(h, RPMTAG_REQUIRENAME, &td, HEADERGET_MINMEM)) + break; + + /* iterator */ + while ((depname = rpmtdNextString(&td))) { + if (deps(depname, userptr)) { + Fclose(fdi); + return EXIT_BADDEPS; + } + } + rpmtdFreeData(&td); + break; + } + + /* Retrieve all provides and run them through provides function */ + while (provides) { + struct rpmtd_s td; + const char *depname; + int found = 0; + + if (!headerGet(h, RPMTAG_PROVIDES, &td, HEADERGET_MINMEM)) + break; + + /* iterator */ + while ((depname = rpmtdNextString(&td))) { + if (!provides(depname, userptr)) { + found++; + } + } + rpmtdFreeData(&td); + if (found<=0) + return EXIT_BADDEPS; + break; + } + + /* Retrieve type of payload compression. */ + compr = headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR); + if (compr && strcmp(compr, "gzip")) { + checked_asprintf(&rpmio_flags, "r.%sdio", compr); + } + else { + checked_asprintf(&rpmio_flags, "r.gzdio"); + } + + /* Open uncompressed cpio stream */ + gzdi = Fdopen(fdi, rpmio_flags); + free(rpmio_flags); + + if (gzdi == NULL) { + logMessage(ERROR, "cannot re-open payload: %s\n", Fstrerror(gzdi)); + return EXIT_FAILURE; + } + + /* initialize cpio decompressor */ + cpio = archive_read_new(); + if (cpio==NULL) { + Fclose(gzdi); + return -1; + } + + cpio_mydata.gzdi = gzdi; + cpio_mydata.buffer = buffer; + archive_read_support_compression_all(cpio); + archive_read_support_format_all(cpio); + rc = archive_read_open(cpio, &cpio_mydata, NULL, rpm_myread, rpm_myclose); + + /* check the status of archive_open */ + if (rc != ARCHIVE_OK){ + Fclose(gzdi); + return -1; + } + + /* read all files in cpio archive */ + while ((rc = archive_read_next_header(cpio, &cpio_entry)) == ARCHIVE_OK){ + const struct stat *fstat; + int64_t fsize; + const char* filename; + int needskip = 1; /* do we need to read the data to get to the next header? */ + int offset = 0; + int towrite = 0; + + filename = archive_entry_pathname(cpio_entry); + fstat = archive_entry_stat(cpio_entry); + fsize = archive_entry_size(cpio_entry); + + /* Strip leading slashes */ + while (filename[offset] == '/') + offset+=1; + + /* Strip leading ./ */ + while (filename[offset] == '.' && filename[offset+1] == '/') + offset+=2; + + /* Other file type - we do not care except special cases */ + if (!S_ISREG(fstat->st_mode)) + towrite = 1; + else + towrite = 2; + + if (filter && filter(filename+offset, fstat, userptr)) { + /* filter this file */ + towrite = 0; + } + + /* Create directories */ + char* dirname = strdup(filename+offset); + + /* If the dup fails, let's hope the dirs already exist */ + if (dirname){ + char* dirptr = dirname; + while (dirptr && *dirptr) { + dirptr = strchr(dirptr, '/'); + if (dirptr) { + *dirptr = 0; + mkdir(dirname, 0700); + *dirptr = '/'; + dirptr++; + } + } + free(dirname); + } + + /* Regular file */ + if (towrite>=2) { + FILE *fdout = fopen(filename+offset, "w"); + + if (fdout==NULL){ + rc = 33; + break; + } + + rc = archive_read_data_into_fd(cpio, fileno(fdout)); + if (rc!=ARCHIVE_OK) { + /* XXX We didn't get the file.. well.. */ + needskip = 0; + } else { + needskip = 0; + fclose(fdout); + } + } + + /* symlink, we assume that the path contained in symlink + * is shorter than BUFFERSIZE */ + while (towrite && S_ISLNK(fstat->st_mode)) { + char symlinkbuffer[BUFFERSIZE-1]; + + needskip = 0; + if ((rc = archive_read_data(cpio, symlinkbuffer, fsize))!=ARCHIVE_OK) { + /* XXX We didn't get the file.. well.. */ + break; + } + + if (symlink(buffer, filename+offset)) { + logMessage(ERROR, "Failed to create symlink %s -> %s", filename+offset, buffer); + } + + break; + } + + if(needskip) + archive_read_data_skip(cpio); + } + + archive_read_finish(cpio); + + return rc != ARCHIVE_OK; +} diff --git a/loader/rpmextract.h b/loader/rpmextract.h new file mode 100644 index 0000000..53a90cf --- /dev/null +++ b/loader/rpmextract.h @@ -0,0 +1,45 @@ +/* + File name: rpmextract.h + Date: 2009/09/16 + Author: msivak + + Copyright (C) 2009 Red Hat, Inc. + + 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/>. +*/ + + +#ifndef __RPMEXTRACT_H__ +#define __RPMEXTRACT_H__ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#define EXIT_BADDEPS 4 +#define BUFFERSIZE 1024 + +/* both filter functions return 0 - match, 1 - match not found */ +typedef int (*filterfunc)(const char* name, const struct stat *fstat, void *userptr); +typedef int (*dependencyfunc)(const char* depends, void *userptr); + +int explodeRPM(const char* file, + filterfunc filter, + dependencyfunc provides, + dependencyfunc deps, + void* userptr); + +#endif + +/* end of rpmextract.h */ diff --git a/loader/selinux.c b/loader/selinux.c new file mode 100644 index 0000000..66bfe4d --- /dev/null +++ b/loader/selinux.c @@ -0,0 +1,56 @@ +/* + * selinux.c - Various SELinux related functionality needed for the loader. + * Portions extracted from libselinux which was released as public domain + * software by the NSA. + * + * Copyright (C) 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): Jeremy Katz <katzj@redhat.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <string.h> + +#include "loader.h" +#include "loadermisc.h" +#include "../isys/log.h" + +int loadpolicy() { + int pid, status; + + logMessage(INFO, "Loading SELinux policy"); + + if (!(pid = fork())) { + setenv("LD_LIBRARY_PATH", LIBPATH, 1); + execl("/sbin/load_policy", + "/sbin/load_policy", "-q", NULL); + logMessage(ERROR, "exec of load_policy failed: %m"); + exit(1); + } + + waitpid(pid, &status, 0); + if (WIFEXITED(status) && (WEXITSTATUS(status) != 0)) + return 1; + + return 0; +} + diff --git a/loader/selinux.h b/loader/selinux.h new file mode 100644 index 0000000..5877ddd --- /dev/null +++ b/loader/selinux.h @@ -0,0 +1,27 @@ +/* + * selinux.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef SELINUX_H +#define SELINUX_H + +int loadpolicy(); + +#define ANACONDA_CONTEXT "system_u:system_r:anaconda_t:s0" + +#endif diff --git a/loader/shutdown.c b/loader/shutdown.c new file mode 100644 index 0000000..3b481f2 --- /dev/null +++ b/loader/shutdown.c @@ -0,0 +1,153 @@ +/* + * shutdown.c + * + * Shutdown a running system. If built with -DAS_SHUTDOWN=1, then + * it builds a standalone shutdown binary. + * + * Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003 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/>. + */ + +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/reboot.h> +#include <sys/types.h> +#include <unistd.h> + +#include "init.h" + +void disableSwap(void); +void unmountFilesystems(void); + +static void performTerminations(void) { + 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"); +} + +static void performUnmounts(void) { + int ignore; + + printf("disabling swap...\n"); + disableSwap(); + + printf("unmounting filesystems...\n"); + unmountFilesystems(); + + printf("waiting for mdraid sets to become clean...\n"); + ignore = system("/sbin/mdadm --wait-clean --scan"); +} + +static void performReboot(reboot_action rebootAction) { + switch (rebootAction) { + case POWEROFF: + printf("powering off system\n"); + sleep(2); + reboot(RB_POWER_OFF); + break; + case REBOOT: + printf("rebooting system\n"); + sleep(2); +#if USE_MINILIBC + reboot(0xfee1dead, 672274793, 0x1234567); +#else + reboot(RB_AUTOBOOT); +#endif + break; + case HALT: + printf("halting system\n"); + reboot(RB_HALT_SYSTEM); + break; + default: + break; + } +} + +static void performDelayedReboot() +{ + printf("The system will be rebooted when you press Ctrl-C or Ctrl-Alt-Delete.\n"); + while (1) { + sleep(1); + } +} + +void shutDown(int doKill, reboot_action rebootAction) +{ + static int reentered = 0; + + if (reentered) { + performUnmounts(); + performTerminations(); + performReboot(rebootAction); + } + reentered = 1; + if (rebootAction != DELAYED_REBOOT && doKill) { + performUnmounts(); + performTerminations(); + performReboot(rebootAction); + } else { + performDelayedReboot(); + } + exit(0); +} + +#ifdef AS_SHUTDOWN +int main(int argc, char ** argv) { + int fd; + reboot_action rebootAction = HALT; + int doKill = 1; + int i = 1; + + while (i < argc) { + if (!strncmp("-r", argv[i], 2)) + rebootAction = REBOOT; + else if (!strncmp("--nokill", argv[i], 8)) + doKill = 0; + else if (!strncmp("-P", argv[i], 2)) + rebootAction = POWEROFF; + i++; + } + + /* ignore some signals so we don't kill ourself */ + signal(SIGINT, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + + /* now change to / */ + i = chdir("/"); + + /* redirect output to the real console */ + fd = open("/dev/console", O_RDWR); + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + close(fd); + + shutDown(doKill, rebootAction); + return 0; +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 ts=4: */ diff --git a/loader/simplemot b/loader/simplemot new file mode 100755 index 0000000..bebc1cf --- /dev/null +++ b/loader/simplemot @@ -0,0 +1,81 @@ +#!/usr/bin/perl +# +# simplemot +# +# Copyright (C) 2007 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/>. +# + +$inone = 0; +$intran = 0; +$total = 0; + +binmode(STDIN, ":raw"); +binmode(STDOUT, ":raw"); + +while (<>) { + if (!$inone && /^msgid/) { + chop; + $str = substr($_, 7, length($_) - 8); + $inone = 1; + } elsif ($inone && /^"/) { + chop; + $str .= substr($_, 1, length($_) - 2); + } elsif ($inone) { + $inone = 0; + + $str =~ s/\\n/\n/g; + $str =~ s/\\t/\t/g; + $str =~ s/\\"/"/g; + + # the string is complete -- calculate a hash + $sum = 0; + $xor = 0; + for ($i = 0; $i < length($str); $i++) { + $char = ord(substr($str, $i, 1)); + $sum += $char; + $xor ^= $char; + } + + $total = ($sum << 16) | (($xor & 0xFF) << 8) | (length($str) & 0xFF); + } + + if (!$intran && /^msgstr/) { + chop; + $tran = substr($_, 8, length($_) - 9); + $intran = 1; + } elsif ($intran && /^"/) { + chop; + $tran .= substr($_, 1, length($_) - 2); + } elsif ($intran) { + $intran = 0; + + $tran =~ s/\\n/\n/g; + $tran =~ s/\\t/\t/g; + $tran =~ s/\\"/"/g; + + if (!$total && $str) { + print STDERR "Missing string for $tran"; + exit 1 + } elsif ($str && $tran) { + print pack("Nn", $total, length($tran)); + print $tran; + + #if ($tran < 60) { + #printf STDERR ("0x%x %s\n", $total, $tran); + #} + } + } +} diff --git a/loader/telnet.c b/loader/telnet.c new file mode 100644 index 0000000..3c123ae --- /dev/null +++ b/loader/telnet.c @@ -0,0 +1,273 @@ +/* + * telnet.c -- basic telnet protocol handling for ttywatch + * + * Copyright (C) 2001 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): Michael K. Johnson <johnsonm@redhat.com> + */ + +/* Shamelessly stolen from ttywatch -- oot */ + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "telnet.h" +#include "../isys/log.h" + +#define IAC "\xff" +#define DONT "\xfe" +#define WONT "\xfc" +#define WILL "\xfb" +#define DO "\xfd" +#define SB "\xfa" +#define SE "\xf0" +#define ECHO "\x01" +#define SUPPRESS_GO_AHEAD "\x03" +#define TERMINAL_TYPE "\x18" +#define NAWS "\x1f" +#define LINEMODE "\x22" +#define NEWENVIRON "\x27" +#define MODE "\x01" + +/* Make a request. Not intended to be RFC-compatible, just enough + * to convince telnet clients to do what we want... To do this + * right, we would have to honestly negotiate, not speak blind. + * + * For now, assume all responses will be favorable and stripped + * out in telnet_process_input()... Sending it all in a single + * write makes it more efficient because it will all go out in a + * single packet, and the responses are more likely to all come + * back in a single packet (and thus, practically, a single read) + * too. + */ +void +telnet_negotiate(int socket, char ** term_type_ptr, int * heightPtr, + int * widthPtr) { + char ch; + int done = 0; + char * termType = NULL; + int termLength = 0, termAlloced = 0; + enum { ST_NONE, ST_TERMTYPE, ST_WINDOWSIZE } state; + char sizeBuf[4]; + int height = -1, width = -1; + char * sizePtr = sizeBuf; + char request[]= + IAC DONT ECHO + IAC WILL ECHO + IAC WILL NAWS + IAC WILL SUPPRESS_GO_AHEAD + IAC DO SUPPRESS_GO_AHEAD + IAC DONT NEWENVIRON + IAC WONT NEWENVIRON + IAC WONT LINEMODE + IAC DO NAWS + IAC SB TERMINAL_TYPE "\x01" IAC SE + ; + int ret; + + ret = write(socket, request, sizeof(request)-1); + + /* Read from the terminal until we get the terminal type. This will + do bad things if the client doesn't send the terminal type, but + those clients have existed for aeons (right?) */ + + do { + ret = read(socket, &ch, 1); + if (ch != '\xff') { + abort(); + } + + ret = read(socket, &ch, 1); /* command */ + + if (ch != '\xfa') { + ret = read(socket, &ch, 1); /* verb */ + continue; + } + + ret = read(socket, &ch, 1); /* suboption */ + if (ch == '\x18') { + state = ST_TERMTYPE; + ret = read(socket, &ch, 1); /* should be 0x0! */ + done = 1; + } else if (ch == '\x1f') { + state = ST_WINDOWSIZE; + } else { + state = ST_NONE;; + } + + ret = read(socket, &ch, 1); /* data */ + while (ch != '\xff') { + if (state == ST_TERMTYPE) { + if (termAlloced == termLength) { + termAlloced += 10; + termType = realloc(termType, termAlloced + 1); + } + + termType[termLength++] = tolower(ch); + } else if (state == ST_WINDOWSIZE) { + if ((sizePtr - sizeBuf) < (int)sizeof(sizeBuf)) + *sizePtr++ = ch; + } + + ret = read(socket, &ch, 1); /* data */ + } + + ret = read(socket, &ch, 1); /* should be a SE */ + + } while (!done); + + termType[termLength] = '\0'; + + if (sizePtr - sizeBuf == sizeof(sizeBuf)) { + width = (sizeBuf[0] << 8) + sizeBuf[1]; + height = (sizeBuf[2] << 8) + sizeBuf[3]; + } + + if (heightPtr) *heightPtr = height; + if (widthPtr) *widthPtr = width; + + if (term_type_ptr) *term_type_ptr = termType; +} + +int +telnet_process_input(telnet_state * ts, char *data, int len) { + char *s, *d; /* source, destination */ + +# if DEBUG_TELNET + printf("\nprinting packet:"); + for (s=data; s<data+len; s++) { + if (!((s-data)%10)) + printf("\n %03d: ", s-data); + printf("%02x ", *s & 0x000000FF); + } + printf("\n"); +# endif /* DEBUG_TELNET */ + + for (s=data, d=data; s<data+len; s++) { + switch (*ts) { + case TS_DATA: + if (*s == '\xff') { /* IAC */ + *ts = TS_IAC; + continue; + } +#if DEBUG_TELNET + printf("copying data element '%c'\n", *s); +#endif /* DEBUG_TELNET */ + if (s>d) { + *(d++) = *s; + } else { + d++; + } + break; + + case TS_IAC: + if (*s == '\xfa') { /* SB */ + *ts = TS_SB; + continue; + } + /* if not SB, skip IAC verb object */ +# if DEBUG_TELNET + printf("skipping verb/object (offset %d)...\n", s-data-1); +# endif /* DEBUG_TELNET */ + s += 1; + *ts = TS_DATA; + break; + + case TS_SB: +# if DEBUG_TELNET + printf("skipping SB (offset %d)...\n", s-data-1); +# endif /* DEBUG_TELNET */ + while (s < (data+(len-1))) { + if (*s == '\xff') { + break; /* fall through to TS_SB_IAC setting below */ + } else { + s++; + } + } + if (*s == '\xff') { + *ts = TS_SB_IAC; + } + break; + + case TS_SB_IAC: + if (*s == '\xf0') { /* SE */ +# if DEBUG_TELNET + printf("SE ends SB (offset %d)...\n", s-data-1); +# endif /* DEBUG_TELNET */ + *ts = TS_DATA; + } else { +# if DEBUG_TELNET + printf("IAC without SE in SB (offset %d)\n", s-data-1); +# endif /* DEBUG_TELNET */ + *ts = TS_SB; + } + break; + + default: + logMessage(WARNING, "unknown telnet state %d for data element %c", + *ts, *s); + *ts = TS_DATA; + break; + } + } + + /* calculate new length after copying data around */ + len = d - data; +#if DEBUG_TELNET + printf("returning len: %d of packet:", len); + for (s=data; s<data+len; s++) { + if (!((s-data)%10)) + printf("\n %03d: ", s-data); + printf("%02x ", *s & 0x000000FF); + } + printf("\n"); +#endif /* DEBUG_TELNET */ + + return len; +} + +/* The telnet protocol requires CR/NL instead of just NL + * We normally deal with Unix, which just uses NL, so we need to translate. + * + * It would be easy to go through line-by-line and write each line, but + * that would create more packet overhead by sending out one packet + * per line, and over things like slow PPP connections, that is painful. + * Therefore, instead, we create a modified copy of the data and write + * the whole modified copy at once. + */ +void +telnet_send_output(int sock, char *data, int len) { + char *s, *d; /* source, destination */ + char *buf; + int ret; + + buf = alloca((len*2)+1); /* max necessary size */ + + /* just may need to add CR before NL (but do not double existing CRs) */ + for (s=data, d=buf; d-buf<len; s++, d++) { + if ((*s == '\n') && (s == data || (*(s-1) != '\r'))) { + /* NL without preceding CR */ + *(d++) = '\r'; + len++; + } + *d = *s; + } + + /* now send it... */ + ret = write(sock, buf, len); +} diff --git a/loader/telnet.h b/loader/telnet.h new file mode 100644 index 0000000..5c34154 --- /dev/null +++ b/loader/telnet.h @@ -0,0 +1,40 @@ +/* + * telnet.h -- basic telnet protocol handling for ttywatch + * + * Copyright (C) 2001 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): Michael K. Johnson <johnsonm@redhat.com> + */ + +#ifndef __TELNET_H__ +#define __TELNET_H__ + +typedef enum { + TS_DATA = 0, + TS_IAC, + TS_SB, + TS_SB_IAC, +} telnet_state; + +void +telnet_negotiate(int socket, char ** term_type_ptr, int * heightPtr, + int * widthPtr); +int +telnet_process_input(telnet_state * ts, char *data, int len); +void +telnet_send_output(int sock, char *data, int len); + +#endif /* __TELNET_H__ */ diff --git a/loader/telnetd.c b/loader/telnetd.c new file mode 100644 index 0000000..e3a021d --- /dev/null +++ b/loader/telnetd.c @@ -0,0 +1,256 @@ +/* + * telnetd.c - glue to tie telnet.c from ttywatch to the loader + * + * Copyright (C) 2002 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> + */ + +#include <arpa/inet.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <newt.h> +#include <pty.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <sys/signal.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include "../isys/log.h" + +#include "lang.h" +#include "loader.h" +#include "modules.h" +#include "net.h" +#include "telnet.h" +#include "windows.h" + +#ifndef IPPORT_TELNET +#define IPPORT_TELNET 23 +#endif + +/* boot flags */ +extern uint64_t flags; + +/* Forks, keeping the loader as our child (so we know when it dies). */ +int beTelnet(void) { + int sock; + int conn; + socklen_t addrLength; + pid_t child; + int i; + int masterFd, ttyFd; + struct sockaddr_in address; + char buf[4096]; + struct pollfd fds[3]; + telnet_state ts = TS_DATA; + char * termType; + int height, width; + struct winsize ws; + + if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { + logMessage(ERROR, "socket: %s", strerror(errno)); + return -1; + } + + address.sin_family = AF_INET; + address.sin_port = htons(IPPORT_TELNET); + memset(&address.sin_addr, 0, sizeof(address.sin_addr)); + addrLength = sizeof(address); + + /* Let the kernel reuse the socket address. This lets us run + twice in a row, without waiting for the (ip, port) tuple + to time out. Makes testing much easier*/ + conn = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &conn, sizeof(conn)); + + bind(sock, (struct sockaddr *) &address, sizeof(address)); + listen(sock, 5); + + winStatus(45, 3, _("Telnet"), _("Waiting for telnet connection.")); + + if ((conn = accept(sock, (struct sockaddr *) &address, &addrLength)) < 0) { + newtWinMessage(_("Error"), _("OK"), "accept failed: %s", + strerror(errno)); + close(sock); + return -1; + } + + stopNewt(); + close(sock); + telnet_negotiate(conn, &termType, &height, &width); + +#ifdef DEBUG_TELNET + printf("got term type %s\n", termType); +#endif + + masterFd = open("/dev/ptmx", O_RDWR); + if (masterFd < 0) { + logMessage(CRITICAL, "cannot open /dev/ptmx"); + close(conn); + return -1; + } + + if (height != -1 && width != -1) { +#ifdef DEBUG_TELNET + printf("setting window size to %d x %d\n", width, height); +#endif + ws.ws_row = height; + ws.ws_col = width; + ioctl(masterFd, TIOCSWINSZ, &ws); + } + + + child = fork(); + + if (child) { +#ifndef DEBUG_TELNET + startNewt(); + winStatus(45, 3, _("Telnet"), _("Running anaconda via telnet.")); +#endif + + fds[0].events = POLLIN; + fds[0].fd = masterFd; + + fds[1].events = POLLIN; + fds[1].fd = conn; + + while ((i = poll(fds, 2, -1)) > 0) { + if (fds[0].revents) { + i = read(masterFd, buf, sizeof(buf)); +#ifdef DEBUG_TELNET + { + int j; + int row; + + for (row = 0; row < (i / 12) + 1; row++) { + printf("wrote:"); + + for (j = (row * 12); j < i && j < ((row + 1) * 12); j++) + printf(" 0x%2x", (unsigned char) buf[j]); + + printf("\nwrote:"); + + for (j = (row*12); j < i && j < ((row+1)*12); j++) { + if (isprint(buf[j])) + printf(" %c ", buf[j]); + else + printf(" "); + } + + printf("\n"); + } + } +#endif + /* child died */ + if (i < 0) + break; + + telnet_send_output(conn, buf, i); + } + + if (fds[1].revents) { + int ret; + i = read(conn, buf, sizeof(buf)); + + /* connection went away */ + if (!i) + break; + + i = telnet_process_input(&ts, buf, i); + ret = write(masterFd, buf, i); +#ifdef DEBUG_TELNET + { + int j; + + printf("got:"); + for (j = 0; j < i; j++) + printf(" 0x%x", (unsigned char) buf[j]); + printf("\n"); + } +#endif + } + } + + if (i < 0) { + logMessage(ERROR, "poll: %s", strerror(errno)); + } + +#ifndef DEBUG_TELNET + stopNewt(); +#endif + + kill(child, SIGTERM); + close(conn); + exit(0); + } + + unlockpt(masterFd); + grantpt(masterFd); + ttyFd = open(ptsname(masterFd), O_RDWR); + close(masterFd); + setsid(); + close(0); + close(1); + close(2); + + if (ttyFd != 0) { + dup2(ttyFd, 0); + close(ttyFd); + } + + dup2(0, 1); + dup2(0, 2); + + /* brand new tty! */ + setenv("TERM", termType, 1); + + startNewt(); + + return 0; +} + +void startTelnetd(struct loaderData_s * loaderData) { + char *ipaddr = NULL; + iface_t iface; + + iface_init_iface_t(&iface); + + if (kickstartNetworkUp(loaderData, &iface)) { + logMessage(ERROR, "unable to bring up network"); + return; + } + + ipaddr = iface_ip2str(iface.device, AF_INET); + if (ipaddr == NULL) { + logMessage(ERROR, "%s (%d): no IP address found for %s", + __func__, __LINE__, iface.device); + return; + } + + logMessage(INFO, "going to beTelnet for %s", ipaddr); + if (!beTelnet()) + flags |= LOADER_FLAGS_TEXT | LOADER_FLAGS_NOSHELL; + + return; +} diff --git a/loader/telnetd.h b/loader/telnetd.h new file mode 100644 index 0000000..fedb0fa --- /dev/null +++ b/loader/telnetd.h @@ -0,0 +1,25 @@ +/* + * telnetd.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef TELNETD_H +#define TELNETD_H + +void startTelnetd(struct loaderData_s * loaderData); + +#endif diff --git a/loader/udelay.h b/loader/udelay.h new file mode 100644 index 0000000..5315074 --- /dev/null +++ b/loader/udelay.h @@ -0,0 +1,199 @@ +/* + * udelay.h -- udelay and other time related functions. + * + * Copyright (C) 2006, 2007 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): Peter Jones <pjones@redhat.com> + */ + +#ifndef UDELAY_H +#define UDELAY_H 1 + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/time.h> +#include <time.h> + +#define USECS_PER_SEC 1000000LL +#define NSECS_PER_USEC 1000LL +#define NSECS_PER_SEC (NSECS_PER_USEC * USECS_PER_SEC) + +static inline void +nsectospec(long long nsecs, struct timespec *ts) +{ + if (nsecs < 0) { + ts->tv_sec = -1; + ts->tv_nsec = -1; + return; + } + ts->tv_sec = nsecs / NSECS_PER_SEC; + ts->tv_nsec = (nsecs % NSECS_PER_SEC); +} + +static inline void +usectospec(long long usecs, struct timespec *ts) +{ + if (usecs > 0 && LLONG_MAX / NSECS_PER_USEC > usecs) + usecs *= NSECS_PER_USEC; + + nsectospec(usecs, ts); +} + +static inline int +speczero(struct timespec *ts) +{ + return (ts->tv_sec == 0 && ts->tv_nsec == 0); +} + +static inline int +specinf(struct timespec *ts) +{ + return (ts->tv_sec < 0 || ts->tv_nsec < 0); +} + +static inline long long +spectonsec(struct timespec *ts) +{ + long long nsecs = 0; + if (specinf(ts)) + return -1; + + nsecs = ts->tv_sec * NSECS_PER_SEC; + nsecs += ts->tv_nsec; + return nsecs; +} + +static inline long long +spectousec(struct timespec *ts) +{ + long long usecs = spectonsec(ts); + + return usecs < 0 ? usecs : usecs / NSECS_PER_USEC; +} + +static inline int +gettimespecofday(struct timespec *ts) +{ + struct timeval tv = {0, 0}; + int rc; + + rc = gettimeofday(&tv, NULL); + if (rc >= 0) { + ts->tv_sec = tv.tv_sec; + ts->tv_nsec = tv.tv_usec * NSECS_PER_USEC; + } + return rc; +} + +/* minuend minus subtrahend equals difference */ +static inline void +tssub(struct timespec *minuend, struct timespec *subtrahend, + struct timespec *difference) +{ + long long m, s, d; + + m = spectonsec(minuend); + s = spectonsec(subtrahend); + + if (s < 0) { + d = 0; + } else if (m < 0) { + d = -1; + } else { + m -= s; + d = m < 0 ? 0 : m; + } + + nsectospec(d, difference); + return; +} + +static inline void +tsadd(struct timespec *augend, struct timespec *addend, struct timespec *sum) +{ + long long aug, add; + + aug = spectonsec(augend); + add = spectonsec(addend); + +// printf("aug: %Ld add: %Ld\n", aug, add); + + if (aug < 0 || add < 0) + nsectospec(-1, sum); + else if (LLONG_MAX - MAX(add,aug) < MAX(add,aug)) + nsectospec(LLONG_MAX, sum); + else + nsectospec(aug+add, sum); + return; +} + +#define tsGT(x,y) (tscmp((x), (y)) < 0) +#define tsGE(x,y) (tscmp((x), (y)) <= 0) +#define tsET(x,y) (tscmp((x), (y)) == 0) +#define tsNE(x,y) (tscmp((x), (y)) != 0) +#define tsLE(x,y) (tscmp((x), (y)) >= 0) +#define tsLT(x,y) (tscmp((x), (y)) > 0) + +static inline int +tscmp(struct timespec *a, struct timespec *b) +{ + long long m, s; + long long rc; + + m = spectonsec(a); + s = spectonsec(b); + + if (s < 0) { + rc = 1; + if (m < 0) + rc = 0; + } else if (m < 0) { + rc = -1; + } else { + rc = MIN(MAX(s-m, -1), 1); + } + + return rc; +} + +static inline void +udelayspec(struct timespec total) +{ + struct timespec rem; + if (specinf(&total)) { + do { + usectospec(LLONG_MAX, &rem); + } while (nanosleep(&rem, &rem) == -1 && errno == EINTR); + } else { + rem = total; + while (nanosleep(&rem, &rem) == -1 && errno == EINTR) + ; + } +} + +static inline void +udelay(long long usecs) +{ + struct timespec rem = {0,0}; + + usectospec(usecs, &rem); + udelayspec(rem); +} + +#endif /* UDELAY_H */ +/* + * vim:ts=8:sw=4:sts=4:et + */ diff --git a/loader/undomounts.c b/loader/undomounts.c new file mode 100644 index 0000000..af5caaa --- /dev/null +++ b/loader/undomounts.c @@ -0,0 +1,239 @@ +/* + * undomounts.c: Handles some basic unmounting stuff for init + * Broken out so that it can be used on s390 in a shutdown binary + * + * Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003 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> + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/swap.h> +#include <unistd.h> + +#include "devt.h" + +/* Defined in linux/fs.h, but inside __KERNEL__. */ +#ifdef MNT_DETACH +#undef MNT_DETACH +#endif + +#define MNT_DETACH 0x00000002 + +struct unmountInfo { + char * name; + int mounted; + int loopDevice; + enum { FS, LOOP } what; +} ; + +void undoLoop(struct unmountInfo * fs, int numFs, int this); + +static void printstr(char * string) { + int ret; + + ret = write(1, string, strlen(string)); +} + +void undoMount(struct unmountInfo * fs, int numFs, int this) { + size_t 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 && (strlen(fs[i].name) >= len) && + (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 (umount2(fs[this].name, MNT_DETACH) < 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 (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") && + strcmp(start, "/dev")) { + filesystems[numFilesystems].name = strdup(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 = strdup(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) && (filesystems[i].name)) { + undoMount(filesystems, numFilesystems, i); + } + } + + for (i = 0; i < numFilesystems; i++) + free(filesystems[i].name); +} + +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; + } +} diff --git a/loader/unicode-linedraw-chars.txt b/loader/unicode-linedraw-chars.txt new file mode 100644 index 0000000..c1a4814 --- /dev/null +++ b/loader/unicode-linedraw-chars.txt @@ -0,0 +1,22 @@ +─ +│ +┌ +┐ +└ +┘ +┤ +├ +┴ +┬ +┼ +▒ +◆ +° +± +· +← +→ +↓ +↑ +▒ +▮ diff --git a/loader/urlinstall.c b/loader/urlinstall.c new file mode 100644 index 0000000..54d1398 --- /dev/null +++ b/loader/urlinstall.c @@ -0,0 +1,414 @@ +/* + * urlinstall.c - code to set up url (ftp/http) installs + * + * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003 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> + * Matt Wilson <msw@redhat.com> + * Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + */ + +#include <newt.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> +#include <unistd.h> +#include <errno.h> +#include <glib.h> + +#include "../isys/iface.h" +#include "../isys/log.h" + +#include "copy.h" +#include "kickstart.h" +#include "loader.h" +#include "loadermisc.h" +#include "lang.h" +#include "method.h" +#include "net.h" +#include "method.h" +#include "urlinstall.h" +#include "cdinstall.h" +#include "urls.h" +#include "windows.h" + +/* boot flags */ +extern uint64_t flags; + +char **extraHeaders = NULL; + +static char **headers() { + int len = 2; + + /* The list of HTTP headers is unlikely to change, unless a new ethernet + * device suddenly shows up since last time we downloaded a file. So, + * cache the result here to save some time. + */ + if (extraHeaders != NULL) + return extraHeaders; + + if ((extraHeaders = realloc(extraHeaders, 2*sizeof(char *))) == NULL) { + logMessage(CRITICAL, "%s: %d: %m", __func__, __LINE__); + abort(); + } + + checked_asprintf(&extraHeaders[0], "X-Anaconda-Architecture: %s", getProductArch()); + checked_asprintf(&extraHeaders[1], "X-Anaconda-System-Release: %s", getProductName()); + + if (FL_KICKSTART_SEND_MAC(flags)) { + /* find all ethernet devices and make a header entry for each one */ + int i; + char *dev, *mac; + struct device **devices; + + devices = getDevices(DEVICE_NETWORK); + for (i = 0; devices && devices[i]; i++) { + dev = devices[i]->device; + mac = iface_mac2str(dev); + + if (mac) { + extraHeaders = realloc(extraHeaders, (len+1)*sizeof(char *)); + checked_asprintf(&extraHeaders[len], "X-RHN-Provisioning-MAC-%d: %s %s", + i, dev, mac); + + len++; + free(mac); + } + } + } + + if (FL_KICKSTART_SEND_SERIAL(flags) && !access("/sbin/dmidecode", X_OK)) { + FILE *f; + char sn[1024]; + size_t sn_len; + + if ((f = popen("/sbin/dmidecode -s system-serial-number", "r")) == NULL) { + logMessage(CRITICAL, "%s: %d: %m", __func__, __LINE__); + abort(); + } + + sn_len = fread(sn, sizeof(char), 1023, f); + if (ferror(f)) { + logMessage(CRITICAL, "%s: %d: %m", __func__, __LINE__); + abort(); + } + + sn[sn_len] = '\0'; + pclose(f); + + extraHeaders = realloc(extraHeaders, (len+1)*sizeof(char *)); + + checked_asprintf(&extraHeaders[len], "X-System-Serial-Number: %s", sn); + + len++; + } + + extraHeaders = realloc(extraHeaders, (len+1)*sizeof(char *)); + extraHeaders[len] = NULL; + return extraHeaders; +} + +static int loadSingleUrlImage(struct loaderData_s *loaderData, struct iurlinfo *ui, + char *dest, char *mntpoint, char *device, int silentErrors) { + char **ehdrs = NULL; + int status; + + if (!strncmp(ui->url, "http", 4)) + ehdrs = headers(); + + status = urlinstTransfer(loaderData, ui, ehdrs, dest); + if (status) { + if (!silentErrors) { + newtWinMessage(_("Error"), _("OK"), + _("Unable to retrieve %s."), ui->url); + } + + return 2; + } + + if (dest != NULL) { + if (mountLoopback(dest, mntpoint, device)) { + logMessage(ERROR, "Error mounting %s on %s: %m", device, mntpoint); + return 1; + } + } + + return 0; +} + +static void copyWarnFn (char *msg) { + logMessage(WARNING, msg); +} + +static void copyErrorFn (char *msg) { + newtWinMessage(_("Error"), _("OK"), _(msg)); +} + +static int loadUrlImages(struct loaderData_s *loaderData, struct iurlinfo *ui) { + char *oldUrl, *path, *dest, *slash; + int rc; + + oldUrl = strdup(ui->url); + free(ui->url); + + /* Figure out the path where updates.img and product.img files are + * kept. Since ui->url points to a stage2 image file, we just need + * to trim off the file name and look in the same directory. + */ + if ((slash = strrchr(oldUrl, '/')) == NULL) + return 0; + + if ((path = strndup(oldUrl, slash-oldUrl)) == NULL) + path = oldUrl; + + /* grab the updates.img before install.img so that we minimize our + * ramdisk usage */ + checked_asprintf(&ui->url, "%s/%s", path, "updates.img"); + + if (!loadSingleUrlImage(loaderData, ui, "/tmp/updates-disk.img", "/tmp/update-disk", + "/dev/loop7", 1)) { + copyDirectory("/tmp/update-disk", "/tmp/updates", copyWarnFn, + copyErrorFn); + umountLoopback("/tmp/update-disk", "/dev/loop7"); + unlink("/tmp/updates-disk.img"); + unlink("/tmp/update-disk"); + } else if (!access("/tmp/updates-disk.img", R_OK)) { + unpackCpioBall("/tmp/updates-disk.img", "/tmp/updates"); + unlink("/tmp/updates-disk.img"); + } + + free(ui->url); + + /* grab the product.img before install.img so that we minimize our + * ramdisk usage */ + checked_asprintf(&ui->url, "%s/%s", path, "product.img"); + + if (!loadSingleUrlImage(loaderData, ui, "/tmp/product-disk.img", "/tmp/product-disk", + "/dev/loop7", 1)) { + copyDirectory("/tmp/product-disk", "/tmp/product", copyWarnFn, + copyErrorFn); + umountLoopback("/tmp/product-disk", "/dev/loop7"); + unlink("/tmp/product-disk.img"); + unlink("/tmp/product-disk"); + } + + free(ui->url); + ui->url = strdup(oldUrl); + + checked_asprintf(&dest, "/tmp/install.img"); + + rc = loadSingleUrlImage(loaderData, ui, dest, "/mnt/runtime", "/dev/loop0", 0); + free(dest); + free(oldUrl); + + if (rc) { + if (rc != 2) + newtWinMessage(_("Error"), _("OK"), + _("Unable to retrieve the install image.")); + return 1; + } + + return 0; +} + +char *mountUrlImage(struct installMethod *method, char *location, + struct loaderData_s *loaderData) { + urlInstallData *stage2Data = (urlInstallData *) loaderData->stage2Data; + struct iurlinfo ui; + + enum { URL_STAGE_MAIN, URL_STAGE_FETCH, + URL_STAGE_DONE } stage = URL_STAGE_MAIN; + + memset(&ui, 0, sizeof(ui)); + + while (stage != URL_STAGE_DONE) { + switch(stage) { + case URL_STAGE_MAIN: { + /* If the stage2= parameter was given (or inferred from repo=) + * then use that configuration info to fetch the image. This + * could also have come from kickstart. Else, we need to show + * the UI. + */ + if (loaderData->method == METHOD_URL && stage2Data) { + ui.url = strdup(stage2Data->url); + logMessage(INFO, "URL_STAGE_MAIN: url is %s", ui.url); + + if (!ui.url) { + logMessage(ERROR, "missing URL specification"); + loaderData->method = -1; + free(loaderData->stage2Data); + loaderData->stage2Data = NULL; + + if (loaderData->inferredStage2) + loaderData->invalidRepoParam = 1; + + break; + } + + /* ks info was adequate, lets skip to fetching image */ + stage = URL_STAGE_FETCH; + break; + } else { + char *substr; + + if (urlMainSetupPanel(loaderData, &ui)) { + loaderData->stage2Data = NULL; + return NULL; + } + + /* If the user-provided URL points at a repo instead of + * a stage2 image, fix it up now. + */ + substr = strstr(ui.url, ".img"); + if (!substr || (substr && *(substr+4) != '\0')) { + loaderData->instRepo = strdup(ui.url); + + checked_asprintf(&ui.url, "%s/images/install.img", + ui.url); + } + + loaderData->invalidRepoParam = 1; + } + + stage = URL_STAGE_FETCH; + break; + } + + case URL_STAGE_FETCH: { + if (loadUrlImages(loaderData, &ui)) { + stage = URL_STAGE_MAIN; + + if (loaderData->method >= 0) + loaderData->method = -1; + + if (loaderData->inferredStage2) + loaderData->invalidRepoParam = 1; + } else { + stage = URL_STAGE_DONE; + } + + break; + } + + case URL_STAGE_DONE: + break; + } + } + + return ui.url; +} + +int getFileFromUrl(char * url, char * dest, + struct loaderData_s * loaderData) { + struct iurlinfo ui; + char **ehdrs = NULL; + int rc; + iface_t iface; + + iface_init_iface_t(&iface); + + if (kickstartNetworkUp(loaderData, &iface)) { + logMessage(ERROR, "unable to bring up network"); + return 1; + } + + memset(&ui, 0, sizeof(ui)); + ui.url = url; + + logMessage(INFO, "file location: %s", url); + + if (!strncmp(url, "http", 4)) { + ehdrs = headers(); + } + + rc = urlinstTransfer(loaderData, &ui, ehdrs, dest); + if (rc) { + logMessage(ERROR, "failed to retrieve %s", ui.url); + return 1; + } + + return 0; +} + +/* pull kickstart configuration file via http */ +int kickstartFromUrl(char * url, struct loaderData_s * loaderData) { + return getFileFromUrl(url, "/tmp/ks.cfg", loaderData); +} + +void setKickstartUrl(struct loaderData_s * loaderData, int argc, + char ** argv) { + char *substr = NULL; + gchar *url = NULL, *proxy = NULL; + GOptionContext *optCon = g_option_context_new(NULL); + GError *optErr = NULL; + GOptionEntry ksUrlOptions[] = { + { "url", 0, 0, G_OPTION_ARG_STRING, &url, NULL, NULL }, + { "proxy", 0, 0, G_OPTION_ARG_STRING, &proxy, NULL, NULL }, + { NULL }, + }; + + logMessage(INFO, "kickstartFromUrl"); + + g_option_context_set_help_enabled(optCon, FALSE); + g_option_context_add_main_entries(optCon, ksUrlOptions, NULL); + + if (!g_option_context_parse(optCon, &argc, &argv, &optErr)) { + startNewt(); + newtWinMessage(_("Kickstart Error"), _("OK"), + _("Bad argument to URL kickstart method " + "command: %s"), optErr->message); + g_error_free(optErr); + g_option_context_free(optCon); + return; + } + + g_option_context_free(optCon); + + if (!url) { + newtWinMessage(_("Kickstart Error"), _("OK"), + _("Must supply a --url argument to Url kickstart method.")); + return; + } + + /* determine install type */ + if (strncmp(url, "http", 4) && strncmp(url, "ftp://", 6)) { + newtWinMessage(_("Kickstart Error"), _("OK"), + _("Unknown Url method %s"), url); + return; + } + + substr = strstr(url, ".img"); + if (!substr || (substr && *(substr+4) != '\0')) { + loaderData->instRepo = strdup(url); + } else { + if ((loaderData->stage2Data = calloc(sizeof(urlInstallData *), 1)) == NULL) + return; + + ((urlInstallData *)loaderData->stage2Data)->url = url; + loaderData->method = METHOD_URL; + } + + if (proxy) { + splitProxyParam(proxy, &loaderData->proxyUser, + &loaderData->proxyPassword, + &loaderData->proxy); + } + logMessage(INFO, "results of url ks, url %s", url); +} + +/* vim:set shiftwidth=4 softtabstop=4: */ diff --git a/loader/urlinstall.h b/loader/urlinstall.h new file mode 100644 index 0000000..710b0ae --- /dev/null +++ b/loader/urlinstall.h @@ -0,0 +1,36 @@ +/* + * urlinstall.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef URLINSTALL_H +#define URLINSTALL_H + +#include "method.h" +#include "urls.h" + +typedef struct iurlinfo urlInstallData; + +void setKickstartUrl(struct loaderData_s * loaderData, int argc, + char ** argv); +int kickstartFromUrl(char * url, struct loaderData_s * loaderData); +char * mountUrlImage(struct installMethod * method, + char * location, struct loaderData_s * loaderData); +int getFileFromUrl(char * url, char * dest, struct loaderData_s * loaderData); + + +#endif diff --git a/loader/urls.c b/loader/urls.c new file mode 100644 index 0000000..a0441fc --- /dev/null +++ b/loader/urls.c @@ -0,0 +1,370 @@ +/* + * urls.c - url handling code + * + * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2009 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> + * Matt Wilson <msw@redhat.com> + * Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + * Chris Lumens <clumens@redhat.com> + */ + +#include <arpa/inet.h> +#include <ctype.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <newt.h> +#include <regex.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <netdb.h> +#include <errno.h> +#include <curl/curl.h> + +#include "../isys/log.h" + +#include "lang.h" +#include "loader.h" +#include "loadermisc.h" +#include "urls.h" +#include "windows.h" +#include "net.h" + +#define NMATCH 10 + +/* boot flags */ +extern uint64_t flags; + +/* This is just a wrapper around the windows.c progress callback that accepts + * the arguments libcurl provides. + */ +int progress_cb(void *data, double dltotal, double dlnow, double ultotal, double ulnow) { + struct progressCBdata *cb_data = (struct progressCBdata *) data; + + progressCallback(cb_data, dlnow, dltotal); + return 0; +} + +int splitProxyParam(char *param, char **user, char **password, char **proxy) { + /* proxy=[protocol://][username[:password]@]host[:port] */ + char *pattern = "([[:alpha:]]+://)?(([[:alnum:]]+)(:[^:@]+)?@)?([^:]+)(:[[:digit:]]+)?(/.*)?"; + regex_t re; + regmatch_t pmatch[NMATCH]; + + if (regcomp(&re, pattern, REG_EXTENDED)) { + return 0; + } + + if (regexec(&re, param, NMATCH, pmatch, 0) == REG_NOMATCH) { + regfree(&re); + return 0; + } + + /* Match 0 is always the whole string (assuming regexec matched anything) + * so skip it. Then, these indices are just the number of the starting + * paren in pattern above. Make sure to change these whenever changing + * the pattern. + */ + if (pmatch[3].rm_so != -1) + *user = strndup(param+pmatch[3].rm_so, pmatch[3].rm_eo-pmatch[3].rm_so); + + /* Skip the leading colon. */ + if (pmatch[4].rm_so != -1) + *password = strndup(param+pmatch[4].rm_so+1, pmatch[4].rm_eo-pmatch[4].rm_so-1); + + if (pmatch[5].rm_so != -1) { + char *portStr = ""; + + if (pmatch[6].rm_so != -1) + portStr = strndup(param+pmatch[6].rm_so, pmatch[6].rm_eo-pmatch[6].rm_so); + + /* If no parameter was given, default to HTTP. yum will want to know + * the protocol, and curl will just ignore it if given. + */ + if (pmatch[1].rm_so != -1) { + checked_asprintf(proxy, "%.*s%.*s%s", pmatch[1].rm_eo-pmatch[1].rm_so, + param+pmatch[1].rm_so, + pmatch[5].rm_eo-pmatch[5].rm_so, + param+pmatch[5].rm_so, + portStr); + } else { + checked_asprintf(proxy, "http://%.*s%s", pmatch[5].rm_eo-pmatch[5].rm_so, + param+pmatch[5].rm_so, + portStr); + } + } + + regfree(&re); + return 1; +} + +int urlinstTransfer(struct loaderData_s *loaderData, struct iurlinfo *ui, + char **extraHeaders, char *dest) { + struct progressCBdata *cb_data; + CURL *curl = NULL; + CURLcode status; + struct curl_slist *headers = NULL; + char *version; + FILE *f = NULL; + + logMessage(INFO, "transferring %s", ui->url); + + f = fopen(dest, "w"); + + /* Initialize libcurl */ + curl_global_init(CURL_GLOBAL_SSL); + curl = curl_easy_init(); + + checked_asprintf(&version, "anaconda/%s", VERSION); + + curl_easy_setopt(curl, CURLOPT_USERAGENT, version); + curl_easy_setopt(curl, CURLOPT_URL, ui->url); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, f); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 10); + + /* If a proxy was provided, add the options for that now. */ + if (loaderData->proxy && strcmp(loaderData->proxy, "")) { + curl_easy_setopt(curl, CURLOPT_PROXY, loaderData->proxy); + + if (loaderData->proxyUser && strcmp(loaderData->proxyUser, "")) + curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, + loaderData->proxyUser); + + if (loaderData->proxyPassword && strcmp(loaderData->proxyPassword, "")) + curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, + loaderData->proxyPassword); + } + + if (extraHeaders) { + int i; + for (i = 0; extraHeaders[i] != NULL; i++) { + headers = curl_slist_append(headers, extraHeaders[i]); + } + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + } + + /* Only set up the progress bar if we've got a UI to display it. */ + if (FL_CMDLINE(flags)) { + printf("%s %s...\n", _("Retrieving"), ui->url); + } else { + char *filename; + + filename = strrchr(ui->url, '/'); + if (!filename) + filename = ui->url; + + cb_data = winProgressBar(70, 5, _("Retrieving"), "%s %s...", _("Retrieving"), filename); + + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_cb); + curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, cb_data); + } + + /* Finally, do the transfer. */ + status = curl_easy_perform(curl); + if (status) + logMessage(ERROR, "Error downloading %s: %s", ui->url, curl_easy_strerror(status)); + + if (!FL_CMDLINE(flags)) + newtPopWindow(); + + if (headers) + curl_slist_free_all(headers); + + fclose(f); + free(version); + + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return status; +} + +char * addrToIp(char * hostname) { + struct in_addr ad; + struct in6_addr ad6; + char *ret; + struct hostent *host; + + if ((ret = malloc(INET6_ADDRSTRLEN+1)) == NULL) + return hostname; + + if (inet_ntop(AF_INET, &ad, ret, INET_ADDRSTRLEN) != NULL) + return ret; + else if (inet_ntop(AF_INET6, &ad6, ret, INET6_ADDRSTRLEN) != NULL) + return ret; + else if ((host = gethostbyname(hostname)) != NULL) + return host->h_name; + else + return NULL; +} + +static void setProxySensitivity(newtComponent co, void *dptr) { + int i; + + /* It's 3 because there are three entry boxes in the proxy grid. Lame. */ + for (i = 0; i < 3; i++) { + newtEntrySetFlags(*((newtComponent *) dptr), NEWT_FLAG_DISABLED, + NEWT_FLAGS_TOGGLE); + dptr += sizeof(newtComponent); + } + + return; +} + +int urlMainSetupPanel(struct loaderData_s *loaderData, struct iurlinfo * ui) { + newtComponent form, okay, cancel, urlEntry, proxyCheckbox; + newtComponent proxyEntries[3]; + newtComponent answer, text; + char enableProxy; + char *url = "", *proxy = "", *proxyUser = "", *proxyPassword = ""; + char * reflowedText = NULL; + int width, height; + newtGrid buttons, grid, proxyGrid; + char * buf = NULL; + + /* Populate the UI with whatever initial value we've got. */ + if (ui && ui->url) + url = ui->url; + + if (loaderData->proxy) + proxy = loaderData->proxy; + + if (loaderData->proxyUser) + proxyUser = loaderData->proxyUser; + + if (loaderData->proxyPassword) + proxyPassword = loaderData->proxyPassword; + + buttons = newtButtonBar(_("OK"), &okay, _("Back"), &cancel, NULL); + + checked_asprintf(&buf, + _("Please enter the URL containing the %s installation image on your server."), + getProductName()); + + reflowedText = newtReflowText(buf, 47, 5, 5, &width, &height); + free(buf); + + text = newtTextbox(-1, -1, width, height, NEWT_TEXTBOX_WRAP); + newtTextboxSetText(text, reflowedText); + free(reflowedText); + + urlEntry = newtEntry(22, 8, url, 60, (const char **) &url, + NEWT_ENTRY_SCROLL); + + /* If we've been provided with proxy settings already, enable the proxy + * grid. This will make sure all the fields get filled in, too. + */ + enableProxy = loaderData->proxy != NULL && strcmp("", loaderData->proxy) ? '*' : ' '; + + proxyCheckbox = newtCheckbox(-1, -1, _("Enable HTTP proxy"), enableProxy, + NULL, &enableProxy); + newtComponentAddCallback(proxyCheckbox, setProxySensitivity, &proxyEntries); + + proxyEntries[0] = newtEntry(-1, -1, proxy, 35, (const char **) &proxy, NEWT_FLAG_SCROLL); + proxyEntries[1] = newtEntry(-1, -1, proxyUser, 15, (const char **) &proxyUser, NEWT_FLAG_SCROLL); + proxyEntries[2] = newtEntry(-1, -1, proxyPassword, 15, (const char **) &proxyPassword, NEWT_FLAG_SCROLL|NEWT_FLAG_PASSWORD); + + /* Set the initial proxy grid sensitivity to match. */ + if (enableProxy == ' ') + setProxySensitivity(proxyCheckbox, proxyEntries); + + proxyGrid = newtCreateGrid(2, 3); + newtGridSetField(proxyGrid, 0, 0, NEWT_GRID_COMPONENT, + newtLabel(-1, -1, _("Proxy URL")), + 0, 0, 0, 0, 0, NEWT_ANCHOR_LEFT); + newtGridSetField(proxyGrid, 1, 0, NEWT_GRID_COMPONENT, proxyEntries[0], + 0, 0, 0, 0, 0, NEWT_ANCHOR_LEFT); + newtGridSetField(proxyGrid, 0, 1, NEWT_GRID_COMPONENT, + newtLabel(-1, -1, _("Username")), + 0, 0, 0, 1, 0, NEWT_ANCHOR_LEFT); + newtGridSetField(proxyGrid, 1, 1, NEWT_GRID_COMPONENT, proxyEntries[1], + 0, 0, 0, 1, 0, NEWT_ANCHOR_LEFT); + newtGridSetField(proxyGrid, 0, 2, NEWT_GRID_COMPONENT, + newtLabel(-1, -1, _("Password")), + 0, 0, 0, 1, 0, NEWT_ANCHOR_LEFT); + newtGridSetField(proxyGrid, 1, 2, NEWT_GRID_COMPONENT, proxyEntries[2], + 0, 0, 0, 1, 0, NEWT_ANCHOR_LEFT); + + grid = newtCreateGrid(1, 5); + newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT, text, + 0, 0, 0, 1, 0, 0); + newtGridSetField(grid, 0, 1, NEWT_GRID_COMPONENT, urlEntry, + 0, 0, 0, 1, 0, 0); + newtGridSetField(grid, 0, 2, NEWT_GRID_COMPONENT, proxyCheckbox, + 0, 0, 0, 1, 0, NEWT_ANCHOR_LEFT); + newtGridSetField(grid, 0, 3, NEWT_GRID_SUBGRID, proxyGrid, + 0, 0, 0, 0, 0, NEWT_GRID_FLAG_GROWX); + newtGridSetField(grid, 0, 4, NEWT_GRID_SUBGRID, buttons, + 0, 0, 0, 0, 0, NEWT_GRID_FLAG_GROWX); + + form = newtForm(NULL, NULL, 0); + newtGridAddComponentsToForm(grid, form, 1); + newtGridWrappedWindow(grid, _("URL Setup")); + newtGridFree(grid, 1); + + do { + answer = newtRunForm(form); + if (answer != cancel) { + if (!strlen(url)) { + newtWinMessage(_("Error"), _("OK"), + _("You must enter a URL.")); + continue; + } + + if (strncmp(url, "http", 4) && strncmp(url, "ftp://", 6)) { + newtWinMessage(_("Error"), _("OK"), + _("URL must be either an ftp or http URL")); + continue; + } + + ui->url = strdup(url); + + if (enableProxy == '*') { + loaderData->proxy = strdup(proxy); + loaderData->proxyUser = strdup(proxyUser); + loaderData->proxyPassword = strdup(proxyPassword); + } else { + loaderData->proxy = ""; + loaderData->proxyUser = ""; + loaderData->proxyPassword = ""; + } + + /* FIXME: add back in hostname checking */ + } + + break; + } while (1); + + if (answer == cancel) { + newtFormDestroy(form); + newtPopWindow(); + + return LOADER_BACK; + } + + newtFormDestroy(form); + newtPopWindow(); + + return 0; +} diff --git a/loader/urls.h b/loader/urls.h new file mode 100644 index 0000000..4ca538f --- /dev/null +++ b/loader/urls.h @@ -0,0 +1,35 @@ +/* + * urls.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef H_LOADER_URLS +#define H_LOADER_URLS + +#include "loader.h" +#include "windows.h" + +struct iurlinfo { + char * url; +}; + +int splitProxyParam(char *param, char **user, char **password, char **proxy); +int urlMainSetupPanel(struct loaderData_s *loaderData, struct iurlinfo * ui); +int urlinstTransfer(struct loaderData_s *loaderData, struct iurlinfo *ui, + char **extraHeaders, char *dest); + +#endif diff --git a/loader/windows.c b/loader/windows.c new file mode 100644 index 0000000..51b00db --- /dev/null +++ b/loader/windows.c @@ -0,0 +1,121 @@ +/* + * windows.c - simple popup windows used by the loader + * + * Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 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> + * Matt Wilson <msw@redhat.com> + * Michael Fulbright <msf@redhat.com> + * Jeremy Katz <katzj@redhat.com> + */ + +#include <errno.h> +#include <newt.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> +#include <math.h> + +#include "../isys/log.h" + +#include "windows.h" + +void winStatus(int width, int height, char * title, char * text, ...) { + newtComponent t, f; + char * buf = NULL; + va_list args; + + va_start(args, text); + + if (vasprintf(&buf, text, args) != -1) { + newtCenteredWindow(width, height, title); + + t = newtTextbox(1, 1, width - 2, height - 2, NEWT_TEXTBOX_WRAP); + newtTextboxSetText(t, buf); + f = newtForm(NULL, NULL, 0); + + free(buf); + + newtFormAddComponent(f, t); + + newtDrawForm(f); + newtRefresh(); + newtFormDestroy(f); + } + + va_end(args); +} + + +void scsiWindow(const char * driver) { + winStatus(40, 3, _("Loading SCSI driver"), + _("Loading %s driver"), driver); +} + +int progressCallback(void *pbdata, long long pos, long long total) { + struct progressCBdata *data = pbdata; + char tickmark[2] = "-"; + char *ticks = "-\\|/"; + int x = ceil(pos * 100.0 / total); + + newtScaleSet(data->scale, x); + *tickmark = ticks[x % 4]; + + newtLabelSetText(data->label, tickmark); + newtRefresh(); + return 0; +} + +struct progressCBdata *winProgressBar(int width, int height, char *title, char *text, ...) { + struct progressCBdata *data; + char *buf = NULL; + va_list args; + int llen; + newtComponent t, f, scale, label; + + va_start(args, text); + + if (vasprintf(&buf, text, args) != -1) { + va_end(args); + newtCenteredWindow(width, height, title); + t = newtTextbox(1, 1, width - 2, height - 2, NEWT_TEXTBOX_WRAP); + newtTextboxSetText(t, buf); + llen = strlen(buf); + free(buf); + label = newtLabel(llen + 1, 1, "-"); + f = newtForm(NULL, NULL, 0); + newtFormAddComponent(f, t); + scale = newtScale(3, 3, width - 6, 100); + newtFormAddComponent(f, scale); + newtDrawForm(f); + newtRefresh(); + + if ((data = malloc(sizeof(struct progressCBdata))) == NULL) { + logMessage(ERROR, "%s: %d: %m", __func__, __LINE__); + abort(); + } + + data->scale = scale; + data->label = label; + return data; + } + + return NULL; +} + +/* vim:set shiftwidth=4 softtabstop=4: */ diff --git a/loader/windows.h b/loader/windows.h new file mode 100644 index 0000000..a1646d4 --- /dev/null +++ b/loader/windows.h @@ -0,0 +1,43 @@ +/* + * windows.h + * + * Copyright (C) 2007 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/>. + */ + +#ifndef _WINDOWS_H_ +#define _WINDOWS_H_ + +#include <newt.h> + +#include "lang.h" + +void winStatus(int width, int height, char * title, char * text, ...); +void scsiWindow(const char * driver); + +#define errorWindow(String) \ + newtWinMessage(_("Error"), _("OK"), String, strerror (errno)); + +typedef void (*progressCB) (void *pbdata, long long offset, long long total); + +struct progressCBdata { + newtComponent scale; + newtComponent label; +}; + +int progressCallback(void *pbdata, long long pos, long long total); +struct progressCBdata *winProgressBar(int width, int height, char *title, char *text, ...); + +#endif /* _WINDOWS_H_ */ |