xref: /openbmc/linux/fs/nfs/nfsroot.c (revision 07d9a767)
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  #if defined(CONFIG_NFS_V2)
92  #define NFS_DEF_OPTIONS		"vers=2,tcp,rsize=4096,wsize=4096"
93  #elif defined(CONFIG_NFS_V3)
94  #define NFS_DEF_OPTIONS		"vers=3,tcp,rsize=4096,wsize=4096"
95  #else
96  #define NFS_DEF_OPTIONS		"vers=4,tcp,rsize=4096,wsize=4096"
97  #endif
98  
99  /* Parameters passed from the kernel command line */
100  static char nfs_root_parms[NFS_MAXPATHLEN + 1] __initdata = "";
101  
102  /* Text-based mount options passed to super.c */
103  static char nfs_root_options[256] __initdata = NFS_DEF_OPTIONS;
104  
105  /* Address of NFS server */
106  static __be32 servaddr __initdata = htonl(INADDR_NONE);
107  
108  /* Name of directory to mount */
109  static char nfs_export_path[NFS_MAXPATHLEN + 1] __initdata = "";
110  
111  /* server:export path string passed to super.c */
112  static char nfs_root_device[NFS_MAXPATHLEN + 1] __initdata = "";
113  
114  #ifdef NFS_DEBUG
115  /*
116   * When the "nfsrootdebug" kernel command line option is specified,
117   * enable debugging messages for NFSROOT.
118   */
119  static int __init nfs_root_debug(char *__unused)
120  {
121  	nfs_debug |= NFSDBG_ROOT | NFSDBG_MOUNT;
122  	return 1;
123  }
124  
125  __setup("nfsrootdebug", nfs_root_debug);
126  #endif
127  
128  /*
129   *  Parse NFS server and directory information passed on the kernel
130   *  command line.
131   *
132   *  nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>]
133   *
134   *  If there is a "%s" token in the <root-dir> string, it is replaced
135   *  by the ASCII-representation of the client's IP address.
136   */
137  static int __init nfs_root_setup(char *line)
138  {
139  	ROOT_DEV = Root_NFS;
140  
141  	if (line[0] == '/' || line[0] == ',' || (line[0] >= '0' && line[0] <= '9')) {
142  		strlcpy(nfs_root_parms, line, sizeof(nfs_root_parms));
143  	} else {
144  		size_t n = strlen(line) + sizeof(NFS_ROOT) - 1;
145  		if (n >= sizeof(nfs_root_parms))
146  			line[sizeof(nfs_root_parms) - sizeof(NFS_ROOT) - 2] = '\0';
147  		sprintf(nfs_root_parms, NFS_ROOT, line);
148  	}
149  
150  	/*
151  	 * Extract the IP address of the NFS server containing our
152  	 * root file system, if one was specified.
153  	 *
154  	 * Note: root_nfs_parse_addr() removes the server-ip from
155  	 *	 nfs_root_parms, if it exists.
156  	 */
157  	root_server_addr = root_nfs_parse_addr(nfs_root_parms);
158  
159  	return 1;
160  }
161  
162  __setup("nfsroot=", nfs_root_setup);
163  
164  static int __init root_nfs_copy(char *dest, const char *src,
165  				     const size_t destlen)
166  {
167  	if (strlcpy(dest, src, destlen) > destlen)
168  		return -1;
169  	return 0;
170  }
171  
172  static int __init root_nfs_cat(char *dest, const char *src,
173  			       const size_t destlen)
174  {
175  	size_t len = strlen(dest);
176  
177  	if (len && dest[len - 1] != ',')
178  		if (strlcat(dest, ",", destlen) > destlen)
179  			return -1;
180  
181  	if (strlcat(dest, src, destlen) > destlen)
182  		return -1;
183  	return 0;
184  }
185  
186  /*
187   * Parse out root export path and mount options from
188   * passed-in string @incoming.
189   *
190   * Copy the export path into @exppath.
191   */
192  static int __init root_nfs_parse_options(char *incoming, char *exppath,
193  					 const size_t exppathlen)
194  {
195  	char *p;
196  
197  	/*
198  	 * Set the NFS remote path
199  	 */
200  	p = strsep(&incoming, ",");
201  	if (*p != '\0' && strcmp(p, "default") != 0)
202  		if (root_nfs_copy(exppath, p, exppathlen))
203  			return -1;
204  
205  	/*
206  	 * @incoming now points to the rest of the string; if it
207  	 * contains something, append it to our root options buffer
208  	 */
209  	if (incoming != NULL && *incoming != '\0')
210  		if (root_nfs_cat(nfs_root_options, incoming,
211  						sizeof(nfs_root_options)))
212  			return -1;
213  	return 0;
214  }
215  
216  /*
217   *  Decode the export directory path name and NFS options from
218   *  the kernel command line.  This has to be done late in order to
219   *  use a dynamically acquired client IP address for the remote
220   *  root directory path.
221   *
222   *  Returns zero if successful; otherwise -1 is returned.
223   */
224  static int __init root_nfs_data(char *cmdline)
225  {
226  	char mand_options[sizeof("nolock,addr=") + INET_ADDRSTRLEN + 1];
227  	int len, retval = -1;
228  	char *tmp = NULL;
229  	const size_t tmplen = sizeof(nfs_export_path);
230  
231  	tmp = kzalloc(tmplen, GFP_KERNEL);
232  	if (tmp == NULL)
233  		goto out_nomem;
234  	strcpy(tmp, NFS_ROOT);
235  
236  	if (root_server_path[0] != '\0') {
237  		dprintk("Root-NFS: DHCPv4 option 17: %s\n",
238  			root_server_path);
239  		if (root_nfs_parse_options(root_server_path, tmp, tmplen))
240  			goto out_optionstoolong;
241  	}
242  
243  	if (cmdline[0] != '\0') {
244  		dprintk("Root-NFS: nfsroot=%s\n", cmdline);
245  		if (root_nfs_parse_options(cmdline, tmp, tmplen))
246  			goto out_optionstoolong;
247  	}
248  
249  	/*
250  	 * Append mandatory options for nfsroot so they override
251  	 * what has come before
252  	 */
253  	snprintf(mand_options, sizeof(mand_options), "nolock,addr=%pI4",
254  			&servaddr);
255  	if (root_nfs_cat(nfs_root_options, mand_options,
256  						sizeof(nfs_root_options)))
257  		goto out_optionstoolong;
258  
259  	/*
260  	 * Set up nfs_root_device.  For NFS mounts, this looks like
261  	 *
262  	 *	server:/path
263  	 *
264  	 * At this point, utsname()->nodename contains our local
265  	 * IP address or hostname, set by ipconfig.  If "%s" exists
266  	 * in tmp, substitute the nodename, then shovel the whole
267  	 * mess into nfs_root_device.
268  	 */
269  	len = snprintf(nfs_export_path, sizeof(nfs_export_path),
270  				tmp, utsname()->nodename);
271  	if (len >= (int)sizeof(nfs_export_path))
272  		goto out_devnametoolong;
273  	len = snprintf(nfs_root_device, sizeof(nfs_root_device),
274  				"%pI4:%s", &servaddr, nfs_export_path);
275  	if (len >= (int)sizeof(nfs_root_device))
276  		goto out_devnametoolong;
277  
278  	retval = 0;
279  
280  out:
281  	kfree(tmp);
282  	return retval;
283  out_nomem:
284  	printk(KERN_ERR "Root-NFS: could not allocate memory\n");
285  	goto out;
286  out_optionstoolong:
287  	printk(KERN_ERR "Root-NFS: mount options string too long\n");
288  	goto out;
289  out_devnametoolong:
290  	printk(KERN_ERR "Root-NFS: root device name too long.\n");
291  	goto out;
292  }
293  
294  /**
295   * nfs_root_data - Return prepared 'data' for NFSROOT mount
296   * @root_device: OUT: address of string containing NFSROOT device
297   * @root_data: OUT: address of string containing NFSROOT mount options
298   *
299   * Returns zero and sets @root_device and @root_data if successful,
300   * otherwise -1 is returned.
301   */
302  int __init nfs_root_data(char **root_device, char **root_data)
303  {
304  	servaddr = root_server_addr;
305  	if (servaddr == htonl(INADDR_NONE)) {
306  		printk(KERN_ERR "Root-NFS: no NFS server address\n");
307  		return -1;
308  	}
309  
310  	if (root_nfs_data(nfs_root_parms) < 0)
311  		return -1;
312  
313  	*root_device = nfs_root_device;
314  	*root_data = nfs_root_options;
315  	return 0;
316  }
317