You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
537 lines
15 KiB
537 lines
15 KiB
/*
|
|
* tcmodchain.c -- simple module system explorer frontend
|
|
* (C) 2006-2010 - Francesco Romani <fromani at gmail dot com>
|
|
*
|
|
* This file is part of transcode, a video stream processing tool.
|
|
*
|
|
* transcode 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.
|
|
*
|
|
* transcode 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#include <glob.h>
|
|
#include "tcstub.h"
|
|
|
|
#define EXE "tcmodchain"
|
|
|
|
/*************************************************************************/
|
|
|
|
|
|
void version(void)
|
|
{
|
|
printf("%s v%s (C) 2006-2010 Transcode Team\n", EXE, VERSION);
|
|
}
|
|
|
|
enum {
|
|
STATUS_DONE = -1, /* used internally */
|
|
STATUS_OK = 0,
|
|
STATUS_BAD_PARAM,
|
|
STATUS_MODULE_ERROR,
|
|
STATUS_MODULE_MISMATCH,
|
|
STATUS_GLOB_FAILED,
|
|
};
|
|
|
|
|
|
#define MAX_MODS (16)
|
|
|
|
typedef struct modrequest_ ModRequest;
|
|
struct modrequest_ {
|
|
char **rawdata; /* main reference */
|
|
|
|
const char *type; /* commodity */
|
|
const char *name; /* commodity */
|
|
|
|
TCModule module;
|
|
};
|
|
|
|
|
|
static void modrequest_init(ModRequest *modr);
|
|
static int modrequest_load(TCFactory factory,
|
|
ModRequest *modr, const char *str);
|
|
static int modrequest_scan(const char *modpath, const char *modstr,
|
|
glob_t *globbuf);
|
|
static int modrequest_fill(TCFactory factory, ModRequest *mods,
|
|
size_t maxmods, size_t *modnum, glob_t *globbuf);
|
|
#define modrequest_free(GLOBBUF) globfree((GLOBBUF))
|
|
static int modrequest_unload(TCFactory factory, ModRequest *modr);
|
|
|
|
/*************************************************************************/
|
|
|
|
enum {
|
|
TC_MODULE_DEMUXER = 1, /* future */
|
|
TC_MODULE_DECODER = 2, /* future */
|
|
TC_MODULE_FILTER = 4, /* future */
|
|
TC_MODULE_ENCODER = 8,
|
|
TC_MODULE_MUXER = 16,
|
|
|
|
TC_MODULE_FIXED = 1024,
|
|
TC_MODULE_TEMPLATE = 2048,
|
|
};
|
|
|
|
|
|
static uint32_t parse_modstr(const char *str)
|
|
{
|
|
uint32_t ret = 0;
|
|
if (str != NULL && strlen(str) > 0) {
|
|
struct {
|
|
const char *name;
|
|
int kind;
|
|
size_t off;
|
|
} tags[] = {
|
|
{ "encode:", TC_MODULE_ENCODER, strlen("encode:") },
|
|
{ "multiplex:", TC_MODULE_MUXER, strlen("multiplex:") },
|
|
{ NULL, 0, 0 },
|
|
};
|
|
int i = 0;
|
|
|
|
for (i = 0; tags[i].name != NULL; i++) {
|
|
if (strncmp(str, tags[i].name, tags[i].off) == 0) {
|
|
ret |= tags[i].kind;
|
|
str += tags[i].off;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* found something supported/making sense */
|
|
if (ret != 0 && (str != NULL && strlen(str) > 0)) {
|
|
if (!strcmp(str, "*")) {
|
|
ret |= TC_MODULE_TEMPLATE;
|
|
} else {
|
|
ret |= TC_MODULE_FIXED;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int parse_path(const char *fullpath, char *modstr, size_t buflen)
|
|
{
|
|
const char *pc = NULL;
|
|
char *pr = NULL;
|
|
|
|
if (!fullpath || !modstr || (buflen < TC_BUF_MIN)) {
|
|
return TC_ERROR;
|
|
}
|
|
|
|
pc = strrchr(fullpath, '/');
|
|
if (pc == NULL || strlen(pc + 1) > buflen) {
|
|
return TC_ERROR;
|
|
}
|
|
strlcpy(modstr, pc + 1, buflen);
|
|
|
|
pr = strrchr(modstr, '.');
|
|
if (pr == NULL) {
|
|
return TC_ERROR;
|
|
}
|
|
*pr = '\0';
|
|
|
|
pr = strchr(modstr, '_');
|
|
if (pr == NULL) {
|
|
return TC_ERROR;
|
|
}
|
|
*pr = ':';
|
|
|
|
return TC_OK;
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
|
|
static void modrequest_init(ModRequest *modr)
|
|
{
|
|
if (modr != NULL) {
|
|
modr->rawdata = NULL;
|
|
modr->type = NULL;
|
|
modr->name = NULL;
|
|
modr->module = NULL;
|
|
}
|
|
}
|
|
|
|
static int modrequest_load(TCFactory factory,
|
|
ModRequest *modr, const char *str)
|
|
{
|
|
size_t pieces = 0;
|
|
|
|
if (factory == NULL || modr == NULL || str == NULL) {
|
|
tc_log_warn(EXE, "wrong parameters for modrequest_load");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
modr->rawdata = tc_strsplit(str, ':', &pieces);
|
|
if (modr->rawdata == NULL || pieces != 2) {
|
|
tc_log_warn(EXE, "malformed module string: %s", str);
|
|
return TC_ERROR;
|
|
}
|
|
modr->type = modr->rawdata[0];
|
|
modr->name = modr->rawdata[1];
|
|
|
|
modr->module = tc_new_module(factory, modr->type, modr->name, TC_NONE);
|
|
if (modr->module == NULL) {
|
|
tc_log_warn(EXE, "failed creation of module: %s", str);
|
|
return TC_ERROR;
|
|
}
|
|
return TC_OK;
|
|
}
|
|
|
|
static int modrequest_unload(TCFactory factory, ModRequest *modr)
|
|
{
|
|
if (factory == NULL || modr == NULL) {
|
|
tc_log_warn(EXE, "wrong parameters for modrequest_load");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
tc_del_module(factory, modr->module);
|
|
tc_strfreev(modr->rawdata);
|
|
|
|
/* re-blank fields */
|
|
modrequest_init(modr);
|
|
|
|
return TC_OK;
|
|
}
|
|
|
|
static int modrequest_scan(const char *modpath, const char *modstr,
|
|
glob_t *globbuf)
|
|
{
|
|
char path_model[PATH_MAX];
|
|
char buf[TC_BUF_MIN];
|
|
const char *pc = NULL;
|
|
int err = 0;
|
|
|
|
pc = strchr(modstr, ':');
|
|
if (pc == NULL) {
|
|
return 1;
|
|
}
|
|
if ((pc - modstr + 1) > sizeof(buf)) {
|
|
return 2; /* XXX watch out here */
|
|
}
|
|
strlcpy(buf, modstr, pc - modstr + 1);
|
|
|
|
tc_snprintf(path_model, sizeof(path_model), "%s/%s_*.so", modpath, buf);
|
|
err = glob(path_model, GLOB_ERR, NULL, globbuf);
|
|
|
|
if (err) {
|
|
tc_log_error(EXE, "error while scanning for modules: %s",
|
|
(err == GLOB_NOSPACE) ?"can't get enough memory" :
|
|
(err == GLOB_ABORTED) ?"read error" :
|
|
/* GLOB_NOMATCH */ "no modules found");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int modrequest_fill(TCFactory factory, ModRequest *mods,
|
|
size_t maxmods, size_t *modnum, glob_t *globbuf)
|
|
{
|
|
char modstr[TC_BUF_MIN];
|
|
int i = 0, count = 0, lim = 0;
|
|
|
|
if (!factory || !mods || !globbuf) {
|
|
return TC_ERROR;
|
|
}
|
|
if (maxmods < globbuf->gl_pathc) {
|
|
tc_log_warn(EXE, "found %u candidate modules, but "
|
|
"only %u allowed (dropping remaining)",
|
|
(unsigned)globbuf->gl_pathc, (unsigned)maxmods);
|
|
}
|
|
lim = TC_MIN(maxmods, globbuf->gl_pathc);
|
|
|
|
for (i = 0; i < lim; i++) {
|
|
int ret;
|
|
|
|
ret = parse_path(globbuf->gl_pathv[i], modstr, TC_BUF_MIN);
|
|
if (ret != 0) {
|
|
tc_log_warn(EXE, "error while parsing '%s', skipping",
|
|
globbuf->gl_pathv[i]);
|
|
continue;
|
|
}
|
|
ret = modrequest_load(factory, &mods[i], modstr);
|
|
if (ret != 0) {
|
|
tc_log_warn(EXE, "error while loading '%s', skipping",
|
|
modstr);
|
|
continue;
|
|
}
|
|
count++;
|
|
}
|
|
|
|
if (modnum != NULL) {
|
|
*modnum += count;
|
|
}
|
|
|
|
return TC_OK;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
typedef struct cmdletdata_ CmdLetData;
|
|
struct cmdletdata_ {
|
|
ModRequest mods[MAX_MODS];
|
|
size_t modsnum;
|
|
|
|
const char *modpath;
|
|
TCFactory factory;
|
|
};
|
|
|
|
typedef int (*CmdLet)(CmdLetData *cdata, int argc, char **argv);
|
|
|
|
|
|
#define CLEANUP(CDATA) do { \
|
|
int i = 0; \
|
|
for (i = 0; i < (CDATA)->modsnum; i++) { \
|
|
modrequest_unload((CDATA)->factory, &((CDATA)->mods[i])); \
|
|
} \
|
|
(CDATA)->modsnum = 0; \
|
|
} while (0)
|
|
|
|
/* XXX */
|
|
static int check_module_pair(const ModRequest *head,
|
|
const ModRequest *tail,
|
|
const ModRequest *ref,
|
|
int verbose)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (head->module == NULL || tail->module == NULL) {
|
|
tc_log_error(EXE, "check_module_pair: missing module handle");
|
|
return -1;
|
|
}
|
|
|
|
ret = tc_module_info_match(TC_CODEC_ANY,
|
|
tc_module_get_info(head->module),
|
|
tc_module_get_info(tail->module));
|
|
if (verbose >= TC_DEBUG) {
|
|
tc_log_info(EXE, "%s:%s | %s:%s [%s]",
|
|
head->type, head->name, tail->type, tail->name,
|
|
(ret == 1) ?"OK" :"MISMATCH");
|
|
} else if (verbose >= TC_INFO) {
|
|
if (ret == 1) {
|
|
printf("%s\n", ref->name);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int cmdlet_usage(CmdLetData *unused, int ac, char **av)
|
|
{
|
|
version();
|
|
printf("Usage: %s [options] module [module... [module...]]\n",
|
|
EXE);
|
|
printf(" -L list mode (see manpage for details)\n");
|
|
printf(" -C check mode (see manpage for details)\n");
|
|
printf(" -d verbosity verbosity mode [1 == TC_INFO]\n");
|
|
printf(" -m PATH use PATH as module path\n");
|
|
printf(" -v show program version and exit\n");
|
|
printf(" -h show this help message\n");
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
static int cmdlet_check(CmdLetData *cdata, int ac, char **av)
|
|
{
|
|
int i = 0, matches = 0;
|
|
int status = STATUS_OK; /* let's be optimisc, once in lifetime */
|
|
|
|
if (ac < 2) {
|
|
tc_log_error(EXE, "not enough arguments for `check' mode");
|
|
return STATUS_BAD_PARAM;
|
|
}
|
|
|
|
for (i = 0; i < ac; i++) {
|
|
modrequest_load(cdata->factory, &cdata->mods[cdata->modsnum], av[i]);
|
|
cdata->modsnum++;
|
|
}
|
|
|
|
status = STATUS_OK;
|
|
if (cdata->modsnum >= 2) {
|
|
/* N modules, so N - 1 interfaces */
|
|
for (i = 0; i < cdata->modsnum - 1; i++) {
|
|
int ret = check_module_pair(&cdata->mods[i], &cdata->mods[i + 1],
|
|
&cdata->mods[i],
|
|
(verbose >= TC_INFO) ?TC_DEBUG :TC_QUIET);
|
|
if (ret != -1) { /* no error */
|
|
matches += ret;
|
|
}
|
|
|
|
}
|
|
if (matches < cdata->modsnum - 1) {
|
|
status = STATUS_MODULE_MISMATCH;
|
|
}
|
|
}
|
|
|
|
if (verbose) {
|
|
if (status == STATUS_OK) {
|
|
tc_log_info(EXE, "module chain OK");
|
|
} else {
|
|
tc_log_info(EXE, "module chain ILLEGAL");
|
|
}
|
|
}
|
|
|
|
CLEANUP(cdata);
|
|
return status;
|
|
}
|
|
|
|
static int cmdlet_list(CmdLetData *cdata, int ac, char **av)
|
|
{
|
|
glob_t globbuf;
|
|
int ret, i = 0, fid = 0 /* fixed id */, tid = 0; /* template id */
|
|
uint32_t modkind[2] = { 0, 0 };
|
|
ModRequest fixed;
|
|
|
|
if (ac != 2) {
|
|
tc_log_error(EXE, "wrong number of arguments for `list' mode");
|
|
return STATUS_BAD_PARAM;
|
|
}
|
|
/* we support only encoder|multiplexor, yet */
|
|
modkind[0] = parse_modstr(av[0]);
|
|
if (!(modkind[0] & TC_MODULE_ENCODER)) {
|
|
tc_log_error(EXE, "unknown/unsupported module '%s'", av[0]);
|
|
return STATUS_BAD_PARAM;
|
|
}
|
|
modkind[1] = parse_modstr(av[1]);
|
|
if (!(modkind[1] & TC_MODULE_MUXER)) {
|
|
tc_log_error(EXE, "unknown/unsupported module '%s'", av[1]);
|
|
return STATUS_BAD_PARAM;
|
|
}
|
|
|
|
if ((modkind[0] & TC_MODULE_FIXED)
|
|
&& (modkind[1] & TC_MODULE_TEMPLATE)) {
|
|
fid = 0;
|
|
tid = 1;
|
|
} else if ((modkind[0] & TC_MODULE_TEMPLATE)
|
|
&& (modkind[1] & TC_MODULE_FIXED)) {
|
|
fid = 1;
|
|
tid = 0;
|
|
} else {
|
|
tc_log_error(EXE, "incorrect arguments,"
|
|
" maybe you want to use `check' mode?");
|
|
return STATUS_BAD_PARAM;
|
|
}
|
|
|
|
modrequest_init(&fixed);
|
|
ret = modrequest_load(cdata->factory, &fixed, av[fid]);
|
|
if (ret != TC_OK) {
|
|
return STATUS_MODULE_ERROR;
|
|
}
|
|
|
|
ret = modrequest_scan(cdata->modpath, av[tid], &globbuf);
|
|
if (ret != 0) {
|
|
return STATUS_GLOB_FAILED;
|
|
}
|
|
ret = modrequest_fill(cdata->factory, cdata->mods, MAX_MODS,
|
|
&cdata->modsnum, &globbuf);
|
|
if (ret != TC_OK) {
|
|
return STATUS_MODULE_ERROR;
|
|
}
|
|
|
|
for (i = 0; i < cdata->modsnum; i++) {
|
|
const ModRequest *H = (tid == 0) ?(&cdata->mods[i]) :(&fixed);
|
|
const ModRequest *T = (tid == 1) ?(&cdata->mods[i]) :(&fixed);
|
|
check_module_pair(H, T, &(cdata->mods[i]),
|
|
(verbose == 0) ?TC_INFO :verbose);
|
|
}
|
|
|
|
CLEANUP(cdata);
|
|
modrequest_free(&globbuf);
|
|
ret = modrequest_unload(cdata->factory, &fixed);
|
|
if (ret != TC_OK) {
|
|
return STATUS_MODULE_ERROR;
|
|
}
|
|
return STATUS_OK;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
/* needed by filter modules */
|
|
TCVHandle tcv_handle = tcv_init();
|
|
CmdLet cmdlet = cmdlet_usage;
|
|
int ch, ret, status, i = 0;
|
|
|
|
CmdLetData cdata = {
|
|
.modpath = MOD_PATH,
|
|
.factory = NULL,
|
|
.modsnum = 0,
|
|
};
|
|
|
|
ac_init(AC_ALL);
|
|
tc_set_config_dir(NULL);
|
|
libtc_init(&argc, &argv);
|
|
|
|
filter[0].id = 0; /* to make gcc happy */
|
|
for (i = 0; i < MAX_MODS; i++) {
|
|
modrequest_init(&cdata.mods[i]);
|
|
}
|
|
|
|
while (1) {
|
|
ch = getopt(argc, argv, "LCd:?vhm:");
|
|
if (ch == -1) {
|
|
break;
|
|
}
|
|
|
|
switch (ch) {
|
|
case 'L':
|
|
cmdlet = cmdlet_list;
|
|
break;
|
|
case 'C':
|
|
cmdlet = cmdlet_check;
|
|
break;
|
|
case 'd':
|
|
if (optarg[0] == '-') {
|
|
cmdlet_usage(&cdata, argc, argv);
|
|
return STATUS_BAD_PARAM;
|
|
}
|
|
verbose = atoi(optarg);
|
|
break;
|
|
case 'm':
|
|
cdata.modpath = optarg;
|
|
break;
|
|
case 'v':
|
|
version();
|
|
return STATUS_OK;
|
|
case '?': /* fallthrough */
|
|
case 'h': /* fallthrough */
|
|
default:
|
|
cmdlet_usage(&cdata, argc, argv);
|
|
return STATUS_OK;
|
|
}
|
|
}
|
|
|
|
/* XXX: watch out here */
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
/*
|
|
* we can't distinguish from OMS and NMS modules at glance, so try
|
|
* first using new module system
|
|
*/
|
|
cdata.factory = tc_new_module_factory(cdata.modpath, verbose);
|
|
|
|
status = cmdlet(&cdata, argc, argv);
|
|
|
|
ret = tc_del_module_factory(cdata.factory); /* XXX: unchecked */
|
|
tcv_free(tcv_handle);
|
|
return status;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-file-style: "stroustrup"
|
|
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vim: expandtab shiftwidth=4:
|
|
*/
|