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 <getopt.h>
34#include <netdb.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <unistd.h>
39#include <sys/param.h>
40#include <sys/extattr.h>
41#include <sys/mount.h>
42#include <sys/socket.h>
43#include <netinet/in.h>
44#include <fs/nfs/nfskpiport.h>
45#include <fs/nfs/nfsproto.h>
46#include <fs/nfs/nfs.h>
47#include <fs/nfs/nfsrvstate.h>
48
49static void usage(void);
50
51static struct option longopts[] = {
52	{ "changeds",	required_argument,	NULL,	'c'	},
53	{ "mirror",	required_argument,	NULL,	'm'	},
54	{ "quiet",	no_argument,		NULL,	'q'	},
55	{ "zerods",	required_argument,	NULL,	'r'	},
56	{ "ds",		required_argument,	NULL,	's'	},
57	{ "zerofh",	no_argument,		NULL,	'z'	},
58	{ NULL,		0,			NULL,	0	}
59};
60
61/*
62 * This program displays the location information of a data storage file
63 * for a given file on a MetaData Server (MDS) in a pNFS service.  This program
64 * must be run on the MDS and the file argument must be a file in a local
65 * file system that has been exported for the pNFS service.
66 */
67int
68main(int argc, char *argv[])
69{
70	struct addrinfo *res, *ad, *newres;
71	struct sockaddr_in *sin, adsin;
72	struct sockaddr_in6 *sin6, adsin6;
73	char hostn[2 * NI_MAXHOST + 2], *cp;
74	struct pnfsdsfile dsfile[NFSDEV_MAXMIRRORS];
75	int ch, dosetxattr, i, mirrorcnt, mirrorit, quiet, zerods, zerofh;
76	in_port_t tport;
77	ssize_t xattrsize, xattrsize2;
78
79	zerods = 0;
80	zerofh = 0;
81	mirrorit = 0;
82	quiet = 0;
83	dosetxattr = 0;
84	res = NULL;
85	newres = NULL;
86	cp = NULL;
87	while ((ch = getopt_long(argc, argv, "c:m:qr:s:z", longopts, NULL)) !=
88	    -1) {
89		switch (ch) {
90		case 'c':
91			/* Replace the first DS server with the second one. */
92			if (zerofh != 0 || zerods != 0 || mirrorit != 0 ||
93			    newres != NULL || res != NULL)
94				errx(1, "-c, -m, -r, -s and -z are mutually "
95				    "exclusive and only can be used once");
96			strlcpy(hostn, optarg, 2 * NI_MAXHOST + 2);
97			cp = strchr(hostn, ',');
98			if (cp == NULL)
99				errx(1, "Bad -c argument %s", hostn);
100			*cp = '\0';
101			if (getaddrinfo(hostn, NULL, NULL, &res) != 0)
102				errx(1, "Can't get IP# for %s", hostn);
103			*cp++ = ',';
104			if (getaddrinfo(cp, NULL, NULL, &newres) != 0)
105				errx(1, "Can't get IP# for %s", cp);
106			break;
107		case 'm':
108			/* Add 0.0.0.0 entries up to mirror level. */
109			if (zerofh != 0 || zerods != 0 || mirrorit != 0 ||
110			    newres != NULL || res != NULL)
111				errx(1, "-c, -m, -r, -s and -z are mutually "
112				    "exclusive and only can be used once");
113			mirrorit = atoi(optarg);
114			if (mirrorit < 2 || mirrorit > NFSDEV_MAXMIRRORS)
115				errx(1, "-m %d out of range", mirrorit);
116			break;
117		case 'q':
118			quiet = 1;
119			break;
120		case 'r':
121			/* Reset the DS server in a mirror with 0.0.0.0. */
122			if (zerofh != 0 || zerods != 0 || mirrorit != 0 ||
123			    newres != NULL || res != NULL)
124				errx(1, "-c, -m, -r, -s and -z are mutually "
125				    "exclusive and only can be used once");
126			zerods = 1;
127			/* Translate the server name to an IP address. */
128			if (getaddrinfo(optarg, NULL, NULL, &res) != 0)
129				errx(1, "Can't get IP# for %s", optarg);
130			break;
131		case 's':
132			/* Translate the server name to an IP address. */
133			if (zerods != 0 || mirrorit != 0 || newres != NULL ||
134			    res != NULL)
135				errx(1, "-c, -m and -r are mutually exclusive "
136				    "from use with -s and -z");
137			if (getaddrinfo(optarg, NULL, NULL, &res) != 0)
138				errx(1, "Can't get IP# for %s", optarg);
139			break;
140		case 'z':
141			if (zerofh != 0 || zerods != 0 || mirrorit != 0 ||
142			    newres != NULL)
143				errx(1, "-c, -m and -r are mutually exclusive "
144				    "from use with -s and -z");
145			zerofh = 1;
146			break;
147		default:
148			usage();
149		}
150	}
151	argc -= optind;
152	if (argc != 1)
153		usage();
154	argv += optind;
155
156	/*
157	 * The host address and directory where the data storage file is
158	 * located is in the extended attribute "pnfsd.dsfile".
159	 */
160	xattrsize = extattr_get_file(*argv, EXTATTR_NAMESPACE_SYSTEM,
161	    "pnfsd.dsfile", dsfile, sizeof(dsfile));
162	mirrorcnt = xattrsize / sizeof(struct pnfsdsfile);
163	xattrsize2 = mirrorcnt * sizeof(struct pnfsdsfile);
164	if (mirrorcnt < 1 || xattrsize != xattrsize2)
165		err(1, "Can't get extattr pnfsd.dsfile for %s", *argv);
166
167	if (quiet == 0)
168		printf("%s:\t", *argv);
169	for (i = 0; i < mirrorcnt; i++) {
170		if (i > 0 && quiet == 0)
171			printf("\t");
172		/* Do the zerofh option. You must be root. */
173		if (zerofh != 0) {
174			if (geteuid() != 0)
175				errx(1, "Must be root/su to zerofh");
176
177			/*
178			 * Do it for the server specified by -s/--ds or all
179			 * servers, if -s/--ds was not specified.
180			 */
181			sin = &dsfile[i].dsf_sin;
182			sin6 = &dsfile[i].dsf_sin6;
183			ad = res;
184			while (ad != NULL) {
185				if (ad->ai_addr->sa_family == AF_INET &&
186				    sin->sin_family == AF_INET &&
187				    ad->ai_addrlen >= sizeof(adsin)) {
188					memcpy(&adsin, ad->ai_addr,
189					    sizeof(adsin));
190					if (sin->sin_addr.s_addr ==
191					    adsin.sin_addr.s_addr)
192						break;
193				}
194				if (ad->ai_addr->sa_family == AF_INET6 &&
195				    sin6->sin6_family == AF_INET6 &&
196				    ad->ai_addrlen >= sizeof(adsin6)) {
197					memcpy(&adsin6, ad->ai_addr,
198					    sizeof(adsin6));
199					if (IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr,
200					    &adsin6.sin6_addr))
201						break;
202				}
203				ad = ad->ai_next;
204			}
205			if (res == NULL || ad != NULL) {
206				memset(&dsfile[i].dsf_fh, 0, sizeof(fhandle_t));
207				dosetxattr = 1;
208			}
209		}
210
211		/* Do the zerods option. You must be root. */
212		if (zerods != 0 && mirrorcnt > 1) {
213			if (geteuid() != 0)
214				errx(1, "Must be root/su to zerods");
215
216			/*
217			 * Do it for the server specified.
218			 */
219			sin = &dsfile[i].dsf_sin;
220			sin6 = &dsfile[i].dsf_sin6;
221			ad = res;
222			while (ad != NULL) {
223				if (ad->ai_addr->sa_family == AF_INET &&
224				    sin->sin_family == AF_INET &&
225				    ad->ai_addrlen >= sizeof(adsin)) {
226					memcpy(&adsin, ad->ai_addr,
227					    sizeof(adsin));
228					if (sin->sin_addr.s_addr ==
229					    adsin.sin_addr.s_addr)
230						break;
231				}
232				if (ad->ai_addr->sa_family == AF_INET6 &&
233				    sin6->sin6_family == AF_INET6 &&
234				    ad->ai_addrlen >= sizeof(adsin6)) {
235					memcpy(&adsin6, ad->ai_addr,
236					    sizeof(adsin6));
237					if (IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr,
238					    &adsin6.sin6_addr))
239						break;
240				}
241				ad = ad->ai_next;
242			}
243			if (ad != NULL) {
244				sin->sin_family = AF_INET;
245				sin->sin_len = sizeof(*sin);
246				sin->sin_port = 0;
247				sin->sin_addr.s_addr = 0;
248				dosetxattr = 1;
249			}
250		}
251
252		/* Do the -c option to replace the DS host address. */
253		if (newres != NULL) {
254			if (geteuid() != 0)
255				errx(1, "Must be root/su to replace the host"
256				    " addr");
257
258			/*
259			 * Check that the old host address matches.
260			 */
261			sin = &dsfile[i].dsf_sin;
262			sin6 = &dsfile[i].dsf_sin6;
263			ad = res;
264			while (ad != NULL) {
265				if (ad->ai_addr->sa_family == AF_INET &&
266				    sin->sin_family == AF_INET &&
267				    ad->ai_addrlen >= sizeof(adsin)) {
268					memcpy(&adsin, ad->ai_addr,
269					    sizeof(adsin));
270					if (sin->sin_addr.s_addr ==
271					    adsin.sin_addr.s_addr)
272						break;
273				}
274				if (ad->ai_addr->sa_family == AF_INET6 &&
275				    sin6->sin6_family == AF_INET6 &&
276				    ad->ai_addrlen >= sizeof(adsin6)) {
277					memcpy(&adsin6, ad->ai_addr,
278					    sizeof(adsin6));
279					if (IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr,
280					    &adsin6.sin6_addr))
281						break;
282				}
283				ad = ad->ai_next;
284			}
285			if (ad != NULL) {
286				if (sin->sin_family == AF_INET)
287					tport = sin->sin_port;
288				else
289					tport = sin6->sin6_port;
290				/*
291				 * We have a match, so replace it with the first
292				 * AF_INET or AF_INET6 address in the newres
293				 * list.
294				 */
295				while (newres->ai_addr->sa_family != AF_INET &&
296				    newres->ai_addr->sa_family != AF_INET6) {
297					newres = newres->ai_next;
298					if (newres == NULL)
299						errx(1, "Hostname %s has no"
300						    " IP#", cp);
301				}
302				if (newres->ai_addr->sa_family == AF_INET) {
303					memcpy(sin, newres->ai_addr,
304					    sizeof(*sin));
305					sin->sin_port = tport;
306				} else if (newres->ai_addr->sa_family ==
307				    AF_INET6) {
308					memcpy(sin6, newres->ai_addr,
309					    sizeof(*sin6));
310					sin6->sin6_port = tport;
311				}
312				dosetxattr = 1;
313			}
314		}
315
316		if (quiet == 0) {
317			/* Translate the IP address to a hostname. */
318			if (getnameinfo((struct sockaddr *)&dsfile[i].dsf_sin,
319			    dsfile[i].dsf_sin.sin_len, hostn, sizeof(hostn),
320			    NULL, 0, 0) < 0)
321				err(1, "Can't get hostname");
322			printf("%s\tds%d/%s", hostn, dsfile[i].dsf_dir,
323			    dsfile[i].dsf_filename);
324		}
325	}
326	/* Add entrie(s) with IP address set to 0.0.0.0, as required. */
327	for (i = mirrorcnt; i < mirrorit; i++) {
328		dsfile[i] = dsfile[0];
329		dsfile[i].dsf_sin.sin_family = AF_INET;
330		dsfile[i].dsf_sin.sin_len = sizeof(struct sockaddr_in);
331		dsfile[i].dsf_sin.sin_addr.s_addr = 0;
332		dsfile[i].dsf_sin.sin_port = 0;
333		if (quiet == 0) {
334			/* Print out the 0.0.0.0 entry. */
335			printf("\t0.0.0.0\tds%d/%s", dsfile[i].dsf_dir,
336			    dsfile[i].dsf_filename);
337		}
338	}
339	if (mirrorit > mirrorcnt) {
340		xattrsize = mirrorit * sizeof(struct pnfsdsfile);
341		dosetxattr = 1;
342	}
343	if (quiet == 0)
344		printf("\n");
345
346	if (dosetxattr != 0 && extattr_set_file(*argv, EXTATTR_NAMESPACE_SYSTEM,
347	    "pnfsd.dsfile", dsfile, xattrsize) != xattrsize)
348		err(1, "Can't set pnfsd.dsfile");
349}
350
351static void
352usage(void)
353{
354
355	fprintf(stderr, "pnfsdsfile [-q/--quiet] [-z/--zerofh] "
356	    "[-c/--changeds <old dshostname> <new dshostname>] "
357	    "[-r/--zerods <dshostname>] "
358	    "[-s/--ds <dshostname>] "
359	    "<filename>\n");
360	exit(1);
361}
362
363