You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
qubes-installer-qubes-os/anaconda/loader/loader.c

2471 lines
77 KiB

/*
* 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 "log.h"
#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/str.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;
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/lib/anaconda-runtime/pyrc.py", R_OK|X_OK))
setenv("PYTHONSTARTUP", "/usr/lib/anaconda-runtime/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 (!strncmp(str2lower(argv), "ipv6=dhcp", 9)) {
loaderData->ipv6 = strdup("dhcp");
} else if (!strncmp(str2lower(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) ||
isKickstartFileRemote(loaderData->ksFile)) {
logMessage(INFO, "early networking required for %s",
argv[i]);
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)) {
int stage2ok = 0;
// marmarek hack for USB install
if (loaderData->method == METHOD_HD) {
char *location = "/mnt/stage2";
char *device = ((struct hdInstallData *)loaderData->stage2Data)->partition;
char *stage2loc = "/mnt/stage2/images/install.img";
char *stage2img;
int stage2inram;
if (!(rc=doPwMount(device, location, "auto", "ro", NULL))) {
if (!access(stage2loc, R_OK)) {
char *updpath;
//TODO? queryCDMediaCheck(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) {
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);
}
stage2ok = 1;
checked_asprintf(&url, "file://%s", location);
} else {
logMessage(INFO, "mounting stage2 failed");
umount(location);
}
} else {
/* this wasnt the CD we were looking for, clean up and */
/* try the next CD drive */
umount(location);
}
} else {
logMessage(WARNING, "Mount failed for %s at %s", device, location);
}
}
if (!url)
url = findAnacondaCD("/mnt/stage2");
if (url) {
if (!stage2ok)
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 */
dasdSetup();
#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: */