xref: /openbmc/linux/fs/nfs/nfsroot.c (revision 5ee9cd065836e5934710ca35653bce7905add20b)
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