From 42ab42acfe6f99858ed213af7b55a9123c4b5ca6 Mon Sep 17 00:00:00 2001 From: mgrimesix <126630154+mgrimesix@users.noreply.github.com> Date: Tue, 31 Dec 2024 13:26:07 -0800 Subject: [PATCH] NAS-131762 / 25.04 / Add private NFS method to clear rmtab or selected entries. (#15276) Make call to clear rmtab from nfs.setup_directories. Cannot use middleware setup hook because /var/db/system/nfs is not yet available at that point. --- src/middlewared/middlewared/plugins/nfs.py | 6 +++ .../middlewared/plugins/nfs_/status.py | 44 ++++++++++++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/middlewared/middlewared/plugins/nfs.py b/src/middlewared/middlewared/plugins/nfs.py index 8f3d3c2e66fc1..b673c85b0bf65 100644 --- a/src/middlewared/middlewared/plugins/nfs.py +++ b/src/middlewared/middlewared/plugins/nfs.py @@ -188,6 +188,12 @@ def setup_directories(self): except Exception: self.logger.error('Unexpected failure initializing %r', path, exc_info=True) + # Clear rmtab on boot. + # We call this here because /var/db/system/nfs is not yet available + # in a middleware 'setup' hook. See NAS-131762 + if not self.middleware.call_sync('system.ready'): + self.middleware.call_sync('nfs.clear_nfs3_rmtab') + @private async def nfs_extend(self, nfs): keytab_has_nfs = await self.middleware.call("kerberos.keytab.has_nfs_principal") diff --git a/src/middlewared/middlewared/plugins/nfs_/status.py b/src/middlewared/middlewared/plugins/nfs_/status.py index cfe8dda470114..f7a73c957a374 100644 --- a/src/middlewared/middlewared/plugins/nfs_/status.py +++ b/src/middlewared/middlewared/plugins/nfs_/status.py @@ -1,12 +1,13 @@ +import os +import tempfile +import yaml + +from contextlib import suppress from middlewared.plugins.nfs import NFSServicePathInfo from middlewared.schema import accepts, Int, returns, Str, Dict from middlewared.service import Service, private, filterable, filterable_returns -from middlewared.utils import filter_list from middlewared.service_exception import CallError -from contextlib import suppress - -import yaml -import os +from middlewared.utils import filter_list class NFSService(Service): @@ -31,6 +32,39 @@ def get_rmtab(self): return entries + @private + def clear_nfs3_rmtab(self, ip_to_clear=["all"]): + """ + Clear some or all NFSv3 client entries in rmtab. + rmtab can become clogged with stale entries, this provides a + method to clear all or selected entries. + Optional input: list of ip to remove, e.g. ip_to_clear=["a.b.c.d"] + DEFAULT: Clear all entries + """ + rmtab = os.path.join(NFSServicePathInfo.STATEDIR.path(), "rmtab") + with suppress(FileNotFoundError): + # Handle default: clear all + if "all" in ip_to_clear: + with open(rmtab, "w"): + # Use 'w' open to truncate + pass + else: + # Replace rmtab excluding ip_to_clear + with tempfile.NamedTemporaryFile( + mode='wt', + dir=NFSServicePathInfo.STATEDIR.path(), + delete=False + ) as outf: + with open(rmtab, "r") as inf: + for entry in inf: + if entry.split(':')[0] in ip_to_clear: + continue + else: + outf.write(entry) + outf.flush() + os.fsync(outf.fileno()) + os.rename(outf.name, rmtab) + # NFS_WRITE because this exposes hostnames and IP addresses # READONLY is considered administrative-level permission @filterable(roles=['READONLY_ADMIN', 'SHARING_NFS_WRITE'])