xref: /freebsd/usr.sbin/pnfsdscopymr/pnfsdscopymr.c (revision dc8725726d0205c10b01c06717cb265ab7d239e5)
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