// SPDX-License-Identifier: LGPL-3.0-or-later
/**------------------------------------------------------------------------
 * @file gpfsext.c
 * @brief Use ioctl to call into the GPFS kernel module.
 *
 * 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
 *
 * ---------------------------------------
 *
 * NAME:        gpfs_ganesha()
 *
 * FUNCTION:    Use ioctl to call into the GPFS kernel module.
 *              If GPFS isn't loaded they receive ENOSYS.
 *
 * Returns:      0      Successful
 *              -1      Failure
 *
 * Errno:       ENOSYS  No quality of service function available
 *              ENOENT  File not found
 *              EINVAL  Not a GPFS file
 *              ESTALE  cached fs information was invalid
 *-------------------------------------------------------------------------*/

#include "config.h"
#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <stdio.h>

#ifdef _VALGRIND_MEMCHECK
#include <valgrind/memcheck.h>
#include <sys/vfs.h>
#include <sys/stat.h>
#include "include/gpfs.h"
#endif
#include "fsal.h"

#define NFS_CONTAINERIZATION 1

#include "include/gpfs_nfs.h"
#include "gsh_config.h"

struct kxArgs {
	signed long arg1;
	signed long arg2;
};

#ifdef _VALGRIND_MEMCHECK
static void valgrind_kganesha(struct kxArgs *args)
{
	int op = (int)args->arg1;

	switch (op) {
	case OPENHANDLE_STATFS_BY_FH: {
		struct statfs_arg *arg = (void *)args->arg2;

		VALGRIND_MAKE_MEM_DEFINED(arg->buf, sizeof(*arg->buf));
		break;
	}
	case OPENHANDLE_READ_BY_FD: {
		struct read_arg *arg = (void *)args->arg2;

		VALGRIND_MAKE_MEM_DEFINED(arg->bufP, arg->length);
		break;
	}
	case OPENHANDLE_NAME_TO_HANDLE: {
		struct name_handle_arg *arg = (void *)args->arg2;

		VALGRIND_MAKE_MEM_DEFINED(arg->handle,
					  sizeof(struct gpfs_file_handle));
		break;
	}
	case OPENHANDLE_GET_HANDLE: {
		struct get_handle_arg *arg = (void *)args->arg2;

		VALGRIND_MAKE_MEM_DEFINED(arg->out_fh,
					  sizeof(struct gpfs_file_handle));
		break;
	}
	case OPENHANDLE_STAT_BY_NAME: {
		struct stat_name_arg *arg = (void *)args->arg2;

		VALGRIND_MAKE_MEM_DEFINED(arg->buf, sizeof(*arg->buf));
		break;
	}
	case OPENHANDLE_CREATE_BY_NAME: {
		struct create_name_arg *arg = (void *)args->arg2;

		VALGRIND_MAKE_MEM_DEFINED(arg->new_fh,
					  sizeof(struct gpfs_file_handle));
		break;
	}
	case OPENHANDLE_READLINK_BY_FH: {
		struct readlink_fh_arg *arg = (void *)args->arg2;

		VALGRIND_MAKE_MEM_DEFINED(arg->buffer, arg->size);
		break;
	}
	case OPENHANDLE_GET_XSTAT: {
		struct xstat_arg *arg = (void *)args->arg2;

		VALGRIND_MAKE_MEM_DEFINED(arg->buf, sizeof(*arg->buf));
		VALGRIND_MAKE_MEM_DEFINED(arg->fsid, sizeof(*arg->fsid));
		if (arg->acl) {
			struct gpfs_acl *gacl;
			size_t outlen;

			/*
			 * arg->acl points to an IN/OUT buffer. First
			 * few fields are initialized by the caller and
			 * the rest are filled in by the ioctl call.
			 */
			gacl = arg->acl;
			outlen = gacl->acl_len -
				 offsetof(struct gpfs_acl, acl_nace);
			VALGRIND_MAKE_MEM_DEFINED(&gacl->acl_nace, outlen);
		}
		break;
	}
	case OPENHANDLE_WRITE_BY_FD: {
		struct write_arg *arg = (void *)args->arg2;

		VALGRIND_MAKE_MEM_DEFINED(arg->stability_got,
					  sizeof(*arg->stability_got));
		break;
	}
	default:
		break;
	}
}
#endif

int gpfs_op2index(int op)
{
	if ((op < GPFS_MIN_OP) || (op > GPFS_MAX_OP) ||
	    (op == 103 || op == 104 || op == 105))
		return GPFS_STAT_PH_INDEX;
	else
		return (op - GPFS_MIN_OP);
}

/**
 *  @param op Operation
 *  @param *oarg Arguments
 *
 *  @return Result
*/
int gpfs_ganesha(int op, void *oarg)
{
	int rc, idx;
	static int gpfs_fd = -2;
	struct kxArgs args;
	struct timespec start_time;
	struct timespec stop_time;
	nsecs_elapsed_t resp_time;
#ifdef NFS_CONTAINERIZATION
	FILE *file = NULL;
	bool readDone = false;
	bool gotPath = false;
	char *dirPath = NULL;
#endif

	if (gpfs_fd < 0) {
		/* If we enable fsal_trace in the config, the following
		 * LogFatal would call us here again for fsal tracing!
		 * Since we can't log as we are unable to open the device,
		 * just exit.
		 *
		 * Also, exit handler _dl_fini() will call gpfs_unload
		 * which will call release_log_facility that tries to
		 * acquire log_rwlock a second time! So do an immediate
		 * exit.
		 */
		if (gpfs_fd == -1) /* failed in a prior invocation */
			_exit(1);

		assert(gpfs_fd == -2);
		gpfs_fd = open(GPFS_DEVNAMEX, O_RDONLY);

#ifdef NFS_CONTAINERIZATION
		if (gpfs_fd < 0) {
			/* In containerization, /dev/ss0 may not be present.
			 * So look for gpfs mount.
			 */
			file = fopen("/proc/mounts", "r");
			if (file == NULL)
				goto exit;

			while (!readDone) {
				char lineBuf[2048];
				char *savePtr;
				char *type;
				char *ptr;
				/* Read the next line from the file */
				if (fgets(lineBuf, sizeof(lineBuf), file) ==
				    NULL) {
					readDone = true;
					break;
					/* Something is broken with the
					 * command pipe,
					 * maybe done or reached to EOF
					 */
				}
				ptr = strtok_r(lineBuf, " ", &savePtr);
				if (ptr == NULL)
					continue;
				/* 2nd is the mount point */
				ptr = strtok_r(NULL, " ", &savePtr);
				if (ptr == NULL)
					continue;
				else
					dirPath = ptr;
				/* 3rd is the type */
				type = strtok_r(NULL, " ", &savePtr);
				if (strcmp(type, "gpfs") == 0) {
					readDone = true;
					gotPath = true;
				}
			}
			fclose(file);
			if (gotPath) {
				gpfs_fd = open(dirPath, O_RDONLY);

				if (gpfs_fd >= 0)
					LogEvent(
						COMPONENT_FSAL,
						"%s GPFS file system found, fd %d,dirPath=%s\n",
						__func__, gpfs_fd, dirPath);
			}
exit:
			if (!gotPath)
				LogEvent(
					COMPONENT_FSAL,
					"%s no mounted GPFS file system found, fd %d\n",
					__func__, gpfs_fd);
		}
#endif
		if (gpfs_fd == -1)
			LogFatal(COMPONENT_FSAL,
				 "open of %s failed with errno %d",
				 GPFS_DEVNAMEX, errno);
		(void)fcntl(gpfs_fd, F_SETFD, FD_CLOEXEC);
	}

	args.arg1 = op;
	args.arg2 = (long)oarg;
#ifdef _VALGRIND_MEMCHECK
	valgrind_kganesha(&args);
#endif
	if (nfs_param.core_param.enable_FSALSTATS) {
		/* Collect FSAL stats */
		now(&start_time);
		rc = ioctl(gpfs_fd, kGanesha, &args);
		now(&stop_time);
		resp_time = timespec_diff(&start_time, &stop_time);

		/* record FSAL stats */
		idx = gpfs_op2index(op);
		(void)atomic_inc_uint64_t(&gpfs_stats.op_stats[idx].num_ops);
		(void)atomic_add_uint64_t(&gpfs_stats.op_stats[idx].resp_time,
					  resp_time);
		if (gpfs_stats.op_stats[idx].resp_time_max < resp_time)
			gpfs_stats.op_stats[idx].resp_time_max = resp_time;
		if (gpfs_stats.op_stats[idx].resp_time_min == 0 ||
		    gpfs_stats.op_stats[idx].resp_time_min > resp_time)
			gpfs_stats.op_stats[idx].resp_time_min = resp_time;

	} else {
		rc = ioctl(gpfs_fd, kGanesha, &args);
	}
	return rc;
}
