// SPDX-License-Identifier: LGPL-3.0-or-later
/*
 * vim:noexpandtab:shiftwidth=8:tabstop=8:
 *
 * Copyright CEA/DAM/DIF  (2008)
 * contributeur : Philippe DENIEL   philippe.deniel@cea.fr
 *                Thomas LEIBOVICI  thomas.leibovici@cea.fr
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 * --------------------------------------- */
#include "config.h"
#include "log.h"
#include "gsh_rpc.h"
#include "fsal.h"
#include "nfs_core.h"
#include "nfs_exports.h"
#include "nfs_creds.h"
#include "nfs_proto_functions.h"
#include "nfs_proto_tools.h"
#include "nfs_file_handle.h"
#include "nfs_convert.h"
#include "export_mgr.h"

#include "gsh_lttng/gsh_lttng.h"
#if defined(USE_LTTNG) && !defined(LTTNG_PARSING)
#include "gsh_lttng/generated_traces/nfs4.h"
#endif

/**
 * @brief Opaque bookkeeping structure for NFSv4 readdir
 *
 * This structure keeps track of the process of writing out an NFSv4
 * READDIR response between calls to nfs4_readdir_callback.
 */
struct nfs4_readdir_cb_data {
	XDR xdr; /*< The xdrmem to serialize the entries */
	uint8_t *entries; /*< The array holding individual entries */
	size_t mem_avail; /*< The amount of memory remaining before we
				   hit maxcount */
	int count; /*< Number of entries accumulated so far. */
	uint32_t max_count; /*< Maximum number of entries allowed. */
	/* dircount accounting (cookie+name bytes, XDR sized).
	 * 0 = unlimited (RFC). */
	size_t dir_bytes_used; /* running total of cookie+name bytes */
	uint32_t dir_bytes_limit; /* requested dircount; 0 means no limit */
	bool dircount_hit; /* stop due to dircount hit */
	bool has_entries; /*< Track if at least one entry fit  */
	nfsstat4 error; /*< Set to a value other than NFS4_OK if the
				   callback function finds a fatal error. */
	struct bitmap4 *req_attr; /*< The requested attributes */
	compound_data_t *data; /*< The compound data, so we can produce
				   nfs_fh4s. */
	struct saved_export_context saved;
};

static void restore_data(struct nfs4_readdir_cb_data *tracker)
{
	if (tracker->saved.saved_export == NULL) {
		LogCrit(COMPONENT_NFS_READDIR, "Nothing to restore!");
		return;
	}

	/* Restore export stuff */
	restore_op_context_export(&tracker->saved);

	/* Restore creds */
	if (nfs_req_creds(tracker->data->req) != NFS4_OK) {
		LogCrit(COMPONENT_EXPORT, "Failure to restore creds");
	}
}

/* Base response of READDIR res ok includes nfsstat4, and verifier. This is the
 * part of the data that is not serialized with struct dirlist4 */
#define READDIR_RESOK_BASE_SIZE (sizeof(verifier4))

/* Base response of dirlist4 It includes entry termination and eof */
#define DIR_LIST4_BASE_SIZE (2 * BYTES_PER_XDR_UNIT)

/* Base response size includes READDIR_RESOK_BASE_SIZE and
 * DIR_LIST4_BASE_SIZE. */
#define READDIR_RESP_BASE_SIZE (READDIR_RESOK_BASE_SIZE + DIR_LIST4_BASE_SIZE)

/* The base size of a readdir entry includes cookie, name length, and the
 * indicator if a next entry follows.
 */
#define BASE_ENTRY_SIZE (sizeof(nfs_cookie4) + 2 * sizeof(uint32_t))

/**
 * @brief Populate entry4s when called from fsal_readdir
 *
 * This function is a callback passed to fsal_readdir.  It
 * fills in a pre-allocated array of entry4 structures and allocates
 * space for the name and attributes.  This space must be freed.
 *
 * @param[in,out] opaque A struct nfs4_readdir_cb_data that stores the
 *                       location of the array and other bookkeeping
 *                       information
 * @param[in]     obj	 Current file
 * @param[in]     attrs  The current file's attributes
 * @param[in]     cookie The readdir cookie for the current entry
 */

fsal_errors_t nfs4_readdir_callback(void *opaque, struct fsal_obj_handle *obj,
				    const struct fsal_attrlist *attr,
				    uint64_t mounted_on_fileid, uint64_t cookie,
				    enum cb_state cb_state)
{
	struct fsal_readdir_cb_parms *cb_parms = opaque;
	struct nfs4_readdir_cb_data *tracker = cb_parms->opaque;
	char val_fh[NFS4_FHSIZE];
	nfs_fh4 entryFH = { .nfs_fh4_len = 0, .nfs_fh4_val = val_fh };
	struct xdr_attrs_args args;
	compound_data_t *data = tracker->data;
	fsal_status_t fsal_status;
	fsal_accessflags_t access_mask_attr = 0;
	u_int pos_start = xdr_getpos(&tracker->xdr);
	/* We must leave space to deserialize DIR_LIST4_BASE_SIZE */
	u_int mem_avail = tracker->mem_avail - DIR_LIST4_BASE_SIZE;
	component4 name;
	bool_t res_false = false;
	bool_t lock_dir = false;
	struct fsal_obj_handle *saved_current_obj = NULL;

	assert(mem_avail >= pos_start);
	LogFullDebug(COMPONENT_NFS_READDIR, "Entry %s pos %d mem_left %d",
		     cb_parms->name, (int)pos_start,
		     (int)(mem_avail - pos_start));

	memset(&args, 0, sizeof(args));

	/* Cleanup after problem with junction processing. */
	if (cb_state == CB_PROBLEM) {
		/* Restore the export. */
		LogDebug(COMPONENT_NFS_READDIR,
			 "Cleanup after problem with junction processing.");
		restore_data(tracker);
		return ERR_FSAL_NO_ERROR;
	}

	/* Test if this is a junction.
	 *
	 * NOTE: If there is a junction within a file system (perhaps setting
	 *       up different permissions for part of the file system), the
	 *       junction inode will ALSO be the root of the nested export.
	 *       By testing cb_state, we allow the call back to process
	 *       that root inode to proceed rather than getting stuck in a
	 *       junction crossing infinite loop.
	 */
	if (obj->type == DIRECTORY && cb_parms->attr_allowed &&
	    cb_state == CB_ORIGINAL) {
		lock_dir = true;
		PTHREAD_RWLOCK_rdlock(&obj->state_hdl->jct_lock);

		if (obj->state_hdl->dir.junction_export == NULL)
			goto not_junction;

		/* This is a junction. Code used to not recognize this
		 * which resulted in readdir giving different attributes
		 * (including FH, FSid, etc...) to clients from a
		 * lookup. AIX refused to list the directory because of
		 * this. Now we go to the junction to get the
		 * attributes.
		 */
		LogDebugAlt(
			COMPONENT_EXPORT, COMPONENT_NFS_READDIR,
			"Offspring DIR %s is a junction Export_id %d Pseudo %s",
			cb_parms->name,
			obj->state_hdl->dir.junction_export->export_id,
			JCT_PSEUDOPATH(obj->state_hdl));

		/* Get a reference to the export and stash it in
		 * compound data.
		 */
		if (!export_ready(obj->state_hdl->dir.junction_export)) {
			/* Export is in the process of being released.
			 * Pretend it's not actually a junction.
			 */
			goto not_junction;
		}

		get_gsh_export_ref(obj->state_hdl->dir.junction_export);

		/* Save the compound data context and cross the junction */
		save_op_context_export_and_set_export(
			&tracker->saved, obj->state_hdl->dir.junction_export);

		/* Build the credentials */
		args.rdattr_error = nfs4_export_check_access(data->req);

		if (args.rdattr_error == NFS4ERR_ACCESS) {
			/* If return is NFS4ERR_ACCESS then this client
			 * doesn't have access to this export, quietly
			 * skip the export.
			 */
			LogDebugAlt(
				COMPONENT_EXPORT, COMPONENT_NFS_READDIR,
				"NFS4ERR_ACCESS Skipping Export_Id %d Pseudo %s",
				op_ctx->ctx_export->export_id,
				CTX_PSEUDOPATH(op_ctx));

			/* Restore export and creds */
			restore_data(tracker);

			/* Indicate success without adding another entry */
			cb_parms->in_result = true;
			PTHREAD_RWLOCK_unlock(&obj->state_hdl->jct_lock);
			return ERR_FSAL_NO_ERROR;
		}

		if (args.rdattr_error == NFS4ERR_WRONGSEC) {
			/* Client isn't using the right SecType for this export,
			 * we will report NFS4ERR_WRONGSEC in
			 * FATTR4_RDATTR_ERROR.
			 *
			 * If the ONLY attributes requested are
			 * FATTR4_RDATTR_ERROR and FATTR4_MOUNTED_ON_FILEID we
			 * will not return an error and instead will return
			 * success with FATTR4_MOUNTED_ON_FILEID. AIX clients
			 * make this request and expect it to succeed.
			 */

			if (check_for_wrongsec_ok_attr(tracker->req_attr)) {
				/* Client is requesting attr that are allowed
				 * when NFS4ERR_WRONGSEC occurs.
				 */
				LogDebugAlt(
					COMPONENT_EXPORT, COMPONENT_NFS_READDIR,
					"Ignoring NFS4ERR_WRONGSEC (only asked for MOUNTED_IN_FILEID) On ReadDir Export_Id %d Path %s",
					op_ctx->ctx_export->export_id,
					CTX_PSEUDOPATH(op_ctx));

				/* Because we are not asking for any attributes
				 * which are a property of the exported file
				 * system's root, really just asking for
				 * MOUNTED_ON_FILEID, we can just get the attr
				 * for this node since it will result in the
				 * correct value for MOUNTED_ON_FILEID since
				 * the fileid of the junction node is the
				 * MOUNTED_ON_FILEID of the root across the
				 * junction, and the mounted_on_filed passed
				 * is the fileid of the junction (since the
				 * node can't be the root of the current
				 * export).
				 *
				 * Go ahead and proceed without an error.
				 */
				args.rdattr_error = NFS4_OK;
			} else {
				/* We really must report the NFS4ERR_WRONGSEC.
				 * We will report it below, but we need to get
				 * the name into the entry.
				 */
				LogDebugAlt(
					COMPONENT_EXPORT, COMPONENT_NFS_READDIR,
					"NFS4ERR_WRONGSEC On ReadDir Export_Id %d Pseudo %s",
					op_ctx->ctx_export->export_id,
					CTX_PSEUDOPATH(op_ctx));
			}
		} else if (args.rdattr_error == NFS4_OK) {
			/* Now we must traverse the junction to get the
			 * attributes. We have already set up the compound data.
			 *
			 * Signal to populate_dirent to call back with the
			 * root node of the export across the junction. Also
			 * signal to ourselves that the call back will be
			 * across the junction.
			 */
			LogDebugAlt(
				COMPONENT_EXPORT, COMPONENT_NFS_READDIR,
				"Need to cross junction to Export_Id %d Pseudo %s",
				op_ctx->ctx_export->export_id,
				CTX_PSEUDOPATH(op_ctx));
			PTHREAD_RWLOCK_unlock(&obj->state_hdl->jct_lock);
			return ERR_FSAL_CROSS_JUNCTION;
		}

		/* An error occurred and we will report it, but we need to get
		 * the name into the entry to proceed.
		 *
		 * Restore export and creds.
		 */
		LogDebugAlt(
			COMPONENT_EXPORT, COMPONENT_NFS_READDIR,
			"Need to report error for junction to Export_Id %d Pseudo %s",
			op_ctx->ctx_export->export_id, CTX_PSEUDOPATH(op_ctx));
		restore_data(tracker);
	}

not_junction:
	if (lock_dir)
		PTHREAD_RWLOCK_unlock(&obj->state_hdl->jct_lock);

	args.attrs = (struct fsal_attrlist *)attr;
	args.data = data;
	args.hdl4 = &entryFH;
	args.mounted_on_fileid = mounted_on_fileid;
	args.fileid = obj->fileid;
	args.fsid = obj->fsid;

	/* Now process the entry */
	memset(val_fh, 0, NFS4_FHSIZE);

	/* See if we have space based on max_count. */
	if ((tracker->max_count != 0) &&
	    (tracker->count == tracker->max_count)) {
		LogDebug(COMPONENT_NFS_READDIR,
			 "Skipping because we already have %d entries",
			 tracker->count);
		goto failure;
	}

	/* Bits that don't require allocation */
	if (xdr_getpos(&tracker->xdr) + BASE_ENTRY_SIZE > mem_avail) {
		LogDebug(COMPONENT_NFS_READDIR,
			 "Skipping because too small for BASE_ENTRY_SIZE %d",
			 (int)BASE_ENTRY_SIZE);
		goto failure;
	}

	/* The filename.  We don't use str2utf8 because that has an
	 * additional copy into a buffer before copying into the
	 * destination.
	 */
	name.utf8string_len = strlen(cb_parms->name);
	name.utf8string_val = (char *)cb_parms->name;

	size_t add_bytes = sizeof(nfs_cookie4) + BYTES_PER_XDR_UNIT +
			   RNDUP(name.utf8string_len);

	/*
	 * Enforce dircount as a byte budget for the directory information
	 * portion: XDR(cookie) + XDR(name with 4-byte rounding).
	 * If dir_bytes_limit == 0, there is no limit (per RFC).
	 */
	{
		if (tracker->dir_bytes_limit != 0 &&
		    tracker->dir_bytes_used + add_bytes >
			    tracker->dir_bytes_limit) {
			LogFullDebug(
				COMPONENT_NFS_READDIR,
				"Stopping due to dircount hit: used=%zu add=%zu limit=%u",
				tracker->dir_bytes_used, add_bytes,
				tracker->dir_bytes_limit);
			tracker->dircount_hit = true;
			goto failure;
		}
	}

	if (xdr_getpos(&tracker->xdr) + BASE_ENTRY_SIZE +
		    RNDUP(name.utf8string_len) >
	    mem_avail) {
		LogDebug(COMPONENT_NFS_READDIR,
			 "Skipping because of name %s too long %d",
			 (char *)cb_parms->name, (int)name.utf8string_len);
		goto failure;
	}

	/* If we carried an error from above, now that we have
	 * the name set up, go ahead and try and put error in
	 * results.
	 */
	if (args.rdattr_error != NFS4_OK) {
		LogDebug(COMPONENT_NFS_READDIR, "Skipping because of %s",
			 nfsstat4_to_str(args.rdattr_error));
		goto skip;
	}

	if (cb_parms->attr_allowed &&
	    attribute_is_set(tracker->req_attr, FATTR4_FILEHANDLE) &&
	    !nfs4_FSALToFhandle(false, &entryFH, obj, op_ctx->ctx_export)) {
		LogDebug(COMPONENT_NFS_READDIR,
			 "Skipping because of problem with handle");
		goto server_fault;
	}

	if (!cb_parms->attr_allowed) {
		/* fsal_readdir is signaling us that client didn't have
		 * search permission in this directory, so we can't return any
		 * attributes, but must indicate NFS4ERR_ACCESS.
		 */
		args.rdattr_error = NFS4ERR_ACCESS;
		LogDebug(COMPONENT_NFS_READDIR, "Skipping because of %s",
			 nfsstat4_to_str(args.rdattr_error));
		goto skip;
	}

	/* Adjust access mask if ACL is asked for.
	 * NOTE: We intentionally do NOT check ACE4_READ_ATTR.
	 */
	if (attribute_is_set(tracker->req_attr, FATTR4_ACL))
		access_mask_attr |= FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_READ_ACL);

	/* Attrs were refreshed before call */
	fsal_status = obj->obj_ops->test_access(obj, access_mask_attr, NULL,
						NULL, false);
	if (FSAL_IS_ERROR(fsal_status)) {
		LogDebug(COMPONENT_NFS_READDIR,
			 "permission check for attributes status=%s",
			 msg_fsal_err(fsal_status.major));

		args.rdattr_error = nfs4_Errno_status(fsal_status);
		LogDebug(COMPONENT_NFS_READDIR, "Skipping because of %s",
			 nfsstat4_to_str(args.rdattr_error));
		goto skip;
	}

	/* Tell is_referral to not cache attrs as it will affect readdir
	 * performance
	 */
	if (obj->obj_ops->is_referral(obj, (struct fsal_attrlist *)attr,
				      false)) {
		args.rdattr_error = NFS4ERR_MOVED;
		LogDebug(COMPONENT_NFS_READDIR, "Skipping because of %s",
			 nfsstat4_to_str(args.rdattr_error));
		goto skip;
	}

	/*
	 * At this point, current_obj points to what is setup by PUTFH.
	 * If readdir involves junctions, then it is possible that the
	 * FSAL for current_obj and obj could be different. If attributes
	 * requested includes Filesystem attributes, then a call will be
	 * made to the FSAL that owns the object as populate_dirent would
	 * have changed fsal_export to the new export. So, a call to
	 * get_dynamic_info of the FSAL will be made with data->current_obj
	 * which is actually the parent's FSAL object.
	 * This normally happens if NFS clients tries to query FS attributes
	 * in READDIR of a directory that contains junctions (ex:- pseudo
	 * namespace)
	 */
	saved_current_obj = data->current_obj;
	data->current_obj = obj;
	if (!xdr_encode_entry4(&tracker->xdr, &args, tracker->req_attr, cookie,
			       &name) ||
	    (xdr_getpos(&tracker->xdr) + BYTES_PER_XDR_UNIT) > mem_avail) {
		/* We had an overflow */
		LogFullDebug(
			COMPONENT_NFS_READDIR,
			"Overflow of buffer after xdr_encode_entry4 - pos = %d",
			xdr_getpos(&tracker->xdr));
		data->current_obj = saved_current_obj;
		goto failure;
	}
	data->current_obj = saved_current_obj;

skip:

	if (args.rdattr_error != NFS4_OK) {
		tracker->error = args.rdattr_error;
		if (!attribute_is_set(tracker->req_attr, FATTR4_RDATTR_ERROR) &&
		    !attribute_is_set(tracker->req_attr, FATTR4_FS_LOCATIONS)) {
			LogDebug(
				COMPONENT_NFS_READDIR,
				"Skipping because of %s and didn't ask for FATTR4_RDATTR_ERROR or FATTR4_FS_LOCATIONS",
				nfsstat4_to_str(args.rdattr_error));
			goto failure;
		}

		if (!xdr_nfs4_fattr_fill_error(&tracker->xdr, tracker->req_attr,
					       cookie, &name, &args) ||
		    (xdr_getpos(&tracker->xdr) + BYTES_PER_XDR_UNIT) >
			    mem_avail) {
			/* We had an overflow */
			LogFullDebug(
				COMPONENT_NFS_READDIR,
				"Overflow of buffer after xdr_nfs4_fattr_fill_error - pos = %d",
				xdr_getpos(&tracker->xdr));
			goto failure;
		}
	}

	tracker->has_entries = true;
	cb_parms->in_result = true;
	tracker->count++;
	/* Account for cookie+name bytes contributing to dircount. */
	tracker->dir_bytes_used += add_bytes;
	goto out;

server_fault:

	tracker->error = NFS4ERR_SERVERFAULT;

failure:

	if (!tracker->has_entries && tracker->error == NFS4_OK) {
		tracker->error = NFS4ERR_TOOSMALL;
	}

	/* Reset to where we started this entry and encode a boolean
	 * false instead (entry_follows is false).
	 */
	if (!xdr_setpos(&tracker->xdr, pos_start) ||
	    !xdr_bool(&tracker->xdr, &res_false)) {
		/* Oops, what broke... */
		LogCrit(COMPONENT_NFS_READDIR,
			"Unexpected XDR failure processing readdir result");
		tracker->error = NFS4ERR_SERVERFAULT;
	}

	cb_parms->in_result = false;

out:

	return ERR_FSAL_NO_ERROR;
}

void xdr_dirlist4_uio_release(struct xdr_uio *uio, u_int flags)
{
	int ix;

	LogFullDebug(COMPONENT_NFS_V4,
		     "Releasing %p, references %" PRIi32 ", count %d", uio,
		     uio->uio_references, (int)uio->uio_count);

	if (!(--uio->uio_references)) {
		if (!(op_ctx && op_ctx->is_rdma_buff_used)) {
			for (ix = 0; ix < uio->uio_count; ix++)
				gsh_free(uio->uio_vio[ix].vio_base);
		}
		gsh_free(uio);
	}
}

/**
 * @brief NFS4_OP_READDIR
 *
 * Implements the NFS4_OP_READDIR operation. If fh is a pseudo FH,
 * then call is routed to routine nfs4_op_readdir_pseudo
 *
 * @param[in]     op   Arguments for nfs4_op
 * @param[in,out] data Compound request's data
 * @param[out]    resp Results for nfs4_op
 *
 * @return per RFC5661, pp. 371-2
 *
 */
enum nfs_req_result nfs4_op_readdir(struct nfs_argop4 *op,
				    compound_data_t *data,
				    struct nfs_resop4 *resp)
{
	READDIR4args *const arg_READDIR4 = &op->nfs_argop4_u.opreaddir;
	READDIR4res *res_READDIR4 = &resp->nfs_resop4_u.opreaddir;
	READDIR4resok *resok = &res_READDIR4->READDIR4res_u.resok4;
	struct fsal_obj_handle *dir_obj = NULL;
	bool eod_met = false;
	unsigned long dircount = 0;
	unsigned long maxcount = 0;
	verifier4 cookie_verifier;
	uint64_t cookie = 0;
	unsigned int num_entries = 0;
	struct nfs4_readdir_cb_data tracker;
	fsal_status_t fsal_status = { 0, 0 };
	attrmask_t attrmask;
	bool use_cookie_verifier =
		op_ctx_export_has_option(EXPORT_OPTION_USE_COOKIE_VERIFIER);

	GSH_AUTO_TRACEPOINT(
		nfs4, op_readir_start, TRACE_INFO,
		"READDIR arg: cookie={} verifier={} dircount={} maxcount={}",
		arg_READDIR4->cookie, TP_VERIFIER(arg_READDIR4->cookieverf),
		arg_READDIR4->dircount, arg_READDIR4->maxcount);

	resp->resop = NFS4_OP_READDIR;
	res_READDIR4->status = NFS4_OK;

	res_READDIR4->status = nfs4_sanity_check_FH(data, DIRECTORY, false);

	memset(&tracker, 0, sizeof(tracker));

	if (res_READDIR4->status != NFS4_OK)
		goto out;

	dir_obj = data->current_obj;

	/* get the characteristic value for readdir operation */
	dircount = arg_READDIR4->dircount;
	cookie = arg_READDIR4->cookie;

	/* Dont over flow V4.1 maxresponsesize or maxcachedsize */
	maxcount = resp_room(data);

	if (nfs_param.core_param.readdir_res_size < maxcount)
		maxcount = nfs_param.core_param.readdir_res_size;

	if (maxcount > (arg_READDIR4->maxcount))
		maxcount = arg_READDIR4->maxcount;

	LogDebug(COMPONENT_NFS_READDIR,
		 "dircount=%lu maxcount=%lu max_entries=%u cookie=%" PRIu64,
		 dircount, maxcount, nfs_param.core_param.readdir_max_count,
		 cookie);

	/* Since we never send a cookie of 1 or 2, we shouldn't ever get
	 * them back.
	 */
	if (cookie == 1 || cookie == 2) {
		res_READDIR4->status = NFS4ERR_BAD_COOKIE;
		LogDebug(COMPONENT_NFS_READDIR, "Bad cookie");
		goto out;
	}

	/* Make sure requested attributes are valid and initialize attrmask */
	res_READDIR4->status =
		bitmap4_to_attrmask_t(&arg_READDIR4->attr_request, &attrmask);
	if (res_READDIR4->status != NFS4_OK) {
		LogDebug(COMPONENT_NFS_READDIR, "Requested invalid attributes");
		goto out;
	}

	/* Make sure the FSAL supports all the requested attributes */
	attrmask_t supported_attrs =
		op_ctx->fsal_export->exp_ops.fs_supported_attrs(
			op_ctx->fsal_export);
	if ((attrmask & ~supported_attrs) != 0) {
		res_READDIR4->status = NFS4ERR_INVAL;
		LogDebug(COMPONENT_NFS_READDIR, "Requested invalid attributes");
		goto out;
	}

	/* Get only attributes that are allowed to be read */
	if (!nfs4_Fattr_Check_Access_Bitmap(&arg_READDIR4->attr_request,
					    FATTR4_ATTR_READ)) {
		res_READDIR4->status = NFS4ERR_INVAL;
		LogDebug(COMPONENT_NFS_READDIR, "Requested invalid attributes");
		goto out;
	}

	/* If maxcount is way too small return NFS4ERR_TOOSMALL */
	if (maxcount < READDIR_RESP_BASE_SIZE) {
		res_READDIR4->status = NFS4ERR_TOOSMALL;
		LogInfo(COMPONENT_NFS_READDIR,
			"Response too small maxcount = %lu need at least %llu",
			maxcount, (unsigned long long)READDIR_RESP_BASE_SIZE);
		goto out;
	}

	/* To make or check the cookie verifier */
	memset(cookie_verifier, 0, sizeof(cookie_verifier));

	/* If cookie verifier is used, then an non-trivial value is
	 * returned to the client This value is the change attribute of the
	 * directory. If verifier is unused (as in many NFS Servers) then
	 * only a set of zeros is returned (trivial value)
	 */
	if (use_cookie_verifier) {
		struct fsal_attrlist attrs;

		fsal_prepare_attrs(&attrs, ATTR_CHANGE);

		fsal_status =
			data->current_obj->obj_ops->getattrs(data->current_obj,
							     &attrs);

		if (FSAL_IS_ERROR(fsal_status)) {
			res_READDIR4->status = nfs4_Errno_status(fsal_status);
			LogDebug(COMPONENT_NFS_READDIR, "getattrs returned %s",
				 msg_fsal_err(fsal_status.major));
			goto out;
		}

		memcpy(cookie_verifier, &attrs.change,
		       MIN(sizeof(cookie_verifier), sizeof(attrs.change)));

		/* Done with the attrs */
		fsal_release_attrs(&attrs);
	}

	/* Cookie delivered by the server and used by the client SHOULD
	 * not be 0, 1 or 2 because these values are reserved (see RFC
	 * 3530, p. 192/RFC 5661, p468).
	 *
	 * 0 - cookie for first READDIR
	 * 1 - reserved for . on client
	 * 2 - reserved for .. on client
	 *
	 * '.' and '..' are not returned, so all cookies will be offset by 2
	 */

	if (cookie != 0 && use_cookie_verifier) {
		if (memcmp(cookie_verifier, arg_READDIR4->cookieverf,
			   NFS4_VERIFIER_SIZE) != 0) {
			res_READDIR4->status = NFS4ERR_BAD_COOKIE;
			LogDebug(COMPONENT_NFS_READDIR, "Bad cookie");
			goto out;
		}
	}

	/* Prepare to read the entries */
	tracker.mem_avail = maxcount - READDIR_RESOK_BASE_SIZE;
	/* Disable legacy entry count  limiting; enforce
	 * total reply via XDR buffer (maxcount)
	 * and enforce dircount as cookie+name byte budget only. */
	tracker.max_count = nfs_param.core_param.readdir_max_count;
	tracker.dir_bytes_used = 0;
	tracker.dir_bytes_limit =
		(uint32_t)dircount; /* 0 => unlimited per RFC */
	tracker.dircount_hit = false;

	/* Allocate the buffer for entried, preferring RDMA buffer if required.
	 */
	tracker.entries =
		get_buffer_for_io_response(tracker.mem_avail, NULL, false);

	tracker.error = NFS4_OK;
	tracker.req_attr = &arg_READDIR4->attr_request;
	tracker.data = data;

	xdrmem_create(&tracker.xdr, (char *)tracker.entries, tracker.mem_avail,
		      XDR_ENCODE);

	/* Assume we need at least the NFS v3 attr.
	 * Any attr is sufficient for permission checking.
	 */
	if (attrmask == 0)
		attrmask = ATTRS_NFS3;

	/* If seclabel is requested but we do not have
	 * EXPORT_OPTION_SECLABEL_SET, turn off that bit.
	 */
	if (attribute_is_set(tracker.req_attr, FATTR4_SEC_LABEL) &&
	    !op_ctx_export_has_option(EXPORT_OPTION_SECLABEL_SET)) {
		attrmask &= ~ATTR4_SEC_LABEL;
	}

	/* Perform the readdir operation */
	fsal_status = fsal_readdir(dir_obj, cookie, &num_entries, &eod_met,
				   attrmask, nfs4_readdir_callback, &tracker);

	if (FSAL_IS_ERROR(fsal_status)) {
		res_READDIR4->status = nfs4_Errno_status(fsal_status);
		LogDebug(COMPONENT_NFS_READDIR, "fsal_readdir returned %s",
			 msg_fsal_err(fsal_status.major));
		goto out_destroy;
	}

	LogDebug(COMPONENT_NFS_READDIR, "fsal_readdir returned %s",
		 msg_fsal_err(fsal_status.major));

	res_READDIR4->status = tracker.error;

	if (res_READDIR4->status != NFS4_OK) {
		LogDebug(COMPONENT_NFS_READDIR, "Tracker error");
		goto out_destroy;
	}

	/* Response size is the space we used for the entires + the response
	 * base size + nfsstat4 size. */
	data->op_resp_size = xdr_getpos(&tracker.xdr) + READDIR_RESP_BASE_SIZE +
			     sizeof(nfsstat4);

	if (tracker.has_entries) {
		struct xdr_uio *uio;
		u_int pos_end;
		bool_t tmp;

		if (eod_met) {
			/* If we hit end of directory, then the dirlist4 we have
			 * encoded so far has a complete entry (we MUST have
			 * consumed the last entry and encoded it). Now we need
			 * to encode a FALSE to indicate no next entry.
			 */
			tmp = FALSE;
			if (!xdr_bool(&tracker.xdr, &tmp)) {
				/* Oops... */
				LogCrit(COMPONENT_NFS_READDIR,
					"Encode of no next entry failed.");
				res_READDIR4->status = NFS4ERR_SERVERFAULT;
				goto out_destroy;
			}
		}

		/* Serialize eod_met into the entries buffer */
		tmp = eod_met;
		if (!xdr_bool(&tracker.xdr, &tmp)) {
			/* Oops... */
			LogCrit(COMPONENT_NFS_READDIR, "Encode of EOD failed.");
			res_READDIR4->status = NFS4ERR_SERVERFAULT;
			goto out_destroy;
		}

		pos_end = xdr_getpos(&tracker.xdr);

		/* Get an xdr_uio and fill it in */
		uio = gsh_calloc(1, sizeof(struct xdr_uio) +
					    sizeof(struct xdr_uio));
		uio->uio_release = xdr_dirlist4_uio_release;
		uio->uio_count = 1;
		uio->uio_vio[0].vio_base = tracker.entries;
		uio->uio_vio[0].vio_head = tracker.entries;
		uio->uio_vio[0].vio_tail = tracker.entries + pos_end;
		uio->uio_vio[0].vio_wrap = tracker.entries + pos_end;
		uio->uio_vio[0].vio_length = pos_end;
		uio->uio_vio[0].vio_type = VIO_DATA;

		/* Take over entries buffer */
		tracker.entries = NULL;
		resok->reply.uio = uio;
	} else {
		/* Directory was empty - need to register eof */
		/* This slight bit of oddness is caused by most booleans
		 * throughout Ganesha being of C99's bool type (taking the
		 * values true and false), but fields in XDR being of the older
		 * bool_t type i(taking the values TRUE and FALSE)
		 */
		if (eod_met)
			res_READDIR4->READDIR4res_u.resok4.reply.eof = TRUE;
		else
			res_READDIR4->READDIR4res_u.resok4.reply.eof = FALSE;
	}

	/* Do not forget to set the verifier */
	memcpy(resok->cookieverf, cookie_verifier, NFS4_VERIFIER_SIZE);

	/* We never use reply.entries */
	resok->reply.entries = NULL;

	res_READDIR4->status = NFS4_OK;

out_destroy:

	xdr_destroy(&tracker.xdr);

out:

	/* If we allocated but didn't consume entries, free it now. */
	if (!op_ctx->is_rdma_buff_used)
		gsh_free(tracker.entries);
	tracker.entries = NULL;

	LogDebug(COMPONENT_NFS_READDIR, "Returning %s",
		 nfsstat4_to_str(res_READDIR4->status));
	GSH_AUTO_TRACEPOINT(
		nfs4, op_readir_end, TRACE_INFO,
		"READDIR res: status={} verifier={} eof={}",
		res_READDIR4->status,
		TP_VERIFIER(res_READDIR4->READDIR4res_u.resok4.cookieverf),
		res_READDIR4->READDIR4res_u.resok4.reply.eof);

	return nfsstat4_to_nfs_req_result(res_READDIR4->status);
} /* nfs4_op_readdir */

/**
 * @brief Free memory allocated for READDIR result
 *
 * This function frees any memory allocated for the results of the
 * NFS4_OP_READDIR operation.
 *
 * @param[in,out] resp nfs4_op results
 */
void nfs4_op_readdir_Free(nfs_resop4 *res)
{
	/* Nothing to do, entries has been handed off or already freed. */
}
