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

538 lines
14 KiB
C

/*
* 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 "log.h"
#include "lang.h"
#include "mediacheck.h"
#include "method.h"
#include "../isys/imount.h"
#include "../isys/isys.h"
#include "../isys/cpio.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, "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;
}
}