mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-09-22 09:34:56 +02:00
NFSD: Add NFSD_CMD_UNLOCK_EXPORT netlink command
When a filesystem is exported to NFS clients, NFSv4 state (opens, locks, delegations, layouts) holds references that prevent the underlying filesystem from being unmounted. NFSD_CMD_UNLOCK_FILESYSTEM addresses this at superblock granularity, but administrators unexporting a single path on a shared filesystem (e.g., one of several exports on the same device) need finer control. Add NFSD_CMD_UNLOCK_EXPORT, which revokes NFSv4 state acquired through exports of a specific path. Matching is by path identity (dentry + vfsmount) via the sc_export field on each nfs4_stid, so multiple svc_export objects for the same path -- one per auth_domain -- are handled correctly without requiring the caller to name a specific client. The command takes a single "path" attribute. Userspace (exportfs -u) sends this after removing the last client for a given path, enabling the underlying filesystem to be unmounted. When multiple clients share an export path, individual unexports do not trigger state revocation; only the final one does. Reviewed-by: Jeff Layton <jlayton@kernel.org> Tested-by: Dai Ngo <dai.ngo@oracle.com> Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
@@ -317,6 +317,19 @@ attribute-sets:
|
|||||||
name: path
|
name: path
|
||||||
type: string
|
type: string
|
||||||
doc: Filesystem path whose state should be released.
|
doc: Filesystem path whose state should be released.
|
||||||
|
-
|
||||||
|
name: unlock-export
|
||||||
|
attributes:
|
||||||
|
-
|
||||||
|
name: path
|
||||||
|
type: string
|
||||||
|
doc: >-
|
||||||
|
Export path whose NFSv4 state should be revoked.
|
||||||
|
All state (opens, locks, delegations, layouts) acquired
|
||||||
|
through any export of this path is revoked, regardless
|
||||||
|
of which client holds the state. Intended for use after
|
||||||
|
all clients have been unexported from a given path,
|
||||||
|
enabling the underlying filesystem to be unmounted.
|
||||||
|
|
||||||
operations:
|
operations:
|
||||||
list:
|
list:
|
||||||
@@ -489,6 +502,20 @@ operations:
|
|||||||
request:
|
request:
|
||||||
attributes:
|
attributes:
|
||||||
- path
|
- path
|
||||||
|
-
|
||||||
|
name: unlock-export
|
||||||
|
doc: >-
|
||||||
|
Revoke NFSv4 state acquired through exports of a given path.
|
||||||
|
Unlike unlock-filesystem, which operates at superblock granularity,
|
||||||
|
this command targets only state associated with a specific export
|
||||||
|
path. Userspace (exportfs -u) sends this after removing the last
|
||||||
|
client for a path so the underlying filesystem can be unmounted.
|
||||||
|
attribute-set: unlock-export
|
||||||
|
flags: [admin-perm]
|
||||||
|
do:
|
||||||
|
request:
|
||||||
|
attributes:
|
||||||
|
- path
|
||||||
|
|
||||||
mcast-groups:
|
mcast-groups:
|
||||||
list:
|
list:
|
||||||
|
|||||||
@@ -113,6 +113,11 @@ static const struct nla_policy nfsd_unlock_filesystem_nl_policy[NFSD_A_UNLOCK_FI
|
|||||||
[NFSD_A_UNLOCK_FILESYSTEM_PATH] = { .type = NLA_NUL_STRING, },
|
[NFSD_A_UNLOCK_FILESYSTEM_PATH] = { .type = NLA_NUL_STRING, },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* NFSD_CMD_UNLOCK_EXPORT - do */
|
||||||
|
static const struct nla_policy nfsd_unlock_export_nl_policy[NFSD_A_UNLOCK_EXPORT_PATH + 1] = {
|
||||||
|
[NFSD_A_UNLOCK_EXPORT_PATH] = { .type = NLA_NUL_STRING, },
|
||||||
|
};
|
||||||
|
|
||||||
/* Ops table for nfsd */
|
/* Ops table for nfsd */
|
||||||
static const struct genl_split_ops nfsd_nl_ops[] = {
|
static const struct genl_split_ops nfsd_nl_ops[] = {
|
||||||
{
|
{
|
||||||
@@ -213,6 +218,13 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
|
|||||||
.maxattr = NFSD_A_UNLOCK_FILESYSTEM_PATH,
|
.maxattr = NFSD_A_UNLOCK_FILESYSTEM_PATH,
|
||||||
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
|
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.cmd = NFSD_CMD_UNLOCK_EXPORT,
|
||||||
|
.doit = nfsd_nl_unlock_export_doit,
|
||||||
|
.policy = nfsd_unlock_export_nl_policy,
|
||||||
|
.maxattr = NFSD_A_UNLOCK_EXPORT_PATH,
|
||||||
|
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct genl_multicast_group nfsd_nl_mcgrps[] = {
|
static const struct genl_multicast_group nfsd_nl_mcgrps[] = {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ int nfsd_nl_expkey_set_reqs_doit(struct sk_buff *skb, struct genl_info *info);
|
|||||||
int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info);
|
int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info);
|
||||||
int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info);
|
int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info);
|
||||||
int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, struct genl_info *info);
|
int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, struct genl_info *info);
|
||||||
|
int nfsd_nl_unlock_export_doit(struct sk_buff *skb, struct genl_info *info);
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
NFSD_NLGRP_NONE,
|
NFSD_NLGRP_NONE,
|
||||||
|
|||||||
@@ -1911,6 +1911,73 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)
|
|||||||
spin_unlock(&nn->client_lock);
|
spin_unlock(&nn->client_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct nfs4_stid *find_one_export_stid(struct nfs4_client *clp,
|
||||||
|
const struct path *path,
|
||||||
|
unsigned int sc_types)
|
||||||
|
{
|
||||||
|
unsigned long id = 0;
|
||||||
|
struct nfs4_stid *stid;
|
||||||
|
|
||||||
|
spin_lock(&clp->cl_lock);
|
||||||
|
while ((stid = idr_get_next_ul(&clp->cl_stateids, &id)) != NULL) {
|
||||||
|
if ((stid->sc_type & sc_types) &&
|
||||||
|
stid->sc_status == 0 &&
|
||||||
|
stid->sc_export &&
|
||||||
|
path_equal(&stid->sc_export->ex_path, path)) {
|
||||||
|
refcount_inc(&stid->sc_count);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
id++;
|
||||||
|
}
|
||||||
|
spin_unlock(&clp->cl_lock);
|
||||||
|
return stid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nfsd4_revoke_export_states - revoke nfsv4 states acquired through an export
|
||||||
|
* @nn: used to identify instance of nfsd (there is one per net namespace)
|
||||||
|
* @path: export path whose states should be revoked
|
||||||
|
*
|
||||||
|
* All nfs4 states (open, lock, delegation, layout) acquired through any
|
||||||
|
* export matching @path are revoked, regardless of which client holds
|
||||||
|
* them. Matching is by path identity (dentry + vfsmount), so multiple
|
||||||
|
* svc_export objects for the same path -- one per auth_domain -- are
|
||||||
|
* handled correctly.
|
||||||
|
*
|
||||||
|
* Userspace (exportfs -u) sends this after removing the last client
|
||||||
|
* for a path, enabling the underlying filesystem to be unmounted.
|
||||||
|
*/
|
||||||
|
void nfsd4_revoke_export_states(struct nfsd_net *nn, const struct path *path)
|
||||||
|
{
|
||||||
|
unsigned int idhashval;
|
||||||
|
unsigned int sc_types;
|
||||||
|
|
||||||
|
sc_types = SC_TYPE_OPEN | SC_TYPE_LOCK | SC_TYPE_DELEG | SC_TYPE_LAYOUT;
|
||||||
|
|
||||||
|
spin_lock(&nn->client_lock);
|
||||||
|
for (idhashval = 0; idhashval < CLIENT_HASH_SIZE; idhashval++) {
|
||||||
|
struct list_head *head = &nn->conf_id_hashtbl[idhashval];
|
||||||
|
struct nfs4_client *clp;
|
||||||
|
retry:
|
||||||
|
list_for_each_entry(clp, head, cl_idhash) {
|
||||||
|
struct nfs4_stid *stid = find_one_export_stid(
|
||||||
|
clp, path,
|
||||||
|
sc_types);
|
||||||
|
if (stid) {
|
||||||
|
spin_unlock(&nn->client_lock);
|
||||||
|
revoke_one_stid(nn, clp, stid);
|
||||||
|
nfs4_put_stid(stid);
|
||||||
|
spin_lock(&nn->client_lock);
|
||||||
|
if (clp->cl_minorversion == 0)
|
||||||
|
nn->nfs40_last_revoke =
|
||||||
|
ktime_get_boottime_seconds();
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spin_unlock(&nn->client_lock);
|
||||||
|
}
|
||||||
|
|
||||||
static inline int
|
static inline int
|
||||||
hash_sessionid(struct nfs4_sessionid *sessionid)
|
hash_sessionid(struct nfs4_sessionid *sessionid)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2354,6 +2354,51 @@ int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb,
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nfsd_nl_unlock_export_doit - revoke NFSv4 state for an export path
|
||||||
|
* @skb: reply buffer
|
||||||
|
* @info: netlink metadata and command arguments
|
||||||
|
*
|
||||||
|
* Revokes all NFSv4 state (opens, locks, delegations, layouts) acquired
|
||||||
|
* through any export of the given path, regardless of which client holds
|
||||||
|
* the state. Userspace (exportfs -u) sends this after removing the last
|
||||||
|
* client for a path so the underlying filesystem can be unmounted.
|
||||||
|
*
|
||||||
|
* Unlike NFSD_CMD_UNLOCK_FILESYSTEM, which operates at superblock
|
||||||
|
* granularity, this command revokes only the state associated with
|
||||||
|
* exports of a specific path.
|
||||||
|
*
|
||||||
|
* Return: 0 on success or a negative errno.
|
||||||
|
*/
|
||||||
|
int nfsd_nl_unlock_export_doit(struct sk_buff *skb, struct genl_info *info)
|
||||||
|
{
|
||||||
|
struct net *net = genl_info_net(info);
|
||||||
|
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
|
||||||
|
struct path path;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
if (GENL_REQ_ATTR_CHECK(info, NFSD_A_UNLOCK_EXPORT_PATH))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
trace_nfsd_ctl_unlock_export(net,
|
||||||
|
nla_data(info->attrs[NFSD_A_UNLOCK_EXPORT_PATH]));
|
||||||
|
error = kern_path(
|
||||||
|
nla_data(info->attrs[NFSD_A_UNLOCK_EXPORT_PATH]),
|
||||||
|
0, &path);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
mutex_lock(&nfsd_mutex);
|
||||||
|
if (nn->nfsd_serv)
|
||||||
|
nfsd4_revoke_export_states(nn, &path);
|
||||||
|
else
|
||||||
|
error = -EINVAL;
|
||||||
|
mutex_unlock(&nfsd_mutex);
|
||||||
|
|
||||||
|
path_put(&path);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* nfsd_net_init - Prepare the nfsd_net portion of a new net namespace
|
* nfsd_net_init - Prepare the nfsd_net portion of a new net namespace
|
||||||
* @net: a freshly-created network namespace
|
* @net: a freshly-created network namespace
|
||||||
|
|||||||
@@ -863,6 +863,7 @@ struct nfsd_file *find_any_file(struct nfs4_file *f);
|
|||||||
|
|
||||||
#ifdef CONFIG_NFSD_V4
|
#ifdef CONFIG_NFSD_V4
|
||||||
void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb);
|
void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb);
|
||||||
|
void nfsd4_revoke_export_states(struct nfsd_net *nn, const struct path *path);
|
||||||
void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb);
|
void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb);
|
||||||
int nfsd_net_cb_init(struct nfsd_net *nn);
|
int nfsd_net_cb_init(struct nfsd_net *nn);
|
||||||
void nfsd_net_cb_shutdown(struct nfsd_net *nn);
|
void nfsd_net_cb_shutdown(struct nfsd_net *nn);
|
||||||
@@ -870,6 +871,10 @@ void nfsd_net_cb_shutdown(struct nfsd_net *nn);
|
|||||||
static inline void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)
|
static inline void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
static inline void nfsd4_revoke_export_states(struct nfsd_net *nn,
|
||||||
|
const struct path *path)
|
||||||
|
{
|
||||||
|
}
|
||||||
static inline void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb)
|
static inline void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2021,6 +2021,25 @@ TRACE_EVENT(nfsd_ctl_unlock_fs,
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
TRACE_EVENT(nfsd_ctl_unlock_export,
|
||||||
|
TP_PROTO(
|
||||||
|
const struct net *net,
|
||||||
|
const char *path
|
||||||
|
),
|
||||||
|
TP_ARGS(net, path),
|
||||||
|
TP_STRUCT__entry(
|
||||||
|
__field(unsigned int, netns_ino)
|
||||||
|
__string(path, path)
|
||||||
|
),
|
||||||
|
TP_fast_assign(
|
||||||
|
__entry->netns_ino = net->ns.inum;
|
||||||
|
__assign_str(path);
|
||||||
|
),
|
||||||
|
TP_printk("path=%s",
|
||||||
|
__get_str(path)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
TRACE_EVENT(nfsd_ctl_filehandle,
|
TRACE_EVENT(nfsd_ctl_filehandle,
|
||||||
TP_PROTO(
|
TP_PROTO(
|
||||||
const struct net *net,
|
const struct net *net,
|
||||||
|
|||||||
@@ -218,6 +218,13 @@ enum {
|
|||||||
NFSD_A_UNLOCK_FILESYSTEM_MAX = (__NFSD_A_UNLOCK_FILESYSTEM_MAX - 1)
|
NFSD_A_UNLOCK_FILESYSTEM_MAX = (__NFSD_A_UNLOCK_FILESYSTEM_MAX - 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
NFSD_A_UNLOCK_EXPORT_PATH = 1,
|
||||||
|
|
||||||
|
__NFSD_A_UNLOCK_EXPORT_MAX,
|
||||||
|
NFSD_A_UNLOCK_EXPORT_MAX = (__NFSD_A_UNLOCK_EXPORT_MAX - 1)
|
||||||
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
NFSD_CMD_RPC_STATUS_GET = 1,
|
NFSD_CMD_RPC_STATUS_GET = 1,
|
||||||
NFSD_CMD_THREADS_SET,
|
NFSD_CMD_THREADS_SET,
|
||||||
@@ -236,6 +243,7 @@ enum {
|
|||||||
NFSD_CMD_CACHE_FLUSH,
|
NFSD_CMD_CACHE_FLUSH,
|
||||||
NFSD_CMD_UNLOCK_IP,
|
NFSD_CMD_UNLOCK_IP,
|
||||||
NFSD_CMD_UNLOCK_FILESYSTEM,
|
NFSD_CMD_UNLOCK_FILESYSTEM,
|
||||||
|
NFSD_CMD_UNLOCK_EXPORT,
|
||||||
|
|
||||||
__NFSD_CMD_MAX,
|
__NFSD_CMD_MAX,
|
||||||
NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)
|
NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user