// 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
 *
 * ---------------------------------------
 */

/**
 * @file nfs_lib.c
 * @brief The file that contain the 'main' routine for the nfsd.
 *
 */
#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <signal.h> /* for sigaction */
#include <errno.h>
#include "fsal.h"
#include "log.h"
#include "nfs_init.h"
#include "nfs_exports.h"
#include "pnfs_utils.h"
#include "conf_url.h"
#include "sal_functions.h"

/* parameters for NFSd startup and default values */

nfs_start_info_t my_nfs_start_info = { .dump_default_config = false,
				       .lw_mark_trigger = false,
				       .drop_caps = false };

config_file_t nfs_config_struct;
char *nfs_host_name = "localhost";

/* Cleanup on shutdown */
void export_cleanup(void)
{
	PTHREAD_RWLOCK_destroy(&export_opt_lock);
}

struct cleanup_list_element export_cleanup_element = {
	.clean = export_cleanup,
};

/**
 * nfs_libmain: library initializer
 *
 * @return status to calling program by calling the exit(3C) function.
 *
 */

int nfs_libmain(const char *ganesha_conf, const char *lpath,
		const int debug_level)
{
	char localmachine[MAXHOSTNAMELEN + 1];
	int dsc;
	int rc;
	char *log_path = NULL;
	const char *exec_name = "nfs-ganesha";
	sigset_t signals_to_block;
	struct config_error_type err_type;

	/* Set the server's boot time and epoch */
	now(&nfs_ServerBootTime);
	nfs_ServerEpoch = (time_t)nfs_ServerBootTime.tv_sec;

	if (ganesha_conf)
		nfs_config_path = gsh_strdup(ganesha_conf);

	if (lpath)
		log_path = gsh_strdup(lpath);

	/* get host name */
	if (gethostname(localmachine, sizeof(localmachine)) != 0) {
		fprintf(stderr, "Could not get local host name, exiting...\n");
		exit(1);
	} else {
		nfs_host_name = gsh_strdup(localmachine);
		if (!nfs_host_name) {
			fprintf(stderr,
				"Unable to allocate memory for hostname, exiting...\n");
			exit(1);
		}
	}

	/* initialize memory and logging */
	nfs_prereq_init(exec_name, nfs_host_name, debug_level, log_path, false,
			8192 * 1024);
#if GANESHA_BUILD_RELEASE
	LogEvent(COMPONENT_MAIN, "%s Starting: Ganesha Version %s", exec_name,
		 GANESHA_VERSION);
#else
	LogEvent(COMPONENT_MAIN, "%s Starting: %s", exec_name,
		 "Ganesha Version " _GIT_DESCRIBE ", built at " __DATE__
		 " " __TIME__ " on " BUILD_HOST);
#endif

	/* initialize nfs_init */
	nfs_init_init();

	nfs_check_malloc();

	/* Make sure Linux file i/o will return with error
	 * if file size is exceeded. */
#ifdef linux
	signal(SIGXFSZ, SIG_IGN);
#endif

	/* Set up for the signal handler.
	 * Blocks the signals the signal handler will handle.
	 */
	sigemptyset(&signals_to_block);
	sigaddset(&signals_to_block, SIGPIPE); /* XXX */
	if (pthread_sigmask(SIG_BLOCK, &signals_to_block, NULL) != 0)
		LogFatal(COMPONENT_MAIN, "pthread_sigmask failed");

	/* init URL package */
	config_url_init();

	/* Create a memstream for parser+processing error messages */
	if (!init_error_type(&err_type))
		goto fatal_die;

	if (nfs_config_path == NULL || nfs_config_path[0] == '\0') {
		LogWarn(COMPONENT_INIT, "No configuration file named.");
		nfs_config_struct = NULL;
	} else
		nfs_config_struct =
			config_ParseFile(nfs_config_path, &err_type);

	if (!config_error_no_error(&err_type)) {
		char *errstr = err_type_str(&err_type);

		if (!config_error_is_harmless(&err_type)) {
			LogCrit(COMPONENT_INIT, "Error %s while parsing (%s)",
				errstr != NULL ? errstr : "unknown",
				nfs_config_path);
			if (errstr != NULL)
				gsh_free(errstr);
			goto fatal_die;
		} else
			LogWarn(COMPONENT_INIT, "Error %s while parsing (%s)",
				errstr != NULL ? errstr : "unknown",
				nfs_config_path);
		if (errstr != NULL)
			gsh_free(errstr);
	}

	if (read_log_config(nfs_config_struct, &err_type) < 0) {
		LogCrit(COMPONENT_INIT,
			"Error while parsing log configuration");
		goto fatal_die;
	}

	/* We need all the fsal modules loaded so we can have
	 * the list available at exports parsing time.
	 */
	if (start_fsals(nfs_config_struct, &err_type) < 0) {
		LogCrit(COMPONENT_INIT, "Error starting FSALs.");
		goto fatal_die;
	}

	/* parse configuration file */

	if (nfs_set_param_from_conf(nfs_config_struct, &my_nfs_start_info,
				    &err_type)) {
		LogCrit(COMPONENT_INIT,
			"Error setting parameters from configuration file.");
		goto fatal_die;
	}

	/* initialize core subsystems and data structures */
	if (init_server_pkgs() != 0) {
		LogCrit(COMPONENT_INIT, "Failed to initialize server packages");
		goto fatal_die;
	}

	/* Load Data Server entries from parsed file
	 * returns the number of DS entries.
	 */
	dsc = ReadDataServers(nfs_config_struct, &err_type);
	if (dsc < 0) {
		LogCrit(COMPONENT_INIT, "Error while parsing DS entries");
		goto fatal_die;
	}

	/* Create stable storage directory, this needs to be done before
	 * starting the recovery thread.
	 */
	rc = nfs4_recovery_init();
	if (rc) {
		LogCrit(COMPONENT_INIT,
			"Recovery backend initialization failed!");
		goto fatal_die;
	}

	/* Start grace period */
	nfs_start_grace(NULL);

	/* Wait for enforcement to begin */
	nfs_wait_for_grace_enforcement();

	PTHREAD_RWLOCK_init(&export_opt_lock, NULL);
	RegisterCleanup(&export_cleanup_element);

	/* Load export entries from parsed file
	 * returns the number of export entries.
	 */
	rc = ReadExports(nfs_config_struct, &err_type);
	if (rc < 0) {
		LogCrit(COMPONENT_INIT, "Error while parsing export entries");
		goto fatal_die;
	}
	if (rc == 0 && dsc == 0)
		LogWarn(COMPONENT_INIT,
			"No export entries found in configuration file !!!");
	(void)report_config_errors(&err_type, NULL, config_errs_to_log);

	/* freeing syntax tree : */

	config_Free(nfs_config_struct);

	/* Everything seems to be OK! We can now start service threads */
	nfs_start(&my_nfs_start_info);

	nfs_prereq_destroy();

	/* nfs_config_path is allocated only if ganesha_conf is not null. */
	if (ganesha_conf)
		gsh_free(nfs_config_path);
	if (log_path)
		gsh_free(log_path);
	gsh_free(nfs_host_name);

	return 0;

fatal_die:
	(void)report_config_errors(&err_type, NULL, config_errs_to_log);
	LogFatal(COMPONENT_INIT, "Fatal errors.  Server exiting...");
	/* NOT REACHED */
	return 2;
}
