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
56static void usage(void);
57
58static 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 */
69int
70main(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 for file %s", *argv);
299	exit(0);
300}
301
302static void
303usage(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