/* vi: set sw=4 ts=4: */
/*
 * simplified modprobe
 *
 * Copyright (c) 2008 Vladimir Dronnikov
 * Copyright (c) 2008 Bernhard Reutner-Fischer (initial depmod code)
 *
 * Licensed under GPLv2, see file LICENSE in this source tree.
 */

/* modprobe-small configs are defined in Config.src to ensure better
 * "make config" order */

//applet:IF_LSMOD(   IF_MODPROBE_SMALL(APPLET_NOEXEC( lsmod,    lsmod,    BB_DIR_SBIN, BB_SUID_DROP, lsmod   )))
//applet:IF_MODPROBE(IF_MODPROBE_SMALL(APPLET_NOEXEC( modprobe, modprobe, BB_DIR_SBIN, BB_SUID_DROP, modprobe)))
//                                     APPLET_ODDNAME:name      main      location     suid_type     help
//applet:IF_DEPMOD(  IF_MODPROBE_SMALL(APPLET_ODDNAME(depmod,   modprobe, BB_DIR_SBIN, BB_SUID_DROP, depmod  )))
//applet:IF_INSMOD(  IF_MODPROBE_SMALL(APPLET_NOEXEC( insmod,   modprobe, BB_DIR_SBIN, BB_SUID_DROP, insmod  )))
//applet:IF_RMMOD(   IF_MODPROBE_SMALL(APPLET_NOEXEC( rmmod,    modprobe, BB_DIR_SBIN, BB_SUID_DROP, rmmod   )))
/* noexec speeds up boot with many modules loaded (need SH_STANDALONE=y) */
/* I measured about ~5 times faster insmod */
/* depmod is not noexec, it runs longer and benefits from memory trimming via exec */

//kbuild:lib-$(CONFIG_MODPROBE_SMALL) += modprobe-small.o

#include "libbb.h"
/* After libbb.h, since it needs sys/types.h on some systems */
#include <sys/utsname.h> /* uname() */
#include <fnmatch.h>
#include <sys/syscall.h>

#define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)
#define delete_module(mod, flags) syscall(__NR_delete_module, mod, flags)
#ifdef __NR_finit_module
# define finit_module(fd, uargs, flags) syscall(__NR_finit_module, fd, uargs, flags)
#endif
/* linux/include/linux/module.h has limit of 64 chars on module names */
#undef MODULE_NAME_LEN
#define MODULE_NAME_LEN 64


#if 1
# define dbg1_error_msg(...) ((void)0)
# define dbg2_error_msg(...) ((void)0)
#else
# define dbg1_error_msg(...) bb_error_msg(__VA_ARGS__)
# define dbg2_error_msg(...) bb_error_msg(__VA_ARGS__)
#endif

#define DEPFILE_BB CONFIG_DEFAULT_DEPMOD_FILE".bb"

//usage:#if ENABLE_MODPROBE_SMALL

//usage:#define lsmod_trivial_usage
//usage:       ""
//usage:#define lsmod_full_usage "\n\n"
//usage:       "List loaded kernel modules"

//usage:#endif

#if ENABLE_LSMOD
int lsmod_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int lsmod_main(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
{
	xprint_and_close_file(xfopen_for_read("/proc/modules"));
	return EXIT_SUCCESS;
}
#endif

/* Num of applets that use modprobe_main() entry point. */
/* lsmod is not here. */
#define MOD_APPLET_CNT (ENABLE_MODPROBE + ENABLE_DEPMOD + ENABLE_INSMOD + ENABLE_RMMOD)

/* Do not bother if MODPROBE_SMALL=y but no applets selected. */
/* The rest of the file is in this if block. */
#if MOD_APPLET_CNT > 0

#define ONLY_APPLET (MOD_APPLET_CNT == 1)
#define is_modprobe (ENABLE_MODPROBE && (ONLY_APPLET || applet_name[0] == 'm'))
#define is_depmod   (ENABLE_DEPMOD   && (ONLY_APPLET || applet_name[0] == 'd'))
#define is_insmod   (ENABLE_INSMOD   && (ONLY_APPLET || applet_name[0] == 'i'))
#define is_rmmod    (ENABLE_RMMOD    && (ONLY_APPLET || applet_name[0] == 'r'))

enum {
	DEPMOD_OPT_n = (1 << 0), /* dry-run, print to stdout */
	OPT_q = (1 << 0), /* be quiet */
	OPT_r = (1 << 1), /* module removal instead of loading */
};

typedef struct module_info {
	char *pathname;
	char *aliases;
	char *deps;
	smallint open_read_failed;
} module_info;

/*
 * GLOBALS
 */
struct globals {
	module_info *modinfo;
	char *module_load_options;
	smallint dep_bb_seen;
	smallint wrote_dep_bb_ok;
	unsigned module_count;
	int module_found_idx;
	unsigned stringbuf_idx;
	unsigned stringbuf_size;
	char *stringbuf; /* some modules have lots of stuff */
	/* for example, drivers/media/video/saa7134/saa7134.ko */
	/* therefore having a fixed biggish buffer is not wise */
};
#define G (*ptr_to_globals)
#define modinfo             (G.modinfo            )
#define dep_bb_seen         (G.dep_bb_seen        )
#define wrote_dep_bb_ok     (G.wrote_dep_bb_ok    )
#define module_count        (G.module_count       )
#define module_found_idx    (G.module_found_idx   )
#define module_load_options (G.module_load_options)
#define stringbuf_idx       (G.stringbuf_idx      )
#define stringbuf_size      (G.stringbuf_size     )
#define stringbuf           (G.stringbuf          )
#define INIT_G() do { \
	SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
} while (0)

static void append(const char *s)
{
	unsigned len = strlen(s);
	if (stringbuf_idx + len + 15 > stringbuf_size) {
		stringbuf_size = stringbuf_idx + len + 127;
		dbg2_error_msg("grow stringbuf to %u", stringbuf_size);
		stringbuf = xrealloc(stringbuf, stringbuf_size);
	}
	memcpy(stringbuf + stringbuf_idx, s, len);
	stringbuf_idx += len;
}

static void appendc(char c)
{
	/* We appendc() only after append(), + 15 trick in append()
	 * makes it unnecessary to check for overflow here */
	stringbuf[stringbuf_idx++] = c;
}

static void bksp(void)
{
	if (stringbuf_idx)
		stringbuf_idx--;
}

static void reset_stringbuf(void)
{
	stringbuf_idx = 0;
}

static char* copy_stringbuf(void)
{
	char *copy = xzalloc(stringbuf_idx + 1); /* terminating NUL */
	return memcpy(copy, stringbuf, stringbuf_idx);
}

static char* find_keyword(char *ptr, size_t len, const char *word)
{
	if (!ptr) /* happens if xmalloc_open_zipped_read_close cannot read it */
		return NULL;

	len -= strlen(word) - 1;
	while ((ssize_t)len > 0) {
		char *old = ptr;
		char *after_word;

		/* search for the first char in word */
		ptr = memchr(ptr, word[0], len);
		if (ptr == NULL) /* no occurrence left, done */
			break;
		after_word = is_prefixed_with(ptr, word);
		if (after_word)
			return after_word; /* found, return ptr past it */
		++ptr;
		len -= (ptr - old);
	}
	return NULL;
}

static void replace(char *s, char what, char with)
{
	while (*s) {
		if (what == *s)
			*s = with;
		++s;
	}
}

static char *filename2modname(const char *filename, char *modname)
{
	int i;
	const char *from;

	// Disabled since otherwise "modprobe dir/name" would work
	// as if it is "modprobe name". It is unclear why
	// 'basenamization' was here in the first place.
	//from = bb_get_last_path_component_nostrip(filename);
	from = filename;
	for (i = 0; i < (MODULE_NAME_LEN-1) && from[i] != '\0' && from[i] != '.'; i++)
		modname[i] = (from[i] == '-') ? '_' : from[i];
	modname[i] = '\0';

	return modname;
}

static int pathname_matches_modname(const char *pathname, const char *modname)
{
	int r;
	char name[MODULE_NAME_LEN];
	filename2modname(bb_get_last_path_component_nostrip(pathname), name);
	r = (strcmp(name, modname) == 0);
	return r;
}

/* Take "word word", return malloced "word",NUL,"word",NUL,NUL */
static char* str_2_list(const char *str)
{
	int len = strlen(str) + 1;
	char *dst = xmalloc(len + 1);

	dst[len] = '\0';
	memcpy(dst, str, len);
//TODO: protect against 2+ spaces: "word  word"
	replace(dst, ' ', '\0');
	return dst;
}

/* We use error numbers in a loose translation... */
static const char *moderror(int err)
{
	switch (err) {
	case ENOEXEC:
		return "invalid module format";
	case ENOENT:
		return "unknown symbol in module or invalid parameter";
	case ESRCH:
		return "module has wrong symbol version";
	case EINVAL: /* "invalid parameter" */
		return "unknown symbol in module or invalid parameter"
		+ sizeof("unknown symbol in module or");
	default:
		return strerror(err);
	}
}

static int load_module(const char *fname, const char *options)
{
#if 1
	int r;
	size_t len = MAXINT(ssize_t);
	char *module_image;

	if (!options)
		options = "";

	dbg1_error_msg("load_module('%s','%s')", fname, options);

	/*
	 * First we try finit_module if available.  Some kernels are configured
	 * to only allow loading of modules off of secure storage (like a read-
	 * only rootfs) which needs the finit_module call.  If it fails, we fall
	 * back to normal module loading to support compressed modules.
	 */
	r = 1;
# ifdef __NR_finit_module
	{
		int fd = open(fname, O_RDONLY | O_CLOEXEC);
		if (fd >= 0) {
			r = finit_module(fd, options, 0) != 0;
			close(fd);
		}
	}
# endif
	if (r != 0) {
		module_image = xmalloc_open_zipped_read_close(fname, &len);
		r = (!module_image || init_module(module_image, len, options) != 0);
		free(module_image);
	}

	dbg1_error_msg("load_module:%d", r);
	return r; /* 0 = success */
#else
	/* For testing */
	dbg1_error_msg("load_module('%s','%s')", fname, options);
	return 1;
#endif
}

/* Returns !0 if open/read was unsuccessful */
static int parse_module(module_info *info, const char *pathname)
{
	char *module_image;
	char *ptr;
	size_t len;
	size_t pos;
	dbg1_error_msg("parse_module('%s')", pathname);

	/* Read (possibly compressed) module */
	errno = 0;
	len = 64 * 1024 * 1024; /* 64 Mb at most */
	module_image = xmalloc_open_zipped_read_close(pathname, &len);
	/* module_image == NULL is ok here, find_keyword handles it */
//TODO: optimize redundant module body reads

	/* "alias1 symbol:sym1 alias2 symbol:sym2" */
	reset_stringbuf();
	pos = 0;
	while (1) {
		unsigned start = stringbuf_idx;
		ptr = find_keyword(module_image + pos, len - pos, "alias=");
		if (!ptr) {
			ptr = find_keyword(module_image + pos, len - pos, "__ksymtab_");
			if (!ptr)
				break;
			/* DOCME: __ksymtab_gpl and __ksymtab_strings occur
			 * in many modules. What do they mean? */
			if (strcmp(ptr, "gpl") == 0 || strcmp(ptr, "strings") == 0)
				goto skip;
			dbg2_error_msg("alias:'symbol:%s'", ptr);
			append("symbol:");
		} else {
			dbg2_error_msg("alias:'%s'", ptr);
		}
		append(ptr);
		appendc(' ');
		/*
		 * Don't add redundant aliases, such as:
		 * libcrc32c.ko symbol:crc32c symbol:crc32c
		 */
		if (start) { /* "if we aren't the first alias" */
			char *found, *last;
			stringbuf[stringbuf_idx] = '\0';
			last = stringbuf + start;
			/*
			 * String at last-1 is " symbol:crc32c "
			 * (with both leading and trailing spaces).
			 */
			if (strncmp(stringbuf, last, stringbuf_idx - start) == 0)
				/* First alias matches us */
				found = stringbuf;
			else
				/* Does any other alias match? */
				found = strstr(stringbuf, last-1);
			if (found < last-1) {
				/* There is absolutely the same string before us */
				dbg2_error_msg("redundant:'%s'", last);
				stringbuf_idx = start;
				goto skip;
			}
		}
 skip:
		pos = (ptr - module_image);
	}
	bksp(); /* remove last ' ' */
	info->aliases = copy_stringbuf();
	replace(info->aliases, '-', '_');

	/* "dependency1 depandency2" */
	reset_stringbuf();
	ptr = find_keyword(module_image, len, "depends=");
	if (ptr && *ptr) {
		replace(ptr, ',', ' ');
		replace(ptr, '-', '_');
		dbg2_error_msg("dep:'%s'", ptr);
		append(ptr);
	}
	free(module_image);
	info->deps = copy_stringbuf();

	info->open_read_failed = (module_image == NULL);
	return info->open_read_failed;
}

static FAST_FUNC int fileAction(struct recursive_state *state,
		const char *pathname,
		struct stat *sb UNUSED_PARAM)
{
	const char *modname_to_match = state->userData;
	int cur;
	const char *fname;
	bool is_remove = (ENABLE_RMMOD && ONLY_APPLET)
		|| ((ENABLE_RMMOD || ENABLE_MODPROBE) && (option_mask32 & OPT_r));

	pathname += 2; /* skip "./" */
	fname = bb_get_last_path_component_nostrip(pathname);
	if (!strrstr(fname, ".ko")) {
		dbg1_error_msg("'%s' is not a module", pathname);
		return TRUE; /* not a module, continue search */
	}

	cur = module_count++;
	modinfo = xrealloc_vector(modinfo, 12, cur);
	modinfo[cur].pathname = xstrdup(pathname);
	/*modinfo[cur].aliases = NULL; - xrealloc_vector did it */
	/*modinfo[cur+1].pathname = NULL;*/

	if (!pathname_matches_modname(fname, modname_to_match)) {
		dbg1_error_msg("'%s' module name doesn't match", pathname);
		return TRUE; /* module name doesn't match, continue search */
	}

	dbg1_error_msg("'%s' module name matches", pathname);
	module_found_idx = cur;
	if (parse_module(&modinfo[cur], pathname) != 0)
		return TRUE; /* failed to open/read it, no point in trying loading */

	if (!is_remove) {
		if (load_module(pathname, module_load_options) == 0) {
			/* Load was successful, there is nothing else to do.
			 * This can happen ONLY for "top-level" module load,
			 * not a dep, because deps don't do dirscan. */
			exit(EXIT_SUCCESS);
		}
	}

	return TRUE;
}

static int load_dep_bb(void)
{
	char *line;
	FILE *fp = fopen_for_read(DEPFILE_BB);

	if (!fp)
		return 0;

	dep_bb_seen = 1;
	dbg1_error_msg("loading "DEPFILE_BB);

	/* Why? There is a rare scenario: we did not find modprobe.dep.bb,
	 * we scanned the dir and found no module by name, then we search
	 * for alias (full scan), and we decided to generate modprobe.dep.bb.
	 * But we see modprobe.dep.bb.new! Other modprobe is at work!
	 * We wait and other modprobe renames it to modprobe.dep.bb.
	 * Now we can use it.
	 * But we already have modinfo[] filled, and "module_count = 0"
	 * makes us start anew. Yes, we leak modinfo[].xxx pointers -
	 * there is not much of data there anyway. */
	module_count = 0;
	memset(&modinfo[0], 0, sizeof(modinfo[0]));

	while ((line = xmalloc_fgetline(fp)) != NULL) {
		char* space;
		char* linebuf;
		int cur;

		if (!line[0]) {
			free(line);
			continue;
		}
		space = strchrnul(line, ' ');
		cur = module_count++;
		modinfo = xrealloc_vector(modinfo, 12, cur);
		/*modinfo[cur+1].pathname = NULL; - xrealloc_vector did it */
		modinfo[cur].pathname = line; /* we take ownership of malloced block here */
		if (*space)
			*space++ = '\0';
		modinfo[cur].aliases = space;
		linebuf = xmalloc_fgetline(fp);
		modinfo[cur].deps = linebuf ? linebuf : xzalloc(1);
		if (modinfo[cur].deps[0]) {
			/* deps are not "", so next line must be empty */
			line = xmalloc_fgetline(fp);
			/* Refuse to work with damaged config file */
			if (line && line[0])
				bb_error_msg_and_die("error in %s at '%s'", DEPFILE_BB, line);
			free(line);
		}
	}
	return 1;
}

static int start_dep_bb_writeout(void)
{
	int fd;

	/* depmod -n: write result to stdout */
	if (is_depmod && (option_mask32 & DEPMOD_OPT_n))
		return STDOUT_FILENO;

	fd = open(DEPFILE_BB".new", O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0644);
	if (fd < 0) {
		if (errno == EEXIST) {
			int count = 5 * 20;
			dbg1_error_msg(DEPFILE_BB".new exists, waiting for "DEPFILE_BB);
			while (1) {
				usleep(1000*1000 / 20);
				if (load_dep_bb()) {
					dbg1_error_msg(DEPFILE_BB" appeared");
					return -2; /* magic number */
				}
				if (!--count)
					break;
			}
			bb_error_msg("deleting stale %s", DEPFILE_BB".new");
			fd = open_or_warn(DEPFILE_BB".new", O_WRONLY | O_CREAT | O_TRUNC);
		}
	}
	dbg1_error_msg("opened "DEPFILE_BB".new:%d", fd);
	return fd;
}

static void write_out_dep_bb(int fd)
{
	int i;
	FILE *fp;

	/* We want good error reporting. fdprintf is not good enough. */
	fp = xfdopen_for_write(fd);
	i = 0;
	while (modinfo[i].pathname) {
		fprintf(fp, "%s%s%s\n" "%s%s\n",
			modinfo[i].pathname, modinfo[i].aliases[0] ? " " : "", modinfo[i].aliases,
			modinfo[i].deps, modinfo[i].deps[0] ? "\n" : "");
		i++;
	}
	/* Badly formatted depfile is a no-no. Be paranoid. */
	errno = 0;
	if (ferror(fp) | fclose(fp)) /* | instead of || is intended */
		goto err;

	if (fd == STDOUT_FILENO) /* it was depmod -n */
		goto ok;

	if (rename(DEPFILE_BB".new", DEPFILE_BB) != 0) {
 err:
		bb_perror_msg("can't create '%s'", DEPFILE_BB);
		unlink(DEPFILE_BB".new");
	} else {
 ok:
		wrote_dep_bb_ok = 1;
		dbg1_error_msg("created "DEPFILE_BB);
	}
}

static module_info** find_alias(const char *alias)
{
	int i;
	int dep_bb_fd;
	int infoidx;
	module_info **infovec;
	dbg1_error_msg("find_alias('%s')", alias);

 try_again:
	/* First try to find by name (cheaper) */
	i = 0;
	while (modinfo[i].pathname) {
		if (pathname_matches_modname(modinfo[i].pathname, alias)) {
			dbg1_error_msg("found '%s' in module '%s'",
					alias, modinfo[i].pathname);
			if (!modinfo[i].aliases) {
				parse_module(&modinfo[i], modinfo[i].pathname);
			}
			infovec = xzalloc(2 * sizeof(infovec[0]));
			infovec[0] = &modinfo[i];
			return infovec;
		}
		i++;
	}

	/* Ok, we definitely have to scan module bodies. This is a good
	 * moment to generate modprobe.dep.bb, if it does not exist yet */
	dep_bb_fd = dep_bb_seen ? -1 : start_dep_bb_writeout();
	if (dep_bb_fd == -2) /* modprobe.dep.bb appeared? */
		goto try_again;

	/* Scan all module bodies, extract modinfo (it contains aliases) */
	i = 0;
	infoidx = 0;
	infovec = NULL;
	while (modinfo[i].pathname) {
		char *desc, *s;
		if (!modinfo[i].aliases) {
			parse_module(&modinfo[i], modinfo[i].pathname);
		}
		/* "alias1 symbol:sym1 alias2 symbol:sym2" */
		desc = str_2_list(modinfo[i].aliases);
		/* Does matching substring exist? */
		for (s = desc; *s; s += strlen(s) + 1) {
			/* Aliases in module bodies can be defined with
			 * shell patterns. Example:
			 * "pci:v000010DEd000000D9sv*sd*bc*sc*i*".
			 * Plain strcmp() won't catch that */
			if (fnmatch(s, alias, 0) == 0) {
				dbg1_error_msg("found alias '%s' in module '%s'",
						alias, modinfo[i].pathname);
				infovec = xrealloc_vector(infovec, 1, infoidx);
				infovec[infoidx++] = &modinfo[i];
				break;
			}
		}
		free(desc);
		i++;
	}

	/* Create module.dep.bb if needed */
	if (dep_bb_fd >= 0) {
		write_out_dep_bb(dep_bb_fd);
	}

	dbg1_error_msg("find_alias '%s' returns %d results", alias, infoidx);
	return infovec;
}

#if ENABLE_FEATURE_MODPROBE_SMALL_CHECK_ALREADY_LOADED
// TODO: open only once, invent config_rewind()
static int already_loaded(const char *name)
{
	int ret;
	char *line;
	FILE *fp;

	ret = 5 * 2;
 again:
	fp = fopen_for_read("/proc/modules");
	if (!fp)
		return 0;
	while ((line = xmalloc_fgetline(fp)) != NULL) {
		char *live;
		char *after_name;

		// Examples from kernel 3.14.6:
		//pcspkr 12718 0 - Live 0xffffffffa017e000
		//snd_timer 28690 2 snd_seq,snd_pcm, Live 0xffffffffa025e000
		//i915 801405 2 - Live 0xffffffffa0096000
		after_name = is_prefixed_with(line, name);
		if (!after_name || *after_name != ' ') {
			free(line);
			continue;
		}
		live = strstr(line, " Live");
		free(line);
		if (!live) {
			/* State can be Unloading, Loading, or Live.
			 * modprobe must not return prematurely if we see "Loading":
			 * it can cause further programs to assume load completed,
			 * but it did not (yet)!
			 * Wait up to 5*20 ms for it to resolve.
			 */
			ret -= 2;
			if (ret == 0)
				break;  /* huh? report as "not loaded" */
			fclose(fp);
			usleep(20*1000);
			goto again;
		}
		ret = 1;
		break;
	}
	fclose(fp);

	return ret & 1;
}
#else
#define already_loaded(name) 0
#endif

static int rmmod(const char *filename)
{
	int r;
	char modname[MODULE_NAME_LEN];

	filename2modname(filename, modname);
	r = delete_module(modname, O_NONBLOCK | O_EXCL);
	dbg1_error_msg("delete_module('%s', O_NONBLOCK | O_EXCL):%d", modname, r);
	if (r != 0 && !(option_mask32 & OPT_q)) {
		bb_perror_msg("remove '%s'", modname);
	}
	return r;
}

/*
 * Given modules definition and module name (or alias, or symbol)
 * load/remove the module respecting dependencies.
 * NB: also called by depmod with bogus name "/",
 * just in order to force modprobe.dep.bb creation.
*/
#if !ENABLE_FEATURE_CMDLINE_MODULE_OPTIONS
#define process_module(a,b) process_module(a)
#define cmdline_options ""
#endif
static int process_module(char *name, const char *cmdline_options)
{
	char *s, *deps, *options;
	module_info **infovec;
	module_info *info;
	int infoidx;
	bool is_remove = (ENABLE_RMMOD && ONLY_APPLET)
		|| ((ENABLE_RMMOD || ENABLE_MODPROBE) && (option_mask32 & OPT_r));
	int exitcode = EXIT_SUCCESS;

	dbg1_error_msg("process_module('%s','%s')", name, cmdline_options);

	replace(name, '-', '_');

	dbg1_error_msg("already_loaded:%d is_remove:%d", already_loaded(name), is_remove);

	if (is_rmmod) {
		/* Does not remove dependencies, no need to scan, just remove.
		 * (compat note: this allows and strips .ko suffix)
		 */
		rmmod(name);
		return EXIT_SUCCESS;
	}

	/*
	 * We used to have "is_remove != already_loaded(name)" check here, but
	 *  modprobe -r pci:v00008086d00007010sv00000000sd00000000bc01sc01i80
	 * won't unload modules (there are more than one)
	 * which have this alias.
	 */
	if (!is_remove && already_loaded(name)) {
		dbg1_error_msg("nothing to do for '%s'", name);
		return EXIT_SUCCESS;
	}

	options = NULL;
	if (!is_remove) {
		char *opt_filename = xasprintf("/etc/modules/%s", name);
		options = xmalloc_open_read_close(opt_filename, NULL);
		if (options)
			replace(options, '\n', ' ');
#if ENABLE_FEATURE_CMDLINE_MODULE_OPTIONS
		if (cmdline_options) {
			/* NB: cmdline_options always have one leading ' '
			 * (see main()), we remove it here */
			char *op = xasprintf(options ? "%s %s" : "%s %s" + 3,
						cmdline_options + 1, options);
			free(options);
			options = op;
		}
#endif
		free(opt_filename);
		module_load_options = options;
		dbg1_error_msg("process_module('%s'): options:'%s'", name, options);
	}

	if (!module_count) {
		/* Scan module directory. This is done only once.
		 * It will attempt module load, and will exit(EXIT_SUCCESS)
		 * on success.
		 */
		module_found_idx = -1;
		recursive_action(".",
			ACTION_RECURSE, /* flags */
			fileAction, /* file action */
			NULL, /* dir action */
			name /* user data */
		);
		dbg1_error_msg("dirscan complete");
		/* Module was not found, or load failed, or is_remove */
		if (module_found_idx >= 0) { /* module was found */
			infovec = xzalloc(2 * sizeof(infovec[0]));
			infovec[0] = &modinfo[module_found_idx];
		} else { /* search for alias, not a plain module name */
			infovec = find_alias(name);
		}
	} else {
		infovec = find_alias(name);
	}

	if (!infovec) {
		/* both dirscan and find_alias found nothing */
		if (!is_remove && !is_depmod) { /* it wasn't rmmod or depmod */
			bb_error_msg("module '%s' not found", name);
//TODO: _and_die()? or should we continue (un)loading modules listed on cmdline?
			/* "modprobe non-existing-module; echo $?" must print 1 */
			exitcode = EXIT_FAILURE;
		}
		goto ret;
	}

	/* There can be more than one module for the given alias. For example,
	 * "pci:v00008086d00007010sv00000000sd00000000bc01sc01i80" matches
	 * ata_piix because it has alias "pci:v00008086d00007010sv*sd*bc*sc*i*"
	 * and ata_generic, it has alias "pci:v*d*sv*sd*bc01sc01i*"
	 * Standard modprobe loads them both. We achieve it by returning
	 * a *list* of modinfo pointers from find_alias().
	 */

	/* modprobe -r? unload module(s) */
	if (is_remove) {
		infoidx = 0;
		while ((info = infovec[infoidx++]) != NULL) {
			int r = rmmod(bb_get_last_path_component_nostrip(info->pathname));
			if (r != 0) {
				goto ret; /* error */
			}
		}
		/* modprobe -r: we do not stop here -
		 * continue to unload modules on which the module depends:
		 * "-r --remove: option causes modprobe to remove a module.
		 * If the modules it depends on are also unused, modprobe
		 * will try to remove them, too."
		 */
	}

	infoidx = 0;
	while ((info = infovec[infoidx++]) != NULL) {
		/* Iterate thru dependencies, trying to (un)load them */
		deps = str_2_list(info->deps);
		for (s = deps; *s; s += strlen(s) + 1) {
			//if (strcmp(name, s) != 0) // N.B. do loops exist?
			dbg1_error_msg("recurse on dep '%s'", s);
			process_module(s, NULL);
			dbg1_error_msg("recurse on dep '%s' done", s);
		}
		free(deps);

		if (is_remove)
			continue;

		/* We are modprobe: load it */
		if (options && strstr(options, "blacklist")) {
			dbg1_error_msg("'%s': blacklisted", info->pathname);
			continue;
		}
		if (info->open_read_failed) {
			/* We already tried it, didn't work. Don't try load again */
			exitcode = EXIT_FAILURE;
			continue;
		}
		errno = 0;
		if (load_module(info->pathname, options) != 0) {
			if (EEXIST != errno) {
				bb_error_msg("'%s': %s",
					info->pathname,
					moderror(errno));
			} else {
				dbg1_error_msg("'%s': %s",
					info->pathname,
					moderror(errno));
			}
			exitcode = EXIT_FAILURE;
		}
	}
 ret:
	free(infovec);
	free(options);

	return exitcode;
}
#undef cmdline_options

#ifdef AB_MODIFIED
#define CONCURRENT_LOAD_INSTALL_KO_SUPPORT 1
#if CONCURRENT_LOAD_INSTALL_KO_SUPPORT
//debug/test macro
#define CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN 0
#define ab_ko_log(...) bb_error_msg(__VA_ARGS__)

#define CONCURRENT_LOAD_AND_INSTALL_KO_MEASURE_EN 0
#if CONCURRENT_LOAD_AND_INSTALL_KO_MEASURE_EN
#define KO_DBG_SEQ_MAX_SIZE 60
#include <time.h>
/*
struct timespec {
time_t tv_sec; // seconds
long tv_nsec; // and nanoseconds
};
*/
typedef struct _ab_ko_dbg_seq{
	long load_diff;
	long install_diff;
	char* module_name;
} ab_ko_dbg_seq;
ab_ko_dbg_seq as_ab_ko_dbg_seq[KO_DBG_SEQ_MAX_SIZE+1] = {0};
int ab_ko_dbg_seq_load_cnt = 0;
int ab_ko_dbg_seq_install_cnt = 0;

struct timespec t_load_start;
struct timespec t_install_start;
void _ab_ko_dbg_get_time_ns(struct timespec *pt)
{
	clock_gettime(CLOCK_REALTIME, pt);
}

long _ab_ko_get_diff_time_ns(struct timespec *pt_before, struct timespec *pt_after)
{
	struct timespec t_diff;
	if ((pt_after->tv_nsec-pt_before->tv_nsec)<0) {
		t_diff.tv_sec = pt_after->tv_sec - pt_before->tv_sec -1;
		t_diff.tv_nsec = 1000000000 + pt_after->tv_nsec - pt_before->tv_nsec;
	} else {
		t_diff.tv_sec = pt_after->tv_sec - pt_before->tv_sec;
		t_diff.tv_nsec = pt_after->tv_nsec - pt_before->tv_nsec;
	}
	
	/*if(t_diff.tv_sec == 0)
	{
		return t_diff.tv_nsec;
	} else {
		return 1000000000*t_diff.tv_sec+t_diff.tv_nsec;
	}*/
	return 1000000000*t_diff.tv_sec+t_diff.tv_nsec;
}

void ab_ko_dbg_load_start(char* name)
{
	as_ab_ko_dbg_seq[ab_ko_dbg_seq_load_cnt].module_name = name;
	_ab_ko_dbg_get_time_ns(&t_load_start);
}

void ab_ko_dbg_load_end(void)
{
	struct timespec t_end;
	_ab_ko_dbg_get_time_ns(&t_end);
	as_ab_ko_dbg_seq[ab_ko_dbg_seq_load_cnt++].load_diff = _ab_ko_get_diff_time_ns( &t_load_start, &t_end);
}

void ab_ko_dbg_install_start(void)
{
	_ab_ko_dbg_get_time_ns(&t_install_start);
}

void ab_ko_dbg_install_end(void)
{
	struct timespec t_end;
	_ab_ko_dbg_get_time_ns(&t_end);
	as_ab_ko_dbg_seq[ab_ko_dbg_seq_install_cnt++].install_diff = _ab_ko_get_diff_time_ns( &t_install_start, &t_end);
}

void ab_ko_dbg_dump_dbg_seq(void)
{
	for(int i=0;i<ab_ko_dbg_seq_load_cnt;i++)
		printf("%02d: %ld %ld %s\n", i, as_ab_ko_dbg_seq[i].load_diff, as_ab_ko_dbg_seq[i].install_diff, as_ab_ko_dbg_seq[i].module_name);
}

#else
#define ab_ko_dbg_load_start(a)
#define ab_ko_dbg_load_end()
#define ab_ko_dbg_install_start()
#define ab_ko_dbg_install_end()
#define ab_ko_dbg_dump_dbg_seq()

#endif //CONCURRENT_LOAD_AND_INSTALL_KO_MEASURE_EN
//header
#include <pthread.h>
#include <semaphore.h>

//ab ko fifo 
typedef struct _ab_ko_node{
	int is_last_one;
	char* ko_buf;
	int ko_size;
	char* ko_options;

	#if CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN
	char* filename;
	#endif //CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN
}ab_ko_node_st, *p_ab_ko_node_st;

typedef enum _e_AB_KO_FIFO_OPERATION_RET_VALUE {
	AB_KO_FIFO_OPERATION_RET_SUCCESS = 0,
	AB_KO_FIFO_OPERATION_RET_CURRENT_EMPTY,
	AB_KO_FIFO_OPERATION_RET_FIFO_CLOSED
}e_AB_KO_FIFO_OPERATION_RET_VALUE;

#define KO_DATA_MAX_CNT 60
ab_ko_node_st as_ko_node_array[KO_DATA_MAX_CNT+1] = {0};
int ai_ko_node_cnt_record_cnt = 0;
int ai_ko_node_cnt_record[KO_DATA_MAX_CNT+1] = {0};

int ko_node_fifo_head_index = 0;
int ko_node_fifo_trail_index = 0;
static sem_t ab_ko_fifo_sem;
static pthread_mutex_t ab_ko_fifo_op_mtx = PTHREAD_MUTEX_INITIALIZER;

#define _get_total_ko_node_cnt_in_fifo() (ko_node_fifo_head_index - ko_node_fifo_trail_index)
#define _ko_node_fifo_index_add(a) (a = (a+1)); if(a >= KO_DATA_MAX_CNT) {bb_error_msg("error, no ring buffer support.\n");};\
ai_ko_node_cnt_record[ai_ko_node_cnt_record_cnt++] = _get_total_ko_node_cnt_in_fifo();

#define AB_KO_NODE_INVALID_VALUE 0
#define TEST_IS_AB_KO_NODE_VALID(ko_node) (ko_node != AB_KO_NODE_INVALID_VALUE)
#define TEST_RET_IF_AB_KO_FIFO_CLOSED(a) (a == AB_KO_FIFO_OPERATION_RET_FIFO_CLOSED)
#define TEST_RET_IF_AB_KO_FIFO_EMPTY(a) (a == AB_KO_FIFO_OPERATION_RET_CURRENT_EMPTY)

#define CONCURRENT_LOAD_INSTALL_KO_ON 1
#define CONCURRENT_LOAD_INSTALL_KO_OFF 0
int ab_concurrent_load_and_install_ko_flag = CONCURRENT_LOAD_INSTALL_KO_OFF;
#define TEST_IS_CONCURRENT_LOAD_INSTALL_KO_ON() (ab_concurrent_load_and_install_ko_flag == CONCURRENT_LOAD_INSTALL_KO_ON)
#define SET_CONCURRENT_LOAD_INSTALL_KO_ON() ab_concurrent_load_and_install_ko_flag = CONCURRENT_LOAD_INSTALL_KO_ON

//function declaration
void ab_ko_fifo_init(void);
e_AB_KO_FIFO_OPERATION_RET_VALUE ab_ko_fifo_push_node(int is_last_one, char* ko_buf, int ko_size, char* ko_options, char* filename);
p_ab_ko_node_st ab_ko_fifo_pop_node(e_AB_KO_FIFO_OPERATION_RET_VALUE *p_fifo_op_ret);
void* ab_ko_install_thread(void* a);

/*
fifo is based on a fixed size array.

Invariants:
fifo head index: h =head
	0. only push operation can change it. when mutex is locked, all calculation perofrmed is part of single push ops.
	1. fifo array[h], is always empty after operation(push) completes.
	2. h is added 1 in push operation when new node is pushed.
fifo trail index: t =trail
	0. only pop operation can change it. when mutex is locked, all calculation perofrmed is part of single pop ops.
	1. when number of valid nodes in array is 0, pop operation is not performed
	2. if number of valid nodes in array is not 0, fifo array[t], is always valid before operation(pop) starts.
	3. t is added 1 in pop operation when old node is popped.

simulation fifo push/pop:
[] : element is of type ab_ko_node_st.
h=head : ko_node_fifo_head_index
t=trail : ko_node_fifo_trail_index
-----------------------------------------------------------
0		1		2		3		4		5		6		7		KO_DATA_MAX_CNT
[]		[*]		[*]		[*]		[*]		[*]		[]		[] ... []
		trail->							head->
-----------------------------------------------------------
[]		[]		[]		[]		[]		[]		[]		[] ... head - trail = 0
^h
^t
->push
[*]		[]		[]		[]		[]		[]		[]		[] ... head - trail = 1
		^h
^t
->push
[*]		[*]		[]		[]		[]		[]		[]		[] ... head - trail = 2
				^h
^t
->push
[*]		[*]		[*]		[]		[]		[]		[]		[] ... head - trail = 3
						^h
^t
->pop
[]		[*]		[*]		[]		[]		[]		[]		[] ... head - trail = 2
						^h
		^t
->pop
[]		[]		[*]		[]		[]		[]		[]		[] ... head - trail = 1
						^h
				^t
->pop
[]		[]		[]		[]		[]		[]		[]		[] ... head - trail = 0
						^h
						^t
-----------------------------------------------------------
*/

void ab_ko_fifo_init(void)
{
	sem_init(&ab_ko_fifo_sem, 0, 0);
}

e_AB_KO_FIFO_OPERATION_RET_VALUE ab_ko_fifo_push_node(int is_last_one, char* ko_buf, int ko_size, char* ko_options, char* filename)
{
	int ret;
	ret = pthread_mutex_lock(&ab_ko_fifo_op_mtx);
	if(ret)
		bb_perror_msg("mutex lock fail.\n");
	#if CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN
	ab_ko_log("before push, fifo cnt %d\n", _get_total_ko_node_cnt_in_fifo());
	#endif //CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN
	if(_get_total_ko_node_cnt_in_fifo() < KO_DATA_MAX_CNT) {
		as_ko_node_array[ko_node_fifo_head_index].ko_buf = ko_buf;
		as_ko_node_array[ko_node_fifo_head_index].ko_size = ko_size;
		as_ko_node_array[ko_node_fifo_head_index].ko_options = ko_options;
		as_ko_node_array[ko_node_fifo_head_index].is_last_one = is_last_one;
		#if CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN
		ab_ko_log("push to index %d, %llx %d %s %s\n", ko_node_fifo_head_index, ko_buf, ko_size, ko_options, filename);
		as_ko_node_array[ko_node_fifo_head_index].filename = filename;
		#endif //CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN
		_ko_node_fifo_index_add(ko_node_fifo_head_index);
		sem_post(&ab_ko_fifo_sem);
	}else{
		bb_perror_msg("lost module data, ko queue is full.\n");
	}
	ret = pthread_mutex_unlock(&ab_ko_fifo_op_mtx);
	if(ret)
		bb_perror_msg("mutex unlock fail.\n");
	return ret;
}

p_ab_ko_node_st ab_ko_fifo_pop_node(e_AB_KO_FIFO_OPERATION_RET_VALUE *p_fifo_op_ret)
{
	#define _i_AB_FIFO_STOP_VALUE 1
	#define _i_TEST_IF_FIFO_STOP(a) (a == _i_AB_FIFO_STOP_VALUE)
	#define _i_SET_FIFO_STOP(a) (a = _i_AB_FIFO_STOP_VALUE)
	#define _i_TEST_IF_PTR_NOT_EMPTY(a) (a != 0) 
	static int pop_internal_stop_flag = 0;
	int mtx_ret;
	p_ab_ko_node_st ret;

	if(_i_TEST_IF_FIFO_STOP(pop_internal_stop_flag))
	{
		#if CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN
		ab_ko_log("no ko node should be left in fifo. \n");
		#endif //CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN
		if(_i_TEST_IF_PTR_NOT_EMPTY(p_fifo_op_ret)) {
			*p_fifo_op_ret = AB_KO_FIFO_OPERATION_RET_FIFO_CLOSED;
		}
		return AB_KO_NODE_INVALID_VALUE;
	}
	sem_wait(&ab_ko_fifo_sem);

	mtx_ret = pthread_mutex_lock(&ab_ko_fifo_op_mtx);
	if(mtx_ret)
		bb_perror_msg("mutex lock fail.\n");

	#if CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN
	ab_ko_log("before pop, fifo cnt %d\n", _get_total_ko_node_cnt_in_fifo());
	#endif //CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN

	if(_get_total_ko_node_cnt_in_fifo() > 0) {
		p_ab_ko_node_st p = &as_ko_node_array[ko_node_fifo_trail_index];
		#if CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN
		ab_ko_log("pop from index %d, %llx %d %s %s\n", ko_node_fifo_trail_index, p->ko_buf, p->ko_size, p->ko_options, p->filename);
		#endif //CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN
		ret = p;
		_ko_node_fifo_index_add(ko_node_fifo_trail_index);		

		if(_i_TEST_IF_PTR_NOT_EMPTY(p_fifo_op_ret)) {
			*p_fifo_op_ret = AB_KO_FIFO_OPERATION_RET_SUCCESS; 
		}

		if(p->is_last_one) {
			_i_SET_FIFO_STOP(pop_internal_stop_flag);
		}
	} else {
		if(_i_TEST_IF_PTR_NOT_EMPTY(p_fifo_op_ret)) {
			*p_fifo_op_ret = AB_KO_FIFO_OPERATION_RET_CURRENT_EMPTY;
		}
		ret = AB_KO_NODE_INVALID_VALUE;
	}
	mtx_ret = pthread_mutex_unlock(&ab_ko_fifo_op_mtx);
	if(mtx_ret)
		bb_perror_msg("mutex unlock fail.\n");

	return ret;
}

void* ab_ko_install_thread(void* a)
{
	while(1) {
		void* map = 0;
		int len = 0;
		char* options = 0;
		e_AB_KO_FIFO_OPERATION_RET_VALUE fifo_op_ret;
		p_ab_ko_node_st p = ab_ko_fifo_pop_node(&fifo_op_ret);
		if( TEST_IS_AB_KO_NODE_VALID(p) ) {

			map = (void*) p->ko_buf;
			len = p->ko_size;
			options = p->ko_options;
			if(p != 0) {
				#if CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN
				ab_ko_log("install %llx %d\n", map, len);
				#endif //CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN

				ab_ko_dbg_install_start();

				init_module(map, len,
					(IF_FEATURE_CMDLINE_MODULE_OPTIONS(options ? options : ) "")
				);			

				ab_ko_dbg_install_end();
			}
		} else {
			if( TEST_RET_IF_AB_KO_FIFO_CLOSED(fifo_op_ret)) {
				bb_perror_msg("concurrent load and install finish.\n");
				pthread_exit(NULL);
			} 
			continue;
		}

	}
}

#endif //CONCURRENT_LOAD_INSTALL_KO_SUPPORT

#endif
/* For reference, module-init-tools v3.4 options:

# insmod
Usage: insmod filename [args]

# rmmod --help
Usage: rmmod [-fhswvV] modulename ...
 -f (or --force) forces a module unload, and may crash your
    machine. This requires the Forced Module Removal option
    when the kernel was compiled.
 -h (or --help) prints this help text
 -s (or --syslog) says use syslog, not stderr
 -v (or --verbose) enables more messages
 -V (or --version) prints the version code
 -w (or --wait) begins module removal even if it is used
    and will stop new users from accessing the module (so it
    should eventually fall to zero).

# modprobe
Usage: modprobe [-v] [-V] [-C config-file] [-d <dirname> ] [-n] [-i] [-q]
    [-b] [-o <modname>] [ --dump-modversions ] <modname> [parameters...]
modprobe -r [-n] [-i] [-v] <modulename> ...
modprobe -l -t <dirname> [ -a <modulename> ...]

# depmod --help
depmod 3.13 -- part of module-init-tools
depmod -[aA] [-n -e -v -q -V -r -u -w -m]
      [-b basedirectory] [forced_version]
depmod [-n -e -v -q -r -u -w] [-F kernelsyms] module1.ko module2.ko ...
If no arguments (except options) are given, "depmod -a" is assumed.
depmod will output a dependency list suitable for the modprobe utility.
Options:
    -a, --all           Probe all modules
    -A, --quick         Only does the work if there's a new module
    -e, --errsyms       Report not supplied symbols
    -m, --map           Create the legacy map files
    -n, --show          Write the dependency file on stdout only
    -P, --symbol-prefix Architecture symbol prefix
    -V, --version       Print the release version
    -v, --verbose       Enable verbose mode
    -w, --warn          Warn on duplicates
    -h, --help          Print this usage message
The following options are useful for people managing distributions:
    -b basedirectory
    --basedir basedirectory
                        Use an image of a module tree
    -F kernelsyms
    --filesyms kernelsyms
                        Use the file instead of the current kernel symbols
    -E Module.symvers
    --symvers Module.symvers
                        Use Module.symvers file to check symbol versions
*/

//usage:#if ENABLE_MODPROBE_SMALL

//usage:#define depmod_trivial_usage "[-n]"
//usage:#define depmod_full_usage "\n\n"
//usage:       "Generate modules.dep.bb"
//usage:     "\n"
//usage:     "\n	-n	Dry run: print file to stdout"

//usage:#define insmod_trivial_usage
//usage:	"FILE" IF_FEATURE_CMDLINE_MODULE_OPTIONS(" [SYMBOL=VALUE]...")
//usage:#define insmod_full_usage "\n\n"
//usage:       "Load kernel module"

//usage:#define rmmod_trivial_usage
//usage:       "MODULE..."
//usage:#define rmmod_full_usage "\n\n"
//usage:       "Unload kernel modules"

//usage:#define modprobe_trivial_usage
//usage:	"[-rq] MODULE" IF_FEATURE_CMDLINE_MODULE_OPTIONS(" [SYMBOL=VALUE]...")
//usage:#define modprobe_full_usage "\n\n"
//usage:       "	-r	Remove MODULE"
#ifdef AB_MODIFIED
//usage:       "	-l	insmod mutiple MODULE in single call\n"
//usage:       "	-l1	insmod mutiple MODULE in single call\n"
//usage:       "	    and run loading and installing concurrently\n"
#endif
//usage:     "\n	-q	Quiet"

//usage:#endif

int modprobe_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int modprobe_main(int argc UNUSED_PARAM, char **argv)
{
#if ENABLE_MODPROBE || ENABLE_INSMOD || ENABLE_RMMOD
	int exitcode;
#endif
	struct utsname uts;
#ifdef AB_MODIFIED
	int flag = 0;
	uint32_t opt;
#endif
	IF_FEATURE_CMDLINE_MODULE_OPTIONS(char *options = NULL;)

	INIT_G();

	/* Prevent ugly corner cases with no modules at all */
	modinfo = xzalloc(sizeof(modinfo[0]));

	if ((MOD_APPLET_CNT == 2 && ENABLE_DEPMOD && ENABLE_MODPROBE)
	 || is_depmod || is_modprobe
	) {
		/* Goto modules directory */
		xchdir(CONFIG_DEFAULT_MODULES_DIR);
		uname(&uts); /* never fails */
	}

	/* depmod? */
	if (is_depmod) {
		/* Supported:
		 * -n: print result to stdout
		 * -a: process all modules (default)
		 * optional VERSION parameter
		 * Ignored:
		 * -A: do work only if a module is newer than depfile
		 * -e: report any symbols which a module needs
		 *  which are not supplied by other modules or the kernel
		 * -F FILE: System.map (symbols for -e)
		 * -q, -r, -u: noop
		 * Not supported:
		 * -b BASEDIR: (TODO!) modules are in
		 *  $BASEDIR/lib/modules/$VERSION
		 * -m: create legacy "modules.*map" files (deprecated; in
		 *  kmod's depmod, prints a warning message and continues)
		 * -v: human readable deps to stdout
		 * -V: version (don't want to support it - people may depend
		 *  on it as an indicator of "standard" depmod)
		 * -h: help (well duh)
		 * module1.o module2.o parameters (just ignored for now)
		 */
		getopt32(argv, "na" "AeF:qru" /* "b:vV", NULL */, NULL);
		argv += optind;
		/* if (argv[0] && argv[1]) bb_show_usage(); */
		/* Goto $VERSION directory */
		xchdir(argv[0] ? argv[0] : uts.release);
		/* Force full module scan by asking to find a bogus module.
		 * This will generate modules.dep.bb as a side effect. */
		process_module((char*)"/", NULL);
		return !wrote_dep_bb_ok;
	}

#if ENABLE_MODPROBE || ENABLE_INSMOD || ENABLE_RMMOD
	/* modprobe, insmod, rmmod require at least one argument */
	/* only -q (quiet) and -r (rmmod),
	 * the rest are accepted and ignored (compat) */
	#ifdef AB_MODIFIED
	char *pointer_to_arg_for_l = 0;
	# if CONCURRENT_LOAD_INSTALL_KO_SUPPORT
	opt = getopt32(argv, "qrfsvwbl::", &pointer_to_arg_for_l);
	# else
	opt = getopt32(argv, "qrfsvwbl");
	# endif //CONCURRENT_LOAD_INSTALL_KO_SUPPORT
	
	if(opt & (1<<7) && (is_insmod || is_modprobe)) {	/* insmod -l or modprobe -l*/
		flag = 1;
		# if CONCURRENT_LOAD_INSTALL_KO_SUPPORT
		if(pointer_to_arg_for_l) {
			if((pointer_to_arg_for_l[0]-'0') == CONCURRENT_LOAD_INSTALL_KO_ON)
				SET_CONCURRENT_LOAD_INSTALL_KO_ON();
		}
		ab_ko_log("concurrent load on/off %s\n", TEST_IS_CONCURRENT_LOAD_INSTALL_KO_ON() ? "on" : "off");
		# endif //CONCURRENT_LOAD_INSTALL_KO_SUPPORT
	}

	argv += optind;
	#else
	getopt32(argv, "^" "qrfsvwb" "\0" "-1");
	argv += optind;
	#endif

	if (is_modprobe) {
		/* Goto $VERSION directory */
		xchdir(uts.release);
	}

	/* are we rmmod? -> simulate modprobe -r, but don't bother the flag if
	 * there're no other applets here */
	if (is_rmmod) {
		if (!ONLY_APPLET)
			option_mask32 |= OPT_r;
	}
	#ifdef AB_MODIFIED
	if (!flag && !(option_mask32 & OPT_r)) {
	#else
	else if (!ENABLE_MODPROBE || !(option_mask32 & OPT_r)) {
	#endif
		#if ENABLE_FEATURE_CMDLINE_MODULE_OPTIONS
		/* If not rmmod/-r, parse possible module options given on command line.
		* insmod/modprobe takes one module name, the rest are parameters. */
		char **arg = argv;
		while (*++arg) {
			/* Enclose options in quotes */
			char *s = options;
			options = xasprintf("%s \"%s\"", s ? s : "", *arg);
			free(s);
			*arg = NULL;
		}
		#else
		argv[1] = NULL;
		#endif
	}

	if (is_insmod) {
		size_t len;
		void *map;

		#ifdef AB_MODIFIED
		# if CONCURRENT_LOAD_INSTALL_KO_SUPPORT
		//The main process is working as load thread.
		pthread_t install_t;
		int ret_pth_create = 0;
		if(TEST_IS_CONCURRENT_LOAD_INSTALL_KO_ON()) {
			ab_ko_fifo_init();
			ret_pth_create = pthread_create(&install_t, NULL, ab_ko_install_thread, NULL);
			if(ret_pth_create != 0) {
				bb_perror_msg("install thread creation is fail");
			}
		}
		# endif //CONCURRENT_LOAD_INSTALL_KO_SUPPORT
		do {
			if(flag) {
				char *pc_ptr = strtok(*argv, " ");
				options = NULL;
				while((pc_ptr = strtok(NULL, " ")) != NULL) {
					char *s = options;
					options = xasprintf("%s \"%s\"", s ? s : "", pc_ptr);
					free(s);
				}
			}
			#if CONCURRENT_LOAD_INSTALL_KO_SUPPORT
			ab_ko_dbg_load_start(*argv);
			#endif //CONCURRENT_LOAD_INSTALL_KO_SUPPORT

			len = MAXINT(ssize_t);
			map = xmalloc_open_zipped_read_close(*argv, &len);

			#if CONCURRENT_LOAD_INSTALL_KO_SUPPORT
			ab_ko_dbg_load_end();
			#endif //CONCURRENT_LOAD_INSTALL_KO_SUPPORT

			if (!map)
			{
				bb_perror_msg("can't read '%s'", *argv);
				continue;
			}

			# if CONCURRENT_LOAD_INSTALL_KO_SUPPORT
			{
				#  if CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN
				if (options != NULL)
					ab_ko_log("load %s addr %llx, len %d, opt %s,%llx\n", *argv, map, len, options, options);
				else
					ab_ko_log("load %s addr %llx, len %d\n", *argv, map, len);
				#  endif //CONCURRENT_LOAD_AND_INSTALL_KO_DBG_EN
			}
			if (TEST_IS_CONCURRENT_LOAD_INSTALL_KO_ON()) {
				if (*(argv+1) == NULL)
					ab_ko_fifo_push_node(1, map, len, options, *argv);
				else
					ab_ko_fifo_push_node(0, map, len, options, *argv);
			} else {
			# endif //CONCURRENT_LOAD_INSTALL_KO_SUPPORT

				if ( init_module(map, len,
					(IF_FEATURE_CMDLINE_MODULE_OPTIONS(options ? options : ) "")
					) != 0
				) {
					bb_error_msg("can't insert '%s': %s",
							*argv, moderror(errno));
					continue;
				}

			# if CONCURRENT_LOAD_INSTALL_KO_SUPPORT
			}
			# endif //CONCURRENT_LOAD_INSTALL_KO_SUPPORT
		} while (flag && *++argv);

		# if CONCURRENT_LOAD_INSTALL_KO_SUPPORT
		if(ab_concurrent_load_and_install_ko_flag) {
			pthread_join (install_t, NULL);
		}
		/*for (int i = 0; i<ai_ko_node_cnt_record_cnt; i++) {
			ab_ko_log("%d th : %d\n", i, ai_ko_node_cnt_record[i]);
		}*/
		ab_ko_dbg_dump_dbg_seq();
		# endif //CONCURRENT_LOAD_INSTALL_KO_SUPPORT

		#else
		len = MAXINT(ssize_t);
		map = xmalloc_open_zipped_read_close(*argv, &len);
		if (!map)
			bb_perror_msg_and_die("can't read '%s'", *argv);

		if (init_module(map, len,
			(IF_FEATURE_CMDLINE_MODULE_OPTIONS(options ? options : ) "")
			) != 0
		) {
			bb_error_msg_and_die("can't insert '%s': %s",
					*argv, moderror(errno));
		}
		#endif
		return EXIT_SUCCESS;
    }

	/* Try to load modprobe.dep.bb */
	if (!is_rmmod) {
		load_dep_bb();
	}

	/* Load/remove modules.
	 * Only rmmod/modprobe -r loops here, insmod/modprobe has only argv[0] */
	exitcode = EXIT_SUCCESS;
	do {
		exitcode |= process_module(*argv, options);
	} while (*++argv);

	if (ENABLE_FEATURE_CLEAN_UP) {
		IF_FEATURE_CMDLINE_MODULE_OPTIONS(free(options);)
	}
	return exitcode;
#endif /* MODPROBE || INSMOD || RMMOD */
}

#endif /* MOD_APPLET_CNT > 0 */
