qubes-installer-qubes-os/loader/driverdisk.c
2011-01-18 04:24:57 -05:00

890 lines
25 KiB
C

/*
* 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 "log.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"
/* 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;
}
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;
}