xref: /freebsd/usr.sbin/pnfsdscopymr/pnfsdscopymr.c (revision 19261079b74319502c6ffa1249920079f0f69a72)
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 for file %s", *argv);
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