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 | strscpy(p: nfs_root_parms, q: line, size: 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(buf: 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(name: 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 (strscpy(p: dest, q: src, size: destlen) == -E2BIG) |
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(p: dest, q: "," , avail: destlen) > destlen) |
179 | return -1; |
180 | |
181 | if (strlcat(p: dest, q: src, avail: 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(dest: exppath, src: p, destlen: 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(dest: nfs_root_options, src: incoming, |
211 | destlen: 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(size: tmplen, GFP_KERNEL); |
232 | if (tmp == NULL) |
233 | goto out_nomem; |
234 | strcpy(p: 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(incoming: root_server_path, exppath: tmp, exppathlen: 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(incoming: cmdline, exppath: tmp, exppathlen: 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(buf: mand_options, size: sizeof(mand_options), fmt: "nolock,addr=%pI4" , |
254 | &servaddr); |
255 | if (root_nfs_cat(dest: nfs_root_options, src: mand_options, |
256 | destlen: 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(buf: nfs_export_path, size: sizeof(nfs_export_path), |
270 | fmt: tmp, utsname()->nodename); |
271 | if (len >= (int)sizeof(nfs_export_path)) |
272 | goto out_devnametoolong; |
273 | len = snprintf(buf: nfs_root_device, size: sizeof(nfs_root_device), |
274 | fmt: "%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(objp: 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(cmdline: 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 | |