18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 1995, 1996 Gero Kuhlmann <gero@gkminix.han.de> 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Allow an NFS filesystem to be mounted as root. The way this works is: 68c2ecf20Sopenharmony_ci * (1) Use the IP autoconfig mechanism to set local IP addresses and routes. 78c2ecf20Sopenharmony_ci * (2) Construct the device string and the options string using DHCP 88c2ecf20Sopenharmony_ci * option 17 and/or kernel command line options. 98c2ecf20Sopenharmony_ci * (3) When mount_root() sets up the root file system, pass these strings 108c2ecf20Sopenharmony_ci * to the NFS client's regular mount interface via sys_mount(). 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * Changes: 148c2ecf20Sopenharmony_ci * 158c2ecf20Sopenharmony_ci * Alan Cox : Removed get_address name clash with FPU. 168c2ecf20Sopenharmony_ci * Alan Cox : Reformatted a bit. 178c2ecf20Sopenharmony_ci * Gero Kuhlmann : Code cleanup 188c2ecf20Sopenharmony_ci * Michael Rausch : Fixed recognition of an incoming RARP answer. 198c2ecf20Sopenharmony_ci * Martin Mares : (2.0) Auto-configuration via BOOTP supported. 208c2ecf20Sopenharmony_ci * Martin Mares : Manual selection of interface & BOOTP/RARP. 218c2ecf20Sopenharmony_ci * Martin Mares : Using network routes instead of host routes, 228c2ecf20Sopenharmony_ci * allowing the default configuration to be used 238c2ecf20Sopenharmony_ci * for normal operation of the host. 248c2ecf20Sopenharmony_ci * Martin Mares : Randomized timer with exponential backoff 258c2ecf20Sopenharmony_ci * installed to minimize network congestion. 268c2ecf20Sopenharmony_ci * Martin Mares : Code cleanup. 278c2ecf20Sopenharmony_ci * Martin Mares : (2.1) BOOTP and RARP made configuration options. 288c2ecf20Sopenharmony_ci * Martin Mares : Server hostname generation fixed. 298c2ecf20Sopenharmony_ci * Gerd Knorr : Fixed wired inode handling 308c2ecf20Sopenharmony_ci * Martin Mares : (2.2) "0.0.0.0" addresses from command line ignored. 318c2ecf20Sopenharmony_ci * Martin Mares : RARP replies not tested for server address. 328c2ecf20Sopenharmony_ci * Gero Kuhlmann : (2.3) Some bug fixes and code cleanup again (please 338c2ecf20Sopenharmony_ci * send me your new patches _before_ bothering 348c2ecf20Sopenharmony_ci * Linus so that I don' always have to cleanup 358c2ecf20Sopenharmony_ci * _afterwards_ - thanks) 368c2ecf20Sopenharmony_ci * Gero Kuhlmann : Last changes of Martin Mares undone. 378c2ecf20Sopenharmony_ci * Gero Kuhlmann : RARP replies are tested for specified server 388c2ecf20Sopenharmony_ci * again. However, it's now possible to have 398c2ecf20Sopenharmony_ci * different RARP and NFS servers. 408c2ecf20Sopenharmony_ci * Gero Kuhlmann : "0.0.0.0" addresses from command line are 418c2ecf20Sopenharmony_ci * now mapped to INADDR_NONE. 428c2ecf20Sopenharmony_ci * Gero Kuhlmann : Fixed a bug which prevented BOOTP path name 438c2ecf20Sopenharmony_ci * from being used (thanks to Leo Spiekman) 448c2ecf20Sopenharmony_ci * Andy Walker : Allow to specify the NFS server in nfs_root 458c2ecf20Sopenharmony_ci * without giving a path name 468c2ecf20Sopenharmony_ci * Swen Thümmler : Allow to specify the NFS options in nfs_root 478c2ecf20Sopenharmony_ci * without giving a path name. Fix BOOTP request 488c2ecf20Sopenharmony_ci * for domainname (domainname is NIS domain, not 498c2ecf20Sopenharmony_ci * DNS domain!). Skip dummy devices for BOOTP. 508c2ecf20Sopenharmony_ci * Jacek Zapala : Fixed a bug which prevented server-ip address 518c2ecf20Sopenharmony_ci * from nfsroot parameter from being used. 528c2ecf20Sopenharmony_ci * Olaf Kirch : Adapted to new NFS code. 538c2ecf20Sopenharmony_ci * Jakub Jelinek : Free used code segment. 548c2ecf20Sopenharmony_ci * Marko Kohtala : Fixed some bugs. 558c2ecf20Sopenharmony_ci * Martin Mares : Debug message cleanup 568c2ecf20Sopenharmony_ci * Martin Mares : Changed to use the new generic IP layer autoconfig 578c2ecf20Sopenharmony_ci * code. BOOTP and RARP moved there. 588c2ecf20Sopenharmony_ci * Martin Mares : Default path now contains host name instead of 598c2ecf20Sopenharmony_ci * host IP address (but host name defaults to IP 608c2ecf20Sopenharmony_ci * address anyway). 618c2ecf20Sopenharmony_ci * Martin Mares : Use root_server_addr appropriately during setup. 628c2ecf20Sopenharmony_ci * Martin Mares : Rewrote parameter parsing, now hopefully giving 638c2ecf20Sopenharmony_ci * correct overriding. 648c2ecf20Sopenharmony_ci * Trond Myklebust : Add in preliminary support for NFSv3 and TCP. 658c2ecf20Sopenharmony_ci * Fix bug in root_nfs_addr(). nfs_data.namlen 668c2ecf20Sopenharmony_ci * is NOT for the length of the hostname. 678c2ecf20Sopenharmony_ci * Hua Qin : Support for mounting root file system via 688c2ecf20Sopenharmony_ci * NFS over TCP. 698c2ecf20Sopenharmony_ci * Fabian Frederick: Option parser rebuilt (using parser lib) 708c2ecf20Sopenharmony_ci * Chuck Lever : Use super.c's text-based mount option parsing 718c2ecf20Sopenharmony_ci * Chuck Lever : Add "nfsrootdebug". 728c2ecf20Sopenharmony_ci */ 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci#include <linux/types.h> 758c2ecf20Sopenharmony_ci#include <linux/string.h> 768c2ecf20Sopenharmony_ci#include <linux/init.h> 778c2ecf20Sopenharmony_ci#include <linux/nfs.h> 788c2ecf20Sopenharmony_ci#include <linux/nfs_fs.h> 798c2ecf20Sopenharmony_ci#include <linux/utsname.h> 808c2ecf20Sopenharmony_ci#include <linux/root_dev.h> 818c2ecf20Sopenharmony_ci#include <net/ipconfig.h> 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci#include "internal.h" 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci#define NFSDBG_FACILITY NFSDBG_ROOT 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci/* Default path we try to mount. "%s" gets replaced by our IP address */ 888c2ecf20Sopenharmony_ci#define NFS_ROOT "/tftpboot/%s" 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci/* Default NFSROOT mount options. */ 918c2ecf20Sopenharmony_ci#if defined(CONFIG_NFS_V2) 928c2ecf20Sopenharmony_ci#define NFS_DEF_OPTIONS "vers=2,tcp,rsize=4096,wsize=4096" 938c2ecf20Sopenharmony_ci#elif defined(CONFIG_NFS_V3) 948c2ecf20Sopenharmony_ci#define NFS_DEF_OPTIONS "vers=3,tcp,rsize=4096,wsize=4096" 958c2ecf20Sopenharmony_ci#else 968c2ecf20Sopenharmony_ci#define NFS_DEF_OPTIONS "vers=4,tcp,rsize=4096,wsize=4096" 978c2ecf20Sopenharmony_ci#endif 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci/* Parameters passed from the kernel command line */ 1008c2ecf20Sopenharmony_cistatic char nfs_root_parms[NFS_MAXPATHLEN + 1] __initdata = ""; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci/* Text-based mount options passed to super.c */ 1038c2ecf20Sopenharmony_cistatic char nfs_root_options[256] __initdata = NFS_DEF_OPTIONS; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci/* Address of NFS server */ 1068c2ecf20Sopenharmony_cistatic __be32 servaddr __initdata = htonl(INADDR_NONE); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci/* Name of directory to mount */ 1098c2ecf20Sopenharmony_cistatic char nfs_export_path[NFS_MAXPATHLEN + 1] __initdata = ""; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci/* server:export path string passed to super.c */ 1128c2ecf20Sopenharmony_cistatic char nfs_root_device[NFS_MAXPATHLEN + 1] __initdata = ""; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci#ifdef NFS_DEBUG 1158c2ecf20Sopenharmony_ci/* 1168c2ecf20Sopenharmony_ci * When the "nfsrootdebug" kernel command line option is specified, 1178c2ecf20Sopenharmony_ci * enable debugging messages for NFSROOT. 1188c2ecf20Sopenharmony_ci */ 1198c2ecf20Sopenharmony_cistatic int __init nfs_root_debug(char *__unused) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci nfs_debug |= NFSDBG_ROOT | NFSDBG_MOUNT; 1228c2ecf20Sopenharmony_ci return 1; 1238c2ecf20Sopenharmony_ci} 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci__setup("nfsrootdebug", nfs_root_debug); 1268c2ecf20Sopenharmony_ci#endif 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci/* 1298c2ecf20Sopenharmony_ci * Parse NFS server and directory information passed on the kernel 1308c2ecf20Sopenharmony_ci * command line. 1318c2ecf20Sopenharmony_ci * 1328c2ecf20Sopenharmony_ci * nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] 1338c2ecf20Sopenharmony_ci * 1348c2ecf20Sopenharmony_ci * If there is a "%s" token in the <root-dir> string, it is replaced 1358c2ecf20Sopenharmony_ci * by the ASCII-representation of the client's IP address. 1368c2ecf20Sopenharmony_ci */ 1378c2ecf20Sopenharmony_cistatic int __init nfs_root_setup(char *line) 1388c2ecf20Sopenharmony_ci{ 1398c2ecf20Sopenharmony_ci ROOT_DEV = Root_NFS; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci if (line[0] == '/' || line[0] == ',' || (line[0] >= '0' && line[0] <= '9')) { 1428c2ecf20Sopenharmony_ci strlcpy(nfs_root_parms, line, sizeof(nfs_root_parms)); 1438c2ecf20Sopenharmony_ci } else { 1448c2ecf20Sopenharmony_ci size_t n = strlen(line) + sizeof(NFS_ROOT) - 1; 1458c2ecf20Sopenharmony_ci if (n >= sizeof(nfs_root_parms)) 1468c2ecf20Sopenharmony_ci line[sizeof(nfs_root_parms) - sizeof(NFS_ROOT) - 2] = '\0'; 1478c2ecf20Sopenharmony_ci sprintf(nfs_root_parms, NFS_ROOT, line); 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci /* 1518c2ecf20Sopenharmony_ci * Extract the IP address of the NFS server containing our 1528c2ecf20Sopenharmony_ci * root file system, if one was specified. 1538c2ecf20Sopenharmony_ci * 1548c2ecf20Sopenharmony_ci * Note: root_nfs_parse_addr() removes the server-ip from 1558c2ecf20Sopenharmony_ci * nfs_root_parms, if it exists. 1568c2ecf20Sopenharmony_ci */ 1578c2ecf20Sopenharmony_ci root_server_addr = root_nfs_parse_addr(nfs_root_parms); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci return 1; 1608c2ecf20Sopenharmony_ci} 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci__setup("nfsroot=", nfs_root_setup); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic int __init root_nfs_copy(char *dest, const char *src, 1658c2ecf20Sopenharmony_ci const size_t destlen) 1668c2ecf20Sopenharmony_ci{ 1678c2ecf20Sopenharmony_ci if (strlcpy(dest, src, destlen) > destlen) 1688c2ecf20Sopenharmony_ci return -1; 1698c2ecf20Sopenharmony_ci return 0; 1708c2ecf20Sopenharmony_ci} 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic int __init root_nfs_cat(char *dest, const char *src, 1738c2ecf20Sopenharmony_ci const size_t destlen) 1748c2ecf20Sopenharmony_ci{ 1758c2ecf20Sopenharmony_ci size_t len = strlen(dest); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci if (len && dest[len - 1] != ',') 1788c2ecf20Sopenharmony_ci if (strlcat(dest, ",", destlen) > destlen) 1798c2ecf20Sopenharmony_ci return -1; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci if (strlcat(dest, src, destlen) > destlen) 1828c2ecf20Sopenharmony_ci return -1; 1838c2ecf20Sopenharmony_ci return 0; 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci/* 1878c2ecf20Sopenharmony_ci * Parse out root export path and mount options from 1888c2ecf20Sopenharmony_ci * passed-in string @incoming. 1898c2ecf20Sopenharmony_ci * 1908c2ecf20Sopenharmony_ci * Copy the export path into @exppath. 1918c2ecf20Sopenharmony_ci */ 1928c2ecf20Sopenharmony_cistatic int __init root_nfs_parse_options(char *incoming, char *exppath, 1938c2ecf20Sopenharmony_ci const size_t exppathlen) 1948c2ecf20Sopenharmony_ci{ 1958c2ecf20Sopenharmony_ci char *p; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci /* 1988c2ecf20Sopenharmony_ci * Set the NFS remote path 1998c2ecf20Sopenharmony_ci */ 2008c2ecf20Sopenharmony_ci p = strsep(&incoming, ","); 2018c2ecf20Sopenharmony_ci if (*p != '\0' && strcmp(p, "default") != 0) 2028c2ecf20Sopenharmony_ci if (root_nfs_copy(exppath, p, exppathlen)) 2038c2ecf20Sopenharmony_ci return -1; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci /* 2068c2ecf20Sopenharmony_ci * @incoming now points to the rest of the string; if it 2078c2ecf20Sopenharmony_ci * contains something, append it to our root options buffer 2088c2ecf20Sopenharmony_ci */ 2098c2ecf20Sopenharmony_ci if (incoming != NULL && *incoming != '\0') 2108c2ecf20Sopenharmony_ci if (root_nfs_cat(nfs_root_options, incoming, 2118c2ecf20Sopenharmony_ci sizeof(nfs_root_options))) 2128c2ecf20Sopenharmony_ci return -1; 2138c2ecf20Sopenharmony_ci return 0; 2148c2ecf20Sopenharmony_ci} 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci/* 2178c2ecf20Sopenharmony_ci * Decode the export directory path name and NFS options from 2188c2ecf20Sopenharmony_ci * the kernel command line. This has to be done late in order to 2198c2ecf20Sopenharmony_ci * use a dynamically acquired client IP address for the remote 2208c2ecf20Sopenharmony_ci * root directory path. 2218c2ecf20Sopenharmony_ci * 2228c2ecf20Sopenharmony_ci * Returns zero if successful; otherwise -1 is returned. 2238c2ecf20Sopenharmony_ci */ 2248c2ecf20Sopenharmony_cistatic int __init root_nfs_data(char *cmdline) 2258c2ecf20Sopenharmony_ci{ 2268c2ecf20Sopenharmony_ci char mand_options[sizeof("nolock,addr=") + INET_ADDRSTRLEN + 1]; 2278c2ecf20Sopenharmony_ci int len, retval = -1; 2288c2ecf20Sopenharmony_ci char *tmp = NULL; 2298c2ecf20Sopenharmony_ci const size_t tmplen = sizeof(nfs_export_path); 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci tmp = kzalloc(tmplen, GFP_KERNEL); 2328c2ecf20Sopenharmony_ci if (tmp == NULL) 2338c2ecf20Sopenharmony_ci goto out_nomem; 2348c2ecf20Sopenharmony_ci strcpy(tmp, NFS_ROOT); 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci if (root_server_path[0] != '\0') { 2378c2ecf20Sopenharmony_ci dprintk("Root-NFS: DHCPv4 option 17: %s\n", 2388c2ecf20Sopenharmony_ci root_server_path); 2398c2ecf20Sopenharmony_ci if (root_nfs_parse_options(root_server_path, tmp, tmplen)) 2408c2ecf20Sopenharmony_ci goto out_optionstoolong; 2418c2ecf20Sopenharmony_ci } 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci if (cmdline[0] != '\0') { 2448c2ecf20Sopenharmony_ci dprintk("Root-NFS: nfsroot=%s\n", cmdline); 2458c2ecf20Sopenharmony_ci if (root_nfs_parse_options(cmdline, tmp, tmplen)) 2468c2ecf20Sopenharmony_ci goto out_optionstoolong; 2478c2ecf20Sopenharmony_ci } 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci /* 2508c2ecf20Sopenharmony_ci * Append mandatory options for nfsroot so they override 2518c2ecf20Sopenharmony_ci * what has come before 2528c2ecf20Sopenharmony_ci */ 2538c2ecf20Sopenharmony_ci snprintf(mand_options, sizeof(mand_options), "nolock,addr=%pI4", 2548c2ecf20Sopenharmony_ci &servaddr); 2558c2ecf20Sopenharmony_ci if (root_nfs_cat(nfs_root_options, mand_options, 2568c2ecf20Sopenharmony_ci sizeof(nfs_root_options))) 2578c2ecf20Sopenharmony_ci goto out_optionstoolong; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci /* 2608c2ecf20Sopenharmony_ci * Set up nfs_root_device. For NFS mounts, this looks like 2618c2ecf20Sopenharmony_ci * 2628c2ecf20Sopenharmony_ci * server:/path 2638c2ecf20Sopenharmony_ci * 2648c2ecf20Sopenharmony_ci * At this point, utsname()->nodename contains our local 2658c2ecf20Sopenharmony_ci * IP address or hostname, set by ipconfig. If "%s" exists 2668c2ecf20Sopenharmony_ci * in tmp, substitute the nodename, then shovel the whole 2678c2ecf20Sopenharmony_ci * mess into nfs_root_device. 2688c2ecf20Sopenharmony_ci */ 2698c2ecf20Sopenharmony_ci len = snprintf(nfs_export_path, sizeof(nfs_export_path), 2708c2ecf20Sopenharmony_ci tmp, utsname()->nodename); 2718c2ecf20Sopenharmony_ci if (len >= (int)sizeof(nfs_export_path)) 2728c2ecf20Sopenharmony_ci goto out_devnametoolong; 2738c2ecf20Sopenharmony_ci len = snprintf(nfs_root_device, sizeof(nfs_root_device), 2748c2ecf20Sopenharmony_ci "%pI4:%s", &servaddr, nfs_export_path); 2758c2ecf20Sopenharmony_ci if (len >= (int)sizeof(nfs_root_device)) 2768c2ecf20Sopenharmony_ci goto out_devnametoolong; 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci retval = 0; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ciout: 2818c2ecf20Sopenharmony_ci kfree(tmp); 2828c2ecf20Sopenharmony_ci return retval; 2838c2ecf20Sopenharmony_ciout_nomem: 2848c2ecf20Sopenharmony_ci printk(KERN_ERR "Root-NFS: could not allocate memory\n"); 2858c2ecf20Sopenharmony_ci goto out; 2868c2ecf20Sopenharmony_ciout_optionstoolong: 2878c2ecf20Sopenharmony_ci printk(KERN_ERR "Root-NFS: mount options string too long\n"); 2888c2ecf20Sopenharmony_ci goto out; 2898c2ecf20Sopenharmony_ciout_devnametoolong: 2908c2ecf20Sopenharmony_ci printk(KERN_ERR "Root-NFS: root device name too long.\n"); 2918c2ecf20Sopenharmony_ci goto out; 2928c2ecf20Sopenharmony_ci} 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci/** 2958c2ecf20Sopenharmony_ci * nfs_root_data - Return prepared 'data' for NFSROOT mount 2968c2ecf20Sopenharmony_ci * @root_device: OUT: address of string containing NFSROOT device 2978c2ecf20Sopenharmony_ci * @root_data: OUT: address of string containing NFSROOT mount options 2988c2ecf20Sopenharmony_ci * 2998c2ecf20Sopenharmony_ci * Returns zero and sets @root_device and @root_data if successful, 3008c2ecf20Sopenharmony_ci * otherwise -1 is returned. 3018c2ecf20Sopenharmony_ci */ 3028c2ecf20Sopenharmony_ciint __init nfs_root_data(char **root_device, char **root_data) 3038c2ecf20Sopenharmony_ci{ 3048c2ecf20Sopenharmony_ci servaddr = root_server_addr; 3058c2ecf20Sopenharmony_ci if (servaddr == htonl(INADDR_NONE)) { 3068c2ecf20Sopenharmony_ci printk(KERN_ERR "Root-NFS: no NFS server address\n"); 3078c2ecf20Sopenharmony_ci return -1; 3088c2ecf20Sopenharmony_ci } 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci if (root_nfs_data(nfs_root_parms) < 0) 3118c2ecf20Sopenharmony_ci return -1; 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_ci *root_device = nfs_root_device; 3148c2ecf20Sopenharmony_ci *root_data = nfs_root_options; 3158c2ecf20Sopenharmony_ci return 0; 3168c2ecf20Sopenharmony_ci} 317