diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile index 8ae9077ee13..46cc80dc6a0 100644 --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -59,6 +59,7 @@ SUBDIR= adduser \ nologin \ pciconf \ periodic \ + pnfsdsfile \ pnfsdskill \ powerd \ prometheus_sysctl_exporter \ diff --git a/usr.sbin/pnfsdsfile/Makefile b/usr.sbin/pnfsdsfile/Makefile new file mode 100644 index 00000000000..83d82eb6ec6 --- /dev/null +++ b/usr.sbin/pnfsdsfile/Makefile @@ -0,0 +1,6 @@ +# $FreeBSD$ + +PROG= pnfsdsfile +MAN= pnfsdsfile.8 + +.include diff --git a/usr.sbin/pnfsdsfile/pnfsdsfile.8 b/usr.sbin/pnfsdsfile/pnfsdsfile.8 new file mode 100644 index 00000000000..e05ff4bd784 --- /dev/null +++ b/usr.sbin/pnfsdsfile/pnfsdsfile.8 @@ -0,0 +1,133 @@ +.\" Copyright (c) 2017 Rick Macklem +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd March 11, 2018 +.Dt PNFSDSFILE 8 +.Os +.Sh NAME +.Nm pnfsdsfile +.Nd display +a pNFS data storage file's location(s) and/or modify the +.Dq pnfsd.dsfile +extended attribute for them +.Sh SYNOPSIS +.Nm +.Op Fl qz +.Op Fl s Ar dshostname +.Op Fl c Ar old-dshostname,new-dshostname +.Op Fl r Ar dshostname +.Ar mdsfile +.Sh DESCRIPTION +The +.Nm +command displays the data storage file's location(s) for a pNFS service and/or +modifies the +.Dq pnfsd.dsfile +extended attribute on the +.Ar mdsfile . +A pNFS service maintains a data storage file for each regular file on +the MetaData Server (MDS) on one or more of the Data Servers (DS). +If mirroring is enabled, the data storage file will be on more that one of the DSs. +Unless command options are specified, this command displays the location(s) +of the data storage file for the MDS file +.Ar mdsfile . +It must be used on the MDS and the +.Ar mdsfile +must be a file on the exported local file system and not an NFSv4.1 mount. +This information is stored in the +.Dq pnfsd.dsfile +extended attribute for this +.Ar mdsfile . +The command line options allow the information in the +.Dq pnfsd.dsfile +extended attribute to be changed. +.Pp +The following options are available: +.Bl -tag -width Ds +.It Fl q +This option suppresses printing of the DS file's location(s). +.It Fl z +This option specifies that the file handle field of the pnfsd.dsfile +extended attribute is to filled with all zero bits. +This forces the pNFS MDS to do a Lookup RPC against the DS to acquire the file +handle to update it. +Normally this will only be necessary after the DS file has been recovered +from a backup, causing the file handle to change. +.It Fl s Ar dshostname +This option can be used with +.Fl z +so that the zeroing out of the file handle is only done if the DS server +is the one specified by this option. +.It Fl c Ar old-dshostname,new-dshostname +This option allows a sysadmin to replace the host IP# for the DS in the +pnfsd.dsfile extended attribute. +The old-hostname must resolve to the IP# already in the pnfsd.dsfile extended +attribute or the replacement will not be done. +If the old-dshostname matches, then the IP# is replaced by the first AF_INET +or AF_INET6 address that +.Xr getaddrinfo 3 +returns for the new-dshostname. +Changing a DS server's host IP# should be avoided, but this option will +allow it to be changed, if the change is unavoidable. +.It Fl r Ar dshostname +This option sets the IP address of the extended attribute entry for the +.Ar dshostname +to 0.0.0.0 so that it will no longer be used. +.Pp +This is meant to be used when mirroring is enabled and the +.Ar dshostname +DS is disabled, so that it can be re-enabled once it is repaired. +This needs to be done for all files in the exported MDS tree where +the data may not be up-to-date on the repaired DS when it is re-enabled. +After being re-enabled, the command +.Xr pnfsdscopymr 1 +with the +.Dq -r +option +will be used to copy the the file's data to this repaired DS and then update the +extended attribute to use it. +.Pp +A typical use of this will be within a +.Xr find 1 +for all regular files in the MDS's exported tree. +.sp +For example, if the disabled DS is nfsv4-data3: +.br +# cd +.br +# find . -type f -exec pnfsdsfile -q -r nfsv4-data3 {} \\; +.El +.Sh SEE ALSO +.Xr find 1 , +.Xr getaddrinfo 3 , +.Xr nfsv4 4 , +.Xr pnfs 4 , +.Xr nfsd 8 , +.Xr pnfsdscopymr 8 , +.Xr pnfsdskill 8 +.Sh HISTORY +The +.Nm +command appeared in FreeBSD12. diff --git a/usr.sbin/pnfsdsfile/pnfsdsfile.c b/usr.sbin/pnfsdsfile/pnfsdsfile.c new file mode 100644 index 00000000000..604557ed1fc --- /dev/null +++ b/usr.sbin/pnfsdsfile/pnfsdsfile.c @@ -0,0 +1,330 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2017 Rick Macklem + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void usage(void); + +static struct option longopts[] = { + { "changeds", required_argument, NULL, 'c' }, + { "quiet", no_argument, NULL, 'q' }, + { "zerods", required_argument, NULL, 'r' }, + { "ds", required_argument, NULL, 's' }, + { "zerofh", no_argument, NULL, 'z' }, + { NULL, 0, NULL, 0 } +}; + +/* + * This program displays the location information of a data storage file + * for a given file on a MetaData Server (MDS) in a pNFS service. This program + * must be run on the MDS and the file argument must be a file in a local + * file system that has been exported for the pNFS service. + */ +int +main(int argc, char *argv[]) +{ + struct addrinfo *res, *ad, *newres; + struct sockaddr_in *sin, adsin; + struct sockaddr_in6 *sin6, adsin6; + char hostn[2 * NI_MAXHOST + 2], *cp; + struct pnfsdsfile dsfile[NFSDEV_MAXMIRRORS]; + int ch, dosetxattr, i, mirrorcnt, quiet, zerods, zerofh; + in_port_t tport; + ssize_t xattrsize, xattrsize2; + + zerods = 0; + zerofh = 0; + quiet = 0; + dosetxattr = 0; + res = NULL; + newres = NULL; + cp = NULL; + while ((ch = getopt_long(argc, argv, "c:qr:s:z", longopts, NULL)) != -1) + { + switch (ch) { + case 'c': + /* Replace the first DS server with the second one. */ + if (zerofh != 0 || zerods != 0) + errx(1, "-c, -r and -z are mutually " + "exclusive"); + if (res != NULL) + errx(1, "-c and -s are mutually exclusive"); + strlcpy(hostn, optarg, 2 * NI_MAXHOST + 2); + cp = strchr(hostn, ','); + if (cp == NULL) + errx(1, "Bad -c argument %s", hostn); + *cp = '\0'; + if (getaddrinfo(hostn, NULL, NULL, &res) != 0) + errx(1, "Can't get IP# for %s", hostn); + *cp++ = ','; + if (getaddrinfo(cp, NULL, NULL, &newres) != 0) + errx(1, "Can't get IP# for %s", cp); + break; + case 'q': + quiet = 1; + break; + case 'r': + /* Reset the DS server in a mirror with 0.0.0.0. */ + if (zerofh != 0 || res != NULL || newres != NULL) + errx(1, "-r and -s, -z or -c are mutually " + "exclusive"); + zerods = 1; + /* Translate the server name to an IP address. */ + if (getaddrinfo(optarg, NULL, NULL, &res) != 0) + errx(1, "Can't get IP# for %s", optarg); + break; + case 's': + if (res != NULL) + errx(1, "-s, -c and -r are mutually " + "exclusive"); + /* Translate the server name to an IP address. */ + if (getaddrinfo(optarg, NULL, NULL, &res) != 0) + errx(1, "Can't get IP# for %s", optarg); + break; + case 'z': + if (newres != NULL || zerods != 0) + errx(1, "-c, -r and -z are mutually " + "exclusive"); + zerofh = 1; + break; + default: + usage(); + } + } + argc -= optind; + if (argc != 1) + usage(); + argv += optind; + + /* + * The host address and directory where the data storage file is + * located is in the extended attribute "pnfsd.dsfile". + */ + xattrsize = extattr_get_file(*argv, EXTATTR_NAMESPACE_SYSTEM, + "pnfsd.dsfile", dsfile, sizeof(dsfile)); + mirrorcnt = xattrsize / sizeof(struct pnfsdsfile); + xattrsize2 = mirrorcnt * sizeof(struct pnfsdsfile); + if (mirrorcnt < 1 || xattrsize != xattrsize2) + err(1, "Can't get extattr pnfsd.dsfile for %s", *argv); + + if (quiet == 0) + printf("%s:\t", *argv); + for (i = 0; i < mirrorcnt; i++) { + if (i > 0 && quiet == 0) + printf("\t"); + /* Do the zerofh option. You must be root. */ + if (zerofh != 0) { + if (geteuid() != 0) + errx(1, "Must be root/su to zerofh"); + + /* + * Do it for the server specified by -s/--ds or all + * servers, if -s/--ds was not specified. + */ + sin = &dsfile[i].dsf_sin; + sin6 = &dsfile[i].dsf_sin6; + ad = res; + while (ad != NULL) { + if (ad->ai_addr->sa_family == AF_INET && + sin->sin_family == AF_INET && + ad->ai_addrlen >= sizeof(adsin)) { + memcpy(&adsin, ad->ai_addr, + sizeof(adsin)); + if (sin->sin_addr.s_addr == + adsin.sin_addr.s_addr) + break; + } + if (ad->ai_addr->sa_family == AF_INET6 && + sin6->sin6_family == AF_INET6 && + ad->ai_addrlen >= sizeof(adsin6)) { + memcpy(&adsin6, ad->ai_addr, + sizeof(adsin6)); + if (IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, + &adsin6.sin6_addr)) + break; + } + ad = ad->ai_next; + } + if (res == NULL || ad != NULL) { + memset(&dsfile[i].dsf_fh, 0, sizeof(fhandle_t)); + dosetxattr = 1; + } + } + + /* Do the zerods option. You must be root. */ + if (zerods != 0 && mirrorcnt > 1) { + if (geteuid() != 0) + errx(1, "Must be root/su to zerods"); + + /* + * Do it for the server specified. + */ + sin = &dsfile[i].dsf_sin; + sin6 = &dsfile[i].dsf_sin6; + ad = res; + while (ad != NULL) { + if (ad->ai_addr->sa_family == AF_INET && + sin->sin_family == AF_INET && + ad->ai_addrlen >= sizeof(adsin)) { + memcpy(&adsin, ad->ai_addr, + sizeof(adsin)); + if (sin->sin_addr.s_addr == + adsin.sin_addr.s_addr) + break; + } + if (ad->ai_addr->sa_family == AF_INET6 && + sin6->sin6_family == AF_INET6 && + ad->ai_addrlen >= sizeof(adsin6)) { + memcpy(&adsin6, ad->ai_addr, + sizeof(adsin6)); + if (IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, + &adsin6.sin6_addr)) + break; + } + ad = ad->ai_next; + } + if (ad != NULL) { + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); + sin->sin_port = 0; + sin->sin_addr.s_addr = 0; + dosetxattr = 1; + } + } + + /* Do the -c option to replace the DS host address. */ + if (newres != NULL) { + if (geteuid() != 0) + errx(1, "Must be root/su to replace the host" + " addr"); + + /* + * Check that the old host address matches. + */ + sin = &dsfile[i].dsf_sin; + sin6 = &dsfile[i].dsf_sin6; + ad = res; + while (ad != NULL) { + if (ad->ai_addr->sa_family == AF_INET && + sin->sin_family == AF_INET && + ad->ai_addrlen >= sizeof(adsin)) { + memcpy(&adsin, ad->ai_addr, + sizeof(adsin)); + if (sin->sin_addr.s_addr == + adsin.sin_addr.s_addr) + break; + } + if (ad->ai_addr->sa_family == AF_INET6 && + sin6->sin6_family == AF_INET6 && + ad->ai_addrlen >= sizeof(adsin6)) { + memcpy(&adsin6, ad->ai_addr, + sizeof(adsin6)); + if (IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, + &adsin6.sin6_addr)) + break; + } + ad = ad->ai_next; + } + if (ad != NULL) { + if (sin->sin_family == AF_INET) + tport = sin->sin_port; + else + tport = sin6->sin6_port; + /* + * We have a match, so replace it with the first + * AF_INET or AF_INET6 address in the newres + * list. + */ + while (newres->ai_addr->sa_family != AF_INET && + newres->ai_addr->sa_family != AF_INET6) { + newres = newres->ai_next; + if (newres == NULL) + errx(1, "Hostname %s has no" + " IP#", cp); + } + if (newres->ai_addr->sa_family == AF_INET) { + memcpy(sin, newres->ai_addr, + sizeof(*sin)); + sin->sin_port = tport; + } else if (newres->ai_addr->sa_family == + AF_INET6) { + memcpy(sin6, newres->ai_addr, + sizeof(*sin6)); + sin6->sin6_port = tport; + } + dosetxattr = 1; + } + } + + if (quiet == 0) { + /* Translate the IP address to a hostname. */ + if (getnameinfo((struct sockaddr *)&dsfile[i].dsf_sin, + dsfile[i].dsf_sin.sin_len, hostn, sizeof(hostn), + NULL, 0, 0) < 0) + err(1, "Can't get hostname"); + printf("%s\tds%d/%s", hostn, dsfile[i].dsf_dir, + dsfile[i].dsf_filename); + } + } + if (quiet == 0) + printf("\n"); + if (dosetxattr != 0 && extattr_set_file(*argv, EXTATTR_NAMESPACE_SYSTEM, + "pnfsd.dsfile", dsfile, xattrsize) != xattrsize) + err(1, "Can't set pnfsd.dsfile"); +} + +static void +usage(void) +{ + + fprintf(stderr, "pnfsdsfile [-q/--quiet] [-z/--zerofh] " + "[-c/--changeds ] " + "[-r/--zerods ] " + "[-s/--ds ] " + "\n"); + exit(1); +} +