482 lines
16 KiB
Diff
482 lines
16 KiB
Diff
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 <agaikwad@redhat.com> and
|
|
+Kenneth D'souza <kdsouza@redhat.com>
|
|
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 <agaikwad@redhat.com>
|
|
+ Kenneth D'souza <kdsouza@redhat.com>
|
|
+
|
|
+ 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 <https://www.gnu.org/licenses/>.
|
|
+'''
|
|
+
|
|
+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()
|