/**
 * Copyright (C) 2020 Daniel-Constantin Mierla (asipto.com)
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#include "../../core/dprint.h"
#include "../../core/ut.h"
#include "../../core/mem/shm.h"
#include "../../core/globals.h"
#include "../../core/rpc.h"
#include "../../core/rpc_lookup.h"

static const char *corex_rpc_shm_info_doc[2] = {
		"Return details of the shared memory manager", 0};


/*
 * RPC command to list the listening sockets
 */
static void corex_rpc_shm_info(rpc_t *rpc, void *ctx)
{
	void *th;

	if(rpc->add(ctx, "{", &th) < 0) {
		rpc->fault(ctx, 500, "Internal error creating rpc");
		return;
	}
	if(rpc->struct_add(th, "sj", "name",
			   (_shm_root.mname) ? _shm_root.mname : "unknown", "size",
			   shm_mem_size)
			< 0) {
		rpc->fault(ctx, 500, "Internal error adding fields");
		return;
	}
}

static const char *corex_rpc_shm_stats_doc[2] = {
		"Return shared memory stats", 0};

/*
 * RPC command to return shm stats
 */
static void corex_rpc_shm_stats(rpc_t *rpc, void *c)
{
	struct mem_info mi;
	void *th;

	shm_info(&mi);
	rpc->add(c, "{", &th);
	rpc->struct_add(th, "jjjjjj", "total", mi.total_size, "free", mi.free_size,
			"used", mi.used_size, "real_used", mi.real_used, "max_used",
			mi.max_used, "fragments", mi.total_frags);
}

static const char *corex_rpc_shm_report_doc[2] = {
		"Return shared memory report", 0};

/*
 * RPC command to return shm report
 */
static void corex_rpc_shm_report(rpc_t *rpc, void *ctx)
{
	mem_report_t mrep;
	void *th;

	if(_shm_root.xreport == NULL) {
		rpc->fault(ctx, 500, "No report callback function");
		return;
	}
	shm_report(&mrep);
	rpc->add(ctx, "{", &th);
	rpc->struct_add(th, "jjjjjjjjjsssjjsssjjsssjjsssj", "total_size",
			mrep.total_size, "free_size_s", mrep.free_size_s, "used_size_s",
			mrep.used_size_s, "real_used_s", mrep.real_used_s, "max_used_s",
			mrep.max_used_s, "free_frags", mrep.free_frags, "used_frags",
			mrep.used_frags, "total_frags", mrep.total_frags,
			"max_free_frag_size", mrep.max_free_frag_size, "max_free_frag_file",
			ZSW(mrep.max_free_frag_file), "max_free_frag_func",
			ZSW(mrep.max_free_frag_func), "max_free_frag_mname",
			ZSW(mrep.max_free_frag_mname), "max_free_frag_line",
			mrep.max_free_frag_line, "max_used_frag_size",
			mrep.max_used_frag_size, "max_used_frag_file",
			ZSW(mrep.max_used_frag_file), "max_used_frag_func",
			ZSW(mrep.max_used_frag_func), "max_used_frag_mname",
			ZSW(mrep.max_used_frag_mname), "max_used_frag_line",
			mrep.max_used_frag_line, "min_free_frag_size",
			mrep.min_free_frag_size, "min_free_frag_file",
			ZSW(mrep.min_free_frag_file), "min_free_frag_func",
			ZSW(mrep.min_free_frag_func), "min_free_frag_mname",
			ZSW(mrep.min_free_frag_mname), "min_free_frag_line",
			mrep.min_free_frag_line, "min_used_frag_size",
			mrep.min_used_frag_size, "min_used_frag_file",
			ZSW(mrep.min_used_frag_file), "min_used_frag_func",
			ZSW(mrep.min_used_frag_func), "min_used_frag_mname",
			ZSW(mrep.min_used_frag_mname), "min_used_frag_line",
			mrep.min_used_frag_line);
}

/*clang-format off*/
static const char *corex_rpc_shm_rprint_doc[2] = {
		"Print shm status report to file matching the filter", 0};
/*clang-format on*/

/*
 * RPC command to print the shm status report
 */
static void corex_rpc_shm_rprint(rpc_t *rpc, void *ctx)
{
	char *fname = NULL;
	str fmatch = STR_NULL;
	FILE *fp = NULL;

	if(rpc->scan(ctx, "sS", &fname, &fmatch) < 2) {
		rpc->fault(ctx, 400, "Filename and filter not provided");
		return;
	}
	fp = fopen(fname, "w");
	if(fp == NULL) {
		LM_ERR("cannot open file: %s\n", fname);
		rpc->fault(ctx, 500, "Cannot open file");
		return;
	}
	LM_DBG("matching file name with: %.*s\n", fmatch.len, fmatch.s);
	shm_status_filter(&fmatch, fp);
	fclose(fp);
}

/* clang-format off */
rpc_export_t corex_rpc_shm_cmds[] = {
	{"shm.info", corex_rpc_shm_info, corex_rpc_shm_info_doc, 0},
	{"shm.report", corex_rpc_shm_report, corex_rpc_shm_report_doc, 0},
	{"shm.stats", corex_rpc_shm_stats, corex_rpc_shm_stats_doc, 0},
	{"shm.rprint", corex_rpc_shm_rprint, corex_rpc_shm_rprint_doc, 0},
	{0, 0, 0, 0}
};
/* clang-format on */

/**
 * register RPC shm commands
 */
int corex_init_rpc_shm(void)
{
	if(rpc_register_array(corex_rpc_shm_cmds) != 0) {
		LM_ERR("failed to register RPC shm commands\n");
		return -1;
	}
	return 0;
}
