1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds * Copyright (C) 1995, 1996 Gero Kuhlmann <gero@gkminix.han.de>
41da177e4SLinus Torvalds *
51da177e4SLinus Torvalds * Allow an NFS filesystem to be mounted as root. The way this works is:
61da177e4SLinus Torvalds * (1) Use the IP autoconfig mechanism to set local IP addresses and routes.
756463e50SChuck Lever * (2) Construct the device string and the options string using DHCP
856463e50SChuck Lever * option 17 and/or kernel command line options.
956463e50SChuck Lever * (3) When mount_root() sets up the root file system, pass these strings
1056463e50SChuck Lever * to the NFS client's regular mount interface via sys_mount().
111da177e4SLinus Torvalds *
121da177e4SLinus Torvalds *
131da177e4SLinus Torvalds * Changes:
141da177e4SLinus Torvalds *
151da177e4SLinus Torvalds * Alan Cox : Removed get_address name clash with FPU.
161da177e4SLinus Torvalds * Alan Cox : Reformatted a bit.
171da177e4SLinus Torvalds * Gero Kuhlmann : Code cleanup
181da177e4SLinus Torvalds * Michael Rausch : Fixed recognition of an incoming RARP answer.
191da177e4SLinus Torvalds * Martin Mares : (2.0) Auto-configuration via BOOTP supported.
201da177e4SLinus Torvalds * Martin Mares : Manual selection of interface & BOOTP/RARP.
211da177e4SLinus Torvalds * Martin Mares : Using network routes instead of host routes,
221da177e4SLinus Torvalds * allowing the default configuration to be used
231da177e4SLinus Torvalds * for normal operation of the host.
241da177e4SLinus Torvalds * Martin Mares : Randomized timer with exponential backoff
251da177e4SLinus Torvalds * installed to minimize network congestion.
261da177e4SLinus Torvalds * Martin Mares : Code cleanup.
271da177e4SLinus Torvalds * Martin Mares : (2.1) BOOTP and RARP made configuration options.
281da177e4SLinus Torvalds * Martin Mares : Server hostname generation fixed.
291da177e4SLinus Torvalds * Gerd Knorr : Fixed wired inode handling
301da177e4SLinus Torvalds * Martin Mares : (2.2) "0.0.0.0" addresses from command line ignored.
311da177e4SLinus Torvalds * Martin Mares : RARP replies not tested for server address.
321da177e4SLinus Torvalds * Gero Kuhlmann : (2.3) Some bug fixes and code cleanup again (please
331da177e4SLinus Torvalds * send me your new patches _before_ bothering
341da177e4SLinus Torvalds * Linus so that I don' always have to cleanup
351da177e4SLinus Torvalds * _afterwards_ - thanks)
361da177e4SLinus Torvalds * Gero Kuhlmann : Last changes of Martin Mares undone.
371da177e4SLinus Torvalds * Gero Kuhlmann : RARP replies are tested for specified server
381da177e4SLinus Torvalds * again. However, it's now possible to have
391da177e4SLinus Torvalds * different RARP and NFS servers.
401da177e4SLinus Torvalds * Gero Kuhlmann : "0.0.0.0" addresses from command line are
411da177e4SLinus Torvalds * now mapped to INADDR_NONE.
421da177e4SLinus Torvalds * Gero Kuhlmann : Fixed a bug which prevented BOOTP path name
431da177e4SLinus Torvalds * from being used (thanks to Leo Spiekman)
441da177e4SLinus Torvalds * Andy Walker : Allow to specify the NFS server in nfs_root
451da177e4SLinus Torvalds * without giving a path name
4696de0e25SJan Engelhardt * Swen Thümmler : Allow to specify the NFS options in nfs_root
471da177e4SLinus Torvalds * without giving a path name. Fix BOOTP request
481da177e4SLinus Torvalds * for domainname (domainname is NIS domain, not
491da177e4SLinus Torvalds * DNS domain!). Skip dummy devices for BOOTP.
501da177e4SLinus Torvalds * Jacek Zapala : Fixed a bug which prevented server-ip address
511da177e4SLinus Torvalds * from nfsroot parameter from being used.
521da177e4SLinus Torvalds * Olaf Kirch : Adapted to new NFS code.
531da177e4SLinus Torvalds * Jakub Jelinek : Free used code segment.
541da177e4SLinus Torvalds * Marko Kohtala : Fixed some bugs.
551da177e4SLinus Torvalds * Martin Mares : Debug message cleanup
561da177e4SLinus Torvalds * Martin Mares : Changed to use the new generic IP layer autoconfig
571da177e4SLinus Torvalds * code. BOOTP and RARP moved there.
581da177e4SLinus Torvalds * Martin Mares : Default path now contains host name instead of
591da177e4SLinus Torvalds * host IP address (but host name defaults to IP
601da177e4SLinus Torvalds * address anyway).
611da177e4SLinus Torvalds * Martin Mares : Use root_server_addr appropriately during setup.
621da177e4SLinus Torvalds * Martin Mares : Rewrote parameter parsing, now hopefully giving
631da177e4SLinus Torvalds * correct overriding.
641da177e4SLinus Torvalds * Trond Myklebust : Add in preliminary support for NFSv3 and TCP.
651da177e4SLinus Torvalds * Fix bug in root_nfs_addr(). nfs_data.namlen
661da177e4SLinus Torvalds * is NOT for the length of the hostname.
671da177e4SLinus Torvalds * Hua Qin : Support for mounting root file system via
681da177e4SLinus Torvalds * NFS over TCP.
691da177e4SLinus Torvalds * Fabian Frederick: Option parser rebuilt (using parser lib)
7056463e50SChuck Lever * Chuck Lever : Use super.c's text-based mount option parsing
71306a0753SChuck Lever * Chuck Lever : Add "nfsrootdebug".
721da177e4SLinus Torvalds */
731da177e4SLinus Torvalds
741da177e4SLinus Torvalds #include <linux/types.h>
751da177e4SLinus Torvalds #include <linux/string.h>
761da177e4SLinus Torvalds #include <linux/init.h>
771da177e4SLinus Torvalds #include <linux/nfs.h>
781da177e4SLinus Torvalds #include <linux/nfs_fs.h>
791da177e4SLinus Torvalds #include <linux/utsname.h>
801da177e4SLinus Torvalds #include <linux/root_dev.h>
811da177e4SLinus Torvalds #include <net/ipconfig.h>
821da177e4SLinus Torvalds
83146ec944SChuck Lever #include "internal.h"
84146ec944SChuck Lever
851da177e4SLinus Torvalds #define NFSDBG_FACILITY NFSDBG_ROOT
861da177e4SLinus Torvalds
871da177e4SLinus Torvalds /* Default path we try to mount. "%s" gets replaced by our IP address */
881da177e4SLinus Torvalds #define NFS_ROOT "/tftpboot/%s"
891da177e4SLinus Torvalds
9053d47375SChuck Lever /* Default NFSROOT mount options. */
913fc2bfa3SHelge Deller #if defined(CONFIG_NFS_V2)
9289c8023fSLiwei Song #define NFS_DEF_OPTIONS "vers=2,tcp,rsize=4096,wsize=4096"
933fc2bfa3SHelge Deller #elif defined(CONFIG_NFS_V3)
943fc2bfa3SHelge Deller #define NFS_DEF_OPTIONS "vers=3,tcp,rsize=4096,wsize=4096"
953fc2bfa3SHelge Deller #else
963fc2bfa3SHelge Deller #define NFS_DEF_OPTIONS "vers=4,tcp,rsize=4096,wsize=4096"
973fc2bfa3SHelge Deller #endif
9853d47375SChuck Lever
991da177e4SLinus Torvalds /* Parameters passed from the kernel command line */
100c6466193SLi RongQing static char nfs_root_parms[NFS_MAXPATHLEN + 1] __initdata = "";
1011da177e4SLinus Torvalds
10256463e50SChuck Lever /* Text-based mount options passed to super.c */
10353d47375SChuck Lever static char nfs_root_options[256] __initdata = NFS_DEF_OPTIONS;
10456463e50SChuck Lever
1051da177e4SLinus Torvalds /* Address of NFS server */
1068d232103SChuck Lever static __be32 servaddr __initdata = htonl(INADDR_NONE);
1071da177e4SLinus Torvalds
1081da177e4SLinus Torvalds /* Name of directory to mount */
10956463e50SChuck Lever static char nfs_export_path[NFS_MAXPATHLEN + 1] __initdata = "";
11056463e50SChuck Lever
11156463e50SChuck Lever /* server:export path string passed to super.c */
11256463e50SChuck Lever static char nfs_root_device[NFS_MAXPATHLEN + 1] __initdata = "";
1131da177e4SLinus Torvalds
114e27d359eSTrond Myklebust #ifdef NFS_DEBUG
1151da177e4SLinus Torvalds /*
116306a0753SChuck Lever * When the "nfsrootdebug" kernel command line option is specified,
117306a0753SChuck Lever * enable debugging messages for NFSROOT.
118306a0753SChuck Lever */
nfs_root_debug(char * __unused)119306a0753SChuck Lever static int __init nfs_root_debug(char *__unused)
120306a0753SChuck Lever {
121306a0753SChuck Lever nfs_debug |= NFSDBG_ROOT | NFSDBG_MOUNT;
122306a0753SChuck Lever return 1;
123306a0753SChuck Lever }
124306a0753SChuck Lever
125306a0753SChuck Lever __setup("nfsrootdebug", nfs_root_debug);
126036a1075STrond Myklebust #endif
127306a0753SChuck Lever
128306a0753SChuck Lever /*
1291da177e4SLinus Torvalds * Parse NFS server and directory information passed on the kernel
1301da177e4SLinus Torvalds * command line.
13160ac0368SChuck Lever *
13260ac0368SChuck Lever * nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>]
13360ac0368SChuck Lever *
13460ac0368SChuck Lever * If there is a "%s" token in the <root-dir> string, it is replaced
13560ac0368SChuck Lever * by the ASCII-representation of the client's IP address.
1361da177e4SLinus Torvalds */
nfs_root_setup(char * line)1371da177e4SLinus Torvalds static int __init nfs_root_setup(char *line)
1381da177e4SLinus Torvalds {
1391da177e4SLinus Torvalds ROOT_DEV = Root_NFS;
14060ac0368SChuck Lever
1411da177e4SLinus Torvalds if (line[0] == '/' || line[0] == ',' || (line[0] >= '0' && line[0] <= '9')) {
1420dd7439fSWolfram Sang strscpy(nfs_root_parms, line, sizeof(nfs_root_parms));
1431da177e4SLinus Torvalds } else {
14460ac0368SChuck Lever size_t n = strlen(line) + sizeof(NFS_ROOT) - 1;
14560ac0368SChuck Lever if (n >= sizeof(nfs_root_parms))
14660ac0368SChuck Lever line[sizeof(nfs_root_parms) - sizeof(NFS_ROOT) - 2] = '\0';
14760ac0368SChuck Lever sprintf(nfs_root_parms, NFS_ROOT, line);
1481da177e4SLinus Torvalds }
14960ac0368SChuck Lever
15060ac0368SChuck Lever /*
15160ac0368SChuck Lever * Extract the IP address of the NFS server containing our
15260ac0368SChuck Lever * root file system, if one was specified.
15360ac0368SChuck Lever *
15460ac0368SChuck Lever * Note: root_nfs_parse_addr() removes the server-ip from
15560ac0368SChuck Lever * nfs_root_parms, if it exists.
15660ac0368SChuck Lever */
15760ac0368SChuck Lever root_server_addr = root_nfs_parse_addr(nfs_root_parms);
15860ac0368SChuck Lever
1591da177e4SLinus Torvalds return 1;
1601da177e4SLinus Torvalds }
1611da177e4SLinus Torvalds
1621da177e4SLinus Torvalds __setup("nfsroot=", nfs_root_setup);
1631da177e4SLinus Torvalds
root_nfs_copy(char * dest,const char * src,const size_t destlen)16456463e50SChuck Lever static int __init root_nfs_copy(char *dest, const char *src,
16556463e50SChuck Lever const size_t destlen)
16656463e50SChuck Lever {
1678ca25e00SAzeem Shaikh if (strscpy(dest, src, destlen) == -E2BIG)
16856463e50SChuck Lever return -1;
16956463e50SChuck Lever return 0;
17056463e50SChuck Lever }
17156463e50SChuck Lever
root_nfs_cat(char * dest,const char * src,const size_t destlen)17256463e50SChuck Lever static int __init root_nfs_cat(char *dest, const char *src,
17356463e50SChuck Lever const size_t destlen)
17456463e50SChuck Lever {
17553d47375SChuck Lever size_t len = strlen(dest);
17653d47375SChuck Lever
17753d47375SChuck Lever if (len && dest[len - 1] != ',')
178*7f029d24SChristophe JAILLET if (strlcat(dest, ",", destlen) >= destlen)
17953d47375SChuck Lever return -1;
18053d47375SChuck Lever
181*7f029d24SChristophe JAILLET if (strlcat(dest, src, destlen) >= destlen)
18256463e50SChuck Lever return -1;
18356463e50SChuck Lever return 0;
18456463e50SChuck Lever }
18556463e50SChuck Lever
18656463e50SChuck Lever /*
18756463e50SChuck Lever * Parse out root export path and mount options from
18856463e50SChuck Lever * passed-in string @incoming.
18956463e50SChuck Lever *
19056463e50SChuck Lever * Copy the export path into @exppath.
19156463e50SChuck Lever */
root_nfs_parse_options(char * incoming,char * exppath,const size_t exppathlen)19256463e50SChuck Lever static int __init root_nfs_parse_options(char *incoming, char *exppath,
19356463e50SChuck Lever const size_t exppathlen)
19456463e50SChuck Lever {
19556463e50SChuck Lever char *p;
19656463e50SChuck Lever
19756463e50SChuck Lever /*
19856463e50SChuck Lever * Set the NFS remote path
19956463e50SChuck Lever */
20056463e50SChuck Lever p = strsep(&incoming, ",");
20156463e50SChuck Lever if (*p != '\0' && strcmp(p, "default") != 0)
20256463e50SChuck Lever if (root_nfs_copy(exppath, p, exppathlen))
20356463e50SChuck Lever return -1;
20456463e50SChuck Lever
20556463e50SChuck Lever /*
20656463e50SChuck Lever * @incoming now points to the rest of the string; if it
20756463e50SChuck Lever * contains something, append it to our root options buffer
20856463e50SChuck Lever */
20956463e50SChuck Lever if (incoming != NULL && *incoming != '\0')
21056463e50SChuck Lever if (root_nfs_cat(nfs_root_options, incoming,
21156463e50SChuck Lever sizeof(nfs_root_options)))
21256463e50SChuck Lever return -1;
21356463e50SChuck Lever return 0;
21456463e50SChuck Lever }
21556463e50SChuck Lever
21656463e50SChuck Lever /*
21756463e50SChuck Lever * Decode the export directory path name and NFS options from
21856463e50SChuck Lever * the kernel command line. This has to be done late in order to
21956463e50SChuck Lever * use a dynamically acquired client IP address for the remote
22056463e50SChuck Lever * root directory path.
22156463e50SChuck Lever *
22256463e50SChuck Lever * Returns zero if successful; otherwise -1 is returned.
22356463e50SChuck Lever */
root_nfs_data(char * cmdline)22456463e50SChuck Lever static int __init root_nfs_data(char *cmdline)
22556463e50SChuck Lever {
22653d47375SChuck Lever char mand_options[sizeof("nolock,addr=") + INET_ADDRSTRLEN + 1];
22756463e50SChuck Lever int len, retval = -1;
22856463e50SChuck Lever char *tmp = NULL;
22956463e50SChuck Lever const size_t tmplen = sizeof(nfs_export_path);
23056463e50SChuck Lever
23156463e50SChuck Lever tmp = kzalloc(tmplen, GFP_KERNEL);
23256463e50SChuck Lever if (tmp == NULL)
23356463e50SChuck Lever goto out_nomem;
23456463e50SChuck Lever strcpy(tmp, NFS_ROOT);
23556463e50SChuck Lever
23656463e50SChuck Lever if (root_server_path[0] != '\0') {
23756463e50SChuck Lever dprintk("Root-NFS: DHCPv4 option 17: %s\n",
23856463e50SChuck Lever root_server_path);
23956463e50SChuck Lever if (root_nfs_parse_options(root_server_path, tmp, tmplen))
24056463e50SChuck Lever goto out_optionstoolong;
24156463e50SChuck Lever }
24256463e50SChuck Lever
24356463e50SChuck Lever if (cmdline[0] != '\0') {
24456463e50SChuck Lever dprintk("Root-NFS: nfsroot=%s\n", cmdline);
24556463e50SChuck Lever if (root_nfs_parse_options(cmdline, tmp, tmplen))
24656463e50SChuck Lever goto out_optionstoolong;
24756463e50SChuck Lever }
24856463e50SChuck Lever
24956463e50SChuck Lever /*
25056463e50SChuck Lever * Append mandatory options for nfsroot so they override
25156463e50SChuck Lever * what has come before
25256463e50SChuck Lever */
25353d47375SChuck Lever snprintf(mand_options, sizeof(mand_options), "nolock,addr=%pI4",
25456463e50SChuck Lever &servaddr);
25553d47375SChuck Lever if (root_nfs_cat(nfs_root_options, mand_options,
25656463e50SChuck Lever sizeof(nfs_root_options)))
25756463e50SChuck Lever goto out_optionstoolong;
25856463e50SChuck Lever
25956463e50SChuck Lever /*
26056463e50SChuck Lever * Set up nfs_root_device. For NFS mounts, this looks like
26156463e50SChuck Lever *
26256463e50SChuck Lever * server:/path
26356463e50SChuck Lever *
26456463e50SChuck Lever * At this point, utsname()->nodename contains our local
26556463e50SChuck Lever * IP address or hostname, set by ipconfig. If "%s" exists
26656463e50SChuck Lever * in tmp, substitute the nodename, then shovel the whole
26756463e50SChuck Lever * mess into nfs_root_device.
26856463e50SChuck Lever */
26956463e50SChuck Lever len = snprintf(nfs_export_path, sizeof(nfs_export_path),
27056463e50SChuck Lever tmp, utsname()->nodename);
271c7c545d4SDan Carpenter if (len >= (int)sizeof(nfs_export_path))
27256463e50SChuck Lever goto out_devnametoolong;
27356463e50SChuck Lever len = snprintf(nfs_root_device, sizeof(nfs_root_device),
27456463e50SChuck Lever "%pI4:%s", &servaddr, nfs_export_path);
275c7c545d4SDan Carpenter if (len >= (int)sizeof(nfs_root_device))
27656463e50SChuck Lever goto out_devnametoolong;
27756463e50SChuck Lever
27856463e50SChuck Lever retval = 0;
27956463e50SChuck Lever
28056463e50SChuck Lever out:
28156463e50SChuck Lever kfree(tmp);
28256463e50SChuck Lever return retval;
28356463e50SChuck Lever out_nomem:
28456463e50SChuck Lever printk(KERN_ERR "Root-NFS: could not allocate memory\n");
28556463e50SChuck Lever goto out;
28656463e50SChuck Lever out_optionstoolong:
28756463e50SChuck Lever printk(KERN_ERR "Root-NFS: mount options string too long\n");
28856463e50SChuck Lever goto out;
28956463e50SChuck Lever out_devnametoolong:
29056463e50SChuck Lever printk(KERN_ERR "Root-NFS: root device name too long.\n");
29156463e50SChuck Lever goto out;
29256463e50SChuck Lever }
29356463e50SChuck Lever
29456463e50SChuck Lever /**
29556463e50SChuck Lever * nfs_root_data - Return prepared 'data' for NFSROOT mount
29656463e50SChuck Lever * @root_device: OUT: address of string containing NFSROOT device
29756463e50SChuck Lever * @root_data: OUT: address of string containing NFSROOT mount options
29856463e50SChuck Lever *
29956463e50SChuck Lever * Returns zero and sets @root_device and @root_data if successful,
30056463e50SChuck Lever * otherwise -1 is returned.
30156463e50SChuck Lever */
nfs_root_data(char ** root_device,char ** root_data)30256463e50SChuck Lever int __init nfs_root_data(char **root_device, char **root_data)
30356463e50SChuck Lever {
30456463e50SChuck Lever servaddr = root_server_addr;
30556463e50SChuck Lever if (servaddr == htonl(INADDR_NONE)) {
30656463e50SChuck Lever printk(KERN_ERR "Root-NFS: no NFS server address\n");
30756463e50SChuck Lever return -1;
30856463e50SChuck Lever }
30956463e50SChuck Lever
31056463e50SChuck Lever if (root_nfs_data(nfs_root_parms) < 0)
31156463e50SChuck Lever return -1;
31256463e50SChuck Lever
31356463e50SChuck Lever *root_device = nfs_root_device;
31456463e50SChuck Lever *root_data = nfs_root_options;
31556463e50SChuck Lever return 0;
31656463e50SChuck Lever }
317