From 6f21ac36e26ac0127e1b828a8c72f68f6534b449 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Jul 28 2020 11:58:11 +0000 Subject: import nfs-utils-2.3.3-35.el8 --- diff --git a/SOURCES/nfs-utils-1.2.5-idmap-errmsg.patch b/SOURCES/nfs-utils-1.2.5-idmap-errmsg.patch deleted file mode 100644 index edaacb7..0000000 --- a/SOURCES/nfs-utils-1.2.5-idmap-errmsg.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff -up nfs-utils-2.1.1/utils/nfsidmap/nfsidmap.c.orig nfs-utils-2.1.1/utils/nfsidmap/nfsidmap.c ---- nfs-utils-2.1.1/utils/nfsidmap/nfsidmap.c.orig 2017-01-12 10:21:39.000000000 -0500 -+++ nfs-utils-2.1.1/utils/nfsidmap/nfsidmap.c 2017-04-26 12:47:56.715133443 -0400 -@@ -432,7 +432,7 @@ int main(int argc, char **argv) - - xlog_stderr(verbose); - if ((argc - optind) != 2) { -- xlog_warn("Bad arg count. Check /etc/request-key.conf"); -+ xlog_err("Bad arg count. Check /etc/request-key.d/request-key.conf"); - xlog_warn(usage, progname); - return EXIT_FAILURE; - } diff --git a/SOURCES/nfs-utils-2.3.3-covscan-resource-leaks.patch b/SOURCES/nfs-utils-2.3.3-covscan-resource-leaks.patch index 9ada419..51fdb6f 100644 --- a/SOURCES/nfs-utils-2.3.3-covscan-resource-leaks.patch +++ b/SOURCES/nfs-utils-2.3.3-covscan-resource-leaks.patch @@ -552,14 +552,6 @@ diff -up nfs-utils-2.3.3/utils/statd/monitor.c.orig nfs-utils-2.3.3/utils/statd/ struct sockaddr_in my_addr = { .sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK), -@@ -177,6 +177,7 @@ sm_mon_1_svc(struct mon *argp, struct sv - * We're committed...ignoring errors. Let's hope that a malloc() - * doesn't fail. (I should probably fix this assumption.) - */ -+ clnt = NULL; - if (!existing && !(clnt = nlist_new(my_name, mon_name, 0))) { - free(dnsname); - xlog_warn("out of memory"); @@ -223,6 +224,7 @@ sm_mon_1_svc(struct mon *argp, struct sv failure: diff --git a/SOURCES/nfs-utils-2.3.3-covscan-rm-deadcode-leaks.patch b/SOURCES/nfs-utils-2.3.3-covscan-rm-deadcode-leaks.patch new file mode 100644 index 0000000..b109f97 --- /dev/null +++ b/SOURCES/nfs-utils-2.3.3-covscan-rm-deadcode-leaks.patch @@ -0,0 +1,27 @@ +From c9305f75070abe76155d6db29889bf5dead218c2 Mon Sep 17 00:00:00 2001 +From: Steve Dickson +Date: Fri, 7 Feb 2020 10:18:21 -0500 +Subject: [PATCH] query_krb5_ccache: Removed dead code that was flagged by a + covscan + +Signed-off-by: Steve Dickson +--- + utils/gssd/krb5_util.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c +index bff759f..a1c43d2 100644 +--- a/utils/gssd/krb5_util.c ++++ b/utils/gssd/krb5_util.c +@@ -1066,8 +1066,6 @@ query_krb5_ccache(const char* cred_cache, char **ret_princname, + *ret_realm = strdup(str+1); + } + k5_free_unparsed_name(context, princstring); +- } else { +- found = 0; + } + } + krb5_free_principal(context, principal); +-- +2.24.1 + diff --git a/SOURCES/nfs-utils-2.3.3-gssd-memoryleak.patch b/SOURCES/nfs-utils-2.3.3-gssd-memoryleak.patch new file mode 100644 index 0000000..89764fe --- /dev/null +++ b/SOURCES/nfs-utils-2.3.3-gssd-memoryleak.patch @@ -0,0 +1,118 @@ +diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c +index eb993aab..26e51edf 100644 +--- a/utils/gssd/krb5_util.c ++++ b/utils/gssd/krb5_util.c +@@ -459,7 +459,7 @@ gssd_get_single_krb5_cred(krb5_context context, + if (ccache) + krb5_cc_close(context, ccache); + krb5_free_cred_contents(context, &my_creds); +- free(k5err); ++ krb5_free_string(context, k5err); + return (code); + } + +@@ -698,7 +698,7 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt, + "we failed to unparse principal name: %s\n", + k5err); + k5_free_kt_entry(context, kte); +- free(k5err); ++ krb5_free_string(context, k5err); + k5err = NULL; + continue; + } +@@ -745,7 +745,7 @@ gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt, + if (retval < 0) + retval = 0; + out: +- free(k5err); ++ krb5_free_string(context, k5err); + return retval; + } + +@@ -774,7 +774,7 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, + int tried_all = 0, tried_default = 0, tried_upper = 0; + krb5_principal princ; + const char *notsetstr = "not set"; +- char *adhostoverride; ++ char *adhostoverride = NULL; + + + /* Get full target hostname */ +@@ -802,7 +802,6 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, + adhostoverride); + /* No overflow: Windows cannot handle strings longer than 19 chars */ + strcpy(myhostad, adhostoverride); +- free(adhostoverride); + } else { + strcpy(myhostad, myhostname); + for (i = 0; myhostad[i] != 0; ++i) { +@@ -811,6 +810,8 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, + myhostad[i] = '$'; + myhostad[i+1] = 0; + } ++ if (adhostoverride) ++ krb5_free_string(context, adhostoverride); + + if (!srchost) { + retval = get_full_hostname(myhostname, myhostname, sizeof(myhostname)); +@@ -901,7 +902,7 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, + k5err = gssd_k5_err_msg(context, code); + printerr(1, "%s while building principal for '%s'\n", + k5err, spn); +- free(k5err); ++ krb5_free_string(context, k5err); + k5err = NULL; + continue; + } +@@ -911,7 +912,7 @@ find_keytab_entry(krb5_context context, krb5_keytab kt, + k5err = gssd_k5_err_msg(context, code); + printerr(3, "%s while getting keytab entry for '%s'\n", + k5err, spn); +- free(k5err); ++ krb5_free_string(context, k5err); + k5err = NULL; + /* + * We tried the active directory machine account +@@ -960,7 +961,7 @@ out: + k5_free_default_realm(context, default_realm); + if (realmnames) + krb5_free_host_realm(context, realmnames); +- free(k5err); ++ krb5_free_string(context, k5err); + return retval; + } + +@@ -1223,7 +1224,7 @@ gssd_destroy_krb5_machine_creds(void) + printerr(0, "WARNING: %s while resolving credential " + "cache '%s' for destruction\n", k5err, + ple->ccname); +- free(k5err); ++ krb5_free_string(context, k5err); + k5err = NULL; + continue; + } +@@ -1232,13 +1233,13 @@ gssd_destroy_krb5_machine_creds(void) + k5err = gssd_k5_err_msg(context, code); + printerr(0, "WARNING: %s while destroying credential " + "cache '%s'\n", k5err, ple->ccname); +- free(k5err); ++ krb5_free_string(context, k5err); + k5err = NULL; + } + } + krb5_free_context(context); + out: +- free(k5err); ++ krb5_free_string(context, k5err); + } + + /* +@@ -1321,7 +1322,7 @@ out_free_kt: + out_free_context: + krb5_free_context(context); + out: +- free(k5err); ++ krb5_free_string(context, k5err); + return retval; + } + diff --git a/SOURCES/nfs-utils-2.3.3-idmap-errmsg.patch b/SOURCES/nfs-utils-2.3.3-idmap-errmsg.patch new file mode 100644 index 0000000..c382703 --- /dev/null +++ b/SOURCES/nfs-utils-2.3.3-idmap-errmsg.patch @@ -0,0 +1,12 @@ +diff -up nfs-utils-2.3.3/utils/nfsidmap/nfsidmap.c.orig nfs-utils-2.3.3/utils/nfsidmap/nfsidmap.c +--- nfs-utils-2.3.3/utils/nfsidmap/nfsidmap.c.orig 2020-05-05 14:07:24.642693179 -0400 ++++ nfs-utils-2.3.3/utils/nfsidmap/nfsidmap.c 2020-05-05 14:08:39.054849153 -0400 +@@ -432,7 +432,7 @@ int main(int argc, char **argv) + + xlog_stderr(verbose); + if ((argc - optind) != 2) { +- xlog_warn("Bad arg count. Check /etc/request-key.conf"); ++ xlog_warn("Bad arg count. Check /etc/request-key.d/request-key.conf"); + xlog_warn(USAGE, progname); + return EXIT_FAILURE; + } diff --git a/SOURCES/nfs-utils-2.3.3-junction-err-fix.patch b/SOURCES/nfs-utils-2.3.3-junction-err-fix.patch new file mode 100644 index 0000000..fbaae1c --- /dev/null +++ b/SOURCES/nfs-utils-2.3.3-junction-err-fix.patch @@ -0,0 +1,57 @@ +commit efefa7845601f551820fa17cb0808dbb3c3cc3dd +Author: Steve Dickson +Date: Wed Nov 13 09:32:00 2019 -0500 + + junction: Fixed debug statement to compile with -Werror=format=2 flag + + Signed-off-by: Steve Dickson + +diff --git a/support/junction/xml.c b/support/junction/xml.c +index 79b0770..7005e95 100644 +--- a/support/junction/xml.c ++++ b/support/junction/xml.c +@@ -327,8 +327,8 @@ junction_parse_xml_read(const char *pathname, int fd, const char *name, + if (retval != FEDFS_OK) + return retval; + +- xlog(D_CALL, "%s: XML document contained in junction:\n%.*s", +- __func__, len, buf); ++ xlog(D_CALL, "%s: XML document contained in junction:\n%ld.%s", ++ __func__, len, (char *)buf); + + retval = junction_parse_xml_buf(pathname, name, buf, len, doc); + +commit f7c0c0dc4a02d87965d3fbbab69786ca07fdecea +Author: Guillaume Rousse +Date: Fri Nov 22 10:20:03 2019 -0500 + + fix compilation with -Werror=format on i586 + + Signed-off-by: Steve Dickson + +diff --git a/support/junction/xml.c b/support/junction/xml.c +index 7005e95..813110b 100644 +--- a/support/junction/xml.c ++++ b/support/junction/xml.c +@@ -327,7 +327,7 @@ junction_parse_xml_read(const char *pathname, int fd, const char *name, + if (retval != FEDFS_OK) + return retval; + +- xlog(D_CALL, "%s: XML document contained in junction:\n%ld.%s", ++ xlog(D_CALL, "%s: XML document contained in junction:\n%zu.%s", + __func__, len, (char *)buf); + + retval = junction_parse_xml_buf(pathname, name, buf, len, doc); +diff --git a/tools/locktest/testlk.c b/tools/locktest/testlk.c +index b392f71..ea51f78 100644 +--- a/tools/locktest/testlk.c ++++ b/tools/locktest/testlk.c +@@ -81,7 +81,7 @@ main(int argc, char **argv) + if (fl.l_type == F_UNLCK) { + printf("%s: no conflicting lock\n", fname); + } else { +- printf("%s: conflicting lock by %d on (%ld;%ld)\n", ++ printf("%s: conflicting lock by %d on (%zd;%zd)\n", + fname, fl.l_pid, fl.l_start, fl.l_len); + } + return 0; diff --git a/SOURCES/nfs-utils-2.3.3-nconnect-manpage.patch b/SOURCES/nfs-utils-2.3.3-nconnect-manpage.patch new file mode 100644 index 0000000..33a8d56 --- /dev/null +++ b/SOURCES/nfs-utils-2.3.3-nconnect-manpage.patch @@ -0,0 +1,40 @@ +commit 3ff6fad27d2cd0772a40ddb65694ce04f3da83bc +Author: Trond Myklebust +Date: Wed Jan 29 10:42:03 2020 -0500 + + manpage: Add a description of the 'nconnect' mount option + + Add a description of the 'nconnect' mount option on the 'nfs' generic + manpage. + + Signed-off-by: Trond Myklebust + Signed-off-by: Steve Dickson + +diff --git a/utils/mount/nfs.man b/utils/mount/nfs.man +index 6ba9cef..84462cd 100644 +--- a/utils/mount/nfs.man ++++ b/utils/mount/nfs.man +@@ -369,6 +369,23 @@ using an automounter (refer to + .BR automount (8) + for details). + .TP 1.5i ++.BR nconnect= n ++When using a connection oriented protocol such as TCP, it may ++sometimes be advantageous to set up multiple connections between ++the client and server. For instance, if your clients and/or servers ++are equipped with multiple network interface cards (NICs), using multiple ++connections to spread the load may improve overall performance. ++In such cases, the ++.BR nconnect ++option allows the user to specify the number of connections ++that should be established between the client and server up to ++a limit of 16. ++.IP ++Note that the ++.BR nconnect ++option may also be used by some pNFS drivers to decide how many ++connections to set up to the data servers. ++.TP 1.5i + .BR rdirplus " / " nordirplus + Selects whether to use NFS v3 or v4 READDIRPLUS requests. + If this option is not specified, the NFS client uses READDIRPLUS requests diff --git a/SOURCES/nfs-utils-2.3.3-nfsclnts-cmd.patch b/SOURCES/nfs-utils-2.3.3-nfsclnts-cmd.patch new file mode 100644 index 0000000..9363c2a --- /dev/null +++ b/SOURCES/nfs-utils-2.3.3-nfsclnts-cmd.patch @@ -0,0 +1,481 @@ +diff -up nfs-utils-2.3.3/configure.ac.orig nfs-utils-2.3.3/configure.ac +--- nfs-utils-2.3.3/configure.ac.orig 2020-06-09 10:58:50.178258035 -0400 ++++ nfs-utils-2.3.3/configure.ac 2020-06-09 11:02:04.203102954 -0400 +@@ -639,6 +639,7 @@ AC_CONFIG_FILES([ + tools/rpcgen/Makefile + tools/mountstats/Makefile + tools/nfs-iostat/Makefile ++ tools/nfsdclnts/Makefile + tools/nfsconf/Makefile + tools/nfsdclddb/Makefile + utils/Makefile +diff -up nfs-utils-2.3.3/tools/Makefile.am.orig nfs-utils-2.3.3/tools/Makefile.am +--- nfs-utils-2.3.3/tools/Makefile.am.orig 2020-06-09 10:58:50.178258035 -0400 ++++ nfs-utils-2.3.3/tools/Makefile.am 2020-06-09 11:02:04.203102954 -0400 +@@ -12,6 +12,6 @@ if CONFIG_NFSDCLD + OPTDIRS += nfsdclddb + endif + +-SUBDIRS = locktest rpcdebug nlmtest mountstats nfs-iostat $(OPTDIRS) ++SUBDIRS = locktest rpcdebug nlmtest mountstats nfs-iostat nfsdclnts $(OPTDIRS) + + MAINTAINERCLEANFILES = Makefile.in +diff -up nfs-utils-2.3.3/tools/nfsdclnts/Makefile.am.orig nfs-utils-2.3.3/tools/nfsdclnts/Makefile.am +--- nfs-utils-2.3.3/tools/nfsdclnts/Makefile.am.orig 2020-06-09 11:02:04.203102954 -0400 ++++ nfs-utils-2.3.3/tools/nfsdclnts/Makefile.am 2020-06-09 11:02:04.203102954 -0400 +@@ -0,0 +1,13 @@ ++## Process this file with automake to produce Makefile.in ++PYTHON_FILES = nfsdclnts.py ++ ++man8_MANS = nfsdclnts.man ++ ++EXTRA_DIST = $(man8_MANS) $(PYTHON_FILES) ++ ++all-local: $(PYTHON_FILES) ++ ++install-data-hook: ++ $(INSTALL) -m 755 nfsdclnts.py $(DESTDIR)$(sbindir)/nfsdclnts ++ ++MAINTAINERCLEANFILES=Makefile.in +diff -up nfs-utils-2.3.3/tools/nfsdclnts/nfsdclnts.man.orig nfs-utils-2.3.3/tools/nfsdclnts/nfsdclnts.man +--- nfs-utils-2.3.3/tools/nfsdclnts/nfsdclnts.man.orig 2020-06-09 11:02:04.203102954 -0400 ++++ nfs-utils-2.3.3/tools/nfsdclnts/nfsdclnts.man 2020-06-09 11:02:04.203102954 -0400 +@@ -0,0 +1,180 @@ ++.\" ++.\" nfsdclnts(8) ++.\" ++.TH "NFSDCLTS" "8" "2020-05-09" "nfsdclnts" "nfsdclnts" ++.ie \n(.g .ds Aq \(aq ++.el .ds Aq ' ++.ss \n[.ss] 0 ++.nh ++.ad l ++.de URL ++\fI\\$2\fP <\\$1>\\$3 ++.. ++.als MTO URL ++.if \n[.g] \{\ ++. mso www.tmac ++. am URL ++. ad l ++. . ++. am MTO ++. ad l ++. . ++. LINKSTYLE blue R < > ++.\} ++.SH "NAME" ++nfsdclnts \- print various nfs client information for knfsd server. ++.SH "SYNOPSIS" ++.sp ++\fBnfsdclnts\fP [\fI\-h\fP] [\fI\-t type\fP] [\fI\-\-clientinfo\fP] [\fI\-\-hostname\fP] [\fI\-q\fP] ++.SH "DESCRIPTION" ++.sp ++The nfsdclnts(8) command parses the content present in /proc/fs/nfsd/clients/ directories. nfsdclnts(8) displays files which are open, locked, delegated by the nfs\-client. It also prints useful client information such as hostname, clientID, NFS version mounted by the nfs\-client. ++.SH "OPTIONS" ++.sp ++\fB\-t, \-\-type\fP=TYPE ++.RS 4 ++Specify the type of file to be displayed. Takes only one TYPE at a time. ++.sp ++\fIopen\fP, \fIlock\fP, \fIdeleg\fP, \fIlayout\fP, or \fIall\fP ++.sp ++open: displays the open files by nfs\-client(s). ++.sp ++lock: displays the files locked by nfs\-client(s). ++.sp ++layout: displays the files for which layout is given. ++.sp ++deleg: displays delegated files information and delegation type. ++.sp ++all: prints all the above type. ++.RE ++.sp ++\fB\-\-clientinfo\fP ++.RS 4 ++displays various nfs\-client info fields such as version of nfs mounted at nfs\-client and clientID. ++.RE ++.sp ++\fB\-\-hostname\fP ++.RS 4 ++Print hostname of nfs\-client instead of ip-address. ++.RE ++.sp ++\fB\-q, \-\-quiet\fP ++.RS 4 ++Hide the header information. ++.RE ++.sp ++\fB\-v, \-\-verbose\fP ++.RS 4 ++Verbose operation, show debug messages. ++.RE ++.sp ++\fB\-f, \-\-file\fP ++.RS 4 ++Instead of processing all client directories under /proc/fs/nfsd/clients, one can provide a specific ++states file to process. One should make sure that info file resides in the same directory as states file. ++If the info file is not valid or present the fields would be marked as "N/A". ++.RE ++.sp ++\fB\-h, \-\-help\fP ++.RS 4 ++Print help explaining the command line options. ++.SH "EXAMPLES" ++.sp ++\fBnfsdclnts \-\-type open\fP ++.RS 4 ++List all files with open type only. ++.RE ++.sp ++.if n .RS 4 ++.nf ++Inode number | Type | Access | Deny | ip address | Filename ++33823232 | open | r\- | \-\- | [::1]:757 | testfile ++.fi ++.if n .RE ++.sp ++\fBnfsdclnts \-\-type deleg\fP ++.RS 4 ++List all files with deleg type only. ++.RE ++.sp ++.if n .RS 4 ++.nf ++Inode number | Type | Access | ip address | Filename ++33823232 | deleg | r | [::1]:757 | testfile ++.fi ++.if n .RE ++.sp ++\fBnfsdclnts \-\-hostname\fP ++.RS 4 ++Print hostname instead of ip\-address. ++.RE ++.sp ++.if n .RS 4 ++.nf ++Inode number | Type | Access | Deny | Hostname | Filename ++33823232 | open | r\- | \-\- | nfs\-server | testfile ++33823232 | deleg | r | | nfs\-server | testfile ++.fi ++.if n .RE ++.sp ++\fBnfsdclnts \-\-clientinfo\fP ++.RS 4 ++Print client information. ++.RE ++.sp ++.if n .RS 4 ++.nf ++Inode number | Type | Access | Deny | ip address | Client ID | vers | Filename ++33823232 | open | r\- | \-\- | [::1]:757 | 0xc79a009f5eb65e84 | 4.2 | testfile ++33823232 | deleg | r | | [::1]:757 | 0xc79a009f5eb65e84 | 4.2 | testfile ++.fi ++.if n .RE ++.sp ++\fBnfsdclnts \-\-file /proc/fs/nfsd/clients/3/states -t open\fP ++.RS 4 ++Process specific states file. ++.RE ++.sp ++.if n .RS 4 ++.nf ++Inode number | Type | Access | Deny | ip address | Client ID | vers | Filename ++33823232 | open | r\- | \-\- | [::1]:757 | 0xc79a009f5eb65e84 | 4.2 | testfile ++.fi ++.if n .RE ++.sp ++\fBnfsdclnts \-\-quiet \-\-hostname\fP ++.RS 4 ++Hide the header information. ++.RE ++.sp ++.if n .RS 4 ++.nf ++33823232 | open | r\- | \-\- | nfs\-server | testfile ++33823232 | deleg | r | | nfs\-server | testfile ++.fi ++.if n .RE ++.SH "FILES" ++.sp ++\fB/proc/fs/nfsd/clients/\fP ++.sp ++Displays basic information about each NFSv4 client. ++.sp ++\fB/proc/fs/nfsd/clients/#/info\fP ++.sp ++Displays information about all the opens held by the given client, including open modes, device numbers, inode numbers, and open owners. ++.sp ++\fB/proc/fs/nfsd/clients/#/states\fP ++.SH "NOTES" ++.sp ++/proc/fs/nfsd/clients/ support was initially introduced in 5.3 kernel and is only implemented for mount points using NFSv4. ++.SH "BUGS" ++Please report any BUGs to \c ++.MTO "linux\-nfs\(atvger.kernel.org" "" "" ++.SH SEE ALSO ++.BR nfsd (8), ++.BR exportfs (8), ++.BR idmapd (8), ++.BR statd (8) ++.SH "AUTHORS" ++Achilles Gaikwad and ++Kenneth D'souza +diff -up nfs-utils-2.3.3/tools/nfsdclnts/nfsdclnts.py.orig nfs-utils-2.3.3/tools/nfsdclnts/nfsdclnts.py +--- nfs-utils-2.3.3/tools/nfsdclnts/nfsdclnts.py.orig 2020-06-09 11:02:04.203102954 -0400 ++++ nfs-utils-2.3.3/tools/nfsdclnts/nfsdclnts.py 2020-06-09 11:02:04.203102954 -0400 +@@ -0,0 +1,254 @@ ++#!/usr/bin/python3 ++# -*- python-mode -*- ++''' ++ Copyright (C) 2020 ++ Authors: Achilles Gaikwad ++ Kenneth D'souza ++ ++ This program is free software: you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation, either version 3 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this program. If not, see . ++''' ++ ++import multiprocessing as mp ++import os ++import signal ++import sys ++ ++try: ++ import argparse ++except ImportError: ++ print('%s: Failed to import argparse - make sure argparse is installed!' ++ % sys.argv[0]) ++ sys.exit(1) ++try: ++ import yaml ++except ImportError: ++ print('%s: Failed to import yaml - make sure python3-pyyaml is installed!' ++ % sys.argv[0]) ++ sys.exit(1) ++ ++BBOLD = '\033[1;30;47m' #Bold black text with white background. ++ENDC = '\033[m' #Rest to defaults ++ ++def init_worker(): ++ signal.signal(signal.SIGINT, signal.SIG_IGN) ++ ++# this function converts the info file to a dictionary format, sorta. ++def file_to_dict(path): ++ client_info = {} ++ try: ++ with open(path) as f: ++ for line in f: ++ try: ++ (key, val) = line.split(':', 1) ++ client_info[key] = val.strip() ++ # FIXME: There has to be a better way of converting the info file to a dictionary. ++ except ValueError as reason: ++ if verbose: ++ print('Exception occured, %s' % reason) ++ ++ if len(client_info) == 0 and verbose: ++ print("Provided %s file is not valid" %path) ++ return client_info ++ ++ except OSError as reason: ++ if verbose: ++ print('%s' % reason) ++ ++# this function gets the paths from /proc/fs/nfsd/clients/ ++# returns a list of paths for each client which has nfs-share mounted. ++def getpaths(): ++ path = [] ++ try: ++ dirs = os.listdir('/proc/fs/nfsd/clients/') ++ except OSError as reason: ++ exit('%s' % reason) ++ if len(dirs) !=0: ++ for i in dirs: ++ path.append('/proc/fs/nfsd/clients/' + i + '/states') ++ return (path) ++ else: ++ exit('Nothing to process') ++ ++# A single function to rule them all, in this function we gather all the data ++# from already populated data_list and client_info. ++def printer(data_list, argument): ++ client_info_path = data_list.pop() ++ client_info = file_to_dict(client_info_path) ++ for i in data_list: ++ for key in i: ++ inode = i[key]['superblock'].split(':')[-1] ++ # The ip address is quoted, so we dequote it. ++ try: ++ client_ip = client_info['address'][1:-1] ++ except: ++ client_ip = "N/A" ++ try: ++ # if the nfs-server reboots while the nfs-client holds the files open, ++ # the nfs-server would print the filename as '/'. For such instaces we ++ # print the output as disconnected dentry instead of '/'. ++ if(i[key]['filename']=='/'): ++ fname = 'disconnected dentry' ++ else: ++ fname = i[key]['filename'].split('/')[-1] ++ except KeyError: ++ # for older kernels which do not have the fname patch in kernel, they ++ # won't be able to see the fname field. Therefore post it as N/A. ++ fname = "N/A" ++ otype = i[key]['type'] ++ try: ++ access = i[key]['access'] ++ except: ++ access = '' ++ try: ++ deny = i[key]['deny'] ++ except: ++ deny = '' ++ try: ++ hostname = client_info['name'].split()[-1].split('"')[0] ++ hostname = hostname.split('.')[0] ++ # if the hostname is too long, it messes up with the output being in columns, ++ # therefore we truncate the hostname followed by two '..' as suffix. ++ if len(hostname) > 20: ++ hostname = hostname[0:20] + '..' ++ except: ++ hostname = "N/A" ++ try: ++ clientid = client_info['clientid'] ++ except: ++ clientid = "N/A" ++ try: ++ minorversion = "4." + client_info['minor version'] ++ except: ++ minorversion = "N/A" ++ ++ otype = i[key]['type'] ++ # since some fields do not have deny column, we drop those if -t is either ++ # layout or lock. ++ drop = ['layout', 'lock'] ++ ++ # Printing the output this way instead of a single string which is concatenated ++ # this makes it better to quickly add more columns in future. ++ if(otype == argument.type or argument.type == 'all'): ++ print('%-13s' %inode, end='| ') ++ print('%-7s' %otype, end='| ') ++ if (argument.type not in drop): ++ print('%-7s' %access, end='| ') ++ if (argument.type not in drop and argument.type !='deleg'): ++ print('%-5s' %deny, end='| ') ++ if (argument.hostname == True): ++ print('%-22s' %hostname, end='| ') ++ else: ++ print('%-22s' %client_ip, end='| ') ++ if (argument.clientinfo == True) : ++ print('%-20s' %clientid, end='| ') ++ print('%-5s' %minorversion, end='| ') ++ print(fname) ++ ++def opener(path): ++ try: ++ with open(path, 'r') as nfsdata: ++ try: ++ data = yaml.load(nfsdata, Loader = yaml.BaseLoader) ++ if data is not None: ++ clientinfo = path.rsplit('/', 1)[0] + '/info' ++ data.append(clientinfo) ++ return data ++ except: ++ if verbose: ++ print("Exception occurred, Please make sure %s is a YAML file" %path) ++ ++ except OSError as reason: ++ if verbose: ++ print('%s' % reason) ++ ++def print_cols(argument): ++ title_inode = 'Inode number' ++ title_otype = 'Type' ++ title_access = 'Access' ++ title_deny = 'Deny' ++ title_fname = 'Filename' ++ title_clientID = 'Client ID' ++ title_hostname = 'Hostname' ++ title_ip = 'ip address' ++ title_nfsvers = 'vers' ++ ++ drop = ['lock', 'layout'] ++ print(BBOLD, end='') ++ print('%-13s' %title_inode, end='| ') ++ print('%-7s' %title_otype, end='| ') ++ if (argument.type not in drop): ++ print('%-7s' %title_access, end='| ') ++ if (argument.type not in drop and argument.type !='deleg'): ++ print('%-5s' %title_deny, end='| ') ++ if (argument.hostname == True): ++ print('%-22s' %title_hostname, end='| ') ++ else: ++ print('%-22s' %title_ip, end='| ') ++ if (argument.clientinfo == True): ++ print('%-20s' %title_clientID, end='| ') ++ print('%-5s' %title_nfsvers, end='| ') ++ print(title_fname, end='') ++ print(ENDC) ++ ++def nfsd4_show(): ++ ++ parser = argparse.ArgumentParser(description = 'Parse the nfsd states and clientinfo files.') ++ parser.add_argument('-t', '--type', metavar = 'type', type = str, choices = ['open', ++ 'deleg', 'lock', 'layout', 'all'], ++ default = 'all', ++ help = 'Input the type that you want to be printed: open, lock, deleg, layout, all') ++ parser.add_argument('--clientinfo', action = 'store_true', ++ help = 'output clients information, --hostname is implied.') ++ parser.add_argument('--hostname', action = 'store_true', ++ help = 'print hostname of client instead of its ip address. Longer hostnames are truncated.') ++ parser.add_argument('-v', '--verbose', action = 'store_true', ++ help = 'Verbose operation, show debug messages.') ++ parser.add_argument('-f', '--file', nargs='+', type = str, metavar='', ++ help = 'pass client states file, provided that info file resides in the same directory.') ++ parser.add_argument('-q', '--quiet', action = 'store_true', ++ help = 'don\'t print the header information') ++ ++ args = parser.parse_args() ++ ++ global verbose ++ verbose = False ++ if args.verbose: ++ verbose = True ++ ++ if args.file: ++ paths = args.file ++ else: ++ paths = getpaths() ++ ++ p = mp.Pool(mp.cpu_count(), init_worker) ++ try: ++ result = p.map(opener, paths) ++ ### Drop None entries from list ++ final_result = list(filter(None, result)) ++ p.close() ++ p.join() ++ ++ if len(final_result) !=0 and not args.quiet: ++ print_cols(args) ++ ++ for item in final_result: ++ printer(item, args) ++ ++ except KeyboardInterrupt: ++ print('Caught KeyboardInterrupt, terminating workers') ++ p.terminate() ++ p.join() ++ ++if __name__ == "__main__": ++ nfsd4_show() diff --git a/SOURCES/nfs-utils-2.3.3-nfsdcld-upstream-update.patch b/SOURCES/nfs-utils-2.3.3-nfsdcld-upstream-update.patch new file mode 100644 index 0000000..e583183 --- /dev/null +++ b/SOURCES/nfs-utils-2.3.3-nfsdcld-upstream-update.patch @@ -0,0 +1,4048 @@ +diff --git a/.gitignore b/.gitignore +index e91e7a25..e97b31f5 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -54,6 +54,7 @@ utils/rquotad/rquotad + utils/rquotad/rquota.h + utils/rquotad/rquota_xdr.c + utils/showmount/showmount ++utils/nfsdcld/nfsdcld + utils/nfsdcltrack/nfsdcltrack + utils/statd/statd + tools/locktest/testlk +diff --git a/aclocal/ax_gcc_func_attribute.m4 b/aclocal/ax_gcc_func_attribute.m4 +new file mode 100644 +index 00000000..098c9aad +--- /dev/null ++++ b/aclocal/ax_gcc_func_attribute.m4 +@@ -0,0 +1,238 @@ ++# =========================================================================== ++# https://www.gnu.org/software/autoconf-archive/ax_gcc_func_attribute.html ++# =========================================================================== ++# ++# SYNOPSIS ++# ++# AX_GCC_FUNC_ATTRIBUTE(ATTRIBUTE) ++# ++# DESCRIPTION ++# ++# This macro checks if the compiler supports one of GCC's function ++# attributes; many other compilers also provide function attributes with ++# the same syntax. Compiler warnings are used to detect supported ++# attributes as unsupported ones are ignored by default so quieting ++# warnings when using this macro will yield false positives. ++# ++# The ATTRIBUTE parameter holds the name of the attribute to be checked. ++# ++# If ATTRIBUTE is supported define HAVE_FUNC_ATTRIBUTE_. ++# ++# The macro caches its result in the ax_cv_have_func_attribute_ ++# variable. ++# ++# The macro currently supports the following function attributes: ++# ++# alias ++# aligned ++# alloc_size ++# always_inline ++# artificial ++# cold ++# const ++# constructor ++# constructor_priority for constructor attribute with priority ++# deprecated ++# destructor ++# dllexport ++# dllimport ++# error ++# externally_visible ++# fallthrough ++# flatten ++# format ++# format_arg ++# gnu_inline ++# hot ++# ifunc ++# leaf ++# malloc ++# noclone ++# noinline ++# nonnull ++# noreturn ++# nothrow ++# optimize ++# pure ++# sentinel ++# sentinel_position ++# unused ++# used ++# visibility ++# warning ++# warn_unused_result ++# weak ++# weakref ++# ++# Unsupported function attributes will be tested with a prototype ++# returning an int and not accepting any arguments and the result of the ++# check might be wrong or meaningless so use with care. ++# ++# LICENSE ++# ++# Copyright (c) 2013 Gabriele Svelto ++# ++# Copying and distribution of this file, with or without modification, are ++# permitted in any medium without royalty provided the copyright notice ++# and this notice are preserved. This file is offered as-is, without any ++# warranty. ++ ++#serial 9 ++ ++AC_DEFUN([AX_GCC_FUNC_ATTRIBUTE], [ ++ AS_VAR_PUSHDEF([ac_var], [ax_cv_have_func_attribute_$1]) ++ ++ AC_CACHE_CHECK([for __attribute__(($1))], [ac_var], [ ++ AC_LINK_IFELSE([AC_LANG_PROGRAM([ ++ m4_case([$1], ++ [alias], [ ++ int foo( void ) { return 0; } ++ int bar( void ) __attribute__(($1("foo"))); ++ ], ++ [aligned], [ ++ int foo( void ) __attribute__(($1(32))); ++ ], ++ [alloc_size], [ ++ void *foo(int a) __attribute__(($1(1))); ++ ], ++ [always_inline], [ ++ inline __attribute__(($1)) int foo( void ) { return 0; } ++ ], ++ [artificial], [ ++ inline __attribute__(($1)) int foo( void ) { return 0; } ++ ], ++ [cold], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [const], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [constructor_priority], [ ++ int foo( void ) __attribute__((__constructor__(65535/2))); ++ ], ++ [constructor], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [deprecated], [ ++ int foo( void ) __attribute__(($1(""))); ++ ], ++ [destructor], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [dllexport], [ ++ __attribute__(($1)) int foo( void ) { return 0; } ++ ], ++ [dllimport], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [error], [ ++ int foo( void ) __attribute__(($1(""))); ++ ], ++ [externally_visible], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [fallthrough], [ ++ int foo( void ) {switch (0) { case 1: __attribute__(($1)); case 2: break ; }}; ++ ], ++ [flatten], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [format], [ ++ int foo(const char *p, ...) __attribute__(($1(printf, 1, 2))); ++ ], ++ [format_arg], [ ++ char *foo(const char *p) __attribute__(($1(1))); ++ ], ++ [gnu_inline], [ ++ inline __attribute__(($1)) int foo( void ) { return 0; } ++ ], ++ [hot], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [ifunc], [ ++ int my_foo( void ) { return 0; } ++ static int (*resolve_foo(void))(void) { return my_foo; } ++ int foo( void ) __attribute__(($1("resolve_foo"))); ++ ], ++ [leaf], [ ++ __attribute__(($1)) int foo( void ) { return 0; } ++ ], ++ [malloc], [ ++ void *foo( void ) __attribute__(($1)); ++ ], ++ [noclone], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [noinline], [ ++ __attribute__(($1)) int foo( void ) { return 0; } ++ ], ++ [nonnull], [ ++ int foo(char *p) __attribute__(($1(1))); ++ ], ++ [noreturn], [ ++ void foo( void ) __attribute__(($1)); ++ ], ++ [nothrow], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [optimize], [ ++ __attribute__(($1(3))) int foo( void ) { return 0; } ++ ], ++ [pure], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [sentinel], [ ++ int foo(void *p, ...) __attribute__(($1)); ++ ], ++ [sentinel_position], [ ++ int foo(void *p, ...) __attribute__(($1(1))); ++ ], ++ [returns_nonnull], [ ++ void *foo( void ) __attribute__(($1)); ++ ], ++ [unused], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [used], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [visibility], [ ++ int foo_def( void ) __attribute__(($1("default"))); ++ int foo_hid( void ) __attribute__(($1("hidden"))); ++ int foo_int( void ) __attribute__(($1("internal"))); ++ int foo_pro( void ) __attribute__(($1("protected"))); ++ ], ++ [warning], [ ++ int foo( void ) __attribute__(($1(""))); ++ ], ++ [warn_unused_result], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [weak], [ ++ int foo( void ) __attribute__(($1)); ++ ], ++ [weakref], [ ++ static int foo( void ) { return 0; } ++ static int bar( void ) __attribute__(($1("foo"))); ++ ], ++ [ ++ m4_warn([syntax], [Unsupported attribute $1, the test may fail]) ++ int foo( void ) __attribute__(($1)); ++ ] ++ )], []) ++ ], ++ dnl GCC doesn't exit with an error if an unknown attribute is ++ dnl provided but only outputs a warning, so accept the attribute ++ dnl only if no warning were issued. ++ [AS_IF([test -s conftest.err], ++ [AS_VAR_SET([ac_var], [no])], ++ [AS_VAR_SET([ac_var], [yes])])], ++ [AS_VAR_SET([ac_var], [no])]) ++ ]) ++ ++ AS_IF([test yes = AS_VAR_GET([ac_var])], ++ [AC_DEFINE_UNQUOTED(AS_TR_CPP(HAVE_FUNC_ATTRIBUTE_$1), 1, ++ [Define to 1 if the system has the `$1' function attribute])], []) ++ ++ AS_VAR_POPDEF([ac_var]) ++]) +diff --git a/configure.ac b/configure.ac +index 48eb9eb6..13ea957f 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -238,6 +238,12 @@ else + AM_CONDITIONAL(MOUNT_CONFIG, [test "$enable_mount" = "yes"]) + fi + ++AC_ARG_ENABLE(nfsdcld, ++ [AC_HELP_STRING([--disable-nfsdcld], ++ [disable NFSv4 clientid tracking daemon @<:@default=no@:>@])], ++ enable_nfsdcld=$enableval, ++ enable_nfsdcld="yes") ++ + AC_ARG_ENABLE(nfsdcltrack, + [AC_HELP_STRING([--disable-nfsdcltrack], + [disable NFSv4 clientid tracking programs @<:@default=no@:>@])], +@@ -317,6 +323,20 @@ if test "$enable_nfsv4" = yes; then + dnl Check for sqlite3 + AC_SQLITE3_VERS + ++ if test "$enable_nfsdcld" = "yes"; then ++ AC_CHECK_HEADERS([libgen.h sys/inotify.h], , ++ AC_MSG_ERROR([Cannot find header needed for nfsdcld])) ++ ++ case $libsqlite3_cv_is_recent in ++ yes) ;; ++ unknown) ++ dnl do not fail when cross-compiling ++ AC_MSG_WARN([assuming sqlite is at least v3.3]) ;; ++ *) ++ AC_MSG_ERROR([nfsdcld requires sqlite-devel]) ;; ++ esac ++ fi ++ + if test "$enable_nfsdcltrack" = "yes"; then + AC_CHECK_HEADERS([libgen.h sys/inotify.h], , + AC_MSG_ERROR([Cannot find header needed for nfsdcltrack])) +@@ -332,6 +352,7 @@ if test "$enable_nfsv4" = yes; then + fi + + else ++ enable_nfsdcld="no" + enable_nfsdcltrack="no" + fi + +@@ -342,6 +363,7 @@ if test "$enable_nfsv41" = yes; then + fi + + dnl enable nfsidmap when its support by libnfsidmap ++AM_CONDITIONAL(CONFIG_NFSDCLD, [test "$enable_nfsdcld" = "yes" ]) + AM_CONDITIONAL(CONFIG_NFSDCLTRACK, [test "$enable_nfsdcltrack" = "yes" ]) + + +@@ -581,6 +603,7 @@ CHECK_CCSUPPORT([-Werror=format-overflow=2], [flg1]) + CHECK_CCSUPPORT([-Werror=int-conversion], [flg2]) + CHECK_CCSUPPORT([-Werror=incompatible-pointer-types], [flg3]) + CHECK_CCSUPPORT([-Werror=misleading-indentation], [flg4]) ++AX_GCC_FUNC_ATTRIBUTE([format]) + + AC_SUBST([AM_CFLAGS], ["$my_am_cflags $flg1 $flg2 $flg3 $flg4"]) + +@@ -617,8 +640,10 @@ AC_CONFIG_FILES([ + tools/mountstats/Makefile + tools/nfs-iostat/Makefile + tools/nfsconf/Makefile ++ tools/clddb-tool/Makefile + utils/Makefile + utils/blkmapd/Makefile ++ utils/nfsdcld/Makefile + utils/nfsdcltrack/Makefile + utils/exportfs/Makefile + utils/gssd/Makefile +diff --git a/nfs.conf b/nfs.conf +index d48a4e55..56172c49 100644 +--- a/nfs.conf ++++ b/nfs.conf +@@ -36,6 +36,10 @@ use-gss-proxy=1 + # state-directory-path=/var/lib/nfs + # ha-callout= + # ++[nfsdcld] ++# debug=0 ++# storagedir=/var/lib/nfs/nfsdcld ++# + [nfsdcltrack] + # debug=0 + # storagedir=/var/lib/nfs/nfsdcltrack +diff --git a/support/include/cld.h b/support/include/cld.h +index f14a9ab0..88d3b63e 100644 +--- a/support/include/cld.h ++++ b/support/include/cld.h +@@ -23,16 +23,22 @@ + #define _NFSD_CLD_H + + /* latest upcall version available */ +-#define CLD_UPCALL_VERSION 1 ++#define CLD_UPCALL_VERSION 2 + + /* defined by RFC3530 */ + #define NFS4_OPAQUE_LIMIT 1024 + ++#ifndef SHA256_DIGEST_SIZE ++#define SHA256_DIGEST_SIZE 32 ++#endif ++ + enum cld_command { + Cld_Create, /* create a record for this cm_id */ + Cld_Remove, /* remove record of this cm_id */ + Cld_Check, /* is this cm_id allowed? */ + Cld_GraceDone, /* grace period is complete */ ++ Cld_GraceStart, /* grace start (upload client records) */ ++ Cld_GetVersion, /* query max supported upcall version */ + }; + + /* representation of long-form NFSv4 client ID */ +@@ -41,6 +47,17 @@ struct cld_name { + unsigned char cn_id[NFS4_OPAQUE_LIMIT]; /* client-provided */ + } __attribute__((packed)); + ++/* sha256 hash of the kerberos principal */ ++struct cld_princhash { ++ uint8_t cp_len; /* length of cp_data */ ++ unsigned char cp_data[SHA256_DIGEST_SIZE]; /* hash of principal */ ++} __attribute__((packed)); ++ ++struct cld_clntinfo { ++ struct cld_name cc_name; ++ struct cld_princhash cc_princhash; ++} __attribute__((packed)); ++ + /* message struct for communication with userspace */ + struct cld_msg { + uint8_t cm_vers; /* upcall version */ +@@ -50,7 +67,28 @@ struct cld_msg { + union { + int64_t cm_gracetime; /* grace period start time */ + struct cld_name cm_name; ++ uint8_t cm_version; /* for getting max version */ ++ } __attribute__((packed)) cm_u; ++} __attribute__((packed)); ++ ++/* version 2 message can include hash of kerberos principal */ ++struct cld_msg_v2 { ++ uint8_t cm_vers; /* upcall version */ ++ uint8_t cm_cmd; /* upcall command */ ++ int16_t cm_status; /* return code */ ++ uint32_t cm_xid; /* transaction id */ ++ union { ++ struct cld_name cm_name; ++ uint8_t cm_version; /* for getting max version */ ++ struct cld_clntinfo cm_clntinfo; /* name & princ hash */ + } __attribute__((packed)) cm_u; + } __attribute__((packed)); + ++struct cld_msg_hdr { ++ uint8_t cm_vers; /* upcall version */ ++ uint8_t cm_cmd; /* upcall command */ ++ int16_t cm_status; /* return code */ ++ uint32_t cm_xid; /* transaction id */ ++} __attribute__((packed)); ++ + #endif /* !_NFSD_CLD_H */ +diff --git a/support/include/xcommon.h b/support/include/xcommon.h +index 23c9a135..30b0403b 100644 +--- a/support/include/xcommon.h ++++ b/support/include/xcommon.h +@@ -9,6 +9,10 @@ + #ifndef _XMALLOC_H + #define _MALLOC_H + ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ + #include + #include + #include +@@ -25,9 +29,15 @@ + + #define streq(s, t) (strcmp ((s), (t)) == 0) + +-/* Functions in sundries.c that are used in mount.c and umount.c */ ++#ifdef HAVE_FUNC_ATTRIBUTE_FORMAT ++#define X_FORMAT(_x) __attribute__((__format__ _x)) ++#else ++#define X_FORMAT(_x) ++#endif ++ ++/* Functions in sundries.c that are used in mount.c and umount.c */ + char *canonicalize (const char *path); +-void nfs_error (const char *fmt, ...); ++void nfs_error (const char *fmt, ...) X_FORMAT((printf, 1, 2)); + void *xmalloc (size_t size); + void *xrealloc(void *p, size_t size); + void xfree(void *); +@@ -36,9 +46,9 @@ char *xstrndup (const char *s, int n); + char *xstrconcat2 (const char *, const char *); + char *xstrconcat3 (const char *, const char *, const char *); + char *xstrconcat4 (const char *, const char *, const char *, const char *); +-void die (int errcode, const char *fmt, ...); ++void die (int errcode, const char *fmt, ...) X_FORMAT((printf, 2, 3)); + +-extern void die(int err, const char *fmt, ...); ++extern void die(int err, const char *fmt, ...) X_FORMAT((printf, 2, 3)); + extern void (*at_die)(void); + + /* exit status - bits below are ORed */ +diff --git a/support/include/xlog.h b/support/include/xlog.h +index a11463ed..32ff5a1b 100644 +--- a/support/include/xlog.h ++++ b/support/include/xlog.h +@@ -7,6 +7,10 @@ + #ifndef XLOG_H + #define XLOG_H + ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ + #include + + /* These are logged always. L_FATAL also does exit(1) */ +@@ -35,6 +39,12 @@ struct xlog_debugfac { + int df_fac; + }; + ++#ifdef HAVE_FUNC_ATTRIBUTE_FORMAT ++#define XLOG_FORMAT(_x) __attribute__((__format__ _x)) ++#else ++#define XLOG_FORMAT(_x) ++#endif ++ + extern int export_errno; + void xlog_open(char *progname); + void xlog_stderr(int on); +@@ -43,10 +53,10 @@ void xlog_config(int fac, int on); + void xlog_sconfig(char *, int on); + void xlog_from_conffile(char *); + int xlog_enabled(int fac); +-void xlog(int fac, const char *fmt, ...); +-void xlog_warn(const char *fmt, ...); +-void xlog_err(const char *fmt, ...); +-void xlog_errno(int err, const char *fmt, ...); +-void xlog_backend(int fac, const char *fmt, va_list args); ++void xlog(int fac, const char *fmt, ...) XLOG_FORMAT((printf, 2, 3)); ++void xlog_warn(const char *fmt, ...) XLOG_FORMAT((printf, 1, 2)); ++void xlog_err(const char *fmt, ...) XLOG_FORMAT((printf, 1, 2)); ++void xlog_errno(int err, const char *fmt, ...) XLOG_FORMAT((printf, 2, 3)); ++void xlog_backend(int fac, const char *fmt, va_list args) XLOG_FORMAT((printf, 2, 0)); + + #endif /* XLOG_H */ +diff --git a/support/junction/junction.c b/support/junction/junction.c +index ab6caa61..41cce261 100644 +--- a/support/junction/junction.c ++++ b/support/junction/junction.c +@@ -23,6 +23,10 @@ + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt + */ + ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ + #include + #include + +diff --git a/support/misc/file.c b/support/misc/file.c +index 4065376e..74973169 100644 +--- a/support/misc/file.c ++++ b/support/misc/file.c +@@ -18,6 +18,10 @@ + * along with nfs-utils. If not, see . + */ + ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ + #include + + #include +diff --git a/support/misc/mountpoint.c b/support/misc/mountpoint.c +index 9f9ce44e..4205b41c 100644 +--- a/support/misc/mountpoint.c ++++ b/support/misc/mountpoint.c +@@ -3,6 +3,10 @@ + * check if a given path is a mountpoint + */ + ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ + #include + #include "xcommon.h" + #include +diff --git a/support/nfs/cacheio.c b/support/nfs/cacheio.c +index 9dc4cf1c..7c4cf373 100644 +--- a/support/nfs/cacheio.c ++++ b/support/nfs/cacheio.c +@@ -15,6 +15,10 @@ + * + */ + ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ + #include + #include + #include +diff --git a/support/nfs/svc_create.c b/support/nfs/svc_create.c +index ef7ff05f..7b595f89 100644 +--- a/support/nfs/svc_create.c ++++ b/support/nfs/svc_create.c +@@ -184,7 +184,7 @@ svc_create_sock(const struct sockaddr *sap, socklen_t salen, + type = SOCK_STREAM; + break; + default: +- xlog(D_GENERAL, "%s: Unrecognized bind address semantics: %u", ++ xlog(D_GENERAL, "%s: Unrecognized bind address semantics: %lu", + __func__, nconf->nc_semantics); + return -1; + } +diff --git a/support/nsm/rpc.c b/support/nsm/rpc.c +index ae49006c..08b4746f 100644 +--- a/support/nsm/rpc.c ++++ b/support/nsm/rpc.c +@@ -182,7 +182,7 @@ nsm_xmit_getport(const int sock, const struct sockaddr_in *sin, + uint32_t xid; + XDR xdr; + +- xlog(D_CALL, "Sending PMAP_GETPORT for %u, %u, udp", program, version); ++ xlog(D_CALL, "Sending PMAP_GETPORT for %lu, %lu, udp", program, version); + + nsm_init_xdrmem(msgbuf, NSM_MAXMSGSIZE, &xdr); + xid = nsm_init_rpc_header(PMAPPROG, PMAPVERS, +diff --git a/systemd/Makefile.am b/systemd/Makefile.am +index d54518bc..53458c62 100644 +--- a/systemd/Makefile.am ++++ b/systemd/Makefile.am +@@ -36,6 +36,11 @@ unit_files += \ + endif + endif + ++if CONFIG_NFSDCLD ++unit_files += \ ++ nfsdcld.service ++endif ++ + man5_MANS = nfs.conf.man + man7_MANS = nfs.systemd.man + EXTRA_DIST = $(unit_files) $(man5_MANS) $(man7_MANS) +diff --git a/systemd/nfs-server.service b/systemd/nfs-server.service +index 136552b5..24118d69 100644 +--- a/systemd/nfs-server.service ++++ b/systemd/nfs-server.service +@@ -6,10 +6,12 @@ Requires= nfs-mountd.service + Wants=rpcbind.socket network-online.target + Wants=rpc-statd.service nfs-idmapd.service + Wants=rpc-statd-notify.service ++Wants=nfsdcld.service + + After= network-online.target local-fs.target + After= proc-fs-nfsd.mount rpcbind.socket nfs-mountd.service + After= nfs-idmapd.service rpc-statd.service ++After= nfsdcld.service + Before= rpc-statd-notify.service + + # GSS services dependencies and ordering +diff --git a/systemd/nfsdcld.service b/systemd/nfsdcld.service +new file mode 100644 +index 00000000..a32d2430 +--- /dev/null ++++ b/systemd/nfsdcld.service +@@ -0,0 +1,10 @@ ++[Unit] ++Description=NFSv4 Client Tracking Daemon ++DefaultDependencies=no ++Conflicts=umount.target ++Requires=rpc_pipefs.target proc-fs-nfsd.mount ++After=rpc_pipefs.target proc-fs-nfsd.mount ++ ++[Service] ++Type=forking ++ExecStart=/usr/sbin/nfsdcld +diff --git a/tools/Makefile.am b/tools/Makefile.am +index 4266da49..53e61170 100644 +--- a/tools/Makefile.am ++++ b/tools/Makefile.am +@@ -8,6 +8,10 @@ endif + + OPTDIRS += nfsconf + ++if CONFIG_NFSDCLD ++OPTDIRS += clddb-tool ++endif ++ + SUBDIRS = locktest rpcdebug nlmtest mountstats nfs-iostat $(OPTDIRS) + + MAINTAINERCLEANFILES = Makefile.in +diff --git a/tools/clddb-tool/Makefile.am b/tools/clddb-tool/Makefile.am +new file mode 100644 +index 00000000..15a8fd47 +--- /dev/null ++++ b/tools/clddb-tool/Makefile.am +@@ -0,0 +1,13 @@ ++## Process this file with automake to produce Makefile.in ++PYTHON_FILES = clddb-tool.py ++ ++man8_MANS = clddb-tool.man ++ ++EXTRA_DIST = $(man8_MANS) $(PYTHON_FILES) ++ ++all-local: $(PYTHON_FILES) ++ ++install-data-hook: ++ $(INSTALL) -m 755 clddb-tool.py $(DESTDIR)$(sbindir)/clddb-tool ++ ++MAINTAINERCLEANFILES=Makefile.in +diff --git a/tools/clddb-tool/clddb-tool.man b/tools/clddb-tool/clddb-tool.man +new file mode 100644 +index 00000000..e80b2c05 +--- /dev/null ++++ b/tools/clddb-tool/clddb-tool.man +@@ -0,0 +1,83 @@ ++.\" ++.\" clddb-tool(8) ++.\" ++.TH clddb-tool 8 "07 Aug 2019" ++.SH NAME ++clddb-tool \- Tool for manipulating the nfsdcld sqlite database ++.SH SYNOPSIS ++.B clddb-tool ++.RB [ \-h | \-\-help ] ++.P ++.B clddb-tool ++.RB [ \-p | \-\-path ++.IR dbpath ] ++.B fix-table-names ++.RB [ \-h | \-\-help ] ++.P ++.B clddb-tool ++.RB [ \-p | \-\-path ++.IR dbpath ] ++.B downgrade-schema ++.RB [ \-h | \-\-help ] ++.RB [ \-v | \-\-version ++.IR to-version ] ++.P ++.B clddb-tool ++.RB [ \-p | \-\-path ++.IR dbpath ] ++.B print ++.RB [ \-h | \-\-help ] ++.RB [ \-s | \-\-summary ] ++.P ++ ++.SH DESCRIPTION ++.RB "The " clddb-tool " command is provided to perform some manipulation of the nfsdcld sqlite database schema and to print the contents of the database." ++.SS Sub-commands ++Valid ++.B clddb-tool ++subcommands are: ++.IP "\fBfix-table-names\fP" ++.RB "A previous version of " nfsdcld "(8) contained a bug that corrupted the reboot epoch table names. This sub-command will fix those table names." ++.IP "\fBdowngrade-schema\fP" ++Downgrade the database schema. Currently the schema can only to downgraded from version 4 to version 3. ++.IP "\fBprint\fP" ++Display the contents of the database. Prints the schema version and the values of the current and recovery epochs. If the ++.BR \-s | \-\-summary ++option is not given, also prints the clients in the reboot epoch tables. ++.SH OPTIONS ++.SS Options valid for all sub-commands ++.TP ++.B \-h, \-\-help ++Show the help message and exit ++.TP ++\fB\-p \fIdbpath\fR, \fB\-\-path \fIdbpath\fR ++Open the sqlite database located at ++.I dbpath ++instead of ++.IR /var/lib/nfs/nfsdcld/main.sqlite ". " ++This is mainly for testing purposes. ++.SS Options specific to the downgrade-schema sub-command ++.TP ++\fB\-v \fIto-version\fR, \fB\-\-version \fIto-version\fR ++The schema version to downgrade to. Currently the schema can only be downgraded to version 3. ++.SS Options specific to the print sub-command ++.TP ++.B \-s, \-\-summary ++Do not list the clients in the reboot epoch tables in the output. ++.SH NOTES ++The ++.B clddb-tool ++command will not allow the ++.B fix-table-names ++or ++.B downgrade-schema ++subcommands to be used if ++.BR nfsdcld (8) ++is running. ++.SH FILES ++.TP ++.B /var/lib/nfs/nfsdcld/main.sqlite ++.SH SEE ALSO ++.BR nfsdcld (8) ++.SH AUTHOR ++Scott Mayhew +diff --git a/tools/clddb-tool/clddb-tool.py b/tools/clddb-tool/clddb-tool.py +new file mode 100644 +index 00000000..8a661318 +--- /dev/null ++++ b/tools/clddb-tool/clddb-tool.py +@@ -0,0 +1,266 @@ ++#!/usr/bin/python3 ++"""Tool for manipulating the nfsdcld sqlite database ++""" ++ ++__copyright__ = """ ++Copyright (C) 2019 Scott Mayhew ++ ++This program is free software; you can redistribute it and/or ++modify it under the terms of the GNU General Public License ++as published by the Free Software Foundation; either version 2 ++of the License, or (at your option) any later version. ++ ++This program is distributed in the hope that it will be useful, ++but WITHOUT ANY WARRANTY; without even the implied warranty of ++MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++GNU General Public License for more details. ++ ++You should have received a copy of the GNU General Public License ++along with this program; if not, write to the Free Software ++Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, ++MA 02110-1301, USA. ++""" ++ ++import argparse ++import os ++import sqlite3 ++import sys ++ ++ ++class CldDb(): ++ def __init__(self, path): ++ self.con = sqlite3.connect(path) ++ self.con.row_factory = sqlite3.Row ++ for row in self.con.execute('select value from parameters ' ++ 'where key = "version"'): ++ self.version = int(row['value']) ++ for row in self.con.execute('select * from grace'): ++ self.current = int(row['current']) ++ self.recovery = int(row['recovery']) ++ ++ def __del__(self): ++ self.con.close() ++ ++ def __str__(self): ++ return ('Schema version: {self.version} ' ++ 'current epoch: {self.current} ' ++ 'recovery epoch: {self.recovery}'.format(self=self)) ++ ++ def _print_clients(self, epoch): ++ if epoch: ++ for row in self.con.execute('select * from "rec-{:016x}"' ++ .format(epoch)): ++ if self.version >= 4: ++ if row['princhash'] is not None: ++ princhash = row['princhash'].hex() ++ else: ++ princhash = "(null)" ++ print('id = {}, princhash = {}' ++ .format(row['id'].decode(), princhash)) ++ else: ++ print('id = {}'.format(row['id'].decode())) ++ ++ def print_current_clients(self): ++ print('Clients in current epoch:') ++ self._print_clients(self.current) ++ ++ def print_recovery_clients(self): ++ if self.recovery: ++ print('Clients in recovery epoch:') ++ self._print_clients(self.recovery) ++ ++ def check_bad_table_names(self): ++ bad_names = [] ++ for row in self.con.execute('select name from sqlite_master ' ++ 'where type = "table" ' ++ 'and name like "%rec-%" ' ++ 'and length(name) < 20'): ++ bad_names.append(row['name']) ++ return bad_names ++ ++ def fix_bad_table_names(self): ++ try: ++ self.con.execute('begin exclusive transaction') ++ bad_names = self.check_bad_table_names() ++ for bad_name in bad_names: ++ epoch = int(bad_name.split('-')[1], base=16) ++ if epoch == self.current or epoch == self.recovery: ++ if epoch == self.current: ++ which = 'current' ++ else: ++ which = 'recovery' ++ print('found invalid table name {} for {} epoch' ++ .format(bad_name, which)) ++ self.con.execute('alter table "{}" ' ++ 'rename to "rec-{:016x}"' ++ .format(bad_name, epoch)) ++ print('renamed to rec-{:016x}'.format(epoch)) ++ else: ++ print('found invalid table name {} for unknown epoch {}' ++ .format(bad_name, epoch)) ++ self.con.execute('drop table "{}"'.format(bad_name)) ++ print('dropped table {}'.format(bad_name)) ++ except sqlite3.Error: ++ self.con.rollback() ++ else: ++ self.con.commit() ++ ++ def has_princ_data(self): ++ if self.version < 4: ++ return False ++ for row in self.con.execute('select count(*) ' ++ 'from "rec-{:016x}" ' ++ 'where princhash not null' ++ .format(self.current)): ++ count = row[0] ++ if self.recovery: ++ for row in self.con.execute('select count(*) ' ++ 'from "rec-{:016x}" ' ++ 'where princhash not null' ++ .format(self.current)): ++ count = count + row[0] ++ if count: ++ return True ++ return False ++ ++ def _downgrade_table_v4_to_v3(self, epoch): ++ if not self.con.in_transaction: ++ raise sqlite3.Error ++ try: ++ self.con.execute('create table "new_rec-{:016x}" ' ++ '(id blob primary key)'.format(epoch)) ++ self.con.execute('insert into "new_rec-{:016x}" ' ++ 'select id from "rec-{:016x}"' ++ .format(epoch, epoch)) ++ self.con.execute('drop table "rec-{:016x}"'.format(epoch)) ++ self.con.execute('alter table "new_rec-{:016x}" ' ++ 'rename to "rec-{:016x}"' ++ .format(epoch, epoch)) ++ except sqlite3.Error: ++ raise ++ ++ def downgrade_schema_v4_to_v3(self): ++ try: ++ self.con.execute('begin exclusive transaction') ++ for row in self.con.execute('select value from parameters ' ++ 'where key = "version"'): ++ version = int(row['value']) ++ if version != self.version: ++ raise sqlite3.Error ++ for row in self.con.execute('select * from grace'): ++ current = int(row['current']) ++ recovery = int(row['recovery']) ++ if current != self.current: ++ raise sqlite3.Error ++ if recovery != self.recovery: ++ raise sqlite3.Error ++ self._downgrade_table_v4_to_v3(current) ++ if recovery: ++ self._downgrade_table_v4_to_v3(recovery) ++ self.con.execute('update parameters ' ++ 'set value = "3" ' ++ 'where key = "version"') ++ self.version = 3 ++ except sqlite3.Error: ++ self.con.rollback() ++ print('Downgrade failed') ++ else: ++ self.con.commit() ++ print('Downgrade successful') ++ ++ ++def nfsdcld_active(): ++ rc = os.system('ps -C nfsdcld >/dev/null 2>/dev/null') ++ if rc == 0: ++ return True ++ return False ++ ++ ++def fix_table_names_command(db, args): ++ if nfsdcld_active(): ++ print('Warning: nfsdcld is running!') ++ ans = input('Continue? ') ++ if ans.lower() not in ['y', 'yes']: ++ print('Operation canceled.') ++ return ++ bad_names = db.check_bad_table_names() ++ if not bad_names: ++ print('No invalid table names found.') ++ return ++ db.fix_bad_table_names() ++ ++ ++def downgrade_schema_command(db, args): ++ if nfsdcld_active(): ++ print('Warning: nfsdcld is running!') ++ ans = input('Continue? ') ++ if ans.lower() not in ['y', 'yes']: ++ print('Operation canceled') ++ return ++ if db.version != 4: ++ print('Cannot downgrade database from schema version {}.' ++ .format(db.version)) ++ return ++ if args.version != 3: ++ print('Cannot downgrade to version {}.'.format(args.version)) ++ return ++ bad_names = db.check_bad_table_names() ++ if bad_names: ++ print('Invalid table names detected.') ++ print('Please run "{} fix-table-names" before downgrading the schema.' ++ .format(sys.argv[0])) ++ return ++ if db.has_princ_data(): ++ print('Warning: database has principal data, which will be erased.') ++ ans = input('Continue? ') ++ if ans.lower() not in ['y', 'yes']: ++ print('Operation canceled') ++ return ++ db.downgrade_schema_v4_to_v3() ++ ++ ++def print_command(db, args): ++ print(str(db)) ++ if not args.summary: ++ bad_names = db.check_bad_table_names() ++ if bad_names: ++ print('Invalid table names detected.') ++ print('Please run "{} fix-table-names".'.format(sys.argv[0])) ++ return ++ db.print_current_clients() ++ db.print_recovery_clients() ++ ++ ++def main(): ++ parser = argparse.ArgumentParser() ++ parser.add_argument('-p', '--path', ++ default='/var/lib/nfs/nfsdcld/main.sqlite', ++ help='path to the database ' ++ '(default: /var/lib/nfs/nfsdcld/main.sqlite)') ++ subparsers = parser.add_subparsers(help='sub-command help') ++ fix_parser = subparsers.add_parser('fix-table-names', ++ help='fix invalid table names') ++ fix_parser.set_defaults(func=fix_table_names_command) ++ downgrade_parser = subparsers.add_parser('downgrade-schema', ++ help='downgrade database schema') ++ downgrade_parser.add_argument('-v', '--version', type=int, choices=[3], ++ default=3, ++ help='version to downgrade to') ++ downgrade_parser.set_defaults(func=downgrade_schema_command) ++ print_parser = subparsers.add_parser('print', ++ help='print database info') ++ print_parser.add_argument('-s', '--summary', default=False, ++ action='store_true', ++ help='print summary only') ++ print_parser.set_defaults(func=print_command) ++ args = parser.parse_args() ++ if not os.path.exists(args.path): ++ return parser.print_usage() ++ clddb = CldDb(args.path) ++ return args.func(clddb, args) ++ ++ ++if __name__ == '__main__': ++ if len(sys.argv) == 1: ++ sys.argv.extend(['print', '--summary']) ++ main() +diff --git a/utils/Makefile.am b/utils/Makefile.am +index 0a5b062c..4c930a4b 100644 +--- a/utils/Makefile.am ++++ b/utils/Makefile.am +@@ -19,6 +19,10 @@ if CONFIG_MOUNT + OPTDIRS += mount + endif + ++if CONFIG_NFSDCLD ++OPTDIRS += nfsdcld ++endif ++ + if CONFIG_NFSDCLTRACK + OPTDIRS += nfsdcltrack + endif +diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c +index cd3c979d..4b9634b7 100644 +--- a/utils/exportfs/exportfs.c ++++ b/utils/exportfs/exportfs.c +@@ -644,6 +644,9 @@ out: + return result; + } + ++#ifdef HAVE_FUNC_ATTRIBUTE_FORMAT ++__attribute__((format (printf, 2, 3))) ++#endif + static char + dumpopt(char c, char *fmt, ...) + { +diff --git a/utils/mount/fstab.c b/utils/mount/fstab.c +index eedbddab..8b0aaf1a 100644 +--- a/utils/mount/fstab.c ++++ b/utils/mount/fstab.c +@@ -7,6 +7,10 @@ + * - Moved code to nfs-utils/support/nfs from util-linux/mount. + */ + ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ + #include + #include + #include +diff --git a/utils/mountd/cache.c b/utils/mountd/cache.c +index a054ce6f..c73e29be 100644 +--- a/utils/mountd/cache.c ++++ b/utils/mountd/cache.c +@@ -967,8 +967,7 @@ lookup_export(char *dom, char *path, struct addrinfo *ai) + } else if (found_type == i && found->m_warned == 0) { + xlog(L_WARNING, "%s exported to both %s and %s, " + "arbitrarily choosing options from first", +- path, found->m_client->m_hostname, exp->m_client->m_hostname, +- dom); ++ path, found->m_client->m_hostname, exp->m_client->m_hostname); + found->m_warned = 1; + } + } +diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c +index 086c39bf..0b891121 100644 +--- a/utils/mountd/mountd.c ++++ b/utils/mountd/mountd.c +@@ -209,10 +209,10 @@ killer (int sig) + } + + static void +-sig_hup (int sig) ++sig_hup (int UNUSED(sig)) + { + /* don't exit on SIGHUP */ +- xlog (L_NOTICE, "Received SIGHUP... Ignoring.\n", sig); ++ xlog (L_NOTICE, "Received SIGHUP... Ignoring.\n"); + return; + } + +diff --git a/utils/nfsdcld/Makefile.am b/utils/nfsdcld/Makefile.am +new file mode 100644 +index 00000000..273d64f1 +--- /dev/null ++++ b/utils/nfsdcld/Makefile.am +@@ -0,0 +1,15 @@ ++## Process this file with automake to produce Makefile.in ++ ++man8_MANS = nfsdcld.man ++EXTRA_DIST = $(man8_MANS) ++ ++AM_CFLAGS += -D_LARGEFILE64_SOURCE ++sbin_PROGRAMS = nfsdcld ++ ++nfsdcld_SOURCES = nfsdcld.c sqlite.c legacy.c ++nfsdcld_LDADD = ../../support/nfs/libnfs.la $(LIBEVENT) $(LIBSQLITE) $(LIBCAP) ++ ++noinst_HEADERS = sqlite.h cld-internal.h legacy.h ++ ++MAINTAINERCLEANFILES = Makefile.in ++ +diff --git a/utils/nfsdcld/cld-internal.h b/utils/nfsdcld/cld-internal.h +new file mode 100644 +index 00000000..05f01be2 +--- /dev/null ++++ b/utils/nfsdcld/cld-internal.h +@@ -0,0 +1,44 @@ ++/* ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version 2 ++ * of the License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, ++ * Boston, MA 02110-1301, USA. ++ */ ++ ++#ifndef _CLD_INTERNAL_H_ ++#define _CLD_INTERNAL_H_ ++ ++#if CLD_UPCALL_VERSION >= 2 ++#define UPCALL_VERSION 2 ++#else ++#define UPCALL_VERSION 1 ++#endif ++ ++struct cld_client { ++ int cl_fd; ++ struct event cl_event; ++ union { ++ struct cld_msg cl_msg; ++#if UPCALL_VERSION >= 2 ++ struct cld_msg_v2 cl_msg_v2; ++#endif ++ } cl_u; ++}; ++ ++uint64_t current_epoch; ++uint64_t recovery_epoch; ++int first_time; ++int num_cltrack_records; ++int num_legacy_records; ++ ++#endif /* _CLD_INTERNAL_H_ */ +diff --git a/utils/nfsdcld/legacy.c b/utils/nfsdcld/legacy.c +new file mode 100644 +index 00000000..3c6bea6c +--- /dev/null ++++ b/utils/nfsdcld/legacy.c +@@ -0,0 +1,185 @@ ++/* ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version 2 ++ * of the License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, ++ * Boston, MA 02110-1301, USA. ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include ++#endif ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "cld.h" ++#include "sqlite.h" ++#include "xlog.h" ++#include "legacy.h" ++ ++#define NFSD_RECDIR_FILE "/proc/fs/nfsd/nfsv4recoverydir" ++ ++/* ++ * Loads client records from the v4recovery directory into the database. ++ * Records are prefixed with the string "hash:" and include the '\0' byte. ++ * ++ * Called during database initialization as part of a one-time "upgrade". ++ */ ++void ++legacy_load_clients_from_recdir(int *num_records) ++{ ++ int fd; ++ DIR *v4recovery; ++ struct dirent *entry; ++ char recdirname[PATH_MAX]; ++ char buf[NFS4_OPAQUE_LIMIT]; ++ struct stat st; ++ char *nl; ++ ++ fd = open(NFSD_RECDIR_FILE, O_RDONLY); ++ if (fd < 0) { ++ xlog(D_GENERAL, "Unable to open %s: %m", NFSD_RECDIR_FILE); ++ return; ++ } ++ if (read(fd, recdirname, PATH_MAX) < 0) { ++ xlog(D_GENERAL, "Unable to read from %s: %m", NFSD_RECDIR_FILE); ++ return; ++ } ++ close(fd); ++ /* the output from the proc file isn't null-terminated */ ++ nl = strchr(recdirname, '\n'); ++ if (!nl) ++ return; ++ *nl = '\0'; ++ if (stat(recdirname, &st) < 0) { ++ xlog(D_GENERAL, "Unable to stat %s: %d", recdirname, errno); ++ return; ++ } ++ if (!S_ISDIR(st.st_mode)) { ++ xlog(D_GENERAL, "%s is not a directory: mode=0%o", recdirname ++ , st.st_mode); ++ return; ++ } ++ v4recovery = opendir(recdirname); ++ if (!v4recovery) ++ return; ++ while ((entry = readdir(v4recovery))) { ++ int ret; ++ ++ /* skip "." and ".." */ ++ if (entry->d_name[0] == '.') { ++ switch (entry->d_name[1]) { ++ case '\0': ++ continue; ++ case '.': ++ if (entry->d_name[2] == '\0') ++ continue; ++ } ++ } ++ /* prefix legacy records with the string "hash:" */ ++ ret = snprintf(buf, sizeof(buf), "hash:%s", entry->d_name); ++ /* if there's a problem, then skip this entry */ ++ if (ret < 0 || (size_t)ret >= sizeof(buf)) { ++ xlog(L_WARNING, "%s: unable to build client string for %s!", ++ __func__, entry->d_name); ++ continue; ++ } ++ /* legacy client records need to include the null terminator */ ++ ret = sqlite_insert_client((unsigned char *)buf, strlen(buf) + 1); ++ if (ret) ++ xlog(L_WARNING, "%s: unable to insert %s: %d", __func__, ++ entry->d_name, ret); ++ else ++ (*num_records)++; ++ } ++ closedir(v4recovery); ++} ++ ++/* ++ * Cleans out the v4recovery directory. ++ * ++ * Called upon receipt of the first "GraceDone" upcall only. ++ */ ++void ++legacy_clear_recdir(void) ++{ ++ int fd; ++ DIR *v4recovery; ++ struct dirent *entry; ++ char recdirname[PATH_MAX]; ++ char dirname[PATH_MAX]; ++ struct stat st; ++ char *nl; ++ ++ fd = open(NFSD_RECDIR_FILE, O_RDONLY); ++ if (fd < 0) { ++ xlog(D_GENERAL, "Unable to open %s: %m", NFSD_RECDIR_FILE); ++ return; ++ } ++ if (read(fd, recdirname, PATH_MAX) < 0) { ++ xlog(D_GENERAL, "Unable to read from %s: %m", NFSD_RECDIR_FILE); ++ return; ++ } ++ close(fd); ++ /* the output from the proc file isn't null-terminated */ ++ nl = strchr(recdirname, '\n'); ++ if (!nl) ++ return; ++ *nl = '\0'; ++ if (stat(recdirname, &st) < 0) { ++ xlog(D_GENERAL, "Unable to stat %s: %d", recdirname, errno); ++ return; ++ } ++ if (!S_ISDIR(st.st_mode)) { ++ xlog(D_GENERAL, "%s is not a directory: mode=0%o", recdirname ++ , st.st_mode); ++ return; ++ } ++ v4recovery = opendir(recdirname); ++ if (!v4recovery) ++ return; ++ while ((entry = readdir(v4recovery))) { ++ int len; ++ ++ /* skip "." and ".." */ ++ if (entry->d_name[0] == '.') { ++ switch (entry->d_name[1]) { ++ case '\0': ++ continue; ++ case '.': ++ if (entry->d_name[2] == '\0') ++ continue; ++ } ++ } ++ len = snprintf(dirname, sizeof(dirname), "%s/%s", recdirname, ++ entry->d_name); ++ /* if there's a problem, then skip this entry */ ++ if (len < 0 || (size_t)len >= sizeof(dirname)) { ++ xlog(L_WARNING, "%s: unable to build filename for %s!", ++ __func__, entry->d_name); ++ continue; ++ } ++ len = rmdir(dirname); ++ if (len) ++ xlog(L_WARNING, "%s: unable to rmdir %s: %d", __func__, ++ dirname, len); ++ } ++ closedir(v4recovery); ++} +diff --git a/utils/nfsdcld/legacy.h b/utils/nfsdcld/legacy.h +new file mode 100644 +index 00000000..8988f6e8 +--- /dev/null ++++ b/utils/nfsdcld/legacy.h +@@ -0,0 +1,24 @@ ++/* ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version 2 ++ * of the License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, ++ * Boston, MA 02110-1301, USA. ++ */ ++ ++#ifndef _LEGACY_H_ ++#define _LEGACY_H_ ++ ++void legacy_load_clients_from_recdir(int *); ++void legacy_clear_recdir(void); ++ ++#endif /* _LEGACY_H_ */ +diff --git a/utils/nfsdcld/nfsdcld.c b/utils/nfsdcld/nfsdcld.c +new file mode 100644 +index 00000000..2ad10019 +--- /dev/null ++++ b/utils/nfsdcld/nfsdcld.c +@@ -0,0 +1,866 @@ ++/* ++ * nfsdcld.c -- NFSv4 client name tracking daemon ++ * ++ * Copyright (C) 2011 Red Hat, Jeff Layton ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version 2 ++ * of the License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, ++ * Boston, MA 02110-1301, USA. ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include "config.h" ++#endif /* HAVE_CONFIG_H */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#ifdef HAVE_SYS_CAPABILITY_H ++#include ++#include ++#endif ++ ++#include "xlog.h" ++#include "nfslib.h" ++#include "cld.h" ++#include "cld-internal.h" ++#include "sqlite.h" ++#include "../mount/version.h" ++#include "conffile.h" ++#include "legacy.h" ++ ++#ifndef DEFAULT_PIPEFS_DIR ++#define DEFAULT_PIPEFS_DIR NFS_STATEDIR "/rpc_pipefs" ++#endif ++ ++#define DEFAULT_CLD_PATH "/nfsd/cld" ++ ++#ifndef CLD_DEFAULT_STORAGEDIR ++#define CLD_DEFAULT_STORAGEDIR NFS_STATEDIR "/nfsdcld" ++#endif ++ ++#define NFSD_END_GRACE_FILE "/proc/fs/nfsd/v4_end_grace" ++ ++/* private data structures */ ++ ++/* global variables */ ++static char pipefs_dir[PATH_MAX] = DEFAULT_PIPEFS_DIR; ++static char pipepath[PATH_MAX]; ++static int inotify_fd = -1; ++static struct event pipedir_event; ++static bool old_kernel = false; ++ ++static struct option longopts[] = ++{ ++ { "help", 0, NULL, 'h' }, ++ { "foreground", 0, NULL, 'F' }, ++ { "debug", 0, NULL, 'd' }, ++ { "pipefsdir", 1, NULL, 'p' }, ++ { "storagedir", 1, NULL, 's' }, ++ { NULL, 0, 0, 0 }, ++}; ++ ++/* forward declarations */ ++static void cldcb(int UNUSED(fd), short which, void *data); ++ ++static void ++usage(char *progname) ++{ ++ printf("%s [ -hFd ] [ -p pipefsdir ] [ -s storagedir ]\n", progname); ++} ++ ++static int ++cld_set_caps(void) ++{ ++ int ret = 0; ++#ifdef HAVE_SYS_CAPABILITY_H ++ unsigned long i; ++ cap_t caps; ++ ++ if (getuid() != 0) { ++ xlog(L_ERROR, "Not running as root. Daemon won't be able to " ++ "open the pipe after dropping capabilities!"); ++ return -EINVAL; ++ } ++ ++ /* prune the bounding set to nothing */ ++ for (i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0 ; ++i) { ++ ret = prctl(PR_CAPBSET_DROP, i, 0, 0, 0); ++ if (ret) { ++ xlog(L_ERROR, "Unable to prune capability %lu from " ++ "bounding set: %m", i); ++ return -errno; ++ } ++ } ++ ++ /* get a blank capset */ ++ caps = cap_init(); ++ if (caps == NULL) { ++ xlog(L_ERROR, "Unable to get blank capability set: %m"); ++ return -errno; ++ } ++ ++ /* reset the process capabilities */ ++ if (cap_set_proc(caps) != 0) { ++ xlog(L_ERROR, "Unable to set process capabilities: %m"); ++ ret = -errno; ++ } ++ cap_free(caps); ++#endif ++ return ret; ++} ++ ++#define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX) ++ ++static int ++cld_pipe_open(struct cld_client *clnt) ++{ ++ int fd; ++ ++ xlog(D_GENERAL, "%s: opening upcall pipe %s", __func__, pipepath); ++ fd = open(pipepath, O_RDWR, 0); ++ if (fd < 0) { ++ xlog(D_GENERAL, "%s: open of %s failed: %m", __func__, pipepath); ++ return -errno; ++ } ++ ++ if (event_initialized(&clnt->cl_event)) ++ event_del(&clnt->cl_event); ++ if (clnt->cl_fd >= 0) ++ close(clnt->cl_fd); ++ ++ clnt->cl_fd = fd; ++ event_set(&clnt->cl_event, clnt->cl_fd, EV_READ, cldcb, clnt); ++ /* event_add is done by the caller */ ++ return 0; ++} ++ ++static void ++cld_inotify_cb(int UNUSED(fd), short which, void *data) ++{ ++ int ret; ++ size_t elen; ++ ssize_t rret; ++ char evbuf[INOTIFY_EVENT_MAX]; ++ char *dirc = NULL, *pname; ++ struct inotify_event *event = (struct inotify_event *)evbuf; ++ struct cld_client *clnt = data; ++ ++ if (which != EV_READ) ++ return; ++ ++ xlog(D_GENERAL, "%s: called for EV_READ", __func__); ++ ++ dirc = strndup(pipepath, PATH_MAX); ++ if (!dirc) { ++ xlog(L_ERROR, "%s: unable to allocate memory", __func__); ++ goto out; ++ } ++ ++ rret = read(inotify_fd, evbuf, INOTIFY_EVENT_MAX); ++ if (rret < 0) { ++ xlog(L_ERROR, "%s: read from inotify fd failed: %m", __func__); ++ goto out; ++ } ++ ++ /* check to see if we have a filename in the evbuf */ ++ if (!event->len) { ++ xlog(D_GENERAL, "%s: no filename in inotify event", __func__); ++ goto out; ++ } ++ ++ pname = basename(dirc); ++ elen = strnlen(event->name, event->len); ++ ++ /* does the filename match our pipe? */ ++ if (strlen(pname) != elen || memcmp(pname, event->name, elen)) { ++ xlog(D_GENERAL, "%s: wrong filename (%s)", __func__, ++ event->name); ++ goto out; ++ } ++ ++ ret = cld_pipe_open(clnt); ++ switch (ret) { ++ case 0: ++ /* readd the event for the cl_event pipe */ ++ event_add(&clnt->cl_event, NULL); ++ break; ++ case -ENOENT: ++ /* pipe must have disappeared, wait for it to come back */ ++ goto out; ++ default: ++ /* anything else is fatal */ ++ xlog(L_FATAL, "%s: unable to open new pipe (%d). Aborting.", ++ __func__, ret); ++ exit(ret); ++ } ++ ++out: ++ event_add(&pipedir_event, NULL); ++ free(dirc); ++} ++ ++static int ++cld_inotify_setup(void) ++{ ++ int ret; ++ char *dirc, *dname; ++ ++ dirc = strndup(pipepath, PATH_MAX); ++ if (!dirc) { ++ xlog_err("%s: unable to allocate memory", __func__); ++ ret = -ENOMEM; ++ goto out_free; ++ } ++ ++ dname = dirname(dirc); ++ ++ inotify_fd = inotify_init(); ++ if (inotify_fd < 0) { ++ xlog_err("%s: inotify_init failed: %m", __func__); ++ ret = -errno; ++ goto out_free; ++ } ++ ++ ret = inotify_add_watch(inotify_fd, dname, IN_CREATE); ++ if (ret < 0) { ++ xlog_err("%s: inotify_add_watch failed: %m", __func__); ++ ret = -errno; ++ goto out_err; ++ } ++ ++out_free: ++ free(dirc); ++ return 0; ++out_err: ++ close(inotify_fd); ++ goto out_free; ++} ++ ++/* ++ * Set an inotify watch on the directory that should contain the pipe, and then ++ * try to open it. If it fails with anything but -ENOENT, return the error ++ * immediately. ++ * ++ * If it succeeds, then set up the pipe event handler. At that point, set up ++ * the inotify event handler and go ahead and return success. ++ */ ++static int ++cld_pipe_init(struct cld_client *clnt) ++{ ++ int ret; ++ ++ xlog(D_GENERAL, "%s: init pipe handlers", __func__); ++ ++ ret = cld_inotify_setup(); ++ if (ret != 0) ++ goto out; ++ ++ clnt->cl_fd = -1; ++ ret = cld_pipe_open(clnt); ++ switch (ret) { ++ case 0: ++ /* add the event and we're good to go */ ++ event_add(&clnt->cl_event, NULL); ++ break; ++ case -ENOENT: ++ /* ignore this error -- cld_inotify_cb will handle it */ ++ ret = 0; ++ break; ++ default: ++ /* anything else is fatal */ ++ close(inotify_fd); ++ goto out; ++ } ++ ++ /* set event for inotify read */ ++ event_set(&pipedir_event, inotify_fd, EV_READ, cld_inotify_cb, clnt); ++ event_add(&pipedir_event, NULL); ++out: ++ return ret; ++} ++ ++/* ++ * Older kernels will not tell nfsdcld when a grace period has started. ++ * Therefore we have to peek at the /proc/fs/nfsd/v4_end_grace file to ++ * see if nfsd is in grace. We have to do this for create and remove ++ * upcalls to ensure that the correct table is being updated - otherwise ++ * we could lose client records when the grace period is lifted. ++ */ ++static int ++cld_check_grace_period(void) ++{ ++ int fd, ret = 0; ++ char c; ++ ++ if (!old_kernel) ++ return 0; ++ if (recovery_epoch != 0) ++ return 0; ++ fd = open(NFSD_END_GRACE_FILE, O_RDONLY); ++ if (fd < 0) { ++ xlog(L_WARNING, "Unable to open %s: %m", ++ NFSD_END_GRACE_FILE); ++ return 1; ++ } ++ if (read(fd, &c, 1) < 0) { ++ xlog(L_WARNING, "Unable to read from %s: %m", ++ NFSD_END_GRACE_FILE); ++ return 1; ++ } ++ close(fd); ++ if (c == 'N') { ++ xlog(L_WARNING, "nfsd is in grace but didn't send a gracestart upcall, " ++ "please update the kernel"); ++ ret = sqlite_grace_start(); ++ } ++ return ret; ++} ++ ++#if UPCALL_VERSION >= 2 ++static ssize_t cld_message_size(void *msg) ++{ ++ struct cld_msg_hdr *hdr = (struct cld_msg_hdr *)msg; ++ ++ switch (hdr->cm_vers) { ++ case 1: ++ return sizeof(struct cld_msg); ++ case 2: ++ return sizeof(struct cld_msg_v2); ++ default: ++ xlog(L_FATAL, "%s invalid upcall version %d", __func__, ++ hdr->cm_vers); ++ exit(-EINVAL); ++ } ++} ++#else ++static ssize_t cld_message_size(void *UNUSED(msg)) ++{ ++ return sizeof(struct cld_msg); ++} ++#endif ++ ++static void ++cld_not_implemented(struct cld_client *clnt) ++{ ++ int ret; ++ ssize_t bsize, wsize; ++#if UPCALL_VERSION >= 2 ++ struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2; ++#else ++ struct cld_msg *cmsg = &clnt->cl_u.cl_msg; ++#endif ++ ++ xlog(D_GENERAL, "%s: downcalling with not implemented error", __func__); ++ ++ /* set up reply */ ++ cmsg->cm_status = -EOPNOTSUPP; ++ ++ bsize = cld_message_size(cmsg); ++ wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize); ++ if (wsize != bsize) ++ xlog(L_ERROR, "%s: problem writing to cld pipe (%zd): %m", ++ __func__, wsize); ++ ++ /* reopen pipe, just to be sure */ ++ ret = cld_pipe_open(clnt); ++ if (ret) { ++ xlog(L_FATAL, "%s: unable to reopen pipe: %d", __func__, ret); ++ exit(ret); ++ } ++} ++ ++static void ++cld_get_version(struct cld_client *clnt) ++{ ++ int ret; ++ ssize_t bsize, wsize; ++#if UPCALL_VERSION >= 2 ++ struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2; ++#else ++ struct cld_msg *cmsg = &clnt->cl_u.cl_msg; ++#endif ++ ++ xlog(D_GENERAL, "%s: version = %u.", __func__, UPCALL_VERSION); ++ ++ cmsg->cm_u.cm_version = UPCALL_VERSION; ++ cmsg->cm_status = 0; ++ ++ bsize = cld_message_size(cmsg); ++ xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status); ++ wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize); ++ if (wsize != bsize) { ++ xlog(L_ERROR, "%s: problem writing to cld pipe (%zd): %m", ++ __func__, wsize); ++ ret = cld_pipe_open(clnt); ++ if (ret) { ++ xlog(L_FATAL, "%s: unable to reopen pipe: %d", ++ __func__, ret); ++ exit(ret); ++ } ++ } ++} ++ ++static void ++cld_create(struct cld_client *clnt) ++{ ++ int ret; ++ ssize_t bsize, wsize; ++#if UPCALL_VERSION >= 2 ++ struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2; ++#else ++ struct cld_msg *cmsg = &clnt->cl_u.cl_msg; ++#endif ++ ++ ret = cld_check_grace_period(); ++ if (ret) ++ goto reply; ++ ++ xlog(D_GENERAL, "%s: create client record.", __func__); ++ ++#if UPCALL_VERSION >= 2 ++ if (cmsg->cm_vers >= 2) ++ ret = sqlite_insert_client_and_princhash( ++ cmsg->cm_u.cm_clntinfo.cc_name.cn_id, ++ cmsg->cm_u.cm_clntinfo.cc_name.cn_len, ++ cmsg->cm_u.cm_clntinfo.cc_princhash.cp_data, ++ cmsg->cm_u.cm_clntinfo.cc_princhash.cp_len); ++ else ++ ret = sqlite_insert_client(cmsg->cm_u.cm_name.cn_id, ++ cmsg->cm_u.cm_name.cn_len); ++#else ++ ret = sqlite_insert_client(cmsg->cm_u.cm_name.cn_id, ++ cmsg->cm_u.cm_name.cn_len); ++#endif ++ ++reply: ++ cmsg->cm_status = ret ? -EREMOTEIO : ret; ++ ++ bsize = cld_message_size(cmsg); ++ xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status); ++ wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize); ++ if (wsize != bsize) { ++ xlog(L_ERROR, "%s: problem writing to cld pipe (%zd): %m", ++ __func__, wsize); ++ ret = cld_pipe_open(clnt); ++ if (ret) { ++ xlog(L_FATAL, "%s: unable to reopen pipe: %d", ++ __func__, ret); ++ exit(ret); ++ } ++ } ++} ++ ++static void ++cld_remove(struct cld_client *clnt) ++{ ++ int ret; ++ ssize_t bsize, wsize; ++#if UPCALL_VERSION >= 2 ++ struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2; ++#else ++ struct cld_msg *cmsg = &clnt->cl_u.cl_msg; ++#endif ++ ++ ret = cld_check_grace_period(); ++ if (ret) ++ goto reply; ++ ++ xlog(D_GENERAL, "%s: remove client record.", __func__); ++ ++ ret = sqlite_remove_client(cmsg->cm_u.cm_name.cn_id, ++ cmsg->cm_u.cm_name.cn_len); ++ ++reply: ++ cmsg->cm_status = ret ? -EREMOTEIO : ret; ++ ++ bsize = cld_message_size(cmsg); ++ xlog(D_GENERAL, "%s: downcall with status %d", __func__, ++ cmsg->cm_status); ++ wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize); ++ if (wsize != bsize) { ++ xlog(L_ERROR, "%s: problem writing to cld pipe (%zd): %m", ++ __func__, wsize); ++ ret = cld_pipe_open(clnt); ++ if (ret) { ++ xlog(L_FATAL, "%s: unable to reopen pipe: %d", ++ __func__, ret); ++ exit(ret); ++ } ++ } ++} ++ ++static void ++cld_check(struct cld_client *clnt) ++{ ++ int ret; ++ ssize_t bsize, wsize; ++#if UPCALL_VERSION >= 2 ++ struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2; ++#else ++ struct cld_msg *cmsg = &clnt->cl_u.cl_msg; ++#endif ++ ++ /* ++ * If we get a check upcall at all, it means we're talking to an old ++ * kernel. Furthermore, if we're not in grace it means this is the ++ * first client to do a reclaim. Log a message and use ++ * sqlite_grace_start() to advance the epoch numbers. ++ */ ++ if (recovery_epoch == 0) { ++ xlog(D_GENERAL, "%s: received a check upcall, please update the kernel", ++ __func__); ++ ret = sqlite_grace_start(); ++ if (ret) ++ goto reply; ++ } ++ ++ xlog(D_GENERAL, "%s: check client record", __func__); ++ ++ ret = sqlite_check_client(cmsg->cm_u.cm_name.cn_id, ++ cmsg->cm_u.cm_name.cn_len); ++ ++reply: ++ /* set up reply */ ++ cmsg->cm_status = ret ? -EACCES : ret; ++ ++ bsize = cld_message_size(cmsg); ++ xlog(D_GENERAL, "%s: downcall with status %d", __func__, ++ cmsg->cm_status); ++ wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize); ++ if (wsize != bsize) { ++ xlog(L_ERROR, "%s: problem writing to cld pipe (%zd): %m", ++ __func__, wsize); ++ ret = cld_pipe_open(clnt); ++ if (ret) { ++ xlog(L_FATAL, "%s: unable to reopen pipe: %d", ++ __func__, ret); ++ exit(ret); ++ } ++ } ++} ++ ++static void ++cld_gracedone(struct cld_client *clnt) ++{ ++ int ret; ++ ssize_t bsize, wsize; ++#if UPCALL_VERSION >= 2 ++ struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2; ++#else ++ struct cld_msg *cmsg = &clnt->cl_u.cl_msg; ++#endif ++ ++ /* ++ * If we got a "gracedone" upcall while we're not in grace, then ++ * 1) we must be talking to an old kernel ++ * 2) no clients attempted to reclaim ++ * In that case, log a message and use sqlite_grace_start() to ++ * advance the epoch numbers, and then proceed as normal. ++ */ ++ if (recovery_epoch == 0) { ++ xlog(D_GENERAL, "%s: received gracedone upcall " ++ "while not in grace, please update the kernel", ++ __func__); ++ ret = sqlite_grace_start(); ++ if (ret) ++ goto reply; ++ } ++ ++ xlog(D_GENERAL, "%s: grace done.", __func__); ++ ++ ret = sqlite_grace_done(); ++ ++ if (first_time) { ++ if (num_cltrack_records > 0) ++ sqlite_delete_cltrack_records(); ++ if (num_legacy_records > 0) ++ legacy_clear_recdir(); ++ sqlite_first_time_done(); ++ first_time = 0; ++ } ++ ++reply: ++ /* set up reply: downcall with 0 status */ ++ cmsg->cm_status = ret ? -EREMOTEIO : ret; ++ ++ bsize = cld_message_size(cmsg); ++ xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status); ++ wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize); ++ if (wsize != bsize) { ++ xlog(L_ERROR, "%s: problem writing to cld pipe (%zd): %m", ++ __func__, wsize); ++ ret = cld_pipe_open(clnt); ++ if (ret) { ++ xlog(L_FATAL, "%s: unable to reopen pipe: %d", ++ __func__, ret); ++ exit(ret); ++ } ++ } ++} ++ ++static int ++gracestart_callback(struct cld_client *clnt) { ++ ssize_t bsize, wsize; ++#if UPCALL_VERSION >= 2 ++ struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2; ++#else ++ struct cld_msg *cmsg = &clnt->cl_u.cl_msg; ++#endif ++ ++ cmsg->cm_status = -EINPROGRESS; ++ ++ bsize = cld_message_size(cmsg); ++ xlog(D_GENERAL, "Sending client %.*s", ++ cmsg->cm_u.cm_name.cn_len, cmsg->cm_u.cm_name.cn_id); ++ wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize); ++ if (wsize != bsize) ++ return -EIO; ++ return 0; ++} ++ ++static void ++cld_gracestart(struct cld_client *clnt) ++{ ++ int ret; ++ ssize_t bsize, wsize; ++#if UPCALL_VERSION >= 2 ++ struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2; ++#else ++ struct cld_msg *cmsg = &clnt->cl_u.cl_msg; ++#endif ++ ++ xlog(D_GENERAL, "%s: updating grace epochs", __func__); ++ ++ ret = sqlite_grace_start(); ++ if (ret) ++ goto reply; ++ ++ xlog(D_GENERAL, "%s: sending client records to the kernel", __func__); ++ ++ ret = sqlite_iterate_recovery(&gracestart_callback, clnt); ++ ++reply: ++ /* set up reply: downcall with 0 status */ ++ cmsg->cm_status = ret ? -EREMOTEIO : ret; ++ ++ bsize = cld_message_size(cmsg); ++ xlog(D_GENERAL, "Doing downcall with status %d", cmsg->cm_status); ++ wsize = atomicio((void *)write, clnt->cl_fd, cmsg, bsize); ++ if (wsize != bsize) { ++ xlog(L_ERROR, "%s: problem writing to cld pipe (%zd): %m", ++ __func__, wsize); ++ ret = cld_pipe_open(clnt); ++ if (ret) { ++ xlog(L_FATAL, "%s: unable to reopen pipe: %d", ++ __func__, ret); ++ exit(ret); ++ } ++ } ++} ++ ++static void ++cldcb(int UNUSED(fd), short which, void *data) ++{ ++ ssize_t len; ++ struct cld_client *clnt = data; ++#if UPCALL_VERSION >= 2 ++ struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2; ++#else ++ struct cld_msg *cmsg = &clnt->cl_u.cl_msg; ++#endif ++ ++ if (which != EV_READ) ++ goto out; ++ ++ len = atomicio(read, clnt->cl_fd, cmsg, sizeof(*cmsg)); ++ if (len <= 0) { ++ xlog(L_ERROR, "%s: pipe read failed: %m", __func__); ++ cld_pipe_open(clnt); ++ goto out; ++ } ++ ++ if (cmsg->cm_vers > UPCALL_VERSION) { ++ xlog(L_ERROR, "%s: unsupported upcall version: %hu", ++ __func__, cmsg->cm_vers); ++ cld_pipe_open(clnt); ++ goto out; ++ } ++ ++ switch(cmsg->cm_cmd) { ++ case Cld_Create: ++ cld_create(clnt); ++ break; ++ case Cld_Remove: ++ cld_remove(clnt); ++ break; ++ case Cld_Check: ++ cld_check(clnt); ++ break; ++ case Cld_GraceDone: ++ cld_gracedone(clnt); ++ break; ++ case Cld_GraceStart: ++ cld_gracestart(clnt); ++ break; ++ case Cld_GetVersion: ++ cld_get_version(clnt); ++ break; ++ default: ++ xlog(L_WARNING, "%s: command %u is not yet implemented", ++ __func__, cmsg->cm_cmd); ++ cld_not_implemented(clnt); ++ } ++out: ++ event_add(&clnt->cl_event, NULL); ++} ++ ++int ++main(int argc, char **argv) ++{ ++ int arg; ++ int rc = 0; ++ bool foreground = false; ++ char *progname; ++ char *storagedir = CLD_DEFAULT_STORAGEDIR; ++ struct cld_client clnt; ++ char *s; ++ first_time = 0; ++ num_cltrack_records = 0; ++ num_legacy_records = 0; ++ ++ memset(&clnt, 0, sizeof(clnt)); ++ ++ progname = strdup(basename(argv[0])); ++ if (!progname) { ++ fprintf(stderr, "%s: unable to allocate memory.\n", argv[0]); ++ return 1; ++ } ++ ++ event_init(); ++ xlog_syslog(0); ++ xlog_stderr(1); ++ ++ conf_init_file(NFS_CONFFILE); ++ s = conf_get_str("general", "pipefs-directory"); ++ if (s) ++ strlcpy(pipefs_dir, s, sizeof(pipefs_dir)); ++ s = conf_get_str("nfsdcld", "storagedir"); ++ if (s) ++ storagedir = s; ++ rc = conf_get_num("nfsdcld", "debug", 0); ++ if (rc > 0) ++ xlog_config(D_ALL, 1); ++ ++ /* process command-line options */ ++ while ((arg = getopt_long(argc, argv, "hdFp:s:", longopts, ++ NULL)) != EOF) { ++ switch (arg) { ++ case 'd': ++ xlog_config(D_ALL, 1); ++ break; ++ case 'F': ++ foreground = true; ++ break; ++ case 'p': ++ strlcpy(pipefs_dir, optarg, sizeof(pipefs_dir)); ++ break; ++ case 's': ++ storagedir = optarg; ++ break; ++ default: ++ usage(progname); ++ return 0; ++ } ++ } ++ ++ strlcpy(pipepath, pipefs_dir, sizeof(pipepath)); ++ strlcat(pipepath, DEFAULT_CLD_PATH, sizeof(pipepath)); ++ ++ xlog_open(progname); ++ if (!foreground) { ++ xlog_syslog(1); ++ xlog_stderr(0); ++ rc = daemon(0, 0); ++ if (rc) { ++ xlog(L_ERROR, "Unable to daemonize: %m"); ++ goto out; ++ } ++ } ++ ++ /* drop all capabilities */ ++ rc = cld_set_caps(); ++ if (rc) ++ goto out; ++ ++ /* ++ * now see if the storagedir is writable by root w/o CAP_DAC_OVERRIDE. ++ * If it isn't then give the user a warning but proceed as if ++ * everything is OK. If the DB has already been created, then ++ * everything might still work. If it doesn't exist at all, then ++ * assume that the maindb init will be able to create it. Fail on ++ * anything else. ++ */ ++ if (access(storagedir, W_OK) == -1) { ++ switch (errno) { ++ case EACCES: ++ xlog(L_WARNING, "Storage directory %s is not writable. " ++ "Should be owned by root and writable " ++ "by owner!", storagedir); ++ break; ++ case ENOENT: ++ /* ignore and assume that we can create dir as root */ ++ break; ++ default: ++ xlog(L_ERROR, "Unexpected error when checking access " ++ "on %s: %m", storagedir); ++ rc = -errno; ++ goto out; ++ } ++ } ++ ++ if (linux_version_code() < MAKE_VERSION(4, 20, 0)) ++ old_kernel = true; ++ ++ /* set up storage db */ ++ rc = sqlite_prepare_dbh(storagedir); ++ if (rc) { ++ xlog(L_ERROR, "Failed to open main database: %d", rc); ++ goto out; ++ } ++ ++ /* set up event handler */ ++ rc = cld_pipe_init(&clnt); ++ if (rc) ++ goto out; ++ ++ xlog(D_GENERAL, "%s: Starting event dispatch handler.", __func__); ++ rc = event_dispatch(); ++ if (rc < 0) ++ xlog(L_ERROR, "%s: event_dispatch failed: %m", __func__); ++ ++ close(clnt.cl_fd); ++ close(inotify_fd); ++out: ++ free(progname); ++ return rc; ++} +diff --git a/utils/nfsdcld/nfsdcld.man b/utils/nfsdcld/nfsdcld.man +new file mode 100644 +index 00000000..4c2b1e80 +--- /dev/null ++++ b/utils/nfsdcld/nfsdcld.man +@@ -0,0 +1,221 @@ ++.\" Automatically generated by Pod::Man 2.22 (Pod::Simple 3.13) ++.\" ++.\" Standard preamble: ++.\" ======================================================================== ++.de Sp \" Vertical space (when we can't use .PP) ++.if t .sp .5v ++.if n .sp ++.. ++.de Vb \" Begin verbatim text ++.ft CW ++.nf ++.ne \\$1 ++.. ++.de Ve \" End verbatim text ++.ft R ++.fi ++.. ++.\" Set up some character translations and predefined strings. \*(-- will ++.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left ++.\" double quote, and \*(R" will give a right double quote. \*(C+ will ++.\" give a nicer C++. Capital omega is used to do unbreakable dashes and ++.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, ++.\" nothing in troff, for use with C<>. ++.tr \(*W- ++.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' ++.ie n \{\ ++. ds -- \(*W- ++. ds PI pi ++. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch ++. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch ++. ds L" "" ++. ds R" "" ++. ds C` "" ++. ds C' "" ++'br\} ++.el\{\ ++. ds -- \|\(em\| ++. ds PI \(*p ++. ds L" `` ++. ds R" '' ++'br\} ++.\" ++.\" Escape single quotes in literal strings from groff's Unicode transform. ++.ie \n(.g .ds Aq \(aq ++.el .ds Aq ' ++.\" ++.\" If the F register is turned on, we'll generate index entries on stderr for ++.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index ++.\" entries marked with X<> in POD. Of course, you'll have to process the ++.\" output yourself in some meaningful fashion. ++.ie \nF \{\ ++. de IX ++. tm Index:\\$1\t\\n%\t"\\$2" ++.. ++. nr % 0 ++. rr F ++.\} ++.el \{\ ++. de IX ++.. ++.\} ++.\" ++.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). ++.\" Fear. Run. Save yourself. No user-serviceable parts. ++. \" fudge factors for nroff and troff ++.if n \{\ ++. ds #H 0 ++. ds #V .8m ++. ds #F .3m ++. ds #[ \f1 ++. ds #] \fP ++.\} ++.if t \{\ ++. ds #H ((1u-(\\\\n(.fu%2u))*.13m) ++. ds #V .6m ++. ds #F 0 ++. ds #[ \& ++. ds #] \& ++.\} ++. \" simple accents for nroff and troff ++.if n \{\ ++. ds ' \& ++. ds ` \& ++. ds ^ \& ++. ds , \& ++. ds ~ ~ ++. ds / ++.\} ++.if t \{\ ++. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" ++. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' ++. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' ++. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' ++. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' ++. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' ++.\} ++. \" troff and (daisy-wheel) nroff accents ++.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' ++.ds 8 \h'\*(#H'\(*b\h'-\*(#H' ++.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] ++.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' ++.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' ++.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] ++.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] ++.ds ae a\h'-(\w'a'u*4/10)'e ++.ds Ae A\h'-(\w'A'u*4/10)'E ++. \" corrections for vroff ++.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' ++.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' ++. \" for low resolution devices (crt and lpr) ++.if \n(.H>23 .if \n(.V>19 \ ++\{\ ++. ds : e ++. ds 8 ss ++. ds o a ++. ds d- d\h'-1'\(ga ++. ds D- D\h'-1'\(hy ++. ds th \o'bp' ++. ds Th \o'LP' ++. ds ae ae ++. ds Ae AE ++.\} ++.rm #[ #] #H #V #F C ++.\" ======================================================================== ++.\" ++.IX Title "NFSDCLD 8" ++.TH NFSDCLD 8 "2011-12-21" "" "" ++.\" For nroff, turn off justification. Always turn off hyphenation; it makes ++.\" way too many mistakes in technical documents. ++.if n .ad l ++.nh ++.SH "NAME" ++nfsdcld \- NFSv4 Client Tracking Daemon ++.SH "SYNOPSIS" ++.IX Header "SYNOPSIS" ++nfsdcld [\-d] [\-F] [\-p path] [\-s stable storage dir] ++.SH "DESCRIPTION" ++.IX Header "DESCRIPTION" ++nfsdcld is the NFSv4 client tracking daemon. It is not necessary to run ++this daemon on machines that are not acting as NFSv4 servers. ++.PP ++When a network partition is combined with a server reboot, there are ++edge conditions that can cause the server to grant lock reclaims when ++other clients have taken conflicting locks in the interim. A more detailed ++explanation of this issue is described in \s-1RFC\s0 3530, section 8.6.3. ++.PP ++In order to prevent these problems, the server must track a small amount ++of per-client information on stable storage. This daemon provides the ++userspace piece of that functionality. ++.SH "OPTIONS" ++.IX Header "OPTIONS" ++.IP "\fB\-d\fR, \fB\-\-debug\fR" 4 ++.IX Item "-d, --debug" ++Enable debug level logging. ++.IP "\fB\-F\fR, \fB\-\-foreground\fR" 4 ++.IX Item "-F, --foreground" ++Runs the daemon in the foreground and prints all output to stderr ++.IP "\fB\-p\fR \fIpath\fR, \fB\-\-pipefsdir\fR=\fIpath\fR" 4 ++.IX Item "-p path, --pipefsdir=path" ++Location of the rpc_pipefs filesystem. The default value is ++\&\fI/var/lib/nfs/rpc_pipefs\fR. ++.IP "\fB\-s\fR \fIstorage_dir\fR, \fB\-\-storagedir\fR=\fIstorage_dir\fR" 4 ++.IX Item "-s storagedir, --storagedir=storage_dir" ++Directory where stable storage information should be kept. The default ++value is \fI/var/lib/nfs/nfsdcld\fR. ++.SH "CONFIGURATION FILE" ++.IX Header "CONFIGURATION FILE" ++The following values are recognized in the \fB[nfsdcld]\fR section ++of the \fI/etc/nfs.conf\fR configuration file: ++.IP "\fBstoragedir\fR" 4 ++.IX Item "storagedir" ++Equivalent to \fB\-s\fR/\fB\-\-storagedir\fR. ++.IP "\fBdebug\fR" 4 ++.IX Item "debug" ++Setting "debug = 1" is equivalent to \fB\-d\fR/\fB\-\-debug\fR. ++.LP ++In addition, the following value is recognized from the \fB[general]\fR section: ++.IP "\fBpipefs\-directory\fR" 4 ++.IX Item "pipefs-directory" ++Equivalent to \fB\-p\fR/\fB\-\-pipefsdir\fR. ++.SH "NOTES" ++.IX Header "NOTES" ++The Linux kernel NFSv4 server has historically tracked this information ++on stable storage by manipulating information on the filesystem ++directly, in the directory to which \fI/proc/fs/nfsd/nfsv4recoverydir\fR ++points. ++.PP ++This changed with the original introduction of \fBnfsdcld\fR upcall in kernel version 3.4, ++which was later deprecated in favor of the \fBnfsdcltrack\fR(8) usermodehelper ++program, support for which was added in kernel version 3.8. However, since the ++usermodehelper upcall does not work in containers, support for a new version of ++the \fBnfsdcld\fR upcall was added in kernel version 5.2. ++.PP ++This daemon requires a kernel that supports the \fBnfsdcld\fR upcall. On older kernels, if ++the legacy client name tracking code was in use, then the kernel would not create the ++pipe that \fBnfsdcld\fR uses to talk to the kernel. On newer kernels, nfsd attempts to ++initialize client tracking in the following order: First, the \fBnfsdcld\fR upcall. Second, ++the \fBnfsdcltrack\fR usermodehelper upcall. Finally, the legacy client tracking. ++.PP ++This daemon should be run as root, as the pipe that it uses to communicate ++with the kernel is only accessable by root. The daemon however does drop all ++superuser capabilities after starting. Because of this, the \fIstoragedir\fR ++should be owned by root, and be readable and writable by owner. ++.PP ++The daemon now supports different upcall versions to allow the kernel to pass additional ++data to be stored in the on-disk database. The kernel will query the supported upcall ++version from \fBnfsdcld\fR during client tracking initialization. A restart of \fBnfsd\fR is ++not necessary after upgrading \fBnfsdcld\fR, however \fBnfsd\fR will not use a later upcall ++version until restart. A restart of \fBnfsd is necessary\fR after downgrading \fBnfsdcld\fR, ++to ensure that \fBnfsd\fR does not use an upcall version that \fBnfsdcld\fR does not support. ++Additionally, a downgrade of \fBnfsdcld\fR requires the schema of the on-disk database to ++be downgraded as well. That can be accomplished using the \fBclddb-tool\fR(8) utility. ++.SH FILES ++.TP ++.B /var/lib/nfs/nfsdcld/main.sqlite ++.SH SEE ALSO ++.BR nfsdcltrack "(8), " clddb-tool (8) ++.SH "AUTHORS" ++.IX Header "AUTHORS" ++The nfsdcld daemon was developed by Jeff Layton ++with modifications from Scott Mayhew . +diff --git a/utils/nfsdcld/sqlite.c b/utils/nfsdcld/sqlite.c +new file mode 100644 +index 00000000..6666c867 +--- /dev/null ++++ b/utils/nfsdcld/sqlite.c +@@ -0,0 +1,1406 @@ ++/* ++ * Copyright (C) 2011 Red Hat, Jeff Layton ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version 2 ++ * of the License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, ++ * Boston, MA 02110-1301, USA. ++ */ ++ ++/* ++ * Explanation: ++ * ++ * This file contains the code to manage the sqlite backend database for the ++ * nfsdcld client tracking daemon. ++ * ++ * The main database is called main.sqlite and contains the following tables: ++ * ++ * parameters: simple key/value pairs for storing database info ++ * ++ * grace: a "current" column containing an INTEGER representing the current ++ * epoch (where should new values be stored) and a "recovery" column ++ * containing an INTEGER representing the recovery epoch (from what ++ * epoch are we allowed to recover). A recovery epoch of 0 means ++ * normal operation (grace period not in force). Note: sqlite stores ++ * integers as signed values, so these must be cast to a uint64_t when ++ * retrieving them from the database and back to an int64_t when storing ++ * them in the database. ++ * ++ * rec-CCCCCCCCCCCCCCCC (where C is the hex representation of the epoch value): ++ * an "id" column containing a BLOB with the long-form clientid ++ * as sent by the client, and a "princhash" column containing a BLOB ++ * with the sha256 hash of the kerberos principal (if available). ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include "config.h" ++#endif /* HAVE_CONFIG_H */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "xlog.h" ++#include "sqlite.h" ++#include "cld.h" ++#include "cld-internal.h" ++#include "conffile.h" ++#include "legacy.h" ++#include "nfslib.h" ++ ++#define CLD_SQLITE_LATEST_SCHEMA_VERSION 4 ++#define CLTRACK_DEFAULT_STORAGEDIR NFS_STATEDIR "/nfsdcltrack" ++ ++/* in milliseconds */ ++#define CLD_SQLITE_BUSY_TIMEOUT 10000 ++ ++/* private data structures */ ++ ++/* global variables */ ++static char *cltrack_storagedir = CLTRACK_DEFAULT_STORAGEDIR; ++ ++/* reusable pathname and sql command buffer */ ++static char buf[PATH_MAX]; ++ ++/* global database handle */ ++static sqlite3 *dbh; ++ ++/* forward declarations */ ++ ++/* make a directory, ignoring EEXIST errors unless it's not a directory */ ++static int ++mkdir_if_not_exist(const char *dirname) ++{ ++ int ret; ++ struct stat statbuf; ++ ++ ret = mkdir(dirname, S_IRWXU); ++ if (ret && errno != EEXIST) ++ return -errno; ++ ++ ret = stat(dirname, &statbuf); ++ if (ret) ++ return -errno; ++ ++ if (!S_ISDIR(statbuf.st_mode)) ++ ret = -ENOTDIR; ++ ++ return ret; ++} ++ ++static int ++sqlite_query_schema_version(void) ++{ ++ int ret; ++ sqlite3_stmt *stmt = NULL; ++ ++ /* prepare select query */ ++ ret = sqlite3_prepare_v2(dbh, ++ "SELECT value FROM parameters WHERE key == \"version\";", ++ -1, &stmt, NULL); ++ if (ret != SQLITE_OK) { ++ xlog(D_GENERAL, "Unable to prepare select statement: %s", ++ sqlite3_errmsg(dbh)); ++ ret = 0; ++ goto out; ++ } ++ ++ /* query schema version */ ++ ret = sqlite3_step(stmt); ++ if (ret != SQLITE_ROW) { ++ xlog(D_GENERAL, "Select statement execution failed: %s", ++ sqlite3_errmsg(dbh)); ++ ret = 0; ++ goto out; ++ } ++ ++ ret = sqlite3_column_int(stmt, 0); ++out: ++ sqlite3_finalize(stmt); ++ return ret; ++} ++ ++static int ++sqlite_query_first_time(int *first_time) ++{ ++ int ret; ++ sqlite3_stmt *stmt = NULL; ++ ++ /* prepare select query */ ++ ret = sqlite3_prepare_v2(dbh, ++ "SELECT value FROM parameters WHERE key == \"first_time\";", ++ -1, &stmt, NULL); ++ if (ret != SQLITE_OK) { ++ xlog(D_GENERAL, "Unable to prepare select statement: %s", ++ sqlite3_errmsg(dbh)); ++ goto out; ++ } ++ ++ /* query first_time */ ++ ret = sqlite3_step(stmt); ++ if (ret != SQLITE_ROW) { ++ xlog(D_GENERAL, "Select statement execution failed: %s", ++ sqlite3_errmsg(dbh)); ++ goto out; ++ } ++ ++ *first_time = sqlite3_column_int(stmt, 0); ++ ret = 0; ++out: ++ sqlite3_finalize(stmt); ++ return ret; ++} ++ ++static int ++sqlite_add_princ_col_cb(void *UNUSED(arg), int ncols, char **cols, ++ char **UNUSED(colnames)) ++{ ++ int ret; ++ char *err; ++ ++ if (ncols > 1) ++ return -EINVAL; ++ ret = snprintf(buf, sizeof(buf), "ALTER TABLE \"%s\" " ++ "ADD COLUMN princhash BLOB;", cols[0]); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ return -EINVAL; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret); ++ return -EINVAL; ++ } ++ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to add princhash column to table %s: %s", ++ cols[0], err); ++ goto out; ++ } ++ xlog(D_GENERAL, "Added princhash column to table %s", cols[0]); ++out: ++ sqlite3_free(err); ++ return ret; ++} ++ ++static int ++sqlite_maindb_update_v3_to_v4(void) ++{ ++ int ret; ++ char *err; ++ ++ ret = sqlite3_exec(dbh, "SELECT name FROM sqlite_master " ++ "WHERE type=\"table\" AND name LIKE \"%rec-%\";", ++ sqlite_add_princ_col_cb, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: Failed to update tables!: %s", __func__, err); ++ } ++ sqlite3_free(err); ++ return ret; ++} ++ ++static int ++sqlite_maindb_update_v1v2_to_v4(void) ++{ ++ int ret; ++ char *err; ++ ++ /* create grace table */ ++ ret = sqlite3_exec(dbh, "CREATE TABLE grace " ++ "(current INTEGER , recovery INTEGER);", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to create grace table: %s", err); ++ goto out; ++ } ++ ++ /* insert initial epochs into grace table */ ++ ret = sqlite3_exec(dbh, "INSERT OR FAIL INTO grace " ++ "values (1, 0);", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to set initial epochs: %s", err); ++ goto out; ++ } ++ ++ /* create recovery table for current epoch */ ++ ret = sqlite3_exec(dbh, "CREATE TABLE \"rec-0000000000000001\" " ++ "(id BLOB PRIMARY KEY, princhash BLOB);", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to create recovery table " ++ "for current epoch: %s", err); ++ goto out; ++ } ++ ++ /* copy records from old clients table */ ++ ret = sqlite3_exec(dbh, "INSERT INTO \"rec-0000000000000001\" (id) " ++ "SELECT id FROM clients;", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to copy client records: %s", err); ++ goto out; ++ } ++ ++ /* drop the old clients table */ ++ ret = sqlite3_exec(dbh, "DROP TABLE clients;", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to drop old clients table: %s", err); ++ } ++out: ++ sqlite3_free(err); ++ return ret; ++} ++ ++static int ++sqlite_maindb_update_schema(int oldversion) ++{ ++ int ret, ret2; ++ char *err; ++ ++ /* begin transaction */ ++ ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL, ++ &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to begin transaction: %s", err); ++ goto rollback; ++ } ++ ++ /* ++ * Check schema version again. This time, under an exclusive ++ * transaction to guard against racing DB setup attempts ++ */ ++ ret = sqlite_query_schema_version(); ++ if (ret != oldversion) { ++ if (ret == CLD_SQLITE_LATEST_SCHEMA_VERSION) ++ /* Someone else raced in and set it up */ ++ ret = 0; ++ else ++ /* Something went wrong -- fail! */ ++ ret = -EINVAL; ++ goto rollback; ++ } ++ ++ /* Still at old version -- do conversion */ ++ ++ switch (oldversion) { ++ case 3: ++ case 2: ++ ret = sqlite_maindb_update_v3_to_v4(); ++ break; ++ case 1: ++ ret = sqlite_maindb_update_v1v2_to_v4(); ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ if (ret != SQLITE_OK) ++ goto rollback; ++ ++ ret = snprintf(buf, sizeof(buf), "UPDATE parameters SET value = %d " ++ "WHERE key = \"version\";", ++ CLD_SQLITE_LATEST_SCHEMA_VERSION); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ goto rollback; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret); ++ ret = -EINVAL; ++ goto rollback; ++ } ++ ++ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to update schema version: %s", err); ++ goto rollback; ++ } ++ ++ ret = sqlite_query_first_time(&first_time); ++ if (ret != SQLITE_OK) { ++ /* insert first_time into parameters table */ ++ ret = sqlite3_exec(dbh, "INSERT OR FAIL INTO parameters " ++ "values (\"first_time\", \"1\");", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to insert into parameter table: %s", err); ++ goto rollback; ++ } ++ } ++ ++ ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to commit transaction: %s", err); ++ goto rollback; ++ } ++out: ++ sqlite3_free(err); ++ return ret; ++rollback: ++ ret2 = sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err); ++ if (ret2 != SQLITE_OK) ++ xlog(L_ERROR, "Unable to rollback transaction: %s", err); ++ goto out; ++} ++ ++/* ++ * Start an exclusive transaction and recheck the DB schema version. If it's ++ * still zero (indicating a new database) then set it up. If that all works, ++ * then insert schema version into the parameters table and commit the ++ * transaction. On any error, rollback the transaction. ++ */ ++static int ++sqlite_maindb_init_v4(void) ++{ ++ int ret, ret2; ++ char *err = NULL; ++ ++ /* Start a transaction */ ++ ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL, ++ &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to begin transaction: %s", err); ++ return ret; ++ } ++ ++ /* ++ * Check schema version again. This time, under an exclusive ++ * transaction to guard against racing DB setup attempts ++ */ ++ ret = sqlite_query_schema_version(); ++ switch (ret) { ++ case 0: ++ /* Query failed again -- set up DB */ ++ break; ++ case CLD_SQLITE_LATEST_SCHEMA_VERSION: ++ /* Someone else raced in and set it up */ ++ ret = 0; ++ goto rollback; ++ default: ++ /* Something went wrong -- fail! */ ++ ret = -EINVAL; ++ goto rollback; ++ } ++ ++ ret = sqlite3_exec(dbh, "CREATE TABLE parameters " ++ "(key TEXT PRIMARY KEY, value TEXT);", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to create parameter table: %s", err); ++ goto rollback; ++ } ++ ++ /* create grace table */ ++ ret = sqlite3_exec(dbh, "CREATE TABLE grace " ++ "(current INTEGER , recovery INTEGER);", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to create grace table: %s", err); ++ goto rollback; ++ } ++ ++ /* insert initial epochs into grace table */ ++ ret = sqlite3_exec(dbh, "INSERT OR FAIL INTO grace " ++ "values (1, 0);", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to set initial epochs: %s", err); ++ goto rollback; ++ } ++ ++ /* create recovery table for current epoch */ ++ ret = sqlite3_exec(dbh, "CREATE TABLE \"rec-0000000000000001\" " ++ "(id BLOB PRIMARY KEY, princhash BLOB);", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to create recovery table " ++ "for current epoch: %s", err); ++ goto rollback; ++ } ++ ++ /* insert version into parameters table */ ++ ret = snprintf(buf, sizeof(buf), "INSERT OR FAIL INTO parameters " ++ "values (\"version\", \"%d\");", ++ CLD_SQLITE_LATEST_SCHEMA_VERSION); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ goto rollback; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret); ++ ret = -EINVAL; ++ goto rollback; ++ } ++ ++ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to insert into parameter table: %s", err); ++ goto rollback; ++ } ++ ++ /* insert first_time into parameters table */ ++ ret = sqlite3_exec(dbh, "INSERT OR FAIL INTO parameters " ++ "values (\"first_time\", \"1\");", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to insert into parameter table: %s", err); ++ goto rollback; ++ } ++ ++ ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to commit transaction: %s", err); ++ goto rollback; ++ } ++out: ++ sqlite3_free(err); ++ return ret; ++ ++rollback: ++ /* Attempt to rollback the transaction */ ++ ret2 = sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err); ++ if (ret2 != SQLITE_OK) ++ xlog(L_ERROR, "Unable to rollback transaction: %s", err); ++ goto out; ++} ++ ++static int ++sqlite_startup_query_grace(void) ++{ ++ int ret; ++ uint64_t tcur; ++ uint64_t trec; ++ sqlite3_stmt *stmt = NULL; ++ ++ /* prepare select query */ ++ ret = sqlite3_prepare_v2(dbh, "SELECT * FROM grace;", -1, &stmt, NULL); ++ if (ret != SQLITE_OK) { ++ xlog(D_GENERAL, "Unable to prepare select statement: %s", ++ sqlite3_errmsg(dbh)); ++ goto out; ++ } ++ ++ ret = sqlite3_step(stmt); ++ if (ret != SQLITE_ROW) { ++ xlog(D_GENERAL, "Select statement execution failed: %s", ++ sqlite3_errmsg(dbh)); ++ goto out; ++ } ++ ++ tcur = (uint64_t)sqlite3_column_int64(stmt, 0); ++ trec = (uint64_t)sqlite3_column_int64(stmt, 1); ++ ++ current_epoch = tcur; ++ recovery_epoch = trec; ++ ret = 0; ++ xlog(D_GENERAL, "%s: current_epoch=%"PRIu64" recovery_epoch=%"PRIu64, ++ __func__, current_epoch, recovery_epoch); ++out: ++ sqlite3_finalize(stmt); ++ return ret; ++} ++ ++/* ++ * Helper for renaming a recovery table to fix the padding. ++ */ ++static int ++sqlite_fix_table_name(const char *name) ++{ ++ int ret; ++ uint64_t val; ++ char *err; ++ ++ if (sscanf(name, "rec-%" PRIx64, &val) != 1) ++ return -EINVAL; ++ ret = snprintf(buf, sizeof(buf), "ALTER TABLE \"%s\" " ++ "RENAME TO \"rec-%016" PRIx64 "\";", ++ name, val); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ return -EINVAL; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret); ++ return -EINVAL; ++ } ++ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to fix table for epoch %"PRIu64": %s", ++ val, err); ++ goto out; ++ } ++ xlog(D_GENERAL, "Renamed table %s to rec-%016" PRIx64, name, val); ++out: ++ sqlite3_free(err); ++ return ret; ++} ++ ++/* ++ * Callback for the sqlite_exec statement in sqlite_check_table_names. ++ * If the epoch encoded in the table name matches either the current ++ * epoch or the recovery epoch, then try to fix the padding. Otherwise, ++ * we bail. ++ */ ++static int ++sqlite_check_table_names_cb(void *UNUSED(arg), int ncols, char **cols, ++ char **UNUSED(colnames)) ++{ ++ int ret = SQLITE_OK; ++ uint64_t val; ++ ++ if (ncols > 1) ++ return -EINVAL; ++ if (sscanf(cols[0], "rec-%" PRIx64, &val) != 1) ++ return -EINVAL; ++ if (val == current_epoch || val == recovery_epoch) { ++ xlog(D_GENERAL, "found invalid table name %s for %s epoch", ++ cols[0], val == current_epoch ? "current" : "recovery"); ++ ret = sqlite_fix_table_name(cols[0]); ++ } else { ++ xlog(L_ERROR, "found invalid table name %s for unknown epoch %" ++ PRId64, cols[0], val); ++ return -EINVAL; ++ } ++ return ret; ++} ++ ++/* ++ * Look for recovery table names where the epoch isn't zero-padded ++ */ ++static int ++sqlite_check_table_names(void) ++{ ++ int ret; ++ char *err; ++ ++ ret = sqlite3_exec(dbh, "SELECT name FROM sqlite_master " ++ "WHERE type=\"table\" AND name LIKE \"%rec-%\" " ++ "AND length(name) < 20;", ++ sqlite_check_table_names_cb, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Table names check failed: %s", err); ++ } ++ sqlite3_free(err); ++ return ret; ++} ++ ++/* ++ * Simple db health check. For now we're just making sure that the recovery ++ * table names are of the format "rec-CCCCCCCCCCCCCCCC" (where C is the hex ++ * representation of the epoch value) and that epoch value matches either ++ * the current epoch or the recovery epoch. ++ */ ++static int ++sqlite_check_db_health(void) ++{ ++ int ret, ret2; ++ char *err; ++ ++ ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL, ++ &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to begin transaction: %s", err); ++ goto rollback; ++ } ++ ++ ret = sqlite_check_table_names(); ++ if (ret != SQLITE_OK) ++ goto rollback; ++ ++ ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to commit transaction: %s", err); ++ goto rollback; ++ } ++ ++cleanup: ++ sqlite3_free(err); ++ xlog(D_GENERAL, "%s: returning %d", __func__, ret); ++ return ret; ++rollback: ++ ret2 = sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err); ++ if (ret2 != SQLITE_OK) ++ xlog(L_ERROR, "Unable to rollback transaction: %s", err); ++ goto cleanup; ++} ++ ++static int ++sqlite_attach_db(const char *path) ++{ ++ int ret; ++ char dbpath[PATH_MAX]; ++ struct stat stb; ++ sqlite3_stmt *stmt = NULL; ++ ++ ret = snprintf(dbpath, PATH_MAX - 1, "%s/main.sqlite", path); ++ if (ret < 0) ++ return ret; ++ ++ dbpath[PATH_MAX - 1] = '\0'; ++ ret = stat(dbpath, &stb); ++ if (ret < 0) ++ return ret; ++ ++ xlog(D_GENERAL, "attaching %s", dbpath); ++ ret = sqlite3_prepare_v2(dbh, "ATTACH DATABASE ? AS attached;", ++ -1, &stmt, NULL); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: unable to prepare attach statement: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ return ret; ++ } ++ ++ ret = sqlite3_bind_text(stmt, 1, dbpath, strlen(dbpath), SQLITE_STATIC); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: bind text failed: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ return ret; ++ } ++ ++ ret = sqlite3_step(stmt); ++ if (ret == SQLITE_DONE) ++ ret = SQLITE_OK; ++ else ++ xlog(L_ERROR, "%s: unexpected return code from attach: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ ++ sqlite3_finalize(stmt); ++ stmt = NULL; ++ return ret; ++} ++ ++static int ++sqlite_detach_db(void) ++{ ++ int ret; ++ char *err = NULL; ++ ++ xlog(D_GENERAL, "detaching database"); ++ ret = sqlite3_exec(dbh, "DETACH DATABASE attached;", NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to detach attached db: %s", err); ++ } ++ ++ sqlite3_free(err); ++ return ret; ++} ++ ++/* ++ * Copies client records from the nfsdcltrack database as part of a one-time ++ * "upgrade". ++ * ++ * Returns a non-zero sqlite error code, or SQLITE_OK (aka 0). ++ * Returns the number of records copied via "num_rec". ++ */ ++static int ++sqlite_copy_cltrack_records(int *num_rec) ++{ ++ int ret, ret2; ++ char *s; ++ char *err = NULL; ++ sqlite3_stmt *stmt = NULL; ++ ++ s = conf_get_str("nfsdcltrack", "storagedir"); ++ if (s) ++ cltrack_storagedir = s; ++ ret = sqlite_attach_db(cltrack_storagedir); ++ if (ret) ++ goto out; ++ ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL, ++ &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to begin transaction: %s", err); ++ goto rollback; ++ } ++ ret = snprintf(buf, sizeof(buf), "DELETE FROM \"rec-%016" PRIx64 "\";", ++ current_epoch); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ goto rollback; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret); ++ ret = -EINVAL; ++ goto rollback; ++ } ++ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to clear records from current epoch: %s", err); ++ goto rollback; ++ } ++ ret = snprintf(buf, sizeof(buf), "INSERT INTO \"rec-%016" PRIx64 "\" (id) " ++ "SELECT id FROM attached.clients;", ++ current_epoch); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ goto rollback; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret); ++ ret = -EINVAL; ++ goto rollback; ++ } ++ ret = sqlite3_prepare_v2(dbh, buf, -1, &stmt, NULL); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: insert statement prepare failed: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ goto rollback; ++ } ++ ret = sqlite3_step(stmt); ++ if (ret != SQLITE_DONE) { ++ xlog(L_ERROR, "%s: unexpected return code from insert: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ goto rollback; ++ } ++ *num_rec = sqlite3_changes(dbh); ++ ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to commit transaction: %s", err); ++ goto rollback; ++ } ++cleanup: ++ sqlite3_finalize(stmt); ++ sqlite3_free(err); ++ sqlite_detach_db(); ++out: ++ xlog(D_GENERAL, "%s: returning %d", __func__, ret); ++ return ret; ++rollback: ++ *num_rec = 0; ++ ret2 = sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err); ++ if (ret2 != SQLITE_OK) ++ xlog(L_ERROR, "Unable to rollback transaction: %s", err); ++ goto cleanup; ++} ++ ++/* Open the database and set up the database handle for it */ ++int ++sqlite_prepare_dbh(const char *topdir) ++{ ++ int ret; ++ ++ /* Do nothing if the database handle is already set up */ ++ if (dbh) ++ return 0; ++ ++ ret = snprintf(buf, PATH_MAX - 1, "%s/main.sqlite", topdir); ++ if (ret < 0) ++ return ret; ++ ++ buf[PATH_MAX - 1] = '\0'; ++ ++ /* open a new DB handle */ ++ ret = sqlite3_open(buf, &dbh); ++ if (ret != SQLITE_OK) { ++ /* try to create the dir */ ++ ret = mkdir_if_not_exist(topdir); ++ if (ret) ++ goto out_close; ++ ++ /* retry open */ ++ ret = sqlite3_open(buf, &dbh); ++ if (ret != SQLITE_OK) ++ goto out_close; ++ } ++ ++ /* set busy timeout */ ++ ret = sqlite3_busy_timeout(dbh, CLD_SQLITE_BUSY_TIMEOUT); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to set sqlite busy timeout: %s", ++ sqlite3_errmsg(dbh)); ++ goto out_close; ++ } ++ ++ ret = sqlite_query_schema_version(); ++ switch (ret) { ++ case CLD_SQLITE_LATEST_SCHEMA_VERSION: ++ /* DB is already set up. Do nothing */ ++ ret = 0; ++ break; ++ case 3: ++ /* Old DB -- update to new schema */ ++ ret = sqlite_maindb_update_schema(3); ++ if (ret) ++ goto out_close; ++ break; ++ case 2: ++ /* Old DB -- update to new schema */ ++ ret = sqlite_maindb_update_schema(2); ++ if (ret) ++ goto out_close; ++ break; ++ ++ case 1: ++ /* Old DB -- update to new schema */ ++ ret = sqlite_maindb_update_schema(1); ++ if (ret) ++ goto out_close; ++ break; ++ case 0: ++ /* Query failed -- try to set up new DB */ ++ ret = sqlite_maindb_init_v4(); ++ if (ret) ++ goto out_close; ++ break; ++ default: ++ /* Unknown DB version -- downgrade? Fail */ ++ xlog(L_ERROR, "Unsupported database schema version! " ++ "Expected %d, got %d.", ++ CLD_SQLITE_LATEST_SCHEMA_VERSION, ret); ++ ret = -EINVAL; ++ goto out_close; ++ } ++ ++ ret = sqlite_startup_query_grace(); ++ ++ ret = sqlite_query_first_time(&first_time); ++ if (ret) ++ goto out_close; ++ ++ ret = sqlite_check_db_health(); ++ if (ret) { ++ xlog(L_ERROR, "Database health check failed! " ++ "Database must be fixed manually."); ++ goto out_close; ++ } ++ ++ /* one-time "upgrade" from older client tracking methods */ ++ if (first_time) { ++ sqlite_copy_cltrack_records(&num_cltrack_records); ++ xlog(D_GENERAL, "%s: num_cltrack_records = %d\n", ++ __func__, num_cltrack_records); ++ legacy_load_clients_from_recdir(&num_legacy_records); ++ xlog(D_GENERAL, "%s: num_legacy_records = %d\n", ++ __func__, num_legacy_records); ++ if (num_cltrack_records > 0 && num_legacy_records > 0) ++ xlog(L_WARNING, "%s: first-time upgrade detected " ++ "both cltrack and legacy records!\n", __func__); ++ } ++ ++ return ret; ++out_close: ++ sqlite3_close(dbh); ++ dbh = NULL; ++ return ret; ++} ++ ++/* ++ * Create a client record ++ * ++ * Returns a non-zero sqlite error code, or SQLITE_OK (aka 0) ++ */ ++int ++sqlite_insert_client(const unsigned char *clname, const size_t namelen) ++{ ++ int ret; ++ sqlite3_stmt *stmt = NULL; ++ ++ ret = snprintf(buf, sizeof(buf), "INSERT OR REPLACE INTO \"rec-%016" PRIx64 "\" (id) " ++ "VALUES (?);", current_epoch); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ return ret; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret); ++ return -EINVAL; ++ } ++ ++ ret = sqlite3_prepare_v2(dbh, buf, -1, &stmt, NULL); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: insert statement prepare failed: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ return ret; ++ } ++ ++ ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen, ++ SQLITE_STATIC); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: bind blob failed: %s", __func__, ++ sqlite3_errmsg(dbh)); ++ goto out_err; ++ } ++ ++ ret = sqlite3_step(stmt); ++ if (ret == SQLITE_DONE) ++ ret = SQLITE_OK; ++ else ++ xlog(L_ERROR, "%s: unexpected return code from insert: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ ++out_err: ++ xlog(D_GENERAL, "%s: returning %d", __func__, ret); ++ sqlite3_finalize(stmt); ++ return ret; ++} ++ ++#if UPCALL_VERSION >= 2 ++/* ++ * Create a client record including hash the kerberos principal ++ * ++ * Returns a non-zero sqlite error code, or SQLITE_OK (aka 0) ++ */ ++int ++sqlite_insert_client_and_princhash(const unsigned char *clname, const size_t namelen, ++ const unsigned char *clprinchash, const size_t princhashlen) ++{ ++ int ret; ++ sqlite3_stmt *stmt = NULL; ++ ++ if (princhashlen > 0) ++ ret = snprintf(buf, sizeof(buf), "INSERT OR REPLACE INTO \"rec-%016" PRIx64 "\" " ++ "VALUES (?, ?);", current_epoch); ++ else ++ ret = snprintf(buf, sizeof(buf), "INSERT OR REPLACE INTO \"rec-%016" PRIx64 "\" (id) " ++ "VALUES (?);", current_epoch); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ return ret; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret); ++ return -EINVAL; ++ } ++ ++ ret = sqlite3_prepare_v2(dbh, buf, -1, &stmt, NULL); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: insert statement prepare failed: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ return ret; ++ } ++ ++ ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen, ++ SQLITE_STATIC); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: bind blob failed: %s", __func__, ++ sqlite3_errmsg(dbh)); ++ goto out_err; ++ } ++ ++ if (princhashlen > 0) { ++ ret = sqlite3_bind_blob(stmt, 2, (const void *)clprinchash, princhashlen, ++ SQLITE_STATIC); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: bind blob failed: %s", __func__, ++ sqlite3_errmsg(dbh)); ++ goto out_err; ++ } ++ } ++ ++ ret = sqlite3_step(stmt); ++ if (ret == SQLITE_DONE) ++ ret = SQLITE_OK; ++ else ++ xlog(L_ERROR, "%s: unexpected return code from insert: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ ++out_err: ++ xlog(D_GENERAL, "%s: returning %d", __func__, ret); ++ sqlite3_finalize(stmt); ++ return ret; ++} ++#else ++int ++sqlite_insert_client_and_princhash(const unsigned char *clname, const size_t namelen, ++ const unsigned char *clprinchash, const size_t princhashlen) ++{ ++ return -EINVAL; ++} ++#endif ++ ++/* Remove a client record */ ++int ++sqlite_remove_client(const unsigned char *clname, const size_t namelen) ++{ ++ int ret; ++ sqlite3_stmt *stmt = NULL; ++ ++ ret = snprintf(buf, sizeof(buf), "DELETE FROM \"rec-%016" PRIx64 "\" " ++ "WHERE id==?;", current_epoch); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ return ret; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret); ++ return -EINVAL; ++ } ++ ++ ret = sqlite3_prepare_v2(dbh, buf, -1, &stmt, NULL); ++ ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: statement prepare failed: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ goto out_err; ++ } ++ ++ ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen, ++ SQLITE_STATIC); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: bind blob failed: %s", __func__, ++ sqlite3_errmsg(dbh)); ++ goto out_err; ++ } ++ ++ ret = sqlite3_step(stmt); ++ if (ret == SQLITE_DONE) ++ ret = SQLITE_OK; ++ else ++ xlog(L_ERROR, "%s: unexpected return code from delete: %d", ++ __func__, ret); ++ ++out_err: ++ xlog(D_GENERAL, "%s: returning %d", __func__, ret); ++ sqlite3_finalize(stmt); ++ return ret; ++} ++ ++/* ++ * Is the given clname in the clients table? If so, then update its timestamp ++ * and return success. If the record isn't present, or the update fails, then ++ * return an error. ++ */ ++int ++sqlite_check_client(const unsigned char *clname, const size_t namelen) ++{ ++ int ret; ++ sqlite3_stmt *stmt = NULL; ++ ++ ret = snprintf(buf, sizeof(buf), "SELECT count(*) FROM \"rec-%016" PRIx64 "\" " ++ "WHERE id==?;", recovery_epoch); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ return ret; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret); ++ return -EINVAL; ++ } ++ ++ ret = sqlite3_prepare_v2(dbh, buf, -1, &stmt, NULL); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: select statement prepare failed: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ return ret; ++ } ++ ++ ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen, ++ SQLITE_STATIC); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: bind blob failed: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ goto out_err; ++ } ++ ++ ret = sqlite3_step(stmt); ++ if (ret != SQLITE_ROW) { ++ xlog(L_ERROR, "%s: unexpected return code from select: %d", ++ __func__, ret); ++ goto out_err; ++ } ++ ++ ret = sqlite3_column_int(stmt, 0); ++ xlog(D_GENERAL, "%s: select returned %d rows", __func__, ret); ++ if (ret != 1) { ++ ret = -EACCES; ++ goto out_err; ++ } ++ ++ sqlite3_finalize(stmt); ++ ++ /* Now insert the client into the table for the current epoch */ ++ return sqlite_insert_client(clname, namelen); ++ ++out_err: ++ xlog(D_GENERAL, "%s: returning %d", __func__, ret); ++ sqlite3_finalize(stmt); ++ return ret; ++} ++ ++int ++sqlite_grace_start(void) ++{ ++ int ret, ret2; ++ char *err; ++ uint64_t tcur = current_epoch; ++ uint64_t trec = recovery_epoch; ++ ++ /* begin transaction */ ++ ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL, ++ &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to begin transaction: %s", err); ++ goto rollback; ++ } ++ ++ if (trec == 0) { ++ /* ++ * A normal grace start - update the epoch values in the grace ++ * table and create a new table for the current reboot epoch. ++ */ ++ trec = tcur; ++ tcur++; ++ ++ ret = snprintf(buf, sizeof(buf), "UPDATE grace " ++ "SET current = %" PRId64 ", recovery = %" PRId64 ";", ++ (int64_t)tcur, (int64_t)trec); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ goto rollback; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ++ ret); ++ ret = -EINVAL; ++ goto rollback; ++ } ++ ++ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to update epochs: %s", err); ++ goto rollback; ++ } ++ ++ ret = snprintf(buf, sizeof(buf), "CREATE TABLE \"rec-%016" PRIx64 "\" " ++ "(id BLOB PRIMARY KEY, princhash blob);", ++ tcur); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ goto rollback; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ++ ret); ++ ret = -EINVAL; ++ goto rollback; ++ } ++ ++ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to create table for current epoch: %s", ++ err); ++ goto rollback; ++ } ++ } else { ++ /* Server restarted while in grace - don't update the epoch ++ * values in the grace table, just clear out the records for ++ * the current reboot epoch. ++ */ ++ ret = snprintf(buf, sizeof(buf), "DELETE FROM \"rec-%016" PRIx64 "\";", ++ tcur); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ goto rollback; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret); ++ ret = -EINVAL; ++ goto rollback; ++ } ++ ++ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to clear table for current epoch: %s", ++ err); ++ goto rollback; ++ } ++ } ++ ++ ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to commit transaction: %s", err); ++ goto rollback; ++ } ++ ++ current_epoch = tcur; ++ recovery_epoch = trec; ++ xlog(D_GENERAL, "%s: current_epoch=%"PRIu64" recovery_epoch=%"PRIu64, ++ __func__, current_epoch, recovery_epoch); ++ ++out: ++ sqlite3_free(err); ++ return ret; ++rollback: ++ ret2 = sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err); ++ if (ret2 != SQLITE_OK) ++ xlog(L_ERROR, "Unable to rollback transaction: %s", err); ++ goto out; ++} ++ ++int ++sqlite_grace_done(void) ++{ ++ int ret, ret2; ++ char *err; ++ ++ /* begin transaction */ ++ ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL, ++ &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to begin transaction: %s", err); ++ goto rollback; ++ } ++ ++ ret = sqlite3_exec(dbh, "UPDATE grace SET recovery = \"0\";", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to clear recovery epoch: %s", err); ++ goto rollback; ++ } ++ ++ ret = snprintf(buf, sizeof(buf), "DROP TABLE \"rec-%016" PRIx64 "\";", ++ recovery_epoch); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ goto rollback; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret); ++ ret = -EINVAL; ++ goto rollback; ++ } ++ ++ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to drop table for recovery epoch: %s", ++ err); ++ goto rollback; ++ } ++ ++ ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to commit transaction: %s", err); ++ goto rollback; ++ } ++ ++ recovery_epoch = 0; ++ xlog(D_GENERAL, "%s: current_epoch=%"PRIu64" recovery_epoch=%"PRIu64, ++ __func__, current_epoch, recovery_epoch); ++ ++out: ++ sqlite3_free(err); ++ return ret; ++rollback: ++ ret2 = sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err); ++ if (ret2 != SQLITE_OK) ++ xlog(L_ERROR, "Unable to rollback transaction: %s", err); ++ goto out; ++} ++ ++ ++int ++sqlite_iterate_recovery(int (*cb)(struct cld_client *clnt), struct cld_client *clnt) ++{ ++ int ret; ++ sqlite3_stmt *stmt = NULL; ++#if UPCALL_VERSION >= 2 ++ struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2; ++#else ++ struct cld_msg *cmsg = &clnt->cl_u.cl_msg; ++#endif ++ ++ if (recovery_epoch == 0) { ++ xlog(D_GENERAL, "%s: not in grace!", __func__); ++ return -EINVAL; ++ } ++ ++ ret = snprintf(buf, sizeof(buf), "SELECT * FROM \"rec-%016" PRIx64 "\";", ++ recovery_epoch); ++ if (ret < 0) { ++ xlog(L_ERROR, "sprintf failed!"); ++ return ret; ++ } else if ((size_t)ret >= sizeof(buf)) { ++ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret); ++ return -EINVAL; ++ } ++ ++ ret = sqlite3_prepare_v2(dbh, buf, -1, &stmt, NULL); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "%s: select statement prepare failed: %s", ++ __func__, sqlite3_errmsg(dbh)); ++ return ret; ++ } ++ ++ while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) { ++ memset(&cmsg->cm_u, 0, sizeof(cmsg->cm_u)); ++#if UPCALL_VERSION >= 2 ++ memcpy(&cmsg->cm_u.cm_clntinfo.cc_name.cn_id, ++ sqlite3_column_blob(stmt, 0), NFS4_OPAQUE_LIMIT); ++ cmsg->cm_u.cm_clntinfo.cc_name.cn_len = sqlite3_column_bytes(stmt, 0); ++ if (sqlite3_column_bytes(stmt, 1) > 0) { ++ memcpy(&cmsg->cm_u.cm_clntinfo.cc_princhash.cp_data, ++ sqlite3_column_blob(stmt, 1), SHA256_DIGEST_SIZE); ++ cmsg->cm_u.cm_clntinfo.cc_princhash.cp_len = sqlite3_column_bytes(stmt, 1); ++ } ++#else ++ memcpy(&cmsg->cm_u.cm_name.cn_id, sqlite3_column_blob(stmt, 0), ++ NFS4_OPAQUE_LIMIT); ++ cmsg->cm_u.cm_name.cn_len = sqlite3_column_bytes(stmt, 0); ++#endif ++ cb(clnt); ++ } ++ if (ret == SQLITE_DONE) ++ ret = 0; ++ sqlite3_finalize(stmt); ++ return ret; ++} ++ ++/* ++ * Cleans out the old nfsdcltrack database. ++ * ++ * Called upon receipt of the first "GraceDone" upcall only. ++ */ ++int ++sqlite_delete_cltrack_records(void) ++{ ++ int ret; ++ char *s; ++ char *err = NULL; ++ ++ s = conf_get_str("nfsdcltrack", "storagedir"); ++ if (s) ++ cltrack_storagedir = s; ++ ret = sqlite_attach_db(cltrack_storagedir); ++ if (ret) ++ goto out; ++ ret = sqlite3_exec(dbh, "DELETE FROM attached.clients;", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) { ++ xlog(L_ERROR, "Unable to clear records from cltrack db: %s", ++ err); ++ } ++ sqlite_detach_db(); ++out: ++ sqlite3_free(err); ++ return ret; ++} ++ ++/* ++ * Sets first_time to 0 in the parameters table to ensure we only ++ * copy old client tracking records into the database one time. ++ * ++ * Called upon receipt of the first "GraceDone" upcall only. ++ */ ++int ++sqlite_first_time_done(void) ++{ ++ int ret; ++ char *err = NULL; ++ ++ ret = sqlite3_exec(dbh, "UPDATE parameters SET value = \"0\" " ++ "WHERE key = \"first_time\";", ++ NULL, NULL, &err); ++ if (ret != SQLITE_OK) ++ xlog(L_ERROR, "Unable to clear first_time: %s", err); ++ ++ sqlite3_free(err); ++ return ret; ++} +diff --git a/utils/nfsdcld/sqlite.h b/utils/nfsdcld/sqlite.h +new file mode 100644 +index 00000000..0a26ad67 +--- /dev/null ++++ b/utils/nfsdcld/sqlite.h +@@ -0,0 +1,37 @@ ++/* ++ * Copyright (C) 2011 Red Hat, Jeff Layton ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version 2 ++ * of the License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, ++ * Boston, MA 02110-1301, USA. ++ */ ++ ++#ifndef _SQLITE_H_ ++#define _SQLITE_H_ ++ ++struct cld_client; ++ ++int sqlite_prepare_dbh(const char *topdir); ++int sqlite_insert_client(const unsigned char *clname, const size_t namelen); ++int sqlite_insert_client_and_princhash(const unsigned char *clname, const size_t namelen, ++ const unsigned char *clprinchash, const size_t princhashlen); ++int sqlite_remove_client(const unsigned char *clname, const size_t namelen); ++int sqlite_check_client(const unsigned char *clname, const size_t namelen); ++int sqlite_grace_start(void); ++int sqlite_grace_done(void); ++int sqlite_iterate_recovery(int (*cb)(struct cld_client *clnt), struct cld_client *clnt); ++int sqlite_delete_cltrack_records(void); ++int sqlite_first_time_done(void); ++ ++#endif /* _SQLITE_H */ +diff --git a/utils/nfsidmap/nfsidmap.c b/utils/nfsidmap/nfsidmap.c +index d3967a3a..4d219ef5 100644 +--- a/utils/nfsidmap/nfsidmap.c ++++ b/utils/nfsidmap/nfsidmap.c +@@ -18,7 +18,7 @@ + #include "xcommon.h" + + int verbose = 0; +-char *usage = "Usage: %s [-vh] [-c || [-u|-g|-r key] || -d || -l || [-t timeout] key desc]"; ++#define USAGE "Usage: %s [-vh] [-c || [-u|-g|-r key] || -d || -l || [-t timeout] key desc]" + + #define MAX_ID_LEN 11 + #define IDMAP_NAMESZ 128 +@@ -401,7 +401,7 @@ int main(int argc, char **argv) + break; + case 'h': + default: +- xlog_warn(usage, progname); ++ xlog_warn(USAGE, progname); + exit(opt == 'h' ? 0 : 1); + } + } +@@ -433,7 +433,7 @@ int main(int argc, char **argv) + xlog_stderr(verbose); + if ((argc - optind) != 2) { + xlog_warn("Bad arg count. Check /etc/request-key.conf"); +- xlog_warn(usage, progname); ++ xlog_warn(USAGE, progname); + return EXIT_FAILURE; + } + +@@ -451,7 +451,7 @@ int main(int argc, char **argv) + return EXIT_FAILURE; + } + if (verbose) { +- xlog_warn("key: 0x%lx type: %s value: %s timeout %ld", ++ xlog_warn("key: 0x%x type: %s value: %s timeout %d", + key, type, value, timeout); + } + +diff --git a/utils/statd/rmtcall.c b/utils/statd/rmtcall.c +index c4f6364f..5b261480 100644 +--- a/utils/statd/rmtcall.c ++++ b/utils/statd/rmtcall.c +@@ -247,7 +247,7 @@ process_reply(FD_SET_TYPE *rfds) + xlog_warn("%s: service %d not registered on localhost", + __func__, NL_MY_PROG(lp)); + } else { +- xlog(D_GENERAL, "%s: Callback to %s (for %d) succeeded", ++ xlog(D_GENERAL, "%s: Callback to %s (for %s) succeeded", + __func__, NL_MY_NAME(lp), NL_MON_NAME(lp)); + } + nlist_free(¬ify, lp); +diff --git a/utils/statd/statd.c b/utils/statd/statd.c +index 14673800..8eef2ff2 100644 +--- a/utils/statd/statd.c ++++ b/utils/statd/statd.c +@@ -136,7 +136,7 @@ static void log_modes(void) + strcat(buf, "TI-RPC "); + #endif + +- xlog_warn(buf); ++ xlog_warn("%s", buf); + } + + /* +diff --git a/utils/statd/svc_run.c b/utils/statd/svc_run.c +index d1dbd74a..e343c768 100644 +--- a/utils/statd/svc_run.c ++++ b/utils/statd/svc_run.c +@@ -53,6 +53,7 @@ + + #include + #include ++#include + #include "statd.h" + #include "notlist.h" + +@@ -104,8 +105,8 @@ my_svc_run(int sockfd) + + tv.tv_sec = NL_WHEN(notify) - now; + tv.tv_usec = 0; +- xlog(D_GENERAL, "Waiting for reply... (timeo %d)", +- tv.tv_sec); ++ xlog(D_GENERAL, "Waiting for reply... (timeo %jd)", ++ (intmax_t)tv.tv_sec); + selret = select(FD_SETSIZE, &readfds, + (void *) 0, (void *) 0, &tv); + } else { diff --git a/SOURCES/nfs-utils-2.3.3-nfsdclddb-rename.patch b/SOURCES/nfs-utils-2.3.3-nfsdclddb-rename.patch new file mode 100644 index 0000000..6d47391 --- /dev/null +++ b/SOURCES/nfs-utils-2.3.3-nfsdclddb-rename.patch @@ -0,0 +1,130 @@ +commit 77d053e4881664e7dbbc3bbb9a242af005598e95 +Author: Steve Dickson +Date: Wed May 13 12:22:41 2020 -0400 + + nfsdclddb: Redname clddb-tool to nfsdclddb + + To try to maintain some type of name convention + rename clddb-tool to nfsdclddb + + Signed-off-by: Steve Dickson + +diff --git a/configure.ac b/configure.ac +index df88e58..0b1c8cc 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -695,7 +695,7 @@ AC_CONFIG_FILES([ + tools/mountstats/Makefile + tools/nfs-iostat/Makefile + tools/nfsconf/Makefile +- tools/clddb-tool/Makefile ++ tools/nfsdclddb/Makefile + utils/Makefile + utils/blkmapd/Makefile + utils/nfsdcld/Makefile +diff --git a/tools/Makefile.am b/tools/Makefile.am +index 53e6117..432d35d 100644 +--- a/tools/Makefile.am ++++ b/tools/Makefile.am +@@ -9,7 +9,7 @@ endif + OPTDIRS += nfsconf + + if CONFIG_NFSDCLD +-OPTDIRS += clddb-tool ++OPTDIRS += nfsdclddb + endif + + SUBDIRS = locktest rpcdebug nlmtest mountstats nfs-iostat $(OPTDIRS) +diff --git a/tools/clddb-tool/Makefile.am b/tools/nfsdclddb/Makefile.am +similarity index 60% +rename from tools/clddb-tool/Makefile.am +rename to tools/nfsdclddb/Makefile.am +index 15a8fd4..18263fb 100644 +--- a/tools/clddb-tool/Makefile.am ++++ b/tools/nfsdclddb/Makefile.am +@@ -1,13 +1,13 @@ + ## Process this file with automake to produce Makefile.in +-PYTHON_FILES = clddb-tool.py ++PYTHON_FILES = nfsdclddb.py + +-man8_MANS = clddb-tool.man ++man8_MANS = nfsdclddb.man + + EXTRA_DIST = $(man8_MANS) $(PYTHON_FILES) + + all-local: $(PYTHON_FILES) + + install-data-hook: +- $(INSTALL) -m 755 clddb-tool.py $(DESTDIR)$(sbindir)/clddb-tool ++ $(INSTALL) -m 755 nfsdclddb.py $(DESTDIR)$(sbindir)/nfsdclddb + + MAINTAINERCLEANFILES=Makefile.in +diff --git a/tools/clddb-tool/clddb-tool.man b/tools/nfsdclddb/nfsdclddb.man +similarity index 84% +rename from tools/clddb-tool/clddb-tool.man +rename to tools/nfsdclddb/nfsdclddb.man +index e80b2c0..8ec7b18 100644 +--- a/tools/clddb-tool/clddb-tool.man ++++ b/tools/nfsdclddb/nfsdclddb.man +@@ -1,20 +1,20 @@ + .\" +-.\" clddb-tool(8) ++.\" nfsdclddb(8) + .\" +-.TH clddb-tool 8 "07 Aug 2019" ++.TH nfsdclddb 8 "07 Aug 2019" + .SH NAME +-clddb-tool \- Tool for manipulating the nfsdcld sqlite database ++nfsdclddb \- Tool for manipulating the nfsdcld sqlite database + .SH SYNOPSIS +-.B clddb-tool ++.B nfsdclddb + .RB [ \-h | \-\-help ] + .P +-.B clddb-tool ++.B nfsdclddb + .RB [ \-p | \-\-path + .IR dbpath ] + .B fix-table-names + .RB [ \-h | \-\-help ] + .P +-.B clddb-tool ++.B nfsdclddb + .RB [ \-p | \-\-path + .IR dbpath ] + .B downgrade-schema +@@ -22,7 +22,7 @@ clddb-tool \- Tool for manipulating the nfsdcld sqlite database + .RB [ \-v | \-\-version + .IR to-version ] + .P +-.B clddb-tool ++.B nfsdclddb + .RB [ \-p | \-\-path + .IR dbpath ] + .B print +@@ -31,10 +31,10 @@ clddb-tool \- Tool for manipulating the nfsdcld sqlite database + .P + + .SH DESCRIPTION +-.RB "The " clddb-tool " command is provided to perform some manipulation of the nfsdcld sqlite database schema and to print the contents of the database." ++.RB "The " nfsdclddb " command is provided to perform some manipulation of the nfsdcld sqlite database schema and to print the contents of the database." + .SS Sub-commands + Valid +-.B clddb-tool ++.B nfsdclddb + subcommands are: + .IP "\fBfix-table-names\fP" + .RB "A previous version of " nfsdcld "(8) contained a bug that corrupted the reboot epoch table names. This sub-command will fix those table names." +@@ -66,7 +66,7 @@ The schema version to downgrade to. Currently the schema can only be downgraded + Do not list the clients in the reboot epoch tables in the output. + .SH NOTES + The +-.B clddb-tool ++.B nfsdclddb + command will not allow the + .B fix-table-names + or +diff --git a/tools/clddb-tool/clddb-tool.py b/tools/nfsdclddb/nfsdclddb.py +similarity index 100% +rename from tools/clddb-tool/clddb-tool.py +rename to tools/nfsdclddb/nfsdclddb.py diff --git a/SPECS/nfs-utils.spec b/SPECS/nfs-utils.spec index 9d749fa..04d3e5d 100644 --- a/SPECS/nfs-utils.spec +++ b/SPECS/nfs-utils.spec @@ -2,7 +2,7 @@ Summary: NFS utilities and supporting clients and daemons for the kernel NFS ser Name: nfs-utils URL: http://linux-nfs.org/ Version: 2.3.3 -Release: 28%{?dist} +Release: 35%{?dist} Epoch: 1 # group all 32bit related archs @@ -49,10 +49,21 @@ Patch019: nfs-utils-2.3.3-nfsiostat-err-cnts.patch Patch020: nfs-utils-2.3.3-gssd-man-verbose.patch Patch021: nfs-utils-2.3.3-nfsconf-rdmaport.patch Patch022: nfs-utils-2.3.3-gssd-early-daemon.patch +Patch023: nfs-utils-2.3.3-covscan-rm-deadcode-leaks.patch +Patch024: nfs-utils-2.3.3-gssd-memoryleak.patch + +# +# RHEL 8.3 +# +Patch025: nfs-utils-2.3.3-junction-err-fix.patch +Patch026: nfs-utils-2.3.3-nfsdcld-upstream-update.patch +Patch027: nfs-utils-2.3.3-nconnect-manpage.patch +Patch028: nfs-utils-2.3.3-nfsdclddb-rename.patch +Patch029: nfs-utils-2.3.3-nfsclnts-cmd.patch Patch100: nfs-utils-1.2.1-statdpath-man.patch Patch101: nfs-utils-1.2.1-exp-subtree-warn-off.patch -Patch102: nfs-utils-1.2.5-idmap-errmsg.patch +Patch102: nfs-utils-2.3.3-idmap-errmsg.patch Patch103: nfs-utils-2.3.1-systemd-gssproxy-restart.patch Patch104: nfs-utils-2.3.1-systemd-svcgssd-removed.patch @@ -74,7 +85,7 @@ Provides: start-statd = %{epoch}:%{version}-%{release} License: MIT and GPLv2 and GPLv2+ and BSD Requires: rpcbind, sed, gawk, grep -Requires: kmod, keyutils, quota +Requires: kmod, keyutils, quota, python3-pyyaml BuildRequires: libevent-devel libcap-devel BuildRequires: libtirpc-devel libblkid-devel BuildRequires: krb5-libs >= 1.4 autoconf >= 2.57 openldap-devel >= 2.2 @@ -310,6 +321,9 @@ fi %{_sbindir}/nfsconf %{_sbindir}/nfsref %{_sbindir}/nfsconvert +%{_sbindir}/nfsdclddb +%{_sbindir}/nfsdcld +%{_sbindir}/nfsdclnts %{_mandir}/*/* %{_pkgdir}/*/* @@ -333,6 +347,29 @@ fi %{_libdir}/libnfsidmap.so %changelog +* Wed Jun 10 2020 Steve Dickson 2.3.3-35 +- Fix dependency problems with nfsdclnts (bz 1841502) + +* Tue Jun 9 2020 Steve Dickson 2.3.3-34 +- New nfsdclnts command added (bz 1841502) + +* Mon May 18 2020 Steve Dickson 2.3.3-33 +- manpage: Add a description of the 'nconnect' mount option (bz 1761352) +- nfsdclddb: Redname clddb-tool to nfsdclddb (bz 1836924) + +* Wed May 6 2020 Steve Dickson 2.3.3-32 +- junction: Fixed debug statement (bz 1831829) +- Userspace support for the latest nfsdcld daemon (bz 1817756) + +* Fri Mar 6 2020 Steve Dickson 2.3.3-31 +- gssd: Closed a memory leak in find_keytab_entry() (bz 1809277) + +* Fri Feb 7 2020 Steve Dickson 2.3.3-30 +- Removed dead code that was flagged by a covscan (bz 1746572) + +* Thu Jan 16 2020 Steve Dickson 2.3.3-29 +- statd: Fix permission denied error path (bz 1776096) + * Tue Nov 26 2019 Steve Dickson 2.3.3-28 - gssd: daemonize earlier (bz 1762847)