1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2017 Rick Macklem 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 * 27 */ 28 29 #include <sys/cdefs.h> 30 #include <err.h> 31 #include <errno.h> 32 #include <getopt.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <unistd.h> 37 #include <netdb.h> 38 #include <sys/param.h> 39 #include <sys/extattr.h> 40 #include <sys/mount.h> 41 #include <sys/socket.h> 42 #include <sys/stat.h> 43 #include <sys/types.h> 44 #include <sys/sysctl.h> 45 #include <arpa/inet.h> 46 #include <netinet/in.h> 47 #include <nfs/nfssvc.h> 48 49 #include <fs/nfs/nfsproto.h> 50 #include <fs/nfs/nfskpiport.h> 51 #include <fs/nfs/nfs.h> 52 #include <fs/nfs/nfsrvstate.h> 53 54 static void usage(void) __dead2; 55 56 static struct option longopts[] = { 57 { "migrate", required_argument, NULL, 'm' }, 58 { "mirror", required_argument, NULL, 'r' }, 59 { NULL, 0, NULL, 0 } 60 }; 61 62 /* 63 * This program creates a copy of the file's (first argument) data on the 64 * new/recovering DS mirror. If the file is already on the new/recovering 65 * DS, it will simply exit(0). 66 */ 67 int 68 main(int argc, char *argv[]) 69 { 70 struct nfsd_pnfsd_args pnfsdarg; 71 struct pnfsdsfile dsfile[NFSDEV_MAXMIRRORS]; 72 struct stat sb; 73 struct statfs sf; 74 struct addrinfo hints, *res, *nres; 75 struct sockaddr_in sin; 76 struct sockaddr_in6 sin6; 77 ssize_t xattrsize, xattrsize2; 78 size_t mirlen; 79 int ch, fnd, fndzero, i, migrateit, mirrorcnt, mirrorit, ret; 80 int mirrorlevel; 81 char host[MNAMELEN + NI_MAXHOST + 2], *cp; 82 83 if (geteuid() != 0) 84 errx(1, "Must be run as root/su"); 85 86 mirrorit = migrateit = 0; 87 pnfsdarg.dspath = pnfsdarg.curdspath = NULL; 88 while ((ch = getopt_long(argc, argv, "m:r:", longopts, NULL)) != -1) { 89 switch (ch) { 90 case 'm': 91 /* Migrate the file from the second DS to the first. */ 92 if (mirrorit != 0) 93 errx(1, "-r and -m are mutually exclusive"); 94 migrateit = 1; 95 pnfsdarg.curdspath = optarg; 96 break; 97 case 'r': 98 /* Mirror the file on the specified DS. */ 99 if (migrateit != 0) 100 errx(1, "-r and -m are mutually exclusive"); 101 mirrorit = 1; 102 pnfsdarg.dspath = optarg; 103 break; 104 default: 105 usage(); 106 } 107 } 108 argc -= optind; 109 argv += optind; 110 if (migrateit != 0) { 111 if (argc != 2) 112 usage(); 113 pnfsdarg.dspath = *argv++; 114 } else if (argc != 1) 115 usage(); 116 117 /* Get the pNFS service's mirror level. */ 118 mirlen = sizeof(mirrorlevel); 119 ret = sysctlbyname("vfs.nfs.pnfsmirror", &mirrorlevel, &mirlen, 120 NULL, 0); 121 if (ret < 0) 122 errx(1, "Can't get vfs.nfs.pnfsmirror"); 123 124 if (pnfsdarg.dspath != NULL && pnfsdarg.curdspath != NULL && 125 strcmp(pnfsdarg.dspath, pnfsdarg.curdspath) == 0) 126 errx(1, "Can't migrate to same server"); 127 128 /* 129 * The host address and directory where the data storage file is 130 * located is in the extended attribute "pnfsd.dsfile". 131 */ 132 xattrsize = extattr_get_file(*argv, EXTATTR_NAMESPACE_SYSTEM, 133 "pnfsd.dsfile", dsfile, sizeof(dsfile)); 134 mirrorcnt = xattrsize / sizeof(struct pnfsdsfile); 135 xattrsize2 = mirrorcnt * sizeof(struct pnfsdsfile); 136 if (mirrorcnt < 1 || xattrsize != xattrsize2) 137 errx(1, "Can't get extattr pnfsd.dsfile for %s", *argv); 138 139 /* See if there is a 0.0.0.0 entry. */ 140 fndzero = 0; 141 for (i = 0; i < mirrorcnt; i++) { 142 if (dsfile[i].dsf_sin.sin_family == AF_INET && 143 dsfile[i].dsf_sin.sin_addr.s_addr == 0) 144 fndzero = 1; 145 } 146 147 /* If already mirrored for default case, just exit(0); */ 148 if (mirrorit == 0 && migrateit == 0 && (mirrorlevel < 2 || 149 (fndzero == 0 && mirrorcnt >= mirrorlevel) || 150 (fndzero != 0 && mirrorcnt > mirrorlevel))) 151 exit(0); 152 153 /* For the "-r" case, there must be a 0.0.0.0 entry. */ 154 if (mirrorit != 0 && (fndzero == 0 || mirrorlevel < 2 || 155 mirrorcnt < 2 || mirrorcnt > mirrorlevel)) 156 exit(0); 157 158 /* For pnfsdarg.dspath set, if it is already in list, just exit(0); */ 159 if (pnfsdarg.dspath != NULL) { 160 /* Check the dspath to see that it's an NFS mount. */ 161 if (stat(pnfsdarg.dspath, &sb) < 0) 162 errx(1, "Can't stat %s", pnfsdarg.dspath); 163 if (!S_ISDIR(sb.st_mode)) 164 errx(1, "%s is not a directory", pnfsdarg.dspath); 165 if (statfs(pnfsdarg.dspath, &sf) < 0) 166 errx(1, "Can't fsstat %s", pnfsdarg.dspath); 167 if (strcmp(sf.f_fstypename, "nfs") != 0) 168 errx(1, "%s is not an NFS mount", pnfsdarg.dspath); 169 if (strcmp(sf.f_mntonname, pnfsdarg.dspath) != 0) 170 errx(1, "%s is not the mounted-on dir for the new DS", 171 pnfsdarg.dspath); 172 173 /* 174 * Check the IP address of the NFS server against the entry(ies) 175 * in the extended attribute. 176 */ 177 strlcpy(host, sf.f_mntfromname, sizeof(host)); 178 cp = strchr(host, ':'); 179 if (cp == NULL) 180 errx(1, "No <host>: in mount %s", host); 181 *cp = '\0'; 182 memset(&hints, 0, sizeof(hints)); 183 hints.ai_family = PF_UNSPEC; 184 hints.ai_socktype = SOCK_STREAM; 185 if (getaddrinfo(host, NULL, &hints, &res) != 0) 186 errx(1, "Can't get address for %s", host); 187 for (i = 0; i < mirrorcnt; i++) { 188 nres = res; 189 while (nres != NULL) { 190 if (dsfile[i].dsf_sin.sin_family == 191 nres->ai_family) { 192 /* 193 * If there is already an entry for this 194 * DS, just exit(0), since copying isn't 195 * required. 196 */ 197 if (nres->ai_family == AF_INET && 198 nres->ai_addrlen >= sizeof(sin)) { 199 memcpy(&sin, nres->ai_addr, 200 sizeof(sin)); 201 if (sin.sin_addr.s_addr == 202 dsfile[i].dsf_sin.sin_addr.s_addr) 203 exit(0); 204 } else if (nres->ai_family == 205 AF_INET6 && nres->ai_addrlen >= 206 sizeof(sin6)) { 207 memcpy(&sin6, nres->ai_addr, 208 sizeof(sin6)); 209 if (IN6_ARE_ADDR_EQUAL(&sin6.sin6_addr, 210 &dsfile[i].dsf_sin6.sin6_addr)) 211 exit(0); 212 } 213 } 214 nres = nres->ai_next; 215 } 216 } 217 freeaddrinfo(res); 218 } 219 220 /* For "-m", the pnfsdarg.curdspath must be in the list. */ 221 if (pnfsdarg.curdspath != NULL) { 222 /* Check pnfsdarg.curdspath to see that it's an NFS mount. */ 223 if (stat(pnfsdarg.curdspath, &sb) < 0) 224 errx(1, "Can't stat %s", pnfsdarg.curdspath); 225 if (!S_ISDIR(sb.st_mode)) 226 errx(1, "%s is not a directory", pnfsdarg.curdspath); 227 if (statfs(pnfsdarg.curdspath, &sf) < 0) 228 errx(1, "Can't fsstat %s", pnfsdarg.curdspath); 229 if (strcmp(sf.f_fstypename, "nfs") != 0) 230 errx(1, "%s is not an NFS mount", pnfsdarg.curdspath); 231 if (strcmp(sf.f_mntonname, pnfsdarg.curdspath) != 0) 232 errx(1, "%s is not the mounted-on dir of the cur DS", 233 pnfsdarg.curdspath); 234 235 /* 236 * Check the IP address of the NFS server against the entry(ies) 237 * in the extended attribute. 238 */ 239 strlcpy(host, sf.f_mntfromname, sizeof(host)); 240 cp = strchr(host, ':'); 241 if (cp == NULL) 242 errx(1, "No <host>: in mount %s", host); 243 *cp = '\0'; 244 memset(&hints, 0, sizeof(hints)); 245 hints.ai_family = PF_UNSPEC; 246 hints.ai_socktype = SOCK_STREAM; 247 if (getaddrinfo(host, NULL, &hints, &res) != 0) 248 errx(1, "Can't get address for %s", host); 249 fnd = 0; 250 for (i = 0; i < mirrorcnt && fnd == 0; i++) { 251 nres = res; 252 while (nres != NULL) { 253 if (dsfile[i].dsf_sin.sin_family == 254 nres->ai_family) { 255 /* 256 * Note if the entry is found. 257 */ 258 if (nres->ai_family == AF_INET && 259 nres->ai_addrlen >= sizeof(sin)) { 260 memcpy(&sin, nres->ai_addr, 261 sizeof(sin)); 262 if (sin.sin_addr.s_addr == 263 dsfile[i].dsf_sin.sin_addr.s_addr) { 264 fnd = 1; 265 break; 266 } 267 } else if (nres->ai_family == 268 AF_INET6 && nres->ai_addrlen >= 269 sizeof(sin6)) { 270 memcpy(&sin6, nres->ai_addr, 271 sizeof(sin6)); 272 if (IN6_ARE_ADDR_EQUAL(&sin6.sin6_addr, 273 &dsfile[i].dsf_sin6.sin6_addr)) { 274 fnd = 1; 275 break; 276 } 277 } 278 } 279 nres = nres->ai_next; 280 } 281 } 282 freeaddrinfo(res); 283 /* 284 * If not found just exit(0), since it is not on the 285 * source DS. 286 */ 287 if (fnd == 0) 288 exit(0); 289 } 290 291 /* Do the copy via the nfssvc() syscall. */ 292 pnfsdarg.op = PNFSDOP_COPYMR; 293 pnfsdarg.mdspath = *argv; 294 ret = nfssvc(NFSSVC_PNFSDS, &pnfsdarg); 295 if (ret < 0 && errno != EEXIST) 296 err(1, "Copymr failed for file %s", *argv); 297 exit(0); 298 } 299 300 static void 301 usage(void) 302 { 303 304 fprintf(stderr, "pnfsdscopymr [-r recovered-DS-mounted-on-path] " 305 "[-m soure-DS-mounted-on-path destination-DS-mounted-on-path] " 306 "mds-filename"); 307 exit(1); 308 } 309 310