* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*/
#include <linux/types.h>
#include <linux/sunrpc/clnt.h>
#include <linux/nfs.h>
#include <linux/nfs4.h>
#include <linux/nfs3.h>
#include <linux/nfs_fs.h>
#include <linux/nfs_fs_sb.h>
#include <linux/sunrpc/sched.h>
#include <linux/nfs_iostat.h>
#include <linux/nfs_mount.h>
#include "enfs_adapter.h"
#include "iostat.h"
#include "enfs/enfs_log.h"
static struct enfs_adapter_ops __rcu *enfs_adapter;
static DEFINE_MUTEX(enfs_module_mutex);
int enfs_adapter_register(struct enfs_adapter_ops *ops)
{
struct enfs_adapter_ops *old;
old = cmpxchg((struct enfs_adapter_ops **)&enfs_adapter, NULL, ops);
if (old == NULL || old == ops)
return 0;
enfs_log_error("regist enfs_adapter ops %p fail. old %p\n", ops, old);
return -EPERM;
}
EXPORT_SYMBOL_GPL(enfs_adapter_register);
int enfs_adapter_unregister(struct enfs_adapter_ops *ops)
{
struct enfs_adapter_ops *old;
old = cmpxchg((struct enfs_adapter_ops **)&enfs_adapter, ops, NULL);
if (old == ops || old == NULL)
return 0;
enfs_log_error("unregist enfs_adapter ops %p fail. old %p\n", ops, old);
return -EPERM;
}
EXPORT_SYMBOL_GPL(enfs_adapter_unregister);
struct enfs_adapter_ops *nfs_multipath_router_get(void)
{
struct enfs_adapter_ops *ops;
rcu_read_lock();
ops = rcu_dereference(enfs_adapter);
if (ops == NULL) {
rcu_read_unlock();
return NULL;
}
if (!try_module_get(ops->owner))
ops = NULL;
rcu_read_unlock();
return ops;
}
void nfs_multipath_router_put(struct enfs_adapter_ops *ops)
{
if (ops)
module_put(ops->owner);
}
bool is_valid_option(enum nfsmultipathoptions option)
{
if (option < REMOTEADDR || option >= INVALID_OPTION) {
pr_warn("ENFS: invalid option %d\n",
option);
return false;
}
return true;
}
int enfs_parse_mount_options(enum nfsmultipathoptions option, char *str,
struct nfs_fs_context *mnt, struct fs_context *fc)
{
int rc;
struct enfs_adapter_ops *ops;
ops = nfs_multipath_router_get();
if (ops == NULL) {
enfs_log_debug("prepare loading eNFS module\n");
mutex_lock(&enfs_module_mutex);
rc = request_module("enfs");
mutex_unlock(&enfs_module_mutex);
if (rc) {
enfs_log_debug("failed loading eNFS module\n");
return -EOPNOTSUPP;
}
ops = nfs_multipath_router_get();
}
if ((ops == NULL) || (ops->parse_mount_options == NULL) ||
!is_valid_option(option)) {
nfs_multipath_router_put(ops);
enfs_log_debug("parsing nfs mount option enfs not load\n");
return -EOPNOTSUPP;
}
enfs_log_debug("parsing nfs mount option '%s' type: %d\n", str, option);
rc = ops->parse_mount_options(option, str, &mnt->enfs_option,
fc->net_ns);
nfs_multipath_router_put(ops);
return rc;
}
void enfs_free_mount_options(struct nfs_fs_context *data)
{
struct enfs_adapter_ops *ops;
if (data->enfs_option == NULL)
return;
ops = nfs_multipath_router_get();
if ((ops == NULL) || (ops->free_mount_options == NULL)) {
nfs_multipath_router_put(ops);
return;
}
ops->free_mount_options((void *)&data->enfs_option);
nfs_multipath_router_put(ops);
}
int nfs_create_multi_path_client(struct nfs_client *client,
const struct nfs_client_initdata *cl_init)
{
int ret = 0;
struct enfs_adapter_ops *ops;
if (cl_init->enfs_option == NULL)
return 0;
ops = nfs_multipath_router_get();
if (ops != NULL && ops->client_info_init != NULL)
ret = ops->client_info_init((void *)&client->cl_multipath_data,
cl_init);
nfs_multipath_router_put(ops);
return ret;
}
EXPORT_SYMBOL_GPL(nfs_create_multi_path_client);
void nfs_free_multi_path_client(struct nfs_client *clp)
{
struct enfs_adapter_ops *ops;
if (clp->cl_multipath_data == NULL)
return;
ops = nfs_multipath_router_get();
if (ops != NULL && ops->client_info_free != NULL)
ops->client_info_free(clp->cl_multipath_data);
nfs_multipath_router_put(ops);
}
int nfs_multipath_client_match(void *src, void *dst)
{
int ret = true;
struct enfs_adapter_ops *ops;
if (src == NULL && dst == NULL)
return true;
if ((src == NULL && dst) || (src && dst == NULL))
return false;
ops = nfs_multipath_router_get();
if (ops != NULL && ops->client_info_match != NULL)
ret = ops->client_info_match(src, dst);
nfs_multipath_router_put(ops);
return ret;
}
int nfs4_multipath_client_match(void *src, void *dst)
{
int ret = true;
struct enfs_adapter_ops *ops;
if (src == NULL && dst == NULL)
return true;
if (src == NULL || dst == NULL)
return false;
ops = nfs_multipath_router_get();
if (ops != NULL && ops->nfs4_client_info_match != NULL)
ret = ops->nfs4_client_info_match(src, dst);
nfs_multipath_router_put(ops);
return ret;
}
EXPORT_SYMBOL_GPL(nfs4_multipath_client_match);
void nfs_multipath_show_client_info(struct seq_file *mount_option,
struct nfs_server *server)
{
struct enfs_adapter_ops *ops;
if (mount_option == NULL || server == NULL || server->client == NULL ||
server->nfs_client->cl_multipath_data == NULL)
return;
ops = nfs_multipath_router_get();
if (ops != NULL && ops->client_info_show != NULL)
ops->client_info_show(mount_option, server);
nfs_multipath_router_put(ops);
}
int nfs_remount_iplist(struct nfs_client *nfs_client, void *enfs_option)
{
int ret = 0;
struct enfs_adapter_ops *ops;
if (nfs_client == NULL || nfs_client->cl_rpcclient == NULL)
return 0;
ops = nfs_multipath_router_get();
if (ops != NULL && ops->remount_ip_list != NULL)
ret = ops->remount_ip_list(nfs_client, enfs_option);
nfs_multipath_router_put(ops);
return ret;
}
EXPORT_SYMBOL_GPL(nfs_remount_iplist);
bool nfs_has_created_multipath(struct nfs_client *nfs_client)
{
if (nfs_client == NULL || nfs_client->cl_multipath_data == NULL)
return false;
else
return true;
}
EXPORT_SYMBOL_GPL(nfs_has_created_multipath);
void nfs_multipath_set_mount_data(void **opt, const char *hostname)
{
struct enfs_adapter_ops *ops = nfs_multipath_router_get();
if (ops != NULL && ops->set_mount_data != NULL)
ops->set_mount_data(opt, hostname);
nfs_multipath_router_put(ops);
}
EXPORT_SYMBOL_GPL(nfs_multipath_set_mount_data);
bool enfs_check_have_lookup_cache_flag(struct nfs_server *server, int flag)
{
* 1. first check user lookupcache flag match or not
* 2. then if user lookupcache option is positive/none, will ignore server
* lookupcache flag. if user lookupcache option is all, will check server
* ookupcache flag.
* we don't use enfs ops to check, because during upgrade ops will be null, it will
* cause result will change when upgrade.
*/
if (server->flags & flag)
return true;
if (server->flags &
(NFS_MOUNT_LOOKUP_CACHE_NONE | NFS_MOUNT_LOOKUP_CACHE_NONEG))
return false;
return ((server->enfs_flags & flag) ? true : false);
}
EXPORT_SYMBOL_GPL(enfs_check_have_lookup_cache_flag);
void enfs_trigger_get_server_capability(struct nfs_server *server)
{
struct enfs_adapter_ops *ops;
ops = nfs_multipath_router_get();
if (ops != NULL && ops->trigger_get_capability != NULL)
ops->trigger_get_capability(server);
nfs_multipath_router_put(ops);
}
EXPORT_SYMBOL_GPL(enfs_trigger_get_server_capability);