diff options
Diffstat (limited to 'loader/nfsinstall.c')
-rw-r--r-- | loader/nfsinstall.c | 556 |
1 files changed, 556 insertions, 0 deletions
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: */ |