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