/*  ksym_mod.c - functions for building symbol lookup tables for klogd
 *  Copyright (c) 1995, 1996  Dr. G.W. Wettstein <greg@wind.rmcc.com>
 *  Copyright (c) 1996 Enjellic Systems Development
 *  Copyright (c) 1998-2007 Martin Schulze <joey@infodrom.org>
 *  Copyright (C) 2007-2009 Rainer Gerhards <rgerhards@adiscon.com>
 *
 * This file is part of rsyslog.
 *
 * Rsyslog 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 3 of the License, or
 * (at your option) any later version.
 *
 * Rsyslog 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 Rsyslog.  If not, see <http://www.gnu.org/licenses/>.
 *
 * A copy of the GPL can be found in the file "COPYING" in this distribution.
*/

/*
 * This file implements functions which are useful for building
 * a symbol lookup table based on the in kernel symbol table
 * maintained by the Linux kernel.
 *
 * Proper logging of kernel panics generated by loadable modules
 * tends to be difficult.  Since the modules are loaded dynamically
 * their addresses are not known at kernel load time.  A general
 * protection fault (Oops) cannot be properly deciphered with 
 * classic methods using the static symbol map produced at link time.
 *
 * One solution to this problem is to have klogd attempt to translate
 * addresses from module when the fault occurs.  By referencing the
 * the kernel symbol table proper resolution of these symbols is made
 * possible.
 *
 * At least that is the plan.
 *
 * Wed Aug 21 09:20:09 CDT 1996:  Dr. Wettstein
 *	The situation where no module support has been compiled into a
 *	kernel is now detected.  An informative message is output indicating
 *	that the kernel has no loadable module support whenever kernel
 *	module symbols are loaded.
 *
 *	An informative message is printed indicating the number of kernel
 *	modules and the number of symbols loaded from these modules.
 *
 * Sun Jun 15 16:23:29 MET DST 1997: Michael Alan Dorman
 *	Some more glibc patches made by <mdorman@debian.org>.
 *
 * Sat Jan 10 15:00:18 CET 1998: Martin Schulze <joey@infodrom.north.de>
 *	Fixed problem with klogd not being able to be built on a kernel
 *	newer than 2.1.18.  It was caused by modified structures
 *	inside the kernel that were included.  I have worked in a
 *	patch from Alessandro Suardi <asuardi@uninetcom.it>.
 *
 * Sun Jan 25 20:57:34 CET 1998: Martin Schulze <joey@infodrom.north.de>
 *	Another patch for Linux/alpha by Christopher C Chimelis
 *	<chris@classnet.med.miami.edu>.
 *
 * Thu Mar 19 23:39:29 CET 1998: Manuel Rodrigues <pmanuel@cindy.fe.up.pt>
 *	Changed lseek() to llseek() in order to support > 2GB address
 *	space which provided by kernels > 2.1.70.
 *
 * Mon Apr 13 18:18:45 CEST 1998: Martin Schulze <joey@infodrom.north.de>
 *	Removed <sys/module.h> as it's no longer part of recent glibc
 *	versions.  Added prototyp for llseek() which has been
 *	forgotton in <unistd.h> from glibc.  Added more log
 *	information if problems occurred while reading a system map
 *	file, by submission from Mark Simon Phillips <M.S.Phillips@nortel.co.uk>.
 *
 * Sun Jan  3 18:38:03 CET 1999: Martin Schulze <joey@infodrom.north.de>
 *	Corrected return value of AddModule if /dev/kmem can't be
 *	loaded.  This will prevent klogd from segfaulting if /dev/kmem
 *	is not available.  Patch from Topi Miettinen <tom@medialab.sonera.net>.
 *
 * Tue Sep 12 23:11:13 CEST 2000: Martin Schulze <joey@infodrom.ffis.de>
 *	Changed llseek() to lseek64() in order to skip a libc warning.
 */

/* Includes. */
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#if !defined(__GLIBC__)
#include <linux/time.h>
#include <linux/module.h>
#else /* __GLIBC__ */
#include "module.h"
#endif /* __GLIBC__ */
#include <stdarg.h>
#include <paths.h>
#include <linux/version.h>

#include "rsyslog.h"
#include "imklog.h"
#include "ksyms.h"

#define KSYMS  "/proc/kallsyms"

static int num_modules = 0;
struct Module *sym_array_modules = (struct Module *) NULL;

static int have_modules = 0;


/* Function prototypes. */
static void FreeModules(void);
static int AddSymbol(const char *);
struct Module *AddModule(const char *);
static int symsort(const void *, const void *);

/* Imported from ksym.c */
extern int num_syms;


/**************************************************************************
 * Function:	InitMsyms
 *
 * Purpose:	This function is responsible for building a symbol
 *		table which can be used to resolve addresses for
 *		loadable modules.
 *
 * Arguements:	Void
 *
 * Return:	A boolean return value is assumed.
 *
 *		A false value indicates that something went wrong.
 *
 *		True if loading is successful.
 **************************************************************************/
extern int InitMsyms(void)
{

        auto int        rtn,
                        tmp;
        FILE *ksyms;
        char buf[128];
        char *p;

	/* Initialize the kernel module symbol table. */
	FreeModules();

       ksyms = fopen(KSYMS, "r");

        if ( ksyms == NULL ) {
                if ( errno == ENOENT )
                        imklogLogIntMsg(LOG_INFO, "No module symbols loaded - "
                               "kernel modules not enabled.\n");
                else
                        imklogLogIntMsg(LOG_ERR, "Error loading kernel symbols " \
                               "- %s\n", strerror(errno));
                return(0);
        }

	dbgprintf("Loading kernel module symbols - Source: %s\n", KSYMS);

        while ( fgets(buf, sizeof(buf), ksyms) != NULL ) {
                if (num_syms > 0 && index(buf, '[') == NULL)
                        continue;

                p = index(buf, ' ');

                if ( p == NULL )
                        continue;

                if ( buf[strlen(buf)-1] == '\n' )
                        buf[strlen(buf)-1] = '\0';
                /* overlong lines will be ignored above */

                AddSymbol(buf);
        }

	if(ksyms != NULL)
		fclose(ksyms);

        have_modules = 1;

        /* Sort the symbol tables in each module. */
        for (rtn = tmp = 0; tmp < num_modules; ++tmp) {
                rtn += sym_array_modules[tmp].num_syms;
                if ( sym_array_modules[tmp].num_syms < 2 )
                        continue;
                qsort(sym_array_modules[tmp].sym_array, \
                      sym_array_modules[tmp].num_syms, \
                      sizeof(struct sym_table), symsort);
        }

        if ( rtn == 0 )
                imklogLogIntMsg(LOG_INFO, "No module symbols loaded.");
        else
                imklogLogIntMsg(LOG_INFO, "Loaded %d %s from %d module%s", rtn, \
                       (rtn == 1) ? "symbol" : "symbols", \
                       num_modules, (num_modules == 1) ? "." : "s.");

        return(1);
}


static int symsort(const void *p1, const void *p2)
{
	auto const struct sym_table	*sym1 = p1,
					*sym2 = p2;

	if ( sym1->value < sym2->value )
		return(-1);
	if ( sym1->value == sym2->value )
		return(0);
	return(1);
}


extern void DeinitMsyms(void)
{
	FreeModules();
}


/**************************************************************************
 * Function:	FreeModules
 *
 * Purpose:	This function is used to free all memory which has been
 *		allocated for the modules and their symbols.
 *
 * Arguements:	None specified.
 *
 * Return:	void
 **************************************************************************/
static void FreeModules()
{
        auto int        nmods,
                        nsyms;
        auto struct Module *mp;

        /* Check to see if the module symbol tables need to be cleared. */
        have_modules = 0;
        if ( num_modules == 0 )
                return;

        if ( sym_array_modules == NULL )
                return;

        for (nmods = 0; nmods < num_modules; ++nmods) {
                mp = &sym_array_modules[nmods];
                if ( mp->num_syms == 0 )
                        continue;

                for (nsyms= 0; nsyms < mp->num_syms; ++nsyms)
                        free(mp->sym_array[nsyms].name);
                free(mp->sym_array);
                if ( mp->name != NULL )
                        free(mp->name);
        }

        free(sym_array_modules);
        sym_array_modules = (struct Module *) NULL;
        num_modules = 0;
        return;
}


/**************************************************************************
 * Function:    AddModule
 *
 * Purpose:     This function is responsible for adding a module to
 *              the list of currently loaded modules.
 *
 * Arguments:   (const char *) module
 *
 *              module:->       The name of the module.
 *
 * Return:      struct Module *
 **************************************************************************/

struct Module *AddModule(module)
     const char *module;
{
        struct Module *mp;

        if ( num_modules == 0 ) {
                sym_array_modules = (struct Module *)malloc(sizeof(struct Module));

                if ( sym_array_modules == NULL )
                {
                        imklogLogIntMsg(LOG_WARNING, "Cannot allocate Module array.\n");
                        return NULL;
                }
                mp = sym_array_modules;
        } else {
                /* Allocate space for the module. */
                mp = (struct Module *) \
                        realloc(sym_array_modules, \
                                (num_modules+1) * sizeof(struct Module));

                if ( mp == NULL )
                {
                        imklogLogIntMsg(LOG_WARNING, "Cannot allocate Module array.\n");
                        return NULL;
                }

                sym_array_modules = mp;
                mp = &sym_array_modules[num_modules];
        }

        num_modules++;
        mp->sym_array = NULL;
        mp->num_syms = 0;

        if ( module != NULL )
                mp->name = strdup(module);
        else
                mp->name = NULL;

        return mp;
}


/**************************************************************************
 * Function:	AddSymbol
 *
 * Purpose:	This function is responsible for adding a symbol name
 *		and its address to the symbol table.
 *
 * Arguements:	const char *
 *
 * Return:	int
 *
 *		A boolean value is assumed.  True if the addition is
 *		successful.  False if not.
 **************************************************************************/
static int AddSymbol(line)
        const char *line;
{
        char *module;
        unsigned long address;
        char *p;
        static char *lastmodule = NULL;
        struct Module *mp;

        module = index(line, '[');

        if ( module != NULL ) {
                p = index(module, ']');
                if ( p != NULL )
                        *p = '\0';
                p = module++;
                while ( isspace(*(--p)) )
			/*SKIP*/;
                *(++p) = '\0';
        }

        p = index(line, ' ');

        if ( p == NULL )
                return(0);

        *p = '\0';

        address  = strtoul(line, (char **) 0, 16);

        p += 3;

        if ( num_modules == 0 ||
             ( lastmodule == NULL && module != NULL ) ||
             ( module == NULL && lastmodule != NULL) ||
             ( module != NULL && strcmp(module, lastmodule))) {
                mp = AddModule(module);

                if ( mp == NULL )
                        return(0);
        } else
                mp = &sym_array_modules[num_modules-1];

        lastmodule = mp->name;

        /* Allocate space for the symbol table entry. */
        mp->sym_array = (struct sym_table *) realloc(mp->sym_array, \
                (mp->num_syms+1) * sizeof(struct sym_table));

        if ( mp->sym_array == (struct sym_table *) NULL )
                return(0);

        mp->sym_array[mp->num_syms].name = strdup(p);
        if ( mp->sym_array[mp->num_syms].name == (char *) NULL )
                return(0);

        /* Stuff interesting information into the module. */
        mp->sym_array[mp->num_syms].value = address;
        ++mp->num_syms;

        return(1);
}



/**************************************************************************
 * Function:	LookupModuleSymbol
 *
 * Purpose:	Find the symbol which is related to the given address from
 *		a kernel module.
 *
 * Arguements:	(long int) value, (struct symbol *) sym
 *
 *		value:->	The address to be located.
 * 
 *		sym:->		A pointer to a structure which will be
 *				loaded with the symbol's parameters.
 *
 * Return:	(char *)
 *
 *		If a match cannot be found a diagnostic string is printed.
 *		If a match is found the pointer to the symbolic name most
 *		closely matching the address is returned.
 *
 * TODO: We are using int values for the offset, but longs for the value
 * values. This may create some trouble in the future (on 64 Bit OS?).
 * Anyhow, I have not changed this, because we do not seem to have any
 * issue and my understanding of this code is limited (and I don't see
 * need to invest more time to dig much deeper).
 * rgerhards, 2009-04-17
 **************************************************************************/
extern char * LookupModuleSymbol(value, sym)
        unsigned long value;
        struct symbol *sym;
{
        int        nmod, nsym;
        struct sym_table *last;
        struct Module *mp;
        static char ret[100];

        sym->size = 0;
        sym->offset = 0;
        if ( num_modules == 0 )
                return((char *) 0);

        for (nmod = 0; nmod < num_modules; ++nmod) {
                mp = &sym_array_modules[nmod];

                /* Run through the list of symbols in this module and
                 * see if the address can be resolved.
		 */
                for(nsym = 1, last = &mp->sym_array[0];
                    nsym < mp->num_syms;
                    ++nsym) {
                        if ( mp->sym_array[nsym].value > value )
                        {
                           if ( sym->size == 0 ||
                                 (int) (value - last->value) < sym->offset ||
                                 ( (sym->offset == (int) (value - last->value)) &&
                                   (int) (mp->sym_array[nsym].value-last->value) < sym->size ) )
                            {
                                sym->offset = value - last->value;
                                sym->size = mp->sym_array[nsym].value - last->value;
                                ret[sizeof(ret)-1] = '\0';
                                if ( mp->name == NULL )
                                        snprintf(ret, sizeof(ret)-1,
                                                 "%s", last->name);
                                else
                                        snprintf(ret, sizeof(ret)-1,
                                                 "%s:%s", mp->name, last->name);
                            }
                            break;
                        }
                        last = &mp->sym_array[nsym];
                }
        }

        if ( sym->size > 0 )
                return(ret);

        /* It has been a hopeless exercise. */
        return(NULL);
}
