linux/fs/nfs/nfsroot.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 *  Copyright (C) 1995, 1996  Gero Kuhlmann <gero@gkminix.han.de>
   4 *
   5 *  Allow an NFS filesystem to be mounted as root. The way this works is:
   6 *     (1) Use the IP autoconfig mechanism to set local IP addresses and routes.
   7 *     (2) Construct the device string and the options string using DHCP
   8 *         option 17 and/or kernel command line options.
   9 *     (3) When mount_root() sets up the root file system, pass these strings
  10 *         to the NFS client's regular mount interface via sys_mount().
  11 *
  12 *
  13 *      Changes:
  14 *
  15 *      Alan Cox        :       Removed get_address name clash with FPU.
  16 *      Alan Cox        :       Reformatted a bit.
  17 *      Gero Kuhlmann   :       Code cleanup
  18 *      Michael Rausch  :       Fixed recognition of an incoming RARP answer.
  19 *      Martin Mares    : (2.0) Auto-configuration via BOOTP supported.
  20 *      Martin Mares    :       Manual selection of interface & BOOTP/RARP.
  21 *      Martin Mares    :       Using network routes instead of host routes,
  22 *                              allowing the default configuration to be used
  23 *                              for normal operation of the host.
  24 *      Martin Mares    :       Randomized timer with exponential backoff
  25 *                              installed to minimize network congestion.
  26 *      Martin Mares    :       Code cleanup.
  27 *      Martin Mares    : (2.1) BOOTP and RARP made configuration options.
  28 *      Martin Mares    :       Server hostname generation fixed.
  29 *      Gerd Knorr      :       Fixed wired inode handling
  30 *      Martin Mares    : (2.2) "0.0.0.0" addresses from command line ignored.
  31 *      Martin Mares    :       RARP replies not tested for server address.
  32 *      Gero Kuhlmann   : (2.3) Some bug fixes and code cleanup again (please
  33 *                              send me your new patches _before_ bothering
  34 *                              Linus so that I don' always have to cleanup
  35 *                              _afterwards_ - thanks)
  36 *      Gero Kuhlmann   :       Last changes of Martin Mares undone.
  37 *      Gero Kuhlmann   :       RARP replies are tested for specified server
  38 *                              again. However, it's now possible to have
  39 *                              different RARP and NFS servers.
  40 *      Gero Kuhlmann   :       "0.0.0.0" addresses from command line are
  41 *                              now mapped to INADDR_NONE.
  42 *      Gero Kuhlmann   :       Fixed a bug which prevented BOOTP path name
  43 *                              from being used (thanks to Leo Spiekman)
  44 *      Andy Walker     :       Allow to specify the NFS server in nfs_root
  45 *                              without giving a path name
  46 *      Swen Thümmler   :       Allow to specify the NFS options in nfs_root
  47 *                              without giving a path name. Fix BOOTP request
  48 *                              for domainname (domainname is NIS domain, not
  49 *                              DNS domain!). Skip dummy devices for BOOTP.
  50 *      Jacek Zapala    :       Fixed a bug which prevented server-ip address
  51 *                              from nfsroot parameter from being used.
  52 *      Olaf Kirch      :       Adapted to new NFS code.
  53 *      Jakub Jelinek   :       Free used code segment.
  54 *      Marko Kohtala   :       Fixed some bugs.
  55 *      Martin Mares    :       Debug message cleanup
  56 *      Martin Mares    :       Changed to use the new generic IP layer autoconfig
  57 *                              code. BOOTP and RARP moved there.
  58 *      Martin Mares    :       Default path now contains host name instead of
  59 *                              host IP address (but host name defaults to IP
  60 *                              address anyway).
  61 *      Martin Mares    :       Use root_server_addr appropriately during setup.
  62 *      Martin Mares    :       Rewrote parameter parsing, now hopefully giving
  63 *                              correct overriding.
  64 *      Trond Myklebust :       Add in preliminary support for NFSv3 and TCP.
  65 *                              Fix bug in root_nfs_addr(). nfs_data.namlen
  66 *                              is NOT for the length of the hostname.
  67 *      Hua Qin         :       Support for mounting root file system via
  68 *                              NFS over TCP.
  69 *      Fabian Frederick:       Option parser rebuilt (using parser lib)
  70 *      Chuck Lever     :       Use super.c's text-based mount option parsing
  71 *      Chuck Lever     :       Add "nfsrootdebug".
  72 */
  73
  74#include <linux/types.h>
  75#include <linux/string.h>
  76#include <linux/init.h>
  77#include <linux/nfs.h>
  78#include <linux/nfs_fs.h>
  79#include <linux/utsname.h>
  80#include <linux/root_dev.h>
  81#include <net/ipconfig.h>
  82
  83#include "internal.h"
  84
  85#define NFSDBG_FACILITY NFSDBG_ROOT
  86
  87/* Default path we try to mount. "%s" gets replaced by our IP address */
  88#define NFS_ROOT                "/tftpboot/%s"
  89
  90/* Default NFSROOT mount options. */
  91#define NFS_DEF_OPTIONS         "vers=2,udp,rsize=4096,wsize=4096"
  92
  93/* Parameters passed from the kernel command line */
  94static char nfs_root_parms[NFS_MAXPATHLEN + 1] __initdata = "";
  95
  96/* Text-based mount options passed to super.c */
  97static char nfs_root_options[256] __initdata = NFS_DEF_OPTIONS;
  98
  99/* Address of NFS server */
 100static __be32 servaddr __initdata = htonl(INADDR_NONE);
 101
 102/* Name of directory to mount */
 103static char nfs_export_path[NFS_MAXPATHLEN + 1] __initdata = "";
 104
 105/* server:export path string passed to super.c */
 106static char nfs_root_device[NFS_MAXPATHLEN + 1] __initdata = "";
 107
 108#ifdef NFS_DEBUG
 109/*
 110 * When the "nfsrootdebug" kernel command line option is specified,
 111 * enable debugging messages for NFSROOT.
 112 */
 113static int __init nfs_root_debug(char *__unused)
 114{
 115        nfs_debug |= NFSDBG_ROOT | NFSDBG_MOUNT;
 116        return 1;
 117}
 118
 119__setup("nfsrootdebug", nfs_root_debug);
 120#endif
 121
 122/*
 123 *  Parse NFS server and directory information passed on the kernel
 124 *  command line.
 125 *
 126 *  nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>]
 127 *
 128 *  If there is a "%s" token in the <root-dir> string, it is replaced
 129 *  by the ASCII-representation of the client's IP address.
 130 */
 131static int __init nfs_root_setup(char *line)
 132{
 133        ROOT_DEV = Root_NFS;
 134
 135        if (line[0] == '/' || line[0] == ',' || (line[0] >= '0' && line[0] <= '9')) {
 136                strlcpy(nfs_root_parms, line, sizeof(nfs_root_parms));
 137        } else {
 138                size_t n = strlen(line) + sizeof(NFS_ROOT) - 1;
 139                if (n >= sizeof(nfs_root_parms))
 140                        line[sizeof(nfs_root_parms) - sizeof(NFS_ROOT) - 2] = '\0';
 141                sprintf(nfs_root_parms, NFS_ROOT, line);
 142        }
 143
 144        /*
 145         * Extract the IP address of the NFS server containing our
 146         * root file system, if one was specified.
 147         *
 148         * Note: root_nfs_parse_addr() removes the server-ip from
 149         *       nfs_root_parms, if it exists.
 150         */
 151        root_server_addr = root_nfs_parse_addr(nfs_root_parms);
 152
 153        return 1;
 154}
 155
 156__setup("nfsroot=", nfs_root_setup);
 157
 158static int __init root_nfs_copy(char *dest, const char *src,
 159                                     const size_t destlen)
 160{
 161        if (strlcpy(dest, src, destlen) > destlen)
 162                return -1;
 163        return 0;
 164}
 165
 166static int __init root_nfs_cat(char *dest, const char *src,
 167                               const size_t destlen)
 168{
 169        size_t len = strlen(dest);
 170
 171        if (len && dest[len - 1] != ',')
 172                if (strlcat(dest, ",", destlen) > destlen)
 173                        return -1;
 174
 175        if (strlcat(dest, src, destlen) > destlen)
 176                return -1;
 177        return 0;
 178}
 179
 180/*
 181 * Parse out root export path and mount options from
 182 * passed-in string @incoming.
 183 *
 184 * Copy the export path into @exppath.
 185 */
 186static int __init root_nfs_parse_options(char *incoming, char *exppath,
 187                                         const size_t exppathlen)
 188{
 189        char *p;
 190
 191        /*
 192         * Set the NFS remote path
 193         */
 194        p = strsep(&incoming, ",");
 195        if (*p != '\0' && strcmp(p, "default") != 0)
 196                if (root_nfs_copy(exppath, p, exppathlen))
 197                        return -1;
 198
 199        /*
 200         * @incoming now points to the rest of the string; if it
 201         * contains something, append it to our root options buffer
 202         */
 203        if (incoming != NULL && *incoming != '\0')
 204                if (root_nfs_cat(nfs_root_options, incoming,
 205                                                sizeof(nfs_root_options)))
 206                        return -1;
 207        return 0;
 208}
 209
 210/*
 211 *  Decode the export directory path name and NFS options from
 212 *  the kernel command line.  This has to be done late in order to
 213 *  use a dynamically acquired client IP address for the remote
 214 *  root directory path.
 215 *
 216 *  Returns zero if successful; otherwise -1 is returned.
 217 */
 218static int __init root_nfs_data(char *cmdline)
 219{
 220        char mand_options[sizeof("nolock,addr=") + INET_ADDRSTRLEN + 1];
 221        int len, retval = -1;
 222        char *tmp = NULL;
 223        const size_t tmplen = sizeof(nfs_export_path);
 224
 225        tmp = kzalloc(tmplen, GFP_KERNEL);
 226        if (tmp == NULL)
 227                goto out_nomem;
 228        strcpy(tmp, NFS_ROOT);
 229
 230        if (root_server_path[0] != '\0') {
 231                dprintk("Root-NFS: DHCPv4 option 17: %s\n",
 232                        root_server_path);
 233                if (root_nfs_parse_options(root_server_path, tmp, tmplen))
 234                        goto out_optionstoolong;
 235        }
 236
 237        if (cmdline[0] != '\0') {
 238                dprintk("Root-NFS: nfsroot=%s\n", cmdline);
 239                if (root_nfs_parse_options(cmdline, tmp, tmplen))
 240                        goto out_optionstoolong;
 241        }
 242
 243        /*
 244         * Append mandatory options for nfsroot so they override
 245         * what has come before
 246         */
 247        snprintf(mand_options, sizeof(mand_options), "nolock,addr=%pI4",
 248                        &servaddr);
 249        if (root_nfs_cat(nfs_root_options, mand_options,
 250                                                sizeof(nfs_root_options)))
 251                goto out_optionstoolong;
 252
 253        /*
 254         * Set up nfs_root_device.  For NFS mounts, this looks like
 255         *
 256         *      server:/path
 257         *
 258         * At this point, utsname()->nodename contains our local
 259         * IP address or hostname, set by ipconfig.  If "%s" exists
 260         * in tmp, substitute the nodename, then shovel the whole
 261         * mess into nfs_root_device.
 262         */
 263        len = snprintf(nfs_export_path, sizeof(nfs_export_path),
 264                                tmp, utsname()->nodename);
 265        if (len >= (int)sizeof(nfs_export_path))
 266                goto out_devnametoolong;
 267        len = snprintf(nfs_root_device, sizeof(nfs_root_device),
 268                                "%pI4:%s", &servaddr, nfs_export_path);
 269        if (len >= (int)sizeof(nfs_root_device))
 270                goto out_devnametoolong;
 271
 272        retval = 0;
 273
 274out:
 275        kfree(tmp);
 276        return retval;
 277out_nomem:
 278        printk(KERN_ERR "Root-NFS: could not allocate memory\n");
 279        goto out;
 280out_optionstoolong:
 281        printk(KERN_ERR "Root-NFS: mount options string too long\n");
 282        goto out;
 283out_devnametoolong:
 284        printk(KERN_ERR "Root-NFS: root device name too long.\n");
 285        goto out;
 286}
 287
 288/**
 289 * nfs_root_data - Return prepared 'data' for NFSROOT mount
 290 * @root_device: OUT: address of string containing NFSROOT device
 291 * @root_data: OUT: address of string containing NFSROOT mount options
 292 *
 293 * Returns zero and sets @root_device and @root_data if successful,
 294 * otherwise -1 is returned.
 295 */
 296int __init nfs_root_data(char **root_device, char **root_data)
 297{
 298        servaddr = root_server_addr;
 299        if (servaddr == htonl(INADDR_NONE)) {
 300                printk(KERN_ERR "Root-NFS: no NFS server address\n");
 301                return -1;
 302        }
 303
 304        if (root_nfs_data(nfs_root_parms) < 0)
 305                return -1;
 306
 307        *root_device = nfs_root_device;
 308        *root_data = nfs_root_options;
 309        return 0;
 310}
 311