/*
 * Specification: /etc/conf.modules / format
 *	Modules may be located at different place in the filesystem.
 *	We expect some standard to emerge. We expect that the
 *	FSSTND will address this in the future.
 *
 *	There will always be some need to override this, especially for
 *	modules developpers.
 *
 *	The file /etc/conf.modules will contain different definition to
 *	control the manipulation of the module.
 *
 *	The format will be fairly simple:
 *
 *	parameter=value
 *	.
 *	parameter=value
 *
 *	Standard Unix style comments and continuation line are supported.
 *	Comments begin with a # and continue until the end of the line.
 *	A line continue on the next one if the last non-white character
 *	is a \.
 */
/* #Specification: /etc/conf.modules / format / official name */
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <signal.h>
#include <limits.h>
#include "link.h"

struct PATH_TYPE{
	char *type;
	char *path;
};

static PATH_TYPE tb[100];
static int nb=0;

struct EXEC_TYPE {
	int when;
	char *module;
	char *cmd;
};

static struct EXEC_TYPE ex[100];
static int nex = 0;

static char *depfile=NULL;
char *insmod_opt = NULL;

static char *tbpath[]={
	"/lib/modules/VERSION", /* just a placeholder, look at config_read */
	"/lib/modules/default",
	"/lib/modules",
	NULL /* marks the end of the list! */
};

static char *tbtype[]={
	"fs",
	"misc",
	"net",
	"scsi",
	"block",
	"cdrom",
	"ipv4",
	NULL /* marks the end of the list! */
};

static char *aliaslist[100]={
	"binfmt-204 binfmt_aout",
	"binfmt-263 binfmt_aout",
	"binfmt-264 binfmt_aout",
	"binfmt-267 binfmt_aout",
	"binfmt-332 iBCS",
	"binfmt--310 binfmt_java",

	"block-major-1 rd",
	"block-major-2 floppy",
	"block-major-3 off", /* not modularized! */
	"block-major-7 loop",
	"block-major-8 sd_mod",
	"block-major-11 sr_mod",
	"block-major-13 xd",
	"block-major-15 cdu31a",
	"block-major-16 gscd",
	"block-major-17 optcd",
	"block-major-18 sjcd",
	"block-major-20 mcdx",
	"block-major-23 mcd",
	"block-major-24 sonycd535",
	"block-major-25 sbpcd",
	"block-major-26 sbpcd",
	"block-major-27 sbpcd",
	"block-major-29 aztcd",
	"block-major-32 cm206",

	"char-major-4 serial",
	"char-major-5 serial",
	"char-major-6 lp",
	"char-major-9 st",
	"char-major-10 misc", 	/* was: mouse */
	"char-major-10-0 busmouse",	/* /dev/logibm Logitech bus mouse */
	"char-major-10-1 psaux",	/* /dev/psaux PS/2-style mouse port */
	"char-major-10-2 msbusmouse",	/* /dev/inportbm Microsoft Inport bus mouse */
	"char-major-10-3 atixlmouse",	/* /dev/atibm ATI XL bus mouse */
					/* /dev/jbm J-mouse */
					/* /dev/amigamouse Amiga mouse (68k/Amiga) */
					/* /dev/atarimouse Atari mouse */
					/* /dev/sunmouse Sun mouse */
					/* /dev/beep Fancy beep device */
					/* /dev/modreq Kernel module load request */
	"char-major-10-130 wdt",	/* /dev/watchdog Watchdog timer port */
	"char-major-10-131 wdt",	/* /dev/temperature Machine internal temperature */
					/* /dev/hwtrap Hardware fault trap */
					/* /dev/exttrp External device trap */
	"char-major-14 sound",
	"char-major-19 cyclades",
	"char-major-20 cyclades",
	"char-major-21 sg",
	"char-major-27 ftape",
	"char-major-34 scc",
	"char-major-35 tclmidi",
	"char-major-36 netlink",
	"char-major-48 riscom8",
	"char-major-49 riscom8",
	"char-major-63 kdebug",

	"dos msdos",
	"dummy0 dummy",
	"dummy1 dummy",
	"eth0 off",
	"iso9660 isofs",
	"md-personality-1 linear",
	"md-personality-2 raid0",
	"net-pf-3 off",
				/* PF_UNIX	1  Unix domain sockets */
				/* PF_INET	2  Internet IP Protocol */
				/* PF_AX25	3  Amateur Radio AX.25 */
	"net-pf-4 ipx",		/* PF_IPX	4  Novell IPX */
	"net-pf-5 appletalk",	/* PF_APPLETALK	5  Appletalk DDP */
				/* PF_NETROM	6  Amateur radio NetROM */
				/* PF_BRIDGE	7  Multiprotocol bridge */
				/* PF_AAL5	8  Reserved for Werner's ATM */
				/* PF_X25	9  Reserved for X.25 project */
				/* PF_INET6	10 IP version 6 */

	"netalias-2 ip_alias",
	"plip0 plip",
	"plip1 plip",
	"ppp0 ppp",
	"ppp1 ppp",
	"scsi_hostadapter off", /* if not in config file */
	"slip0 slip",
	"slip1 slip",
	"tty-ldisc-1 slip",
	"tty-ldisc-3 ppp",
	NULL /* marks the end of the list! */
};

char *optlist[100]={
	"dummy0 -o dummy0",
	"dummy1 -o dummy1",
	NULL /* marks the end of the list! */
};

/*
 *	Do a popen with error message reporting.
 *	If quit != 0, the function does not return if an error occur.
 *
 *	Return the FILE *. Must be close with pclose().
 */
FILE *popen_err (
	const char *cmd,
	const char *mode,
	int quit)
{
	FILE *ret = popen (cmd,mode);
	if (ret == NULL){
		depmod_error("Can't execute: %s",cmd);
		if (quit) exit(-1);
	}
	return ret;
}

static char *next_word(char *pt)
{
	char *pt2;

	for (pt2 = pt; *pt2 && !(isspace(*pt2)); ++pt2)
		;

	if (*pt2) {
		*pt2++ = '\0';
		while (*pt2 && isspace(*pt2))
			++pt2;
	}
	return pt2;
}

static char *no_uname_r (char *pt, char *force)
{
	char *p;
	char *s;

	if ((p = strchr(pt, '`')) != NULL) {
		for (s = p+1; isspace(*s); ++s)
			;
		if (strncmp(s, "uname", 5) == 0) {
			while (*s && (*s != '`'))
				++s;
			if (*s == '`') { /* Oh dear, belt and suspenders... */
				char no_uname_buf[100];
				*p = '\0';
				sprintf(no_uname_buf, "%s%s%s", pt, force, s+1);
				return strdup_err (no_uname_buf);
			}
		}
	}

	return strdup_err (pt);
}

/*
 *	Read the configuration file.
 *	Return -1 if any error. Error messages a generated.
 */
int config_read (char *force_ver)
{
	struct utsname uts_info;
	int assgn;
	int drop_default_paths = 1;
	int noline = 0;
	int ret = 0;
	char buf[3000];
	char depfile_tmp[PATH_MAX];
	char firstline[100];

	if (force_ver)
		sprintf(depfile_tmp, "/lib/modules/%s/modules.dep", force_ver);
	else
		depfile_tmp[0] = '\0';

	/*
	 * Specification: /etc/conf.modules / missing
	 * This file is optional. No error is printed if it
	 * is missing. If it is missing the following content
	 * is assumed.
	 *
	 * path[boot]=/lib/modules/boot
	 * path[fs]=/lib/modules/`uname -r`/fs
	 * path[misc]=/lib/modules/`uname -r`/misc
	 * path[net]=/lib/modules/`uname -r`/net
	 * path[scsi]=/lib/modules/`uname -r`/scsi
	 *
	 * path[fs]=/lib/modules/default/fs
	 * path[misc]=/lib/modules/default/misc
	 * path[net]=/lib/modules/default/net
	 * path[scsi]=/lib/modules/default/scsi
	 * 
	 * path[fs]=/lib/modules/fs
	 * path[misc]=/lib/modules/misc
	 * path[net]=/lib/modules/net
	 * path[scsi]=/lib/modules/scsi
	 *
	 * The idea is that modprobe will look first it the
	 * modules compiled for the current release of the kernel.
	 * If not found, it will look into the default release.
	 * And if not found, it will look in the other directory.
	 * 
	 * The strategy should be like this. When you install a
	 * new linux, the modules should go in a directory
	 * related to the release of the kernel you are installing.
	 * Then you do a symlink default to this directory.
	 * 
	 * Each time you compile a new kernel, the make modules_install
	 * will set new directory, but won't change de default.
	 *
	 * When you get a module unrelated to the kernel distribution
	 * you place it in one of the last three directory.
	 * 
	 * This is the default strategy. Off course you can overide
	 * this in /etc/conf.modules.
	 */

	/*
	 * Build the default "path[type]" configuration
	 */
	uname(&uts_info);
	sprintf (firstline,"/lib/modules/%s",
		force_ver?force_ver:uts_info.release);
	tbpath[0] = firstline;

	tb[0].type = strdup_err("boot");
	tb[0].path = strdup_err("/lib/modules/boot");
	nb = 1;

	for (char **pathp = tbpath; *pathp; ++pathp) {
		for (char **type = tbtype; *type; ++type) {
			char path[100];
			sprintf (path,"%s/%s", *pathp, *type);
			tb[nb].type = strdup_err(*type);
			tb[nb].path = strdup_err(path);
			nb++;
		}
	}

	FILE *fin = fopen (ETC_CONF_MODULES,"r");
	if (fin == NULL)
		fin = fopen("/etc/modules.conf", "r"); /* So what... */

	while (fin && fgets_strip(buf, sizeof(buf)-1, fin, &noline) != NULL) {
		char *parm = str_skip (buf);
		if (*parm == '\0')
			continue;

		int one_err = 1;
		char *pt = parm;
		char *pt2;
		while (*pt > ' ' && *pt != '=')
			pt++;

		if (*pt == '=')
			assgn = 1;
		else
			assgn = 0;
		*pt++ = '\0';
		pt = str_skip (pt);

		/*
		 * options
		 */
		if (!assgn && strcmp(parm,"options")==0){
			// Replace previous (default) definitions
			int i;
			char modname[100];
			sscanf(pt, "%s", modname);
			int len = strlen(modname);
			for (i = 0; optlist[i]; ++i) {
				if (strncmp(optlist[i], modname, len) == 0
					&& isspace(optlist[i][len])) {
					optlist[i] = strdup_err(pt);
					break;
				}
			}
			if (optlist[i] == (char *)0) {
				optlist[i++] = strdup_err(pt);
				optlist[i] = (char *)0;
			}
			one_err = 0;
		}

		/*
		 * alias
		 */
		else if (!assgn && strcmp(parm,"alias")==0){
			// Replace previous (default) definitions
			char modname[100];
			sscanf(pt, "%s", modname);
			int len = strlen(modname);
			int i;
			for (i = 0; aliaslist[i]; i++) {
				if (strncmp(aliaslist[i], modname, len) == 0
					&& isspace(aliaslist[i][len])) {
					aliaslist[i] = strdup_err(pt);
					break;
				}
			}
			if (aliaslist[i] == (char *)0) {
				aliaslist[i++] = strdup_err(pt);
				aliaslist[i] = (char *)0;
			}
			one_err = 0;
		}

		/*
		 * keep
		 */
		else if (!assgn && (strcmp(parm,"keep") == 0)) {
			drop_default_paths = 0;
			one_err = 0;
		}

		/*
		 * pre-install
		 */
		else if (!assgn && (strcmp(parm,"pre-install")==0)) {
			ex[nex].when = EXEC_PRE_INSTALL;
			pt2 = next_word(pt);
			ex[nex].module = strdup_err(pt);
			ex[nex].cmd = strdup_err(pt2);
			++nex;
			one_err = 0;
		}


		/*
		 * install
		 */
		else if (!assgn && (strcmp(parm,"install")==0)) {
			ex[nex].when = EXEC_INSTALL;
			pt2 = next_word(pt);
			ex[nex].module = strdup_err(pt);
			ex[nex].cmd = strdup_err(pt2);
			++nex;
			one_err = 0;
		}

		/*
		 * post-install
		 */
		else if (!assgn && (strcmp(parm,"post-install")==0)) {
			ex[nex].when = EXEC_POST_INSTALL;
			pt2 = next_word(pt);
			ex[nex].module = strdup_err(pt);
			ex[nex].cmd = strdup_err(pt2);
			++nex;
			one_err = 0;
		}

		/*
		 * pre-remove
		 */
		else if (!assgn && (strcmp(parm,"pre-remove")==0)) {
			ex[nex].when = EXEC_PRE_REMOVE;
			pt2 = next_word(pt);
			ex[nex].module = strdup_err(pt);
			ex[nex].cmd = strdup_err(pt2);
			++nex;
			one_err = 0;
		}

		/*
		 * remove
		 */
		else if (!assgn && (strcmp(parm,"remove")==0)) {
			ex[nex].when = EXEC_REMOVE;
			pt2 = next_word(pt);
			ex[nex].module = strdup_err(pt);
			ex[nex].cmd = strdup_err(pt2);
			++nex;
			one_err = 0;
		}


		/*
		 * post-remove
		 */
		else if (!assgn && (strcmp(parm,"post-remove")==0)) {
			ex[nex].when = EXEC_POST_REMOVE;
			pt2 = next_word(pt);
			ex[nex].module = strdup_err(pt);
			ex[nex].cmd = strdup_err(pt2);
			++nex;
			one_err = 0;
		}

		/*
		 * insmod_opt=
		 */
		else if (assgn && (strcmp(parm,"insmod_opt")==0)) {
			insmod_opt = strdup_err(pt);
			one_err = 0;
		}

		/*
		 * depfile=
		 */
		else if (assgn && (strcmp(parm,"depfile")==0) &&
			  (depfile_tmp[0] == '\0')) {
			strcpy (depfile_tmp,pt);
			one_err = 0;
		}

		/*
		 * path...=
		 */
		else if (assgn && strncmp(parm,"path",4)==0){
			/*
			 * Specification: config file / path parameter
			 * The path parameter specify a directory to
			 * search for module. This parameter may
			 * be repeated multiple time.
			 *
			 * Optionally the path parameter carries
			 * a tag. This tells us a little more about
			 * the purpose of this directory and
			 * allows some automated operations.
			 * The tag is sticked to the path word
			 * enclose in square braket.
			 * #
			 * path[boot]=/lib/modules/boot
			 * #
			 *
			 * This identifies the path a of directory
			 * holding modules loadable a boot time.
			 */
			if (drop_default_paths){
				/*
				 * Specification: config file / path / default
				 * Whenever there is a path[] specification
				 * in the config file, all the default
				 * path are reset.	
				 *
				 * If one instead wants to _add_ to the default
				 * set of paths, one has to have the option
				 *    keep
				 * before the first path[]-specification line
				 * in the configuration file.
				 */
				drop_default_paths = 0;
				for (int n = 1; n < nb; n++){
					free (tb[n].path);
					free (tb[n].type);
				}
				nb = 1;
			}
			assert ((unsigned)nb < sizeof(tb)/sizeof(tb[0]));	

			/*
			 * If the tag is missing, the word "misc"
			 * is assumed.
			 */
			if (parm[4] == '\0'){
				tb[nb].type = strdup_err ("misc");
				if (force_ver)
					tb[nb].path = no_uname_r(pt, force_ver);
				else
					tb[nb].path = strdup_err (pt);
				nb++;
				one_err = 0;
			}
			/*
			 * get tag
			 */
			else if (parm[4] == '['){
				char *pt_type = parm+5;
				while (*pt_type != '\0' && *pt_type != ']')
					pt_type++;

				if (*pt_type == ']' && pt_type[1] == '\0'){
					*pt_type = '\0';		
					tb[nb].type = strdup_err (parm+5);
					if (force_ver)
						tb[nb].path = no_uname_r (pt, force_ver);
					else
						tb[nb].path = strdup_err (pt);
					nb++;
					one_err = 0;
				}
			}
		}

		/*
		 * any errors so far?
		 */
		if (one_err){
			depmod_error ("Invalid line %d in "
					ETC_CONF_MODULES "\n\t%s",
					noline,buf);
			ret = -1;
		}
	}
	if (fin) fclose (fin);

	if (ret != -1){
		if (depfile_tmp[0] == '\0'){
			/*
			 * Specification: config file / depfile parameter
			 * The default value for the depfile parameter is:
			 *
			 * depfile=/lib/modules/`uname -r`/modules.dep
			 *
			 * If the config file exist but lack a depfile
			 * specification, it is used also since the system
			 * can't work without one.
			 *
			 * Once we have the depfile value, we pass to the shell
			 * with a popen() to resolve whatever shell construct in
			 * its value. We execute a echo command.
			 *
			 * echo read_value
			 */
			struct utsname uts_info;
			uname(&uts_info);
			sprintf(depfile_tmp,"/lib/modules/%s/modules.dep",
				uts_info.release);
			depfile = strdup_err (depfile_tmp);
		}
		else { // depfile defined in conf.modules
			char cmd[PATH_MAX+10];
			sprintf (cmd,"echo %s",depfile_tmp);
			FILE *fin = popen_err (cmd,"r",0);
			if (fin != NULL){
				if (fgets(depfile_tmp,sizeof(depfile_tmp)-1,fin)!=NULL){
					strip_end (depfile_tmp);
					depfile = strdup_err (depfile_tmp);
				}
				else
					ret = -1;
				pclose (fin);
			}
		}
	}
	return ret;
}

/*
 *	Add conditionally a file name if it exist
 */
static int config_add (const char *acc, char *lst[], int &nb)
{
	int ret = 0;
	if (access(acc, R_OK) == 0) {
		lst[nb++] = strdup_err (acc);
		ret = 1;
	}
	return ret;
}

/*
 *	Find all modules matching the name "match" in directory of type "type"
 *
 *	Return the number of module located and placed in lst[]. The caller
 *	must free all entry in lst[] itself by doing a tbstr_free(lst,nb);
 */
int config_lstmod (
	const char *match,
	const char *type, // Type of directory (path[type]), or NULL to match all
	char *lst[],
	int many)	// Are we looking for the first match or all
{
	/*
	 * Specification: reading directory / echo command
	 * We are using the command echo to locate entries in directories.
	 * This has the advantage of allowing complex wildcard specification
	 * in /etc/conf.modules. For example.
	 *
	 * path[misc]=/lib/modules/1.1.5?/misc
	 */
	char cmd[1000];
	int find_one = 0;
	int ret = 0;
	{
		char *pt = stpcpy (cmd,"echo ");

		for (int i=0; i<nb; i++){
			if (type == NULL || strcmp(tb[i].type,type)==0){
				char acc[PATH_MAX];
				sprintf(acc, "%s/%s", tb[i].path, match);
				pt = stpcpy (pt,acc);
				if (!many && strpbrk(acc, SHELL_WILD)==NULL) {
					if (config_add (acc,lst,ret)) return 1;
				}

				*pt++ = ' ';
				find_one = 1;
			}
		}
		*pt = '\0';
	}

	if (find_one){
		int fd2 = dup(2);
		if (fd2 == -1){
			depmod_error ("Out of file handle");
		}else{
			close (2);
			FILE *fin = popen_err (cmd,"r",1);
			if (fin != NULL){
				char acc[PATH_MAX];
				char *pt = acc;
				int carac;
				while ((carac = fgetc(fin))!=EOF){
					if (!isspace (carac)){
						*pt++ = carac;
					}else if (pt > acc){
						*pt = '\0';
						config_add (acc,lst,ret);
						pt = acc;
					}
				}
				if (pt > acc){
					*pt = '\0';
					config_add (acc,lst,ret);
				}
				pclose (fin);
			}
			dup2(fd2,2);
			close (fd2);
		}
	}
	return ret;
}

/*
 *	Return the path of the depandancy file to produce
 */
const char *config_getdepfile()
{
	return depfile;
}

/*
 *	Print out all the configuration in use
 */
void config_show ()
{
	PATH_TYPE *pttb = tb;
	int i;
	printf ("# Generated by modprobe -c (%s)\n", DEPMOD_RELEASE);
	for (i=0; i<nb; i++, pttb++){
		if (pttb->type != NULL){
			printf ("path[%s]=%s\n",pttb->type,pttb->path);
		}else{
			printf ("path=%s\n",pttb->path);
		}
	}
	puts ("# Aliases");
	for (i=0; aliaslist[i] != (char *)0; i++)
		printf ("alias %s\n",aliaslist[i]);
	puts ("# Options");
	for (i=0; optlist[i]; i++)
		printf ("options %s\n",optlist[i]);
	if (nex > 0) {
		puts ("# Commands");
		for (i=0; i < nex; i++) {
			switch (ex[i].when) {
			case EXEC_PRE_INSTALL: printf("pre-install "); break;
			case EXEC_INSTALL: printf("install "); break;
			case EXEC_POST_INSTALL: printf("post-install "); break;
			case EXEC_PRE_REMOVE: printf("pre-remove "); break;
			case EXEC_REMOVE: printf("remove "); break;
			case EXEC_POST_REMOVE: printf("post-remove "); break;
			}
			printf ("%s %s\n", ex[i].module, ex[i].cmd);
		}
	}
}

/*
 *	Check if a module needs an external command
 *	while being loaded or unloaded.
 *	Return the command, or NULL if there was no command defined
 */
char *exec_cmd(int when, const char *mod)
{
	const char *modname = stripo(mod);
	/*
	 * Specification: /etc/conf.modules
	 * The format of the commands in /etc/conf.modules are:
	 *
	 *	pre-install module command
	 *	install module command
	 *	post-install module command
	 *	pre-remove module command
	 *	remove module command
	 *	post-remove module command
	 *
	 * The different words are separated by tabs or spaces.
	 */
	for (int i = 0; i < nex; i++) {
		if (ex[i].when != when)
			continue;
		if (strcmp(ex[i].module, modname) == 0)
			return ex[i].cmd;
	}
	return NULL;
}

/*
 *	Check if a module name is indeed an alias for another module.
 *	Return the real module or this one (mod) if it is not an alias
 */
const char *any_alias(const char *mod)
{
	const char *modname = stripo(mod);
	int len = strlen(modname);
	/*
	 * Specification: /etc/conf.modules / alias / format
	 * The format of the alias command in /etc/conf.modules
	 * is
	 * #
	 * alias alias_name module_name
	 * #
	 * THe different word are separated by tabs or spaces.
	 */
	for (int i = 0; aliaslist[i]; i++) {
		if (strncmp(aliaslist[i], modname, len) == 0
			&& isspace(aliaslist[i][len])) {
			return str_skip(&(aliaslist[i][len]));
		}
	}
	return mod;
}


/*
 *	Locate all module matching "match".
 *	Return the number of modules found. The caller must free the content
 *	of abs_path[].
 */
int config_locate (
	const char *match,
	char *abs_path[1000],
	const char *type)	// Restric search to path[type]
						// or type is NULL (No restriction)
{
	int ret=0;

	if (strchr(match,'/')!=NULL){
		abs_path[0] = strdup_err (match);
		return 0;
	}
	/* else */
	char *lst[1000];
	match = any_alias (match);
	if (!match || !(*match) || (strcmp(match, "off") == 0))
		return -1;

	char match_o[PATH_MAX];
	sprintf (match_o,"%s.o", match);
	int nb = config_lstmod (match_o,type,lst,
			strpbrk(match, SHELL_WILD) != NULL ? 1 : 0);
	if (nb == 0) {
		nb = config_lstmod (match,type,lst,
			strpbrk(match, SHELL_WILD) != NULL ? 1 : 0);
	}

	if (nb <= 0)
		return ret;
	/* else */

	// The list may contain many duplication of some module
	// for example, the module slip may be in /lib/modules/default/net
	// and in /lib/modules/x.y.z/net
	for (int i=0; i<nb; i++){
		char *found = lst[i];
		if (found == NULL)
			continue;

		abs_path[ret++] = found;
		lst[i] = NULL;
		char name[PATH_MAX];
		path_extrname (found,name);

		for (int j=i+1; j<nb; j++) {
			char *nxt = lst[j];
			if (nxt == NULL)
				continue;

			char name2[PATH_MAX];
			path_extrname(nxt,name2);
			if (strcmp(name,name2)==0){
				free (nxt);
				lst[j] = NULL;
			}
		}
	}
	tbstr_free (lst,nb);

	return ret;
}

#ifdef TEST

int main (int argc, char *argv[])
{
	if (argc != 3){
		depmod_error ("type and match");
	}else if (config_read(NULL) == -1){
		depmod_error ("Can't read configuration file");
	}else{
		char *lst[1000];
		int nb = config_lstmod (argv[1],argv[2],lst);
		for (int i=0; i<nb; i++){
			printf ("\t%s\n",lst[i]);
		}
		tbstr_free (lst,nb);
	}
	return 0;
}

#endif
