/* Display information gleaned from a module's .modinfo section.
   Copyright 1996, 1997 Linux International.

   Contributed by Tom Dyas <tdyas@eden.rutgers.edu>

   This file is part of the Linux modutils.

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2 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
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */

/*
 * Fixes:
 *
 * optind++ in show_module_info, Salvador Ortiz Garcia <sog@msg.com.mx>
 * unified module path handling: Bjorn Ekwall <bj0rn@blox.se> February 1999
 * Rationalize common code for 32/64 bit architectures.
 *   Keith Owens <kaos@ocs.com.au> December 1999.
 * Add arch64().
 *   Keith Owens <kaos@ocs.com.au> December 1999.
 */

#ident "$Id: modinfo.c 1.8 Thu, 20 Apr 2000 14:31:49 +1000 kaos $"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/stat.h>

#include "module.h"
#include "obj.h"
#include "util.h"
#include "version.h"
#include "config.h"

extern int show_module_info(char *filename, char *fmtstr, int do_parameters);
extern int show_module_info_32(char *filename, char *fmtstr, int do_parameters);
extern int show_module_info_64(char *filename, char *fmtstr, int do_parameters);

#if defined(COMMON_3264) && defined(ONLY_32)
#define SHOW_MODULE_INFO show_module_info_32		/* 32 bit version */
#elif defined(COMMON_3264) && defined(ONLY_64)
#define SHOW_MODULE_INFO show_module_info_64		/* 64 bit version */
#else
#define SHOW_MODULE_INFO show_module_info		/* Not common 3264 code */
#endif

static char *get_modinfo_value(struct obj_file *f, const char *key)
{
	struct obj_section *sec;
	char *p, *v, *n, *ep;
	size_t klen = strlen(key);

	sec = obj_find_section(f, ".modinfo");
	if (sec == NULL)
		return NULL;

	p = sec->contents;
	ep = p + sec->header.sh_size;
	while (p < ep) {
		v = strchr(p, '=');
		n = strchr(p, '\0');
		if (v) {
			if (v - p == klen && strncmp(p, key, klen) == 0)
				return v + 1;
		} else {
			if (n - p == klen && strcmp(p, key) == 0)
				return n;
		}
		p = n + 1;
	}

	return NULL;
}

static int append_modinfo_tag(struct obj_file *f, char *tag, char *def,
			      char **out_str, int n)
{
	char *p;

	p = get_modinfo_value(f, tag);
	if (!p && !def)
		return -1;

	if (!p)
		p = def;

	if (strlen(p) < n)
		n = strlen(p);

	strncpy(*out_str, p, n);
	*out_str += n;

	return 0;
}

/* Format the "format" string based on the module contents. */
static char *format_query_string(struct obj_file *f, char *format)
{
	static char buffer[1024];
	char *in_str = format, *out_str = &buffer[0];
	char *last_char = buffer + sizeof(buffer) - 1;

	while (*in_str && out_str < last_char) {
		/* Just copy normal characters into the output. */
		if (*in_str != '%') {
			*out_str++ = *in_str++;
			continue;
		}
		/* Ensure that the replacement is there. */
		if (*++in_str == '\0') {
			error("missing replacement");
			return NULL;
		}
		/* Process the replacement as required. */
		switch (*in_str++) {
		case '%':
			*out_str++ = '%';
			break;

		case 'a':
			(void) append_modinfo_tag(f, "author", "<none>",
				&out_str, last_char - out_str);
			break;

		case 'd':
			(void) append_modinfo_tag(f, "description", "<none>",
				&out_str, last_char - out_str);
			break;

		case '{':{
				char tag[128], *end = strchr(in_str, '}');

				/* Make sure the %{...} is formatted correctly. */
				if (!end) {
					error("unterminated %%{...} construct");
					return NULL;
				}
				if (end - in_str > sizeof(tag) - 1) {
					error("%%{...} construct is too large");
					return NULL;
				}
				/* Copy out the tag name. */
				memset(tag, 0, sizeof(tag));
				strncpy(tag, in_str, end - in_str);
				tag[end - in_str] = '\0';

				/* Append the tag's value if it exists. */
				if (append_modinfo_tag(f, tag, NULL, &out_str, last_char - out_str) < 0) {
					error("%s: nonexistent tag", tag);
					return NULL;
				}
				/* Advance past the end of the replacement. */
				in_str = end + 1;
				break;
			}

		default:
			error("%c: unknown replacement", in_str[-1]);
			return NULL;
		}		/* switch */
	}			/* while */

	*out_str = '\0';
	return &buffer[0];
}

static void show_parameter(struct obj_file *f, char *key, char *value,
			   char *desc)
{
	struct obj_symbol *sym;
	int min, max;
	char *p = value;

	sym = obj_find_symbol(f, key);
	if (sym == NULL)
		printf("warning: symbol for parameter %s not found\n", key);

	if (isdigit(*p)) {
		min = strtoul(p, &p, 10);
		if (*p == '-')
			max = strtoul(p + 1, &p, 10);
		else
			max = min;
	} else
		min = max = 1;

	if (max < min)
		printf("warning: parameter %s has max < min!\n", key);

	printf("%s ", key);

	switch (*p) {
	case 'c':
		printf("char");
		break;
	case 'b':
		printf("byte");
		break;
	case 'h':
		printf("short");
		break;
	case 'i':
		printf("int");
		break;
	case 'l':
		printf("long");
		break;
	case 's':
		printf("string");
		break;
	case '\0':
		printf("no format character!\n");
		return;
	default:
		printf("unknown format character %c", *p);
		return;
	}

	if (min > 1 || max > 1)
		printf(" array (min = %d, max = %d)", min, max);

	if (desc)
		printf(", description \"%s\"", desc);

	printf("\n");
}

static void show_module_parameters(struct obj_file *f)
{
	struct obj_section *sec;
	char *ptr, *value, *n, *endptr;
	int namelen;

	sec = obj_find_section(f, ".modinfo");
	if (sec == NULL) {
		error("module does not support typed parameters");
		return;
	}
	ptr = sec->contents;
	endptr = ptr + sec->header.sh_size;
	while (ptr < endptr) {
		value = strchr(ptr, '=');
		n = strchr(ptr, '\0');
		if (value) {
			namelen = value - ptr;
			if (namelen >= 5 && strncmp(ptr, "parm_", 5) == 0
			    && !(namelen > 10 && strncmp(ptr, "parm_desc_", 10) == 0)) {
				char *pname = xmalloc(namelen + 1);
				char *desckey = xmalloc(namelen + 5 + 1);

				strncpy(pname, ptr + 5, namelen - 5);
				pname[namelen - 5] = '\0';

				strcpy(desckey, "parm_desc_");
				strncat(desckey, ptr + 5, namelen - 5);
				desckey[namelen + 5] = '\0';

				show_parameter(f, pname, value + 1, get_modinfo_value(f, desckey));

				free(pname);
				free(desckey);
			}
		} else {
			if (n - ptr >= 5 && strncmp(ptr, "parm_", 5) == 0) {
				error("parameter %s found with no value", ptr);
			}
		}
		ptr = n + 1;
	}
}

int SHOW_MODULE_INFO(char *filename, char *fmtstr, int do_parameters)
{
	FILE *fp;
	struct obj_file *f;
	char *p;

	/* Locate the file to be loaded.  */
	if (!strchr(filename, '/') && !strchr(filename, '.')) {
		char *tmp = search_module_path(filename);
		if (tmp == NULL) {
			error("%s: no module by that name found", filename);
			return 1;
		}
		filename = tmp;
	}
	error_file = filename;

	/* Attempt to open and parse the module file. */
	if ((fp = fopen(filename, "r")) == NULL) {
		error("%s: %m", filename);
		return -1;
	} else if ((f = obj_load(fp, ET_REL)) == NULL)
		return -1;
	fclose(fp);

	if (do_parameters) {
		show_module_parameters(f);
	} else if (fmtstr) {
		p = format_query_string(f, fmtstr);
		if (p)
			fputs(p, stdout);
	}
	return 0;
}

/* For common 3264 code, add an overall show_module_info, in the 64 bit version. */
#if defined(COMMON_3264) && defined(ONLY_64)
int show_module_info(char *filename, char *fmtstr, int do_parameters)
{
	if (arch64())
		return show_module_info_64(filename, fmtstr, do_parameters);
	else
		return show_module_info_32(filename, fmtstr, do_parameters);
}
#endif  /* defined(COMMON_3264) && defined(ONLY_64) */

/* For common 3264 code, only compile the usage message once, in the 64 bit version */
#if defined(COMMON_3264) && defined(ONLY_32)
extern void modinfo_usage(void);	/* Use the copy in the 64 bit version */
#else	/* Common 64 bit version or any non common code - compile usage routine */
void modinfo_usage(void)
{
	printf("usage: modinfo <parameters> <module>\n");
	printf("  -a, --author            display module author\n");
	printf("  -d, --description       display module description\n");
	printf("  -f <str>\n");
	printf("      --format <str>      print the <query-string>\n");
	printf("  -p, --parameters        display module parameters\n");
	printf("  -V, --version           show version\n");
	printf("  -h, --help              this usage screen\n");
}
#endif  /* defined(COMMON_3264) && defined(ONLY_32) */


/* For common 3264 code, only compile main in the 64 bit version. */
#if defined(COMMON_3264) && defined(ONLY_32)
/* Use the main in the 64 bit version */
#else
int main(int argc, char *argv[])
{
	static struct option long_opts[] =
	{
		{"author", 0, 0, 'a'},
		{"description", 0, 0, 'd'},
		{"format", 0, 0, 'f'},
		{"parameters", 0, 0, 'p'},
		{"version", 0, 0, 'V'},
		{"help", 0, 0, 'h'},
		{0, 0, 0, 0}
	};
	int do_parameters = 0, opt;
	char *fmtstr = NULL;

	while ((opt = getopt_long(argc, argv, "adq:pVh",
				  &long_opts[0], NULL)) != EOF)
		switch (opt) {
		case 'a':
			fmtstr = "%a\n";
			break;
		case 'd':
			fmtstr = "%d\n";
			break;
		case 'f':
			fmtstr = xstrdup(optarg);
			break;
		case 'p':
			do_parameters = 1;
			break;
		case 'V':
			fputs("modinfo (Linux modutils) " MODUTILS_VERSION "\n", stdout);
			exit(0);
		case 'h':
		default:
			modinfo_usage();
			exit(opt == 'h' ? 0 : 1);
		}

	if (optind >= argc) {
		modinfo_usage();
		exit(1);
	}
	error_file = "modinfo";
	while (optind < argc)
		show_module_info(argv[optind++], fmtstr, do_parameters);

	return 0;
}
#endif  /* defined(COMMON_3264) && defined(ONLY_32) */
