2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2017 Rick Macklem
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
40 #include <sys/param.h>
41 #include <sys/extattr.h>
42 #include <sys/mount.h>
43 #include <sys/socket.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>
51 #include <fs/nfs/nfsproto.h>
52 #include <fs/nfs/nfskpiport.h>
53 #include <fs/nfs/nfs.h>
54 #include <fs/nfs/nfsrvstate.h>
56 static void usage(void);
58 static struct option longopts[] = {
59 { "migrate", required_argument, NULL, 'm' },
60 { "mirror", required_argument, NULL, 'r' },
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).
70 main(int argc, char *argv[])
72 struct nfsd_pnfsd_args pnfsdarg;
73 struct pnfsdsfile dsfile[NFSDEV_MAXMIRRORS];
76 struct addrinfo hints, *res, *nres;
77 struct sockaddr_in sin;
78 struct sockaddr_in6 sin6;
79 ssize_t xattrsize, xattrsize2;
81 int ch, fnd, fndzero, i, migrateit, mirrorcnt, mirrorit, ret;
83 char host[MNAMELEN + NI_MAXHOST + 2], *cp;
86 errx(1, "Must be run as root/su");
88 mirrorit = migrateit = 0;
89 pnfsdarg.dspath = pnfsdarg.curdspath = NULL;
90 while ((ch = getopt_long(argc, argv, "m:r:", longopts, NULL)) != -1) {
93 /* Migrate the file from the second DS to the first. */
95 errx(1, "-r and -m are mutually exclusive");
97 pnfsdarg.curdspath = optarg;
100 /* Mirror the file on the specified DS. */
102 errx(1, "-r and -m are mutually exclusive");
104 pnfsdarg.dspath = optarg;
112 if (migrateit != 0) {
115 pnfsdarg.dspath = *argv++;
116 } else if (argc != 1)
119 /* Get the pNFS service's mirror level. */
120 mirlen = sizeof(mirrorlevel);
121 ret = sysctlbyname("vfs.nfs.pnfsmirror", &mirrorlevel, &mirlen,
124 errx(1, "Can't get vfs.nfs.pnfsmirror");
126 if (pnfsdarg.dspath != NULL && pnfsdarg.curdspath != NULL &&
127 strcmp(pnfsdarg.dspath, pnfsdarg.curdspath) == 0)
128 errx(1, "Can't migrate to same server");
131 * The host address and directory where the data storage file is
132 * located is in the extended attribute "pnfsd.dsfile".
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);
141 /* See if there is a 0.0.0.0 entry. */
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)
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)))
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))
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",
176 * Check the IP address of the NFS server against the entrie(s)
177 * in the extended attribute.
179 strlcpy(host, sf.f_mntfromname, sizeof(host));
180 cp = strchr(host, ':');
182 errx(1, "No <host>: in mount %s", host);
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++) {
191 while (nres != NULL) {
192 if (dsfile[i].dsf_sin.sin_family ==
195 * If there is already an entry for this
196 * DS, just exit(0), since copying isn't
199 if (nres->ai_family == AF_INET &&
200 nres->ai_addrlen >= sizeof(sin)) {
201 memcpy(&sin, nres->ai_addr,
203 if (sin.sin_addr.s_addr ==
204 dsfile[i].dsf_sin.sin_addr.s_addr)
206 } else if (nres->ai_family ==
207 AF_INET6 && nres->ai_addrlen >=
209 memcpy(&sin6, nres->ai_addr,
211 if (IN6_ARE_ADDR_EQUAL(&sin6.sin6_addr,
212 &dsfile[i].dsf_sin6.sin6_addr))
216 nres = nres->ai_next;
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",
238 * Check the IP address of the NFS server against the entrie(s)
239 * in the extended attribute.
241 strlcpy(host, sf.f_mntfromname, sizeof(host));
242 cp = strchr(host, ':');
244 errx(1, "No <host>: in mount %s", host);
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);
252 for (i = 0; i < mirrorcnt && fnd == 0; i++) {
254 while (nres != NULL) {
255 if (dsfile[i].dsf_sin.sin_family ==
258 * Note if the entry is found.
260 if (nres->ai_family == AF_INET &&
261 nres->ai_addrlen >= sizeof(sin)) {
262 memcpy(&sin, nres->ai_addr,
264 if (sin.sin_addr.s_addr ==
265 dsfile[i].dsf_sin.sin_addr.s_addr) {
269 } else if (nres->ai_family ==
270 AF_INET6 && nres->ai_addrlen >=
272 memcpy(&sin6, nres->ai_addr,
274 if (IN6_ARE_ADDR_EQUAL(&sin6.sin6_addr,
275 &dsfile[i].dsf_sin6.sin6_addr)) {
281 nres = nres->ai_next;
286 * If not found just exit(0), since it is not on the
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 for file %s", *argv);
306 fprintf(stderr, "pnfsdscopymr [-r recovered-DS-mounted-on-path] "
307 "[-m soure-DS-mounted-on-path destination-DS-mounted-on-path] "