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