1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 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 __FBSDID("$FreeBSD$"); 31 32 #include <err.h> 33 #include <errno.h> 34 #include <getopt.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <unistd.h> 39 #include <netdb.h> 40 #include <sys/param.h> 41 #include <sys/extattr.h> 42 #include <sys/mount.h> 43 #include <sys/socket.h> 44 #include <sys/stat.h> 45 #include <sys/types.h> 46 #include <sys/sysctl.h> 47 #include <arpa/inet.h> 48 #include <netinet/in.h> 49 #include <nfs/nfssvc.h> 50 51 #include <fs/nfs/nfsproto.h> 52 #include <fs/nfs/nfskpiport.h> 53 #include <fs/nfs/nfs.h> 54 #include <fs/nfs/nfsrvstate.h> 55 56 static void usage(void); 57 58 static struct option longopts[] = { 59 { "migrate", required_argument, NULL, 'm' }, 60 { "mirror", required_argument, NULL, 'r' }, 61 { NULL, 0, NULL, 0 } 62 }; 63 64 /* 65 * This program creates a copy of the file's (first argument) data on the 66 * new/recovering DS mirror. If the file is already on the new/recovering 67 * DS, it will simply exit(0). 68 */ 69 int 70 main(int argc, char *argv[]) 71 { 72 struct nfsd_pnfsd_args pnfsdarg; 73 struct pnfsdsfile dsfile[NFSDEV_MAXMIRRORS]; 74 struct stat sb; 75 struct statfs sf; 76 struct addrinfo hints, *res, *nres; 77 struct sockaddr_in sin; 78 struct sockaddr_in6 sin6; 79 ssize_t xattrsize, xattrsize2; 80 size_t mirlen; 81 int ch, fnd, fndzero, i, migrateit, mirrorcnt, mirrorit, ret; 82 int mirrorlevel; 83 char host[MNAMELEN + NI_MAXHOST + 2], *cp; 84 85 if (geteuid() != 0) 86 errx(1, "Must be run as root/su"); 87 88 mirrorit = migrateit = 0; 89 pnfsdarg.dspath = pnfsdarg.curdspath = NULL; 90 while ((ch = getopt_long(argc, argv, "m:r:", longopts, NULL)) != -1) { 91 switch (ch) { 92 case 'm': 93 /* Migrate the file from the second DS to the first. */ 94 if (mirrorit != 0) 95 errx(1, "-r and -m are mutually exclusive"); 96 migrateit = 1; 97 pnfsdarg.curdspath = optarg; 98 break; 99 case 'r': 100 /* Mirror the file on the specified DS. */ 101 if (migrateit != 0) 102 errx(1, "-r and -m are mutually exclusive"); 103 mirrorit = 1; 104 pnfsdarg.dspath = optarg; 105 break; 106 default: 107 usage(); 108 } 109 } 110 argc -= optind; 111 argv += optind; 112 if (migrateit != 0) { 113 if (argc != 2) 114 usage(); 115 pnfsdarg.dspath = *argv++; 116 } else if (argc != 1) 117 usage(); 118 119 /* Get the pNFS service's mirror level. */ 120 mirlen = sizeof(mirrorlevel); 121 ret = sysctlbyname("vfs.nfs.pnfsmirror", &mirrorlevel, &mirlen, 122 NULL, 0); 123 if (ret < 0) 124 errx(1, "Can't get vfs.nfs.pnfsmirror"); 125 126 if (pnfsdarg.dspath != NULL && pnfsdarg.curdspath != NULL && 127 strcmp(pnfsdarg.dspath, pnfsdarg.curdspath) == 0) 128 errx(1, "Can't migrate to same server"); 129 130 /* 131 * The host address and directory where the data storage file is 132 * located is in the extended attribute "pnfsd.dsfile". 133 */ 134 xattrsize = extattr_get_file(*argv, EXTATTR_NAMESPACE_SYSTEM, 135 "pnfsd.dsfile", dsfile, sizeof(dsfile)); 136 mirrorcnt = xattrsize / sizeof(struct pnfsdsfile); 137 xattrsize2 = mirrorcnt * sizeof(struct pnfsdsfile); 138 if (mirrorcnt < 1 || xattrsize != xattrsize2) 139 errx(1, "Can't get extattr pnfsd.dsfile for %s", *argv); 140 141 /* See if there is a 0.0.0.0 entry. */ 142 fndzero = 0; 143 for (i = 0; i < mirrorcnt; i++) { 144 if (dsfile[i].dsf_sin.sin_family == AF_INET && 145 dsfile[i].dsf_sin.sin_addr.s_addr == 0) 146 fndzero = 1; 147 } 148 149 /* If already mirrored for default case, just exit(0); */ 150 if (mirrorit == 0 && migrateit == 0 && (mirrorlevel < 2 || 151 (fndzero == 0 && mirrorcnt >= mirrorlevel) || 152 (fndzero != 0 && mirrorcnt > mirrorlevel))) 153 exit(0); 154 155 /* For the "-r" case, there must be a 0.0.0.0 entry. */ 156 if (mirrorit != 0 && (fndzero == 0 || mirrorlevel < 2 || 157 mirrorcnt < 2 || mirrorcnt > mirrorlevel)) 158 exit(0); 159 160 /* For pnfsdarg.dspath set, if it is already in list, just exit(0); */ 161 if (pnfsdarg.dspath != NULL) { 162 /* Check the dspath to see that it's an NFS mount. */ 163 if (stat(pnfsdarg.dspath, &sb) < 0) 164 errx(1, "Can't stat %s", pnfsdarg.dspath); 165 if (!S_ISDIR(sb.st_mode)) 166 errx(1, "%s is not a directory", pnfsdarg.dspath); 167 if (statfs(pnfsdarg.dspath, &sf) < 0) 168 errx(1, "Can't fsstat %s", pnfsdarg.dspath); 169 if (strcmp(sf.f_fstypename, "nfs") != 0) 170 errx(1, "%s is not an NFS mount", pnfsdarg.dspath); 171 if (strcmp(sf.f_mntonname, pnfsdarg.dspath) != 0) 172 errx(1, "%s is not the mounted-on dir for the new DS", 173 pnfsdarg.dspath); 174 175 /* 176 * Check the IP address of the NFS server against the entrie(s) 177 * in the extended attribute. 178 */ 179 strlcpy(host, sf.f_mntfromname, sizeof(host)); 180 cp = strchr(host, ':'); 181 if (cp == NULL) 182 errx(1, "No <host>: in mount %s", host); 183 *cp = '\0'; 184 memset(&hints, 0, sizeof(hints)); 185 hints.ai_family = PF_UNSPEC; 186 hints.ai_socktype = SOCK_STREAM; 187 if (getaddrinfo(host, NULL, &hints, &res) != 0) 188 errx(1, "Can't get address for %s", host); 189 for (i = 0; i < mirrorcnt; i++) { 190 nres = res; 191 while (nres != NULL) { 192 if (dsfile[i].dsf_sin.sin_family == 193 nres->ai_family) { 194 /* 195 * If there is already an entry for this 196 * DS, just exit(0), since copying isn't 197 * required. 198 */ 199 if (nres->ai_family == AF_INET && 200 nres->ai_addrlen >= sizeof(sin)) { 201 memcpy(&sin, nres->ai_addr, 202 sizeof(sin)); 203 if (sin.sin_addr.s_addr == 204 dsfile[i].dsf_sin.sin_addr.s_addr) 205 exit(0); 206 } else if (nres->ai_family == 207 AF_INET6 && nres->ai_addrlen >= 208 sizeof(sin6)) { 209 memcpy(&sin6, nres->ai_addr, 210 sizeof(sin6)); 211 if (IN6_ARE_ADDR_EQUAL(&sin6.sin6_addr, 212 &dsfile[i].dsf_sin6.sin6_addr)) 213 exit(0); 214 } 215 } 216 nres = nres->ai_next; 217 } 218 } 219 freeaddrinfo(res); 220 } 221 222 /* For "-m", the pnfsdarg.curdspath must be in the list. */ 223 if (pnfsdarg.curdspath != NULL) { 224 /* Check pnfsdarg.curdspath to see that it's an NFS mount. */ 225 if (stat(pnfsdarg.curdspath, &sb) < 0) 226 errx(1, "Can't stat %s", pnfsdarg.curdspath); 227 if (!S_ISDIR(sb.st_mode)) 228 errx(1, "%s is not a directory", pnfsdarg.curdspath); 229 if (statfs(pnfsdarg.curdspath, &sf) < 0) 230 errx(1, "Can't fsstat %s", pnfsdarg.curdspath); 231 if (strcmp(sf.f_fstypename, "nfs") != 0) 232 errx(1, "%s is not an NFS mount", pnfsdarg.curdspath); 233 if (strcmp(sf.f_mntonname, pnfsdarg.curdspath) != 0) 234 errx(1, "%s is not the mounted-on dir of the cur DS", 235 pnfsdarg.curdspath); 236 237 /* 238 * Check the IP address of the NFS server against the entrie(s) 239 * in the extended attribute. 240 */ 241 strlcpy(host, sf.f_mntfromname, sizeof(host)); 242 cp = strchr(host, ':'); 243 if (cp == NULL) 244 errx(1, "No <host>: in mount %s", host); 245 *cp = '\0'; 246 memset(&hints, 0, sizeof(hints)); 247 hints.ai_family = PF_UNSPEC; 248 hints.ai_socktype = SOCK_STREAM; 249 if (getaddrinfo(host, NULL, &hints, &res) != 0) 250 errx(1, "Can't get address for %s", host); 251 fnd = 0; 252 for (i = 0; i < mirrorcnt && fnd == 0; i++) { 253 nres = res; 254 while (nres != NULL) { 255 if (dsfile[i].dsf_sin.sin_family == 256 nres->ai_family) { 257 /* 258 * Note if the entry is found. 259 */ 260 if (nres->ai_family == AF_INET && 261 nres->ai_addrlen >= sizeof(sin)) { 262 memcpy(&sin, nres->ai_addr, 263 sizeof(sin)); 264 if (sin.sin_addr.s_addr == 265 dsfile[i].dsf_sin.sin_addr.s_addr) { 266 fnd = 1; 267 break; 268 } 269 } else if (nres->ai_family == 270 AF_INET6 && nres->ai_addrlen >= 271 sizeof(sin6)) { 272 memcpy(&sin6, nres->ai_addr, 273 sizeof(sin6)); 274 if (IN6_ARE_ADDR_EQUAL(&sin6.sin6_addr, 275 &dsfile[i].dsf_sin6.sin6_addr)) { 276 fnd = 1; 277 break; 278 } 279 } 280 } 281 nres = nres->ai_next; 282 } 283 } 284 freeaddrinfo(res); 285 /* 286 * If not found just exit(0), since it is not on the 287 * source DS. 288 */ 289 if (fnd == 0) 290 exit(0); 291 } 292 293 /* Do the copy via the nfssvc() syscall. */ 294 pnfsdarg.op = PNFSDOP_COPYMR; 295 pnfsdarg.mdspath = *argv; 296 ret = nfssvc(NFSSVC_PNFSDS, &pnfsdarg); 297 if (ret < 0 && errno != EEXIST) 298 err(1, "Copymr failed args %s, %s", argv[1], argv[2]); 299 exit(0); 300 } 301 302 static void 303 usage(void) 304 { 305 306 fprintf(stderr, "pnfsdscopymr [-r recovered-DS-mounted-on-path] " 307 "[-m soure-DS-mounted-on-path destination-DS-mounted-on-path] " 308 "mds-filename"); 309 exit(1); 310 } 311 312